From d94a055c8c93db75b912577ed413fcd26561100f Mon Sep 17 00:00:00 2001 From: Eleiyas Date: Fri, 31 Jan 2025 15:33:26 +0000 Subject: [PATCH 1/5] .NET 8, Updated Packages, Custom WpfXnaControl DLL --- .gitignore | 3 +- SpineViewerWPF.sln | 4 +- .../.vs/SpineViewerWPF.csproj.dtbcache.json | 1 + SpineViewerWPF/App.config | 6 +- SpineViewerWPF/App.xaml.cs | 15 +- SpineViewerWPF/MainWindow.xaml.cs | 21 +- SpineViewerWPF/Properties/AssemblyInfo.cs | 55 - SpineViewerWPF/PublicFunction/BlendXna.cs | 9 +- SpineViewerWPF/PublicFunction/Common.cs | 45 +- SpineViewerWPF/PublicFunction/GlobalValue.cs | 11 +- .../PublicFunction/NewTextureLoader.cs | 10 +- SpineViewerWPF/PublicFunction/Player.cs | 31 +- .../PublicFunction/Player/IPlayer.cs | 6 - .../PublicFunction/Player/Player_2_1_08.cs | 16 +- .../PublicFunction/Player/Player_2_1_25.cs | 16 +- .../PublicFunction/Player/Player_3_1_07.cs | 16 +- .../PublicFunction/Player/Player_3_2_xx.cs | 14 +- .../PublicFunction/Player/Player_3_4_02.cs | 14 +- .../PublicFunction/Player/Player_3_5_51.cs | 16 +- .../PublicFunction/Player/Player_3_6_32.cs | 15 +- .../PublicFunction/Player/Player_3_6_39.cs | 15 +- .../PublicFunction/Player/Player_3_6_53.cs | 15 +- .../PublicFunction/Player/Player_3_7_94.cs | 25 +- .../PublicFunction/Player/Player_3_8_95.cs | 27 +- .../PublicFunction/Player/Player_4_0_31.cs | 21 +- .../PublicFunction/Player/Player_4_0_64.cs | 21 +- .../PublicFunction/Player/Player_4_1_00.cs | 21 +- .../PublicFunction/XnaLoader/Util.cs | 38 +- SpineViewerWPF/Resources/WpfXnaControl.dll | Bin 0 -> 13312 bytes .../spine-runtimes-2.1.08/Animation.cs | 1472 +++-- .../spine-runtimes-2.1.08/AnimationState.cs | 567 +- .../AnimationStateData.cs | 68 +- .../spine-runtimes-2.1.08/Atlas.cs | 474 +- .../Attachments/AtlasAttachmentLoader.cs | 129 +- .../Attachments/Attachment.cs | 26 +- .../Attachments/AttachmentLoader.cs | 24 +- .../Attachments/AttachmentType.cs | 10 +- .../Attachments/BoundingBoxAttachment.cs | 55 +- .../Attachments/MeshAttachment.cs | 147 +- .../Attachments/RegionAttachment.cs | 227 +- .../Attachments/SkinnedMeshAttachment.cs | 199 +- .../spine-runtimes-2.1.08/Bone.cs | 265 +- .../spine-runtimes-2.1.08/BoneData.cs | 64 +- .../spine-runtimes-2.1.08/Event.cs | 30 +- .../spine-runtimes-2.1.08/EventData.cs | 34 +- .../spine-runtimes-2.1.08/IkConstraint.cs | 228 +- .../spine-runtimes-2.1.08/IkConstraintData.cs | 44 +- .../spine-runtimes-2.1.08/Json.cs | 650 +- .../spine-runtimes-2.1.08/Skeleton.cs | 532 +- .../spine-runtimes-2.1.08/SkeletonBounds.cs | 386 +- .../spine-runtimes-2.1.08/SkeletonData.cs | 263 +- .../spine-runtimes-2.1.08/SkeletonJson.cs | 1216 ++-- .../spine-runtimes-2.1.08/Skin.cs | 126 +- .../spine-runtimes-2.1.08/Slot.cs | 128 +- .../spine-runtimes-2.1.08/SlotData.cs | 56 +- .../XnaLoader/MeshBatcher.cs | 246 +- .../XnaLoader/RegionBatcher.cs | 270 +- .../XnaLoader/SkeletonMeshRenderer.cs | 418 +- .../XnaLoader/SkeletonRegionRenderer.cs | 223 +- .../XnaLoader/XnaTextureLoader.cs | 35 +- .../spine-runtimes-2.1.25/Animation.cs | 1478 +++-- .../spine-runtimes-2.1.25/AnimationState.cs | 567 +- .../AnimationStateData.cs | 68 +- .../spine-runtimes-2.1.25/Atlas.cs | 474 +- .../Attachments/AtlasAttachmentLoader.cs | 149 +- .../Attachments/Attachment.cs | 26 +- .../Attachments/AttachmentLoader.cs | 24 +- .../Attachments/AttachmentType.cs | 10 +- .../Attachments/BoundingBoxAttachment.cs | 55 +- .../Attachments/MeshAttachment.cs | 147 +- .../Attachments/RegionAttachment.cs | 227 +- .../Attachments/SkinnedMeshAttachment.cs | 199 +- .../spine-runtimes-2.1.25/Bone.cs | 265 +- .../spine-runtimes-2.1.25/BoneData.cs | 64 +- .../spine-runtimes-2.1.25/Event.cs | 30 +- .../spine-runtimes-2.1.25/EventData.cs | 34 +- .../spine-runtimes-2.1.25/IkConstraint.cs | 232 +- .../spine-runtimes-2.1.25/IkConstraintData.cs | 44 +- .../spine-runtimes-2.1.25/Json.cs | 650 +- .../spine-runtimes-2.1.25/Skeleton.cs | 532 +- .../spine-runtimes-2.1.25/SkeletonBinary.cs | 1287 ++-- .../spine-runtimes-2.1.25/SkeletonBounds.cs | 386 +- .../spine-runtimes-2.1.25/SkeletonData.cs | 263 +- .../spine-runtimes-2.1.25/SkeletonJson.cs | 1246 ++-- .../spine-runtimes-2.1.25/Skin.cs | 126 +- .../spine-runtimes-2.1.25/Slot.cs | 128 +- .../spine-runtimes-2.1.25/SlotData.cs | 56 +- .../XnaLoader/MeshBatcher.cs | 246 +- .../XnaLoader/RegionBatcher.cs | 270 +- .../XnaLoader/SkeletonMeshRenderer.cs | 418 +- .../XnaLoader/SkeletonRegionRenderer.cs | 223 +- .../XnaLoader/XnaTextureLoader.cs | 35 +- .../spine-runtimes-3.1.07/Animation.cs | 1383 ++-- .../spine-runtimes-3.1.07/AnimationState.cs | 574 +- .../AnimationStateData.cs | 113 +- .../spine-runtimes-3.1.07/Atlas.cs | 498 +- .../Attachments/AtlasAttachmentLoader.cs | 149 +- .../Attachments/Attachment.cs | 26 +- .../Attachments/AttachmentLoader.cs | 24 +- .../Attachments/AttachmentType.cs | 10 +- .../Attachments/BoundingBoxAttachment.cs | 55 +- .../Attachments/IFfdAttachment.cs | 10 +- .../Attachments/MeshAttachment.cs | 193 +- .../Attachments/RegionAttachment.cs | 227 +- .../Attachments/WeightedMeshAttachment.cs | 248 +- .../spine-runtimes-3.1.07/BlendMode.cs | 10 +- .../spine-runtimes-3.1.07/Bone.cs | 444 +- .../spine-runtimes-3.1.07/BoneData.cs | 58 +- .../spine-runtimes-3.1.07/Event.cs | 34 +- .../spine-runtimes-3.1.07/EventData.cs | 34 +- .../spine-runtimes-3.1.07/ExposedList.cs | 1179 ++-- .../spine-runtimes-3.1.07/IUpdatable.cs | 12 +- .../spine-runtimes-3.1.07/IkConstraint.cs | 394 +- .../spine-runtimes-3.1.07/IkConstraintData.cs | 44 +- .../spine-runtimes-3.1.07/Json.cs | 978 +-- .../spine-runtimes-3.1.07/MathUtils.cs | 116 +- .../spine-runtimes-3.1.07/Skeleton.cs | 547 +- .../spine-runtimes-3.1.07/SkeletonBinary.cs | 1447 +++-- .../spine-runtimes-3.1.07/SkeletonBounds.cs | 389 +- .../spine-runtimes-3.1.07/SkeletonData.cs | 296 +- .../spine-runtimes-3.1.07/SkeletonJson.cs | 1394 ++-- .../spine-runtimes-3.1.07/Skin.cs | 148 +- .../spine-runtimes-3.1.07/Slot.cs | 141 +- .../spine-runtimes-3.1.07/SlotData.cs | 56 +- .../TransformConstraint.cs | 86 +- .../TransformConstraintData.cs | 45 +- .../XnaLoader/MeshBatcher.cs | 246 +- .../XnaLoader/RegionBatcher.cs | 270 +- .../XnaLoader/SkeletonMeshRenderer.cs | 421 +- .../XnaLoader/SkeletonRegionRenderer.cs | 209 +- .../XnaLoader/XnaTextureLoader.cs | 33 +- .../spine-runtimes-3.2.xx/Animation.cs | 1572 ++--- .../spine-runtimes-3.2.xx/AnimationState.cs | 574 +- .../AnimationStateData.cs | 113 +- .../spine-runtimes-3.2.xx/Atlas.cs | 498 +- .../Attachments/AtlasAttachmentLoader.cs | 149 +- .../Attachments/Attachment.cs | 26 +- .../Attachments/AttachmentLoader.cs | 24 +- .../Attachments/AttachmentType.cs | 10 +- .../Attachments/BoundingBoxAttachment.cs | 55 +- .../Attachments/IFfdAttachment.cs | 10 +- .../Attachments/MeshAttachment.cs | 193 +- .../Attachments/RegionAttachment.cs | 227 +- .../Attachments/WeightedMeshAttachment.cs | 248 +- .../spine-runtimes-3.2.xx/BlendMode.cs | 10 +- .../spine-runtimes-3.2.xx/Bone.cs | 450 +- .../spine-runtimes-3.2.xx/BoneData.cs | 62 +- .../spine-runtimes-3.2.xx/Event.cs | 34 +- .../spine-runtimes-3.2.xx/EventData.cs | 34 +- .../spine-runtimes-3.2.xx/ExposedList.cs | 1179 ++-- .../spine-runtimes-3.2.xx/IUpdatable.cs | 12 +- .../spine-runtimes-3.2.xx/IkConstraint.cs | 379 +- .../spine-runtimes-3.2.xx/IkConstraintData.cs | 44 +- .../spine-runtimes-3.2.xx/Json.cs | 978 +-- .../spine-runtimes-3.2.xx/MathUtils.cs | 118 +- .../spine-runtimes-3.2.xx/Skeleton.cs | 549 +- .../spine-runtimes-3.2.xx/SkeletonBinary.cs | 1499 +++-- .../spine-runtimes-3.2.xx/SkeletonBounds.cs | 389 +- .../spine-runtimes-3.2.xx/SkeletonData.cs | 296 +- .../spine-runtimes-3.2.xx/SkeletonJson.cs | 1465 +++-- .../spine-runtimes-3.2.xx/Skin.cs | 148 +- .../spine-runtimes-3.2.xx/Slot.cs | 141 +- .../spine-runtimes-3.2.xx/SlotData.cs | 56 +- .../TransformConstraint.cs | 193 +- .../TransformConstraintData.cs | 59 +- .../XnaLoader/MeshBatcher.cs | 246 +- .../XnaLoader/RegionBatcher.cs | 270 +- .../XnaLoader/SkeletonMeshRenderer.cs | 421 +- .../XnaLoader/SkeletonRegionRenderer.cs | 209 +- .../XnaLoader/XnaTextureLoader.cs | 33 +- .../spine-runtimes-3.4.02/Animation.cs | 1793 +++--- .../spine-runtimes-3.4.02/AnimationState.cs | 582 +- .../AnimationStateData.cs | 115 +- .../spine-runtimes-3.4.02/Atlas.cs | 498 +- .../Attachments/AtlasAttachmentLoader.cs | 119 +- .../Attachments/Attachment.cs | 26 +- .../Attachments/AttachmentLoader.cs | 24 +- .../Attachments/AttachmentType.cs | 10 +- .../Attachments/BoundingBoxAttachment.cs | 19 +- .../Attachments/MeshAttachment.cs | 167 +- .../Attachments/PathAttachment.cs | 28 +- .../Attachments/RegionAttachment.cs | 229 +- .../Attachments/VertexAttachment.cs | 173 +- .../spine-runtimes-3.4.02/BlendMode.cs | 10 +- .../spine-runtimes-3.4.02/Bone.cs | 549 +- .../spine-runtimes-3.4.02/BoneData.cs | 72 +- .../spine-runtimes-3.4.02/Event.cs | 36 +- .../spine-runtimes-3.4.02/EventData.cs | 34 +- .../spine-runtimes-3.4.02/ExposedList.cs | 1192 ++-- .../spine-runtimes-3.4.02/IUpdatable.cs | 12 +- .../spine-runtimes-3.4.02/IkConstraint.cs | 415 +- .../spine-runtimes-3.4.02/IkConstraintData.cs | 44 +- .../spine-runtimes-3.4.02/Json.cs | 978 +-- .../spine-runtimes-3.4.02/MathUtils.cs | 133 +- .../spine-runtimes-3.4.02/PathConstraint.cs | 736 ++- .../PathConstraintData.cs | 78 +- .../spine-runtimes-3.4.02/Skeleton.cs | 905 +-- .../spine-runtimes-3.4.02/SkeletonBinary.cs | 1577 ++--- .../spine-runtimes-3.4.02/SkeletonBounds.cs | 386 +- .../spine-runtimes-3.4.02/SkeletonData.cs | 346 +- .../spine-runtimes-3.4.02/SkeletonJson.cs | 1601 ++--- .../spine-runtimes-3.4.02/Skin.cs | 150 +- .../spine-runtimes-3.4.02/Slot.cs | 115 +- .../spine-runtimes-3.4.02/SlotData.cs | 64 +- .../TransformConstraint.cs | 183 +- .../TransformConstraintData.cs | 60 +- .../XnaLoader/MeshBatcher.cs | 246 +- .../XnaLoader/RegionBatcher.cs | 270 +- .../XnaLoader/SkeletonMeshRenderer.cs | 341 +- .../XnaLoader/SkeletonRegionRenderer.cs | 209 +- .../XnaLoader/XnaTextureLoader.cs | 33 +- .../spine-runtimes-3.5.51/Animation.cs | 2406 +++---- .../spine-runtimes-3.5.51/AnimationState.cs | 2047 +++--- .../AnimationStateData.cs | 126 +- .../spine-runtimes-3.5.51/Atlas.cs | 498 +- .../Attachments/AtlasAttachmentLoader.cs | 119 +- .../Attachments/Attachment.cs | 26 +- .../Attachments/AttachmentLoader.cs | 24 +- .../Attachments/AttachmentType.cs | 10 +- .../Attachments/BoundingBoxAttachment.cs | 19 +- .../Attachments/MeshAttachment.cs | 167 +- .../Attachments/PathAttachment.cs | 28 +- .../Attachments/RegionAttachment.cs | 227 +- .../Attachments/VertexAttachment.cs | 179 +- .../spine-runtimes-3.5.51/BlendMode.cs | 10 +- .../spine-runtimes-3.5.51/Bone.cs | 592 +- .../spine-runtimes-3.5.51/BoneData.cs | 95 +- .../spine-runtimes-3.5.51/Event.cs | 46 +- .../spine-runtimes-3.5.51/EventData.cs | 34 +- .../spine-runtimes-3.5.51/ExposedList.cs | 1216 ++-- .../spine-runtimes-3.5.51/IConstraint.cs | 10 +- .../spine-runtimes-3.5.51/IUpdatable.cs | 12 +- .../spine-runtimes-3.5.51/IkConstraint.cs | 423 +- .../spine-runtimes-3.5.51/IkConstraintData.cs | 48 +- .../spine-runtimes-3.5.51/Json.cs | 978 +-- .../spine-runtimes-3.5.51/MathUtils.cs | 133 +- .../spine-runtimes-3.5.51/PathConstraint.cs | 759 ++- .../PathConstraintData.cs | 89 +- .../spine-runtimes-3.5.51/Skeleton.cs | 955 +-- .../spine-runtimes-3.5.51/SkeletonBinary.cs | 1652 ++--- .../spine-runtimes-3.5.51/SkeletonBounds.cs | 403 +- .../spine-runtimes-3.5.51/SkeletonData.cs | 360 +- .../spine-runtimes-3.5.51/SkeletonJson.cs | 1611 ++--- .../spine-runtimes-3.5.51/Skin.cs | 168 +- .../spine-runtimes-3.5.51/Slot.cs | 115 +- .../spine-runtimes-3.5.51/SlotData.cs | 64 +- .../TransformConstraint.cs | 203 +- .../TransformConstraintData.cs | 64 +- .../XnaLoader/MeshBatcher.cs | 246 +- .../XnaLoader/RegionBatcher.cs | 270 +- .../XnaLoader/SkeletonMeshRenderer.cs | 341 +- .../XnaLoader/SkeletonRegionRenderer.cs | 209 +- .../XnaLoader/XnaTextureLoader.cs | 33 +- .../spine-runtimes-3.6.32/Animation.cs | 2912 +++++---- .../spine-runtimes-3.6.32/AnimationState.cs | 2164 ++++--- .../AnimationStateData.cs | 148 +- .../spine-runtimes-3.6.32/Atlas.cs | 498 +- .../Attachments/AtlasAttachmentLoader.cs | 141 +- .../Attachments/Attachment.cs | 26 +- .../Attachments/AttachmentLoader.cs | 28 +- .../Attachments/AttachmentType.cs | 10 +- .../Attachments/BoundingBoxAttachment.cs | 19 +- .../Attachments/ClippingAttachment.cs | 11 +- .../Attachments/MeshAttachment.cs | 171 +- .../Attachments/PathAttachment.cs | 28 +- .../Attachments/PointAttachment.cs | 57 +- .../Attachments/RegionAttachment.cs | 269 +- .../Attachments/VertexAttachment.cs | 197 +- .../spine-runtimes-3.6.32/BlendMode.cs | 10 +- .../spine-runtimes-3.6.32/Bone.cs | 746 ++- .../spine-runtimes-3.6.32/BoneData.cs | 139 +- .../spine-runtimes-3.6.32/Event.cs | 50 +- .../spine-runtimes-3.6.32/EventData.cs | 38 +- .../spine-runtimes-3.6.32/ExposedList.cs | 1216 ++-- .../spine-runtimes-3.6.32/IConstraint.cs | 16 +- .../spine-runtimes-3.6.32/IUpdatable.cs | 10 +- .../spine-runtimes-3.6.32/IkConstraint.cs | 402 +- .../spine-runtimes-3.6.32/IkConstraintData.cs | 89 +- .../spine-runtimes-3.6.32/Json.cs | 978 +-- .../spine-runtimes-3.6.32/MathUtils.cs | 133 +- .../spine-runtimes-3.6.32/PathConstraint.cs | 759 ++- .../PathConstraintData.cs | 89 +- .../spine-runtimes-3.6.32/Skeleton.cs | 1077 ++-- .../spine-runtimes-3.6.32/SkeletonBinary.cs | 1781 ++--- .../spine-runtimes-3.6.32/SkeletonBounds.cs | 429 +- .../spine-runtimes-3.6.32/SkeletonClipping.cs | 470 +- .../spine-runtimes-3.6.32/SkeletonData.cs | 402 +- .../spine-runtimes-3.6.32/SkeletonJson.cs | 1715 ++--- .../spine-runtimes-3.6.32/Skin.cs | 172 +- .../spine-runtimes-3.6.32/Slot.cs | 127 +- .../spine-runtimes-3.6.32/SlotData.cs | 76 +- .../TransformConstraint.cs | 533 +- .../TransformConstraintData.cs | 70 +- .../spine-runtimes-3.6.32/Triangulator.cs | 521 +- .../XnaLoader/MeshBatcher.cs | 313 +- .../XnaLoader/SkeletonRenderer.cs | 336 +- .../XnaLoader/XnaTextureLoader.cs | 33 +- .../spine-runtimes-3.6.39/Animation.cs | 2950 +++++---- .../spine-runtimes-3.6.39/AnimationState.cs | 2166 ++++--- .../AnimationStateData.cs | 148 +- .../spine-runtimes-3.6.39/Atlas.cs | 518 +- .../Attachments/AtlasAttachmentLoader.cs | 141 +- .../Attachments/Attachment.cs | 26 +- .../Attachments/AttachmentLoader.cs | 28 +- .../Attachments/AttachmentType.cs | 10 +- .../Attachments/BoundingBoxAttachment.cs | 19 +- .../Attachments/ClippingAttachment.cs | 11 +- .../Attachments/MeshAttachment.cs | 171 +- .../Attachments/PathAttachment.cs | 28 +- .../Attachments/PointAttachment.cs | 57 +- .../Attachments/RegionAttachment.cs | 269 +- .../Attachments/VertexAttachment.cs | 197 +- .../spine-runtimes-3.6.39/BlendMode.cs | 10 +- .../spine-runtimes-3.6.39/Bone.cs | 746 ++- .../spine-runtimes-3.6.39/BoneData.cs | 139 +- .../spine-runtimes-3.6.39/Event.cs | 50 +- .../spine-runtimes-3.6.39/EventData.cs | 38 +- .../spine-runtimes-3.6.39/ExposedList.cs | 1247 ++-- .../spine-runtimes-3.6.39/IConstraint.cs | 16 +- .../spine-runtimes-3.6.39/IUpdatable.cs | 10 +- .../spine-runtimes-3.6.39/IkConstraint.cs | 402 +- .../spine-runtimes-3.6.39/IkConstraintData.cs | 89 +- .../spine-runtimes-3.6.39/Json.cs | 978 +-- .../spine-runtimes-3.6.39/MathUtils.cs | 133 +- .../spine-runtimes-3.6.39/PathConstraint.cs | 763 ++- .../PathConstraintData.cs | 89 +- .../spine-runtimes-3.6.39/Skeleton.cs | 1081 ++-- .../spine-runtimes-3.6.39/SkeletonBinary.cs | 1781 ++--- .../spine-runtimes-3.6.39/SkeletonBounds.cs | 429 +- .../spine-runtimes-3.6.39/SkeletonClipping.cs | 474 +- .../spine-runtimes-3.6.39/SkeletonData.cs | 402 +- .../spine-runtimes-3.6.39/SkeletonJson.cs | 1730 ++--- .../spine-runtimes-3.6.39/Skin.cs | 172 +- .../spine-runtimes-3.6.39/Slot.cs | 127 +- .../spine-runtimes-3.6.39/SlotData.cs | 76 +- .../TransformConstraint.cs | 533 +- .../TransformConstraintData.cs | 70 +- .../spine-runtimes-3.6.39/Triangulator.cs | 521 +- .../XnaLoader/MeshBatcher.cs | 313 +- .../XnaLoader/ShapeRenderer.cs | 285 +- .../XnaLoader/SkeletonRenderer.cs | 336 +- .../XnaLoader/XnaTextureLoader.cs | 33 +- .../spine-runtimes-3.6.53/Animation.cs | 2958 +++++---- .../spine-runtimes-3.6.53/AnimationState.cs | 2188 ++++--- .../AnimationStateData.cs | 148 +- .../spine-runtimes-3.6.53/Atlas.cs | 518 +- .../Attachments/AtlasAttachmentLoader.cs | 141 +- .../Attachments/Attachment.cs | 33 +- .../Attachments/AttachmentLoader.cs | 28 +- .../Attachments/AttachmentType.cs | 10 +- .../Attachments/BoundingBoxAttachment.cs | 19 +- .../Attachments/ClippingAttachment.cs | 11 +- .../Attachments/MeshAttachment.cs | 171 +- .../Attachments/PathAttachment.cs | 28 +- .../Attachments/PointAttachment.cs | 57 +- .../Attachments/RegionAttachment.cs | 286 +- .../Attachments/VertexAttachment.cs | 197 +- .../spine-runtimes-3.6.53/BlendMode.cs | 10 +- .../spine-runtimes-3.6.53/Bone.cs | 738 ++- .../spine-runtimes-3.6.53/BoneData.cs | 139 +- .../spine-runtimes-3.6.53/Event.cs | 50 +- .../spine-runtimes-3.6.53/EventData.cs | 38 +- .../spine-runtimes-3.6.53/ExposedList.cs | 1268 ++-- .../spine-runtimes-3.6.53/IConstraint.cs | 16 +- .../spine-runtimes-3.6.53/IUpdatable.cs | 10 +- .../spine-runtimes-3.6.53/IkConstraint.cs | 402 +- .../spine-runtimes-3.6.53/IkConstraintData.cs | 89 +- .../spine-runtimes-3.6.53/Json.cs | 978 +-- .../spine-runtimes-3.6.53/MathUtils.cs | 133 +- .../spine-runtimes-3.6.53/PathConstraint.cs | 780 +-- .../PathConstraintData.cs | 89 +- .../spine-runtimes-3.6.53/Skeleton.cs | 1091 ++-- .../spine-runtimes-3.6.53/SkeletonBinary.cs | 1781 ++--- .../spine-runtimes-3.6.53/SkeletonBounds.cs | 429 +- .../spine-runtimes-3.6.53/SkeletonClipping.cs | 474 +- .../spine-runtimes-3.6.53/SkeletonData.cs | 402 +- .../spine-runtimes-3.6.53/SkeletonJson.cs | 1728 ++--- .../spine-runtimes-3.6.53/Skin.cs | 172 +- .../spine-runtimes-3.6.53/Slot.cs | 127 +- .../spine-runtimes-3.6.53/SlotData.cs | 76 +- .../TransformConstraint.cs | 533 +- .../TransformConstraintData.cs | 70 +- .../spine-runtimes-3.6.53/Triangulator.cs | 521 +- .../XnaLoader/MeshBatcher.cs | 313 +- .../XnaLoader/ShapeRenderer.cs | 285 +- .../XnaLoader/SkeletonRenderer.cs | 311 +- .../XnaLoader/XnaTextureLoader.cs | 2 - .../spine-runtimes-3.7.94/Animation.cs | 3774 ++++++----- .../spine-runtimes-3.7.94/AnimationState.cs | 2589 ++++---- .../AnimationStateData.cs | 148 +- .../spine-runtimes-3.7.94/Atlas.cs | 544 +- .../Attachments/AtlasAttachmentLoader.cs | 141 +- .../Attachments/Attachment.cs | 33 +- .../Attachments/AttachmentLoader.cs | 28 +- .../Attachments/AttachmentType.cs | 10 +- .../Attachments/BoundingBoxAttachment.cs | 19 +- .../Attachments/ClippingAttachment.cs | 11 +- .../Attachments/MeshAttachment.cs | 193 +- .../Attachments/PathAttachment.cs | 28 +- .../Attachments/PointAttachment.cs | 57 +- .../Attachments/RegionAttachment.cs | 286 +- .../Attachments/VertexAttachment.cs | 197 +- .../spine-runtimes-3.7.94/BlendMode.cs | 10 +- .../spine-runtimes-3.7.94/Bone.cs | 693 +- .../spine-runtimes-3.7.94/BoneData.cs | 139 +- .../spine-runtimes-3.7.94/Event.cs | 58 +- .../spine-runtimes-3.7.94/EventData.cs | 44 +- .../spine-runtimes-3.7.94/ExposedList.cs | 1268 ++-- .../spine-runtimes-3.7.94/IConstraint.cs | 16 +- .../spine-runtimes-3.7.94/IUpdatable.cs | 10 +- .../spine-runtimes-3.7.94/IkConstraint.cs | 604 +- .../spine-runtimes-3.7.94/IkConstraintData.cs | 147 +- .../spine-runtimes-3.7.94/Json.cs | 978 +-- .../spine-runtimes-3.7.94/MathUtils.cs | 247 +- .../spine-runtimes-3.7.94/PathConstraint.cs | 886 +-- .../PathConstraintData.cs | 89 +- .../spine-runtimes-3.7.94/Skeleton.cs | 1212 ++-- .../spine-runtimes-3.7.94/SkeletonBinary.cs | 1821 +++--- .../spine-runtimes-3.7.94/SkeletonBounds.cs | 429 +- .../spine-runtimes-3.7.94/SkeletonClipping.cs | 562 +- .../spine-runtimes-3.7.94/SkeletonData.cs | 412 +- .../spine-runtimes-3.7.94/SkeletonJson.cs | 1765 ++--- .../spine-runtimes-3.7.94/Skin.cs | 211 +- .../spine-runtimes-3.7.94/Slot.cs | 316 +- .../spine-runtimes-3.7.94/SlotData.cs | 76 +- .../TransformConstraint.cs | 590 +- .../TransformConstraintData.cs | 70 +- .../spine-runtimes-3.7.94/Triangulator.cs | 521 +- .../XnaLoader/MeshBatcher.cs | 313 +- .../XnaLoader/ShapeRenderer.cs | 285 +- .../XnaLoader/SkeletonRenderer.cs | 324 +- .../XnaLoader/VertexEffect.cs | 118 +- .../XnaLoader/XnaTextureLoader.cs | 2 - .../spine-runtimes-3.8.95/Animation.cs | 3861 ++++++----- .../spine-runtimes-3.8.95/AnimationState.cs | 2728 ++++---- .../AnimationStateData.cs | 148 +- .../spine-runtimes-3.8.95/Atlas.cs | 560 +- .../Attachments/AtlasAttachmentLoader.cs | 143 +- .../Attachments/Attachment.cs | 37 +- .../Attachments/AttachmentLoader.cs | 28 +- .../Attachments/AttachmentType.cs | 10 +- .../Attachments/BoundingBoxAttachment.cs | 30 +- .../Attachments/ClippingAttachment.cs | 32 +- .../Attachments/MeshAttachment.cs | 405 +- .../Attachments/PathAttachment.cs | 47 +- .../Attachments/PointAttachment.cs | 72 +- .../Attachments/RegionAttachment.cs | 363 +- .../Attachments/VertexAttachment.cs | 249 +- .../spine-runtimes-3.8.95/BlendMode.cs | 10 +- .../spine-runtimes-3.8.95/Bone.cs | 699 +- .../spine-runtimes-3.8.95/BoneData.cs | 151 +- .../Collections/OrderedDictionary.cs | 1272 ++-- .../spine-runtimes-3.8.95/ConstraintData.cs | 48 +- .../spine-runtimes-3.8.95/Event.cs | 58 +- .../spine-runtimes-3.8.95/EventData.cs | 44 +- .../spine-runtimes-3.8.95/ExposedList.cs | 1303 ++-- .../spine-runtimes-3.8.95/IUpdatable.cs | 22 +- .../spine-runtimes-3.8.95/IkConstraint.cs | 660 +- .../spine-runtimes-3.8.95/IkConstraintData.cs | 128 +- .../spine-runtimes-3.8.95/Json.cs | 978 +-- .../spine-runtimes-3.8.95/MathUtils.cs | 190 +- .../spine-runtimes-3.8.95/PathConstraint.cs | 877 +-- .../PathConstraintData.cs | 70 +- .../spine-runtimes-3.8.95/Skeleton.cs | 1289 ++-- .../spine-runtimes-3.8.95/SkeletonBinary.cs | 2002 +++--- .../spine-runtimes-3.8.95/SkeletonBounds.cs | 431 +- .../spine-runtimes-3.8.95/SkeletonClipping.cs | 562 +- .../spine-runtimes-3.8.95/SkeletonData.cs | 416 +- .../spine-runtimes-3.8.95/SkeletonJson.cs | 1848 +++--- .../spine-runtimes-3.8.95/Skin.cs | 344 +- .../spine-runtimes-3.8.95/Slot.cs | 348 +- .../spine-runtimes-3.8.95/SlotData.cs | 84 +- .../TransformConstraint.cs | 594 +- .../TransformConstraintData.cs | 53 +- .../spine-runtimes-3.8.95/Triangulator.cs | 521 +- .../XnaLoader/MeshBatcher.cs | 338 +- .../XnaLoader/ShapeRenderer.cs | 285 +- .../XnaLoader/SkeletonRenderer.cs | 353 +- .../XnaLoader/VertexEffect.cs | 118 +- .../XnaLoader/XnaTextureLoader.cs | 2 - .../spine-runtimes-4.0.31/Animation.cs | 5502 ++++++++-------- .../spine-runtimes-4.0.31/AnimationState.cs | 2923 +++++---- .../AnimationStateData.cs | 148 +- .../spine-runtimes-4.0.31/Atlas.cs | 594 +- .../Attachments/AtlasAttachmentLoader.cs | 141 +- .../Attachments/Attachment.cs | 37 +- .../Attachments/AttachmentLoader.cs | 28 +- .../Attachments/AttachmentType.cs | 10 +- .../Attachments/BoundingBoxAttachment.cs | 30 +- .../Attachments/ClippingAttachment.cs | 32 +- .../Attachments/MeshAttachment.cs | 399 +- .../Attachments/PathAttachment.cs | 53 +- .../Attachments/PointAttachment.cs | 72 +- .../Attachments/RegionAttachment.cs | 334 +- .../Attachments/VertexAttachment.cs | 245 +- .../spine-runtimes-4.0.31/BlendMode.cs | 10 +- .../spine-runtimes-4.0.31/Bone.cs | 723 ++- .../spine-runtimes-4.0.31/BoneData.cs | 151 +- .../spine-runtimes-4.0.31/ConstraintData.cs | 51 +- .../spine-runtimes-4.0.31/Event.cs | 58 +- .../spine-runtimes-4.0.31/EventData.cs | 44 +- .../spine-runtimes-4.0.31/ExposedList.cs | 1295 ++-- .../spine-runtimes-4.0.31/IUpdatable.cs | 22 +- .../spine-runtimes-4.0.31/IkConstraint.cs | 682 +- .../spine-runtimes-4.0.31/IkConstraintData.cs | 136 +- .../spine-runtimes-4.0.31/Json.cs | 964 +-- .../spine-runtimes-4.0.31/MathUtils.cs | 180 +- .../spine-runtimes-4.0.31/PathConstraint.cs | 1024 +-- .../PathConstraintData.cs | 78 +- .../spine-runtimes-4.0.31/Skeleton.cs | 1250 ++-- .../spine-runtimes-4.0.31/SkeletonBinary.cs | 2451 +++---- .../spine-runtimes-4.0.31/SkeletonBounds.cs | 429 +- .../spine-runtimes-4.0.31/SkeletonClipping.cs | 554 +- .../spine-runtimes-4.0.31/SkeletonData.cs | 363 +- .../spine-runtimes-4.0.31/SkeletonJson.cs | 2431 +++---- .../spine-runtimes-4.0.31/SkeletonLoader.cs | 109 +- .../spine-runtimes-4.0.31/Skin.cs | 373 +- .../spine-runtimes-4.0.31/Slot.cs | 357 +- .../spine-runtimes-4.0.31/SlotData.cs | 84 +- .../TransformConstraint.cs | 587 +- .../TransformConstraintData.cs | 69 +- .../spine-runtimes-4.0.31/Triangulator.cs | 515 +- .../XnaLoader/MeshBatcher.cs | 340 +- .../XnaLoader/ShapeRenderer.cs | 283 +- .../XnaLoader/SkeletonRenderer.cs | 355 +- .../XnaLoader/VertexEffect.cs | 118 +- .../XnaLoader/XnaTextureLoader.cs | 4 +- .../spine-runtimes-4.0.64/Animation.cs | 5495 ++++++++-------- .../spine-runtimes-4.0.64/AnimationState.cs | 2976 +++++---- .../AnimationStateData.cs | 148 +- .../spine-runtimes-4.0.64/Atlas.cs | 594 +- .../Attachments/AtlasAttachmentLoader.cs | 141 +- .../Attachments/Attachment.cs | 37 +- .../Attachments/AttachmentLoader.cs | 28 +- .../Attachments/AttachmentType.cs | 10 +- .../Attachments/BoundingBoxAttachment.cs | 30 +- .../Attachments/ClippingAttachment.cs | 32 +- .../Attachments/MeshAttachment.cs | 399 +- .../Attachments/PathAttachment.cs | 53 +- .../Attachments/PointAttachment.cs | 72 +- .../Attachments/RegionAttachment.cs | 334 +- .../Attachments/VertexAttachment.cs | 245 +- .../spine-runtimes-4.0.64/BlendMode.cs | 10 +- .../spine-runtimes-4.0.64/Bone.cs | 723 ++- .../spine-runtimes-4.0.64/BoneData.cs | 151 +- .../spine-runtimes-4.0.64/ConstraintData.cs | 51 +- .../spine-runtimes-4.0.64/Event.cs | 58 +- .../spine-runtimes-4.0.64/EventData.cs | 44 +- .../spine-runtimes-4.0.64/ExposedList.cs | 1295 ++-- .../spine-runtimes-4.0.64/IUpdatable.cs | 22 +- .../spine-runtimes-4.0.64/IkConstraint.cs | 682 +- .../spine-runtimes-4.0.64/IkConstraintData.cs | 136 +- .../spine-runtimes-4.0.64/Json.cs | 964 +-- .../spine-runtimes-4.0.64/MathUtils.cs | 180 +- .../spine-runtimes-4.0.64/PathConstraint.cs | 1024 +-- .../PathConstraintData.cs | 78 +- .../spine-runtimes-4.0.64/Skeleton.cs | 1245 ++-- .../spine-runtimes-4.0.64/SkeletonBinary.cs | 2451 +++---- .../spine-runtimes-4.0.64/SkeletonBounds.cs | 429 +- .../spine-runtimes-4.0.64/SkeletonClipping.cs | 554 +- .../spine-runtimes-4.0.64/SkeletonData.cs | 363 +- .../spine-runtimes-4.0.64/SkeletonJson.cs | 2431 +++---- .../spine-runtimes-4.0.64/SkeletonLoader.cs | 109 +- .../spine-runtimes-4.0.64/Skin.cs | 373 +- .../spine-runtimes-4.0.64/Slot.cs | 357 +- .../spine-runtimes-4.0.64/SlotData.cs | 84 +- .../TransformConstraint.cs | 587 +- .../TransformConstraintData.cs | 69 +- .../spine-runtimes-4.0.64/Triangulator.cs | 515 +- .../XnaLoader/MeshBatcher.cs | 340 +- .../XnaLoader/ShapeRenderer.cs | 283 +- .../XnaLoader/SkeletonRenderer.cs | 355 +- .../XnaLoader/VertexEffect.cs | 118 +- .../XnaLoader/XnaTextureLoader.cs | 110 +- .../spine-runtimes-4.1.00/Animation.cs | 5725 +++++++++-------- .../spine-runtimes-4.1.00/AnimationState.cs | 3004 +++++---- .../AnimationStateData.cs | 148 +- .../spine-runtimes-4.1.00/Atlas.cs | 606 +- .../Attachments/AtlasAttachmentLoader.cs | 145 +- .../Attachments/Attachment.cs | 43 +- .../Attachments/AttachmentLoader.cs | 28 +- .../Attachments/AttachmentType.cs | 10 +- .../Attachments/BoundingBoxAttachment.cs | 35 +- .../Attachments/ClippingAttachment.cs | 37 +- .../Attachments/IHasTextureRegion.cs | 45 +- .../Attachments/MeshAttachment.cs | 400 +- .../Attachments/PathAttachment.cs | 58 +- .../Attachments/PointAttachment.cs | 79 +- .../Attachments/RegionAttachment.cs | 376 +- .../Attachments/Sequence.cs | 118 +- .../Attachments/VertexAttachment.cs | 254 +- .../spine-runtimes-4.1.00/BlendMode.cs | 10 +- .../spine-runtimes-4.1.00/Bone.cs | 754 +-- .../spine-runtimes-4.1.00/BoneData.cs | 151 +- .../spine-runtimes-4.1.00/ConstraintData.cs | 51 +- .../spine-runtimes-4.1.00/Event.cs | 58 +- .../spine-runtimes-4.1.00/EventData.cs | 44 +- .../spine-runtimes-4.1.00/ExposedList.cs | 1295 ++-- .../spine-runtimes-4.1.00/IUpdatable.cs | 22 +- .../spine-runtimes-4.1.00/IkConstraint.cs | 682 +- .../spine-runtimes-4.1.00/IkConstraintData.cs | 136 +- .../spine-runtimes-4.1.00/Json.cs | 964 +-- .../spine-runtimes-4.1.00/MathUtils.cs | 180 +- .../spine-runtimes-4.1.00/PathConstraint.cs | 1033 +-- .../PathConstraintData.cs | 78 +- .../spine-runtimes-4.1.00/Skeleton.cs | 1474 +++-- .../spine-runtimes-4.1.00/SkeletonBinary.cs | 2528 ++++---- .../spine-runtimes-4.1.00/SkeletonBounds.cs | 429 +- .../spine-runtimes-4.1.00/SkeletonClipping.cs | 554 +- .../spine-runtimes-4.1.00/SkeletonData.cs | 409 +- .../spine-runtimes-4.1.00/SkeletonJson.cs | 2533 ++++---- .../spine-runtimes-4.1.00/SkeletonLoader.cs | 109 +- .../spine-runtimes-4.1.00/Skin.cs | 373 +- .../spine-runtimes-4.1.00/Slot.cs | 354 +- .../spine-runtimes-4.1.00/SlotData.cs | 84 +- .../spine-runtimes-4.1.00/SpringConstraint.cs | 136 +- .../SpringConstraintData.cs | 53 +- .../spine-runtimes-4.1.00/TextureRegion.cs | 22 +- .../TransformConstraint.cs | 587 +- .../TransformConstraintData.cs | 69 +- .../spine-runtimes-4.1.00/Triangulator.cs | 515 +- .../XnaLoader/MeshBatcher.cs | 340 +- .../XnaLoader/ShapeRenderer.cs | 283 +- .../XnaLoader/SkeletonRenderer.cs | 355 +- .../XnaLoader/VertexEffect.cs | 118 +- .../XnaLoader/XnaTextureLoader.cs | 110 +- SpineViewerWPF/SpineViewerWPF.csproj | 799 +-- SpineViewerWPF/Views/UCPlayer.xaml.cs | 25 +- SpineViewerWPF/Windows/Open.xaml.cs | 34 +- SpineViewerWPF/packages.config | 12 - 630 files changed, 140420 insertions(+), 124196 deletions(-) create mode 100644 SpineViewerWPF/.vs/SpineViewerWPF.csproj.dtbcache.json delete mode 100644 SpineViewerWPF/Properties/AssemblyInfo.cs create mode 100644 SpineViewerWPF/Resources/WpfXnaControl.dll delete mode 100644 SpineViewerWPF/packages.config diff --git a/.gitignore b/.gitignore index 7ef3958..434c942 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /SpineViewerWPF/bin /SpineViewerWPF/obj -/packages +/.vs +/MigrationBackup \ No newline at end of file diff --git a/SpineViewerWPF.sln b/SpineViewerWPF.sln index cf62350..e68699b 100644 --- a/SpineViewerWPF.sln +++ b/SpineViewerWPF.sln @@ -1,9 +1,9 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.27004.2006 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpineViewerWPF", "SpineViewerWPF\SpineViewerWPF.csproj", "{28600FC6-6C22-4BEF-8865-AD159B5E8C5F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SpineViewerWPF", "SpineViewerWPF\SpineViewerWPF.csproj", "{28600FC6-6C22-4BEF-8865-AD159B5E8C5F}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/SpineViewerWPF/.vs/SpineViewerWPF.csproj.dtbcache.json b/SpineViewerWPF/.vs/SpineViewerWPF.csproj.dtbcache.json new file mode 100644 index 0000000..d06170e --- /dev/null +++ b/SpineViewerWPF/.vs/SpineViewerWPF.csproj.dtbcache.json @@ -0,0 +1 @@ +{"RootPath":"F:\\SpineViewerWPF\\SpineViewerWPF","ProjectFileName":"SpineViewerWPF.csproj","Configuration":"Debug|AnyCPU","FrameworkPath":"","Sources":[{"SourceFile":"PublicFunction\\BlendXna.cs"},{"SourceFile":"PublicFunction\\Common.cs"},{"SourceFile":"PublicFunction\\GlobalValue.cs"},{"SourceFile":"PublicFunction\\NewTextureLoader.cs"},{"SourceFile":"PublicFunction\\Player.cs"},{"SourceFile":"PublicFunction\\Player\\IPlayer.cs"},{"SourceFile":"PublicFunction\\Player\\Player_2_1_08.cs"},{"SourceFile":"PublicFunction\\Player\\Player_2_1_25.cs"},{"SourceFile":"PublicFunction\\Player\\Player_3_1_07.cs"},{"SourceFile":"PublicFunction\\Player\\Player_3_2_xx.cs"},{"SourceFile":"PublicFunction\\Player\\Player_3_4_02.cs"},{"SourceFile":"PublicFunction\\Player\\Player_3_5_51.cs"},{"SourceFile":"PublicFunction\\Player\\Player_3_6_32.cs"},{"SourceFile":"PublicFunction\\Player\\Player_3_6_39.cs"},{"SourceFile":"PublicFunction\\Player\\Player_3_6_53.cs"},{"SourceFile":"PublicFunction\\Player\\Player_3_7_94.cs"},{"SourceFile":"PublicFunction\\Player\\Player_4_1_00.cs"},{"SourceFile":"PublicFunction\\Player\\Player_4_0_64.cs"},{"SourceFile":"PublicFunction\\Player\\Player_4_0_31.cs"},{"SourceFile":"PublicFunction\\Player\\Player_3_8_95.cs"},{"SourceFile":"PublicFunction\\XnaLoader\\Util.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.08\\Animation.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.08\\AnimationState.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.08\\AnimationStateData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.08\\Atlas.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.08\\Attachments\\AtlasAttachmentLoader.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.08\\Attachments\\Attachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.08\\Attachments\\AttachmentLoader.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.08\\Attachments\\AttachmentType.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.08\\Attachments\\BoundingBoxAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.08\\Attachments\\MeshAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.08\\Attachments\\RegionAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.08\\Attachments\\SkinnedMeshAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.08\\Bone.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.08\\BoneData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.08\\Event.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.08\\EventData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.08\\IkConstraint.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.08\\IkConstraintData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.08\\Json.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.08\\Skeleton.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.08\\SkeletonBounds.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.08\\SkeletonData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.08\\SkeletonJson.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.08\\Skin.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.08\\Slot.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.08\\SlotData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.08\\XnaLoader\\MeshBatcher.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.08\\XnaLoader\\RegionBatcher.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.08\\XnaLoader\\SkeletonMeshRenderer.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.08\\XnaLoader\\SkeletonRegionRenderer.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.08\\XnaLoader\\XnaTextureLoader.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.25\\Animation.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.25\\AnimationState.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.25\\AnimationStateData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.25\\Atlas.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.25\\Attachments\\AtlasAttachmentLoader.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.25\\Attachments\\Attachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.25\\Attachments\\AttachmentLoader.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.25\\Attachments\\AttachmentType.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.25\\Attachments\\BoundingBoxAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.25\\Attachments\\MeshAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.25\\Attachments\\RegionAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.25\\Attachments\\SkinnedMeshAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.25\\Bone.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.25\\BoneData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.25\\Event.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.25\\EventData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.25\\IkConstraint.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.25\\IkConstraintData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.25\\Json.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.25\\Skeleton.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.25\\SkeletonBinary.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.25\\SkeletonBounds.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.25\\SkeletonData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.25\\SkeletonJson.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.25\\Skin.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.25\\Slot.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.25\\SlotData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.25\\XnaLoader\\MeshBatcher.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.25\\XnaLoader\\RegionBatcher.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.25\\XnaLoader\\SkeletonMeshRenderer.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.25\\XnaLoader\\SkeletonRegionRenderer.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-2.1.25\\XnaLoader\\XnaTextureLoader.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.1.07\\Animation.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.1.07\\AnimationState.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.1.07\\AnimationStateData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.1.07\\Atlas.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.1.07\\Attachments\\AtlasAttachmentLoader.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.1.07\\Attachments\\Attachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.1.07\\Attachments\\AttachmentLoader.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.1.07\\Attachments\\AttachmentType.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.1.07\\Attachments\\BoundingBoxAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.1.07\\Attachments\\IFfdAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.1.07\\Attachments\\MeshAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.1.07\\Attachments\\RegionAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.1.07\\Attachments\\WeightedMeshAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.1.07\\BlendMode.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.1.07\\Bone.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.1.07\\BoneData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.1.07\\Event.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.1.07\\EventData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.1.07\\ExposedList.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.1.07\\IkConstraint.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.1.07\\IkConstraintData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.1.07\\IUpdatable.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.1.07\\Json.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.1.07\\MathUtils.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.1.07\\Skeleton.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.1.07\\SkeletonBinary.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.1.07\\SkeletonBounds.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.1.07\\SkeletonData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.1.07\\SkeletonJson.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.1.07\\Skin.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.1.07\\Slot.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.1.07\\SlotData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.1.07\\TransformConstraint.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.1.07\\TransformConstraintData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.1.07\\XnaLoader\\MeshBatcher.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.1.07\\XnaLoader\\RegionBatcher.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.1.07\\XnaLoader\\SkeletonMeshRenderer.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.1.07\\XnaLoader\\SkeletonRegionRenderer.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.1.07\\XnaLoader\\XnaTextureLoader.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.2.xx\\Animation.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.2.xx\\AnimationState.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.2.xx\\AnimationStateData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.2.xx\\Atlas.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.2.xx\\Attachments\\AtlasAttachmentLoader.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.2.xx\\Attachments\\Attachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.2.xx\\Attachments\\AttachmentLoader.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.2.xx\\Attachments\\AttachmentType.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.2.xx\\Attachments\\BoundingBoxAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.2.xx\\Attachments\\IFfdAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.2.xx\\Attachments\\MeshAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.2.xx\\Attachments\\RegionAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.2.xx\\Attachments\\WeightedMeshAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.2.xx\\BlendMode.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.2.xx\\Bone.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.2.xx\\BoneData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.2.xx\\Event.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.2.xx\\EventData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.2.xx\\ExposedList.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.2.xx\\IkConstraint.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.2.xx\\IkConstraintData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.2.xx\\IUpdatable.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.2.xx\\Json.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.2.xx\\MathUtils.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.2.xx\\Skeleton.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.2.xx\\SkeletonBinary.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.2.xx\\SkeletonBounds.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.2.xx\\SkeletonData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.2.xx\\SkeletonJson.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.2.xx\\Skin.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.2.xx\\Slot.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.2.xx\\SlotData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.2.xx\\TransformConstraint.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.2.xx\\TransformConstraintData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.2.xx\\XnaLoader\\MeshBatcher.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.2.xx\\XnaLoader\\RegionBatcher.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.2.xx\\XnaLoader\\SkeletonMeshRenderer.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.2.xx\\XnaLoader\\SkeletonRegionRenderer.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.2.xx\\XnaLoader\\XnaTextureLoader.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.4.02\\Animation.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.4.02\\AnimationState.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.4.02\\AnimationStateData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.4.02\\Atlas.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.4.02\\Attachments\\AtlasAttachmentLoader.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.4.02\\Attachments\\Attachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.4.02\\Attachments\\AttachmentLoader.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.4.02\\Attachments\\AttachmentType.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.4.02\\Attachments\\BoundingBoxAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.4.02\\Attachments\\MeshAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.4.02\\Attachments\\PathAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.4.02\\Attachments\\RegionAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.4.02\\Attachments\\VertexAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.4.02\\BlendMode.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.4.02\\Bone.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.4.02\\BoneData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.4.02\\Event.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.4.02\\EventData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.4.02\\ExposedList.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.4.02\\IkConstraint.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.4.02\\IkConstraintData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.4.02\\IUpdatable.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.4.02\\Json.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.4.02\\MathUtils.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.4.02\\PathConstraint.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.4.02\\PathConstraintData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.4.02\\Skeleton.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.4.02\\SkeletonBinary.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.4.02\\SkeletonBounds.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.4.02\\SkeletonData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.4.02\\SkeletonJson.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.4.02\\Skin.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.4.02\\Slot.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.4.02\\SlotData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.4.02\\TransformConstraint.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.4.02\\TransformConstraintData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.4.02\\XnaLoader\\MeshBatcher.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.4.02\\XnaLoader\\RegionBatcher.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.4.02\\XnaLoader\\SkeletonMeshRenderer.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.4.02\\XnaLoader\\SkeletonRegionRenderer.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.4.02\\XnaLoader\\XnaTextureLoader.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.5.51\\Animation.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.5.51\\AnimationState.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.5.51\\AnimationStateData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.5.51\\Atlas.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.5.51\\Attachments\\AtlasAttachmentLoader.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.5.51\\Attachments\\Attachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.5.51\\Attachments\\AttachmentLoader.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.5.51\\Attachments\\AttachmentType.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.5.51\\Attachments\\BoundingBoxAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.5.51\\Attachments\\MeshAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.5.51\\Attachments\\PathAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.5.51\\Attachments\\RegionAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.5.51\\Attachments\\VertexAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.5.51\\BlendMode.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.5.51\\Bone.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.5.51\\BoneData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.5.51\\Event.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.5.51\\EventData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.5.51\\ExposedList.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.5.51\\IConstraint.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.5.51\\IkConstraint.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.5.51\\IkConstraintData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.5.51\\IUpdatable.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.5.51\\Json.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.5.51\\MathUtils.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.5.51\\PathConstraint.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.5.51\\PathConstraintData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.5.51\\Skeleton.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.5.51\\SkeletonBinary.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.5.51\\SkeletonBounds.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.5.51\\SkeletonData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.5.51\\SkeletonJson.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.5.51\\Skin.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.5.51\\Slot.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.5.51\\SlotData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.5.51\\TransformConstraint.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.5.51\\TransformConstraintData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.5.51\\XnaLoader\\MeshBatcher.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.5.51\\XnaLoader\\RegionBatcher.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.5.51\\XnaLoader\\SkeletonMeshRenderer.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.5.51\\XnaLoader\\SkeletonRegionRenderer.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.5.51\\XnaLoader\\XnaTextureLoader.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.32\\Animation.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.32\\AnimationState.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.32\\AnimationStateData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.32\\Atlas.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.32\\Attachments\\AtlasAttachmentLoader.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.32\\Attachments\\Attachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.32\\Attachments\\AttachmentLoader.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.32\\Attachments\\AttachmentType.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.32\\Attachments\\BoundingBoxAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.32\\Attachments\\ClippingAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.32\\Attachments\\MeshAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.32\\Attachments\\PathAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.32\\Attachments\\PointAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.32\\Attachments\\RegionAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.32\\Attachments\\VertexAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.32\\BlendMode.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.32\\Bone.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.32\\BoneData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.32\\Event.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.32\\EventData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.32\\ExposedList.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.32\\IConstraint.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.32\\IkConstraint.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.32\\IkConstraintData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.32\\IUpdatable.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.32\\Json.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.32\\MathUtils.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.32\\PathConstraint.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.32\\PathConstraintData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.32\\Skeleton.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.32\\SkeletonBinary.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.32\\SkeletonBounds.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.32\\SkeletonClipping.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.32\\SkeletonData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.32\\SkeletonJson.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.32\\Skin.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.32\\Slot.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.32\\SlotData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.32\\TransformConstraint.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.32\\TransformConstraintData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.32\\Triangulator.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.32\\XnaLoader\\MeshBatcher.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.32\\XnaLoader\\SkeletonRenderer.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.32\\XnaLoader\\XnaTextureLoader.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.39\\Animation.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.39\\AnimationState.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.39\\AnimationStateData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.39\\Atlas.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.39\\Attachments\\AtlasAttachmentLoader.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.39\\Attachments\\Attachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.39\\Attachments\\AttachmentLoader.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.39\\Attachments\\AttachmentType.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.39\\Attachments\\BoundingBoxAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.39\\Attachments\\ClippingAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.39\\Attachments\\MeshAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.39\\Attachments\\PathAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.39\\Attachments\\PointAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.39\\Attachments\\RegionAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.39\\Attachments\\VertexAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.39\\BlendMode.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.39\\Bone.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.39\\BoneData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.39\\Event.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.39\\EventData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.39\\ExposedList.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.39\\IConstraint.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.39\\IkConstraint.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.39\\IkConstraintData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.39\\IUpdatable.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.39\\Json.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.39\\MathUtils.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.39\\PathConstraint.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.39\\PathConstraintData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.39\\Skeleton.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.39\\SkeletonBinary.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.39\\SkeletonBounds.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.39\\SkeletonClipping.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.39\\SkeletonData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.39\\SkeletonJson.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.39\\Skin.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.39\\Slot.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.39\\SlotData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.39\\TransformConstraint.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.39\\TransformConstraintData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.39\\Triangulator.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.39\\XnaLoader\\MeshBatcher.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.39\\XnaLoader\\ShapeRenderer.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.39\\XnaLoader\\SkeletonRenderer.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.39\\XnaLoader\\XnaTextureLoader.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.53\\Animation.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.53\\AnimationState.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.53\\AnimationStateData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.53\\Atlas.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.53\\Attachments\\AtlasAttachmentLoader.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.53\\Attachments\\Attachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.53\\Attachments\\AttachmentLoader.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.53\\Attachments\\AttachmentType.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.53\\Attachments\\BoundingBoxAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.53\\Attachments\\ClippingAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.53\\Attachments\\MeshAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.53\\Attachments\\PathAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.53\\Attachments\\PointAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.53\\Attachments\\RegionAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.53\\Attachments\\VertexAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.53\\BlendMode.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.53\\Bone.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.53\\BoneData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.53\\Event.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.53\\EventData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.53\\ExposedList.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.53\\IConstraint.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.53\\IkConstraint.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.53\\IkConstraintData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.53\\IUpdatable.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.53\\Json.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.53\\MathUtils.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.53\\PathConstraint.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.53\\PathConstraintData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.53\\Skeleton.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.53\\SkeletonBinary.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.53\\SkeletonBounds.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.53\\SkeletonClipping.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.53\\SkeletonData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.53\\SkeletonJson.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.53\\Skin.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.53\\Slot.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.53\\SlotData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.53\\TransformConstraint.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.53\\TransformConstraintData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.53\\Triangulator.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.53\\XnaLoader\\MeshBatcher.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.53\\XnaLoader\\ShapeRenderer.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.53\\XnaLoader\\SkeletonRenderer.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.6.53\\XnaLoader\\XnaTextureLoader.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\Animation.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\AnimationState.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\AnimationStateData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\Atlas.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\Attachments\\AtlasAttachmentLoader.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\Attachments\\Attachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\Attachments\\AttachmentLoader.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\Attachments\\AttachmentType.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\Attachments\\BoundingBoxAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\Attachments\\ClippingAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\Attachments\\MeshAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\Attachments\\PathAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\Attachments\\PointAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\Attachments\\RegionAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\Attachments\\VertexAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\BlendMode.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\Bone.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\BoneData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\Event.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\EventData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\ExposedList.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\IConstraint.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\IkConstraint.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\IkConstraintData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\IUpdatable.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\Json.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\MathUtils.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\PathConstraint.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\PathConstraintData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\Skeleton.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\SkeletonBinary.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\SkeletonBounds.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\SkeletonClipping.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\SkeletonData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\SkeletonJson.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\Skin.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\Slot.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\SlotData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\TransformConstraint.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\TransformConstraintData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\Triangulator.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\XnaLoader\\MeshBatcher.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\XnaLoader\\ShapeRenderer.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\XnaLoader\\SkeletonRenderer.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\XnaLoader\\VertexEffect.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.7.94\\XnaLoader\\XnaTextureLoader.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\Animation.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\AnimationState.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\AnimationStateData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\Atlas.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\Attachments\\AtlasAttachmentLoader.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\Attachments\\Attachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\Attachments\\AttachmentLoader.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\Attachments\\AttachmentType.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\Attachments\\BoundingBoxAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\Attachments\\ClippingAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\Attachments\\MeshAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\Attachments\\PathAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\Attachments\\PointAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\Attachments\\RegionAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\Attachments\\VertexAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\BlendMode.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\Bone.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\BoneData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\Collections\\OrderedDictionary.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\ConstraintData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\Event.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\EventData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\ExposedList.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\IkConstraint.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\IkConstraintData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\IUpdatable.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\Json.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\MathUtils.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\PathConstraint.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\PathConstraintData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\Skeleton.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\SkeletonBinary.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\SkeletonBounds.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\SkeletonClipping.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\SkeletonData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\SkeletonJson.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\Skin.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\Slot.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\SlotData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\TransformConstraint.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\TransformConstraintData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\Triangulator.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\XnaLoader\\MeshBatcher.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\XnaLoader\\ShapeRenderer.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\XnaLoader\\SkeletonRenderer.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\XnaLoader\\VertexEffect.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-3.8.95\\XnaLoader\\XnaTextureLoader.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\Animation.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\AnimationState.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\AnimationStateData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\Atlas.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\Attachments\\AtlasAttachmentLoader.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\Attachments\\Attachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\Attachments\\AttachmentLoader.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\Attachments\\AttachmentType.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\Attachments\\BoundingBoxAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\Attachments\\ClippingAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\Attachments\\MeshAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\Attachments\\PathAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\Attachments\\PointAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\Attachments\\RegionAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\Attachments\\VertexAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\BlendMode.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\Bone.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\BoneData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\ConstraintData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\Event.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\EventData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\ExposedList.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\IkConstraint.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\IkConstraintData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\IUpdatable.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\Json.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\MathUtils.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\PathConstraint.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\PathConstraintData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\Skeleton.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\SkeletonBinary.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\SkeletonBounds.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\SkeletonClipping.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\SkeletonData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\SkeletonJson.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\SkeletonLoader.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\Skin.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\Slot.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\SlotData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\TransformConstraint.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\TransformConstraintData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\Triangulator.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\XnaLoader\\MeshBatcher.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\XnaLoader\\ShapeRenderer.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\XnaLoader\\SkeletonRenderer.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\XnaLoader\\VertexEffect.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.31\\XnaLoader\\XnaTextureLoader.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\Animation.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\AnimationState.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\AnimationStateData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\Atlas.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\Attachments\\AtlasAttachmentLoader.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\Attachments\\Attachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\Attachments\\AttachmentLoader.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\Attachments\\AttachmentType.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\Attachments\\BoundingBoxAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\Attachments\\ClippingAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\Attachments\\MeshAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\Attachments\\PathAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\Attachments\\PointAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\Attachments\\RegionAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\Attachments\\VertexAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\BlendMode.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\Bone.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\BoneData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\ConstraintData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\Event.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\EventData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\ExposedList.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\IkConstraint.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\IkConstraintData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\IUpdatable.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\Json.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\MathUtils.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\PathConstraint.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\PathConstraintData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\Skeleton.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\SkeletonBinary.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\SkeletonBounds.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\SkeletonClipping.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\SkeletonData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\SkeletonJson.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\SkeletonLoader.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\Skin.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\Slot.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\SlotData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\TransformConstraint.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\TransformConstraintData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\Triangulator.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\XnaLoader\\MeshBatcher.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\XnaLoader\\ShapeRenderer.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\XnaLoader\\SkeletonRenderer.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\XnaLoader\\VertexEffect.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.0.64\\XnaLoader\\XnaTextureLoader.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\Animation.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\AnimationState.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\AnimationStateData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\Atlas.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\Attachments\\AtlasAttachmentLoader.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\Attachments\\Attachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\Attachments\\AttachmentLoader.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\Attachments\\AttachmentType.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\Attachments\\BoundingBoxAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\Attachments\\ClippingAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\Attachments\\IHasTextureRegion.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\Attachments\\MeshAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\Attachments\\PathAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\Attachments\\PointAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\Attachments\\RegionAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\Attachments\\Sequence.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\Attachments\\VertexAttachment.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\BlendMode.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\Bone.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\BoneData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\ConstraintData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\Event.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\EventData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\ExposedList.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\IkConstraint.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\IkConstraintData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\IUpdatable.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\Json.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\MathUtils.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\PathConstraint.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\PathConstraintData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\Skeleton.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\SkeletonBinary.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\SkeletonBounds.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\SkeletonClipping.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\SkeletonData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\SkeletonJson.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\SkeletonLoader.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\Skin.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\Slot.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\SlotData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\SpringConstraint.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\SpringConstraintData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\TextureRegion.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\TransformConstraint.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\TransformConstraintData.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\Triangulator.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\XnaLoader\\MeshBatcher.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\XnaLoader\\ShapeRenderer.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\XnaLoader\\SkeletonRenderer.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\XnaLoader\\VertexEffect.cs"},{"SourceFile":"SpineLibrary\\spine-runtimes-4.1.00\\XnaLoader\\XnaTextureLoader.cs"},{"SourceFile":"Views\\UCPlayer.xaml.cs"},{"SourceFile":"Windows\\Open.xaml.cs"},{"SourceFile":"App.xaml.cs"},{"SourceFile":"MainWindow.xaml.cs"},{"SourceFile":"Properties\\AssemblyInfo.cs"},{"SourceFile":"Properties\\Resources.Designer.cs"},{"SourceFile":"Properties\\Settings.Designer.cs"},{"SourceFile":"obj\\Debug\\.NETFramework,Version=v4.7.2.AssemblyAttributes.cs"},{"SourceFile":"F:\\SpineViewerWPF\\SpineViewerWPF\\obj\\Debug\\MainWindow.g.cs"},{"SourceFile":"F:\\SpineViewerWPF\\SpineViewerWPF\\obj\\Debug\\Views\\UCPlayer.g.cs"},{"SourceFile":"F:\\SpineViewerWPF\\SpineViewerWPF\\obj\\Debug\\Windows\\Open.g.cs"},{"SourceFile":"F:\\SpineViewerWPF\\SpineViewerWPF\\obj\\Debug\\App.g.cs"}],"References":[{"Reference":"C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.7.2\\Microsoft.CSharp.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\WINDOWS\\Microsoft.Net\\assembly\\GAC_32\\Microsoft.Xna.Framework\\v4.0_4.0.0.0__842cf8be1de50553\\Microsoft.Xna.Framework.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\WINDOWS\\Microsoft.Net\\assembly\\GAC_32\\Microsoft.Xna.Framework.Game\\v4.0_4.0.0.0__842cf8be1de50553\\Microsoft.Xna.Framework.Game.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\WINDOWS\\Microsoft.Net\\assembly\\GAC_32\\Microsoft.Xna.Framework.Graphics\\v4.0_4.0.0.0__842cf8be1de50553\\Microsoft.Xna.Framework.Graphics.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.7.2\\mscorlib.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.7.2\\PresentationCore.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.7.2\\PresentationFramework.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\Common7\\IDE\\PublicAssemblies\\System.Buffers.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.7.2\\System.Core.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.7.2\\System.Data.DataSetExtensions.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.7.2\\System.Data.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.7.2\\System.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.7.2\\System.Drawing.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.7.2\\System.Net.Http.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.7.2\\System.Numerics.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\Common7\\IDE\\PublicAssemblies\\System.Numerics.Vectors.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.7.2\\System.Windows.Forms.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.7.2\\System.Xaml.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.7.2\\System.Xml.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.7.2\\System.Xml.Linq.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.7.2\\WindowsBase.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""}],"Analyzers":[],"Outputs":[{"OutputItemFullPath":"F:\\SpineViewerWPF\\SpineViewerWPF\\bin\\Debug\\SpineViewerWPF.exe","OutputItemRelativePath":"SpineViewerWPF.exe"},{"OutputItemFullPath":"","OutputItemRelativePath":""}],"CopyToOutputEntries":[]} \ No newline at end of file diff --git a/SpineViewerWPF/App.config b/SpineViewerWPF/App.config index 647c18b..611b730 100644 --- a/SpineViewerWPF/App.config +++ b/SpineViewerWPF/App.config @@ -16,7 +16,11 @@ - + + + + + diff --git a/SpineViewerWPF/App.xaml.cs b/SpineViewerWPF/App.xaml.cs index dea2748..11e88c3 100644 --- a/SpineViewerWPF/App.xaml.cs +++ b/SpineViewerWPF/App.xaml.cs @@ -1,11 +1,6 @@ -using Microsoft.Xna.Framework.Graphics; -using System; -using System.Collections.Generic; -using System.Configuration; -using System.Data; -using System.Linq; -using System.Threading.Tasks; +using System; using System.Windows; +using Microsoft.Xna.Framework.Graphics; using WpfXnaControl; namespace SpineViewerWPF @@ -30,9 +25,9 @@ public partial class App : Application public static int recordImageCount; public static double canvasWidth = SystemParameters.WorkArea.Width; public static double canvasHeight = SystemParameters.WorkArea.Height; - public static double mainWidth ; - public static double mainHeight ; + public static double mainWidth; + public static double mainHeight; - public static string tempDirPath = $"{App.rootDir}\\Temp\\"; + public static string tempDirPath = $"{App.rootDir}\\Temp\\"; } } diff --git a/SpineViewerWPF/MainWindow.xaml.cs b/SpineViewerWPF/MainWindow.xaml.cs index 94869c8..9d1620e 100644 --- a/SpineViewerWPF/MainWindow.xaml.cs +++ b/SpineViewerWPF/MainWindow.xaml.cs @@ -1,25 +1,14 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.IO; +using System.Text.RegularExpressions; using System.Windows; using System.Windows.Controls; using System.Windows.Data; -using System.Windows.Documents; using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Navigation; -using System.Windows.Shapes; -using SpineViewerWPF.Views; using Microsoft.Win32; -using WpfXnaControl; -using System.Text.RegularExpressions; -using System.IO; using Microsoft.Xna.Framework; +using SpineViewerWPF.Views; using SpineViewerWPF.Windows; -using Microsoft.Xna.Framework.Graphics; namespace SpineViewerWPF { @@ -41,7 +30,7 @@ public MainWindow() this.Title = $"SpineViewerWPF v{System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString()}"; MasterMain = this; dispatcherTimer.Tick += dispatcherTimer_Tick; - dispatcherTimer.Interval = new TimeSpan(0, 0, 0,0,100); + dispatcherTimer.Interval = new TimeSpan(0, 0, 0, 0, 100); dispatcherTimer.Start(); LoadSetting(); @@ -400,7 +389,7 @@ private void Window_GotFocus(object sender, RoutedEventArgs e) private void dispatcherTimer_Tick(object sender, EventArgs e) { - if (App.graphicsDevice != null && App.graphicsDevice.GraphicsDeviceStatus == Microsoft.Xna.Framework.Graphics.GraphicsDeviceStatus.NotReset) + if (App.graphicsDevice != null && App.graphicsDevice.GraphicsDeviceStatus == Microsoft.Xna.Framework.Graphics.GraphicsDeviceStatus.NotReset) { App.graphicsDevice.Reset(); } diff --git a/SpineViewerWPF/Properties/AssemblyInfo.cs b/SpineViewerWPF/Properties/AssemblyInfo.cs deleted file mode 100644 index 424ed66..0000000 --- a/SpineViewerWPF/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System.Reflection; -using System.Resources; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Windows; - -// 組件的一般資訊是由下列的屬性集控制。 -// 變更這些屬性的值即可修改組件的相關 -// 資訊。 -[assembly: AssemblyTitle("SpineViewerWPF")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("SpineViewerWPF")] -[assembly: AssemblyCopyright("Copyright © 2017")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// 將 ComVisible 設為 false 可對 COM 元件隱藏 -// 組件中的類型。若必須從 COM 存取此組件中的類型, -// 的類型,請在該類型上將 ComVisible 屬性設定為 true。 -[assembly: ComVisible(false)] - -//若要開始建置可當地語系化的應用程式,請在 -//.csproj 檔案中的 CultureYouAreCodingWith -//CultureYouAreCodingWith。例如,如果原始程式檔使用美式英文, -//請將 設為 en-US。然後取消註解下列 -//NeutralResourceLanguage 屬性。在下一行中更新 "en-US", -//以符合專案檔中的 UICulture 設定。 - -//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] - - -[assembly: ThemeInfo( - ResourceDictionaryLocation.None, //主題特定資源字典的位置 - //(在頁面中找不到時使用, - // 或應用程式資源字典中找不到資源時) - ResourceDictionaryLocation.SourceAssembly //泛型資源字典的位置 - //(在頁面中找不到時使用, - // 或是應用程式或任何主題特定資源字典中找不到資源時) -)] - - -// 組件的版本資訊由下列四個值所組成: -// -// 主要版本 -// 次要版本 -// 組建編號 -// 修訂編號 -// -// 您可以指定所有的值,或將組建編號或修訂編號設為預設值 -// 指定為預設值: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("2.4.0.0")] -[assembly: AssemblyFileVersion("2.4.0.0")] diff --git a/SpineViewerWPF/PublicFunction/BlendXna.cs b/SpineViewerWPF/PublicFunction/BlendXna.cs index 384da7b..a9f163d 100644 --- a/SpineViewerWPF/PublicFunction/BlendXna.cs +++ b/SpineViewerWPF/PublicFunction/BlendXna.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Graphics; public static class BlendXna { @@ -32,7 +27,7 @@ public static class BlendXna public const int GL_DST_COLOR = 0x0306; public const int GL_ONE_MINUS_DST_COLOR = 0x0307; public const int GL_SRC_ALPHA_SATURATE = 0x0308; - + public static Blend GetXNABlend(int glBlend) { diff --git a/SpineViewerWPF/PublicFunction/Common.cs b/SpineViewerWPF/PublicFunction/Common.cs index fb95268..efe4928 100644 --- a/SpineViewerWPF/PublicFunction/Common.cs +++ b/SpineViewerWPF/PublicFunction/Common.cs @@ -1,19 +1,16 @@ -using Microsoft.Win32; +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Windows; +using System.Windows.Media.Imaging; +using Microsoft.Win32; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Formats.Gif; -//using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; using SpineViewerWPF; -using System; -using System.Collections.Generic; -using System.Drawing; -using System.IO; -using System.Threading; -using System.Windows; -using System.Windows.Media.Imaging; public class Common { @@ -85,7 +82,7 @@ public static bool CheckSpineFile(string path) App.globalValues.SelectSpineFile = ""; return false; } - + } @@ -132,7 +129,7 @@ public static void SetBGXY(double MosX, double MosY, double oldX, double oldY) public static void RecodingEnd(float AnimationEnd) { - if(App.globalValues.ExportType == "Gif") + if (App.globalValues.ExportType == "Gif") { Thread t = new Thread(() => { @@ -188,7 +185,7 @@ public static void SaveToGif(List lms, float time = 0) { delay = (int)(time * 1000 * (App.globalValues.Speed / 30f) / lms.Count); } - + if (saveFileDialog.FileName != "") { @@ -251,12 +248,12 @@ public static void SaveToGif2(float time = 0) fileName += $"_{App.globalValues.SelectSkin}"; saveFileDialog.FileName = fileName; - if(saveFileDialog.ShowDialog() == false ) + if (saveFileDialog.ShowDialog() == false) { return; } - string[] pngList = Directory.GetFiles($"{App.rootDir}\\Temp\\", "*.png",SearchOption.TopDirectoryOnly); + string[] pngList = Directory.GetFiles($"{App.rootDir}\\Temp\\", "*.png", SearchOption.TopDirectoryOnly); int delay = 0; @@ -283,12 +280,12 @@ public static void SaveToGif2(float time = 0) img.Dispose(); } - + foreach (ImageFrame frame in gif.Frames) { GifFrameMetadata meta = frame.Metadata.GetGifMetadata(); // Get or create if none. - - meta.FrameDelay = delay/10; // Set to 30/100 of a second. + + meta.FrameDelay = delay / 10; // Set to 30/100 of a second. meta.DisposalMethod = GifDisposalMethod.RestoreToBackground; } @@ -297,7 +294,7 @@ public static void SaveToGif2(float time = 0) gif.Frames.RemoveFrame(0); using (FileStream fs = File.Create(saveFileDialog.FileName)) { - gif.SaveAsGif(fs,new GifEncoder() { ColorTableMode = GifColorTableMode.Global}); + gif.SaveAsGif(fs, new GifEncoder() { ColorTableMode = GifColorTableMode.Global }); } gif.Dispose(); @@ -324,7 +321,7 @@ public static BitmapSource SourceFrom(MemoryStream stream, int? size = null) bitmapImage.StreamSource = stream; bitmapImage.EndInit(); - bitmapImage.Freeze(); + bitmapImage.Freeze(); return bitmapImage; } @@ -377,12 +374,12 @@ public static void TakeRecodeScreenshot(GraphicsDevice _graphicsDevice) fileName += $"_{App.globalValues.SelectSkin}"; string exportDir = App.tempDirPath; - if(App.globalValues.ExportType == "Png Sequence") + if (App.globalValues.ExportType == "Png Sequence") { exportDir = App.globalValues.ExportPath + "\\"; } - using (FileStream fs = new FileStream($"{exportDir}{fileName}_{App.recordImageCount.ToString().PadLeft(7,'0')}.png" - ,FileMode.Create)) + using (FileStream fs = new FileStream($"{exportDir}{fileName}_{App.recordImageCount.ToString().PadLeft(7, '0')}.png" + , FileMode.Create)) { texture.SaveAsPng(fs, _graphicsDevice.PresentationParameters.BackBufferWidth , _graphicsDevice.PresentationParameters.BackBufferHeight); @@ -429,7 +426,7 @@ public static void ClearCacheFile() string[] fileList = Directory.GetFiles($"{App.rootDir}\\Temp\\", "*.*", SearchOption.AllDirectories); if (fileList.Length > 0) { - foreach(string path in fileList) + foreach (string path in fileList) { File.Delete(path); } diff --git a/SpineViewerWPF/PublicFunction/GlobalValue.cs b/SpineViewerWPF/PublicFunction/GlobalValue.cs index c7a3764..e4556ca 100644 --- a/SpineViewerWPF/PublicFunction/GlobalValue.cs +++ b/SpineViewerWPF/PublicFunction/GlobalValue.cs @@ -1,10 +1,7 @@ -using Microsoft.Xna.Framework.Graphics; -using System; +using System; using System.Collections.Generic; using System.ComponentModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Microsoft.Xna.Framework.Graphics; public class GlobalValue : INotifyPropertyChanged @@ -154,7 +151,7 @@ public double ViewScale { if (_ViewScale != value) { - _ViewScale = (double)Math.Round(value, 2); + _ViewScale = Math.Round(value, 2); OnPropertyChanged("ViewScale"); } } @@ -551,7 +548,7 @@ public List GifList { _GifList = value; } - + } } diff --git a/SpineViewerWPF/PublicFunction/NewTextureLoader.cs b/SpineViewerWPF/PublicFunction/NewTextureLoader.cs index 2e73b50..1f9a76e 100644 --- a/SpineViewerWPF/PublicFunction/NewTextureLoader.cs +++ b/SpineViewerWPF/PublicFunction/NewTextureLoader.cs @@ -1,12 +1,6 @@ -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; -using System; -using System.Collections.Generic; -using System.Drawing; +using System.Drawing; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Microsoft.Xna.Framework.Graphics; public class NewTextureLoader { diff --git a/SpineViewerWPF/PublicFunction/Player.cs b/SpineViewerWPF/PublicFunction/Player.cs index d83e6f3..a59cea7 100644 --- a/SpineViewerWPF/PublicFunction/Player.cs +++ b/SpineViewerWPF/PublicFunction/Player.cs @@ -1,13 +1,8 @@ -using Microsoft.Xna.Framework; +using System.Windows; +using System.Windows.Input; +using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using SpineViewerWPF; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Input; public class Player { @@ -37,18 +32,18 @@ public static void UserControl_SizeChanged(ref GraphicsDevice graphicsDevice) public static void Frame_MouseWheel(MouseWheelEventArgs e) { - if (e.Delta > 0) - { - App.globalValues.Scale += 0.02f; - } - else + if (e.Delta > 0) + { + App.globalValues.Scale += 0.02f; + } + else + { + if (App.globalValues.Scale > 0.04f) { - if (App.globalValues.Scale > 0.04f) - { - App.globalValues.Scale -= 0.02f; - } + App.globalValues.Scale -= 0.02f; } - + } + } public static void DrawBG(ref SpriteBatch spriteBatch) diff --git a/SpineViewerWPF/PublicFunction/Player/IPlayer.cs b/SpineViewerWPF/PublicFunction/Player/IPlayer.cs index 0b3ce43..4b36184 100644 --- a/SpineViewerWPF/PublicFunction/Player/IPlayer.cs +++ b/SpineViewerWPF/PublicFunction/Player/IPlayer.cs @@ -1,11 +1,5 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; -using Microsoft.Xna.Framework.Graphics; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; public interface IPlayer diff --git a/SpineViewerWPF/PublicFunction/Player/Player_2_1_08.cs b/SpineViewerWPF/PublicFunction/Player/Player_2_1_08.cs index 68e3272..16d7b45 100644 --- a/SpineViewerWPF/PublicFunction/Player/Player_2_1_08.cs +++ b/SpineViewerWPF/PublicFunction/Player/Player_2_1_08.cs @@ -1,13 +1,9 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Spine2_1_08; -using SpineViewerWPF; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; -using Microsoft.Xna.Framework.Graphics; +using Spine2_1_08; +using SpineViewerWPF; public class Player_2_1_08 : IPlayer @@ -107,7 +103,7 @@ public void Update(GameTime gameTime) App.graphicsDevice.Clear(Color.Transparent); Player.DrawBG(ref App.spriteBatch); - App.globalValues.TimeScale = (float)App.globalValues.Speed / 30f; + App.globalValues.TimeScale = App.globalValues.Speed / 30f; state.Update((float)gameTime.ElapsedGameTime.TotalMilliseconds / 1000f); @@ -141,12 +137,12 @@ public void Update(GameTime gameTime) public void Draw() { - + if (state != null) { TrackEntry entry = state.GetCurrent(0); - float speed = (float)App.globalValues.Speed / 30f; + float speed = App.globalValues.Speed / 30f; if (entry != null) { if (App.globalValues.IsRecoding && (App.globalValues.GifList != null || App.recordImageCount > 0) && entry.LastTime < entry.EndTime) @@ -182,7 +178,7 @@ public void Draw() App.globalValues.Lock = (entry.LastTime % entry.EndTime) / entry.EndTime; entry.TimeScale = App.globalValues.TimeScale; } - App.globalValues.LoadingProcess = $"{ Math.Round((entry.Time % entry.EndTime) / entry.EndTime * 100, 2)}%"; + App.globalValues.LoadingProcess = $"{Math.Round((entry.Time % entry.EndTime) / entry.EndTime * 100, 2)}%"; } } diff --git a/SpineViewerWPF/PublicFunction/Player/Player_2_1_25.cs b/SpineViewerWPF/PublicFunction/Player/Player_2_1_25.cs index d1a3000..5fd269d 100644 --- a/SpineViewerWPF/PublicFunction/Player/Player_2_1_25.cs +++ b/SpineViewerWPF/PublicFunction/Player/Player_2_1_25.cs @@ -1,13 +1,9 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Spine2_1_25; -using SpineViewerWPF; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; -using Microsoft.Xna.Framework.Graphics; +using Spine2_1_25; +using SpineViewerWPF; public class Player_2_1_25 : IPlayer { @@ -116,7 +112,7 @@ public void Update(GameTime gameTime) App.graphicsDevice.Clear(Color.Transparent); Player.DrawBG(ref App.spriteBatch); - App.globalValues.TimeScale = (float)App.globalValues.Speed / 30f; + App.globalValues.TimeScale = App.globalValues.Speed / 30f; state.Update((float)gameTime.ElapsedGameTime.TotalMilliseconds / 1000f); @@ -159,12 +155,12 @@ public void Update(GameTime gameTime) public void Draw() { - + if (state != null) { TrackEntry entry = state.GetCurrent(0); - float speed = (float)App.globalValues.Speed / 30f; + float speed = App.globalValues.Speed / 30f; if (entry != null) { if (App.globalValues.IsRecoding && (App.globalValues.GifList != null || App.recordImageCount > 0) && entry.LastTime < entry.EndTime) @@ -200,7 +196,7 @@ public void Draw() App.globalValues.Lock = (entry.LastTime % entry.EndTime) / entry.EndTime; entry.TimeScale = App.globalValues.TimeScale; } - App.globalValues.LoadingProcess = $"{ Math.Round((entry.Time % entry.EndTime) / entry.EndTime * 100, 2)}%"; + App.globalValues.LoadingProcess = $"{Math.Round((entry.Time % entry.EndTime) / entry.EndTime * 100, 2)}%"; } } diff --git a/SpineViewerWPF/PublicFunction/Player/Player_3_1_07.cs b/SpineViewerWPF/PublicFunction/Player/Player_3_1_07.cs index 1b58a03..62720fe 100644 --- a/SpineViewerWPF/PublicFunction/Player/Player_3_1_07.cs +++ b/SpineViewerWPF/PublicFunction/Player/Player_3_1_07.cs @@ -1,13 +1,9 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Spine3_1_07; -using SpineViewerWPF; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; -using Microsoft.Xna.Framework.Graphics; +using Spine3_1_07; +using SpineViewerWPF; public class Player_3_1_07 : IPlayer { @@ -116,7 +112,7 @@ public void Update(GameTime gameTime) App.graphicsDevice.Clear(Color.Transparent); Player.DrawBG(ref App.spriteBatch); - App.globalValues.TimeScale = (float)App.globalValues.Speed / 30f; + App.globalValues.TimeScale = App.globalValues.Speed / 30f; state.Update((float)gameTime.ElapsedGameTime.TotalMilliseconds / 1000f); @@ -159,12 +155,12 @@ public void Update(GameTime gameTime) public void Draw() { - + if (state != null) { TrackEntry entry = state.GetCurrent(0); - float speed = (float)App.globalValues.Speed / 30f; + float speed = App.globalValues.Speed / 30f; if (entry != null) { @@ -201,7 +197,7 @@ public void Draw() App.globalValues.Lock = (entry.LastTime % entry.EndTime) / entry.EndTime; entry.TimeScale = App.globalValues.TimeScale; } - App.globalValues.LoadingProcess = $"{ Math.Round((entry.Time % entry.EndTime) / entry.EndTime * 100, 2)}%"; + App.globalValues.LoadingProcess = $"{Math.Round((entry.Time % entry.EndTime) / entry.EndTime * 100, 2)}%"; } } diff --git a/SpineViewerWPF/PublicFunction/Player/Player_3_2_xx.cs b/SpineViewerWPF/PublicFunction/Player/Player_3_2_xx.cs index c28f6cf..a1b2902 100644 --- a/SpineViewerWPF/PublicFunction/Player/Player_3_2_xx.cs +++ b/SpineViewerWPF/PublicFunction/Player/Player_3_2_xx.cs @@ -1,13 +1,9 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Spine3_2_xx; -using SpineViewerWPF; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; -using Microsoft.Xna.Framework.Graphics; +using Spine3_2_xx; +using SpineViewerWPF; public class Player_3_2_xx : IPlayer { @@ -116,7 +112,7 @@ public void Update(GameTime gameTime) App.graphicsDevice.Clear(Color.Transparent); Player.DrawBG(ref App.spriteBatch); - App.globalValues.TimeScale = (float)App.globalValues.Speed / 30f; + App.globalValues.TimeScale = App.globalValues.Speed / 30f; state.Update((float)gameTime.ElapsedGameTime.TotalMilliseconds / 1000f); @@ -164,7 +160,7 @@ public void Draw() if (state != null) { TrackEntry entry = state.GetCurrent(0); - float speed = (float)App.globalValues.Speed / 30f; + float speed = App.globalValues.Speed / 30f; if (entry != null) { @@ -201,7 +197,7 @@ public void Draw() App.globalValues.Lock = (entry.LastTime % entry.EndTime) / entry.EndTime; entry.TimeScale = App.globalValues.TimeScale; } - App.globalValues.LoadingProcess = $"{ Math.Round((entry.Time % entry.EndTime) / entry.EndTime * 100, 2)}%"; + App.globalValues.LoadingProcess = $"{Math.Round((entry.Time % entry.EndTime) / entry.EndTime * 100, 2)}%"; } } diff --git a/SpineViewerWPF/PublicFunction/Player/Player_3_4_02.cs b/SpineViewerWPF/PublicFunction/Player/Player_3_4_02.cs index ea74209..900eff9 100644 --- a/SpineViewerWPF/PublicFunction/Player/Player_3_4_02.cs +++ b/SpineViewerWPF/PublicFunction/Player/Player_3_4_02.cs @@ -1,13 +1,9 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Spine3_4_02; -using SpineViewerWPF; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; -using Microsoft.Xna.Framework.Graphics; +using Spine3_4_02; +using SpineViewerWPF; public class Player_3_4_02 : IPlayer { @@ -116,7 +112,7 @@ public void Update(GameTime gameTime) App.graphicsDevice.Clear(Color.Transparent); Player.DrawBG(ref App.spriteBatch); - App.globalValues.TimeScale = (float)App.globalValues.Speed / 30f; + App.globalValues.TimeScale = App.globalValues.Speed / 30f; state.Update((float)gameTime.ElapsedGameTime.TotalMilliseconds / 1000f); @@ -164,7 +160,7 @@ public void Draw() if (state != null) { TrackEntry entry = state.GetCurrent(0); - float speed = (float)App.globalValues.Speed / 30f; + float speed = App.globalValues.Speed / 30f; if (entry != null) { @@ -201,7 +197,7 @@ public void Draw() App.globalValues.Lock = (entry.LastTime % entry.EndTime) / entry.EndTime; entry.TimeScale = App.globalValues.TimeScale; } - App.globalValues.LoadingProcess = $"{ Math.Round((entry.Time % entry.EndTime) / entry.EndTime * 100, 2)}%"; + App.globalValues.LoadingProcess = $"{Math.Round((entry.Time % entry.EndTime) / entry.EndTime * 100, 2)}%"; } } diff --git a/SpineViewerWPF/PublicFunction/Player/Player_3_5_51.cs b/SpineViewerWPF/PublicFunction/Player/Player_3_5_51.cs index d9dd65e..7d81aa1 100644 --- a/SpineViewerWPF/PublicFunction/Player/Player_3_5_51.cs +++ b/SpineViewerWPF/PublicFunction/Player/Player_3_5_51.cs @@ -1,13 +1,9 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Spine3_5_51; -using SpineViewerWPF; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; -using Microsoft.Xna.Framework.Graphics; +using Spine3_5_51; +using SpineViewerWPF; public class Player_3_5_51 : IPlayer { @@ -116,7 +112,7 @@ public void Update(GameTime gameTime) App.graphicsDevice.Clear(Color.Transparent); Player.DrawBG(ref App.spriteBatch); - App.globalValues.TimeScale = (float)App.globalValues.Speed / 30f; + App.globalValues.TimeScale = App.globalValues.Speed / 30f; state.Update((float)gameTime.ElapsedGameTime.TotalMilliseconds / 1000f); @@ -159,12 +155,12 @@ public void Update(GameTime gameTime) public void Draw() { - + if (state != null) { TrackEntry entry = state.GetCurrent(0); - float speed = (float)App.globalValues.Speed / 30f; + float speed = App.globalValues.Speed / 30f; if (entry != null) { if (App.globalValues.IsRecoding && (App.globalValues.GifList != null || App.recordImageCount > 0) && !entry.IsComplete) @@ -200,7 +196,7 @@ public void Draw() App.globalValues.Lock = entry.AnimationTime / entry.AnimationEnd; entry.TimeScale = App.globalValues.TimeScale; } - App.globalValues.LoadingProcess = $"{ Math.Round(entry.AnimationTime / entry.AnimationEnd * 100, 2)}%"; + App.globalValues.LoadingProcess = $"{Math.Round(entry.AnimationTime / entry.AnimationEnd * 100, 2)}%"; } } diff --git a/SpineViewerWPF/PublicFunction/Player/Player_3_6_32.cs b/SpineViewerWPF/PublicFunction/Player/Player_3_6_32.cs index 7fd7c53..da9c245 100644 --- a/SpineViewerWPF/PublicFunction/Player/Player_3_6_32.cs +++ b/SpineViewerWPF/PublicFunction/Player/Player_3_6_32.cs @@ -1,13 +1,10 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Spine3_6_32; -using SpineViewerWPF; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; +using Spine3_6_32; +using SpineViewerWPF; public class Player_3_6_32 : IPlayer { @@ -116,7 +113,7 @@ public void Update(GameTime gameTime) App.graphicsDevice.Clear(Color.Transparent); Player.DrawBG(ref App.spriteBatch); - App.globalValues.TimeScale = (float)App.globalValues.Speed / 30f; + App.globalValues.TimeScale = App.globalValues.Speed / 30f; state.Update((float)gameTime.ElapsedGameTime.TotalMilliseconds / 1000f); @@ -167,12 +164,12 @@ public void Update(GameTime gameTime) public void Draw() { - + if (state != null) { TrackEntry entry = state.GetCurrent(0); - float speed = (float)App.globalValues.Speed / 30f; + float speed = App.globalValues.Speed / 30f; if (entry != null) { if (App.globalValues.IsRecoding && (App.globalValues.GifList != null || App.recordImageCount > 0) && !entry.IsComplete) @@ -208,7 +205,7 @@ public void Draw() App.globalValues.Lock = entry.AnimationTime / entry.AnimationEnd; entry.TimeScale = App.globalValues.TimeScale; } - App.globalValues.LoadingProcess = $"{ Math.Round(entry.AnimationTime / entry.AnimationEnd * 100, 2)}%"; + App.globalValues.LoadingProcess = $"{Math.Round(entry.AnimationTime / entry.AnimationEnd * 100, 2)}%"; } } diff --git a/SpineViewerWPF/PublicFunction/Player/Player_3_6_39.cs b/SpineViewerWPF/PublicFunction/Player/Player_3_6_39.cs index ccd2536..bfdc8de 100644 --- a/SpineViewerWPF/PublicFunction/Player/Player_3_6_39.cs +++ b/SpineViewerWPF/PublicFunction/Player/Player_3_6_39.cs @@ -1,13 +1,10 @@ using System; -using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Spine3_6_39; -using SpineViewerWPF; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; +using Spine3_6_39; +using SpineViewerWPF; public class Player_3_6_39 : IPlayer { @@ -101,7 +98,7 @@ public void Update(GameTime gameTime) App.graphicsDevice.Clear(Color.Transparent); Player.DrawBG(ref App.spriteBatch); - App.globalValues.TimeScale = (float)App.globalValues.Speed / 30f; + App.globalValues.TimeScale = App.globalValues.Speed / 30f; state.Update((float)gameTime.ElapsedGameTime.TotalMilliseconds / 1000f); @@ -152,12 +149,12 @@ public void Update(GameTime gameTime) public void Draw() { - + if (state != null) { TrackEntry entry = state.GetCurrent(0); - float speed = (float)App.globalValues.Speed / 30f; + float speed = App.globalValues.Speed / 30f; if (entry != null) { if (App.globalValues.IsRecoding && (App.globalValues.GifList != null || App.recordImageCount > 0) && !entry.IsComplete) @@ -193,7 +190,7 @@ public void Draw() App.globalValues.Lock = entry.AnimationTime / entry.AnimationEnd; entry.TimeScale = App.globalValues.TimeScale; } - App.globalValues.LoadingProcess = $"{ Math.Round(entry.AnimationTime / entry.AnimationEnd * 100, 2)}%"; + App.globalValues.LoadingProcess = $"{Math.Round(entry.AnimationTime / entry.AnimationEnd * 100, 2)}%"; } } diff --git a/SpineViewerWPF/PublicFunction/Player/Player_3_6_53.cs b/SpineViewerWPF/PublicFunction/Player/Player_3_6_53.cs index da310eb..862b602 100644 --- a/SpineViewerWPF/PublicFunction/Player/Player_3_6_53.cs +++ b/SpineViewerWPF/PublicFunction/Player/Player_3_6_53.cs @@ -1,13 +1,10 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Spine3_6_53; -using SpineViewerWPF; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; +using Spine3_6_53; +using SpineViewerWPF; public class Player_3_6_53 : IPlayer { @@ -116,7 +113,7 @@ public void Update(GameTime gameTime) App.graphicsDevice.Clear(Color.Transparent); Player.DrawBG(ref App.spriteBatch); - App.globalValues.TimeScale = (float)((float)(App.globalValues.Speed) / 30f); + App.globalValues.TimeScale = (float)(App.globalValues.Speed / 30f); state.Update((float)gameTime.ElapsedGameTime.TotalMilliseconds / 1000f); @@ -167,12 +164,12 @@ public void Update(GameTime gameTime) public void Draw() { - + if (state != null) { TrackEntry entry = state.GetCurrent(0); - float speed = (float)App.globalValues.Speed / 30f; + float speed = App.globalValues.Speed / 30f; if (entry != null) { if (App.globalValues.IsRecoding && (App.globalValues.GifList != null || App.recordImageCount > 0) && !entry.IsComplete) @@ -208,7 +205,7 @@ public void Draw() App.globalValues.Lock = entry.AnimationTime / entry.AnimationEnd; entry.TimeScale = App.globalValues.TimeScale; } - App.globalValues.LoadingProcess = $"{ Math.Round(entry.AnimationTime / entry.AnimationEnd * 100, 2)}%"; + App.globalValues.LoadingProcess = $"{Math.Round(entry.AnimationTime / entry.AnimationEnd * 100, 2)}%"; } } diff --git a/SpineViewerWPF/PublicFunction/Player/Player_3_7_94.cs b/SpineViewerWPF/PublicFunction/Player/Player_3_7_94.cs index 75c9364..6e22af0 100644 --- a/SpineViewerWPF/PublicFunction/Player/Player_3_7_94.cs +++ b/SpineViewerWPF/PublicFunction/Player/Player_3_7_94.cs @@ -1,13 +1,10 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Spine3_7_94; -using SpineViewerWPF; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; +using Spine3_7_94; +using SpineViewerWPF; public class Player_3_7_94 : IPlayer { @@ -25,7 +22,7 @@ public class Player_3_7_94 : IPlayer public void Initialize() { Player.Initialize(ref App.graphicsDevice, ref App.spriteBatch); - + } public void LoadContent(ContentManager contentManager) @@ -117,15 +114,15 @@ public void Update(GameTime gameTime) App.graphicsDevice.Clear(Color.Transparent); Player.DrawBG(ref App.spriteBatch); - App.globalValues.TimeScale = (float)App.globalValues.Speed / 30f; + App.globalValues.TimeScale = App.globalValues.Speed / 30f; - state.Update((float)gameTime.ElapsedGameTime.TotalMilliseconds/1000f); + state.Update((float)gameTime.ElapsedGameTime.TotalMilliseconds / 1000f); state.Apply(skeleton); skeleton.X = App.globalValues.PosX; skeleton.Y = App.globalValues.PosY; - skeleton.ScaleX = (App.globalValues.FilpX ? -1 : 1)* App.globalValues.Scale; - skeleton.ScaleY = (App.globalValues.FilpY ? 1 : -1)* App.globalValues.Scale; + skeleton.ScaleX = (App.globalValues.FilpX ? -1 : 1) * App.globalValues.Scale; + skeleton.ScaleY = (App.globalValues.FilpY ? 1 : -1) * App.globalValues.Scale; skeleton.RootBone.Rotation = App.globalValues.Rotation; @@ -143,7 +140,7 @@ public void Update(GameTime gameTime) skeletonRenderer.Draw(skeleton); skeletonRenderer.End(); - + @@ -156,7 +153,7 @@ public void Draw() { TrackEntry entry = state.GetCurrent(0); - float speed = (float)App.globalValues.Speed / 30f; + float speed = App.globalValues.Speed / 30f; if (entry != null) { if (App.globalValues.IsRecoding && (App.globalValues.GifList != null || App.recordImageCount > 0) && !entry.IsComplete) @@ -193,7 +190,7 @@ public void Draw() App.globalValues.Lock = entry.AnimationTime / entry.AnimationEnd; entry.TimeScale = App.globalValues.TimeScale; } - App.globalValues.LoadingProcess = $"{ Math.Round(entry.AnimationTime / entry.AnimationEnd * 100, 2)}%"; + App.globalValues.LoadingProcess = $"{Math.Round(entry.AnimationTime / entry.AnimationEnd * 100, 2)}%"; } } @@ -209,7 +206,7 @@ public void ChangeSet() public void SizeChange() { - if(App.graphicsDevice != null) + if (App.graphicsDevice != null) Player.UserControl_SizeChanged(ref App.graphicsDevice); } diff --git a/SpineViewerWPF/PublicFunction/Player/Player_3_8_95.cs b/SpineViewerWPF/PublicFunction/Player/Player_3_8_95.cs index 66029e9..63e09ba 100644 --- a/SpineViewerWPF/PublicFunction/Player/Player_3_8_95.cs +++ b/SpineViewerWPF/PublicFunction/Player/Player_3_8_95.cs @@ -1,13 +1,10 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using SpineViewerWPF; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using Spine3_8_95; +using SpineViewerWPF; public class Player_3_8_95 : IPlayer { @@ -32,15 +29,15 @@ public void LoadContent(ContentManager contentManager) skeletonRenderer = new SkeletonRenderer(App.graphicsDevice); skeletonRenderer.PremultipliedAlpha = App.globalValues.Alpha; - if(App.mulitTexture != null && App.mulitTexture.Length == 0) + if (App.mulitTexture != null && App.mulitTexture.Length == 0) { atlas = new Atlas(App.globalValues.SelectAtlasFile, new XnaTextureLoader(App.graphicsDevice)); } else { - atlas = new Atlas(App.globalValues.SelectAtlasFile,new XnaTextureLoader(App.graphicsDevice,true,App.mulitTexture)); + atlas = new Atlas(App.globalValues.SelectAtlasFile, new XnaTextureLoader(App.graphicsDevice, true, App.mulitTexture)); } - + if (Common.IsBinaryData(App.globalValues.SelectSpineFile)) { @@ -58,7 +55,7 @@ public void LoadContent(ContentManager contentManager) skeleton = new Skeleton(skeletonData); - + Common.SetInitLocation(skeleton.Data.Height); App.globalValues.FileHash = skeleton.Data.Hash; @@ -94,8 +91,8 @@ public void LoadContent(ContentManager contentManager) if (App.isNew) { - App.globalValues.PosX = (float)App.canvasWidth/2; - App.globalValues.PosY = (float)App.canvasHeight/2; + App.globalValues.PosX = (float)App.canvasWidth / 2; + App.globalValues.PosY = (float)App.canvasHeight / 2; MainWindow.SetCBAnimeName(); } App.isNew = false; @@ -136,7 +133,7 @@ public void Draw() Player.DrawBG(ref App.spriteBatch); - + state.Update(App.globalValues.Speed / 1000f); state.Apply(skeleton); state.TimeScale = App.globalValues.TimeScale; @@ -161,8 +158,8 @@ public void Draw() skeleton.X = App.globalValues.PosX; skeleton.Y = App.globalValues.PosY; - skeleton.ScaleX = (App.globalValues.FilpX ? -1 : 1) ; - skeleton.ScaleY = (App.globalValues.FilpY ? -1 : 1) ; + skeleton.ScaleX = (App.globalValues.FilpX ? -1 : 1); + skeleton.ScaleY = (App.globalValues.FilpY ? -1 : 1); skeleton.RootBone.Rotation = App.globalValues.Rotation; @@ -185,7 +182,7 @@ public void Draw() TrackEntry entry = state.GetCurrent(0); if (entry != null) { - if (App.globalValues.IsRecoding && (App.globalValues.GifList != null || App.recordImageCount >0 ) && !entry.IsComplete) + if (App.globalValues.IsRecoding && (App.globalValues.GifList != null || App.recordImageCount > 0) && !entry.IsComplete) { if (App.recordImageCount == 1) { @@ -218,7 +215,7 @@ public void Draw() App.globalValues.Lock = entry.AnimationTime / entry.AnimationEnd; entry.TimeScale = 1; } - App.globalValues.LoadingProcess = $"{ Math.Round(entry.AnimationTime / entry.AnimationEnd * 100, 2)}%"; + App.globalValues.LoadingProcess = $"{Math.Round(entry.AnimationTime / entry.AnimationEnd * 100, 2)}%"; } } diff --git a/SpineViewerWPF/PublicFunction/Player/Player_4_0_31.cs b/SpineViewerWPF/PublicFunction/Player/Player_4_0_31.cs index 20cb2a7..05fdf7a 100644 --- a/SpineViewerWPF/PublicFunction/Player/Player_4_0_31.cs +++ b/SpineViewerWPF/PublicFunction/Player/Player_4_0_31.cs @@ -1,13 +1,10 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using SpineViewerWPF; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using Spine4_0_31; +using SpineViewerWPF; public class Player_4_0_31 : IPlayer { @@ -57,7 +54,7 @@ public void LoadContent(ContentManager contentManager) skeleton = new Skeleton(skeletonData); - + Common.SetInitLocation(skeleton.Data.Height); App.globalValues.FileHash = skeleton.Data.Hash; @@ -93,8 +90,8 @@ public void LoadContent(ContentManager contentManager) if (App.isNew) { - App.globalValues.PosX = (float)App.canvasWidth/2; - App.globalValues.PosY = (float)App.canvasHeight/2; + App.globalValues.PosX = (float)App.canvasWidth / 2; + App.globalValues.PosY = (float)App.canvasHeight / 2; MainWindow.SetCBAnimeName(); } App.isNew = false; @@ -135,7 +132,7 @@ public void Draw() Player.DrawBG(ref App.spriteBatch); - + state.Update(App.globalValues.Speed / 1000f); state.Apply(skeleton); state.TimeScale = App.globalValues.TimeScale; @@ -160,8 +157,8 @@ public void Draw() skeleton.X = App.globalValues.PosX; skeleton.Y = App.globalValues.PosY; - skeleton.ScaleX = (App.globalValues.FilpX ? -1 : 1) ; - skeleton.ScaleY = (App.globalValues.FilpY ? -1 : 1) ; + skeleton.ScaleX = (App.globalValues.FilpX ? -1 : 1); + skeleton.ScaleY = (App.globalValues.FilpY ? -1 : 1); skeleton.RootBone.Rotation = App.globalValues.Rotation; @@ -184,7 +181,7 @@ public void Draw() TrackEntry entry = state.GetCurrent(0); if (entry != null) { - if (App.globalValues.IsRecoding && (App.globalValues.GifList != null || App.recordImageCount >0 ) && !entry.IsComplete) + if (App.globalValues.IsRecoding && (App.globalValues.GifList != null || App.recordImageCount > 0) && !entry.IsComplete) { if (App.recordImageCount == 1) { @@ -217,7 +214,7 @@ public void Draw() App.globalValues.Lock = entry.AnimationTime / entry.AnimationEnd; entry.TimeScale = 1; } - App.globalValues.LoadingProcess = $"{ Math.Round(entry.AnimationTime / entry.AnimationEnd * 100, 2)}%"; + App.globalValues.LoadingProcess = $"{Math.Round(entry.AnimationTime / entry.AnimationEnd * 100, 2)}%"; } } diff --git a/SpineViewerWPF/PublicFunction/Player/Player_4_0_64.cs b/SpineViewerWPF/PublicFunction/Player/Player_4_0_64.cs index c2c87ec..d0e27a5 100644 --- a/SpineViewerWPF/PublicFunction/Player/Player_4_0_64.cs +++ b/SpineViewerWPF/PublicFunction/Player/Player_4_0_64.cs @@ -1,13 +1,10 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using SpineViewerWPF; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using Spine4_0_64; +using SpineViewerWPF; public class Player_4_0_64 : IPlayer { @@ -57,7 +54,7 @@ public void LoadContent(ContentManager contentManager) skeleton = new Skeleton(skeletonData); - + Common.SetInitLocation(skeleton.Data.Height); App.globalValues.FileHash = skeleton.Data.Hash; @@ -93,8 +90,8 @@ public void LoadContent(ContentManager contentManager) if (App.isNew) { - App.globalValues.PosX = (float)App.canvasWidth/2; - App.globalValues.PosY = (float)App.canvasHeight/2; + App.globalValues.PosX = (float)App.canvasWidth / 2; + App.globalValues.PosY = (float)App.canvasHeight / 2; MainWindow.SetCBAnimeName(); } App.isNew = false; @@ -135,7 +132,7 @@ public void Draw() Player.DrawBG(ref App.spriteBatch); - + state.Update(App.globalValues.Speed / 1000f); state.Apply(skeleton); state.TimeScale = App.globalValues.TimeScale; @@ -160,8 +157,8 @@ public void Draw() skeleton.X = App.globalValues.PosX; skeleton.Y = App.globalValues.PosY; - skeleton.ScaleX = (App.globalValues.FilpX ? -1 : 1) ; - skeleton.ScaleY = (App.globalValues.FilpY ? -1 : 1) ; + skeleton.ScaleX = (App.globalValues.FilpX ? -1 : 1); + skeleton.ScaleY = (App.globalValues.FilpY ? -1 : 1); skeleton.RootBone.Rotation = App.globalValues.Rotation; @@ -184,7 +181,7 @@ public void Draw() TrackEntry entry = state.GetCurrent(0); if (entry != null) { - if (App.globalValues.IsRecoding && (App.globalValues.GifList != null || App.recordImageCount >0 ) && !entry.IsComplete) + if (App.globalValues.IsRecoding && (App.globalValues.GifList != null || App.recordImageCount > 0) && !entry.IsComplete) { if (App.recordImageCount == 1) { @@ -217,7 +214,7 @@ public void Draw() App.globalValues.Lock = entry.AnimationTime / entry.AnimationEnd; entry.TimeScale = 1; } - App.globalValues.LoadingProcess = $"{ Math.Round(entry.AnimationTime / entry.AnimationEnd * 100, 2)}%"; + App.globalValues.LoadingProcess = $"{Math.Round(entry.AnimationTime / entry.AnimationEnd * 100, 2)}%"; } } diff --git a/SpineViewerWPF/PublicFunction/Player/Player_4_1_00.cs b/SpineViewerWPF/PublicFunction/Player/Player_4_1_00.cs index 2820c15..d510ce8 100644 --- a/SpineViewerWPF/PublicFunction/Player/Player_4_1_00.cs +++ b/SpineViewerWPF/PublicFunction/Player/Player_4_1_00.cs @@ -1,13 +1,10 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using SpineViewerWPF; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using Spine4_1_00; +using SpineViewerWPF; public class Player_4_1_00 : IPlayer { @@ -57,7 +54,7 @@ public void LoadContent(ContentManager contentManager) skeleton = new Skeleton(skeletonData); - + Common.SetInitLocation(skeleton.Data.Height); App.globalValues.FileHash = skeleton.Data.Hash; @@ -93,8 +90,8 @@ public void LoadContent(ContentManager contentManager) if (App.isNew) { - App.globalValues.PosX = (float)App.canvasWidth/2; - App.globalValues.PosY = (float)App.canvasHeight/2; + App.globalValues.PosX = (float)App.canvasWidth / 2; + App.globalValues.PosY = (float)App.canvasHeight / 2; MainWindow.SetCBAnimeName(); } App.isNew = false; @@ -135,7 +132,7 @@ public void Draw() Player.DrawBG(ref App.spriteBatch); - + state.Update(App.globalValues.Speed / 1000f); state.Apply(skeleton); state.TimeScale = App.globalValues.TimeScale; @@ -160,8 +157,8 @@ public void Draw() skeleton.X = App.globalValues.PosX; skeleton.Y = App.globalValues.PosY; - skeleton.ScaleX = (App.globalValues.FilpX ? -1 : 1) ; - skeleton.ScaleY = (App.globalValues.FilpY ? -1 : 1) ; + skeleton.ScaleX = (App.globalValues.FilpX ? -1 : 1); + skeleton.ScaleY = (App.globalValues.FilpY ? -1 : 1); skeleton.RootBone.Rotation = App.globalValues.Rotation; @@ -184,7 +181,7 @@ public void Draw() TrackEntry entry = state.GetCurrent(0); if (entry != null) { - if (App.globalValues.IsRecoding && (App.globalValues.GifList != null || App.recordImageCount >0 ) && !entry.IsComplete) + if (App.globalValues.IsRecoding && (App.globalValues.GifList != null || App.recordImageCount > 0) && !entry.IsComplete) { if (App.recordImageCount == 1) { @@ -217,7 +214,7 @@ public void Draw() App.globalValues.Lock = entry.AnimationTime / entry.AnimationEnd; entry.TimeScale = 1; } - App.globalValues.LoadingProcess = $"{ Math.Round(entry.AnimationTime / entry.AnimationEnd * 100, 2)}%"; + App.globalValues.LoadingProcess = $"{Math.Round(entry.AnimationTime / entry.AnimationEnd * 100, 2)}%"; } } diff --git a/SpineViewerWPF/PublicFunction/XnaLoader/Util.cs b/SpineViewerWPF/PublicFunction/XnaLoader/Util.cs index 63fdf5d..2880369 100644 --- a/SpineViewerWPF/PublicFunction/XnaLoader/Util.cs +++ b/SpineViewerWPF/PublicFunction/XnaLoader/Util.cs @@ -1,11 +1,11 @@ using System; using System.IO; -using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using SpineViewerWPF; - public static class Util { +public static class Util +{ #if WINDOWS_STOREAPP private static async Task LoadFile(GraphicsDevice device, String path) { var folder = Windows.ApplicationModel.Package.Current.InstalledLocation; @@ -21,27 +21,33 @@ static public Texture2D LoadTexture (GraphicsDevice device, String path) { return LoadFile(device, path).Result; } #else - static public Texture2D LoadTexture (GraphicsDevice device, String path) { + static public Texture2D LoadTexture(GraphicsDevice device, String path) + { #if WINDOWS_PHONE Stream stream = Microsoft.Xna.Framework.TitleContainer.OpenStream(path); using (Stream input = stream) { #else - using (Stream input = new FileStream(path, FileMode.Open, FileAccess.Read)) { + using (Stream input = new FileStream(path, FileMode.Open, FileAccess.Read)) + { #endif - try { - return Util.LoadTexture(device, input); - } catch (Exception ex) { - throw new Exception("Error reading texture file: " + path, ex); - } - } - } + try + { + return Util.LoadTexture(device, input); + } + catch (Exception ex) + { + throw new Exception("Error reading texture file: " + path, ex); + } + } + } #endif - static public Texture2D LoadTexture (GraphicsDevice device, Stream input) { - NewTextureLoader ntl = new NewTextureLoader(device); - return ntl.FromStreamFast(input, App.globalValues.PreMultiplyAlpha); - } - } + static public Texture2D LoadTexture(GraphicsDevice device, Stream input) + { + NewTextureLoader ntl = new NewTextureLoader(device); + return ntl.FromStreamFast(input, App.globalValues.PreMultiplyAlpha); + } +} diff --git a/SpineViewerWPF/Resources/WpfXnaControl.dll b/SpineViewerWPF/Resources/WpfXnaControl.dll new file mode 100644 index 0000000000000000000000000000000000000000..b106933c840671747799687fe7486fd208bd90ce GIT binary patch literal 13312 zcmeHNeRLevb-!Oj$4>K8WP8NuC)o2Xot71wSiu>JD77Z7y5RO&s# zto&bidMJ}{-3Yl`xX_Efd+iv_Ut>h2K;FNes5?)57ljCsUc{yYUsr_3vgSw@_(N9# zpicI6l75hTW42@WqgvY5ivo8vhP&u$!EnWFGi8C1ZKW~XENcnwqN{~yO%W=KA7a1a zV%rq*)3s5ehPgySQi)O+pK`y4yo<-q!)WQdVsYt02c~FOj4@hUtsI2izS{YUdPo*% zYJ*CPAP?0Q%0Vdbg|w1@8bzZ}7Q{KF%KMqB2cp2P3Tc6+AJu~Y?b=QGz1ktDE}@U9 z@2s!Tu2|piZWp+uKBm62zCycVeZRXMBJUFYW9mEWE3_-tcTu})C3+eB?T&|!KIp}T z;$WhM4yjP19Ap?di?OpRK=kky#>07hhBY-`R;_9I@+@n5zC6R4!R6>oz?rTXrxN8T z6liYb)Oiu49G-;=Yc@dbEg!%H@I~ivtELi;Ax*K$0A+mEzT#F_p}J1gDhGK8Sqwjm z#3)fn(Jah|kTOkbtX-lUECG|n6&%-)DdQWxa^qVTKC3v@Xmp5cmFVT%o^8<-<-CUe zXifyA=sW;8aZZDvXwHWRF#~*74f9GxbtRrccwGBoK#5}XLoe0}xEdx1d|A(A$gdOWxza{PoV?XNzLt*Mqlxr3~Ka(gnNAH z;AK>g<{I7MyV0-FCnbGX$;(=Zo)zuj4AIM)Mx!C7d^*IGGVp}x%cz|~&ud4KUK)b0 z2Q+$9dl^f?lX@6sGr+9`+J@-3eylV^OVu`Q3jK|8G&Gx>&>6s^fioeER;kQ+9W2q{ zp9gh~njlA`pQ`r&W_%1!DJ*9twFhU@6M@yRvqk?`@P9n8HBe3Wsnrlg|w;U4CX~Ze;J8yozex58w%TYOylxO zmH31$8wn$tvuz#nKNAus=q( zX?F$}(lY4P=m(_-VgCcLpT}qc1Ne;p%IP`rq&7E?eOr4{TR}n4xvoRuy6J?81MG*v z)uFjmNm!wf5N-;s4jqRTwa6T$%8R0c9w|L51U(Y^cfg<1j{(0&KLh-VpWz!N3}2Jw zPW9&~Un$`^@gmCI%CE#cdOG+Ufk^OQ0KC6cRS-XF3E)$u7r?VrOjT66N13SzI!1E< zyG1qNR8a%itVfj!dR40fyjyJqyj)qPY>jYV@76DfmEakW*ek(571sevOKe>KsAvZ^ z;?;ef)+-9#?_tjZQ|X+CeZs$9@lj+R;d&+bATU4e_OS1ltcRt?JnUz{0`x5p`z|=K zZq9exodXu6W{Hhg)?nO1)Z$`vi%0?cp2Y5^h5l}(gnlKl+vy2aL$+wFVJWxk+=o(H z?P24=8-R6s*e!Ywu+0)1*N^H6V7uHpsjrM~li1@l7cpK&-*P$WFx{k-QB(%V1g2e99uhs8TW+OdwySwzm>PLW; zdDx$;p8z&pVt4DSO7BzVP?f}Pmr~}?0*M{3Ton8q>UciAg*5s5!;s3YTMXn zn*|s`p05d(Q=_CClDJ8l6DxLo^NL$50#8PBSb$O%G7L@-)gE#dnAg zYCocnh{u(4bV9tOyb8(R6u+R;^la%{fVYMeG0s}V15(SwV%T2-N-8i*dJ}P?zegKrm3{&^hjcYYHB_OtP!nxYdnFu`a9qNBB|IVFrzlS2>ZfP}I6oyR-;nT82_N%d zoE}rp%JNGR5;hE5R3Twd+z5>kSzaW|EwbDzVT;0?trGT1ctXNQU5d(jd(}8~t7Eb} z=9O=x5%pe`+kQjBvl5bzDKUD1!eY6|3I#DuSW^(GfPbr=0c@QtB^6Rs!W45W_ybxLeQZsB~uc5Fj- z;_F6s*#y2vMfxJz6XNR^(N2jAQhc$FN5zoTQB3<6vGpRpQ(`f)(PX_}yQu9canB|6 z-pTt`d2s}s1GZb{T4X#7^BmfP%r?$dt^L`gmF{h%j{RmjyUs`_Ql<@b(9HI%!MoL_ zC3Nj3D>0lhSJT=y>c*?YVcT5NPTS3N!nC^$8=OGnqfXWwitS3K6V?GIw#iH+4O(kv z+l{P2{Z=|{N=01>G7^cNjh2xx6SO;Rv{~t_ZKZOQ9H>p}7m)i&5 z3y5vE(37>YMoQAWdEgns=JzGjCb11v?29(bb}es%e=(!vc<0LSnezH;k%0sSU$ZAh(-+!-JRuo2~Q?$Fwh1$;t{1CRtIaa1%)< zKIWJQEPG#}V!JsIHwMh?=yv2Fr$Bd&l|Dh&jS*QYs`o}f#!oicmrNzIqlMOMM$xJL zMrs(bCNl;CD>u0)KAg#52%OGjTFy4Y{DK*|9M`=Uxh8=@$Pp$ay%@)BQKe@`Y^P~E zm;+qHSFEvsdxeuF&ehB*GLi1(Va;LCgdC|U6bZii>OP{uFODT#fT+RdA{Ao(4<;&!%?m+ zo;3KtomI%&#B(*CL4o_(<#Z0GQt^J9dmON6-~qfV|pMtIE?v%0n8EC;!P5V zj!SgcSh6xb@!`I#ZOD}{$BMv8xoaa8t#V!I0gJrpFUtlE;?&RuoDZ?AHx2w+eJYV1AxSN22Aa8V>NmFMKpK#rAn_Pnc$e`Ov(~0GV)+u6k z^F5LbPsq1#i&cl0a?>t|!|=EZSMiXx+ITz5fzX}o-7=2zD+LJaGCq~em=S#YObW7h&OqjeWmXj@Zi5TK3!qIf`V&KiP!d_ciRJ_Av z&rZOza!+2gH^xYs*-I<|QuaDCIk-0~i*Ek+2%H!T9ruWPU1mhyP||JVO(-O9Z{_Yt znM26fa0c?JEOn)GCEnHNCs5IfTcEw&T;f<@sjdXK++&+qn&56sR_yzz-8K%$er&ef zXRG6wLw%`Hcr`Oh9YdMys5}x~)8?FMd8uGR)uimsu4Jcm%v zZQK_1G$J{Pfpj5PSOMc*wnfd8z+u`aMhxG8x zTs5U)>?Mt0WccPO5lw`fbzMPUE5=TscNJr=mw~A3Wo0Oa$A001jw)Y3(fxr2yt$fN zO=?K^@v1}2_3M>94tgNsLjzrxjv5d8BNQGllbZ14E{s>;U%&slo)1)<39Zx{zrK9u z$z_|H^)KJDXXmV?XYbITIym*h(~Ezw4bIdRO~-@%+8pqPRn;n5ftTa%z}>}k_M`XQ z0U9^GUcuK)Ej%{P;E1Zt^>g7421ij{3Fn1%AFOMZ{%#J!hhE&kNWGG)IvB_S3U8Cc z8vYaaf!M;(k4t#Ob?K1+#{t|2zd?_LN8K?w%DO7~Ev-*yrBNtl(7|Av9{{2l9)T;l zEyJVCTd520MN2h*Yw1>I?7~S5rwg3_khgh@jC1?}M}SX}b9t4Z%GlY%^x~ELbCnnx1r;O3f$WuDEh=9Y#uRv-9%_!cPt{G0I65WQg&k3x+S4RBd zEllp?W2>O1+!-&ohQ_)rEK@OjZnSWK9_8%=&I|4WD}|Z1-gtFR)D40*6w;i#;f-g9 z6GltBZL1&{b)37`xY0U1aNUAI_s2%+YR~#nY?$ zuD;r6?r&b!xT0yP*|2;?&Be;xb9qiD`p2Wk#__eawVnTG{o48^qd&r3edz?-mrJl< znP(xw@Y){V_~ZDoSu36!f%WEZfnOQocZOB?PG2_R=zwXAt8xO34=>(^gI}>#?Gh9( z4hoL1f@=^u@6I(%jeP@6%bS-OiKb;s&8Ft6JqqnEmYWaHKZI~oC1cW_Be#i)ZBO>~ zS?TwnW%B4&ZTHdUqBjrkLF8nwOzWf-d$ZZh%KCbzf3G=YII*Gpsj}Z1sy~oS4~*0= zX=qqhKZ3Jfea0@lRYHzNF5nm0Ronfv+3Py@GY zYbLeD?m9g(lp1h%sxiwRtRIrQ!o}!j2FFGBMTP8Yk*F4tSTae{L=J&#P>=U5 z=>!W{UJ$TAf9itL-0*kj7wAvA1ub;$!Z}6!=Z6y)VsBhfe|(-6^K`r%`QyjuRUc=` z1(y*8^Karl@|F+Z#U29B|M|QE8|CHY6}FkL&jmmESrVJJa_ULoer7%HI<9dq?mvEF zgs%!jOE=)g54DOf{>@17wLRV*|F@ZauHcS89LTdvrrh|Ki{L)lb5ozZK@@ zu8(s$lEWrGb3eg#sW^Yz?Wxoz9G6L^f76t2Rb=gjD+M_H*c_&tZ9>5Olq~p|uUs`Vl-UZm{{_2{(>U-^cVbRGq zC;T`{URW}{O@&9mqaC+^R0F&6PVCiF*qL`>&p&{jKVJ(l(fQZfHaPGy3H65D)APB& zz3%rtf}p@^BF_m)&<8Ie>8~YE1LgjfQXk6v8zcTsm($!X&jtO^mc)64&ki{~5%4x( z>r~*~30@l(6?htO$|&62w=np*H+&MZ@ibunX22of^bTjD80Jq3fB4;$v~MH$@mdoU zK09S-R9ZMldm))*EN+y#ws>^*0(0m3#n`*oxm5PkJw+vOUh0RI$-OP~llx5g7RbJ7 mg+CnFIt1^gU}fZYwnNiDUVkVLw7C1V`|(T4KVJX;Jn%mh2#P}h literal 0 HcmV?d00001 diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Animation.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Animation.cs index 2716936..5bbff1e 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Animation.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Animation.cs @@ -31,690 +31,790 @@ using System; using System.Collections.Generic; -namespace Spine2_1_08 { - public class Animation { - internal List timelines; - internal float duration; - internal String name; - - public String Name { get { return name; } } - public List Timelines { get { return timelines; } set { timelines = value; } } - public float Duration { get { return duration; } set { duration = value; } } - - public Animation (String name, List timelines, float duration) { - if (name == null) throw new ArgumentNullException("name cannot be null."); - if (timelines == null) throw new ArgumentNullException("timelines cannot be null."); - this.name = name; - this.timelines = timelines; - this.duration = duration; - } - - /// Poses the skeleton at the specified time for this animation. - /// The last time the animation was applied. - /// Any triggered events are added. - public void Apply (Skeleton skeleton, float lastTime, float time, bool loop, List events) { - if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); - - if (loop && duration != 0) { - time %= duration; - lastTime %= duration; - } - - List timelines = this.timelines; - for (int i = 0, n = timelines.Count; i < n; i++) - timelines[i].Apply(skeleton, lastTime, time, events, 1); - } - - /// Poses the skeleton at the specified time for this animation mixed with the current pose. - /// The last time the animation was applied. - /// Any triggered events are added. - /// The amount of this animation that affects the current pose. - public void Mix (Skeleton skeleton, float lastTime, float time, bool loop, List events, float alpha) { - if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); - - if (loop && duration != 0) { - time %= duration; - lastTime %= duration; - } - - List timelines = this.timelines; - for (int i = 0, n = timelines.Count; i < n; i++) - timelines[i].Apply(skeleton, lastTime, time, events, alpha); - } - - /// After the first and before the last entry. - internal static int binarySearch (float[] values, float target, int step) { - int low = 0; - int high = values.Length / step - 2; - if (high == 0) return step; - int current = (int)((uint)high >> 1); - while (true) { - if (values[(current + 1) * step] <= target) - low = current + 1; - else - high = current; - if (low == high) return (low + 1) * step; - current = (int)((uint)(low + high) >> 1); - } - } - - /// After the first and before the last entry. - internal static int binarySearch (float[] values, float target) { - int low = 0; - int high = values.Length - 2; - if (high == 0) return 1; - int current = (int)((uint)high >> 1); - while (true) { - if (values[(current + 1)] <= target) - low = current + 1; - else - high = current; - if (low == high) return (low + 1); - current = (int)((uint)(low + high) >> 1); - } - } - - internal static int linearSearch (float[] values, float target, int step) { - for (int i = 0, last = values.Length - step; i <= last; i += step) - if (values[i] > target) return i; - return -1; - } - } - - public interface Timeline { - /// Sets the value(s) for the specified time. - /// May be null to not collect fired events. - void Apply (Skeleton skeleton, float lastTime, float time, List events, float alpha); - } - - /// Base class for frames that use an interpolation bezier curve. - abstract public class CurveTimeline : Timeline { - protected const float LINEAR = 0, STEPPED = 1, BEZIER = 2; - protected const int BEZIER_SEGMENTS = 10, BEZIER_SIZE = BEZIER_SEGMENTS * 2 - 1; - - private float[] curves; // type, x, y, ... - public int FrameCount { get { return curves.Length / BEZIER_SIZE + 1; } } - - public CurveTimeline (int frameCount) { - curves = new float[(frameCount - 1) * BEZIER_SIZE]; - } - - abstract public void Apply (Skeleton skeleton, float lastTime, float time, List firedEvents, float alpha); - - public void SetLinear (int frameIndex) { - curves[frameIndex * BEZIER_SIZE] = LINEAR; - } - - public void SetStepped (int frameIndex) { - curves[frameIndex * BEZIER_SIZE] = STEPPED; - } - - /// Sets the control handle positions for an interpolation bezier curve used to transition from this keyframe to the next. - /// cx1 and cx2 are from 0 to 1, representing the percent of time between the two keyframes. cy1 and cy2 are the percent of - /// the difference between the keyframe's values. - public void SetCurve (int frameIndex, float cx1, float cy1, float cx2, float cy2) { - float subdiv1 = 1f / BEZIER_SEGMENTS, subdiv2 = subdiv1 * subdiv1, subdiv3 = subdiv2 * subdiv1; - float pre1 = 3 * subdiv1, pre2 = 3 * subdiv2, pre4 = 6 * subdiv2, pre5 = 6 * subdiv3; - float tmp1x = -cx1 * 2 + cx2, tmp1y = -cy1 * 2 + cy2, tmp2x = (cx1 - cx2) * 3 + 1, tmp2y = (cy1 - cy2) * 3 + 1; - float dfx = cx1 * pre1 + tmp1x * pre2 + tmp2x * subdiv3, dfy = cy1 * pre1 + tmp1y * pre2 + tmp2y * subdiv3; - float ddfx = tmp1x * pre4 + tmp2x * pre5, ddfy = tmp1y * pre4 + tmp2y * pre5; - float dddfx = tmp2x * pre5, dddfy = tmp2y * pre5; - - int i = frameIndex * BEZIER_SIZE; - float[] curves = this.curves; - curves[i++] = BEZIER; - - float x = dfx, y = dfy; - for (int n = i + BEZIER_SIZE - 1; i < n; i += 2) { - curves[i] = x; - curves[i + 1] = y; - dfx += ddfx; - dfy += ddfy; - ddfx += dddfx; - ddfy += dddfy; - x += dfx; - y += dfy; - } - } - - public float GetCurvePercent (int frameIndex, float percent) { - float[] curves = this.curves; - int i = frameIndex * BEZIER_SIZE; - float type = curves[i]; - if (type == LINEAR) return percent; - if (type == STEPPED) return 0; - i++; - float x = 0; - for (int start = i, n = i + BEZIER_SIZE - 1; i < n; i += 2) { - x = curves[i]; - if (x >= percent) { - float prevX, prevY; - if (i == start) { - prevX = 0; - prevY = 0; - } else { - prevX = curves[i - 2]; - prevY = curves[i - 1]; - } - return prevY + (curves[i + 1] - prevY) * (percent - prevX) / (x - prevX); - } - } - float y = curves[i - 1]; - return y + (1 - y) * (percent - x) / (1 - x); // Last point is 1,1. - } - } - - public class RotateTimeline : CurveTimeline { - protected const int PREV_FRAME_TIME = -2; - protected const int FRAME_VALUE = 1; - - internal int boneIndex; - internal float[] frames; - - public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, value, ... - - public RotateTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount << 1]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, float angle) { - frameIndex *= 2; - frames[frameIndex] = time; - frames[frameIndex + 1] = angle; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, List firedEvents, float alpha) { - float[] frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - Bone bone = skeleton.bones[boneIndex]; - - float amount; - - if (time >= frames[frames.Length - 2]) { // Time is after last frame. - amount = bone.data.rotation + frames[frames.Length - 1] - bone.rotation; - while (amount > 180) - amount -= 360; - while (amount < -180) - amount += 360; - bone.rotation += amount * alpha; - return; - } - - // Interpolate between the previous frame and the current frame. - int frameIndex = Animation.binarySearch(frames, time, 2); - float prevFrameValue = frames[frameIndex - 1]; - float frameTime = frames[frameIndex]; - float percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime); - percent = GetCurvePercent((frameIndex >> 1) - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); - - amount = frames[frameIndex + FRAME_VALUE] - prevFrameValue; - while (amount > 180) - amount -= 360; - while (amount < -180) - amount += 360; - amount = bone.data.rotation + (prevFrameValue + amount * percent) - bone.rotation; - while (amount > 180) - amount -= 360; - while (amount < -180) - amount += 360; - bone.rotation += amount * alpha; - } - } - - public class TranslateTimeline : CurveTimeline { - protected const int PREV_FRAME_TIME = -3; - protected const int FRAME_X = 1; - protected const int FRAME_Y = 2; - - internal int boneIndex; - internal float[] frames; - - public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, value, value, ... - - public TranslateTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * 3]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, float x, float y) { - frameIndex *= 3; - frames[frameIndex] = time; - frames[frameIndex + 1] = x; - frames[frameIndex + 2] = y; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, List firedEvents, float alpha) { - float[] frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - Bone bone = skeleton.bones[boneIndex]; - - if (time >= frames[frames.Length - 3]) { // Time is after last frame. - bone.x += (bone.data.x + frames[frames.Length - 2] - bone.x) * alpha; - bone.y += (bone.data.y + frames[frames.Length - 1] - bone.y) * alpha; - return; - } - - // Interpolate between the previous frame and the current frame. - int frameIndex = Animation.binarySearch(frames, time, 3); - float prevFrameX = frames[frameIndex - 2]; - float prevFrameY = frames[frameIndex - 1]; - float frameTime = frames[frameIndex]; - float percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime); - percent = GetCurvePercent(frameIndex / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); - - bone.x += (bone.data.x + prevFrameX + (frames[frameIndex + FRAME_X] - prevFrameX) * percent - bone.x) * alpha; - bone.y += (bone.data.y + prevFrameY + (frames[frameIndex + FRAME_Y] - prevFrameY) * percent - bone.y) * alpha; - } - } - - public class ScaleTimeline : TranslateTimeline { - public ScaleTimeline (int frameCount) - : base(frameCount) { - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, List firedEvents, float alpha) { - float[] frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - Bone bone = skeleton.bones[boneIndex]; - if (time >= frames[frames.Length - 3]) { // Time is after last frame. - bone.scaleX += (bone.data.scaleX * frames[frames.Length - 2] - bone.scaleX) * alpha; - bone.scaleY += (bone.data.scaleY * frames[frames.Length - 1] - bone.scaleY) * alpha; - return; - } - - // Interpolate between the previous frame and the current frame. - int frameIndex = Animation.binarySearch(frames, time, 3); - float prevFrameX = frames[frameIndex - 2]; - float prevFrameY = frames[frameIndex - 1]; - float frameTime = frames[frameIndex]; - float percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime); - percent = GetCurvePercent(frameIndex / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); - - bone.scaleX += (bone.data.scaleX * (prevFrameX + (frames[frameIndex + FRAME_X] - prevFrameX) * percent) - bone.scaleX) * alpha; - bone.scaleY += (bone.data.scaleY * (prevFrameY + (frames[frameIndex + FRAME_Y] - prevFrameY) * percent) - bone.scaleY) * alpha; - } - } - - public class ColorTimeline : CurveTimeline { - protected const int PREV_FRAME_TIME = -5; - protected const int FRAME_R = 1; - protected const int FRAME_G = 2; - protected const int FRAME_B = 3; - protected const int FRAME_A = 4; - - internal int slotIndex; - internal float[] frames; - - public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, r, g, b, a, ... - - public ColorTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * 5]; - } - - /// Sets the time and value of the specified keyframe. - public void setFrame (int frameIndex, float time, float r, float g, float b, float a) { - frameIndex *= 5; - frames[frameIndex] = time; - frames[frameIndex + 1] = r; - frames[frameIndex + 2] = g; - frames[frameIndex + 3] = b; - frames[frameIndex + 4] = a; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, List firedEvents, float alpha) { - float[] frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - float r, g, b, a; - if (time >= frames[frames.Length - 5]) { - // Time is after last frame. - int i = frames.Length - 1; - r = frames[i - 3]; - g = frames[i - 2]; - b = frames[i - 1]; - a = frames[i]; - } else { - // Interpolate between the previous frame and the current frame. - int frameIndex = Animation.binarySearch(frames, time, 5); - float prevFrameR = frames[frameIndex - 4]; - float prevFrameG = frames[frameIndex - 3]; - float prevFrameB = frames[frameIndex - 2]; - float prevFrameA = frames[frameIndex - 1]; - float frameTime = frames[frameIndex]; - float percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime); - percent = GetCurvePercent(frameIndex / 5 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); - - r = prevFrameR + (frames[frameIndex + FRAME_R] - prevFrameR) * percent; - g = prevFrameG + (frames[frameIndex + FRAME_G] - prevFrameG) * percent; - b = prevFrameB + (frames[frameIndex + FRAME_B] - prevFrameB) * percent; - a = prevFrameA + (frames[frameIndex + FRAME_A] - prevFrameA) * percent; - } - Slot slot = skeleton.slots[slotIndex]; - if (alpha < 1) { - slot.r += (r - slot.r) * alpha; - slot.g += (g - slot.g) * alpha; - slot.b += (b - slot.b) * alpha; - slot.a += (a - slot.a) * alpha; - } else { - slot.r = r; - slot.g = g; - slot.b = b; - slot.a = a; - } - } - } - - public class AttachmentTimeline : Timeline { - internal int slotIndex; - internal float[] frames; - private String[] attachmentNames; - - public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, ... - public String[] AttachmentNames { get { return attachmentNames; } set { attachmentNames = value; } } - public int FrameCount { get { return frames.Length; } } - - public AttachmentTimeline (int frameCount) { - frames = new float[frameCount]; - attachmentNames = new String[frameCount]; - } - - /// Sets the time and value of the specified keyframe. - public void setFrame (int frameIndex, float time, String attachmentName) { - frames[frameIndex] = time; - attachmentNames[frameIndex] = attachmentName; - } - - public void Apply (Skeleton skeleton, float lastTime, float time, List firedEvents, float alpha) { - float[] frames = this.frames; - if (time < frames[0]) { - if (lastTime > time) Apply(skeleton, lastTime, int.MaxValue, null, 0); - return; - } else if (lastTime > time) // - lastTime = -1; - - int frameIndex = (time >= frames[frames.Length - 1] ? frames.Length : Animation.binarySearch(frames, time)) - 1; - if (frames[frameIndex] < lastTime) return; - - String attachmentName = attachmentNames[frameIndex]; - skeleton.slots[slotIndex].Attachment = - attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); - } - } - - public class EventTimeline : Timeline { - internal float[] frames; - private Event[] events; - - public float[] Frames { get { return frames; } set { frames = value; } } // time, ... - public Event[] Events { get { return events; } set { events = value; } } - public int FrameCount { get { return frames.Length; } } - - public EventTimeline (int frameCount) { - frames = new float[frameCount]; - events = new Event[frameCount]; - } - - /// Sets the time and value of the specified keyframe. - public void setFrame (int frameIndex, float time, Event e) { - frames[frameIndex] = time; - events[frameIndex] = e; - } - - /// Fires events for frames > lastTime and <= time. - public void Apply (Skeleton skeleton, float lastTime, float time, List firedEvents, float alpha) { - if (firedEvents == null) return; - float[] frames = this.frames; - int frameCount = frames.Length; - - if (lastTime > time) { // Fire events after last time for looped animations. - Apply(skeleton, lastTime, int.MaxValue, firedEvents, alpha); - lastTime = -1f; - } else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame. - return; - if (time < frames[0]) return; // Time is before first frame. - - int frameIndex; - if (lastTime < frames[0]) - frameIndex = 0; - else { - frameIndex = Animation.binarySearch(frames, lastTime); - float frame = frames[frameIndex]; - while (frameIndex > 0) { // Fire multiple events with the same frame. - if (frames[frameIndex - 1] != frame) break; - frameIndex--; - } - } - for (; frameIndex < frameCount && time >= frames[frameIndex]; frameIndex++) - firedEvents.Add(events[frameIndex]); - } - } - - public class DrawOrderTimeline : Timeline { - internal float[] frames; - private int[][] drawOrders; - - public float[] Frames { get { return frames; } set { frames = value; } } // time, ... - public int[][] DrawOrders { get { return drawOrders; } set { drawOrders = value; } } - public int FrameCount { get { return frames.Length; } } - - public DrawOrderTimeline (int frameCount) { - frames = new float[frameCount]; - drawOrders = new int[frameCount][]; - } - - /// Sets the time and value of the specified keyframe. - /// May be null to use bind pose draw order. - public void setFrame (int frameIndex, float time, int[] drawOrder) { - frames[frameIndex] = time; - drawOrders[frameIndex] = drawOrder; - } - - public void Apply (Skeleton skeleton, float lastTime, float time, List firedEvents, float alpha) { - float[] frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - int frameIndex; - if (time >= frames[frames.Length - 1]) // Time is after last frame. - frameIndex = frames.Length - 1; - else - frameIndex = Animation.binarySearch(frames, time) - 1; - - List drawOrder = skeleton.drawOrder; - List slots = skeleton.slots; - int[] drawOrderToSetupIndex = drawOrders[frameIndex]; - if (drawOrderToSetupIndex == null) { - drawOrder.Clear(); - drawOrder.AddRange(slots); - } else { - for (int i = 0, n = drawOrderToSetupIndex.Length; i < n; i++) - drawOrder[i] = slots[drawOrderToSetupIndex[i]]; - } - } - } - - public class FFDTimeline : CurveTimeline { - internal int slotIndex; - internal float[] frames; - private float[][] frameVertices; - internal Attachment attachment; - - public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, ... - public float[][] Vertices { get { return frameVertices; } set { frameVertices = value; } } - public Attachment Attachment { get { return attachment; } set { attachment = value; } } - - public FFDTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount]; - frameVertices = new float[frameCount][]; - } - - /// Sets the time and value of the specified keyframe. - public void setFrame (int frameIndex, float time, float[] vertices) { - frames[frameIndex] = time; - frameVertices[frameIndex] = vertices; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, List firedEvents, float alpha) { - Slot slot = skeleton.slots[slotIndex]; - if (slot.attachment != attachment) return; - - float[] frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - float[][] frameVertices = this.frameVertices; - int vertexCount = frameVertices[0].Length; - - float[] vertices = slot.attachmentVertices; - if (vertices.Length < vertexCount) { - vertices = new float[vertexCount]; - slot.attachmentVertices = vertices; - } else if (vertices.Length > vertexCount) - alpha = 1; // Don't mix from uninitialized slot vertices. - slot.attachmentVerticesCount = vertexCount; - - if (time >= frames[frames.Length - 1]) { // Time is after last frame. - float[] lastVertices = frameVertices[frames.Length - 1]; - if (alpha < 1) { - for (int i = 0; i < vertexCount; i++) { - float vertex = vertices[i]; - vertices[i] = vertex + (lastVertices[i] - vertex) * alpha; - } - } else - Array.Copy(lastVertices, 0, vertices, 0, vertexCount); - return; - } - - // Interpolate between the previous frame and the current frame. - int frameIndex = Animation.binarySearch(frames, time); - float frameTime = frames[frameIndex]; - float percent = 1 - (time - frameTime) / (frames[frameIndex - 1] - frameTime); - percent = GetCurvePercent(frameIndex - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); - - float[] prevVertices = frameVertices[frameIndex - 1]; - float[] nextVertices = frameVertices[frameIndex]; - - if (alpha < 1) { - for (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - float vertex = vertices[i]; - vertices[i] = vertex + (prev + (nextVertices[i] - prev) * percent - vertex) * alpha; - } - } else { - for (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - vertices[i] = prev + (nextVertices[i] - prev) * percent; - } - } - } - } - - public class IkConstraintTimeline : CurveTimeline { - private const int PREV_FRAME_TIME = -3; - private const int PREV_FRAME_MIX = -2; - private const int PREV_FRAME_BEND_DIRECTION = -1; - private const int FRAME_MIX = 1; - - internal int ikConstraintIndex; - internal float[] frames; - - public int IkConstraintIndex { get { return ikConstraintIndex; } set { ikConstraintIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, mix, bendDirection, ... - - public IkConstraintTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * 3]; - } - - /** Sets the time, mix and bend direction of the specified keyframe. */ - public void setFrame (int frameIndex, float time, float mix, int bendDirection) { - frameIndex *= 3; - frames[frameIndex] = time; - frames[frameIndex + 1] = mix; - frames[frameIndex + 2] = bendDirection; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, List firedEvents, float alpha) { - float[] frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - IkConstraint ikConstraint = skeleton.ikConstraints[ikConstraintIndex]; - - if (time >= frames[frames.Length - 3]) { // Time is after last frame. - ikConstraint.mix += (frames[frames.Length - 2] - ikConstraint.mix) * alpha; - ikConstraint.bendDirection = (int)frames[frames.Length - 1]; - return; - } - - // Interpolate between the previous frame and the current frame. - int frameIndex = Animation.binarySearch(frames, time, 3); - float prevFrameMix = frames[frameIndex + PREV_FRAME_MIX]; - float frameTime = frames[frameIndex]; - float percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime); - percent = GetCurvePercent(frameIndex / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); - - float mix = prevFrameMix + (frames[frameIndex + FRAME_MIX] - prevFrameMix) * percent; - ikConstraint.mix += (mix - ikConstraint.mix) * alpha; - ikConstraint.bendDirection = (int)frames[frameIndex + PREV_FRAME_BEND_DIRECTION]; - } - } - - public class FlipXTimeline : Timeline { - internal int boneIndex; - internal float[] frames; - - public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, flip, ... - public int FrameCount { get { return frames.Length >> 1; } } - - public FlipXTimeline (int frameCount) { - frames = new float[frameCount << 1]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, bool flip) { - frameIndex *= 2; - frames[frameIndex] = time; - frames[frameIndex + 1] = flip ? 1 : 0; - } - - public void Apply (Skeleton skeleton, float lastTime, float time, List firedEvents, float alpha) { - float[] frames = this.frames; - if (time < frames[0]) { - if (lastTime > time) Apply(skeleton, lastTime, int.MaxValue, null, 0); - return; - } else if (lastTime > time) // - lastTime = -1; - - int frameIndex = (time >= frames[frames.Length - 2] ? frames.Length : Animation.binarySearch(frames, time, 2)) - 2; - if (frames[frameIndex] < lastTime) return; - - SetFlip(skeleton.bones[boneIndex], frames[frameIndex + 1] != 0); - } - - virtual protected void SetFlip (Bone bone, bool flip) { - bone.flipX = flip; - } - } - - public class FlipYTimeline : FlipXTimeline { - public FlipYTimeline (int frameCount) - : base(frameCount) { - } - - override protected void SetFlip (Bone bone, bool flip) { - bone.flipY = flip; - } - } +namespace Spine2_1_08 +{ + public class Animation + { + internal List timelines; + internal float duration; + internal String name; + + public String Name { get { return name; } } + public List Timelines { get { return timelines; } set { timelines = value; } } + public float Duration { get { return duration; } set { duration = value; } } + + public Animation(String name, List timelines, float duration) + { + if (name == null) throw new ArgumentNullException("name cannot be null."); + if (timelines == null) throw new ArgumentNullException("timelines cannot be null."); + this.name = name; + this.timelines = timelines; + this.duration = duration; + } + + /// Poses the skeleton at the specified time for this animation. + /// The last time the animation was applied. + /// Any triggered events are added. + public void Apply(Skeleton skeleton, float lastTime, float time, bool loop, List events) + { + if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); + + if (loop && duration != 0) + { + time %= duration; + lastTime %= duration; + } + + List timelines = this.timelines; + for (int i = 0, n = timelines.Count; i < n; i++) + timelines[i].Apply(skeleton, lastTime, time, events, 1); + } + + /// Poses the skeleton at the specified time for this animation mixed with the current pose. + /// The last time the animation was applied. + /// Any triggered events are added. + /// The amount of this animation that affects the current pose. + public void Mix(Skeleton skeleton, float lastTime, float time, bool loop, List events, float alpha) + { + if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); + + if (loop && duration != 0) + { + time %= duration; + lastTime %= duration; + } + + List timelines = this.timelines; + for (int i = 0, n = timelines.Count; i < n; i++) + timelines[i].Apply(skeleton, lastTime, time, events, alpha); + } + + /// After the first and before the last entry. + internal static int binarySearch(float[] values, float target, int step) + { + int low = 0; + int high = values.Length / step - 2; + if (high == 0) return step; + int current = (int)((uint)high >> 1); + while (true) + { + if (values[(current + 1) * step] <= target) + low = current + 1; + else + high = current; + if (low == high) return (low + 1) * step; + current = (int)((uint)(low + high) >> 1); + } + } + + /// After the first and before the last entry. + internal static int binarySearch(float[] values, float target) + { + int low = 0; + int high = values.Length - 2; + if (high == 0) return 1; + int current = (int)((uint)high >> 1); + while (true) + { + if (values[(current + 1)] <= target) + low = current + 1; + else + high = current; + if (low == high) return (low + 1); + current = (int)((uint)(low + high) >> 1); + } + } + + internal static int linearSearch(float[] values, float target, int step) + { + for (int i = 0, last = values.Length - step; i <= last; i += step) + if (values[i] > target) return i; + return -1; + } + } + + public interface Timeline + { + /// Sets the value(s) for the specified time. + /// May be null to not collect fired events. + void Apply(Skeleton skeleton, float lastTime, float time, List events, float alpha); + } + + /// Base class for frames that use an interpolation bezier curve. + abstract public class CurveTimeline : Timeline + { + protected const float LINEAR = 0, STEPPED = 1, BEZIER = 2; + protected const int BEZIER_SEGMENTS = 10, BEZIER_SIZE = BEZIER_SEGMENTS * 2 - 1; + + private float[] curves; // type, x, y, ... + public int FrameCount { get { return curves.Length / BEZIER_SIZE + 1; } } + + public CurveTimeline(int frameCount) + { + curves = new float[(frameCount - 1) * BEZIER_SIZE]; + } + + abstract public void Apply(Skeleton skeleton, float lastTime, float time, List firedEvents, float alpha); + + public void SetLinear(int frameIndex) + { + curves[frameIndex * BEZIER_SIZE] = LINEAR; + } + + public void SetStepped(int frameIndex) + { + curves[frameIndex * BEZIER_SIZE] = STEPPED; + } + + /// Sets the control handle positions for an interpolation bezier curve used to transition from this keyframe to the next. + /// cx1 and cx2 are from 0 to 1, representing the percent of time between the two keyframes. cy1 and cy2 are the percent of + /// the difference between the keyframe's values. + public void SetCurve(int frameIndex, float cx1, float cy1, float cx2, float cy2) + { + float subdiv1 = 1f / BEZIER_SEGMENTS, subdiv2 = subdiv1 * subdiv1, subdiv3 = subdiv2 * subdiv1; + float pre1 = 3 * subdiv1, pre2 = 3 * subdiv2, pre4 = 6 * subdiv2, pre5 = 6 * subdiv3; + float tmp1x = -cx1 * 2 + cx2, tmp1y = -cy1 * 2 + cy2, tmp2x = (cx1 - cx2) * 3 + 1, tmp2y = (cy1 - cy2) * 3 + 1; + float dfx = cx1 * pre1 + tmp1x * pre2 + tmp2x * subdiv3, dfy = cy1 * pre1 + tmp1y * pre2 + tmp2y * subdiv3; + float ddfx = tmp1x * pre4 + tmp2x * pre5, ddfy = tmp1y * pre4 + tmp2y * pre5; + float dddfx = tmp2x * pre5, dddfy = tmp2y * pre5; + + int i = frameIndex * BEZIER_SIZE; + float[] curves = this.curves; + curves[i++] = BEZIER; + + float x = dfx, y = dfy; + for (int n = i + BEZIER_SIZE - 1; i < n; i += 2) + { + curves[i] = x; + curves[i + 1] = y; + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + x += dfx; + y += dfy; + } + } + + public float GetCurvePercent(int frameIndex, float percent) + { + float[] curves = this.curves; + int i = frameIndex * BEZIER_SIZE; + float type = curves[i]; + if (type == LINEAR) return percent; + if (type == STEPPED) return 0; + i++; + float x = 0; + for (int start = i, n = i + BEZIER_SIZE - 1; i < n; i += 2) + { + x = curves[i]; + if (x >= percent) + { + float prevX, prevY; + if (i == start) + { + prevX = 0; + prevY = 0; + } + else + { + prevX = curves[i - 2]; + prevY = curves[i - 1]; + } + return prevY + (curves[i + 1] - prevY) * (percent - prevX) / (x - prevX); + } + } + float y = curves[i - 1]; + return y + (1 - y) * (percent - x) / (1 - x); // Last point is 1,1. + } + } + + public class RotateTimeline : CurveTimeline + { + protected const int PREV_FRAME_TIME = -2; + protected const int FRAME_VALUE = 1; + + internal int boneIndex; + internal float[] frames; + + public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, value, ... + + public RotateTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount << 1]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, float angle) + { + frameIndex *= 2; + frames[frameIndex] = time; + frames[frameIndex + 1] = angle; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, List firedEvents, float alpha) + { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + Bone bone = skeleton.bones[boneIndex]; + + float amount; + + if (time >= frames[frames.Length - 2]) + { // Time is after last frame. + amount = bone.data.rotation + frames[frames.Length - 1] - bone.rotation; + while (amount > 180) + amount -= 360; + while (amount < -180) + amount += 360; + bone.rotation += amount * alpha; + return; + } + + // Interpolate between the previous frame and the current frame. + int frameIndex = Animation.binarySearch(frames, time, 2); + float prevFrameValue = frames[frameIndex - 1]; + float frameTime = frames[frameIndex]; + float percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime); + percent = GetCurvePercent((frameIndex >> 1) - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); + + amount = frames[frameIndex + FRAME_VALUE] - prevFrameValue; + while (amount > 180) + amount -= 360; + while (amount < -180) + amount += 360; + amount = bone.data.rotation + (prevFrameValue + amount * percent) - bone.rotation; + while (amount > 180) + amount -= 360; + while (amount < -180) + amount += 360; + bone.rotation += amount * alpha; + } + } + + public class TranslateTimeline : CurveTimeline + { + protected const int PREV_FRAME_TIME = -3; + protected const int FRAME_X = 1; + protected const int FRAME_Y = 2; + + internal int boneIndex; + internal float[] frames; + + public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, value, value, ... + + public TranslateTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * 3]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, float x, float y) + { + frameIndex *= 3; + frames[frameIndex] = time; + frames[frameIndex + 1] = x; + frames[frameIndex + 2] = y; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, List firedEvents, float alpha) + { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + Bone bone = skeleton.bones[boneIndex]; + + if (time >= frames[frames.Length - 3]) + { // Time is after last frame. + bone.x += (bone.data.x + frames[frames.Length - 2] - bone.x) * alpha; + bone.y += (bone.data.y + frames[frames.Length - 1] - bone.y) * alpha; + return; + } + + // Interpolate between the previous frame and the current frame. + int frameIndex = Animation.binarySearch(frames, time, 3); + float prevFrameX = frames[frameIndex - 2]; + float prevFrameY = frames[frameIndex - 1]; + float frameTime = frames[frameIndex]; + float percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime); + percent = GetCurvePercent(frameIndex / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); + + bone.x += (bone.data.x + prevFrameX + (frames[frameIndex + FRAME_X] - prevFrameX) * percent - bone.x) * alpha; + bone.y += (bone.data.y + prevFrameY + (frames[frameIndex + FRAME_Y] - prevFrameY) * percent - bone.y) * alpha; + } + } + + public class ScaleTimeline : TranslateTimeline + { + public ScaleTimeline(int frameCount) + : base(frameCount) + { + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, List firedEvents, float alpha) + { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + Bone bone = skeleton.bones[boneIndex]; + if (time >= frames[frames.Length - 3]) + { // Time is after last frame. + bone.scaleX += (bone.data.scaleX * frames[frames.Length - 2] - bone.scaleX) * alpha; + bone.scaleY += (bone.data.scaleY * frames[frames.Length - 1] - bone.scaleY) * alpha; + return; + } + + // Interpolate between the previous frame and the current frame. + int frameIndex = Animation.binarySearch(frames, time, 3); + float prevFrameX = frames[frameIndex - 2]; + float prevFrameY = frames[frameIndex - 1]; + float frameTime = frames[frameIndex]; + float percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime); + percent = GetCurvePercent(frameIndex / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); + + bone.scaleX += (bone.data.scaleX * (prevFrameX + (frames[frameIndex + FRAME_X] - prevFrameX) * percent) - bone.scaleX) * alpha; + bone.scaleY += (bone.data.scaleY * (prevFrameY + (frames[frameIndex + FRAME_Y] - prevFrameY) * percent) - bone.scaleY) * alpha; + } + } + + public class ColorTimeline : CurveTimeline + { + protected const int PREV_FRAME_TIME = -5; + protected const int FRAME_R = 1; + protected const int FRAME_G = 2; + protected const int FRAME_B = 3; + protected const int FRAME_A = 4; + + internal int slotIndex; + internal float[] frames; + + public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, r, g, b, a, ... + + public ColorTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * 5]; + } + + /// Sets the time and value of the specified keyframe. + public void setFrame(int frameIndex, float time, float r, float g, float b, float a) + { + frameIndex *= 5; + frames[frameIndex] = time; + frames[frameIndex + 1] = r; + frames[frameIndex + 2] = g; + frames[frameIndex + 3] = b; + frames[frameIndex + 4] = a; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, List firedEvents, float alpha) + { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + float r, g, b, a; + if (time >= frames[frames.Length - 5]) + { + // Time is after last frame. + int i = frames.Length - 1; + r = frames[i - 3]; + g = frames[i - 2]; + b = frames[i - 1]; + a = frames[i]; + } + else + { + // Interpolate between the previous frame and the current frame. + int frameIndex = Animation.binarySearch(frames, time, 5); + float prevFrameR = frames[frameIndex - 4]; + float prevFrameG = frames[frameIndex - 3]; + float prevFrameB = frames[frameIndex - 2]; + float prevFrameA = frames[frameIndex - 1]; + float frameTime = frames[frameIndex]; + float percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime); + percent = GetCurvePercent(frameIndex / 5 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); + + r = prevFrameR + (frames[frameIndex + FRAME_R] - prevFrameR) * percent; + g = prevFrameG + (frames[frameIndex + FRAME_G] - prevFrameG) * percent; + b = prevFrameB + (frames[frameIndex + FRAME_B] - prevFrameB) * percent; + a = prevFrameA + (frames[frameIndex + FRAME_A] - prevFrameA) * percent; + } + Slot slot = skeleton.slots[slotIndex]; + if (alpha < 1) + { + slot.r += (r - slot.r) * alpha; + slot.g += (g - slot.g) * alpha; + slot.b += (b - slot.b) * alpha; + slot.a += (a - slot.a) * alpha; + } + else + { + slot.r = r; + slot.g = g; + slot.b = b; + slot.a = a; + } + } + } + + public class AttachmentTimeline : Timeline + { + internal int slotIndex; + internal float[] frames; + private String[] attachmentNames; + + public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, ... + public String[] AttachmentNames { get { return attachmentNames; } set { attachmentNames = value; } } + public int FrameCount { get { return frames.Length; } } + + public AttachmentTimeline(int frameCount) + { + frames = new float[frameCount]; + attachmentNames = new String[frameCount]; + } + + /// Sets the time and value of the specified keyframe. + public void setFrame(int frameIndex, float time, String attachmentName) + { + frames[frameIndex] = time; + attachmentNames[frameIndex] = attachmentName; + } + + public void Apply(Skeleton skeleton, float lastTime, float time, List firedEvents, float alpha) + { + float[] frames = this.frames; + if (time < frames[0]) + { + if (lastTime > time) Apply(skeleton, lastTime, int.MaxValue, null, 0); + return; + } + else if (lastTime > time) // + lastTime = -1; + + int frameIndex = (time >= frames[frames.Length - 1] ? frames.Length : Animation.binarySearch(frames, time)) - 1; + if (frames[frameIndex] < lastTime) return; + + String attachmentName = attachmentNames[frameIndex]; + skeleton.slots[slotIndex].Attachment = + attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); + } + } + + public class EventTimeline : Timeline + { + internal float[] frames; + private Event[] events; + + public float[] Frames { get { return frames; } set { frames = value; } } // time, ... + public Event[] Events { get { return events; } set { events = value; } } + public int FrameCount { get { return frames.Length; } } + + public EventTimeline(int frameCount) + { + frames = new float[frameCount]; + events = new Event[frameCount]; + } + + /// Sets the time and value of the specified keyframe. + public void setFrame(int frameIndex, float time, Event e) + { + frames[frameIndex] = time; + events[frameIndex] = e; + } + + /// Fires events for frames > lastTime and <= time. + public void Apply(Skeleton skeleton, float lastTime, float time, List firedEvents, float alpha) + { + if (firedEvents == null) return; + float[] frames = this.frames; + int frameCount = frames.Length; + + if (lastTime > time) + { // Fire events after last time for looped animations. + Apply(skeleton, lastTime, int.MaxValue, firedEvents, alpha); + lastTime = -1f; + } + else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame. + return; + if (time < frames[0]) return; // Time is before first frame. + + int frameIndex; + if (lastTime < frames[0]) + frameIndex = 0; + else + { + frameIndex = Animation.binarySearch(frames, lastTime); + float frame = frames[frameIndex]; + while (frameIndex > 0) + { // Fire multiple events with the same frame. + if (frames[frameIndex - 1] != frame) break; + frameIndex--; + } + } + for (; frameIndex < frameCount && time >= frames[frameIndex]; frameIndex++) + firedEvents.Add(events[frameIndex]); + } + } + + public class DrawOrderTimeline : Timeline + { + internal float[] frames; + private int[][] drawOrders; + + public float[] Frames { get { return frames; } set { frames = value; } } // time, ... + public int[][] DrawOrders { get { return drawOrders; } set { drawOrders = value; } } + public int FrameCount { get { return frames.Length; } } + + public DrawOrderTimeline(int frameCount) + { + frames = new float[frameCount]; + drawOrders = new int[frameCount][]; + } + + /// Sets the time and value of the specified keyframe. + /// May be null to use bind pose draw order. + public void setFrame(int frameIndex, float time, int[] drawOrder) + { + frames[frameIndex] = time; + drawOrders[frameIndex] = drawOrder; + } + + public void Apply(Skeleton skeleton, float lastTime, float time, List firedEvents, float alpha) + { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + int frameIndex; + if (time >= frames[frames.Length - 1]) // Time is after last frame. + frameIndex = frames.Length - 1; + else + frameIndex = Animation.binarySearch(frames, time) - 1; + + List drawOrder = skeleton.drawOrder; + List slots = skeleton.slots; + int[] drawOrderToSetupIndex = drawOrders[frameIndex]; + if (drawOrderToSetupIndex == null) + { + drawOrder.Clear(); + drawOrder.AddRange(slots); + } + else + { + for (int i = 0, n = drawOrderToSetupIndex.Length; i < n; i++) + drawOrder[i] = slots[drawOrderToSetupIndex[i]]; + } + } + } + + public class FFDTimeline : CurveTimeline + { + internal int slotIndex; + internal float[] frames; + private float[][] frameVertices; + internal Attachment attachment; + + public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, ... + public float[][] Vertices { get { return frameVertices; } set { frameVertices = value; } } + public Attachment Attachment { get { return attachment; } set { attachment = value; } } + + public FFDTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount]; + frameVertices = new float[frameCount][]; + } + + /// Sets the time and value of the specified keyframe. + public void setFrame(int frameIndex, float time, float[] vertices) + { + frames[frameIndex] = time; + frameVertices[frameIndex] = vertices; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, List firedEvents, float alpha) + { + Slot slot = skeleton.slots[slotIndex]; + if (slot.attachment != attachment) return; + + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + float[][] frameVertices = this.frameVertices; + int vertexCount = frameVertices[0].Length; + + float[] vertices = slot.attachmentVertices; + if (vertices.Length < vertexCount) + { + vertices = new float[vertexCount]; + slot.attachmentVertices = vertices; + } + else if (vertices.Length > vertexCount) + alpha = 1; // Don't mix from uninitialized slot vertices. + slot.attachmentVerticesCount = vertexCount; + + if (time >= frames[frames.Length - 1]) + { // Time is after last frame. + float[] lastVertices = frameVertices[frames.Length - 1]; + if (alpha < 1) + { + for (int i = 0; i < vertexCount; i++) + { + float vertex = vertices[i]; + vertices[i] = vertex + (lastVertices[i] - vertex) * alpha; + } + } + else + Array.Copy(lastVertices, 0, vertices, 0, vertexCount); + return; + } + + // Interpolate between the previous frame and the current frame. + int frameIndex = Animation.binarySearch(frames, time); + float frameTime = frames[frameIndex]; + float percent = 1 - (time - frameTime) / (frames[frameIndex - 1] - frameTime); + percent = GetCurvePercent(frameIndex - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); + + float[] prevVertices = frameVertices[frameIndex - 1]; + float[] nextVertices = frameVertices[frameIndex]; + + if (alpha < 1) + { + for (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i]; + float vertex = vertices[i]; + vertices[i] = vertex + (prev + (nextVertices[i] - prev) * percent - vertex) * alpha; + } + } + else + { + for (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i]; + vertices[i] = prev + (nextVertices[i] - prev) * percent; + } + } + } + } + + public class IkConstraintTimeline : CurveTimeline + { + private const int PREV_FRAME_TIME = -3; + private const int PREV_FRAME_MIX = -2; + private const int PREV_FRAME_BEND_DIRECTION = -1; + private const int FRAME_MIX = 1; + + internal int ikConstraintIndex; + internal float[] frames; + + public int IkConstraintIndex { get { return ikConstraintIndex; } set { ikConstraintIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, mix, bendDirection, ... + + public IkConstraintTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * 3]; + } + + /** Sets the time, mix and bend direction of the specified keyframe. */ + public void setFrame(int frameIndex, float time, float mix, int bendDirection) + { + frameIndex *= 3; + frames[frameIndex] = time; + frames[frameIndex + 1] = mix; + frames[frameIndex + 2] = bendDirection; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, List firedEvents, float alpha) + { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + IkConstraint ikConstraint = skeleton.ikConstraints[ikConstraintIndex]; + + if (time >= frames[frames.Length - 3]) + { // Time is after last frame. + ikConstraint.mix += (frames[frames.Length - 2] - ikConstraint.mix) * alpha; + ikConstraint.bendDirection = (int)frames[frames.Length - 1]; + return; + } + + // Interpolate between the previous frame and the current frame. + int frameIndex = Animation.binarySearch(frames, time, 3); + float prevFrameMix = frames[frameIndex + PREV_FRAME_MIX]; + float frameTime = frames[frameIndex]; + float percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime); + percent = GetCurvePercent(frameIndex / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); + + float mix = prevFrameMix + (frames[frameIndex + FRAME_MIX] - prevFrameMix) * percent; + ikConstraint.mix += (mix - ikConstraint.mix) * alpha; + ikConstraint.bendDirection = (int)frames[frameIndex + PREV_FRAME_BEND_DIRECTION]; + } + } + + public class FlipXTimeline : Timeline + { + internal int boneIndex; + internal float[] frames; + + public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, flip, ... + public int FrameCount { get { return frames.Length >> 1; } } + + public FlipXTimeline(int frameCount) + { + frames = new float[frameCount << 1]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, bool flip) + { + frameIndex *= 2; + frames[frameIndex] = time; + frames[frameIndex + 1] = flip ? 1 : 0; + } + + public void Apply(Skeleton skeleton, float lastTime, float time, List firedEvents, float alpha) + { + float[] frames = this.frames; + if (time < frames[0]) + { + if (lastTime > time) Apply(skeleton, lastTime, int.MaxValue, null, 0); + return; + } + else if (lastTime > time) // + lastTime = -1; + + int frameIndex = (time >= frames[frames.Length - 2] ? frames.Length : Animation.binarySearch(frames, time, 2)) - 2; + if (frames[frameIndex] < lastTime) return; + + SetFlip(skeleton.bones[boneIndex], frames[frameIndex + 1] != 0); + } + + virtual protected void SetFlip(Bone bone, bool flip) + { + bone.flipX = flip; + } + } + + public class FlipYTimeline : FlipXTimeline + { + public FlipYTimeline(int frameCount) + : base(frameCount) + { + } + + override protected void SetFlip(Bone bone, bool flip) + { + bone.flipY = flip; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/AnimationState.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/AnimationState.cs index 0d11eb5..b24ffa7 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/AnimationState.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/AnimationState.cs @@ -32,268 +32,307 @@ using System.Collections.Generic; using System.Text; -namespace Spine2_1_08 { - public class AnimationState { - private AnimationStateData data; - private List tracks = new List(); - private List events = new List(); - private float timeScale = 1; - - public AnimationStateData Data { get { return data; } } - public float TimeScale { get { return timeScale; } set { timeScale = value; } } - - public delegate void StartEndDelegate(AnimationState state, int trackIndex); - public event StartEndDelegate Start; - public event StartEndDelegate End; - - public delegate void EventDelegate(AnimationState state, int trackIndex, Event e); - public event EventDelegate Event; - - public delegate void CompleteDelegate(AnimationState state, int trackIndex, int loopCount); - public event CompleteDelegate Complete; - - public AnimationState (AnimationStateData data) { - if (data == null) throw new ArgumentNullException("data cannot be null."); - this.data = data; - } - - public void Update (float delta) { - delta *= timeScale; - for (int i = 0; i < tracks.Count; i++) { - TrackEntry current = tracks[i]; - if (current == null) continue; - - float trackDelta = delta * current.timeScale; - float time = current.time + trackDelta; - float endTime = current.endTime; - - current.time = time; - if (current.previous != null) { - current.previous.time += trackDelta; - current.mixTime += trackDelta; - } - - // Check if completed the animation or a loop iteration. - if (current.loop ? (current.lastTime % endTime > time % endTime) : (current.lastTime < endTime && time >= endTime)) { - int count = (int)(time / endTime); - current.OnComplete(this, i, count); - if (Complete != null) Complete(this, i, count); - } - - TrackEntry next = current.next; - if (next != null) { - next.time = current.lastTime - next.delay; - if (next.time >= 0) SetCurrent(i, next); - } else { - // End non-looping animation when it reaches its end time and there is no next entry. - if (!current.loop && current.lastTime >= current.endTime) ClearTrack(i); - } - } - } - - public void Apply (Skeleton skeleton) { - List events = this.events; - - for (int i = 0; i < tracks.Count; i++) { - TrackEntry current = tracks[i]; - if (current == null) continue; - - events.Clear(); - - float time = current.time; - bool loop = current.loop; - if (!loop && time > current.endTime) time = current.endTime; - - TrackEntry previous = current.previous; - if (previous == null) { - if (current.mix == 1) - current.animation.Apply(skeleton, current.lastTime, time, loop, events); - else - current.animation.Mix(skeleton, current.lastTime, time, loop, events, current.mix); - } else { - float previousTime = previous.time; - if (!previous.loop && previousTime > previous.endTime) previousTime = previous.endTime; - previous.animation.Apply(skeleton, previousTime, previousTime, previous.loop, null); - - float alpha = current.mixTime / current.mixDuration * current.mix; - if (alpha >= 1) { - alpha = 1; - current.previous = null; - } - current.animation.Mix(skeleton, current.lastTime, time, loop, events, alpha); - } - - for (int ii = 0, nn = events.Count; ii < nn; ii++) { - Event e = events[ii]; - current.OnEvent(this, i, e); - if (Event != null) Event(this, i, e); - } - - current.lastTime = current.time; - } - } - - public void ClearTracks () { - for (int i = 0, n = tracks.Count; i < n; i++) - ClearTrack(i); - tracks.Clear(); - } - - public void ClearTrack (int trackIndex) { - if (trackIndex >= tracks.Count) return; - TrackEntry current = tracks[trackIndex]; - if (current == null) return; - - current.OnEnd(this, trackIndex); - if (End != null) End(this, trackIndex); - - tracks[trackIndex] = null; - } - - private TrackEntry ExpandToIndex (int index) { - if (index < tracks.Count) return tracks[index]; - while (index >= tracks.Count) - tracks.Add(null); - return null; - } - - private void SetCurrent (int index, TrackEntry entry) { - TrackEntry current = ExpandToIndex(index); - if (current != null) { - TrackEntry previous = current.previous; - current.previous = null; - - current.OnEnd(this, index); - if (End != null) End(this, index); - - entry.mixDuration = data.GetMix(current.animation, entry.animation); - if (entry.mixDuration > 0) { - entry.mixTime = 0; - // If a mix is in progress, mix from the closest animation. - if (previous != null && current.mixTime / current.mixDuration < 0.5f) - entry.previous = previous; - else - entry.previous = current; - } - } - - tracks[index] = entry; - - entry.OnStart(this, index); - if (Start != null) Start(this, index); - } - - public TrackEntry SetAnimation (int trackIndex, String animationName, bool loop) { - Animation animation = data.skeletonData.FindAnimation(animationName); - if (animation == null) throw new ArgumentException("Animation not found: " + animationName); - return SetAnimation(trackIndex, animation, loop); - } - - /// Set the current animation. Any queued animations are cleared. - public TrackEntry SetAnimation (int trackIndex, Animation animation, bool loop) { - if (animation == null) throw new ArgumentException("animation cannot be null."); - TrackEntry entry = new TrackEntry(); - entry.animation = animation; - entry.loop = loop; - entry.time = 0; - entry.endTime = animation.Duration; - SetCurrent(trackIndex, entry); - return entry; - } - - public TrackEntry AddAnimation (int trackIndex, String animationName, bool loop, float delay) { - Animation animation = data.skeletonData.FindAnimation(animationName); - if (animation == null) throw new ArgumentException("Animation not found: " + animationName); - return AddAnimation(trackIndex, animation, loop, delay); - } - - /// Adds an animation to be played delay seconds after the current or last queued animation. - /// May be <= 0 to use duration of previous animation minus any mix duration plus the negative delay. - public TrackEntry AddAnimation (int trackIndex, Animation animation, bool loop, float delay) { - if (animation == null) throw new ArgumentException("animation cannot be null."); - TrackEntry entry = new TrackEntry(); - entry.animation = animation; - entry.loop = loop; - entry.time = 0; - entry.endTime = animation.Duration; - - TrackEntry last = ExpandToIndex(trackIndex); - if (last != null) { - while (last.next != null) - last = last.next; - last.next = entry; - } else - tracks[trackIndex] = entry; - - if (delay <= 0) { - if (last != null) - delay += last.endTime - data.GetMix(last.animation, animation); - else - delay = 0; - } - entry.delay = delay; - - return entry; - } - - /// May be null. - public TrackEntry GetCurrent (int trackIndex) { - if (trackIndex >= tracks.Count) return null; - return tracks[trackIndex]; - } - - override public String ToString () { - StringBuilder buffer = new StringBuilder(); - for (int i = 0, n = tracks.Count; i < n; i++) { - TrackEntry entry = tracks[i]; - if (entry == null) continue; - if (buffer.Length > 0) buffer.Append(", "); - buffer.Append(entry.ToString()); - } - if (buffer.Length == 0) return ""; - return buffer.ToString(); - } - } - - public class TrackEntry { - internal TrackEntry next, previous; - internal Animation animation; - internal bool loop; - internal float delay, time, lastTime = -1, endTime, timeScale = 1; - internal float mixTime, mixDuration, mix = 1; - - public Animation Animation { get { return animation; } } - public float Delay { get { return delay; } set { delay = value; } } - public float Time { get { return time; } set { time = value; } } - public float LastTime { get { return lastTime; } set { lastTime = value; } } - public float EndTime { get { return endTime; } set { endTime = value; } } - public float TimeScale { get { return timeScale; } set { timeScale = value; } } - public float Mix { get { return mix; } set { mix = value; } } - public bool Loop { get { return loop; } set { loop = value; } } - - public event AnimationState.StartEndDelegate Start; - public event AnimationState.StartEndDelegate End; - public event AnimationState.EventDelegate Event; - public event AnimationState.CompleteDelegate Complete; - - internal void OnStart (AnimationState state, int index) { - if (Start != null) Start(state, index); - } - - internal void OnEnd (AnimationState state, int index) { - if (End != null) End(state, index); - } - - internal void OnEvent (AnimationState state, int index, Event e) { - if (Event != null) Event(state, index, e); - } - - internal void OnComplete (AnimationState state, int index, int loopCount) { - if (Complete != null) Complete(state, index, loopCount); - } - - override public String ToString () { - return animation == null ? "" : animation.name; - } - } +namespace Spine2_1_08 +{ + public class AnimationState + { + private AnimationStateData data; + private List tracks = new List(); + private List events = new List(); + private float timeScale = 1; + + public AnimationStateData Data { get { return data; } } + public float TimeScale { get { return timeScale; } set { timeScale = value; } } + + public delegate void StartEndDelegate(AnimationState state, int trackIndex); + public event StartEndDelegate Start; + public event StartEndDelegate End; + + public delegate void EventDelegate(AnimationState state, int trackIndex, Event e); + public event EventDelegate Event; + + public delegate void CompleteDelegate(AnimationState state, int trackIndex, int loopCount); + public event CompleteDelegate Complete; + + public AnimationState(AnimationStateData data) + { + if (data == null) throw new ArgumentNullException("data cannot be null."); + this.data = data; + } + + public void Update(float delta) + { + delta *= timeScale; + for (int i = 0; i < tracks.Count; i++) + { + TrackEntry current = tracks[i]; + if (current == null) continue; + + float trackDelta = delta * current.timeScale; + float time = current.time + trackDelta; + float endTime = current.endTime; + + current.time = time; + if (current.previous != null) + { + current.previous.time += trackDelta; + current.mixTime += trackDelta; + } + + // Check if completed the animation or a loop iteration. + if (current.loop ? (current.lastTime % endTime > time % endTime) : (current.lastTime < endTime && time >= endTime)) + { + int count = (int)(time / endTime); + current.OnComplete(this, i, count); + if (Complete != null) Complete(this, i, count); + } + + TrackEntry next = current.next; + if (next != null) + { + next.time = current.lastTime - next.delay; + if (next.time >= 0) SetCurrent(i, next); + } + else + { + // End non-looping animation when it reaches its end time and there is no next entry. + if (!current.loop && current.lastTime >= current.endTime) ClearTrack(i); + } + } + } + + public void Apply(Skeleton skeleton) + { + List events = this.events; + + for (int i = 0; i < tracks.Count; i++) + { + TrackEntry current = tracks[i]; + if (current == null) continue; + + events.Clear(); + + float time = current.time; + bool loop = current.loop; + if (!loop && time > current.endTime) time = current.endTime; + + TrackEntry previous = current.previous; + if (previous == null) + { + if (current.mix == 1) + current.animation.Apply(skeleton, current.lastTime, time, loop, events); + else + current.animation.Mix(skeleton, current.lastTime, time, loop, events, current.mix); + } + else + { + float previousTime = previous.time; + if (!previous.loop && previousTime > previous.endTime) previousTime = previous.endTime; + previous.animation.Apply(skeleton, previousTime, previousTime, previous.loop, null); + + float alpha = current.mixTime / current.mixDuration * current.mix; + if (alpha >= 1) + { + alpha = 1; + current.previous = null; + } + current.animation.Mix(skeleton, current.lastTime, time, loop, events, alpha); + } + + for (int ii = 0, nn = events.Count; ii < nn; ii++) + { + Event e = events[ii]; + current.OnEvent(this, i, e); + if (Event != null) Event(this, i, e); + } + + current.lastTime = current.time; + } + } + + public void ClearTracks() + { + for (int i = 0, n = tracks.Count; i < n; i++) + ClearTrack(i); + tracks.Clear(); + } + + public void ClearTrack(int trackIndex) + { + if (trackIndex >= tracks.Count) return; + TrackEntry current = tracks[trackIndex]; + if (current == null) return; + + current.OnEnd(this, trackIndex); + if (End != null) End(this, trackIndex); + + tracks[trackIndex] = null; + } + + private TrackEntry ExpandToIndex(int index) + { + if (index < tracks.Count) return tracks[index]; + while (index >= tracks.Count) + tracks.Add(null); + return null; + } + + private void SetCurrent(int index, TrackEntry entry) + { + TrackEntry current = ExpandToIndex(index); + if (current != null) + { + TrackEntry previous = current.previous; + current.previous = null; + + current.OnEnd(this, index); + if (End != null) End(this, index); + + entry.mixDuration = data.GetMix(current.animation, entry.animation); + if (entry.mixDuration > 0) + { + entry.mixTime = 0; + // If a mix is in progress, mix from the closest animation. + if (previous != null && current.mixTime / current.mixDuration < 0.5f) + entry.previous = previous; + else + entry.previous = current; + } + } + + tracks[index] = entry; + + entry.OnStart(this, index); + if (Start != null) Start(this, index); + } + + public TrackEntry SetAnimation(int trackIndex, String animationName, bool loop) + { + Animation animation = data.skeletonData.FindAnimation(animationName); + if (animation == null) throw new ArgumentException("Animation not found: " + animationName); + return SetAnimation(trackIndex, animation, loop); + } + + /// Set the current animation. Any queued animations are cleared. + public TrackEntry SetAnimation(int trackIndex, Animation animation, bool loop) + { + if (animation == null) throw new ArgumentException("animation cannot be null."); + TrackEntry entry = new TrackEntry(); + entry.animation = animation; + entry.loop = loop; + entry.time = 0; + entry.endTime = animation.Duration; + SetCurrent(trackIndex, entry); + return entry; + } + + public TrackEntry AddAnimation(int trackIndex, String animationName, bool loop, float delay) + { + Animation animation = data.skeletonData.FindAnimation(animationName); + if (animation == null) throw new ArgumentException("Animation not found: " + animationName); + return AddAnimation(trackIndex, animation, loop, delay); + } + + /// Adds an animation to be played delay seconds after the current or last queued animation. + /// May be <= 0 to use duration of previous animation minus any mix duration plus the negative delay. + public TrackEntry AddAnimation(int trackIndex, Animation animation, bool loop, float delay) + { + if (animation == null) throw new ArgumentException("animation cannot be null."); + TrackEntry entry = new TrackEntry(); + entry.animation = animation; + entry.loop = loop; + entry.time = 0; + entry.endTime = animation.Duration; + + TrackEntry last = ExpandToIndex(trackIndex); + if (last != null) + { + while (last.next != null) + last = last.next; + last.next = entry; + } + else + tracks[trackIndex] = entry; + + if (delay <= 0) + { + if (last != null) + delay += last.endTime - data.GetMix(last.animation, animation); + else + delay = 0; + } + entry.delay = delay; + + return entry; + } + + /// May be null. + public TrackEntry GetCurrent(int trackIndex) + { + if (trackIndex >= tracks.Count) return null; + return tracks[trackIndex]; + } + + override public String ToString() + { + StringBuilder buffer = new StringBuilder(); + for (int i = 0, n = tracks.Count; i < n; i++) + { + TrackEntry entry = tracks[i]; + if (entry == null) continue; + if (buffer.Length > 0) buffer.Append(", "); + buffer.Append(entry.ToString()); + } + if (buffer.Length == 0) return ""; + return buffer.ToString(); + } + } + + public class TrackEntry + { + internal TrackEntry next, previous; + internal Animation animation; + internal bool loop; + internal float delay, time, lastTime = -1, endTime, timeScale = 1; + internal float mixTime, mixDuration, mix = 1; + + public Animation Animation { get { return animation; } } + public float Delay { get { return delay; } set { delay = value; } } + public float Time { get { return time; } set { time = value; } } + public float LastTime { get { return lastTime; } set { lastTime = value; } } + public float EndTime { get { return endTime; } set { endTime = value; } } + public float TimeScale { get { return timeScale; } set { timeScale = value; } } + public float Mix { get { return mix; } set { mix = value; } } + public bool Loop { get { return loop; } set { loop = value; } } + + public event AnimationState.StartEndDelegate Start; + public event AnimationState.StartEndDelegate End; + public event AnimationState.EventDelegate Event; + public event AnimationState.CompleteDelegate Complete; + + internal void OnStart(AnimationState state, int index) + { + if (Start != null) Start(state, index); + } + + internal void OnEnd(AnimationState state, int index) + { + if (End != null) End(state, index); + } + + internal void OnEvent(AnimationState state, int index, Event e) + { + if (Event != null) Event(state, index, e); + } + + internal void OnComplete(AnimationState state, int index, int loopCount) + { + if (Complete != null) Complete(state, index, loopCount); + } + + override public String ToString() + { + return animation == null ? "" : animation.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/AnimationStateData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/AnimationStateData.cs index c157c0a..6b47b0c 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/AnimationStateData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/AnimationStateData.cs @@ -31,40 +31,46 @@ using System; using System.Collections.Generic; -namespace Spine2_1_08 { - public class AnimationStateData { - internal SkeletonData skeletonData; - private Dictionary, float> animationToMixTime = new Dictionary, float>(); - internal float defaultMix; +namespace Spine2_1_08 +{ + public class AnimationStateData + { + internal SkeletonData skeletonData; + private Dictionary, float> animationToMixTime = new Dictionary, float>(); + internal float defaultMix; - public SkeletonData SkeletonData { get { return skeletonData; } } - public float DefaultMix { get { return defaultMix; } set { defaultMix = value; } } + public SkeletonData SkeletonData { get { return skeletonData; } } + public float DefaultMix { get { return defaultMix; } set { defaultMix = value; } } - public AnimationStateData (SkeletonData skeletonData) { - this.skeletonData = skeletonData; - } + public AnimationStateData(SkeletonData skeletonData) + { + this.skeletonData = skeletonData; + } - public void SetMix (String fromName, String toName, float duration) { - Animation from = skeletonData.FindAnimation(fromName); - if (from == null) throw new ArgumentException("Animation not found: " + fromName); - Animation to = skeletonData.FindAnimation(toName); - if (to == null) throw new ArgumentException("Animation not found: " + toName); - SetMix(from, to, duration); - } + public void SetMix(String fromName, String toName, float duration) + { + Animation from = skeletonData.FindAnimation(fromName); + if (from == null) throw new ArgumentException("Animation not found: " + fromName); + Animation to = skeletonData.FindAnimation(toName); + if (to == null) throw new ArgumentException("Animation not found: " + toName); + SetMix(from, to, duration); + } - public void SetMix (Animation from, Animation to, float duration) { - if (from == null) throw new ArgumentNullException("from cannot be null."); - if (to == null) throw new ArgumentNullException("to cannot be null."); - KeyValuePair key = new KeyValuePair(from, to); - animationToMixTime.Remove(key); - animationToMixTime.Add(key, duration); - } + public void SetMix(Animation from, Animation to, float duration) + { + if (from == null) throw new ArgumentNullException("from cannot be null."); + if (to == null) throw new ArgumentNullException("to cannot be null."); + KeyValuePair key = new KeyValuePair(from, to); + animationToMixTime.Remove(key); + animationToMixTime.Add(key, duration); + } - public float GetMix (Animation from, Animation to) { - KeyValuePair key = new KeyValuePair(from, to); - float duration; - if (animationToMixTime.TryGetValue(key, out duration)) return duration; - return defaultMix; - } - } + public float GetMix(Animation from, Animation to) + { + KeyValuePair key = new KeyValuePair(from, to); + float duration; + if (animationToMixTime.TryGetValue(key, out duration)) return duration; + return defaultMix; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Atlas.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Atlas.cs index 72549e0..484fd94 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Atlas.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Atlas.cs @@ -31,18 +31,19 @@ using System; using System.Collections.Generic; using System.IO; -using System.Reflection; #if WINDOWS_STOREAPP using System.Threading.Tasks; using Windows.Storage; #endif -namespace Spine2_1_08 { - public class Atlas { - List pages = new List(); - List regions = new List(); - TextureLoader textureLoader; +namespace Spine2_1_08 +{ + public class Atlas + { + List pages = new List(); + List regions = new List(); + TextureLoader textureLoader; #if WINDOWS_STOREAPP private async Task ReadFile(string path, TextureLoader textureLoader) { @@ -61,228 +62,259 @@ public Atlas(String path, TextureLoader textureLoader) { this.ReadFile(path, textureLoader).Wait(); } #else - public Atlas (String path, TextureLoader textureLoader) { + public Atlas(String path, TextureLoader textureLoader) + { #if WINDOWS_PHONE Stream stream = Microsoft.Xna.Framework.TitleContainer.OpenStream(path); using (StreamReader reader = new StreamReader(stream)) { #else - using (StreamReader reader = new StreamReader(path)) { + using (StreamReader reader = new StreamReader(path)) + { #endif - try { - Load(reader, Path.GetDirectoryName(path), textureLoader); - } catch (Exception ex) { - throw new Exception("Error reading atlas file: " + path, ex); - } - } - } + try + { + Load(reader, Path.GetDirectoryName(path), textureLoader); + } + catch (Exception ex) + { + throw new Exception("Error reading atlas file: " + path, ex); + } + } + } #endif - public Atlas (TextReader reader, String dir, TextureLoader textureLoader) { - Load(reader, dir, textureLoader); - } - - public Atlas (List pages, List regions) { - this.pages = pages; - this.regions = regions; - this.textureLoader = null; - } - - private void Load (TextReader reader, String imagesDir, TextureLoader textureLoader) { - if (textureLoader == null) throw new ArgumentNullException("textureLoader cannot be null."); - this.textureLoader = textureLoader; - - String[] tuple = new String[4]; - AtlasPage page = null; - while (true) { - String line = reader.ReadLine(); - if (line == null) break; - if (line.Trim().Length == 0) - page = null; - else if (page == null) { - page = new AtlasPage(); - page.name = line; - - if (readTuple(reader, tuple) == 2) { // size is only optional for an atlas packed with an old TexturePacker. - page.width = int.Parse(tuple[0]); - page.height = int.Parse(tuple[1]); - readTuple(reader, tuple); - } - page.format = (Format)Enum.Parse(typeof(Format), tuple[0], false); - - readTuple(reader, tuple); - page.minFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[0], false); - page.magFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[1], false); - - String direction = readValue(reader); - page.uWrap = TextureWrap.ClampToEdge; - page.vWrap = TextureWrap.ClampToEdge; - if (direction == "x") - page.uWrap = TextureWrap.Repeat; - else if (direction == "y") - page.vWrap = TextureWrap.Repeat; - else if (direction == "xy") - page.uWrap = page.vWrap = TextureWrap.Repeat; - - textureLoader.Load(page, Path.Combine(imagesDir, line)); - - pages.Add(page); - - } else { - AtlasRegion region = new AtlasRegion(); - region.name = line; - region.page = page; - - region.rotate = Boolean.Parse(readValue(reader)); - - readTuple(reader, tuple); - int x = int.Parse(tuple[0]); - int y = int.Parse(tuple[1]); - - readTuple(reader, tuple); - int width = int.Parse(tuple[0]); - int height = int.Parse(tuple[1]); - - region.u = x / (float)page.width; - region.v = y / (float)page.height; - if (region.rotate) { - region.u2 = (x + height) / (float)page.width; - region.v2 = (y + width) / (float)page.height; - } else { - region.u2 = (x + width) / (float)page.width; - region.v2 = (y + height) / (float)page.height; - } - region.x = x; - region.y = y; - region.width = Math.Abs(width); - region.height = Math.Abs(height); - - if (readTuple(reader, tuple) == 4) { // split is optional - region.splits = new int[] {int.Parse(tuple[0]), int.Parse(tuple[1]), - int.Parse(tuple[2]), int.Parse(tuple[3])}; - - if (readTuple(reader, tuple) == 4) { // pad is optional, but only present with splits - region.pads = new int[] {int.Parse(tuple[0]), int.Parse(tuple[1]), - int.Parse(tuple[2]), int.Parse(tuple[3])}; - - readTuple(reader, tuple); - } - } - - region.originalWidth = int.Parse(tuple[0]); - region.originalHeight = int.Parse(tuple[1]); - - readTuple(reader, tuple); - region.offsetX = int.Parse(tuple[0]); - region.offsetY = int.Parse(tuple[1]); - - region.index = int.Parse(readValue(reader)); - - regions.Add(region); - } - } - } - - static String readValue (TextReader reader) { - String line = reader.ReadLine(); - int colon = line.IndexOf(':'); - if (colon == -1) throw new Exception("Invalid line: " + line); - return line.Substring(colon + 1).Trim(); - } - - /// Returns the number of tuple values read (1, 2 or 4). - static int readTuple (TextReader reader, String[] tuple) { - String line = reader.ReadLine(); - int colon = line.IndexOf(':'); - if (colon == -1) throw new Exception("Invalid line: " + line); - int i = 0, lastMatch = colon + 1; - for (; i < 3; i++) { - int comma = line.IndexOf(',', lastMatch); - if (comma == -1) break; - tuple[i] = line.Substring(lastMatch, comma - lastMatch).Trim(); - lastMatch = comma + 1; - } - tuple[i] = line.Substring(lastMatch).Trim(); - return i + 1; - } - - public void FlipV () { - for (int i = 0, n = regions.Count; i < n; i++) { - AtlasRegion region = regions[i]; - region.v = 1 - region.v; - region.v2 = 1 - region.v2; - } - } - - /// 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. - /// The region, or null. - public AtlasRegion FindRegion (String name) { - for (int i = 0, n = regions.Count; i < n; i++) - if (regions[i].name == name) return regions[i]; - return null; - } - - public void Dispose () { - if (textureLoader == null) return; - for (int i = 0, n = pages.Count; i < n; i++) - textureLoader.Unload(pages[i].rendererObject); - } - } - - public enum Format { - Alpha, - Intensity, - LuminanceAlpha, - RGB565, - RGBA4444, - RGB888, - RGBA8888 - } - - public enum TextureFilter { - Nearest, - Linear, - MipMap, - MipMapNearestNearest, - MipMapLinearNearest, - MipMapNearestLinear, - MipMapLinearLinear - } - - public enum TextureWrap { - MirroredRepeat, - ClampToEdge, - Repeat - } - - public class AtlasPage { - public String name; - public Format format; - public TextureFilter minFilter; - public TextureFilter magFilter; - public TextureWrap uWrap; - public TextureWrap vWrap; - public Object rendererObject; - public int width, height; - } - - public class AtlasRegion { - public AtlasPage page; - public String name; - public int x, y, width, height; - public float u, v, u2, v2; - public float offsetX, offsetY; - public int originalWidth, originalHeight; - public int index; - public bool rotate; - public int[] splits; - public int[] pads; - } - - public interface TextureLoader { - void Load (AtlasPage page, String path); - void Unload (Object texture); - } + public Atlas(TextReader reader, String dir, TextureLoader textureLoader) + { + Load(reader, dir, textureLoader); + } + + public Atlas(List pages, List regions) + { + this.pages = pages; + this.regions = regions; + this.textureLoader = null; + } + + private void Load(TextReader reader, String imagesDir, TextureLoader textureLoader) + { + if (textureLoader == null) throw new ArgumentNullException("textureLoader cannot be null."); + this.textureLoader = textureLoader; + + String[] tuple = new String[4]; + AtlasPage page = null; + while (true) + { + String line = reader.ReadLine(); + if (line == null) break; + if (line.Trim().Length == 0) + page = null; + else if (page == null) + { + page = new AtlasPage(); + page.name = line; + + if (readTuple(reader, tuple) == 2) + { // size is only optional for an atlas packed with an old TexturePacker. + page.width = int.Parse(tuple[0]); + page.height = int.Parse(tuple[1]); + readTuple(reader, tuple); + } + page.format = (Format)Enum.Parse(typeof(Format), tuple[0], false); + + readTuple(reader, tuple); + page.minFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[0], false); + page.magFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[1], false); + + String direction = readValue(reader); + page.uWrap = TextureWrap.ClampToEdge; + page.vWrap = TextureWrap.ClampToEdge; + if (direction == "x") + page.uWrap = TextureWrap.Repeat; + else if (direction == "y") + page.vWrap = TextureWrap.Repeat; + else if (direction == "xy") + page.uWrap = page.vWrap = TextureWrap.Repeat; + + textureLoader.Load(page, Path.Combine(imagesDir, line)); + + pages.Add(page); + + } + else + { + AtlasRegion region = new AtlasRegion(); + region.name = line; + region.page = page; + + region.rotate = Boolean.Parse(readValue(reader)); + + readTuple(reader, tuple); + int x = int.Parse(tuple[0]); + int y = int.Parse(tuple[1]); + + readTuple(reader, tuple); + int width = int.Parse(tuple[0]); + int height = int.Parse(tuple[1]); + + region.u = x / (float)page.width; + region.v = y / (float)page.height; + if (region.rotate) + { + region.u2 = (x + height) / (float)page.width; + region.v2 = (y + width) / (float)page.height; + } + else + { + region.u2 = (x + width) / (float)page.width; + region.v2 = (y + height) / (float)page.height; + } + region.x = x; + region.y = y; + region.width = Math.Abs(width); + region.height = Math.Abs(height); + + if (readTuple(reader, tuple) == 4) + { // split is optional + region.splits = new int[] {int.Parse(tuple[0]), int.Parse(tuple[1]), + int.Parse(tuple[2]), int.Parse(tuple[3])}; + + if (readTuple(reader, tuple) == 4) + { // pad is optional, but only present with splits + region.pads = new int[] {int.Parse(tuple[0]), int.Parse(tuple[1]), + int.Parse(tuple[2]), int.Parse(tuple[3])}; + + readTuple(reader, tuple); + } + } + + region.originalWidth = int.Parse(tuple[0]); + region.originalHeight = int.Parse(tuple[1]); + + readTuple(reader, tuple); + region.offsetX = int.Parse(tuple[0]); + region.offsetY = int.Parse(tuple[1]); + + region.index = int.Parse(readValue(reader)); + + regions.Add(region); + } + } + } + + static String readValue(TextReader reader) + { + String line = reader.ReadLine(); + int colon = line.IndexOf(':'); + if (colon == -1) throw new Exception("Invalid line: " + line); + return line.Substring(colon + 1).Trim(); + } + + /// Returns the number of tuple values read (1, 2 or 4). + static int readTuple(TextReader reader, String[] tuple) + { + String line = reader.ReadLine(); + int colon = line.IndexOf(':'); + if (colon == -1) throw new Exception("Invalid line: " + line); + int i = 0, lastMatch = colon + 1; + for (; i < 3; i++) + { + int comma = line.IndexOf(',', lastMatch); + if (comma == -1) break; + tuple[i] = line.Substring(lastMatch, comma - lastMatch).Trim(); + lastMatch = comma + 1; + } + tuple[i] = line.Substring(lastMatch).Trim(); + return i + 1; + } + + public void FlipV() + { + for (int i = 0, n = regions.Count; i < n; i++) + { + AtlasRegion region = regions[i]; + region.v = 1 - region.v; + region.v2 = 1 - region.v2; + } + } + + /// 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. + /// The region, or null. + public AtlasRegion FindRegion(String name) + { + for (int i = 0, n = regions.Count; i < n; i++) + if (regions[i].name == name) return regions[i]; + return null; + } + + public void Dispose() + { + if (textureLoader == null) return; + for (int i = 0, n = pages.Count; i < n; i++) + textureLoader.Unload(pages[i].rendererObject); + } + } + + public enum Format + { + Alpha, + Intensity, + LuminanceAlpha, + RGB565, + RGBA4444, + RGB888, + RGBA8888 + } + + public enum TextureFilter + { + Nearest, + Linear, + MipMap, + MipMapNearestNearest, + MipMapLinearNearest, + MipMapNearestLinear, + MipMapLinearLinear + } + + public enum TextureWrap + { + MirroredRepeat, + ClampToEdge, + Repeat + } + + public class AtlasPage + { + public String name; + public Format format; + public TextureFilter minFilter; + public TextureFilter magFilter; + public TextureWrap uWrap; + public TextureWrap vWrap; + public Object rendererObject; + public int width, height; + } + + public class AtlasRegion + { + public AtlasPage page; + public String name; + public int x, y, width, height; + public float u, v, u2, v2; + public float offsetX, offsetY; + public int originalWidth, originalHeight; + public int index; + public bool rotate; + public int[] splits; + public int[] pads; + } + + public interface TextureLoader + { + void Load(AtlasPage page, String path); + void Unload(Object texture); + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Attachments/AtlasAttachmentLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Attachments/AtlasAttachmentLoader.cs index bcc0fc2..9e6ee73 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Attachments/AtlasAttachmentLoader.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Attachments/AtlasAttachmentLoader.cs @@ -30,70 +30,77 @@ using System; -namespace Spine2_1_08 { - public class AtlasAttachmentLoader : AttachmentLoader { - private Atlas atlas; +namespace Spine2_1_08 +{ + public class AtlasAttachmentLoader : AttachmentLoader + { + private Atlas atlas; - public AtlasAttachmentLoader (Atlas atlas) { - if (atlas == null) throw new ArgumentNullException("atlas cannot be null."); - this.atlas = atlas; - } + public AtlasAttachmentLoader(Atlas atlas) + { + if (atlas == null) throw new ArgumentNullException("atlas cannot be null."); + this.atlas = atlas; + } - public RegionAttachment NewRegionAttachment (Skin skin, String name, String path) { - AtlasRegion region = atlas.FindRegion(path); - if (region == null) throw new Exception("Region not found in atlas: " + path + " (region attachment: " + name + ")"); - RegionAttachment attachment = new RegionAttachment(name); - attachment.RendererObject = region; - attachment.SetUVs(region.u, region.v, region.u2, region.v2, region.rotate); - attachment.regionOffsetX = region.offsetX; - attachment.regionOffsetY = region.offsetY; - attachment.regionWidth = region.width; - attachment.regionHeight = region.height; - attachment.regionOriginalWidth = region.originalWidth; - attachment.regionOriginalHeight = region.originalHeight; - return attachment; - } + public RegionAttachment NewRegionAttachment(Skin skin, String name, String path) + { + AtlasRegion region = atlas.FindRegion(path); + if (region == null) throw new Exception("Region not found in atlas: " + path + " (region attachment: " + name + ")"); + RegionAttachment attachment = new RegionAttachment(name); + attachment.RendererObject = region; + attachment.SetUVs(region.u, region.v, region.u2, region.v2, region.rotate); + attachment.regionOffsetX = region.offsetX; + attachment.regionOffsetY = region.offsetY; + attachment.regionWidth = region.width; + attachment.regionHeight = region.height; + attachment.regionOriginalWidth = region.originalWidth; + attachment.regionOriginalHeight = region.originalHeight; + return attachment; + } - public MeshAttachment NewMeshAttachment (Skin skin, String name, String path) { - AtlasRegion region = atlas.FindRegion(path); - if (region == null) throw new Exception("Region not found in atlas: " + path + " (mesh attachment: " + name + ")"); - MeshAttachment attachment = new MeshAttachment(name); - attachment.RendererObject = region; - attachment.RegionU = region.u; - attachment.RegionV = region.v; - attachment.RegionU2 = region.u2; - attachment.RegionV2 = region.v2; - attachment.RegionRotate = region.rotate; - attachment.regionOffsetX = region.offsetX; - attachment.regionOffsetY = region.offsetY; - attachment.regionWidth = region.width; - attachment.regionHeight = region.height; - attachment.regionOriginalWidth = region.originalWidth; - attachment.regionOriginalHeight = region.originalHeight; - return attachment; - } + public MeshAttachment NewMeshAttachment(Skin skin, String name, String path) + { + AtlasRegion region = atlas.FindRegion(path); + if (region == null) throw new Exception("Region not found in atlas: " + path + " (mesh attachment: " + name + ")"); + MeshAttachment attachment = new MeshAttachment(name); + attachment.RendererObject = region; + attachment.RegionU = region.u; + attachment.RegionV = region.v; + attachment.RegionU2 = region.u2; + attachment.RegionV2 = region.v2; + attachment.RegionRotate = region.rotate; + attachment.regionOffsetX = region.offsetX; + attachment.regionOffsetY = region.offsetY; + attachment.regionWidth = region.width; + attachment.regionHeight = region.height; + attachment.regionOriginalWidth = region.originalWidth; + attachment.regionOriginalHeight = region.originalHeight; + return attachment; + } - public SkinnedMeshAttachment NewSkinnedMeshAttachment (Skin skin, String name, String path) { - AtlasRegion region = atlas.FindRegion(path); - if (region == null) throw new Exception("Region not found in atlas: " + path + " (skinned mesh attachment: " + name + ")"); - SkinnedMeshAttachment attachment = new SkinnedMeshAttachment(name); - attachment.RendererObject = region; - attachment.RegionU = region.u; - attachment.RegionV = region.v; - attachment.RegionU2 = region.u2; - attachment.RegionV2 = region.v2; - attachment.RegionRotate = region.rotate; - attachment.regionOffsetX = region.offsetX; - attachment.regionOffsetY = region.offsetY; - attachment.regionWidth = region.width; - attachment.regionHeight = region.height; - attachment.regionOriginalWidth = region.originalWidth; - attachment.regionOriginalHeight = region.originalHeight; - return attachment; - } + public SkinnedMeshAttachment NewSkinnedMeshAttachment(Skin skin, String name, String path) + { + AtlasRegion region = atlas.FindRegion(path); + if (region == null) throw new Exception("Region not found in atlas: " + path + " (skinned mesh attachment: " + name + ")"); + SkinnedMeshAttachment attachment = new SkinnedMeshAttachment(name); + attachment.RendererObject = region; + attachment.RegionU = region.u; + attachment.RegionV = region.v; + attachment.RegionU2 = region.u2; + attachment.RegionV2 = region.v2; + attachment.RegionRotate = region.rotate; + attachment.regionOffsetX = region.offsetX; + attachment.regionOffsetY = region.offsetY; + attachment.regionWidth = region.width; + attachment.regionHeight = region.height; + attachment.regionOriginalWidth = region.originalWidth; + attachment.regionOriginalHeight = region.originalHeight; + return attachment; + } - public BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, String name) { - return new BoundingBoxAttachment(name); - } - } + public BoundingBoxAttachment NewBoundingBoxAttachment(Skin skin, String name) + { + return new BoundingBoxAttachment(name); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Attachments/Attachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Attachments/Attachment.cs index f5deec5..9f719da 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Attachments/Attachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Attachments/Attachment.cs @@ -30,17 +30,21 @@ using System; -namespace Spine2_1_08 { - abstract public class Attachment { - public String Name { get; private set; } +namespace Spine2_1_08 +{ + abstract public class Attachment + { + public String Name { get; private set; } - public Attachment (String name) { - if (name == null) throw new ArgumentNullException("name cannot be null."); - Name = name; - } + public Attachment(String name) + { + if (name == null) throw new ArgumentNullException("name cannot be null."); + Name = name; + } - override public String ToString () { - return Name; - } - } + override public String ToString() + { + return Name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Attachments/AttachmentLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Attachments/AttachmentLoader.cs index 73e6c5c..db83f10 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Attachments/AttachmentLoader.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Attachments/AttachmentLoader.cs @@ -30,18 +30,20 @@ using System; -namespace Spine2_1_08 { - public interface AttachmentLoader { - /// May be null to not load any attachment. - RegionAttachment NewRegionAttachment (Skin skin, String name, String path); +namespace Spine2_1_08 +{ + public interface AttachmentLoader + { + /// May be null to not load any attachment. + RegionAttachment NewRegionAttachment(Skin skin, String name, String path); - /// May be null to not load any attachment. - MeshAttachment NewMeshAttachment (Skin skin, String name, String path); + /// May be null to not load any attachment. + MeshAttachment NewMeshAttachment(Skin skin, String name, String path); - /// May be null to not load any attachment. - SkinnedMeshAttachment NewSkinnedMeshAttachment (Skin skin, String name, String path); + /// May be null to not load any attachment. + SkinnedMeshAttachment NewSkinnedMeshAttachment(Skin skin, String name, String path); - /// May be null to not load any attachment. - BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, String name); - } + /// May be null to not load any attachment. + BoundingBoxAttachment NewBoundingBoxAttachment(Skin skin, String name); + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Attachments/AttachmentType.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Attachments/AttachmentType.cs index e240b72..c84ea86 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Attachments/AttachmentType.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Attachments/AttachmentType.cs @@ -28,8 +28,10 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine2_1_08 { - public enum AttachmentType { - region, boundingbox, mesh, skinnedmesh - } +namespace Spine2_1_08 +{ + public enum AttachmentType + { + region, boundingbox, mesh, skinnedmesh + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Attachments/BoundingBoxAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Attachments/BoundingBoxAttachment.cs index 3edfd2f..f4cebf0 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Attachments/BoundingBoxAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Attachments/BoundingBoxAttachment.cs @@ -28,33 +28,36 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; +namespace Spine2_1_08 +{ + /// Attachment that has a polygon for bounds checking. + public class BoundingBoxAttachment : Attachment + { + internal float[] vertices; -namespace Spine2_1_08 { - /// Attachment that has a polygon for bounds checking. - public class BoundingBoxAttachment : Attachment { - internal float[] vertices; + public float[] Vertices { get { return vertices; } set { vertices = value; } } - public float[] Vertices { get { return vertices; } set { vertices = value; } } + public BoundingBoxAttachment(string name) + : base(name) + { + } - public BoundingBoxAttachment (string name) - : base(name) { - } - - /// Must have at least the same length as this attachment's vertices. - public void ComputeWorldVertices (Bone bone, float[] worldVertices) { - float x = bone.skeleton.x + bone.worldX, y = bone.skeleton.y + bone.worldY; - float m00 = bone.m00; - float m01 = bone.m01; - float m10 = bone.m10; - float m11 = bone.m11; - float[] vertices = this.vertices; - for (int i = 0, n = vertices.Length; i < n; i += 2) { - float px = vertices[i]; - float py = vertices[i + 1]; - worldVertices[i] = px * m00 + py * m01 + x; - worldVertices[i + 1] = px * m10 + py * m11 + y; - } - } - } + /// Must have at least the same length as this attachment's vertices. + public void ComputeWorldVertices(Bone bone, float[] worldVertices) + { + float x = bone.skeleton.x + bone.worldX, y = bone.skeleton.y + bone.worldY; + float m00 = bone.m00; + float m01 = bone.m01; + float m10 = bone.m10; + float m11 = bone.m11; + float[] vertices = this.vertices; + for (int i = 0, n = vertices.Length; i < n; i += 2) + { + float px = vertices[i]; + float py = vertices[i + 1]; + worldVertices[i] = px * m00 + py * m01 + x; + worldVertices[i + 1] = px * m10 + py * m11 + y; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Attachments/MeshAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Attachments/MeshAttachment.cs index 349999d..96c8671 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Attachments/MeshAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Attachments/MeshAttachment.cs @@ -30,79 +30,90 @@ using System; -namespace Spine2_1_08 { - /// Attachment that displays a texture region. - public class MeshAttachment : Attachment { - internal float[] vertices, uvs, regionUVs; - internal int[] triangles; - internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; - internal float r = 1, g = 1, b = 1, a = 1; +namespace Spine2_1_08 +{ + /// Attachment that displays a texture region. + public class MeshAttachment : Attachment + { + internal float[] vertices, uvs, regionUVs; + internal int[] triangles; + internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; + internal float r = 1, g = 1, b = 1, a = 1; - public int HullLength { get; set; } - public float[] Vertices { get { return vertices; } set { vertices = value; } } - public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } } - public float[] UVs { get { return uvs; } set { uvs = value; } } - public int[] Triangles { get { return triangles; } set { triangles = value; } } + public int HullLength { get; set; } + public float[] Vertices { get { return vertices; } set { vertices = value; } } + public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } } + public float[] UVs { get { return uvs; } set { uvs = value; } } + public int[] Triangles { get { return triangles; } set { triangles = value; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } - public String Path { get; set; } - public Object RendererObject { get; set; } - public float RegionU { get; set; } - public float RegionV { get; set; } - public float RegionU2 { get; set; } - public float RegionV2 { get; set; } - public bool RegionRotate { get; set; } - public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } - public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. - public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } - public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. - public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } - public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. + public String Path { get; set; } + public Object RendererObject { get; set; } + public float RegionU { get; set; } + public float RegionV { get; set; } + public float RegionU2 { get; set; } + public float RegionV2 { get; set; } + public bool RegionRotate { get; set; } + public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } + public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. + public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } + public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. + public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } + public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. - // Nonessential. - public int[] Edges { get; set; } - public float Width { get; set; } - public float Height { get; set; } + // Nonessential. + public int[] Edges { get; set; } + public float Width { get; set; } + public float Height { get; set; } - public MeshAttachment (string name) - : base(name) { - } + public MeshAttachment(string name) + : base(name) + { + } - public void UpdateUVs () { - float u = RegionU, v = RegionV, width = RegionU2 - RegionU, height = RegionV2 - RegionV; - float[] regionUVs = this.regionUVs; - if (this.uvs == null || this.uvs.Length != regionUVs.Length) this.uvs = new float[regionUVs.Length]; - float[] uvs = this.uvs; - if (RegionRotate) { - for (int i = 0, n = uvs.Length; i < n; i += 2) { - uvs[i] = u + regionUVs[i + 1] * width; - uvs[i + 1] = v + height - regionUVs[i] * height; - } - } else { - for (int i = 0, n = uvs.Length; i < n; i += 2) { - uvs[i] = u + regionUVs[i] * width; - uvs[i + 1] = v + regionUVs[i + 1] * height; - } - } - } + public void UpdateUVs() + { + float u = RegionU, v = RegionV, width = RegionU2 - RegionU, height = RegionV2 - RegionV; + float[] regionUVs = this.regionUVs; + if (this.uvs == null || this.uvs.Length != regionUVs.Length) this.uvs = new float[regionUVs.Length]; + float[] uvs = this.uvs; + if (RegionRotate) + { + for (int i = 0, n = uvs.Length; i < n; i += 2) + { + uvs[i] = u + regionUVs[i + 1] * width; + uvs[i + 1] = v + height - regionUVs[i] * height; + } + } + else + { + for (int i = 0, n = uvs.Length; i < n; i += 2) + { + uvs[i] = u + regionUVs[i] * width; + uvs[i + 1] = v + regionUVs[i + 1] * height; + } + } + } - public void ComputeWorldVertices (Slot slot, float[] worldVertices) { - Bone bone = slot.bone; - float x = bone.skeleton.x + bone.worldX, y = bone.skeleton.y + bone.worldY; - float m00 = bone.m00, m01 = bone.m01, m10 = bone.m10, m11 = bone.m11; - float[] vertices = this.vertices; - int verticesCount = vertices.Length; - if (slot.attachmentVerticesCount == verticesCount) vertices = slot.AttachmentVertices; - for (int i = 0; i < verticesCount; i += 2) { - float vx = vertices[i]; - float vy = vertices[i + 1]; - worldVertices[i] = vx * m00 + vy * m01 + x; - worldVertices[i + 1] = vx * m10 + vy * m11 + y; - } - } - } + public void ComputeWorldVertices(Slot slot, float[] worldVertices) + { + Bone bone = slot.bone; + float x = bone.skeleton.x + bone.worldX, y = bone.skeleton.y + bone.worldY; + float m00 = bone.m00, m01 = bone.m01, m10 = bone.m10, m11 = bone.m11; + float[] vertices = this.vertices; + int verticesCount = vertices.Length; + if (slot.attachmentVerticesCount == verticesCount) vertices = slot.AttachmentVertices; + for (int i = 0; i < verticesCount; i += 2) + { + float vx = vertices[i]; + float vy = vertices[i + 1]; + worldVertices[i] = vx * m00 + vy * m01 + x; + worldVertices[i + 1] = vx * m10 + vy * m11 + y; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Attachments/RegionAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Attachments/RegionAttachment.cs index a7678d6..8be3914 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Attachments/RegionAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Attachments/RegionAttachment.cs @@ -30,122 +30,131 @@ using System; -namespace Spine2_1_08 { - /// Attachment that displays a texture region. - public class RegionAttachment : Attachment { - public const int X1 = 0; - public const int Y1 = 1; - public const int X2 = 2; - public const int Y2 = 3; - public const int X3 = 4; - public const int Y3 = 5; - public const int X4 = 6; - public const int Y4 = 7; +namespace Spine2_1_08 +{ + /// Attachment that displays a texture region. + public class RegionAttachment : Attachment + { + public const int X1 = 0; + public const int Y1 = 1; + public const int X2 = 2; + public const int Y2 = 3; + public const int X3 = 4; + public const int Y3 = 5; + public const int X4 = 6; + public const int Y4 = 7; - internal float x, y, rotation, scaleX = 1, scaleY = 1, width, height; - internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; - internal float[] offset = new float[8], uvs = new float[8]; - internal float r = 1, g = 1, b = 1, a = 1; + internal float x, y, rotation, scaleX = 1, scaleY = 1, width, height; + internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; + internal float[] offset = new float[8], uvs = new float[8]; + internal float r = 1, g = 1, b = 1, a = 1; - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public float Rotation { get { return rotation; } set { rotation = value; } } - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - public float ScaleY { get { return scaleY; } set { scaleY = value; } } - public float Width { get { return width; } set { width = value; } } - public float Height { get { return height; } set { height = value; } } + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public float Rotation { get { return rotation; } set { rotation = value; } } + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + public float Width { get { return width; } set { width = value; } } + public float Height { get { return height; } set { height = value; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } - public String Path { get; set; } - public Object RendererObject { get; set; } - public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } - public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. - public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } - public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. - public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } - public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. + public String Path { get; set; } + public Object RendererObject { get; set; } + public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } + public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. + public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } + public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. + public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } + public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. - public float[] Offset { get { return offset; } } - public float[] UVs { get { return uvs; } } + public float[] Offset { get { return offset; } } + public float[] UVs { get { return uvs; } } - public RegionAttachment (string name) - : base(name) { - } + public RegionAttachment(string name) + : base(name) + { + } - public void SetUVs (float u, float v, float u2, float v2, bool rotate) { - float[] uvs = this.uvs; - if (rotate) { - uvs[X2] = u; - uvs[Y2] = v2; - uvs[X3] = u; - uvs[Y3] = v; - uvs[X4] = u2; - uvs[Y4] = v; - uvs[X1] = u2; - uvs[Y1] = v2; - } else { - uvs[X1] = u; - uvs[Y1] = v2; - uvs[X2] = u; - uvs[Y2] = v; - uvs[X3] = u2; - uvs[Y3] = v; - uvs[X4] = u2; - uvs[Y4] = v2; - } - } + public void SetUVs(float u, float v, float u2, float v2, bool rotate) + { + float[] uvs = this.uvs; + if (rotate) + { + uvs[X2] = u; + uvs[Y2] = v2; + uvs[X3] = u; + uvs[Y3] = v; + uvs[X4] = u2; + uvs[Y4] = v; + uvs[X1] = u2; + uvs[Y1] = v2; + } + else + { + uvs[X1] = u; + uvs[Y1] = v2; + uvs[X2] = u; + uvs[Y2] = v; + uvs[X3] = u2; + uvs[Y3] = v; + uvs[X4] = u2; + uvs[Y4] = v2; + } + } - public void UpdateOffset () { - float width = this.width; - float height = this.height; - float scaleX = this.scaleX; - float scaleY = this.scaleY; - float regionScaleX = width / regionOriginalWidth * scaleX; - float regionScaleY = height / regionOriginalHeight * scaleY; - float localX = -width / 2 * scaleX + regionOffsetX * regionScaleX; - float localY = -height / 2 * scaleY + regionOffsetY * regionScaleY; - float localX2 = localX + regionWidth * regionScaleX; - float localY2 = localY + regionHeight * regionScaleY; - float radians = rotation * (float)Math.PI / 180; - float cos = (float)Math.Cos(radians); - float sin = (float)Math.Sin(radians); - float x = this.x; - float y = this.y; - 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; - float[] offset = this.offset; - offset[X1] = localXCos - localYSin; - offset[Y1] = localYCos + localXSin; - offset[X2] = localXCos - localY2Sin; - offset[Y2] = localY2Cos + localXSin; - offset[X3] = localX2Cos - localY2Sin; - offset[Y3] = localY2Cos + localX2Sin; - offset[X4] = localX2Cos - localYSin; - offset[Y4] = localYCos + localX2Sin; - } + public void UpdateOffset() + { + float width = this.width; + float height = this.height; + float scaleX = this.scaleX; + float scaleY = this.scaleY; + float regionScaleX = width / regionOriginalWidth * scaleX; + float regionScaleY = height / regionOriginalHeight * scaleY; + float localX = -width / 2 * scaleX + regionOffsetX * regionScaleX; + float localY = -height / 2 * scaleY + regionOffsetY * regionScaleY; + float localX2 = localX + regionWidth * regionScaleX; + float localY2 = localY + regionHeight * regionScaleY; + float radians = rotation * (float)Math.PI / 180; + float cos = (float)Math.Cos(radians); + float sin = (float)Math.Sin(radians); + float x = this.x; + float y = this.y; + 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; + float[] offset = this.offset; + offset[X1] = localXCos - localYSin; + offset[Y1] = localYCos + localXSin; + offset[X2] = localXCos - localY2Sin; + offset[Y2] = localY2Cos + localXSin; + offset[X3] = localX2Cos - localY2Sin; + offset[Y3] = localY2Cos + localX2Sin; + offset[X4] = localX2Cos - localYSin; + offset[Y4] = localYCos + localX2Sin; + } - public void ComputeWorldVertices (Bone bone, float[] worldVertices) { - float x = bone.skeleton.x + bone.worldX, y = bone.skeleton.y + bone.worldY; - float m00 = bone.m00, m01 = bone.m01, m10 = bone.m10, m11 = bone.m11; - float[] offset = this.offset; - worldVertices[X1] = offset[X1] * m00 + offset[Y1] * m01 + x; - worldVertices[Y1] = offset[X1] * m10 + offset[Y1] * m11 + y; - worldVertices[X2] = offset[X2] * m00 + offset[Y2] * m01 + x; - worldVertices[Y2] = offset[X2] * m10 + offset[Y2] * m11 + y; - worldVertices[X3] = offset[X3] * m00 + offset[Y3] * m01 + x; - worldVertices[Y3] = offset[X3] * m10 + offset[Y3] * m11 + y; - worldVertices[X4] = offset[X4] * m00 + offset[Y4] * m01 + x; - worldVertices[Y4] = offset[X4] * m10 + offset[Y4] * m11 + y; - } - } + public void ComputeWorldVertices(Bone bone, float[] worldVertices) + { + float x = bone.skeleton.x + bone.worldX, y = bone.skeleton.y + bone.worldY; + float m00 = bone.m00, m01 = bone.m01, m10 = bone.m10, m11 = bone.m11; + float[] offset = this.offset; + worldVertices[X1] = offset[X1] * m00 + offset[Y1] * m01 + x; + worldVertices[Y1] = offset[X1] * m10 + offset[Y1] * m11 + y; + worldVertices[X2] = offset[X2] * m00 + offset[Y2] * m01 + x; + worldVertices[Y2] = offset[X2] * m10 + offset[Y2] * m11 + y; + worldVertices[X3] = offset[X3] * m00 + offset[Y3] * m01 + x; + worldVertices[Y3] = offset[X3] * m10 + offset[Y3] * m11 + y; + worldVertices[X4] = offset[X4] * m00 + offset[Y4] * m01 + x; + worldVertices[Y4] = offset[X4] * m10 + offset[Y4] * m11 + y; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Attachments/SkinnedMeshAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Attachments/SkinnedMeshAttachment.cs index 3343bce..5c61d77 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Attachments/SkinnedMeshAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Attachments/SkinnedMeshAttachment.cs @@ -31,102 +31,119 @@ using System; using System.Collections.Generic; -namespace Spine2_1_08 { - /// Attachment that displays a texture region. - public class SkinnedMeshAttachment : Attachment { - internal int[] bones; - internal float[] weights, uvs, regionUVs; - internal int[] triangles; - internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; - internal float r = 1, g = 1, b = 1, a = 1; +namespace Spine2_1_08 +{ + /// Attachment that displays a texture region. + public class SkinnedMeshAttachment : Attachment + { + internal int[] bones; + internal float[] weights, uvs, regionUVs; + internal int[] triangles; + internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; + internal float r = 1, g = 1, b = 1, a = 1; - public int HullLength { get; set; } - public int[] Bones { get { return bones; } set { bones = value; } } - public float[] Weights { get { return weights; } set { weights = value; } } - public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } } - public float[] UVs { get { return uvs; } set { uvs = value; } } - public int[] Triangles { get { return triangles; } set { triangles = value; } } + public int HullLength { get; set; } + public int[] Bones { get { return bones; } set { bones = value; } } + public float[] Weights { get { return weights; } set { weights = value; } } + public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } } + public float[] UVs { get { return uvs; } set { uvs = value; } } + public int[] Triangles { get { return triangles; } set { triangles = value; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } - public String Path { get; set; } - public Object RendererObject { get; set; } - public float RegionU { get; set; } - public float RegionV { get; set; } - public float RegionU2 { get; set; } - public float RegionV2 { get; set; } - public bool RegionRotate { get; set; } - public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } - public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. - public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } - public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. - public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } - public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. + public String Path { get; set; } + public Object RendererObject { get; set; } + public float RegionU { get; set; } + public float RegionV { get; set; } + public float RegionU2 { get; set; } + public float RegionV2 { get; set; } + public bool RegionRotate { get; set; } + public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } + public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. + public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } + public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. + public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } + public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. - // Nonessential. - public int[] Edges { get; set; } - public float Width { get; set; } - public float Height { get; set; } + // Nonessential. + public int[] Edges { get; set; } + public float Width { get; set; } + public float Height { get; set; } - public SkinnedMeshAttachment (string name) - : base(name) { - } + public SkinnedMeshAttachment(string name) + : base(name) + { + } - public void UpdateUVs () { - float u = RegionU, v = RegionV, width = RegionU2 - RegionU, height = RegionV2 - RegionV; - float[] regionUVs = this.regionUVs; - if (this.uvs == null || this.uvs.Length != regionUVs.Length) this.uvs = new float[regionUVs.Length]; - float[] uvs = this.uvs; - if (RegionRotate) { - for (int i = 0, n = uvs.Length; i < n; i += 2) { - uvs[i] = u + regionUVs[i + 1] * width; - uvs[i + 1] = v + height - regionUVs[i] * height; - } - } else { - for (int i = 0, n = uvs.Length; i < n; i += 2) { - uvs[i] = u + regionUVs[i] * width; - uvs[i + 1] = v + regionUVs[i + 1] * height; - } - } - } + public void UpdateUVs() + { + float u = RegionU, v = RegionV, width = RegionU2 - RegionU, height = RegionV2 - RegionV; + float[] regionUVs = this.regionUVs; + if (this.uvs == null || this.uvs.Length != regionUVs.Length) this.uvs = new float[regionUVs.Length]; + float[] uvs = this.uvs; + if (RegionRotate) + { + for (int i = 0, n = uvs.Length; i < n; i += 2) + { + uvs[i] = u + regionUVs[i + 1] * width; + uvs[i + 1] = v + height - regionUVs[i] * height; + } + } + else + { + for (int i = 0, n = uvs.Length; i < n; i += 2) + { + uvs[i] = u + regionUVs[i] * width; + uvs[i + 1] = v + regionUVs[i + 1] * height; + } + } + } - public void ComputeWorldVertices (Slot slot, float[] worldVertices) { - Skeleton skeleton = slot.bone.skeleton; - List skeletonBones = skeleton.bones; - float x = skeleton.x, y = skeleton.y; - float[] weights = this.weights; - int[] bones = this.bones; - if (slot.attachmentVerticesCount == 0) { - for (int w = 0, v = 0, b = 0, n = bones.Length; v < n; w += 2) { - float wx = 0, wy = 0; - int nn = bones[v++] + v; - for (; v < nn; v++, b += 3) { - Bone bone = skeletonBones[bones[v]]; - float vx = weights[b], vy = weights[b + 1], weight = weights[b + 2]; - wx += (vx * bone.m00 + vy * bone.m01 + bone.worldX) * weight; - wy += (vx * bone.m10 + vy * bone.m11 + bone.worldY) * weight; - } - worldVertices[w] = wx + x; - worldVertices[w + 1] = wy + y; - } - } else { - float[] ffd = slot.AttachmentVertices; - for (int w = 0, v = 0, b = 0, f = 0, n = bones.Length; v < n; w += 2) { - float wx = 0, wy = 0; - int nn = bones[v++] + v; - for (; v < nn; v++, b += 3, f += 2) { - Bone bone = skeletonBones[bones[v]]; - float vx = weights[b] + ffd[f], vy = weights[b + 1] + ffd[f + 1], weight = weights[b + 2]; - wx += (vx * bone.m00 + vy * bone.m01 + bone.worldX) * weight; - wy += (vx * bone.m10 + vy * bone.m11 + bone.worldY) * weight; - } - worldVertices[w] = wx + x; - worldVertices[w + 1] = wy + y; - } - } - } - } + public void ComputeWorldVertices(Slot slot, float[] worldVertices) + { + Skeleton skeleton = slot.bone.skeleton; + List skeletonBones = skeleton.bones; + float x = skeleton.x, y = skeleton.y; + float[] weights = this.weights; + int[] bones = this.bones; + if (slot.attachmentVerticesCount == 0) + { + for (int w = 0, v = 0, b = 0, n = bones.Length; v < n; w += 2) + { + float wx = 0, wy = 0; + int nn = bones[v++] + v; + for (; v < nn; v++, b += 3) + { + Bone bone = skeletonBones[bones[v]]; + float vx = weights[b], vy = weights[b + 1], weight = weights[b + 2]; + wx += (vx * bone.m00 + vy * bone.m01 + bone.worldX) * weight; + wy += (vx * bone.m10 + vy * bone.m11 + bone.worldY) * weight; + } + worldVertices[w] = wx + x; + worldVertices[w + 1] = wy + y; + } + } + else + { + float[] ffd = slot.AttachmentVertices; + for (int w = 0, v = 0, b = 0, f = 0, n = bones.Length; v < n; w += 2) + { + float wx = 0, wy = 0; + int nn = bones[v++] + v; + for (; v < nn; v++, b += 3, f += 2) + { + Bone bone = skeletonBones[bones[v]]; + float vx = weights[b] + ffd[f], vy = weights[b + 1] + ffd[f + 1], weight = weights[b + 2]; + wx += (vx * bone.m00 + vy * bone.m01 + bone.worldX) * weight; + wy += (vx * bone.m10 + vy * bone.m11 + bone.worldY) * weight; + } + worldVertices[w] = wx + x; + worldVertices[w + 1] = wy + y; + } + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Bone.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Bone.cs index 27564b2..50a40b7 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Bone.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Bone.cs @@ -31,135 +31,156 @@ using System; using System.Collections.Generic; -namespace Spine2_1_08 { - public class Bone{ - static public bool yDown; +namespace Spine2_1_08 +{ + public class Bone + { + static public bool yDown; - internal BoneData data; - internal Skeleton skeleton; - internal Bone parent; - internal List children = new List(); - internal float x, y, rotation, rotationIK, scaleX, scaleY; - internal bool flipX, flipY; - internal float m00, m01, m10, m11; - internal float worldX, worldY, worldRotation, worldScaleX, worldScaleY; - internal bool worldFlipX, worldFlipY; + internal BoneData data; + internal Skeleton skeleton; + internal Bone parent; + internal List children = new List(); + internal float x, y, rotation, rotationIK, scaleX, scaleY; + internal bool flipX, flipY; + internal float m00, m01, m10, m11; + internal float worldX, worldY, worldRotation, worldScaleX, worldScaleY; + internal bool worldFlipX, worldFlipY; - public BoneData Data { get { return data; } } - public Skeleton Skeleton { get { return skeleton; } } - public Bone Parent { get { return parent; } } - public List Children { get { return children; } } - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - /// The forward kinetics rotation. - public float Rotation { get { return rotation; } set { rotation = value; } } - /// The inverse kinetics rotation, as calculated by any IK constraints. - public float RotationIK { get { return rotationIK; } set { rotationIK = value; } } - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - public float ScaleY { get { return scaleY; } set { scaleY = value; } } - public bool FlipX { get { return flipX; } set { flipX = value; } } - public bool FlipY { get { return flipY; } set { flipY = value; } } + public BoneData Data { get { return data; } } + public Skeleton Skeleton { get { return skeleton; } } + public Bone Parent { get { return parent; } } + public List Children { get { return children; } } + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + /// The forward kinetics rotation. + public float Rotation { get { return rotation; } set { rotation = value; } } + /// The inverse kinetics rotation, as calculated by any IK constraints. + public float RotationIK { get { return rotationIK; } set { rotationIK = value; } } + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + public bool FlipX { get { return flipX; } set { flipX = value; } } + public bool FlipY { get { return flipY; } set { flipY = value; } } - public float M00 { get { return m00; } } - public float M01 { get { return m01; } } - public float M10 { get { return m10; } } - public float M11 { get { return m11; } } - public float WorldX { get { return worldX; } } - public float WorldY { get { return worldY; } } - public float WorldRotation { get { return worldRotation; } } - public float WorldScaleX { get { return worldScaleX; } } - public float WorldScaleY { get { return worldScaleY; } } - public bool WorldFlipX { get { return worldFlipX; } set { worldFlipX = value; } } - public bool WorldFlipY { get { return worldFlipY; } set { worldFlipY = value; } } + public float M00 { get { return m00; } } + public float M01 { get { return m01; } } + public float M10 { get { return m10; } } + public float M11 { get { return m11; } } + public float WorldX { get { return worldX; } } + public float WorldY { get { return worldY; } } + public float WorldRotation { get { return worldRotation; } } + public float WorldScaleX { get { return worldScaleX; } } + public float WorldScaleY { get { return worldScaleY; } } + public bool WorldFlipX { get { return worldFlipX; } set { worldFlipX = value; } } + public bool WorldFlipY { get { return worldFlipY; } set { worldFlipY = value; } } - /// May be null. - public Bone (BoneData data, Skeleton skeleton, Bone parent) { - if (data == null) throw new ArgumentNullException("data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); - this.data = data; - this.skeleton = skeleton; - this.parent = parent; - SetToSetupPose(); - } + /// May be null. + public Bone(BoneData data, Skeleton skeleton, Bone parent) + { + if (data == null) throw new ArgumentNullException("data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); + this.data = data; + this.skeleton = skeleton; + this.parent = parent; + SetToSetupPose(); + } - /// Computes the world SRT using the parent bone and the local SRT. - public void UpdateWorldTransform () { - Bone parent = this.parent; - float x = this.x, y = this.y; - if (parent != null) { - worldX = x * parent.m00 + y * parent.m01 + parent.worldX; - worldY = x * parent.m10 + y * parent.m11 + parent.worldY; - if (data.inheritScale) { - worldScaleX = parent.worldScaleX * scaleX; - worldScaleY = parent.worldScaleY * scaleY; - } else { - worldScaleX = scaleX; - worldScaleY = scaleY; - } - worldRotation = data.inheritRotation ? parent.worldRotation + rotationIK : rotationIK; - worldFlipX = parent.worldFlipX != flipX; - worldFlipY = parent.worldFlipY != flipY; - } else { - Skeleton skeleton = this.skeleton; - bool skeletonFlipX = skeleton.flipX, skeletonFlipY = skeleton.flipY; - worldX = skeletonFlipX ? -x : x; - worldY = skeletonFlipY != yDown ? -y : y; - worldScaleX = scaleX; - worldScaleY = scaleY; - worldRotation = rotationIK; - worldFlipX = skeletonFlipX != flipX; - worldFlipY = skeletonFlipY != flipY; - } - float radians = worldRotation * (float)Math.PI / 180; - float cos = (float)Math.Cos(radians); - float sin = (float)Math.Sin(radians); - if (worldFlipX) { - m00 = -cos * worldScaleX; - m01 = sin * worldScaleY; - } else { - m00 = cos * worldScaleX; - m01 = -sin * worldScaleY; - } - if (worldFlipY != yDown) { - m10 = -sin * worldScaleX; - m11 = -cos * worldScaleY; - } else { - m10 = sin * worldScaleX; - m11 = cos * worldScaleY; - } - } + /// Computes the world SRT using the parent bone and the local SRT. + public void UpdateWorldTransform() + { + Bone parent = this.parent; + float x = this.x, y = this.y; + if (parent != null) + { + worldX = x * parent.m00 + y * parent.m01 + parent.worldX; + worldY = x * parent.m10 + y * parent.m11 + parent.worldY; + if (data.inheritScale) + { + worldScaleX = parent.worldScaleX * scaleX; + worldScaleY = parent.worldScaleY * scaleY; + } + else + { + worldScaleX = scaleX; + worldScaleY = scaleY; + } + worldRotation = data.inheritRotation ? parent.worldRotation + rotationIK : rotationIK; + worldFlipX = parent.worldFlipX != flipX; + worldFlipY = parent.worldFlipY != flipY; + } + else + { + Skeleton skeleton = this.skeleton; + bool skeletonFlipX = skeleton.flipX, skeletonFlipY = skeleton.flipY; + worldX = skeletonFlipX ? -x : x; + worldY = skeletonFlipY != yDown ? -y : y; + worldScaleX = scaleX; + worldScaleY = scaleY; + worldRotation = rotationIK; + worldFlipX = skeletonFlipX != flipX; + worldFlipY = skeletonFlipY != flipY; + } + float radians = worldRotation * (float)Math.PI / 180; + float cos = (float)Math.Cos(radians); + float sin = (float)Math.Sin(radians); + if (worldFlipX) + { + m00 = -cos * worldScaleX; + m01 = sin * worldScaleY; + } + else + { + m00 = cos * worldScaleX; + m01 = -sin * worldScaleY; + } + if (worldFlipY != yDown) + { + m10 = -sin * worldScaleX; + m11 = -cos * worldScaleY; + } + else + { + m10 = sin * worldScaleX; + m11 = cos * worldScaleY; + } + } - public void SetToSetupPose () { - BoneData data = this.data; - x = data.x; - y = data.y; - rotation = data.rotation; - rotationIK = rotation; - scaleX = data.scaleX; - scaleY = data.scaleY; - flipX = data.flipX; - flipY = data.flipY; - } + public void SetToSetupPose() + { + BoneData data = this.data; + x = data.x; + y = data.y; + rotation = data.rotation; + rotationIK = rotation; + scaleX = data.scaleX; + scaleY = data.scaleY; + flipX = data.flipX; + flipY = data.flipY; + } - public void worldToLocal (float worldX, float worldY, out float localX, out float localY) { - float dx = worldX - this.worldX, dy = worldY - this.worldY; - float m00 = this.m00, m10 = this.m10, m01 = this.m01, m11 = this.m11; - if (worldFlipX != (worldFlipY != yDown)) { - m00 = -m00; - m11 = -m11; - } - float invDet = 1 / (m00 * m11 - m01 * m10); - localX = (dx * m00 * invDet - dy * m01 * invDet); - localY = (dy * m11 * invDet - dx * m10 * invDet); - } + public void worldToLocal(float worldX, float worldY, out float localX, out float localY) + { + float dx = worldX - this.worldX, dy = worldY - this.worldY; + float m00 = this.m00, m10 = this.m10, m01 = this.m01, m11 = this.m11; + if (worldFlipX != (worldFlipY != yDown)) + { + m00 = -m00; + m11 = -m11; + } + float invDet = 1 / (m00 * m11 - m01 * m10); + localX = (dx * m00 * invDet - dy * m01 * invDet); + localY = (dy * m11 * invDet - dx * m10 * invDet); + } - public void localToWorld (float localX, float localY, out float worldX, out float worldY) { - worldX = localX * m00 + localY * m01 + this.worldX; - worldY = localX * m10 + localY * m11 + this.worldY; - } + public void localToWorld(float localX, float localY, out float worldX, out float worldY) + { + worldX = localX * m00 + localY * m01 + this.worldX; + worldY = localX * m10 + localY * m11 + this.worldY; + } - override public String ToString () { - return data.name; - } - } + override public String ToString() + { + return data.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/BoneData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/BoneData.cs index 907198c..3bd452d 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/BoneData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/BoneData.cs @@ -30,37 +30,41 @@ using System; -namespace Spine2_1_08 { - public class BoneData { - internal BoneData parent; - internal String name; - internal float length, x, y, rotation, scaleX = 1, scaleY = 1; - internal bool flipX, flipY; - internal bool inheritScale = true, inheritRotation = true; +namespace Spine2_1_08 +{ + public class BoneData + { + internal BoneData parent; + internal String name; + internal float length, x, y, rotation, scaleX = 1, scaleY = 1; + internal bool flipX, flipY; + internal bool inheritScale = true, inheritRotation = true; - /// May be null. - public BoneData Parent { get { return parent; } } - public String Name { get { return name; } } - public float Length { get { return length; } set { length = value; } } - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public float Rotation { get { return rotation; } set { rotation = value; } } - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - public float ScaleY { get { return scaleY; } set { scaleY = value; } } - public bool FlipX { get { return flipX; } set { flipX = value; } } - public bool FlipY { get { return flipY; } set { flipY = value; } } - public bool InheritScale { get { return inheritScale; } set { inheritScale = value; } } - public bool InheritRotation { get { return inheritRotation; } set { inheritRotation = value; } } + /// May be null. + public BoneData Parent { get { return parent; } } + public String Name { get { return name; } } + public float Length { get { return length; } set { length = value; } } + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public float Rotation { get { return rotation; } set { rotation = value; } } + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + public bool FlipX { get { return flipX; } set { flipX = value; } } + public bool FlipY { get { return flipY; } set { flipY = value; } } + public bool InheritScale { get { return inheritScale; } set { inheritScale = value; } } + public bool InheritRotation { get { return inheritRotation; } set { inheritRotation = value; } } - /// May be null. - public BoneData (String name, BoneData parent) { - if (name == null) throw new ArgumentNullException("name cannot be null."); - this.name = name; - this.parent = parent; - } + /// May be null. + public BoneData(String name, BoneData parent) + { + if (name == null) throw new ArgumentNullException("name cannot be null."); + this.name = name; + this.parent = parent; + } - override public String ToString () { - return name; - } - } + override public String ToString() + { + return name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Event.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Event.cs index ef56658..dd48732 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Event.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Event.cs @@ -30,19 +30,23 @@ using System; -namespace Spine2_1_08 { - public class Event { - public EventData Data { get; private set; } - public int Int { get; set; } - public float Float { get; set; } - public String String { get; set; } +namespace Spine2_1_08 +{ + public class Event + { + public EventData Data { get; private set; } + public int Int { get; set; } + public float Float { get; set; } + public String String { get; set; } - public Event (EventData data) { - Data = data; - } + public Event(EventData data) + { + Data = data; + } - override public String ToString () { - return Data.Name; - } - } + override public String ToString() + { + return Data.Name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/EventData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/EventData.cs index a06df6a..437bd62 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/EventData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/EventData.cs @@ -30,22 +30,26 @@ using System; -namespace Spine2_1_08 { - public class EventData { - internal String name; +namespace Spine2_1_08 +{ + public class EventData + { + internal String name; - public String Name { get { return name; } } - public int Int { get; set; } - public float Float { get; set; } - public String String { get; set; } + public String Name { get { return name; } } + public int Int { get; set; } + public float Float { get; set; } + public String String { get; set; } - public EventData (String name) { - if (name == null) throw new ArgumentNullException("name cannot be null."); - this.name = name; - } + public EventData(String name) + { + if (name == null) throw new ArgumentNullException("name cannot be null."); + this.name = name; + } - override public String ToString () { - return Name; - } - } + override public String ToString() + { + return Name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/IkConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/IkConstraint.cs index bb8cf40..2fc097a 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/IkConstraint.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/IkConstraint.cs @@ -31,118 +31,134 @@ using System; using System.Collections.Generic; -namespace Spine2_1_08 { - public class IkConstraint { - private const float radDeg = 180 / (float)Math.PI; +namespace Spine2_1_08 +{ + public class IkConstraint + { + private const float radDeg = 180 / (float)Math.PI; - internal IkConstraintData data; - internal List bones = new List(); - internal Bone target; - internal int bendDirection; - internal float mix; + internal IkConstraintData data; + internal List bones = new List(); + internal Bone target; + internal int bendDirection; + internal float mix; - public IkConstraintData Data { get { return data; } } - public List Bones { get { return bones; } } - public Bone Target { get { return target; } set { target = value; } } - public int BendDirection { get { return bendDirection; } set { bendDirection = value; } } - public float Mix { get { return mix; } set { mix = value; } } + public IkConstraintData Data { get { return data; } } + public List Bones { get { return bones; } } + public Bone Target { get { return target; } set { target = value; } } + public int BendDirection { get { return bendDirection; } set { bendDirection = value; } } + public float Mix { get { return mix; } set { mix = value; } } - public IkConstraint (IkConstraintData data, Skeleton skeleton) { - if (data == null) throw new ArgumentNullException("data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); - this.data = data; - mix = data.mix; - bendDirection = data.bendDirection; + public IkConstraint(IkConstraintData data, Skeleton skeleton) + { + if (data == null) throw new ArgumentNullException("data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); + this.data = data; + mix = data.mix; + bendDirection = data.bendDirection; - bones = new List(data.bones.Count); - foreach (BoneData boneData in data.bones) - bones.Add(skeleton.FindBone(boneData.name)); - target = skeleton.FindBone(data.target.name); - } + bones = new List(data.bones.Count); + foreach (BoneData boneData in data.bones) + bones.Add(skeleton.FindBone(boneData.name)); + target = skeleton.FindBone(data.target.name); + } - public void apply () { - Bone target = this.target; - List bones = this.bones; - switch (bones.Count) { - case 1: - apply(bones[0], target.worldX, target.worldY, mix); - break; - case 2: - apply(bones[0], bones[1], target.worldX, target.worldY, bendDirection, mix); - break; - } - } + public void apply() + { + Bone target = this.target; + List bones = this.bones; + switch (bones.Count) + { + case 1: + apply(bones[0], target.worldX, target.worldY, mix); + break; + case 2: + apply(bones[0], bones[1], target.worldX, target.worldY, bendDirection, mix); + break; + } + } - override public String ToString () { - return data.name; - } + override public String ToString() + { + return data.name; + } - /// 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 public void apply (Bone bone, float targetX, float targetY, float alpha) { - float parentRotation = (!bone.data.inheritRotation || bone.parent == null) ? 0 : bone.parent.worldRotation; - float rotation = bone.rotation; - float rotationIK = (float)Math.Atan2(targetY - bone.worldY, targetX - bone.worldX) * radDeg - parentRotation; - bone.rotationIK = rotation + (rotationIK - rotation) * alpha; - } + /// 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 public void apply(Bone bone, float targetX, float targetY, float alpha) + { + float parentRotation = (!bone.data.inheritRotation || bone.parent == null) ? 0 : bone.parent.worldRotation; + float rotation = bone.rotation; + float rotationIK = (float)Math.Atan2(targetY - bone.worldY, targetX - bone.worldX) * radDeg - parentRotation; + bone.rotationIK = rotation + (rotationIK - rotation) * 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. - /// Any descendant bone of the parent. - static public void apply (Bone parent, Bone child, float targetX, float targetY, int bendDirection, float alpha) { - float childRotation = child.rotation, parentRotation = parent.rotation; - if (alpha == 0) { - child.rotationIK = childRotation; - parent.rotationIK = parentRotation; - return; - } - float positionX, positionY; - Bone parentParent = parent.parent; - if (parentParent != null) { - parentParent.worldToLocal(targetX, targetY, out positionX, out positionY); - targetX = (positionX - parent.x) * parentParent.worldScaleX; - targetY = (positionY - parent.y) * parentParent.worldScaleY; - } else { - targetX -= parent.x; - targetY -= parent.y; - } - if (child.parent == parent) { - positionX = child.x; - positionY = child.y; - } else { - child.parent.localToWorld(child.x, child.y, out positionX, out positionY); - parent.worldToLocal(positionX, positionY, out positionX, out positionY); - } - float childX = positionX * parent.worldScaleX, childY = positionY * parent.worldScaleY; - float offset = (float)Math.Atan2(childY, childX); - float len1 = (float)Math.Sqrt(childX * childX + childY * childY), len2 = child.data.length * child.worldScaleX; - // Based on code by Ryan Juckett with permission: Copyright (c) 2008-2009 Ryan Juckett, http://www.ryanjuckett.com/ - float cosDenom = 2 * len1 * len2; - if (cosDenom < 0.0001f) { - child.rotationIK = childRotation + ((float)Math.Atan2(targetY, targetX) * radDeg - parentRotation - childRotation) - * alpha; - return; - } - float cos = (targetX * targetX + targetY * targetY - len1 * len1 - len2 * len2) / cosDenom; - if (cos < -1) - cos = -1; - else if (cos > 1) - cos = 1; - float childAngle = (float)Math.Acos(cos) * bendDirection; - float adjacent = len1 + len2 * cos, opposite = len2 * (float)Math.Sin(childAngle); - float parentAngle = (float)Math.Atan2(targetY * adjacent - targetX * opposite, targetX * adjacent + targetY * opposite); - float rotation = (parentAngle - offset) * radDeg - parentRotation; - if (rotation > 180) - rotation -= 360; - else if (rotation < -180) // - rotation += 360; - parent.rotationIK = parentRotation + rotation * alpha; - rotation = (childAngle + offset) * radDeg - childRotation; - if (rotation > 180) - rotation -= 360; - else if (rotation < -180) // - rotation += 360; - child.rotationIK = childRotation + (rotation + parent.worldRotation - child.parent.worldRotation) * 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. + /// Any descendant bone of the parent. + static public void apply(Bone parent, Bone child, float targetX, float targetY, int bendDirection, float alpha) + { + float childRotation = child.rotation, parentRotation = parent.rotation; + if (alpha == 0) + { + child.rotationIK = childRotation; + parent.rotationIK = parentRotation; + return; + } + float positionX, positionY; + Bone parentParent = parent.parent; + if (parentParent != null) + { + parentParent.worldToLocal(targetX, targetY, out positionX, out positionY); + targetX = (positionX - parent.x) * parentParent.worldScaleX; + targetY = (positionY - parent.y) * parentParent.worldScaleY; + } + else + { + targetX -= parent.x; + targetY -= parent.y; + } + if (child.parent == parent) + { + positionX = child.x; + positionY = child.y; + } + else + { + child.parent.localToWorld(child.x, child.y, out positionX, out positionY); + parent.worldToLocal(positionX, positionY, out positionX, out positionY); + } + float childX = positionX * parent.worldScaleX, childY = positionY * parent.worldScaleY; + float offset = (float)Math.Atan2(childY, childX); + float len1 = (float)Math.Sqrt(childX * childX + childY * childY), len2 = child.data.length * child.worldScaleX; + // Based on code by Ryan Juckett with permission: Copyright (c) 2008-2009 Ryan Juckett, http://www.ryanjuckett.com/ + float cosDenom = 2 * len1 * len2; + if (cosDenom < 0.0001f) + { + child.rotationIK = childRotation + ((float)Math.Atan2(targetY, targetX) * radDeg - parentRotation - childRotation) + * alpha; + return; + } + float cos = (targetX * targetX + targetY * targetY - len1 * len1 - len2 * len2) / cosDenom; + if (cos < -1) + cos = -1; + else if (cos > 1) + cos = 1; + float childAngle = (float)Math.Acos(cos) * bendDirection; + float adjacent = len1 + len2 * cos, opposite = len2 * (float)Math.Sin(childAngle); + float parentAngle = (float)Math.Atan2(targetY * adjacent - targetX * opposite, targetX * adjacent + targetY * opposite); + float rotation = (parentAngle - offset) * radDeg - parentRotation; + if (rotation > 180) + rotation -= 360; + else if (rotation < -180) // + rotation += 360; + parent.rotationIK = parentRotation + rotation * alpha; + rotation = (childAngle + offset) * radDeg - childRotation; + if (rotation > 180) + rotation -= 360; + else if (rotation < -180) // + rotation += 360; + child.rotationIK = childRotation + (rotation + parent.worldRotation - child.parent.worldRotation) * alpha; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/IkConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/IkConstraintData.cs index d83f3c4..b5fc366 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/IkConstraintData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/IkConstraintData.cs @@ -31,27 +31,31 @@ using System; using System.Collections.Generic; -namespace Spine2_1_08 { - public class IkConstraintData { - internal String name; - internal List bones = new List(); - internal BoneData target; - internal int bendDirection = 1; - internal float mix = 1; +namespace Spine2_1_08 +{ + public class IkConstraintData + { + internal String name; + internal List bones = new List(); + internal BoneData target; + internal int bendDirection = 1; + internal float mix = 1; - public String Name { get { return name; } } - public List Bones { get { return bones; } } - public BoneData Target { get { return target; } set { target = value; } } - public int BendDirection { get { return bendDirection; } set { bendDirection = value; } } - public float Mix { get { return mix; } set { mix = value; } } + public String Name { get { return name; } } + public List Bones { get { return bones; } } + public BoneData Target { get { return target; } set { target = value; } } + public int BendDirection { get { return bendDirection; } set { bendDirection = value; } } + public float Mix { get { return mix; } set { mix = value; } } - public IkConstraintData (String name) { - if (name == null) throw new ArgumentNullException("name cannot be null."); - this.name = name; - } + public IkConstraintData(String name) + { + if (name == null) throw new ArgumentNullException("name cannot be null."); + this.name = name; + } - override public String ToString () { - return name; - } - } + override public String ToString() + { + return name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Json.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Json.cs index 28b3e27..cbbf236 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Json.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Json.cs @@ -29,10 +29,10 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Text; -using System.Globalization; - + namespace Spine2_1_08 { // Example usage: @@ -58,7 +58,7 @@ namespace Spine2_1_08 // Debug.Log("deserialized: " + dict.GetType()); // Debug.Log("dict['array'][0]: " + ((List) dict["array"])[0]); // Debug.Log("dict['string']: " + (string) dict["string"]); - // Debug.Log("dict['float']: " + (float) dict["float"]); + // Debug.Log("dict['float']: " + (float) dict["float"]); // Debug.Log("dict['int']: " + (long) dict["int"]); // ints come out as longs // Debug.Log("dict['unicode']: " + (string) dict["unicode"]); // @@ -67,7 +67,7 @@ namespace Spine2_1_08 // Debug.Log("serialized: " + str); // } // } - + /// /// This class encodes and decodes JSON strings. /// Spec. details, see http://www.json.org/ @@ -75,24 +75,29 @@ namespace Spine2_1_08 /// JSON uses Arrays and Objects. These correspond here to the datatypes IList and IDictionary. /// All numbers are parsed to floats. /// - public static class Json { + public static class Json + { /// /// Parses the string json into a value /// /// A JSON string. - /// An List<object>, a Dictionary<string, object>, a float, an integer,a string, null, true, or false - public static object Deserialize (TextReader json) { - if (json == null) { + /// An List<object>, a Dictionary<string, object>, a float, an integer,a string, null, true, or false + public static object Deserialize(TextReader json) + { + if (json == null) + { return null; - } + } return Parser.Parse(json); } - - sealed class Parser : IDisposable { + + sealed class Parser : IDisposable + { const string WHITE_SPACE = " \t\n\r"; const string WORD_BREAK = " \t\n\r{}[],:\""; - - enum TOKEN { + + enum TOKEN + { NONE, CURLY_OPEN, CURLY_CLOSE, @@ -106,434 +111,499 @@ enum TOKEN { FALSE, NULL }; - + TextReader json; - - Parser(TextReader reader) { - json = reader; + + Parser(TextReader reader) + { + json = reader; } - public static object Parse (TextReader reader) { - using (var instance = new Parser(reader)) { + public static object Parse(TextReader reader) + { + using (var instance = new Parser(reader)) + { return instance.ParseValue(); } } - - public void Dispose() { + + public void Dispose() + { json.Dispose(); json = null; } - - Dictionary ParseObject() { + + Dictionary ParseObject() + { Dictionary table = new Dictionary(); - + // ditch opening brace json.Read(); - + // { - while (true) { - switch (NextToken) { - case TOKEN.NONE: - return null; - case TOKEN.COMMA: - continue; - case TOKEN.CURLY_CLOSE: - return table; - default: - // name - string name = ParseString(); - if (name == null) { - return null; - } - - // : - if (NextToken != TOKEN.COLON) { + while (true) + { + switch (NextToken) + { + case TOKEN.NONE: return null; - } - // ditch the colon - json.Read(); - - // value - table[name] = ParseValue(); - break; + case TOKEN.COMMA: + continue; + case TOKEN.CURLY_CLOSE: + return table; + default: + // name + string name = ParseString(); + if (name == null) + { + return null; + } + + // : + if (NextToken != TOKEN.COLON) + { + return null; + } + // ditch the colon + json.Read(); + + // value + table[name] = ParseValue(); + break; } } } - - List ParseArray() { + + List ParseArray() + { List array = new List(); - + // ditch opening bracket json.Read(); - + // [ var parsing = true; - while (parsing) { + while (parsing) + { TOKEN nextToken = NextToken; - - switch (nextToken) { - case TOKEN.NONE: - return null; - case TOKEN.COMMA: - continue; - case TOKEN.SQUARED_CLOSE: - parsing = false; - break; - default: - object value = ParseByToken(nextToken); - - array.Add(value); - break; + + switch (nextToken) + { + case TOKEN.NONE: + return null; + case TOKEN.COMMA: + continue; + case TOKEN.SQUARED_CLOSE: + parsing = false; + break; + default: + object value = ParseByToken(nextToken); + + array.Add(value); + break; } } - + return array; } - - object ParseValue() { + + object ParseValue() + { TOKEN nextToken = NextToken; return ParseByToken(nextToken); } - - object ParseByToken(TOKEN token) { - switch (token) { - case TOKEN.STRING: - return ParseString(); - case TOKEN.NUMBER: - return ParseNumber(); - case TOKEN.CURLY_OPEN: - return ParseObject(); - case TOKEN.SQUARED_OPEN: - return ParseArray(); - case TOKEN.TRUE: - return true; - case TOKEN.FALSE: - return false; - case TOKEN.NULL: - return null; - default: - return null; + + object ParseByToken(TOKEN token) + { + switch (token) + { + case TOKEN.STRING: + return ParseString(); + case TOKEN.NUMBER: + return ParseNumber(); + case TOKEN.CURLY_OPEN: + return ParseObject(); + case TOKEN.SQUARED_OPEN: + return ParseArray(); + case TOKEN.TRUE: + return true; + case TOKEN.FALSE: + return false; + case TOKEN.NULL: + return null; + default: + return null; } } - - string ParseString() { + + string ParseString() + { StringBuilder s = new StringBuilder(); char c; - + // ditch opening quote json.Read(); - + bool parsing = true; - while (parsing) { - - if (json.Peek() == -1) { + while (parsing) + { + + if (json.Peek() == -1) + { parsing = false; break; } - + c = NextChar; - switch (c) { - case '"': - parsing = false; - break; - case '\\': - if (json.Peek() == -1) { + switch (c) + { + case '"': parsing = false; break; - } - - c = NextChar; - switch (c) { - case '"': case '\\': - case '/': - s.Append(c); - break; - case 'b': - s.Append('\b'); - break; - case 'f': - s.Append('\f'); - break; - case 'n': - s.Append('\n'); - break; - case 'r': - s.Append('\r'); - break; - case 't': - s.Append('\t'); - break; - case 'u': - var hex = new StringBuilder(); - - for (int i=0; i< 4; i++) { - hex.Append(NextChar); + if (json.Peek() == -1) + { + parsing = false; + break; + } + + c = NextChar; + switch (c) + { + case '"': + case '\\': + case '/': + s.Append(c); + break; + case 'b': + s.Append('\b'); + break; + case 'f': + s.Append('\f'); + break; + case 'n': + s.Append('\n'); + break; + case 'r': + s.Append('\r'); + break; + case 't': + s.Append('\t'); + break; + case 'u': + var hex = new StringBuilder(); + + for (int i = 0; i < 4; i++) + { + hex.Append(NextChar); + } + + s.Append((char)Convert.ToInt32(hex.ToString(), 16)); + break; } - - s.Append((char) Convert.ToInt32(hex.ToString(), 16)); break; - } - break; - default: - s.Append(c); - break; + default: + s.Append(c); + break; } } - + return s.ToString(); } - - object ParseNumber() { + + object ParseNumber() + { string number = NextWord; - float parsedFloat; - float.TryParse(number, NumberStyles.Float, CultureInfo.InvariantCulture, out parsedFloat); - return parsedFloat; + float parsedFloat; + float.TryParse(number, NumberStyles.Float, CultureInfo.InvariantCulture, out parsedFloat); + return parsedFloat; } - - void EatWhitespace() { - while (WHITE_SPACE.IndexOf(PeekChar) != -1) { + + void EatWhitespace() + { + while (WHITE_SPACE.IndexOf(PeekChar) != -1) + { json.Read(); - - if (json.Peek() == -1) { + + if (json.Peek() == -1) + { break; } } } - - char PeekChar { - get { + + char PeekChar + { + get + { return Convert.ToChar(json.Peek()); } } - - char NextChar { - get { + + char NextChar + { + get + { return Convert.ToChar(json.Read()); } } - - string NextWord { - get { + + string NextWord + { + get + { StringBuilder word = new StringBuilder(); - - while (WORD_BREAK.IndexOf(PeekChar) == -1) { + + while (WORD_BREAK.IndexOf(PeekChar) == -1) + { word.Append(NextChar); - - if (json.Peek() == -1) { + + if (json.Peek() == -1) + { break; } } - + return word.ToString(); } } - - TOKEN NextToken { - get { + + TOKEN NextToken + { + get + { EatWhitespace(); - - if (json.Peek() == -1) { + + if (json.Peek() == -1) + { return TOKEN.NONE; } - + char c = PeekChar; - switch (c) { - case '{': - return TOKEN.CURLY_OPEN; - case '}': - json.Read(); - return TOKEN.CURLY_CLOSE; - case '[': - return TOKEN.SQUARED_OPEN; - case ']': - json.Read(); - return TOKEN.SQUARED_CLOSE; - case ',': - json.Read(); - return TOKEN.COMMA; - case '"': - return TOKEN.STRING; - case ':': - return TOKEN.COLON; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case '-': - return TOKEN.NUMBER; + switch (c) + { + case '{': + return TOKEN.CURLY_OPEN; + case '}': + json.Read(); + return TOKEN.CURLY_CLOSE; + case '[': + return TOKEN.SQUARED_OPEN; + case ']': + json.Read(); + return TOKEN.SQUARED_CLOSE; + case ',': + json.Read(); + return TOKEN.COMMA; + case '"': + return TOKEN.STRING; + case ':': + return TOKEN.COLON; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + return TOKEN.NUMBER; } - + string word = NextWord; - - switch (word) { - case "false": - return TOKEN.FALSE; - case "true": - return TOKEN.TRUE; - case "null": - return TOKEN.NULL; + + switch (word) + { + case "false": + return TOKEN.FALSE; + case "true": + return TOKEN.TRUE; + case "null": + return TOKEN.NULL; } - + return TOKEN.NONE; } } } - + /// /// Converts a IDictionary / IList object or a simple type (string, int, etc.) into a JSON string /// /// A Dictionary<string, object> / List<object> /// A JSON encoded string, or null if object 'json' is not serializable - public static string Serialize(object obj) { + public static string Serialize(object obj) + { return Serializer.Serialize(obj); } - - sealed class Serializer { + + sealed class Serializer + { StringBuilder builder; - - Serializer() { + + Serializer() + { builder = new StringBuilder(); } - - public static string Serialize(object obj) { + + public static string Serialize(object obj) + { var instance = new Serializer(); - + instance.SerializeValue(obj); - + return instance.builder.ToString(); } - - void SerializeValue(object value) { + + void SerializeValue(object value) + { IList asList; IDictionary asDict; string asStr; - - if (value == null) { + + if (value == null) + { builder.Append("null"); } - else if ((asStr = value as string) != null) { + else if ((asStr = value as string) != null) + { SerializeString(asStr); } - else if (value is bool) { + else if (value is bool) + { builder.Append(value.ToString().ToLower()); } - else if ((asList = value as IList) != null) { + else if ((asList = value as IList) != null) + { SerializeArray(asList); } - else if ((asDict = value as IDictionary) != null) { + else if ((asDict = value as IDictionary) != null) + { SerializeObject(asDict); } - else if (value is char) { + else if (value is char) + { SerializeString(value.ToString()); } - else { + else + { SerializeOther(value); } } - - void SerializeObject(IDictionary obj) { + + void SerializeObject(IDictionary obj) + { bool first = true; - + builder.Append('{'); - - foreach (object e in obj.Keys) { - if (!first) { + + foreach (object e in obj.Keys) + { + if (!first) + { builder.Append(','); } - + SerializeString(e.ToString()); builder.Append(':'); - + SerializeValue(obj[e]); - + first = false; } - + builder.Append('}'); } - - void SerializeArray(IList anArray) { + + void SerializeArray(IList anArray) + { builder.Append('['); - + bool first = true; - - foreach (object obj in anArray) { - if (!first) { + + foreach (object obj in anArray) + { + if (!first) + { builder.Append(','); } - + SerializeValue(obj); - + first = false; } - + builder.Append(']'); } - - void SerializeString(string str) { + + void SerializeString(string str) + { builder.Append('\"'); - + char[] charArray = str.ToCharArray(); - foreach (var c in charArray) { - switch (c) { - case '"': - builder.Append("\\\""); - break; - case '\\': - builder.Append("\\\\"); - break; - case '\b': - builder.Append("\\b"); - break; - case '\f': - builder.Append("\\f"); - break; - case '\n': - builder.Append("\\n"); - break; - case '\r': - builder.Append("\\r"); - break; - case '\t': - builder.Append("\\t"); - break; - default: - int codepoint = Convert.ToInt32(c); - if ((codepoint >= 32) && (codepoint <= 126)) { - builder.Append(c); - } - else { - builder.Append("\\u" + Convert.ToString(codepoint, 16).PadLeft(4, '0')); - } - break; + foreach (var c in charArray) + { + switch (c) + { + case '"': + builder.Append("\\\""); + break; + case '\\': + builder.Append("\\\\"); + break; + case '\b': + builder.Append("\\b"); + break; + case '\f': + builder.Append("\\f"); + break; + case '\n': + builder.Append("\\n"); + break; + case '\r': + builder.Append("\\r"); + break; + case '\t': + builder.Append("\\t"); + break; + default: + int codepoint = Convert.ToInt32(c); + if ((codepoint >= 32) && (codepoint <= 126)) + { + builder.Append(c); + } + else + { + builder.Append("\\u" + Convert.ToString(codepoint, 16).PadLeft(4, '0')); + } + break; } } - + builder.Append('\"'); } - - void SerializeOther(object value) { + + void SerializeOther(object value) + { if (value is float || value is int || value is uint || value is long - || value is float + || value is float || value is sbyte || value is byte || value is short || value is ushort || value is ulong - || value is decimal) { + || value is decimal) + { builder.Append(value.ToString()); } - else { + else + { SerializeString(value.ToString()); } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Skeleton.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Skeleton.cs index ebba642..cc64332 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Skeleton.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Skeleton.cs @@ -31,276 +31,318 @@ using System; using System.Collections.Generic; -namespace Spine2_1_08 { - public class Skeleton { - internal SkeletonData data; - internal List bones; - internal List slots; - internal List drawOrder; - internal List ikConstraints; - private List> boneCache = new List>(); - internal Skin skin; - internal float r = 1, g = 1, b = 1, a = 1; - internal float time; - internal bool flipX, flipY; - internal float x, y; +namespace Spine2_1_08 +{ + public class Skeleton + { + internal SkeletonData data; + internal List bones; + internal List slots; + internal List drawOrder; + internal List ikConstraints; + private List> boneCache = new List>(); + internal Skin skin; + internal float r = 1, g = 1, b = 1, a = 1; + internal float time; + internal bool flipX, flipY; + internal float x, y; - public SkeletonData Data { get { return data; } } - public List Bones { get { return bones; } } - public List Slots { get { return slots; } } - public List DrawOrder { get { return drawOrder; } } - public List IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } } - public Skin Skin { get { return skin; } set { skin = value; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } - public float Time { get { return time; } set { time = value; } } - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public bool FlipX { get { return flipX; } set { flipX = value; } } - public bool FlipY { get { return flipY; } set { flipY = value; } } + public SkeletonData Data { get { return data; } } + public List Bones { get { return bones; } } + public List Slots { get { return slots; } } + public List DrawOrder { get { return drawOrder; } } + public List IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } } + public Skin Skin { get { return skin; } set { skin = value; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } + public float Time { get { return time; } set { time = value; } } + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public bool FlipX { get { return flipX; } set { flipX = value; } } + public bool FlipY { get { return flipY; } set { flipY = value; } } - public Bone RootBone { - get { - return bones.Count == 0 ? null : bones[0]; - } - } + public Bone RootBone + { + get + { + return bones.Count == 0 ? null : bones[0]; + } + } - public Skeleton (SkeletonData data) { - if (data == null) throw new ArgumentNullException("data cannot be null."); - this.data = data; + public Skeleton(SkeletonData data) + { + if (data == null) throw new ArgumentNullException("data cannot be null."); + this.data = data; - bones = new List(data.bones.Count); - foreach (BoneData boneData in data.bones) { - Bone parent = boneData.parent == null ? null : bones[data.bones.IndexOf(boneData.parent)]; - Bone bone = new Bone(boneData, this, parent); - if (parent != null) parent.children.Add(bone); - bones.Add(bone); - } + bones = new List(data.bones.Count); + foreach (BoneData boneData in data.bones) + { + Bone parent = boneData.parent == null ? null : bones[data.bones.IndexOf(boneData.parent)]; + Bone bone = new Bone(boneData, this, parent); + if (parent != null) parent.children.Add(bone); + bones.Add(bone); + } - slots = new List(data.slots.Count); - drawOrder = new List(data.slots.Count); - foreach (SlotData slotData in data.slots) { - Bone bone = bones[data.bones.IndexOf(slotData.boneData)]; - Slot slot = new Slot(slotData, bone); - slots.Add(slot); - drawOrder.Add(slot); - } + slots = new List(data.slots.Count); + drawOrder = new List(data.slots.Count); + foreach (SlotData slotData in data.slots) + { + Bone bone = bones[data.bones.IndexOf(slotData.boneData)]; + Slot slot = new Slot(slotData, bone); + slots.Add(slot); + drawOrder.Add(slot); + } - ikConstraints = new List(data.ikConstraints.Count); - foreach (IkConstraintData ikConstraintData in data.ikConstraints) - ikConstraints.Add(new IkConstraint(ikConstraintData, this)); + ikConstraints = new List(data.ikConstraints.Count); + foreach (IkConstraintData ikConstraintData in data.ikConstraints) + ikConstraints.Add(new IkConstraint(ikConstraintData, this)); - UpdateCache(); - } + UpdateCache(); + } - /// Caches information about bones and IK constraints. Must be called if bones or IK constraints are added or - /// removed. - public void UpdateCache () { - List> boneCache = this.boneCache; - List ikConstraints = this.ikConstraints; - int ikConstraintsCount = ikConstraints.Count; + /// Caches information about bones and IK constraints. Must be called if bones or IK constraints are added or + /// removed. + public void UpdateCache() + { + List> boneCache = this.boneCache; + List ikConstraints = this.ikConstraints; + int ikConstraintsCount = ikConstraints.Count; - int arrayCount = ikConstraintsCount + 1; - if (boneCache.Count > arrayCount) boneCache.RemoveRange(arrayCount, boneCache.Count - arrayCount); - for (int i = 0, n = boneCache.Count; i < n; i++) - boneCache[i].Clear(); - while (boneCache.Count < arrayCount) - boneCache.Add(new List()); + int arrayCount = ikConstraintsCount + 1; + if (boneCache.Count > arrayCount) boneCache.RemoveRange(arrayCount, boneCache.Count - arrayCount); + for (int i = 0, n = boneCache.Count; i < n; i++) + boneCache[i].Clear(); + while (boneCache.Count < arrayCount) + boneCache.Add(new List()); - List nonIkBones = boneCache[0]; + List nonIkBones = boneCache[0]; - for (int i = 0, n = bones.Count; i < n; i++) { - Bone bone = bones[i]; - Bone current = bone; - do { - for (int ii = 0; ii < ikConstraintsCount; ii++) { - IkConstraint ikConstraint = ikConstraints[ii]; - Bone parent = ikConstraint.bones[0]; - Bone child = ikConstraint.bones[ikConstraint.bones.Count - 1]; - while (true) { - if (current == child) { - boneCache[ii].Add(bone); - boneCache[ii + 1].Add(bone); - goto outer; - } - if (child == parent) break; - child = child.parent; - } - } - current = current.parent; - } while (current != null); - nonIkBones.Add(bone); - outer: {} - } - } + for (int i = 0, n = bones.Count; i < n; i++) + { + Bone bone = bones[i]; + Bone current = bone; + do + { + for (int ii = 0; ii < ikConstraintsCount; ii++) + { + IkConstraint ikConstraint = ikConstraints[ii]; + Bone parent = ikConstraint.bones[0]; + Bone child = ikConstraint.bones[ikConstraint.bones.Count - 1]; + while (true) + { + if (current == child) + { + boneCache[ii].Add(bone); + boneCache[ii + 1].Add(bone); + goto outer; + } + if (child == parent) break; + child = child.parent; + } + } + current = current.parent; + } while (current != null); + nonIkBones.Add(bone); + outer: { } + } + } - /// Updates the world transform for each bone and applies IK constraints. - public void UpdateWorldTransform () { - List bones = this.bones; - for (int ii = 0, nn = bones.Count; ii < nn; ii++) { - Bone bone = bones[ii]; - bone.rotationIK = bone.rotation; - } - List> boneCache = this.boneCache; - List ikConstraints = this.ikConstraints; - int i = 0, last = boneCache.Count - 1; - while (true) { - List updateBones = boneCache[i]; - for (int ii = 0, nn = updateBones.Count; ii < nn; ii++) - updateBones[ii].UpdateWorldTransform(); - if (i == last) break; - ikConstraints[i].apply(); - i++; - } - } + /// Updates the world transform for each bone and applies IK constraints. + public void UpdateWorldTransform() + { + List bones = this.bones; + for (int ii = 0, nn = bones.Count; ii < nn; ii++) + { + Bone bone = bones[ii]; + bone.rotationIK = bone.rotation; + } + List> boneCache = this.boneCache; + List ikConstraints = this.ikConstraints; + int i = 0, last = boneCache.Count - 1; + while (true) + { + List updateBones = boneCache[i]; + for (int ii = 0, nn = updateBones.Count; ii < nn; ii++) + updateBones[ii].UpdateWorldTransform(); + if (i == last) break; + ikConstraints[i].apply(); + i++; + } + } - /// Sets the bones and slots to their setup pose values. - public void SetToSetupPose () { - SetBonesToSetupPose(); - SetSlotsToSetupPose(); - } + /// Sets the bones and slots to their setup pose values. + public void SetToSetupPose() + { + SetBonesToSetupPose(); + SetSlotsToSetupPose(); + } - public void SetBonesToSetupPose () { - List bones = this.bones; - for (int i = 0, n = bones.Count; i < n; i++) - bones[i].SetToSetupPose(); + public void SetBonesToSetupPose() + { + List bones = this.bones; + for (int i = 0, n = bones.Count; i < n; i++) + bones[i].SetToSetupPose(); - List ikConstraints = this.ikConstraints; - for (int i = 0, n = ikConstraints.Count; i < n; i++) { - IkConstraint ikConstraint = ikConstraints[i]; - ikConstraint.bendDirection = ikConstraint.data.bendDirection; - ikConstraint.mix = ikConstraint.data.mix; - } - } + List ikConstraints = this.ikConstraints; + for (int i = 0, n = ikConstraints.Count; i < n; i++) + { + IkConstraint ikConstraint = ikConstraints[i]; + ikConstraint.bendDirection = ikConstraint.data.bendDirection; + ikConstraint.mix = ikConstraint.data.mix; + } + } - public void SetSlotsToSetupPose () { - List slots = this.slots; - drawOrder.Clear(); - drawOrder.AddRange(slots); - for (int i = 0, n = slots.Count; i < n; i++) - slots[i].SetToSetupPose(i); - } + public void SetSlotsToSetupPose() + { + List slots = this.slots; + drawOrder.Clear(); + drawOrder.AddRange(slots); + for (int i = 0, n = slots.Count; i < n; i++) + slots[i].SetToSetupPose(i); + } - /// May be null. - public Bone FindBone (String boneName) { - if (boneName == null) throw new ArgumentNullException("boneName cannot be null."); - List bones = this.bones; - for (int i = 0, n = bones.Count; i < n; i++) { - Bone bone = bones[i]; - if (bone.data.name == boneName) return bone; - } - return null; - } + /// May be null. + public Bone FindBone(String boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName cannot be null."); + List bones = this.bones; + for (int i = 0, n = bones.Count; i < n; i++) + { + Bone bone = bones[i]; + if (bone.data.name == boneName) return bone; + } + return null; + } - /// -1 if the bone was not found. - public int FindBoneIndex (String boneName) { - if (boneName == null) throw new ArgumentNullException("boneName cannot be null."); - List bones = this.bones; - for (int i = 0, n = bones.Count; i < n; i++) - if (bones[i].data.name == boneName) return i; - return -1; - } + /// -1 if the bone was not found. + public int FindBoneIndex(String boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName cannot be null."); + List bones = this.bones; + for (int i = 0, n = bones.Count; i < n; i++) + if (bones[i].data.name == boneName) return i; + return -1; + } - /// May be null. - public Slot FindSlot (String slotName) { - if (slotName == null) throw new ArgumentNullException("slotName cannot be null."); - List slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) { - Slot slot = slots[i]; - if (slot.data.name == slotName) return slot; - } - return null; - } + /// May be null. + public Slot FindSlot(String slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName cannot be null."); + List slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + { + Slot slot = slots[i]; + if (slot.data.name == slotName) return slot; + } + return null; + } - /// -1 if the bone was not found. - public int FindSlotIndex (String slotName) { - if (slotName == null) throw new ArgumentNullException("slotName cannot be null."); - List slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) - if (slots[i].data.name.Equals(slotName)) return i; - return -1; - } + /// -1 if the bone was not found. + public int FindSlotIndex(String slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName cannot be null."); + List slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + if (slots[i].data.name.Equals(slotName)) return i; + return -1; + } - /// Sets a skin by name (see SetSkin). - public void SetSkin (String skinName) { - Skin skin = data.FindSkin(skinName); - if (skin == null) throw new ArgumentException("Skin not found: " + skinName); - SetSkin(skin); - } + /// Sets a skin by name (see SetSkin). + public void SetSkin(String skinName) + { + Skin skin = data.FindSkin(skinName); + if (skin == null) throw new ArgumentException("Skin not found: " + skinName); + SetSkin(skin); + } - /// Sets the skin used to look up attachments before looking in the {@link SkeletonData#getDefaultSkin() default - /// skin}. Attachmentsfrom 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. - /// May be null. - public void SetSkin (Skin newSkin) { - if (newSkin != null) { - if (skin != null) - newSkin.AttachAll(this, skin); - else { - List slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) { - Slot slot = slots[i]; - String name = slot.data.attachmentName; - if (name != null) { - Attachment attachment = newSkin.GetAttachment(i, name); - if (attachment != null) slot.Attachment = attachment; - } - } - } - } - skin = newSkin; - } + /// Sets the skin used to look up attachments before looking in the {@link SkeletonData#getDefaultSkin() default + /// skin}. Attachmentsfrom 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. + /// May be null. + public void SetSkin(Skin newSkin) + { + if (newSkin != null) + { + if (skin != null) + newSkin.AttachAll(this, skin); + else + { + List slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + { + Slot slot = slots[i]; + String name = slot.data.attachmentName; + if (name != null) + { + Attachment attachment = newSkin.GetAttachment(i, name); + if (attachment != null) slot.Attachment = attachment; + } + } + } + } + skin = newSkin; + } - /// May be null. - public Attachment GetAttachment (String slotName, String attachmentName) { - return GetAttachment(data.FindSlotIndex(slotName), attachmentName); - } + /// May be null. + public Attachment GetAttachment(String slotName, String attachmentName) + { + return GetAttachment(data.FindSlotIndex(slotName), attachmentName); + } - /// May be null. - public Attachment GetAttachment (int slotIndex, String attachmentName) { - if (attachmentName == null) throw new ArgumentNullException("attachmentName cannot be null."); - if (skin != null) { - Attachment attachment = skin.GetAttachment(slotIndex, attachmentName); - if (attachment != null) return attachment; - } - if (data.defaultSkin != null) return data.defaultSkin.GetAttachment(slotIndex, attachmentName); - return null; - } + /// May be null. + public Attachment GetAttachment(int slotIndex, String attachmentName) + { + if (attachmentName == null) throw new ArgumentNullException("attachmentName cannot be null."); + if (skin != null) + { + Attachment attachment = skin.GetAttachment(slotIndex, attachmentName); + if (attachment != null) return attachment; + } + if (data.defaultSkin != null) return data.defaultSkin.GetAttachment(slotIndex, attachmentName); + return null; + } - /// May be null. - public void SetAttachment (String slotName, String attachmentName) { - if (slotName == null) throw new ArgumentNullException("slotName cannot be null."); - List slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) { - Slot slot = slots[i]; - if (slot.data.name == slotName) { - Attachment attachment = null; - if (attachmentName != null) { - attachment = GetAttachment(i, attachmentName); - if (attachment == null) throw new Exception("Attachment not found: " + attachmentName + ", for slot: " + slotName); - } - slot.Attachment = attachment; - return; - } - } - throw new Exception("Slot not found: " + slotName); - } + /// May be null. + public void SetAttachment(String slotName, String attachmentName) + { + if (slotName == null) throw new ArgumentNullException("slotName cannot be null."); + List slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + { + Slot slot = slots[i]; + if (slot.data.name == slotName) + { + Attachment attachment = null; + if (attachmentName != null) + { + attachment = GetAttachment(i, attachmentName); + if (attachment == null) throw new Exception("Attachment not found: " + attachmentName + ", for slot: " + slotName); + } + slot.Attachment = attachment; + return; + } + } + throw new Exception("Slot not found: " + slotName); + } - /** @return May be null. */ - public IkConstraint FindIkConstraint (String ikConstraintName) { - if (ikConstraintName == null) throw new ArgumentNullException("ikConstraintName cannot be null."); - List ikConstraints = this.ikConstraints; - for (int i = 0, n = ikConstraints.Count; i < n; i++) { - IkConstraint ikConstraint = ikConstraints[i]; - if (ikConstraint.data.name == ikConstraintName) return ikConstraint; - } - return null; - } + /** @return May be null. */ + public IkConstraint FindIkConstraint(String ikConstraintName) + { + if (ikConstraintName == null) throw new ArgumentNullException("ikConstraintName cannot be null."); + List ikConstraints = this.ikConstraints; + for (int i = 0, n = ikConstraints.Count; i < n; i++) + { + IkConstraint ikConstraint = ikConstraints[i]; + if (ikConstraint.data.name == ikConstraintName) return ikConstraint; + } + return null; + } - public void Update (float delta) { - time += delta; - } - } + public void Update(float delta) + { + time += delta; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/SkeletonBounds.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/SkeletonBounds.cs index a761031..69f088d 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/SkeletonBounds.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/SkeletonBounds.cs @@ -31,185 +31,209 @@ using System; using System.Collections.Generic; -namespace Spine2_1_08 { - public class SkeletonBounds { - private List polygonPool = new List(); - private float minX, minY, maxX, maxY; - - public List BoundingBoxes { get; private set; } - public List Polygons { get; private set; } - public float MinX { get { return minX; } set { minX = value; } } - public float MinY { get { return minY; } set { minY = value; } } - public float MaxX { get { return maxX; } set { maxX = value; } } - public float MaxY { get { return maxY; } set { maxY = value; } } - public float Width { get { return maxX - minX; } } - public float Height { get { return maxY - minY; } } - - public SkeletonBounds () { - BoundingBoxes = new List(); - Polygons = new List(); - } - - public void Update (Skeleton skeleton, bool updateAabb) { - List boundingBoxes = BoundingBoxes; - List polygons = Polygons; - List slots = skeleton.slots; - int slotCount = slots.Count; - - boundingBoxes.Clear(); - foreach (Polygon polygon in polygons) - polygonPool.Add(polygon); - polygons.Clear(); - - for (int i = 0; i < slotCount; i++) { - Slot slot = slots[i]; - BoundingBoxAttachment boundingBox = slot.attachment as BoundingBoxAttachment; - if (boundingBox == null) continue; - boundingBoxes.Add(boundingBox); - - Polygon polygon = null; - int poolCount = polygonPool.Count; - if (poolCount > 0) { - polygon = polygonPool[poolCount - 1]; - polygonPool.RemoveAt(poolCount - 1); - } else - polygon = new Polygon(); - polygons.Add(polygon); - - int count = boundingBox.Vertices.Length; - polygon.Count = count; - if (polygon.Vertices.Length < count) polygon.Vertices = new float[count]; - boundingBox.ComputeWorldVertices(slot.bone, polygon.Vertices); - } - - if (updateAabb) aabbCompute(); - } - - private void aabbCompute () { - float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; - List polygons = Polygons; - for (int i = 0, n = polygons.Count; i < n; i++) { - Polygon polygon = polygons[i]; - float[] vertices = polygon.Vertices; - for (int ii = 0, nn = polygon.Count; ii < nn; ii += 2) { - float x = vertices[ii]; - float y = vertices[ii + 1]; - minX = Math.Min(minX, x); - minY = Math.Min(minY, y); - maxX = Math.Max(maxX, x); - maxY = Math.Max(maxY, y); - } - } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; - } - - - /// Returns true if the axis aligned bounding box contains the point. - public bool AabbContainsPoint (float x, float y) { - return x >= minX && x <= maxX && y >= minY && y <= maxY; - } - - /// Returns true if the axis aligned bounding box intersects the line segment. - public bool AabbIntersectsSegment (float x1, float y1, float x2, float y2) { - float minX = this.minX; - float minY = this.minY; - float maxX = this.maxX; - float maxY = this.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; - } - - /// Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds. - public bool AabbIntersectsSkeleton (SkeletonBounds bounds) { - return minX < bounds.maxX && maxX > bounds.minX && minY < bounds.maxY && maxY > bounds.minY; - } - - /// Returns true if the polygon contains the point. - public bool ContainsPoint (Polygon polygon, float x, float y) { - float[] 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; - } - - /// 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. - public BoundingBoxAttachment ContainsPoint (float x, float y) { - List polygons = Polygons; - for (int i = 0, n = polygons.Count; i < n; i++) - if (ContainsPoint(polygons[i], x, y)) return BoundingBoxes[i]; - return null; - } - - /// 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. - public BoundingBoxAttachment IntersectsSegment (float x1, float y1, float x2, float y2) { - List polygons = Polygons; - for (int i = 0, n = polygons.Count; i < n; i++) - if (IntersectsSegment(polygons[i], x1, y1, x2, y2)) return BoundingBoxes[i]; - return null; - } - - /// Returns true if the polygon contains the line segment. - public bool IntersectsSegment (Polygon polygon, float x1, float y1, float x2, float y2) { - float[] vertices = polygon.Vertices; - int 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 (int 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; - } - - public Polygon getPolygon (BoundingBoxAttachment attachment) { - int index = BoundingBoxes.IndexOf(attachment); - return index == -1 ? null : Polygons[index]; - } - } - - public class Polygon { - public float[] Vertices { get; set; } - public int Count { get; set; } - - public Polygon () { - Vertices = new float[16]; - } - } +namespace Spine2_1_08 +{ + public class SkeletonBounds + { + private List polygonPool = new List(); + private float minX, minY, maxX, maxY; + + public List BoundingBoxes { get; private set; } + public List Polygons { get; private set; } + public float MinX { get { return minX; } set { minX = value; } } + public float MinY { get { return minY; } set { minY = value; } } + public float MaxX { get { return maxX; } set { maxX = value; } } + public float MaxY { get { return maxY; } set { maxY = value; } } + public float Width { get { return maxX - minX; } } + public float Height { get { return maxY - minY; } } + + public SkeletonBounds() + { + BoundingBoxes = new List(); + Polygons = new List(); + } + + public void Update(Skeleton skeleton, bool updateAabb) + { + List boundingBoxes = BoundingBoxes; + List polygons = Polygons; + List slots = skeleton.slots; + int slotCount = slots.Count; + + boundingBoxes.Clear(); + foreach (Polygon polygon in polygons) + polygonPool.Add(polygon); + polygons.Clear(); + + for (int i = 0; i < slotCount; i++) + { + Slot slot = slots[i]; + BoundingBoxAttachment boundingBox = slot.attachment as BoundingBoxAttachment; + if (boundingBox == null) continue; + boundingBoxes.Add(boundingBox); + + Polygon polygon = null; + int poolCount = polygonPool.Count; + if (poolCount > 0) + { + polygon = polygonPool[poolCount - 1]; + polygonPool.RemoveAt(poolCount - 1); + } + else + polygon = new Polygon(); + polygons.Add(polygon); + + int count = boundingBox.Vertices.Length; + polygon.Count = count; + if (polygon.Vertices.Length < count) polygon.Vertices = new float[count]; + boundingBox.ComputeWorldVertices(slot.bone, polygon.Vertices); + } + + if (updateAabb) aabbCompute(); + } + + private void aabbCompute() + { + float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; + List polygons = Polygons; + for (int i = 0, n = polygons.Count; i < n; i++) + { + Polygon polygon = polygons[i]; + float[] vertices = polygon.Vertices; + for (int ii = 0, nn = polygon.Count; ii < nn; ii += 2) + { + float x = vertices[ii]; + float y = vertices[ii + 1]; + minX = Math.Min(minX, x); + minY = Math.Min(minY, y); + maxX = Math.Max(maxX, x); + maxY = Math.Max(maxY, y); + } + } + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + + /// Returns true if the axis aligned bounding box contains the point. + public bool AabbContainsPoint(float x, float y) + { + return x >= minX && x <= maxX && y >= minY && y <= maxY; + } + + /// Returns true if the axis aligned bounding box intersects the line segment. + public bool AabbIntersectsSegment(float x1, float y1, float x2, float y2) + { + float minX = this.minX; + float minY = this.minY; + float maxX = this.maxX; + float maxY = this.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; + } + + /// Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds. + public bool AabbIntersectsSkeleton(SkeletonBounds bounds) + { + return minX < bounds.maxX && maxX > bounds.minX && minY < bounds.maxY && maxY > bounds.minY; + } + + /// Returns true if the polygon contains the point. + public bool ContainsPoint(Polygon polygon, float x, float y) + { + float[] 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; + } + + /// 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. + public BoundingBoxAttachment ContainsPoint(float x, float y) + { + List polygons = Polygons; + for (int i = 0, n = polygons.Count; i < n; i++) + if (ContainsPoint(polygons[i], x, y)) return BoundingBoxes[i]; + return null; + } + + /// 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. + public BoundingBoxAttachment IntersectsSegment(float x1, float y1, float x2, float y2) + { + List polygons = Polygons; + for (int i = 0, n = polygons.Count; i < n; i++) + if (IntersectsSegment(polygons[i], x1, y1, x2, y2)) return BoundingBoxes[i]; + return null; + } + + /// Returns true if the polygon contains the line segment. + public bool IntersectsSegment(Polygon polygon, float x1, float y1, float x2, float y2) + { + float[] vertices = polygon.Vertices; + int 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 (int 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; + } + + public Polygon getPolygon(BoundingBoxAttachment attachment) + { + int index = BoundingBoxes.IndexOf(attachment); + return index == -1 ? null : Polygons[index]; + } + } + + public class Polygon + { + public float[] Vertices { get; set; } + public int Count { get; set; } + + public Polygon() + { + Vertices = new float[16]; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/SkeletonData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/SkeletonData.cs index 9a75a47..7f501a6 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/SkeletonData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/SkeletonData.cs @@ -31,128 +31,143 @@ using System; using System.Collections.Generic; -namespace Spine2_1_08 { - public class SkeletonData { - internal String name; - internal List bones = new List(); - internal List slots = new List(); - internal List skins = new List(); - internal Skin defaultSkin; - internal List events = new List(); - internal List animations = new List(); - internal List ikConstraints = new List(); - internal float width, height; - internal String version, hash; - - public String Name { get { return name; } set { name = value; } } - public List Bones { get { return bones; } } // Ordered parents first. - public List Slots { get { return slots; } } // Setup pose draw order. - public List Skins { get { return skins; } set { skins = value; } } - /// May be null. - public Skin DefaultSkin { get { return defaultSkin; } set { defaultSkin = value; } } - public List Events { get { return events; } set { events = value; } } - public List Animations { get { return animations; } set { animations = value; } } - public List IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } } - public float Width { get { return width; } set { width = value; } } - public float Height { get { return height; } set { height = value; } } - /// The Spine version used to export this data. - public String Version { get { return version; } set { version = value; } } - public String Hash { get { return hash; } set { hash = value; } } - - // --- Bones. - - /// May be null. - public BoneData FindBone (String boneName) { - if (boneName == null) throw new ArgumentNullException("boneName cannot be null."); - List bones = this.bones; - for (int i = 0, n = bones.Count; i < n; i++) { - BoneData bone = bones[i]; - if (bone.name == boneName) return bone; - } - return null; - } - - /// -1 if the bone was not found. - public int FindBoneIndex (String boneName) { - if (boneName == null) throw new ArgumentNullException("boneName cannot be null."); - List bones = this.bones; - for (int i = 0, n = bones.Count; i < n; i++) - if (bones[i].name == boneName) return i; - return -1; - } - - // --- Slots. - - /// May be null. - public SlotData FindSlot (String slotName) { - if (slotName == null) throw new ArgumentNullException("slotName cannot be null."); - List slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) { - SlotData slot = slots[i]; - if (slot.name == slotName) return slot; - } - return null; - } - - /// -1 if the bone was not found. - public int FindSlotIndex (String slotName) { - if (slotName == null) throw new ArgumentNullException("slotName cannot be null."); - List slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) - if (slots[i].name == slotName) return i; - return -1; - } - - // --- Skins. - - /// May be null. - public Skin FindSkin (String skinName) { - if (skinName == null) throw new ArgumentNullException("skinName cannot be null."); - foreach (Skin skin in skins) - if (skin.name == skinName) return skin; - return null; - } - - // --- Events. - - /// May be null. - public EventData FindEvent (String eventDataName) { - if (eventDataName == null) throw new ArgumentNullException("eventDataName cannot be null."); - foreach (EventData eventData in events) - if (eventData.name == eventDataName) return eventData; - return null; - } - - // --- Animations. - - /// May be null. - public Animation FindAnimation (String animationName) { - if (animationName == null) throw new ArgumentNullException("animationName cannot be null."); - List animations = this.animations; - for (int i = 0, n = animations.Count; i < n; i++) { - Animation animation = animations[i]; - if (animation.name == animationName) return animation; - } - return null; - } - - // --- IK constraints. - - /// May be null. - public IkConstraintData FindIkConstraint (String ikConstraintName) { - if (ikConstraintName == null) throw new ArgumentNullException("ikConstraintName cannot be null."); - List ikConstraints = this.ikConstraints; - for (int i = 0, n = ikConstraints.Count; i < n; i++) { - IkConstraintData ikConstraint = ikConstraints[i]; - if (ikConstraint.name == ikConstraintName) return ikConstraint; - } - return null; - } - - // --- - - override public String ToString () { - return name ?? base.ToString(); - } - } +namespace Spine2_1_08 +{ + public class SkeletonData + { + internal String name; + internal List bones = new List(); + internal List slots = new List(); + internal List skins = new List(); + internal Skin defaultSkin; + internal List events = new List(); + internal List animations = new List(); + internal List ikConstraints = new List(); + internal float width, height; + internal String version, hash; + + public String Name { get { return name; } set { name = value; } } + public List Bones { get { return bones; } } // Ordered parents first. + public List Slots { get { return slots; } } // Setup pose draw order. + public List Skins { get { return skins; } set { skins = value; } } + /// May be null. + public Skin DefaultSkin { get { return defaultSkin; } set { defaultSkin = value; } } + public List Events { get { return events; } set { events = value; } } + public List Animations { get { return animations; } set { animations = value; } } + public List IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } } + public float Width { get { return width; } set { width = value; } } + public float Height { get { return height; } set { height = value; } } + /// The Spine version used to export this data. + public String Version { get { return version; } set { version = value; } } + public String Hash { get { return hash; } set { hash = value; } } + + // --- Bones. + + /// May be null. + public BoneData FindBone(String boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName cannot be null."); + List bones = this.bones; + for (int i = 0, n = bones.Count; i < n; i++) + { + BoneData bone = bones[i]; + if (bone.name == boneName) return bone; + } + return null; + } + + /// -1 if the bone was not found. + public int FindBoneIndex(String boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName cannot be null."); + List bones = this.bones; + for (int i = 0, n = bones.Count; i < n; i++) + if (bones[i].name == boneName) return i; + return -1; + } + + // --- Slots. + + /// May be null. + public SlotData FindSlot(String slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName cannot be null."); + List slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + { + SlotData slot = slots[i]; + if (slot.name == slotName) return slot; + } + return null; + } + + /// -1 if the bone was not found. + public int FindSlotIndex(String slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName cannot be null."); + List slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + if (slots[i].name == slotName) return i; + return -1; + } + + // --- Skins. + + /// May be null. + public Skin FindSkin(String skinName) + { + if (skinName == null) throw new ArgumentNullException("skinName cannot be null."); + foreach (Skin skin in skins) + if (skin.name == skinName) return skin; + return null; + } + + // --- Events. + + /// May be null. + public EventData FindEvent(String eventDataName) + { + if (eventDataName == null) throw new ArgumentNullException("eventDataName cannot be null."); + foreach (EventData eventData in events) + if (eventData.name == eventDataName) return eventData; + return null; + } + + // --- Animations. + + /// May be null. + public Animation FindAnimation(String animationName) + { + if (animationName == null) throw new ArgumentNullException("animationName cannot be null."); + List animations = this.animations; + for (int i = 0, n = animations.Count; i < n; i++) + { + Animation animation = animations[i]; + if (animation.name == animationName) return animation; + } + return null; + } + + // --- IK constraints. + + /// May be null. + public IkConstraintData FindIkConstraint(String ikConstraintName) + { + if (ikConstraintName == null) throw new ArgumentNullException("ikConstraintName cannot be null."); + List ikConstraints = this.ikConstraints; + for (int i = 0, n = ikConstraints.Count; i < n; i++) + { + IkConstraintData ikConstraint = ikConstraints[i]; + if (ikConstraint.name == ikConstraintName) return ikConstraint; + } + return null; + } + + // --- + + override public String ToString() + { + return name ?? base.ToString(); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/SkeletonJson.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/SkeletonJson.cs index 3e32882..2fb2398 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/SkeletonJson.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/SkeletonJson.cs @@ -29,28 +29,32 @@ *****************************************************************************/ using System; -using System.IO; using System.Collections.Generic; +using System.IO; #if WINDOWS_STOREAPP using System.Threading.Tasks; using Windows.Storage; #endif -namespace Spine2_1_08 { - public class SkeletonJson { - private AttachmentLoader attachmentLoader; - public float Scale { get; set; } +namespace Spine2_1_08 +{ + public class SkeletonJson + { + private AttachmentLoader attachmentLoader; + public float Scale { get; set; } - public SkeletonJson (Atlas atlas) - : this(new AtlasAttachmentLoader(atlas)) { - } + public SkeletonJson(Atlas atlas) + : this(new AtlasAttachmentLoader(atlas)) + { + } - public SkeletonJson (AttachmentLoader attachmentLoader) { - if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader cannot be null."); - this.attachmentLoader = attachmentLoader; - Scale = 1; - } + public SkeletonJson(AttachmentLoader attachmentLoader) + { + if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader cannot be null."); + this.attachmentLoader = attachmentLoader; + Scale = 1; + } #if WINDOWS_STOREAPP private async Task ReadFile(string path) { @@ -67,573 +71,657 @@ public SkeletonData ReadSkeletonData (String path) { return this.ReadFile(path).Result; } #else - public SkeletonData ReadSkeletonData (String path) { + public SkeletonData ReadSkeletonData(String path) + { #if WINDOWS_PHONE Stream stream = Microsoft.Xna.Framework.TitleContainer.OpenStream(path); using (StreamReader reader = new StreamReader(stream)) { #else - using (StreamReader reader = new StreamReader(path)) { + using (StreamReader reader = new StreamReader(path)) + { #endif - SkeletonData skeletonData = ReadSkeletonData(reader); - skeletonData.name = Path.GetFileNameWithoutExtension(path); - return skeletonData; - } - } + SkeletonData skeletonData = ReadSkeletonData(reader); + skeletonData.name = Path.GetFileNameWithoutExtension(path); + return skeletonData; + } + } #endif - public SkeletonData ReadSkeletonData (TextReader reader) { - if (reader == null) throw new ArgumentNullException("reader cannot be null."); - - var skeletonData = new SkeletonData(); - - var root = Json.Deserialize(reader) as Dictionary; - if (root == null) throw new Exception("Invalid JSON."); - - // Skeleton. - if (root.ContainsKey("skeleton")) { - var skeletonMap = (Dictionary)root["skeleton"]; - skeletonData.hash = (String)skeletonMap["hash"]; - skeletonData.version = (String)skeletonMap["spine"]; - skeletonData.width = GetFloat(skeletonMap, "width", 0); - skeletonData.height = GetFloat(skeletonMap, "height", 0); - } - - // Bones. - foreach (Dictionary boneMap in (List)root["bones"]) { - BoneData parent = null; - if (boneMap.ContainsKey("parent")) { - parent = skeletonData.FindBone((String)boneMap["parent"]); - if (parent == null) - throw new Exception("Parent bone not found: " + boneMap["parent"]); - } - var boneData = new BoneData((String)boneMap["name"], parent); - boneData.length = GetFloat(boneMap, "length", 0) * Scale; - boneData.x = GetFloat(boneMap, "x", 0) * Scale; - boneData.y = GetFloat(boneMap, "y", 0) * Scale; - boneData.rotation = GetFloat(boneMap, "rotation", 0); - boneData.scaleX = GetFloat(boneMap, "scaleX", 1); - boneData.scaleY = GetFloat(boneMap, "scaleY", 1); - boneData.flipX = GetBoolean(boneMap, "flipX", false); - boneData.flipY = GetBoolean(boneMap, "flipY", false); - boneData.inheritScale = GetBoolean(boneMap, "inheritScale", true); - boneData.inheritRotation = GetBoolean(boneMap, "inheritRotation", true); - skeletonData.bones.Add(boneData); - } - - // IK constraints. - if (root.ContainsKey("ik")) { - foreach (Dictionary ikMap in (List)root["ik"]) { - IkConstraintData ikConstraintData = new IkConstraintData((String)ikMap["name"]); - - foreach (String boneName in (List)ikMap["bones"]) { - BoneData bone = skeletonData.FindBone(boneName); - if (bone == null) throw new Exception("IK bone not found: " + boneName); - ikConstraintData.bones.Add(bone); - } - - String targetName = (String)ikMap["target"]; - ikConstraintData.target = skeletonData.FindBone(targetName); - if (ikConstraintData.target == null) throw new Exception("Target bone not found: " + targetName); - - ikConstraintData.bendDirection = GetBoolean(ikMap, "bendPositive", true) ? 1 : -1; - ikConstraintData.mix = GetFloat(ikMap, "mix", 1); - - skeletonData.ikConstraints.Add(ikConstraintData); - } - } - - // Slots. - if (root.ContainsKey("slots")) { - foreach (Dictionary slotMap in (List)root["slots"]) { - var slotName = (String)slotMap["name"]; - var boneName = (String)slotMap["bone"]; - BoneData boneData = skeletonData.FindBone(boneName); - if (boneData == null) - throw new Exception("Slot bone not found: " + boneName); - var slotData = new SlotData(slotName, boneData); - - if (slotMap.ContainsKey("color")) { - var color = (String)slotMap["color"]; - slotData.r = ToColor(color, 0); - slotData.g = ToColor(color, 1); - slotData.b = ToColor(color, 2); - slotData.a = ToColor(color, 3); - } - - if (slotMap.ContainsKey("attachment")) - slotData.attachmentName = (String)slotMap["attachment"]; - - if (slotMap.ContainsKey("additive")) - slotData.additiveBlending = (bool)slotMap["additive"]; - - skeletonData.slots.Add(slotData); - } - } - - // Skins. - if (root.ContainsKey("skins")) { - foreach (KeyValuePair entry in (Dictionary)root["skins"]) { - var skin = new Skin(entry.Key); - foreach (KeyValuePair slotEntry in (Dictionary)entry.Value) { - int slotIndex = skeletonData.FindSlotIndex(slotEntry.Key); - foreach (KeyValuePair attachmentEntry in ((Dictionary)slotEntry.Value)) { - Attachment attachment = ReadAttachment(skin, attachmentEntry.Key, (Dictionary)attachmentEntry.Value); - if (attachment != null) skin.AddAttachment(slotIndex, attachmentEntry.Key, attachment); - } - } - skeletonData.skins.Add(skin); - if (skin.name == "default") - skeletonData.defaultSkin = skin; - } - } - - // Events. - if (root.ContainsKey("events")) { - foreach (KeyValuePair entry in (Dictionary)root["events"]) { - var entryMap = (Dictionary)entry.Value; - var eventData = new EventData(entry.Key); - eventData.Int = GetInt(entryMap, "int", 0); - eventData.Float = GetFloat(entryMap, "float", 0); - eventData.String = GetString(entryMap, "string", null); - skeletonData.events.Add(eventData); - } - } - - // Animations. - if (root.ContainsKey("animations")) { - foreach (KeyValuePair entry in (Dictionary)root["animations"]) - ReadAnimation(entry.Key, (Dictionary)entry.Value, skeletonData); - } - - skeletonData.bones.TrimExcess(); - skeletonData.slots.TrimExcess(); - skeletonData.skins.TrimExcess(); - skeletonData.animations.TrimExcess(); - return skeletonData; - } + public SkeletonData ReadSkeletonData(TextReader reader) + { + if (reader == null) throw new ArgumentNullException("reader cannot be null."); - private Attachment ReadAttachment (Skin skin, String name, Dictionary map) { - if (map.ContainsKey("name")) - name = (String)map["name"]; - - var type = AttachmentType.region; - if (map.ContainsKey("type")) - type = (AttachmentType)Enum.Parse(typeof(AttachmentType), (String)map["type"], false); - - String path = name; - if (map.ContainsKey("path")) - path = (String)map["path"]; - - switch (type) { - case AttachmentType.region: - RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); - if (region == null) return null; - region.Path = path; - region.x = GetFloat(map, "x", 0) * Scale; - region.y = GetFloat(map, "y", 0) * Scale; - region.scaleX = GetFloat(map, "scaleX", 1); - region.scaleY = GetFloat(map, "scaleY", 1); - region.rotation = GetFloat(map, "rotation", 0); - region.width = GetFloat(map, "width", 32) * Scale; - region.height = GetFloat(map, "height", 32) * Scale; - region.UpdateOffset(); - - if (map.ContainsKey("color")) { - var color = (String)map["color"]; - region.r = ToColor(color, 0); - region.g = ToColor(color, 1); - region.b = ToColor(color, 2); - region.a = ToColor(color, 3); - } - - return region; - case AttachmentType.mesh: { - MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); - if (mesh == null) return null; - - mesh.Path = path; - mesh.vertices = GetFloatArray(map, "vertices", Scale); - mesh.triangles = GetIntArray(map, "triangles"); - mesh.regionUVs = GetFloatArray(map, "uvs", 1); - mesh.UpdateUVs(); - - if (map.ContainsKey("color")) { - var color = (String)map["color"]; - mesh.r = ToColor(color, 0); - mesh.g = ToColor(color, 1); - mesh.b = ToColor(color, 2); - mesh.a = ToColor(color, 3); - } - - mesh.HullLength = GetInt(map, "hull", 0) * 2; - if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges"); - mesh.Width = GetInt(map, "width", 0) * Scale; - mesh.Height = GetInt(map, "height", 0) * Scale; - - return mesh; - } - case AttachmentType.skinnedmesh: { - SkinnedMeshAttachment mesh = attachmentLoader.NewSkinnedMeshAttachment(skin, name, path); - if (mesh == null) return null; - - mesh.Path = path; - float[] uvs = GetFloatArray(map, "uvs", 1); - float[] vertices = GetFloatArray(map, "vertices", 1); - var weights = new List(uvs.Length * 3 * 3); - var bones = new List(uvs.Length * 3); - float scale = Scale; - for (int i = 0, n = vertices.Length; i < n; ) { - int boneCount = (int)vertices[i++]; - bones.Add(boneCount); - for (int nn = i + boneCount * 4; i < nn; ) { - bones.Add((int)vertices[i]); - weights.Add(vertices[i + 1] * scale); - weights.Add(vertices[i + 2] * scale); - weights.Add(vertices[i + 3]); - i += 4; - } - } - mesh.bones = bones.ToArray(); - mesh.weights = weights.ToArray(); - mesh.triangles = GetIntArray(map, "triangles"); - mesh.regionUVs = uvs; - mesh.UpdateUVs(); - - if (map.ContainsKey("color")) { - var color = (String)map["color"]; - mesh.r = ToColor(color, 0); - mesh.g = ToColor(color, 1); - mesh.b = ToColor(color, 2); - mesh.a = ToColor(color, 3); - } - - mesh.HullLength = GetInt(map, "hull", 0) * 2; - if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges"); - mesh.Width = GetInt(map, "width", 0) * Scale; - mesh.Height = GetInt(map, "height", 0) * Scale; - - return mesh; - } - case AttachmentType.boundingbox: - BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); - if (box == null) return null; - box.vertices = GetFloatArray(map, "vertices", Scale); - return box; - } - return null; - } + var skeletonData = new SkeletonData(); - private float[] GetFloatArray (Dictionary map, String name, float scale) { - var list = (List)map[name]; - var values = new float[list.Count]; - if (scale == 1) { - for (int i = 0, n = list.Count; i < n; i++) - values[i] = (float)list[i]; - } else { - for (int i = 0, n = list.Count; i < n; i++) - values[i] = (float)list[i] * scale; - } - return values; - } + var root = Json.Deserialize(reader) as Dictionary; + if (root == null) throw new Exception("Invalid JSON."); - private int[] GetIntArray (Dictionary map, String name) { - var list = (List)map[name]; - var values = new int[list.Count]; - for (int i = 0, n = list.Count; i < n; i++) - values[i] = (int)(float)list[i]; - return values; - } + // Skeleton. + if (root.ContainsKey("skeleton")) + { + var skeletonMap = (Dictionary)root["skeleton"]; + skeletonData.hash = (String)skeletonMap["hash"]; + skeletonData.version = (String)skeletonMap["spine"]; + skeletonData.width = GetFloat(skeletonMap, "width", 0); + skeletonData.height = GetFloat(skeletonMap, "height", 0); + } - private float GetFloat (Dictionary map, String name, float defaultValue) { - if (!map.ContainsKey(name)) - return defaultValue; - return (float)map[name]; - } + // Bones. + foreach (Dictionary boneMap in (List)root["bones"]) + { + BoneData parent = null; + if (boneMap.ContainsKey("parent")) + { + parent = skeletonData.FindBone((String)boneMap["parent"]); + if (parent == null) + throw new Exception("Parent bone not found: " + boneMap["parent"]); + } + var boneData = new BoneData((String)boneMap["name"], parent); + boneData.length = GetFloat(boneMap, "length", 0) * Scale; + boneData.x = GetFloat(boneMap, "x", 0) * Scale; + boneData.y = GetFloat(boneMap, "y", 0) * Scale; + boneData.rotation = GetFloat(boneMap, "rotation", 0); + boneData.scaleX = GetFloat(boneMap, "scaleX", 1); + boneData.scaleY = GetFloat(boneMap, "scaleY", 1); + boneData.flipX = GetBoolean(boneMap, "flipX", false); + boneData.flipY = GetBoolean(boneMap, "flipY", false); + boneData.inheritScale = GetBoolean(boneMap, "inheritScale", true); + boneData.inheritRotation = GetBoolean(boneMap, "inheritRotation", true); + skeletonData.bones.Add(boneData); + } - private int GetInt (Dictionary map, String name, int defaultValue) { - if (!map.ContainsKey(name)) - return defaultValue; - return (int)(float)map[name]; - } + // IK constraints. + if (root.ContainsKey("ik")) + { + foreach (Dictionary ikMap in (List)root["ik"]) + { + IkConstraintData ikConstraintData = new IkConstraintData((String)ikMap["name"]); + + foreach (String boneName in (List)ikMap["bones"]) + { + BoneData bone = skeletonData.FindBone(boneName); + if (bone == null) throw new Exception("IK bone not found: " + boneName); + ikConstraintData.bones.Add(bone); + } + + String targetName = (String)ikMap["target"]; + ikConstraintData.target = skeletonData.FindBone(targetName); + if (ikConstraintData.target == null) throw new Exception("Target bone not found: " + targetName); + + ikConstraintData.bendDirection = GetBoolean(ikMap, "bendPositive", true) ? 1 : -1; + ikConstraintData.mix = GetFloat(ikMap, "mix", 1); + + skeletonData.ikConstraints.Add(ikConstraintData); + } + } - private bool GetBoolean (Dictionary map, String name, bool defaultValue) { - if (!map.ContainsKey(name)) - return defaultValue; - return (bool)map[name]; - } + // Slots. + if (root.ContainsKey("slots")) + { + foreach (Dictionary slotMap in (List)root["slots"]) + { + var slotName = (String)slotMap["name"]; + var boneName = (String)slotMap["bone"]; + BoneData boneData = skeletonData.FindBone(boneName); + if (boneData == null) + throw new Exception("Slot bone not found: " + boneName); + var slotData = new SlotData(slotName, boneData); + + if (slotMap.ContainsKey("color")) + { + var color = (String)slotMap["color"]; + slotData.r = ToColor(color, 0); + slotData.g = ToColor(color, 1); + slotData.b = ToColor(color, 2); + slotData.a = ToColor(color, 3); + } + + if (slotMap.ContainsKey("attachment")) + slotData.attachmentName = (String)slotMap["attachment"]; + + if (slotMap.ContainsKey("additive")) + slotData.additiveBlending = (bool)slotMap["additive"]; + + skeletonData.slots.Add(slotData); + } + } - private String GetString (Dictionary map, String name, String defaultValue) { - if (!map.ContainsKey(name)) - return defaultValue; - return (String)map[name]; - } + // Skins. + if (root.ContainsKey("skins")) + { + foreach (KeyValuePair entry in (Dictionary)root["skins"]) + { + var skin = new Skin(entry.Key); + foreach (KeyValuePair slotEntry in (Dictionary)entry.Value) + { + int slotIndex = skeletonData.FindSlotIndex(slotEntry.Key); + foreach (KeyValuePair attachmentEntry in ((Dictionary)slotEntry.Value)) + { + Attachment attachment = ReadAttachment(skin, attachmentEntry.Key, (Dictionary)attachmentEntry.Value); + if (attachment != null) skin.AddAttachment(slotIndex, attachmentEntry.Key, attachment); + } + } + skeletonData.skins.Add(skin); + if (skin.name == "default") + skeletonData.defaultSkin = skin; + } + } - public static float ToColor (String hexString, int colorIndex) { - if (hexString.Length != 8) - throw new ArgumentException("Color hexidecimal length must be 8, recieved: " + hexString); - return Convert.ToInt32(hexString.Substring(colorIndex * 2, 2), 16) / (float)255; - } + // Events. + if (root.ContainsKey("events")) + { + foreach (KeyValuePair entry in (Dictionary)root["events"]) + { + var entryMap = (Dictionary)entry.Value; + var eventData = new EventData(entry.Key); + eventData.Int = GetInt(entryMap, "int", 0); + eventData.Float = GetFloat(entryMap, "float", 0); + eventData.String = GetString(entryMap, "string", null); + skeletonData.events.Add(eventData); + } + } - private void ReadAnimation (String name, Dictionary map, SkeletonData skeletonData) { - var timelines = new List(); - float duration = 0; - float scale = Scale; - - if (map.ContainsKey("slots")) { - foreach (KeyValuePair entry in (Dictionary)map["slots"]) { - String slotName = entry.Key; - int slotIndex = skeletonData.FindSlotIndex(slotName); - var timelineMap = (Dictionary)entry.Value; - - foreach (KeyValuePair timelineEntry in timelineMap) { - var values = (List)timelineEntry.Value; - var timelineName = (String)timelineEntry.Key; - if (timelineName == "color") { - var timeline = new ColorTimeline(values.Count); - timeline.slotIndex = slotIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - String c = (String)valueMap["color"]; - timeline.setFrame(frameIndex, time, ToColor(c, 0), ToColor(c, 1), ToColor(c, 2), ToColor(c, 3)); - ReadCurve(timeline, frameIndex, valueMap); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 5 - 5]); - - } else if (timelineName == "attachment") { - var timeline = new AttachmentTimeline(values.Count); - timeline.slotIndex = slotIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - timeline.setFrame(frameIndex++, time, (String)valueMap["name"]); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); - - } else - throw new Exception("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")"); - } - } - } - - if (map.ContainsKey("bones")) { - foreach (KeyValuePair entry in (Dictionary)map["bones"]) { - String boneName = entry.Key; - int boneIndex = skeletonData.FindBoneIndex(boneName); - if (boneIndex == -1) - throw new Exception("Bone not found: " + boneName); - - var timelineMap = (Dictionary)entry.Value; - foreach (KeyValuePair timelineEntry in timelineMap) { - var values = (List)timelineEntry.Value; - var timelineName = (String)timelineEntry.Key; - if (timelineName == "rotate") { - var timeline = new RotateTimeline(values.Count); - timeline.boneIndex = boneIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - timeline.SetFrame(frameIndex, time, (float)valueMap["angle"]); - ReadCurve(timeline, frameIndex, valueMap); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 2 - 2]); - - } else if (timelineName == "translate" || timelineName == "scale") { - TranslateTimeline timeline; - float timelineScale = 1; - if (timelineName == "scale") - timeline = new ScaleTimeline(values.Count); - else { - timeline = new TranslateTimeline(values.Count); - timelineScale = scale; - } - timeline.boneIndex = boneIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - float x = valueMap.ContainsKey("x") ? (float)valueMap["x"] : 0; - float y = valueMap.ContainsKey("y") ? (float)valueMap["y"] : 0; - timeline.SetFrame(frameIndex, time, (float)x * timelineScale, (float)y * timelineScale); - ReadCurve(timeline, frameIndex, valueMap); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 3 - 3]); - - } else if (timelineName == "flipX" || timelineName == "flipY") { - bool x = timelineName == "flipX"; - var timeline = x ? new FlipXTimeline(values.Count) : new FlipYTimeline(values.Count); - timeline.boneIndex = boneIndex; - - String field = x ? "x" : "y"; - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - timeline.SetFrame(frameIndex, time, valueMap.ContainsKey(field) ? (bool)valueMap[field] : false); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 2 - 2]); - - } else - throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")"); - } - } - } - - if (map.ContainsKey("ik")) { - foreach (KeyValuePair ikMap in (Dictionary)map["ik"]) { - IkConstraintData ikConstraint = skeletonData.FindIkConstraint(ikMap.Key); - var values = (List)ikMap.Value; - var timeline = new IkConstraintTimeline(values.Count); - timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(ikConstraint); - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - float mix = valueMap.ContainsKey("mix") ? (float)valueMap["mix"] : 1; - bool bendPositive = valueMap.ContainsKey("bendPositive") ? (bool)valueMap["bendPositive"] : true; - timeline.setFrame(frameIndex, time, mix, bendPositive ? 1 : -1); - ReadCurve(timeline, frameIndex, valueMap); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 3 - 3]); - } - } - - if (map.ContainsKey("ffd")) { - foreach (KeyValuePair ffdMap in (Dictionary)map["ffd"]) { - Skin skin = skeletonData.FindSkin(ffdMap.Key); - foreach (KeyValuePair slotMap in (Dictionary)ffdMap.Value) { - int slotIndex = skeletonData.FindSlotIndex(slotMap.Key); - foreach (KeyValuePair meshMap in (Dictionary)slotMap.Value) { - var values = (List)meshMap.Value; - var timeline = new FFDTimeline(values.Count); - Attachment attachment = skin.GetAttachment(slotIndex, meshMap.Key); - if (attachment == null) throw new Exception("FFD attachment not found: " + meshMap.Key); - timeline.slotIndex = slotIndex; - timeline.attachment = attachment; - - int vertexCount; - if (attachment is MeshAttachment) - vertexCount = ((MeshAttachment)attachment).vertices.Length; - else - vertexCount = ((SkinnedMeshAttachment)attachment).Weights.Length / 3 * 2; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float[] vertices; - if (!valueMap.ContainsKey("vertices")) { - if (attachment is MeshAttachment) - vertices = ((MeshAttachment)attachment).vertices; - else - vertices = new float[vertexCount]; - } else { - var verticesValue = (List)valueMap["vertices"]; - vertices = new float[vertexCount]; - int start = GetInt(valueMap, "offset", 0); - if (scale == 1) { - for (int i = 0, n = verticesValue.Count; i < n; i++) - vertices[i + start] = (float)verticesValue[i]; - } else { - for (int i = 0, n = verticesValue.Count; i < n; i++) - vertices[i + start] = (float)verticesValue[i] * scale; - } - if (attachment is MeshAttachment) { - float[] meshVertices = ((MeshAttachment)attachment).vertices; - for (int i = 0; i < vertexCount; i++) - vertices[i] += meshVertices[i]; - } - } - - timeline.setFrame(frameIndex, (float)valueMap["time"], vertices); - ReadCurve(timeline, frameIndex, valueMap); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); - } - } - } - } - - if (map.ContainsKey("drawOrder") || map.ContainsKey("draworder")) { - var values = (List)map[map.ContainsKey("drawOrder") ? "drawOrder" : "draworder"]; - var timeline = new DrawOrderTimeline(values.Count); - int slotCount = skeletonData.slots.Count; - int frameIndex = 0; - foreach (Dictionary drawOrderMap in values) { - int[] drawOrder = null; - if (drawOrderMap.ContainsKey("offsets")) { - drawOrder = new int[slotCount]; - for (int i = slotCount - 1; i >= 0; i--) - drawOrder[i] = -1; - var offsets = (List)drawOrderMap["offsets"]; - int[] unchanged = new int[slotCount - offsets.Count]; - int originalIndex = 0, unchangedIndex = 0; - foreach (Dictionary offsetMap in offsets) { - int slotIndex = skeletonData.FindSlotIndex((String)offsetMap["slot"]); - if (slotIndex == -1) throw new Exception("Slot not found: " + offsetMap["slot"]); - // Collect unchanged items. - while (originalIndex != slotIndex) - unchanged[unchangedIndex++] = originalIndex++; - // Set changed items. - drawOrder[originalIndex + (int)(float)offsetMap["offset"]] = originalIndex++; - } - // Collect remaining unchanged items. - while (originalIndex < slotCount) - unchanged[unchangedIndex++] = originalIndex++; - // Fill in unchanged items. - for (int i = slotCount - 1; i >= 0; i--) - if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex]; - } - timeline.setFrame(frameIndex++, (float)drawOrderMap["time"], drawOrder); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); - } - - if (map.ContainsKey("events")) { - var eventsMap = (List)map["events"]; - var timeline = new EventTimeline(eventsMap.Count); - int frameIndex = 0; - foreach (Dictionary eventMap in eventsMap) { - EventData eventData = skeletonData.FindEvent((String)eventMap["name"]); - if (eventData == null) throw new Exception("Event not found: " + eventMap["name"]); - var e = new Event(eventData); - e.Int = GetInt(eventMap, "int", eventData.Int); - e.Float = GetFloat(eventMap, "float", eventData.Float); - e.String = GetString(eventMap, "string", eventData.String); - timeline.setFrame(frameIndex++, (float)eventMap["time"], e); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); - } - - timelines.TrimExcess(); - skeletonData.animations.Add(new Animation(name, timelines, duration)); - } + // Animations. + if (root.ContainsKey("animations")) + { + foreach (KeyValuePair entry in (Dictionary)root["animations"]) + ReadAnimation(entry.Key, (Dictionary)entry.Value, skeletonData); + } - private void ReadCurve (CurveTimeline timeline, int frameIndex, Dictionary valueMap) { - if (!valueMap.ContainsKey("curve")) - return; - Object curveObject = valueMap["curve"]; - if (curveObject.Equals("stepped")) - timeline.SetStepped(frameIndex); - else if (curveObject is List) { - var curve = (List)curveObject; - timeline.SetCurve(frameIndex, (float)curve[0], (float)curve[1], (float)curve[2], (float)curve[3]); - } - } - } + skeletonData.bones.TrimExcess(); + skeletonData.slots.TrimExcess(); + skeletonData.skins.TrimExcess(); + skeletonData.animations.TrimExcess(); + return skeletonData; + } + + private Attachment ReadAttachment(Skin skin, String name, Dictionary map) + { + if (map.ContainsKey("name")) + name = (String)map["name"]; + + var type = AttachmentType.region; + if (map.ContainsKey("type")) + type = (AttachmentType)Enum.Parse(typeof(AttachmentType), (String)map["type"], false); + + String path = name; + if (map.ContainsKey("path")) + path = (String)map["path"]; + + switch (type) + { + case AttachmentType.region: + RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); + if (region == null) return null; + region.Path = path; + region.x = GetFloat(map, "x", 0) * Scale; + region.y = GetFloat(map, "y", 0) * Scale; + region.scaleX = GetFloat(map, "scaleX", 1); + region.scaleY = GetFloat(map, "scaleY", 1); + region.rotation = GetFloat(map, "rotation", 0); + region.width = GetFloat(map, "width", 32) * Scale; + region.height = GetFloat(map, "height", 32) * Scale; + region.UpdateOffset(); + + if (map.ContainsKey("color")) + { + var color = (String)map["color"]; + region.r = ToColor(color, 0); + region.g = ToColor(color, 1); + region.b = ToColor(color, 2); + region.a = ToColor(color, 3); + } + + return region; + case AttachmentType.mesh: + { + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); + if (mesh == null) return null; + + mesh.Path = path; + mesh.vertices = GetFloatArray(map, "vertices", Scale); + mesh.triangles = GetIntArray(map, "triangles"); + mesh.regionUVs = GetFloatArray(map, "uvs", 1); + mesh.UpdateUVs(); + + if (map.ContainsKey("color")) + { + var color = (String)map["color"]; + mesh.r = ToColor(color, 0); + mesh.g = ToColor(color, 1); + mesh.b = ToColor(color, 2); + mesh.a = ToColor(color, 3); + } + + mesh.HullLength = GetInt(map, "hull", 0) * 2; + if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges"); + mesh.Width = GetInt(map, "width", 0) * Scale; + mesh.Height = GetInt(map, "height", 0) * Scale; + + return mesh; + } + case AttachmentType.skinnedmesh: + { + SkinnedMeshAttachment mesh = attachmentLoader.NewSkinnedMeshAttachment(skin, name, path); + if (mesh == null) return null; + + mesh.Path = path; + float[] uvs = GetFloatArray(map, "uvs", 1); + float[] vertices = GetFloatArray(map, "vertices", 1); + var weights = new List(uvs.Length * 3 * 3); + var bones = new List(uvs.Length * 3); + float scale = Scale; + for (int i = 0, n = vertices.Length; i < n;) + { + int boneCount = (int)vertices[i++]; + bones.Add(boneCount); + for (int nn = i + boneCount * 4; i < nn;) + { + bones.Add((int)vertices[i]); + weights.Add(vertices[i + 1] * scale); + weights.Add(vertices[i + 2] * scale); + weights.Add(vertices[i + 3]); + i += 4; + } + } + mesh.bones = bones.ToArray(); + mesh.weights = weights.ToArray(); + mesh.triangles = GetIntArray(map, "triangles"); + mesh.regionUVs = uvs; + mesh.UpdateUVs(); + + if (map.ContainsKey("color")) + { + var color = (String)map["color"]; + mesh.r = ToColor(color, 0); + mesh.g = ToColor(color, 1); + mesh.b = ToColor(color, 2); + mesh.a = ToColor(color, 3); + } + + mesh.HullLength = GetInt(map, "hull", 0) * 2; + if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges"); + mesh.Width = GetInt(map, "width", 0) * Scale; + mesh.Height = GetInt(map, "height", 0) * Scale; + + return mesh; + } + case AttachmentType.boundingbox: + BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); + if (box == null) return null; + box.vertices = GetFloatArray(map, "vertices", Scale); + return box; + } + return null; + } + + private float[] GetFloatArray(Dictionary map, String name, float scale) + { + var list = (List)map[name]; + var values = new float[list.Count]; + if (scale == 1) + { + for (int i = 0, n = list.Count; i < n; i++) + values[i] = (float)list[i]; + } + else + { + for (int i = 0, n = list.Count; i < n; i++) + values[i] = (float)list[i] * scale; + } + return values; + } + + private int[] GetIntArray(Dictionary map, String name) + { + var list = (List)map[name]; + var values = new int[list.Count]; + for (int i = 0, n = list.Count; i < n; i++) + values[i] = (int)(float)list[i]; + return values; + } + + private float GetFloat(Dictionary map, String name, float defaultValue) + { + if (!map.ContainsKey(name)) + return defaultValue; + return (float)map[name]; + } + + private int GetInt(Dictionary map, String name, int defaultValue) + { + if (!map.ContainsKey(name)) + return defaultValue; + return (int)(float)map[name]; + } + + private bool GetBoolean(Dictionary map, String name, bool defaultValue) + { + if (!map.ContainsKey(name)) + return defaultValue; + return (bool)map[name]; + } + + private String GetString(Dictionary map, String name, String defaultValue) + { + if (!map.ContainsKey(name)) + return defaultValue; + return (String)map[name]; + } + + public static float ToColor(String hexString, int colorIndex) + { + if (hexString.Length != 8) + throw new ArgumentException("Color hexidecimal length must be 8, recieved: " + hexString); + return Convert.ToInt32(hexString.Substring(colorIndex * 2, 2), 16) / (float)255; + } + + private void ReadAnimation(String name, Dictionary map, SkeletonData skeletonData) + { + var timelines = new List(); + float duration = 0; + float scale = Scale; + + if (map.ContainsKey("slots")) + { + foreach (KeyValuePair entry in (Dictionary)map["slots"]) + { + String slotName = entry.Key; + int slotIndex = skeletonData.FindSlotIndex(slotName); + var timelineMap = (Dictionary)entry.Value; + + foreach (KeyValuePair timelineEntry in timelineMap) + { + var values = (List)timelineEntry.Value; + var timelineName = timelineEntry.Key; + if (timelineName == "color") + { + var timeline = new ColorTimeline(values.Count); + timeline.slotIndex = slotIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + String c = (String)valueMap["color"]; + timeline.setFrame(frameIndex, time, ToColor(c, 0), ToColor(c, 1), ToColor(c, 2), ToColor(c, 3)); + ReadCurve(timeline, frameIndex, valueMap); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 5 - 5]); + + } + else if (timelineName == "attachment") + { + var timeline = new AttachmentTimeline(values.Count); + timeline.slotIndex = slotIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + timeline.setFrame(frameIndex++, time, (String)valueMap["name"]); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); + + } + else + throw new Exception("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")"); + } + } + } + + if (map.ContainsKey("bones")) + { + foreach (KeyValuePair entry in (Dictionary)map["bones"]) + { + String boneName = entry.Key; + int boneIndex = skeletonData.FindBoneIndex(boneName); + if (boneIndex == -1) + throw new Exception("Bone not found: " + boneName); + + var timelineMap = (Dictionary)entry.Value; + foreach (KeyValuePair timelineEntry in timelineMap) + { + var values = (List)timelineEntry.Value; + var timelineName = timelineEntry.Key; + if (timelineName == "rotate") + { + var timeline = new RotateTimeline(values.Count); + timeline.boneIndex = boneIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + timeline.SetFrame(frameIndex, time, (float)valueMap["angle"]); + ReadCurve(timeline, frameIndex, valueMap); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 2 - 2]); + + } + else if (timelineName == "translate" || timelineName == "scale") + { + TranslateTimeline timeline; + float timelineScale = 1; + if (timelineName == "scale") + timeline = new ScaleTimeline(values.Count); + else + { + timeline = new TranslateTimeline(values.Count); + timelineScale = scale; + } + timeline.boneIndex = boneIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + float x = valueMap.ContainsKey("x") ? (float)valueMap["x"] : 0; + float y = valueMap.ContainsKey("y") ? (float)valueMap["y"] : 0; + timeline.SetFrame(frameIndex, time, (float)x * timelineScale, (float)y * timelineScale); + ReadCurve(timeline, frameIndex, valueMap); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 3 - 3]); + + } + else if (timelineName == "flipX" || timelineName == "flipY") + { + bool x = timelineName == "flipX"; + var timeline = x ? new FlipXTimeline(values.Count) : new FlipYTimeline(values.Count); + timeline.boneIndex = boneIndex; + + String field = x ? "x" : "y"; + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + timeline.SetFrame(frameIndex, time, valueMap.ContainsKey(field) ? (bool)valueMap[field] : false); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 2 - 2]); + + } + else + throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")"); + } + } + } + + if (map.ContainsKey("ik")) + { + foreach (KeyValuePair ikMap in (Dictionary)map["ik"]) + { + IkConstraintData ikConstraint = skeletonData.FindIkConstraint(ikMap.Key); + var values = (List)ikMap.Value; + var timeline = new IkConstraintTimeline(values.Count); + timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(ikConstraint); + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + float mix = valueMap.ContainsKey("mix") ? (float)valueMap["mix"] : 1; + bool bendPositive = valueMap.ContainsKey("bendPositive") ? (bool)valueMap["bendPositive"] : true; + timeline.setFrame(frameIndex, time, mix, bendPositive ? 1 : -1); + ReadCurve(timeline, frameIndex, valueMap); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 3 - 3]); + } + } + + if (map.ContainsKey("ffd")) + { + foreach (KeyValuePair ffdMap in (Dictionary)map["ffd"]) + { + Skin skin = skeletonData.FindSkin(ffdMap.Key); + foreach (KeyValuePair slotMap in (Dictionary)ffdMap.Value) + { + int slotIndex = skeletonData.FindSlotIndex(slotMap.Key); + foreach (KeyValuePair meshMap in (Dictionary)slotMap.Value) + { + var values = (List)meshMap.Value; + var timeline = new FFDTimeline(values.Count); + Attachment attachment = skin.GetAttachment(slotIndex, meshMap.Key); + if (attachment == null) throw new Exception("FFD attachment not found: " + meshMap.Key); + timeline.slotIndex = slotIndex; + timeline.attachment = attachment; + + int vertexCount; + if (attachment is MeshAttachment) + vertexCount = ((MeshAttachment)attachment).vertices.Length; + else + vertexCount = ((SkinnedMeshAttachment)attachment).Weights.Length / 3 * 2; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float[] vertices; + if (!valueMap.ContainsKey("vertices")) + { + if (attachment is MeshAttachment) + vertices = ((MeshAttachment)attachment).vertices; + else + vertices = new float[vertexCount]; + } + else + { + var verticesValue = (List)valueMap["vertices"]; + vertices = new float[vertexCount]; + int start = GetInt(valueMap, "offset", 0); + if (scale == 1) + { + for (int i = 0, n = verticesValue.Count; i < n; i++) + vertices[i + start] = (float)verticesValue[i]; + } + else + { + for (int i = 0, n = verticesValue.Count; i < n; i++) + vertices[i + start] = (float)verticesValue[i] * scale; + } + if (attachment is MeshAttachment) + { + float[] meshVertices = ((MeshAttachment)attachment).vertices; + for (int i = 0; i < vertexCount; i++) + vertices[i] += meshVertices[i]; + } + } + + timeline.setFrame(frameIndex, (float)valueMap["time"], vertices); + ReadCurve(timeline, frameIndex, valueMap); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); + } + } + } + } + + if (map.ContainsKey("drawOrder") || map.ContainsKey("draworder")) + { + var values = (List)map[map.ContainsKey("drawOrder") ? "drawOrder" : "draworder"]; + var timeline = new DrawOrderTimeline(values.Count); + int slotCount = skeletonData.slots.Count; + int frameIndex = 0; + foreach (Dictionary drawOrderMap in values) + { + int[] drawOrder = null; + if (drawOrderMap.ContainsKey("offsets")) + { + drawOrder = new int[slotCount]; + for (int i = slotCount - 1; i >= 0; i--) + drawOrder[i] = -1; + var offsets = (List)drawOrderMap["offsets"]; + int[] unchanged = new int[slotCount - offsets.Count]; + int originalIndex = 0, unchangedIndex = 0; + foreach (Dictionary offsetMap in offsets) + { + int slotIndex = skeletonData.FindSlotIndex((String)offsetMap["slot"]); + if (slotIndex == -1) throw new Exception("Slot not found: " + offsetMap["slot"]); + // Collect unchanged items. + while (originalIndex != slotIndex) + unchanged[unchangedIndex++] = originalIndex++; + // Set changed items. + drawOrder[originalIndex + (int)(float)offsetMap["offset"]] = originalIndex++; + } + // Collect remaining unchanged items. + while (originalIndex < slotCount) + unchanged[unchangedIndex++] = originalIndex++; + // Fill in unchanged items. + for (int i = slotCount - 1; i >= 0; i--) + if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex]; + } + timeline.setFrame(frameIndex++, (float)drawOrderMap["time"], drawOrder); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); + } + + if (map.ContainsKey("events")) + { + var eventsMap = (List)map["events"]; + var timeline = new EventTimeline(eventsMap.Count); + int frameIndex = 0; + foreach (Dictionary eventMap in eventsMap) + { + EventData eventData = skeletonData.FindEvent((String)eventMap["name"]); + if (eventData == null) throw new Exception("Event not found: " + eventMap["name"]); + var e = new Event(eventData); + e.Int = GetInt(eventMap, "int", eventData.Int); + e.Float = GetFloat(eventMap, "float", eventData.Float); + e.String = GetString(eventMap, "string", eventData.String); + timeline.setFrame(frameIndex++, (float)eventMap["time"], e); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); + } + + timelines.TrimExcess(); + skeletonData.animations.Add(new Animation(name, timelines, duration)); + } + + private void ReadCurve(CurveTimeline timeline, int frameIndex, Dictionary valueMap) + { + if (!valueMap.ContainsKey("curve")) + return; + Object curveObject = valueMap["curve"]; + if (curveObject.Equals("stepped")) + timeline.SetStepped(frameIndex); + else if (curveObject is List) + { + var curve = (List)curveObject; + timeline.SetCurve(frameIndex, (float)curve[0], (float)curve[1], (float)curve[2], (float)curve[3]); + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Skin.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Skin.cs index 30d71ca..49529f1 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Skin.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Skin.cs @@ -31,71 +31,85 @@ using System; using System.Collections.Generic; -namespace Spine2_1_08 { - /// Stores attachments by slot index and attachment name. - public class Skin { - internal String name; - private Dictionary, Attachment> attachments = - new Dictionary, Attachment>(AttachmentComparer.Instance); +namespace Spine2_1_08 +{ + /// Stores attachments by slot index and attachment name. + public class Skin + { + internal String name; + private Dictionary, Attachment> attachments = + new Dictionary, Attachment>(AttachmentComparer.Instance); - public String Name { get { return name; } } + public String Name { get { return name; } } - public Skin (String name) { - if (name == null) throw new ArgumentNullException("name cannot be null."); - this.name = name; - } + public Skin(String name) + { + if (name == null) throw new ArgumentNullException("name cannot be null."); + this.name = name; + } - public void AddAttachment (int slotIndex, String name, Attachment attachment) { - if (attachment == null) throw new ArgumentNullException("attachment cannot be null."); - attachments[new KeyValuePair(slotIndex, name)] = attachment; - } + public void AddAttachment(int slotIndex, String name, Attachment attachment) + { + if (attachment == null) throw new ArgumentNullException("attachment cannot be null."); + attachments[new KeyValuePair(slotIndex, name)] = attachment; + } - /// May be null. - public Attachment GetAttachment (int slotIndex, String name) { - Attachment attachment; - attachments.TryGetValue(new KeyValuePair(slotIndex, name), out attachment); - return attachment; - } + /// May be null. + public Attachment GetAttachment(int slotIndex, String name) + { + Attachment attachment; + attachments.TryGetValue(new KeyValuePair(slotIndex, name), out attachment); + return attachment; + } - public void FindNamesForSlot (int slotIndex, List names) { - if (names == null) throw new ArgumentNullException("names cannot be null."); - foreach (KeyValuePair key in attachments.Keys) - if (key.Key == slotIndex) names.Add(key.Value); - } + public void FindNamesForSlot(int slotIndex, List names) + { + if (names == null) throw new ArgumentNullException("names cannot be null."); + foreach (KeyValuePair key in attachments.Keys) + if (key.Key == slotIndex) names.Add(key.Value); + } - public void FindAttachmentsForSlot (int slotIndex, List attachments) { - if (attachments == null) throw new ArgumentNullException("attachments cannot be null."); - foreach (KeyValuePair, Attachment> entry in this.attachments) - if (entry.Key.Key == slotIndex) attachments.Add(entry.Value); - } + public void FindAttachmentsForSlot(int slotIndex, List attachments) + { + if (attachments == null) throw new ArgumentNullException("attachments cannot be null."); + foreach (KeyValuePair, Attachment> entry in this.attachments) + if (entry.Key.Key == slotIndex) attachments.Add(entry.Value); + } - override public String ToString () { - return name; - } + override public String ToString() + { + return name; + } - /// Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached. - internal void AttachAll (Skeleton skeleton, Skin oldSkin) { - foreach (KeyValuePair, Attachment> entry in oldSkin.attachments) { - int slotIndex = entry.Key.Key; - Slot slot = skeleton.slots[slotIndex]; - if (slot.attachment == entry.Value) { - Attachment attachment = GetAttachment(slotIndex, entry.Key.Value); - if (attachment != null) slot.Attachment = attachment; - } - } - } + /// Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached. + internal void AttachAll(Skeleton skeleton, Skin oldSkin) + { + foreach (KeyValuePair, Attachment> entry in oldSkin.attachments) + { + int slotIndex = entry.Key.Key; + Slot slot = skeleton.slots[slotIndex]; + if (slot.attachment == entry.Value) + { + Attachment attachment = GetAttachment(slotIndex, entry.Key.Value); + if (attachment != null) slot.Attachment = attachment; + } + } + } - // Avoids boxing in the dictionary. - private class AttachmentComparer : IEqualityComparer> { - internal static readonly AttachmentComparer Instance = new AttachmentComparer(); + // Avoids boxing in the dictionary. + private class AttachmentComparer : IEqualityComparer> + { + internal static readonly AttachmentComparer Instance = new AttachmentComparer(); - bool IEqualityComparer>.Equals (KeyValuePair o1, KeyValuePair o2) { - return o1.Key == o2.Key && o1.Value == o2.Value; - } + bool IEqualityComparer>.Equals(KeyValuePair o1, KeyValuePair o2) + { + return o1.Key == o2.Key && o1.Value == o2.Value; + } - int IEqualityComparer>.GetHashCode (KeyValuePair o) { - return o.Key; - } - } - } + int IEqualityComparer>.GetHashCode(KeyValuePair o) + { + return o.Key; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Slot.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Slot.cs index 0b73440..b8a0c97 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Slot.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/Slot.cs @@ -30,70 +30,82 @@ using System; -namespace Spine2_1_08 { - public class Slot { - internal SlotData data; - internal Bone bone; - internal float r, g, b, a; - internal Attachment attachment; - internal float attachmentTime; - internal float[] attachmentVertices = new float[0]; - internal int attachmentVerticesCount; +namespace Spine2_1_08 +{ + public class Slot + { + internal SlotData data; + internal Bone bone; + internal float r, g, b, a; + internal Attachment attachment; + internal float attachmentTime; + internal float[] attachmentVertices = new float[0]; + internal int attachmentVerticesCount; - public SlotData Data { get { return data; } } - public Bone Bone { get { return bone; } } - public Skeleton Skeleton { get { return bone.skeleton; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } + public SlotData Data { get { return data; } } + public Bone Bone { get { return bone; } } + public Skeleton Skeleton { get { return bone.skeleton; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } - /// May be null. - public Attachment Attachment { - get { - return attachment; - } - set { - attachment = value; - attachmentTime = bone.skeleton.time; - attachmentVerticesCount = 0; - } - } + /// May be null. + public Attachment Attachment + { + get + { + return attachment; + } + set + { + attachment = value; + attachmentTime = bone.skeleton.time; + attachmentVerticesCount = 0; + } + } - public float AttachmentTime { - get { - return bone.skeleton.time - attachmentTime; - } - set { - attachmentTime = bone.skeleton.time - value; - } - } + public float AttachmentTime + { + get + { + return bone.skeleton.time - attachmentTime; + } + set + { + attachmentTime = bone.skeleton.time - value; + } + } - public float[] AttachmentVertices { get { return attachmentVertices; } set { attachmentVertices = value; } } - public int AttachmentVerticesCount { get { return attachmentVerticesCount; } set { attachmentVerticesCount = value; } } + public float[] AttachmentVertices { get { return attachmentVertices; } set { attachmentVertices = value; } } + public int AttachmentVerticesCount { get { return attachmentVerticesCount; } set { attachmentVerticesCount = value; } } - public Slot (SlotData data, Bone bone) { - if (data == null) throw new ArgumentNullException("data cannot be null."); - if (bone == null) throw new ArgumentNullException("bone cannot be null."); - this.data = data; - this.bone = bone; - SetToSetupPose(); - } + public Slot(SlotData data, Bone bone) + { + if (data == null) throw new ArgumentNullException("data cannot be null."); + if (bone == null) throw new ArgumentNullException("bone cannot be null."); + this.data = data; + this.bone = bone; + SetToSetupPose(); + } - internal void SetToSetupPose (int slotIndex) { - r = data.r; - g = data.g; - b = data.b; - a = data.a; - Attachment = data.attachmentName == null ? null : bone.skeleton.GetAttachment(slotIndex, data.attachmentName); - } + internal void SetToSetupPose(int slotIndex) + { + r = data.r; + g = data.g; + b = data.b; + a = data.a; + Attachment = data.attachmentName == null ? null : bone.skeleton.GetAttachment(slotIndex, data.attachmentName); + } - public void SetToSetupPose () { - SetToSetupPose(bone.skeleton.data.slots.IndexOf(data)); - } + public void SetToSetupPose() + { + SetToSetupPose(bone.skeleton.data.slots.IndexOf(data)); + } - override public String ToString () { - return data.name; - } - } + override public String ToString() + { + return data.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/SlotData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/SlotData.cs index db42ae8..c0bb984 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/SlotData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/SlotData.cs @@ -30,33 +30,37 @@ using System; -namespace Spine2_1_08 { - public class SlotData { - internal String name; - internal BoneData boneData; - internal float r = 1, g = 1, b = 1, a = 1; - internal String attachmentName; - internal bool additiveBlending; +namespace Spine2_1_08 +{ + public class SlotData + { + internal String name; + internal BoneData boneData; + internal float r = 1, g = 1, b = 1, a = 1; + internal String attachmentName; + internal bool additiveBlending; - public String Name { get { return name; } } - public BoneData BoneData { get { return boneData; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } - /// May be null. - public String AttachmentName { get { return attachmentName; } set { attachmentName = value; } } - public bool AdditiveBlending { get { return additiveBlending; } set { additiveBlending = value; } } + public String Name { get { return name; } } + public BoneData BoneData { get { return boneData; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } + /// May be null. + public String AttachmentName { get { return attachmentName; } set { attachmentName = value; } } + public bool AdditiveBlending { get { return additiveBlending; } set { additiveBlending = value; } } - public SlotData (String name, BoneData boneData) { - if (name == null) throw new ArgumentNullException("name cannot be null."); - if (boneData == null) throw new ArgumentNullException("boneData cannot be null."); - this.name = name; - this.boneData = boneData; - } + public SlotData(String name, BoneData boneData) + { + if (name == null) throw new ArgumentNullException("name cannot be null."); + if (boneData == null) throw new ArgumentNullException("boneData cannot be null."); + this.name = name; + this.boneData = boneData; + } - override public String ToString () { - return name; - } - } + override public String ToString() + { + return name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/XnaLoader/MeshBatcher.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/XnaLoader/MeshBatcher.cs index 8c776d4..69f12e7 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/XnaLoader/MeshBatcher.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/XnaLoader/MeshBatcher.cs @@ -31,136 +31,146 @@ using System; using System.Collections.Generic; using Microsoft.Xna.Framework.Graphics; -using Microsoft.Xna.Framework; -namespace Spine2_1_08 { - // #region License - // /* - // Microsoft Public License (Ms-PL) - // MonoGame - Copyright 2009 The MonoGame Team - // - // All rights reserved. - // - // This license governs use of the accompanying software. If you use the software, you accept this license. If you do not - // accept the license, do not use the software. - // - // 1. Definitions - // The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under - // U.S. copyright law. - // - // A "contribution" is the original software, or any additions or changes to the software. - // A "contributor" is any person that distributes its contribution under this license. - // "Licensed patents" are a contributor's patent claims that read directly on its contribution. - // - // 2. Grant of Rights - // (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, - // each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. - // (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, - // each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. - // - // 3. Conditions and Limitations - // (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. - // (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, - // your patent license from such contributor to the software ends automatically. - // (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution - // notices that are present in the software. - // (D) If you distribute any portion of the software in source code form, you may do so only under this license by including - // a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object - // code form, you may only do so under a license that complies with this license. - // (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees - // or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent - // permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular - // purpose and non-infringement. - // */ - // #endregion License - // +namespace Spine2_1_08 +{ + // #region License + // /* + // Microsoft Public License (Ms-PL) + // MonoGame - Copyright 2009 The MonoGame Team + // + // All rights reserved. + // + // This license governs use of the accompanying software. If you use the software, you accept this license. If you do not + // accept the license, do not use the software. + // + // 1. Definitions + // The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under + // U.S. copyright law. + // + // A "contribution" is the original software, or any additions or changes to the software. + // A "contributor" is any person that distributes its contribution under this license. + // "Licensed patents" are a contributor's patent claims that read directly on its contribution. + // + // 2. Grant of Rights + // (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, + // each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. + // (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, + // each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. + // + // 3. Conditions and Limitations + // (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. + // (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, + // your patent license from such contributor to the software ends automatically. + // (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution + // notices that are present in the software. + // (D) If you distribute any portion of the software in source code form, you may do so only under this license by including + // a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object + // code form, you may only do so under a license that complies with this license. + // (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees + // or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent + // permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular + // purpose and non-infringement. + // */ + // #endregion License + // - /// Draws batched meshes. - public class MeshBatcher { - private readonly List items; - private readonly Queue freeItems; - private VertexPositionColorTexture[] vertexArray = { }; - private short[] triangles = { }; + /// Draws batched meshes. + public class MeshBatcher + { + private readonly List items; + private readonly Queue freeItems; + private VertexPositionColorTexture[] vertexArray = { }; + private short[] triangles = { }; - public MeshBatcher () { - items = new List(256); - freeItems = new Queue(256); - EnsureCapacity(256, 512); - } + public MeshBatcher() + { + items = new List(256); + freeItems = new Queue(256); + EnsureCapacity(256, 512); + } - /// Returns a pooled MeshItem. - public MeshItem NextItem (int vertexCount, int triangleCount) { - MeshItem item = freeItems.Count > 0 ? freeItems.Dequeue() : new MeshItem(); - if (item.vertices.Length < vertexCount) item.vertices = new VertexPositionColorTexture[vertexCount]; - if (item.triangles.Length < triangleCount) item.triangles = new int[triangleCount]; - item.vertexCount = vertexCount; - item.triangleCount = triangleCount; - items.Add(item); - return item; - } + /// Returns a pooled MeshItem. + public MeshItem NextItem(int vertexCount, int triangleCount) + { + MeshItem item = freeItems.Count > 0 ? freeItems.Dequeue() : new MeshItem(); + if (item.vertices.Length < vertexCount) item.vertices = new VertexPositionColorTexture[vertexCount]; + if (item.triangles.Length < triangleCount) item.triangles = new int[triangleCount]; + item.vertexCount = vertexCount; + item.triangleCount = triangleCount; + items.Add(item); + return item; + } - private void EnsureCapacity (int vertexCount, int triangleCount) { - if (vertexArray.Length < vertexCount) vertexArray = new VertexPositionColorTexture[vertexCount]; - if (triangles.Length < triangleCount) triangles = new short[triangleCount]; - } + private void EnsureCapacity(int vertexCount, int triangleCount) + { + if (vertexArray.Length < vertexCount) vertexArray = new VertexPositionColorTexture[vertexCount]; + if (triangles.Length < triangleCount) triangles = new short[triangleCount]; + } - public void Draw (GraphicsDevice device) { - if (items.Count == 0) return; + public void Draw(GraphicsDevice device) + { + if (items.Count == 0) return; - int itemCount = items.Count; - int vertexCount = 0, triangleCount = 0; - for (int i = 0; i < itemCount; i++) { - MeshItem item = items[i]; - vertexCount += item.vertexCount; - triangleCount += item.triangleCount; - } - EnsureCapacity(vertexCount, triangleCount); + int itemCount = items.Count; + int vertexCount = 0, triangleCount = 0; + for (int i = 0; i < itemCount; i++) + { + MeshItem item = items[i]; + vertexCount += item.vertexCount; + triangleCount += item.triangleCount; + } + EnsureCapacity(vertexCount, triangleCount); - vertexCount = 0; - triangleCount = 0; - Texture2D lastTexture = null; - for (int i = 0; i < itemCount; i++) { - MeshItem item = items[i]; - int itemVertexCount = item.vertexCount; + vertexCount = 0; + triangleCount = 0; + Texture2D lastTexture = null; + for (int i = 0; i < itemCount; i++) + { + MeshItem item = items[i]; + int itemVertexCount = item.vertexCount; - if (item.texture != lastTexture || vertexCount + itemVertexCount > short.MaxValue) { - FlushVertexArray(device, vertexCount, triangleCount); - vertexCount = 0; - triangleCount = 0; - lastTexture = item.texture; - device.Textures[0] = lastTexture; - } + if (item.texture != lastTexture || vertexCount + itemVertexCount > short.MaxValue) + { + FlushVertexArray(device, vertexCount, triangleCount); + vertexCount = 0; + triangleCount = 0; + lastTexture = item.texture; + device.Textures[0] = lastTexture; + } - int[] itemTriangles = item.triangles; - int itemTriangleCount = item.triangleCount; - for (int ii = 0, t = triangleCount; ii < itemTriangleCount; ii++, t++) - triangles[t] = (short)(itemTriangles[ii] + vertexCount); - triangleCount += itemTriangleCount; + int[] itemTriangles = item.triangles; + int itemTriangleCount = item.triangleCount; + for (int ii = 0, t = triangleCount; ii < itemTriangleCount; ii++, t++) + triangles[t] = (short)(itemTriangles[ii] + vertexCount); + triangleCount += itemTriangleCount; - Array.Copy(item.vertices, 0, vertexArray, vertexCount, itemVertexCount); - vertexCount += itemVertexCount; + Array.Copy(item.vertices, 0, vertexArray, vertexCount, itemVertexCount); + vertexCount += itemVertexCount; - item.texture = null; - freeItems.Enqueue(item); - } - FlushVertexArray(device, vertexCount, triangleCount); - items.Clear(); - } + item.texture = null; + freeItems.Enqueue(item); + } + FlushVertexArray(device, vertexCount, triangleCount); + items.Clear(); + } - private void FlushVertexArray (GraphicsDevice device, int vertexCount, int triangleCount) { - if (vertexCount == 0) return; - device.DrawUserIndexedPrimitives( - PrimitiveType.TriangleList, - vertexArray, 0, vertexCount, - triangles, 0, triangleCount / 3, - VertexPositionColorTexture.VertexDeclaration); - } - } + private void FlushVertexArray(GraphicsDevice device, int vertexCount, int triangleCount) + { + if (vertexCount == 0) return; + device.DrawUserIndexedPrimitives( + PrimitiveType.TriangleList, + vertexArray, 0, vertexCount, + triangles, 0, triangleCount / 3, + VertexPositionColorTexture.VertexDeclaration); + } + } - public class MeshItem { - public Texture2D texture; - public int vertexCount, triangleCount; - public VertexPositionColorTexture[] vertices = { }; - public int[] triangles = { }; - } + public class MeshItem + { + public Texture2D texture; + public int vertexCount, triangleCount; + public VertexPositionColorTexture[] vertices = { }; + public int[] triangles = { }; + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/XnaLoader/RegionBatcher.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/XnaLoader/RegionBatcher.cs index 426096f..6af706a 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/XnaLoader/RegionBatcher.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/XnaLoader/RegionBatcher.cs @@ -31,151 +31,163 @@ using System; using System.Collections.Generic; using Microsoft.Xna.Framework.Graphics; -using Microsoft.Xna.Framework; -namespace Spine2_1_08 { - // #region License - // /* - // Microsoft Public License (Ms-PL) - // MonoGame - Copyright ?2009 The MonoGame Team - // - // All rights reserved. - // - // This license governs use of the accompanying software. If you use the software, you accept this license. If you do not - // accept the license, do not use the software. - // - // 1. Definitions - // The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under - // U.S. copyright law. - // - // A "contribution" is the original software, or any additions or changes to the software. - // A "contributor" is any person that distributes its contribution under this license. - // "Licensed patents" are a contributor's patent claims that read directly on its contribution. - // - // 2. Grant of Rights - // (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, - // each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. - // (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, - // each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. - // - // 3. Conditions and Limitations - // (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. - // (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, - // your patent license from such contributor to the software ends automatically. - // (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution - // notices that are present in the software. - // (D) If you distribute any portion of the software in source code form, you may do so only under this license by including - // a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object - // code form, you may only do so under a license that complies with this license. - // (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees - // or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent - // permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular - // purpose and non-infringement. - // */ - // #endregion License - // +namespace Spine2_1_08 +{ + // #region License + // /* + // Microsoft Public License (Ms-PL) + // MonoGame - Copyright ?2009 The MonoGame Team + // + // All rights reserved. + // + // This license governs use of the accompanying software. If you use the software, you accept this license. If you do not + // accept the license, do not use the software. + // + // 1. Definitions + // The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under + // U.S. copyright law. + // + // A "contribution" is the original software, or any additions or changes to the software. + // A "contributor" is any person that distributes its contribution under this license. + // "Licensed patents" are a contributor's patent claims that read directly on its contribution. + // + // 2. Grant of Rights + // (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, + // each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. + // (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, + // each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. + // + // 3. Conditions and Limitations + // (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. + // (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, + // your patent license from such contributor to the software ends automatically. + // (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution + // notices that are present in the software. + // (D) If you distribute any portion of the software in source code form, you may do so only under this license by including + // a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object + // code form, you may only do so under a license that complies with this license. + // (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees + // or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent + // permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular + // purpose and non-infringement. + // */ + // #endregion License + // - /// Draws batched quads using indices. - public class RegionBatcher { - private const int maxBatchSize = short.MaxValue / 6; // 6 = 4 vertices unique and 2 shared, per quad - private readonly List items; - private readonly Queue freeItems; - private VertexPositionColorTexture[] vertexArray; - private short[] indices; + /// Draws batched quads using indices. + public class RegionBatcher + { + private const int maxBatchSize = short.MaxValue / 6; // 6 = 4 vertices unique and 2 shared, per quad + private readonly List items; + private readonly Queue freeItems; + private VertexPositionColorTexture[] vertexArray; + private short[] indices; - public RegionBatcher () { - items = new List(256); - freeItems = new Queue(256); - EnsureArrayCapacity(256); - } + public RegionBatcher() + { + items = new List(256); + freeItems = new Queue(256); + EnsureArrayCapacity(256); + } - /// Returns a pooled RegionItem. - public RegionItem NextItem () { - RegionItem item = freeItems.Count > 0 ? freeItems.Dequeue() : new RegionItem(); - items.Add(item); - return item; - } + /// Returns a pooled RegionItem. + public RegionItem NextItem() + { + RegionItem item = freeItems.Count > 0 ? freeItems.Dequeue() : new RegionItem(); + items.Add(item); + return item; + } - /// Resize and recreate the indices and vertex position color buffers. - private void EnsureArrayCapacity (int itemCount) { - if (indices != null && indices.Length >= 6 * itemCount) return; + /// Resize and recreate the indices and vertex position color buffers. + private void EnsureArrayCapacity(int itemCount) + { + if (indices != null && indices.Length >= 6 * itemCount) return; - short[] newIndices = new short[6 * itemCount]; - int start = 0; - if (indices != null) { - indices.CopyTo(newIndices, 0); - start = indices.Length / 6; - } - for (var i = start; i < itemCount; i++) { - /* TL TR + short[] newIndices = new short[6 * itemCount]; + int start = 0; + if (indices != null) + { + indices.CopyTo(newIndices, 0); + start = indices.Length / 6; + } + for (var i = start; i < itemCount; i++) + { + /* TL TR * 0----1 0,1,2,3 = index offsets for vertex indices * | | TL,TR,BL,BR are vertex references in RegionItem. * 2----3 * BL BR */ - newIndices[i * 6 + 0] = (short)(i * 4); - newIndices[i * 6 + 1] = (short)(i * 4 + 1); - newIndices[i * 6 + 2] = (short)(i * 4 + 2); - newIndices[i * 6 + 3] = (short)(i * 4 + 1); - newIndices[i * 6 + 4] = (short)(i * 4 + 3); - newIndices[i * 6 + 5] = (short)(i * 4 + 2); - } - indices = newIndices; + newIndices[i * 6 + 0] = (short)(i * 4); + newIndices[i * 6 + 1] = (short)(i * 4 + 1); + newIndices[i * 6 + 2] = (short)(i * 4 + 2); + newIndices[i * 6 + 3] = (short)(i * 4 + 1); + newIndices[i * 6 + 4] = (short)(i * 4 + 3); + newIndices[i * 6 + 5] = (short)(i * 4 + 2); + } + indices = newIndices; - vertexArray = new VertexPositionColorTexture[4 * itemCount]; - } + vertexArray = new VertexPositionColorTexture[4 * itemCount]; + } - public void Draw (GraphicsDevice device) { - if (items.Count == 0) return; + public void Draw(GraphicsDevice device) + { + if (items.Count == 0) return; - int itemIndex = 0; - int itemCount = items.Count; - while (itemCount > 0) { - int itemsToProcess = Math.Min(itemCount, maxBatchSize); - EnsureArrayCapacity(itemsToProcess); + int itemIndex = 0; + int itemCount = items.Count; + while (itemCount > 0) + { + int itemsToProcess = Math.Min(itemCount, maxBatchSize); + EnsureArrayCapacity(itemsToProcess); - var count = 0; - Texture2D texture = null; - for (int i = 0; i < itemsToProcess; i++, itemIndex++) { - RegionItem item = items[itemIndex]; - if (item.texture != texture) { - FlushVertexArray(device, count); - texture = item.texture; - count = 0; - device.Textures[0] = texture; - } + var count = 0; + Texture2D texture = null; + for (int i = 0; i < itemsToProcess; i++, itemIndex++) + { + RegionItem item = items[itemIndex]; + if (item.texture != texture) + { + FlushVertexArray(device, count); + texture = item.texture; + count = 0; + device.Textures[0] = texture; + } - vertexArray[count++] = item.vertexTL; - vertexArray[count++] = item.vertexTR; - vertexArray[count++] = item.vertexBL; - vertexArray[count++] = item.vertexBR; + vertexArray[count++] = item.vertexTL; + vertexArray[count++] = item.vertexTR; + vertexArray[count++] = item.vertexBL; + vertexArray[count++] = item.vertexBR; - item.texture = null; - freeItems.Enqueue(item); - } - FlushVertexArray(device, count); - itemCount -= itemsToProcess; - } - items.Clear(); - } + item.texture = null; + freeItems.Enqueue(item); + } + FlushVertexArray(device, count); + itemCount -= itemsToProcess; + } + items.Clear(); + } - /// Sends the triangle list to the graphics device. - /// Start index of vertices to draw. Not used except to compute the count of vertices to draw. - /// End index of vertices to draw. Not used except to compute the count of vertices to draw. - private void FlushVertexArray (GraphicsDevice device, int count) { - if (count == 0) return; - device.DrawUserIndexedPrimitives( - PrimitiveType.TriangleList, - vertexArray, 0, count, - indices, 0, (count / 4) * 2, - VertexPositionColorTexture.VertexDeclaration); - } - } + /// Sends the triangle list to the graphics device. + /// Start index of vertices to draw. Not used except to compute the count of vertices to draw. + /// End index of vertices to draw. Not used except to compute the count of vertices to draw. + private void FlushVertexArray(GraphicsDevice device, int count) + { + if (count == 0) return; + device.DrawUserIndexedPrimitives( + PrimitiveType.TriangleList, + vertexArray, 0, count, + indices, 0, (count / 4) * 2, + VertexPositionColorTexture.VertexDeclaration); + } + } - public class RegionItem { - public Texture2D texture; - public VertexPositionColorTexture vertexTL; - public VertexPositionColorTexture vertexTR; - public VertexPositionColorTexture vertexBL; - public VertexPositionColorTexture vertexBR; - } + public class RegionItem + { + public Texture2D texture; + public VertexPositionColorTexture vertexTL; + public VertexPositionColorTexture vertexTR; + public VertexPositionColorTexture vertexBL; + public VertexPositionColorTexture vertexBR; + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/XnaLoader/SkeletonMeshRenderer.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/XnaLoader/SkeletonMeshRenderer.cs index 9a8dde3..1764b15 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/XnaLoader/SkeletonMeshRenderer.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/XnaLoader/SkeletonMeshRenderer.cs @@ -28,204 +28,228 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; using System.Collections.Generic; -using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; -namespace Spine2_1_08 { - /// Draws region and mesh attachments. - public class SkeletonMeshRenderer { - private const int TL = 0; - private const int TR = 1; - private const int BL = 2; - private const int BR = 3; - - GraphicsDevice device; - MeshBatcher batcher; - RasterizerState rasterizerState; - float[] vertices = new float[8]; - int[] quadTriangles = { 0, 1, 2, 1, 3, 2 }; - BlendState defaultBlendState; - - BasicEffect effect; - public BasicEffect Effect { get { return effect; } set { effect = value; } } - - private bool premultipliedAlpha; - public bool PremultipliedAlpha { get { return premultipliedAlpha; } set { premultipliedAlpha = value; } } - - public SkeletonMeshRenderer (GraphicsDevice device) { - this.device = device; - - batcher = new MeshBatcher(); - - effect = new BasicEffect(device); - effect.World = Matrix.Identity; - effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); - effect.TextureEnabled = true; - effect.VertexColorEnabled = true; - - rasterizerState = new RasterizerState(); - rasterizerState.CullMode = CullMode.None; - - Bone.yDown = true; - } - - public void Begin () { - defaultBlendState = premultipliedAlpha ? BlendState.AlphaBlend : BlendState.NonPremultiplied; - - device.RasterizerState = rasterizerState; - device.BlendState = defaultBlendState; - - effect.Projection = Matrix.CreateOrthographicOffCenter(0, device.Viewport.Width, device.Viewport.Height, 0, 1, 0); - } - - public void End () { - foreach (EffectPass pass in effect.CurrentTechnique.Passes) { - pass.Apply(); - batcher.Draw(device); - } - } - - public void Draw (Skeleton skeleton) { - float[] vertices = this.vertices; - List drawOrder = skeleton.DrawOrder; - float skeletonR = skeleton.R, skeletonG = skeleton.G, skeletonB = skeleton.B, skeletonA = skeleton.A; - for (int i = 0, n = drawOrder.Count; i < n; i++) { - Slot slot = drawOrder[i]; - Attachment attachment = slot.Attachment; - if (attachment is RegionAttachment) { - RegionAttachment regionAttachment = (RegionAttachment)attachment; - BlendState blend = slot.Data.AdditiveBlending ? BlendState.Additive : defaultBlendState; - if (device.BlendState != blend) { - End(); - device.BlendState = blend; - } - - MeshItem item = batcher.NextItem(4, 6); - item.triangles = quadTriangles; - VertexPositionColorTexture[] itemVertices = item.vertices; - - AtlasRegion region = (AtlasRegion)regionAttachment.RendererObject; - item.texture = (Texture2D)region.page.rendererObject; - - Color color; - float a = skeletonA * slot.A * regionAttachment.A; - if (premultipliedAlpha) { - color = new Color( - skeletonR * slot.R * regionAttachment.R * a, - skeletonG * slot.G * regionAttachment.G * a, - skeletonB * slot.B * regionAttachment.B * a, a); - } else { - color = new Color( - skeletonR * slot.R * regionAttachment.R, - skeletonG * slot.G * regionAttachment.G, - skeletonB * slot.B * regionAttachment.B, a); - } - itemVertices[TL].Color = color; - itemVertices[BL].Color = color; - itemVertices[BR].Color = color; - itemVertices[TR].Color = color; - - regionAttachment.ComputeWorldVertices(slot.Bone, vertices); - itemVertices[TL].Position.X = vertices[RegionAttachment.X1]; - itemVertices[TL].Position.Y = vertices[RegionAttachment.Y1]; - itemVertices[TL].Position.Z = 0; - itemVertices[BL].Position.X = vertices[RegionAttachment.X2]; - itemVertices[BL].Position.Y = vertices[RegionAttachment.Y2]; - itemVertices[BL].Position.Z = 0; - itemVertices[BR].Position.X = vertices[RegionAttachment.X3]; - itemVertices[BR].Position.Y = vertices[RegionAttachment.Y3]; - itemVertices[BR].Position.Z = 0; - itemVertices[TR].Position.X = vertices[RegionAttachment.X4]; - itemVertices[TR].Position.Y = vertices[RegionAttachment.Y4]; - itemVertices[TR].Position.Z = 0; - - float[] uvs = regionAttachment.UVs; - itemVertices[TL].TextureCoordinate.X = uvs[RegionAttachment.X1]; - itemVertices[TL].TextureCoordinate.Y = uvs[RegionAttachment.Y1]; - itemVertices[BL].TextureCoordinate.X = uvs[RegionAttachment.X2]; - itemVertices[BL].TextureCoordinate.Y = uvs[RegionAttachment.Y2]; - itemVertices[BR].TextureCoordinate.X = uvs[RegionAttachment.X3]; - itemVertices[BR].TextureCoordinate.Y = uvs[RegionAttachment.Y3]; - itemVertices[TR].TextureCoordinate.X = uvs[RegionAttachment.X4]; - itemVertices[TR].TextureCoordinate.Y = uvs[RegionAttachment.Y4]; - } else if (attachment is MeshAttachment) { - MeshAttachment mesh = (MeshAttachment)attachment; - int vertexCount = mesh.Vertices.Length; - if (vertices.Length < vertexCount) vertices = new float[vertexCount]; - mesh.ComputeWorldVertices(slot, vertices); - - int[] triangles = mesh.Triangles; - MeshItem item = batcher.NextItem(vertexCount, triangles.Length); - item.triangles = triangles; - - AtlasRegion region = (AtlasRegion)mesh.RendererObject; - item.texture = (Texture2D)region.page.rendererObject; - - Color color; - float a = skeletonA * slot.A * mesh.A; - if (premultipliedAlpha) { - color = new Color( - skeletonR * slot.R * mesh.R * a, - skeletonG * slot.G * mesh.G * a, - skeletonB * slot.B * mesh.B * a, a); - } else { - color = new Color( - skeletonR * slot.R * mesh.R, - skeletonG * slot.G * mesh.G, - skeletonB * slot.B * mesh.B, a); - } - - float[] uvs = mesh.UVs; - VertexPositionColorTexture[] itemVertices = item.vertices; - for (int ii = 0, v = 0; v < vertexCount; ii++, v += 2) { - itemVertices[ii].Color = color; - itemVertices[ii].Position.X = vertices[v]; - itemVertices[ii].Position.Y = vertices[v + 1]; - itemVertices[ii].Position.Z = 0; - itemVertices[ii].TextureCoordinate.X = uvs[v]; - itemVertices[ii].TextureCoordinate.Y = uvs[v + 1]; - } - } else if (attachment is SkinnedMeshAttachment) { - SkinnedMeshAttachment mesh = (SkinnedMeshAttachment)attachment; - int vertexCount = mesh.UVs.Length; - if (vertices.Length < vertexCount) vertices = new float[vertexCount]; - mesh.ComputeWorldVertices(slot, vertices); - - int[] triangles = mesh.Triangles; - MeshItem item = batcher.NextItem(vertexCount, triangles.Length); - item.triangles = triangles; - - AtlasRegion region = (AtlasRegion)mesh.RendererObject; - item.texture = (Texture2D)region.page.rendererObject; - - Color color; - float a = skeletonA * slot.A * mesh.A; - if (premultipliedAlpha) { - color = new Color( - skeletonR * slot.R * mesh.R * a, - skeletonG * slot.G * mesh.G * a, - skeletonB * slot.B * mesh.B * a, a); - } else { - color = new Color( - skeletonR * slot.R * mesh.R, - skeletonG * slot.G * mesh.G, - skeletonB * slot.B * mesh.B, a); - } - - float[] uvs = mesh.UVs; - VertexPositionColorTexture[] itemVertices = item.vertices; - for (int ii = 0, v = 0; v < vertexCount; ii++, v += 2) { - itemVertices[ii].Color = color; - itemVertices[ii].Position.X = vertices[v]; - itemVertices[ii].Position.Y = vertices[v + 1]; - itemVertices[ii].Position.Z = 0; - itemVertices[ii].TextureCoordinate.X = uvs[v]; - itemVertices[ii].TextureCoordinate.Y = uvs[v + 1]; - } - } - } - } - } +namespace Spine2_1_08 +{ + /// Draws region and mesh attachments. + public class SkeletonMeshRenderer + { + private const int TL = 0; + private const int TR = 1; + private const int BL = 2; + private const int BR = 3; + + GraphicsDevice device; + MeshBatcher batcher; + RasterizerState rasterizerState; + float[] vertices = new float[8]; + int[] quadTriangles = { 0, 1, 2, 1, 3, 2 }; + BlendState defaultBlendState; + + BasicEffect effect; + public BasicEffect Effect { get { return effect; } set { effect = value; } } + + private bool premultipliedAlpha; + public bool PremultipliedAlpha { get { return premultipliedAlpha; } set { premultipliedAlpha = value; } } + + public SkeletonMeshRenderer(GraphicsDevice device) + { + this.device = device; + + batcher = new MeshBatcher(); + + effect = new BasicEffect(device); + effect.World = Matrix.Identity; + effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); + effect.TextureEnabled = true; + effect.VertexColorEnabled = true; + + rasterizerState = new RasterizerState(); + rasterizerState.CullMode = CullMode.None; + + Bone.yDown = true; + } + + public void Begin() + { + defaultBlendState = premultipliedAlpha ? BlendState.AlphaBlend : BlendState.NonPremultiplied; + + device.RasterizerState = rasterizerState; + device.BlendState = defaultBlendState; + + effect.Projection = Matrix.CreateOrthographicOffCenter(0, device.Viewport.Width, device.Viewport.Height, 0, 1, 0); + } + + public void End() + { + foreach (EffectPass pass in effect.CurrentTechnique.Passes) + { + pass.Apply(); + batcher.Draw(device); + } + } + + public void Draw(Skeleton skeleton) + { + float[] vertices = this.vertices; + List drawOrder = skeleton.DrawOrder; + float skeletonR = skeleton.R, skeletonG = skeleton.G, skeletonB = skeleton.B, skeletonA = skeleton.A; + for (int i = 0, n = drawOrder.Count; i < n; i++) + { + Slot slot = drawOrder[i]; + Attachment attachment = slot.Attachment; + if (attachment is RegionAttachment) + { + RegionAttachment regionAttachment = (RegionAttachment)attachment; + BlendState blend = slot.Data.AdditiveBlending ? BlendState.Additive : defaultBlendState; + if (device.BlendState != blend) + { + End(); + device.BlendState = blend; + } + + MeshItem item = batcher.NextItem(4, 6); + item.triangles = quadTriangles; + VertexPositionColorTexture[] itemVertices = item.vertices; + + AtlasRegion region = (AtlasRegion)regionAttachment.RendererObject; + item.texture = (Texture2D)region.page.rendererObject; + + Color color; + float a = skeletonA * slot.A * regionAttachment.A; + if (premultipliedAlpha) + { + color = new Color( + skeletonR * slot.R * regionAttachment.R * a, + skeletonG * slot.G * regionAttachment.G * a, + skeletonB * slot.B * regionAttachment.B * a, a); + } + else + { + color = new Color( + skeletonR * slot.R * regionAttachment.R, + skeletonG * slot.G * regionAttachment.G, + skeletonB * slot.B * regionAttachment.B, a); + } + itemVertices[TL].Color = color; + itemVertices[BL].Color = color; + itemVertices[BR].Color = color; + itemVertices[TR].Color = color; + + regionAttachment.ComputeWorldVertices(slot.Bone, vertices); + itemVertices[TL].Position.X = vertices[RegionAttachment.X1]; + itemVertices[TL].Position.Y = vertices[RegionAttachment.Y1]; + itemVertices[TL].Position.Z = 0; + itemVertices[BL].Position.X = vertices[RegionAttachment.X2]; + itemVertices[BL].Position.Y = vertices[RegionAttachment.Y2]; + itemVertices[BL].Position.Z = 0; + itemVertices[BR].Position.X = vertices[RegionAttachment.X3]; + itemVertices[BR].Position.Y = vertices[RegionAttachment.Y3]; + itemVertices[BR].Position.Z = 0; + itemVertices[TR].Position.X = vertices[RegionAttachment.X4]; + itemVertices[TR].Position.Y = vertices[RegionAttachment.Y4]; + itemVertices[TR].Position.Z = 0; + + float[] uvs = regionAttachment.UVs; + itemVertices[TL].TextureCoordinate.X = uvs[RegionAttachment.X1]; + itemVertices[TL].TextureCoordinate.Y = uvs[RegionAttachment.Y1]; + itemVertices[BL].TextureCoordinate.X = uvs[RegionAttachment.X2]; + itemVertices[BL].TextureCoordinate.Y = uvs[RegionAttachment.Y2]; + itemVertices[BR].TextureCoordinate.X = uvs[RegionAttachment.X3]; + itemVertices[BR].TextureCoordinate.Y = uvs[RegionAttachment.Y3]; + itemVertices[TR].TextureCoordinate.X = uvs[RegionAttachment.X4]; + itemVertices[TR].TextureCoordinate.Y = uvs[RegionAttachment.Y4]; + } + else if (attachment is MeshAttachment) + { + MeshAttachment mesh = (MeshAttachment)attachment; + int vertexCount = mesh.Vertices.Length; + if (vertices.Length < vertexCount) vertices = new float[vertexCount]; + mesh.ComputeWorldVertices(slot, vertices); + + int[] triangles = mesh.Triangles; + MeshItem item = batcher.NextItem(vertexCount, triangles.Length); + item.triangles = triangles; + + AtlasRegion region = (AtlasRegion)mesh.RendererObject; + item.texture = (Texture2D)region.page.rendererObject; + + Color color; + float a = skeletonA * slot.A * mesh.A; + if (premultipliedAlpha) + { + color = new Color( + skeletonR * slot.R * mesh.R * a, + skeletonG * slot.G * mesh.G * a, + skeletonB * slot.B * mesh.B * a, a); + } + else + { + color = new Color( + skeletonR * slot.R * mesh.R, + skeletonG * slot.G * mesh.G, + skeletonB * slot.B * mesh.B, a); + } + + float[] uvs = mesh.UVs; + VertexPositionColorTexture[] itemVertices = item.vertices; + for (int ii = 0, v = 0; v < vertexCount; ii++, v += 2) + { + itemVertices[ii].Color = color; + itemVertices[ii].Position.X = vertices[v]; + itemVertices[ii].Position.Y = vertices[v + 1]; + itemVertices[ii].Position.Z = 0; + itemVertices[ii].TextureCoordinate.X = uvs[v]; + itemVertices[ii].TextureCoordinate.Y = uvs[v + 1]; + } + } + else if (attachment is SkinnedMeshAttachment) + { + SkinnedMeshAttachment mesh = (SkinnedMeshAttachment)attachment; + int vertexCount = mesh.UVs.Length; + if (vertices.Length < vertexCount) vertices = new float[vertexCount]; + mesh.ComputeWorldVertices(slot, vertices); + + int[] triangles = mesh.Triangles; + MeshItem item = batcher.NextItem(vertexCount, triangles.Length); + item.triangles = triangles; + + AtlasRegion region = (AtlasRegion)mesh.RendererObject; + item.texture = (Texture2D)region.page.rendererObject; + + Color color; + float a = skeletonA * slot.A * mesh.A; + if (premultipliedAlpha) + { + color = new Color( + skeletonR * slot.R * mesh.R * a, + skeletonG * slot.G * mesh.G * a, + skeletonB * slot.B * mesh.B * a, a); + } + else + { + color = new Color( + skeletonR * slot.R * mesh.R, + skeletonG * slot.G * mesh.G, + skeletonB * slot.B * mesh.B, a); + } + + float[] uvs = mesh.UVs; + VertexPositionColorTexture[] itemVertices = item.vertices; + for (int ii = 0, v = 0; v < vertexCount; ii++, v += 2) + { + itemVertices[ii].Color = color; + itemVertices[ii].Position.X = vertices[v]; + itemVertices[ii].Position.Y = vertices[v + 1]; + itemVertices[ii].Position.Z = 0; + itemVertices[ii].TextureCoordinate.X = uvs[v]; + itemVertices[ii].TextureCoordinate.Y = uvs[v + 1]; + } + } + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/XnaLoader/SkeletonRegionRenderer.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/XnaLoader/SkeletonRegionRenderer.cs index b5f3447..04529a2 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/XnaLoader/SkeletonRegionRenderer.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/XnaLoader/SkeletonRegionRenderer.cs @@ -28,114 +28,123 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; using System.Collections.Generic; -using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; -namespace Spine2_1_08 { - /// Draws region attachments. - public class SkeletonRegionRenderer { - GraphicsDevice device; - RegionBatcher batcher; - RasterizerState rasterizerState; - float[] vertices = new float[8]; - BlendState defaultBlendState; - - BasicEffect effect; - public BasicEffect Effect { get { return effect; } set { effect = value; } } - - private bool premultipliedAlpha; - public bool PremultipliedAlpha { get { return premultipliedAlpha; } set { premultipliedAlpha = value; } } - - public SkeletonRegionRenderer (GraphicsDevice device) { - this.device = device; - - batcher = new RegionBatcher(); - - effect = new BasicEffect(device); - effect.World = Matrix.Identity; - effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); - effect.TextureEnabled = true; - effect.VertexColorEnabled = true; - - rasterizerState = new RasterizerState(); - rasterizerState.CullMode = CullMode.None; - - Bone.yDown = true; - } - - public void Begin () { - defaultBlendState = premultipliedAlpha ? BlendState.AlphaBlend : BlendState.NonPremultiplied; - - device.RasterizerState = rasterizerState; - device.BlendState = defaultBlendState; - - effect.Projection = Matrix.CreateOrthographicOffCenter(0, device.Viewport.Width, device.Viewport.Height, 0, 1, 0); - } - - public void End () { - foreach (EffectPass pass in effect.CurrentTechnique.Passes) { - pass.Apply(); - batcher.Draw(device); - } - } - - public void Draw (Skeleton skeleton) { - List drawOrder = skeleton.DrawOrder; - float skeletonR = skeleton.R, skeletonG = skeleton.G, skeletonB = skeleton.B, skeletonA = skeleton.A; - for (int i = 0, n = drawOrder.Count; i < n; i++) { - Slot slot = drawOrder[i]; - RegionAttachment regionAttachment = slot.Attachment as RegionAttachment; - if (regionAttachment != null) { - BlendState blend = slot.Data.AdditiveBlending ? BlendState.Additive : defaultBlendState; - if (device.BlendState != blend) { - End(); - device.BlendState = blend; - } - - RegionItem item = batcher.NextItem(); - - AtlasRegion region = (AtlasRegion)regionAttachment.RendererObject; - item.texture = (Texture2D)region.page.rendererObject; - - Color color; - float a = skeletonA * slot.A; - if (premultipliedAlpha) - color = new Color(skeletonR * slot.R * a, skeletonG * slot.G * a, skeletonB * slot.B * a, a); - else - color = new Color(skeletonR * slot.R, skeletonG * slot.G, skeletonB * slot.B, a); - item.vertexTL.Color = color; - item.vertexBL.Color = color; - item.vertexBR.Color = color; - item.vertexTR.Color = color; - - float[] vertices = this.vertices; - regionAttachment.ComputeWorldVertices(slot.Bone, vertices); - item.vertexTL.Position.X = vertices[RegionAttachment.X1]; - item.vertexTL.Position.Y = vertices[RegionAttachment.Y1]; - item.vertexTL.Position.Z = 0; - item.vertexBL.Position.X = vertices[RegionAttachment.X2]; - item.vertexBL.Position.Y = vertices[RegionAttachment.Y2]; - item.vertexBL.Position.Z = 0; - item.vertexBR.Position.X = vertices[RegionAttachment.X3]; - item.vertexBR.Position.Y = vertices[RegionAttachment.Y3]; - item.vertexBR.Position.Z = 0; - item.vertexTR.Position.X = vertices[RegionAttachment.X4]; - item.vertexTR.Position.Y = vertices[RegionAttachment.Y4]; - item.vertexTR.Position.Z = 0; - - float[] uvs = regionAttachment.UVs; - item.vertexTL.TextureCoordinate.X = uvs[RegionAttachment.X1]; - item.vertexTL.TextureCoordinate.Y = uvs[RegionAttachment.Y1]; - item.vertexBL.TextureCoordinate.X = uvs[RegionAttachment.X2]; - item.vertexBL.TextureCoordinate.Y = uvs[RegionAttachment.Y2]; - item.vertexBR.TextureCoordinate.X = uvs[RegionAttachment.X3]; - item.vertexBR.TextureCoordinate.Y = uvs[RegionAttachment.Y3]; - item.vertexTR.TextureCoordinate.X = uvs[RegionAttachment.X4]; - item.vertexTR.TextureCoordinate.Y = uvs[RegionAttachment.Y4]; - } - } - } - } +namespace Spine2_1_08 +{ + /// Draws region attachments. + public class SkeletonRegionRenderer + { + GraphicsDevice device; + RegionBatcher batcher; + RasterizerState rasterizerState; + float[] vertices = new float[8]; + BlendState defaultBlendState; + + BasicEffect effect; + public BasicEffect Effect { get { return effect; } set { effect = value; } } + + private bool premultipliedAlpha; + public bool PremultipliedAlpha { get { return premultipliedAlpha; } set { premultipliedAlpha = value; } } + + public SkeletonRegionRenderer(GraphicsDevice device) + { + this.device = device; + + batcher = new RegionBatcher(); + + effect = new BasicEffect(device); + effect.World = Matrix.Identity; + effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); + effect.TextureEnabled = true; + effect.VertexColorEnabled = true; + + rasterizerState = new RasterizerState(); + rasterizerState.CullMode = CullMode.None; + + Bone.yDown = true; + } + + public void Begin() + { + defaultBlendState = premultipliedAlpha ? BlendState.AlphaBlend : BlendState.NonPremultiplied; + + device.RasterizerState = rasterizerState; + device.BlendState = defaultBlendState; + + effect.Projection = Matrix.CreateOrthographicOffCenter(0, device.Viewport.Width, device.Viewport.Height, 0, 1, 0); + } + + public void End() + { + foreach (EffectPass pass in effect.CurrentTechnique.Passes) + { + pass.Apply(); + batcher.Draw(device); + } + } + + public void Draw(Skeleton skeleton) + { + List drawOrder = skeleton.DrawOrder; + float skeletonR = skeleton.R, skeletonG = skeleton.G, skeletonB = skeleton.B, skeletonA = skeleton.A; + for (int i = 0, n = drawOrder.Count; i < n; i++) + { + Slot slot = drawOrder[i]; + RegionAttachment regionAttachment = slot.Attachment as RegionAttachment; + if (regionAttachment != null) + { + BlendState blend = slot.Data.AdditiveBlending ? BlendState.Additive : defaultBlendState; + if (device.BlendState != blend) + { + End(); + device.BlendState = blend; + } + + RegionItem item = batcher.NextItem(); + + AtlasRegion region = (AtlasRegion)regionAttachment.RendererObject; + item.texture = (Texture2D)region.page.rendererObject; + + Color color; + float a = skeletonA * slot.A; + if (premultipliedAlpha) + color = new Color(skeletonR * slot.R * a, skeletonG * slot.G * a, skeletonB * slot.B * a, a); + else + color = new Color(skeletonR * slot.R, skeletonG * slot.G, skeletonB * slot.B, a); + item.vertexTL.Color = color; + item.vertexBL.Color = color; + item.vertexBR.Color = color; + item.vertexTR.Color = color; + + float[] vertices = this.vertices; + regionAttachment.ComputeWorldVertices(slot.Bone, vertices); + item.vertexTL.Position.X = vertices[RegionAttachment.X1]; + item.vertexTL.Position.Y = vertices[RegionAttachment.Y1]; + item.vertexTL.Position.Z = 0; + item.vertexBL.Position.X = vertices[RegionAttachment.X2]; + item.vertexBL.Position.Y = vertices[RegionAttachment.Y2]; + item.vertexBL.Position.Z = 0; + item.vertexBR.Position.X = vertices[RegionAttachment.X3]; + item.vertexBR.Position.Y = vertices[RegionAttachment.Y3]; + item.vertexBR.Position.Z = 0; + item.vertexTR.Position.X = vertices[RegionAttachment.X4]; + item.vertexTR.Position.Y = vertices[RegionAttachment.Y4]; + item.vertexTR.Position.Z = 0; + + float[] uvs = regionAttachment.UVs; + item.vertexTL.TextureCoordinate.X = uvs[RegionAttachment.X1]; + item.vertexTL.TextureCoordinate.Y = uvs[RegionAttachment.Y1]; + item.vertexBL.TextureCoordinate.X = uvs[RegionAttachment.X2]; + item.vertexBL.TextureCoordinate.Y = uvs[RegionAttachment.Y2]; + item.vertexBR.TextureCoordinate.X = uvs[RegionAttachment.X3]; + item.vertexBR.TextureCoordinate.Y = uvs[RegionAttachment.Y3]; + item.vertexTR.TextureCoordinate.X = uvs[RegionAttachment.X4]; + item.vertexTR.TextureCoordinate.Y = uvs[RegionAttachment.Y4]; + } + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/XnaLoader/XnaTextureLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/XnaLoader/XnaTextureLoader.cs index 3766091..895e99d 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/XnaLoader/XnaTextureLoader.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.08/XnaLoader/XnaTextureLoader.cs @@ -28,22 +28,24 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; -using System.IO; -using Microsoft.Xna.Framework; +using System; using Microsoft.Xna.Framework.Graphics; -namespace Spine2_1_08 { - public class XnaTextureLoader : TextureLoader { - GraphicsDevice device; +namespace Spine2_1_08 +{ + public class XnaTextureLoader : TextureLoader + { + GraphicsDevice device; - public XnaTextureLoader (GraphicsDevice device) { - this.device = device; - } + public XnaTextureLoader(GraphicsDevice device) + { + this.device = device; + } - public void Load (AtlasPage page, String path) { - Texture2D texture = Util.LoadTexture(device, path); - page.rendererObject = texture; + public void Load(AtlasPage page, String path) + { + Texture2D texture = Util.LoadTexture(device, path); + page.rendererObject = texture; if (page.width == 0 || page.height == 0) { page.width = texture.Width; @@ -51,8 +53,9 @@ public void Load (AtlasPage page, String path) { } } - public void Unload (Object texture) { - ((Texture2D)texture).Dispose(); - } - } + public void Unload(Object texture) + { + ((Texture2D)texture).Dispose(); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Animation.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Animation.cs index 5146010..0ef5963 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Animation.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Animation.cs @@ -31,693 +31,793 @@ using System; using System.Collections.Generic; -namespace Spine2_1_25 { - public class Animation { - internal List timelines; - internal float duration; - internal String name; - - public String Name { get { return name; } } - public List Timelines { get { return timelines; } set { timelines = value; } } - public float Duration { get { return duration; } set { duration = value; } } - - public Animation (String name, List timelines, float duration) { - if (name == null) throw new ArgumentNullException("name cannot be null."); - if (timelines == null) throw new ArgumentNullException("timelines cannot be null."); - this.name = name; - this.timelines = timelines; - this.duration = duration; - } - - /// Poses the skeleton at the specified time for this animation. - /// The last time the animation was applied. - /// Any triggered events are added. - public void Apply (Skeleton skeleton, float lastTime, float time, bool loop, List events) { - if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); - - if (loop && duration != 0) { - time %= duration; - lastTime %= duration; - } - - List timelines = this.timelines; - for (int i = 0, n = timelines.Count; i < n; i++) - timelines[i].Apply(skeleton, lastTime, time, events, 1); - } - - /// Poses the skeleton at the specified time for this animation mixed with the current pose. - /// The last time the animation was applied. - /// Any triggered events are added. - /// The amount of this animation that affects the current pose. - public void Mix (Skeleton skeleton, float lastTime, float time, bool loop, List events, float alpha) { - if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); - - if (loop && duration != 0) { - time %= duration; - lastTime %= duration; - } - - List timelines = this.timelines; - for (int i = 0, n = timelines.Count; i < n; i++) - timelines[i].Apply(skeleton, lastTime, time, events, alpha); - } - - /// After the first and before the last entry. - internal static int binarySearch (float[] values, float target, int step) { - int low = 0; - int high = values.Length / step - 2; - if (high == 0) return step; - int current = (int)((uint)high >> 1); - while (true) { - if (values[(current + 1) * step] <= target) - low = current + 1; - else - high = current; - if (low == high) return (low + 1) * step; - current = (int)((uint)(low + high) >> 1); - } - } - - /// After the first and before the last entry. - internal static int binarySearch (float[] values, float target) { - int low = 0; - int high = values.Length - 2; - if (high == 0) return 1; - int current = (int)((uint)high >> 1); - while (true) { - if (values[(current + 1)] <= target) - low = current + 1; - else - high = current; - if (low == high) return (low + 1); - current = (int)((uint)(low + high) >> 1); - } - } - - internal static int linearSearch (float[] values, float target, int step) { - for (int i = 0, last = values.Length - step; i <= last; i += step) - if (values[i] > target) return i; - return -1; - } - } - - public interface Timeline { - /// Sets the value(s) for the specified time. - /// May be null to not collect fired events. - void Apply (Skeleton skeleton, float lastTime, float time, List events, float alpha); - } - - /// Base class for frames that use an interpolation bezier curve. - abstract public class CurveTimeline : Timeline { - protected const float LINEAR = 0, STEPPED = 1, BEZIER = 2; - protected const int BEZIER_SEGMENTS = 10, BEZIER_SIZE = BEZIER_SEGMENTS * 2 - 1; - - private float[] curves; // type, x, y, ... - public int FrameCount { get { return curves.Length / BEZIER_SIZE + 1; } } - - public CurveTimeline (int frameCount) { - curves = new float[(frameCount - 1) * BEZIER_SIZE]; - } - - abstract public void Apply (Skeleton skeleton, float lastTime, float time, List firedEvents, float alpha); - - public void SetLinear (int frameIndex) { - curves[frameIndex * BEZIER_SIZE] = LINEAR; - } - - public void SetStepped (int frameIndex) { - curves[frameIndex * BEZIER_SIZE] = STEPPED; - } - - /// Sets the control handle positions for an interpolation bezier curve used to transition from this keyframe to the next. - /// cx1 and cx2 are from 0 to 1, representing the percent of time between the two keyframes. cy1 and cy2 are the percent of - /// the difference between the keyframe's values. - public void SetCurve (int frameIndex, float cx1, float cy1, float cx2, float cy2) { - float subdiv1 = 1f / BEZIER_SEGMENTS, subdiv2 = subdiv1 * subdiv1, subdiv3 = subdiv2 * subdiv1; - float pre1 = 3 * subdiv1, pre2 = 3 * subdiv2, pre4 = 6 * subdiv2, pre5 = 6 * subdiv3; - float tmp1x = -cx1 * 2 + cx2, tmp1y = -cy1 * 2 + cy2, tmp2x = (cx1 - cx2) * 3 + 1, tmp2y = (cy1 - cy2) * 3 + 1; - float dfx = cx1 * pre1 + tmp1x * pre2 + tmp2x * subdiv3, dfy = cy1 * pre1 + tmp1y * pre2 + tmp2y * subdiv3; - float ddfx = tmp1x * pre4 + tmp2x * pre5, ddfy = tmp1y * pre4 + tmp2y * pre5; - float dddfx = tmp2x * pre5, dddfy = tmp2y * pre5; - - int i = frameIndex * BEZIER_SIZE; - float[] curves = this.curves; - curves[i++] = BEZIER; - - float x = dfx, y = dfy; - for (int n = i + BEZIER_SIZE - 1; i < n; i += 2) { - curves[i] = x; - curves[i + 1] = y; - dfx += ddfx; - dfy += ddfy; - ddfx += dddfx; - ddfy += dddfy; - x += dfx; - y += dfy; - } - } - - public float GetCurvePercent (int frameIndex, float percent) { - float[] curves = this.curves; - int i = frameIndex * BEZIER_SIZE; - float type = curves[i]; - if (type == LINEAR) return percent; - if (type == STEPPED) return 0; - i++; - float x = 0; - for (int start = i, n = i + BEZIER_SIZE - 1; i < n; i += 2) { - x = curves[i]; - if (x >= percent) { - float prevX, prevY; - if (i == start) { - prevX = 0; - prevY = 0; - } else { - prevX = curves[i - 2]; - prevY = curves[i - 1]; - } - return prevY + (curves[i + 1] - prevY) * (percent - prevX) / (x - prevX); - } - } - float y = curves[i - 1]; - return y + (1 - y) * (percent - x) / (1 - x); // Last point is 1,1. - } - public float GetCurveType (int frameIndex) { - return curves[frameIndex * BEZIER_SIZE]; - } - } - - public class RotateTimeline : CurveTimeline { - protected const int PREV_FRAME_TIME = -2; - protected const int FRAME_VALUE = 1; - - internal int boneIndex; - internal float[] frames; - - public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, value, ... - - public RotateTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount << 1]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, float angle) { - frameIndex *= 2; - frames[frameIndex] = time; - frames[frameIndex + 1] = angle; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, List firedEvents, float alpha) { - float[] frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - Bone bone = skeleton.bones[boneIndex]; - - float amount; - - if (time >= frames[frames.Length - 2]) { // Time is after last frame. - amount = bone.data.rotation + frames[frames.Length - 1] - bone.rotation; - while (amount > 180) - amount -= 360; - while (amount < -180) - amount += 360; - bone.rotation += amount * alpha; - return; - } - - // Interpolate between the previous frame and the current frame. - int frameIndex = Animation.binarySearch(frames, time, 2); - float prevFrameValue = frames[frameIndex - 1]; - float frameTime = frames[frameIndex]; - float percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime); - percent = GetCurvePercent((frameIndex >> 1) - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); - - amount = frames[frameIndex + FRAME_VALUE] - prevFrameValue; - while (amount > 180) - amount -= 360; - while (amount < -180) - amount += 360; - amount = bone.data.rotation + (prevFrameValue + amount * percent) - bone.rotation; - while (amount > 180) - amount -= 360; - while (amount < -180) - amount += 360; - bone.rotation += amount * alpha; - } - } - - public class TranslateTimeline : CurveTimeline { - protected const int PREV_FRAME_TIME = -3; - protected const int FRAME_X = 1; - protected const int FRAME_Y = 2; - - internal int boneIndex; - internal float[] frames; - - public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, value, value, ... - - public TranslateTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * 3]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, float x, float y) { - frameIndex *= 3; - frames[frameIndex] = time; - frames[frameIndex + 1] = x; - frames[frameIndex + 2] = y; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, List firedEvents, float alpha) { - float[] frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - Bone bone = skeleton.bones[boneIndex]; - - if (time >= frames[frames.Length - 3]) { // Time is after last frame. - bone.x += (bone.data.x + frames[frames.Length - 2] - bone.x) * alpha; - bone.y += (bone.data.y + frames[frames.Length - 1] - bone.y) * alpha; - return; - } - - // Interpolate between the previous frame and the current frame. - int frameIndex = Animation.binarySearch(frames, time, 3); - float prevFrameX = frames[frameIndex - 2]; - float prevFrameY = frames[frameIndex - 1]; - float frameTime = frames[frameIndex]; - float percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime); - percent = GetCurvePercent(frameIndex / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); - - bone.x += (bone.data.x + prevFrameX + (frames[frameIndex + FRAME_X] - prevFrameX) * percent - bone.x) * alpha; - bone.y += (bone.data.y + prevFrameY + (frames[frameIndex + FRAME_Y] - prevFrameY) * percent - bone.y) * alpha; - } - } - - public class ScaleTimeline : TranslateTimeline { - public ScaleTimeline (int frameCount) - : base(frameCount) { - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, List firedEvents, float alpha) { - float[] frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - Bone bone = skeleton.bones[boneIndex]; - if (time >= frames[frames.Length - 3]) { // Time is after last frame. - bone.scaleX += (bone.data.scaleX * frames[frames.Length - 2] - bone.scaleX) * alpha; - bone.scaleY += (bone.data.scaleY * frames[frames.Length - 1] - bone.scaleY) * alpha; - return; - } - - // Interpolate between the previous frame and the current frame. - int frameIndex = Animation.binarySearch(frames, time, 3); - float prevFrameX = frames[frameIndex - 2]; - float prevFrameY = frames[frameIndex - 1]; - float frameTime = frames[frameIndex]; - float percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime); - percent = GetCurvePercent(frameIndex / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); - - bone.scaleX += (bone.data.scaleX * (prevFrameX + (frames[frameIndex + FRAME_X] - prevFrameX) * percent) - bone.scaleX) * alpha; - bone.scaleY += (bone.data.scaleY * (prevFrameY + (frames[frameIndex + FRAME_Y] - prevFrameY) * percent) - bone.scaleY) * alpha; - } - } - - public class ColorTimeline : CurveTimeline { - protected const int PREV_FRAME_TIME = -5; - protected const int FRAME_R = 1; - protected const int FRAME_G = 2; - protected const int FRAME_B = 3; - protected const int FRAME_A = 4; - - internal int slotIndex; - internal float[] frames; - - public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, r, g, b, a, ... - - public ColorTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * 5]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, float r, float g, float b, float a) { - frameIndex *= 5; - frames[frameIndex] = time; - frames[frameIndex + 1] = r; - frames[frameIndex + 2] = g; - frames[frameIndex + 3] = b; - frames[frameIndex + 4] = a; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, List firedEvents, float alpha) { - float[] frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - float r, g, b, a; - if (time >= frames[frames.Length - 5]) { - // Time is after last frame. - int i = frames.Length - 1; - r = frames[i - 3]; - g = frames[i - 2]; - b = frames[i - 1]; - a = frames[i]; - } else { - // Interpolate between the previous frame and the current frame. - int frameIndex = Animation.binarySearch(frames, time, 5); - float prevFrameR = frames[frameIndex - 4]; - float prevFrameG = frames[frameIndex - 3]; - float prevFrameB = frames[frameIndex - 2]; - float prevFrameA = frames[frameIndex - 1]; - float frameTime = frames[frameIndex]; - float percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime); - percent = GetCurvePercent(frameIndex / 5 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); - - r = prevFrameR + (frames[frameIndex + FRAME_R] - prevFrameR) * percent; - g = prevFrameG + (frames[frameIndex + FRAME_G] - prevFrameG) * percent; - b = prevFrameB + (frames[frameIndex + FRAME_B] - prevFrameB) * percent; - a = prevFrameA + (frames[frameIndex + FRAME_A] - prevFrameA) * percent; - } - Slot slot = skeleton.slots[slotIndex]; - if (alpha < 1) { - slot.r += (r - slot.r) * alpha; - slot.g += (g - slot.g) * alpha; - slot.b += (b - slot.b) * alpha; - slot.a += (a - slot.a) * alpha; - } else { - slot.r = r; - slot.g = g; - slot.b = b; - slot.a = a; - } - } - } - - public class AttachmentTimeline : Timeline { - internal int slotIndex; - internal float[] frames; - private String[] attachmentNames; - - public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, ... - public String[] AttachmentNames { get { return attachmentNames; } set { attachmentNames = value; } } - public int FrameCount { get { return frames.Length; } } - - public AttachmentTimeline (int frameCount) { - frames = new float[frameCount]; - attachmentNames = new String[frameCount]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, String attachmentName) { - frames[frameIndex] = time; - attachmentNames[frameIndex] = attachmentName; - } - - public void Apply (Skeleton skeleton, float lastTime, float time, List firedEvents, float alpha) { - float[] frames = this.frames; - if (time < frames[0]) { - if (lastTime > time) Apply(skeleton, lastTime, int.MaxValue, null, 0); - return; - } else if (lastTime > time) // - lastTime = -1; - - int frameIndex = (time >= frames[frames.Length - 1] ? frames.Length : Animation.binarySearch(frames, time)) - 1; - if (frames[frameIndex] < lastTime) return; - - String attachmentName = attachmentNames[frameIndex]; - skeleton.slots[slotIndex].Attachment = - attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); - } - } - - public class EventTimeline : Timeline { - internal float[] frames; - private Event[] events; - - public float[] Frames { get { return frames; } set { frames = value; } } // time, ... - public Event[] Events { get { return events; } set { events = value; } } - public int FrameCount { get { return frames.Length; } } - - public EventTimeline (int frameCount) { - frames = new float[frameCount]; - events = new Event[frameCount]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, Event e) { - frames[frameIndex] = time; - events[frameIndex] = e; - } - - /// Fires events for frames > lastTime and <= time. - public void Apply (Skeleton skeleton, float lastTime, float time, List firedEvents, float alpha) { - if (firedEvents == null) return; - float[] frames = this.frames; - int frameCount = frames.Length; - - if (lastTime > time) { // Fire events after last time for looped animations. - Apply(skeleton, lastTime, int.MaxValue, firedEvents, alpha); - lastTime = -1f; - } else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame. - return; - if (time < frames[0]) return; // Time is before first frame. - - int frameIndex; - if (lastTime < frames[0]) - frameIndex = 0; - else { - frameIndex = Animation.binarySearch(frames, lastTime); - float frame = frames[frameIndex]; - while (frameIndex > 0) { // Fire multiple events with the same frame. - if (frames[frameIndex - 1] != frame) break; - frameIndex--; - } - } - for (; frameIndex < frameCount && time >= frames[frameIndex]; frameIndex++) - firedEvents.Add(events[frameIndex]); - } - } - - public class DrawOrderTimeline : Timeline { - internal float[] frames; - private int[][] drawOrders; - - public float[] Frames { get { return frames; } set { frames = value; } } // time, ... - public int[][] DrawOrders { get { return drawOrders; } set { drawOrders = value; } } - public int FrameCount { get { return frames.Length; } } - - public DrawOrderTimeline (int frameCount) { - frames = new float[frameCount]; - drawOrders = new int[frameCount][]; - } - - /// Sets the time and value of the specified keyframe. - /// May be null to use bind pose draw order. - public void SetFrame (int frameIndex, float time, int[] drawOrder) { - frames[frameIndex] = time; - drawOrders[frameIndex] = drawOrder; - } - - public void Apply (Skeleton skeleton, float lastTime, float time, List firedEvents, float alpha) { - float[] frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - int frameIndex; - if (time >= frames[frames.Length - 1]) // Time is after last frame. - frameIndex = frames.Length - 1; - else - frameIndex = Animation.binarySearch(frames, time) - 1; - - List drawOrder = skeleton.drawOrder; - List slots = skeleton.slots; - int[] drawOrderToSetupIndex = drawOrders[frameIndex]; - if (drawOrderToSetupIndex == null) { - drawOrder.Clear(); - drawOrder.AddRange(slots); - } else { - for (int i = 0, n = drawOrderToSetupIndex.Length; i < n; i++) - drawOrder[i] = slots[drawOrderToSetupIndex[i]]; - } - } - } - - public class FFDTimeline : CurveTimeline { - internal int slotIndex; - internal float[] frames; - private float[][] frameVertices; - internal Attachment attachment; - - public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, ... - public float[][] Vertices { get { return frameVertices; } set { frameVertices = value; } } - public Attachment Attachment { get { return attachment; } set { attachment = value; } } - - public FFDTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount]; - frameVertices = new float[frameCount][]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, float[] vertices) { - frames[frameIndex] = time; - frameVertices[frameIndex] = vertices; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, List firedEvents, float alpha) { - Slot slot = skeleton.slots[slotIndex]; - if (slot.attachment != attachment) return; - - float[] frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - float[][] frameVertices = this.frameVertices; - int vertexCount = frameVertices[0].Length; - - float[] vertices = slot.attachmentVertices; - if (vertices.Length < vertexCount) { - vertices = new float[vertexCount]; - slot.attachmentVertices = vertices; - } - if (vertices.Length != vertexCount) alpha = 1; // Don't mix from uninitialized slot vertices. - slot.attachmentVerticesCount = vertexCount; - - if (time >= frames[frames.Length - 1]) { // Time is after last frame. - float[] lastVertices = frameVertices[frames.Length - 1]; - if (alpha < 1) { - for (int i = 0; i < vertexCount; i++) { - float vertex = vertices[i]; - vertices[i] = vertex + (lastVertices[i] - vertex) * alpha; - } - } else - Array.Copy(lastVertices, 0, vertices, 0, vertexCount); - return; - } - - // Interpolate between the previous frame and the current frame. - int frameIndex = Animation.binarySearch(frames, time); - float frameTime = frames[frameIndex]; - float percent = 1 - (time - frameTime) / (frames[frameIndex - 1] - frameTime); - percent = GetCurvePercent(frameIndex - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); - - float[] prevVertices = frameVertices[frameIndex - 1]; - float[] nextVertices = frameVertices[frameIndex]; - - if (alpha < 1) { - for (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - float vertex = vertices[i]; - vertices[i] = vertex + (prev + (nextVertices[i] - prev) * percent - vertex) * alpha; - } - } else { - for (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - vertices[i] = prev + (nextVertices[i] - prev) * percent; - } - } - } - } - - public class IkConstraintTimeline : CurveTimeline { - private const int PREV_FRAME_TIME = -3; - private const int PREV_FRAME_MIX = -2; - private const int PREV_FRAME_BEND_DIRECTION = -1; - private const int FRAME_MIX = 1; - - internal int ikConstraintIndex; - internal float[] frames; - - public int IkConstraintIndex { get { return ikConstraintIndex; } set { ikConstraintIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, mix, bendDirection, ... - - public IkConstraintTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * 3]; - } - - /** Sets the time, mix and bend direction of the specified keyframe. */ - public void SetFrame (int frameIndex, float time, float mix, int bendDirection) { - frameIndex *= 3; - frames[frameIndex] = time; - frames[frameIndex + 1] = mix; - frames[frameIndex + 2] = bendDirection; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, List firedEvents, float alpha) { - float[] frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - IkConstraint ikConstraint = skeleton.ikConstraints[ikConstraintIndex]; - - if (time >= frames[frames.Length - 3]) { // Time is after last frame. - ikConstraint.mix += (frames[frames.Length - 2] - ikConstraint.mix) * alpha; - ikConstraint.bendDirection = (int)frames[frames.Length - 1]; - return; - } - - // Interpolate between the previous frame and the current frame. - int frameIndex = Animation.binarySearch(frames, time, 3); - float prevFrameMix = frames[frameIndex + PREV_FRAME_MIX]; - float frameTime = frames[frameIndex]; - float percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime); - percent = GetCurvePercent(frameIndex / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); - - float mix = prevFrameMix + (frames[frameIndex + FRAME_MIX] - prevFrameMix) * percent; - ikConstraint.mix += (mix - ikConstraint.mix) * alpha; - ikConstraint.bendDirection = (int)frames[frameIndex + PREV_FRAME_BEND_DIRECTION]; - } - } - - public class FlipXTimeline : Timeline { - internal int boneIndex; - internal float[] frames; - - public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, flip, ... - public int FrameCount { get { return frames.Length >> 1; } } - - public FlipXTimeline (int frameCount) { - frames = new float[frameCount << 1]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, bool flip) { - frameIndex *= 2; - frames[frameIndex] = time; - frames[frameIndex + 1] = flip ? 1 : 0; - } - - public void Apply (Skeleton skeleton, float lastTime, float time, List firedEvents, float alpha) { - float[] frames = this.frames; - if (time < frames[0]) { - if (lastTime > time) Apply(skeleton, lastTime, int.MaxValue, null, 0); - return; - } else if (lastTime > time) // - lastTime = -1; - - int frameIndex = (time >= frames[frames.Length - 2] ? frames.Length : Animation.binarySearch(frames, time, 2)) - 2; - if (frames[frameIndex] < lastTime) return; - - SetFlip(skeleton.bones[boneIndex], frames[frameIndex + 1] != 0); - } - - virtual protected void SetFlip (Bone bone, bool flip) { - bone.flipX = flip; - } - } - - public class FlipYTimeline : FlipXTimeline { - public FlipYTimeline (int frameCount) - : base(frameCount) { - } - - override protected void SetFlip (Bone bone, bool flip) { - bone.flipY = flip; - } - } +namespace Spine2_1_25 +{ + public class Animation + { + internal List timelines; + internal float duration; + internal String name; + + public String Name { get { return name; } } + public List Timelines { get { return timelines; } set { timelines = value; } } + public float Duration { get { return duration; } set { duration = value; } } + + public Animation(String name, List timelines, float duration) + { + if (name == null) throw new ArgumentNullException("name cannot be null."); + if (timelines == null) throw new ArgumentNullException("timelines cannot be null."); + this.name = name; + this.timelines = timelines; + this.duration = duration; + } + + /// Poses the skeleton at the specified time for this animation. + /// The last time the animation was applied. + /// Any triggered events are added. + public void Apply(Skeleton skeleton, float lastTime, float time, bool loop, List events) + { + if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); + + if (loop && duration != 0) + { + time %= duration; + lastTime %= duration; + } + + List timelines = this.timelines; + for (int i = 0, n = timelines.Count; i < n; i++) + timelines[i].Apply(skeleton, lastTime, time, events, 1); + } + + /// Poses the skeleton at the specified time for this animation mixed with the current pose. + /// The last time the animation was applied. + /// Any triggered events are added. + /// The amount of this animation that affects the current pose. + public void Mix(Skeleton skeleton, float lastTime, float time, bool loop, List events, float alpha) + { + if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); + + if (loop && duration != 0) + { + time %= duration; + lastTime %= duration; + } + + List timelines = this.timelines; + for (int i = 0, n = timelines.Count; i < n; i++) + timelines[i].Apply(skeleton, lastTime, time, events, alpha); + } + + /// After the first and before the last entry. + internal static int binarySearch(float[] values, float target, int step) + { + int low = 0; + int high = values.Length / step - 2; + if (high == 0) return step; + int current = (int)((uint)high >> 1); + while (true) + { + if (values[(current + 1) * step] <= target) + low = current + 1; + else + high = current; + if (low == high) return (low + 1) * step; + current = (int)((uint)(low + high) >> 1); + } + } + + /// After the first and before the last entry. + internal static int binarySearch(float[] values, float target) + { + int low = 0; + int high = values.Length - 2; + if (high == 0) return 1; + int current = (int)((uint)high >> 1); + while (true) + { + if (values[(current + 1)] <= target) + low = current + 1; + else + high = current; + if (low == high) return (low + 1); + current = (int)((uint)(low + high) >> 1); + } + } + + internal static int linearSearch(float[] values, float target, int step) + { + for (int i = 0, last = values.Length - step; i <= last; i += step) + if (values[i] > target) return i; + return -1; + } + } + + public interface Timeline + { + /// Sets the value(s) for the specified time. + /// May be null to not collect fired events. + void Apply(Skeleton skeleton, float lastTime, float time, List events, float alpha); + } + + /// Base class for frames that use an interpolation bezier curve. + abstract public class CurveTimeline : Timeline + { + protected const float LINEAR = 0, STEPPED = 1, BEZIER = 2; + protected const int BEZIER_SEGMENTS = 10, BEZIER_SIZE = BEZIER_SEGMENTS * 2 - 1; + + private float[] curves; // type, x, y, ... + public int FrameCount { get { return curves.Length / BEZIER_SIZE + 1; } } + + public CurveTimeline(int frameCount) + { + curves = new float[(frameCount - 1) * BEZIER_SIZE]; + } + + abstract public void Apply(Skeleton skeleton, float lastTime, float time, List firedEvents, float alpha); + + public void SetLinear(int frameIndex) + { + curves[frameIndex * BEZIER_SIZE] = LINEAR; + } + + public void SetStepped(int frameIndex) + { + curves[frameIndex * BEZIER_SIZE] = STEPPED; + } + + /// Sets the control handle positions for an interpolation bezier curve used to transition from this keyframe to the next. + /// cx1 and cx2 are from 0 to 1, representing the percent of time between the two keyframes. cy1 and cy2 are the percent of + /// the difference between the keyframe's values. + public void SetCurve(int frameIndex, float cx1, float cy1, float cx2, float cy2) + { + float subdiv1 = 1f / BEZIER_SEGMENTS, subdiv2 = subdiv1 * subdiv1, subdiv3 = subdiv2 * subdiv1; + float pre1 = 3 * subdiv1, pre2 = 3 * subdiv2, pre4 = 6 * subdiv2, pre5 = 6 * subdiv3; + float tmp1x = -cx1 * 2 + cx2, tmp1y = -cy1 * 2 + cy2, tmp2x = (cx1 - cx2) * 3 + 1, tmp2y = (cy1 - cy2) * 3 + 1; + float dfx = cx1 * pre1 + tmp1x * pre2 + tmp2x * subdiv3, dfy = cy1 * pre1 + tmp1y * pre2 + tmp2y * subdiv3; + float ddfx = tmp1x * pre4 + tmp2x * pre5, ddfy = tmp1y * pre4 + tmp2y * pre5; + float dddfx = tmp2x * pre5, dddfy = tmp2y * pre5; + + int i = frameIndex * BEZIER_SIZE; + float[] curves = this.curves; + curves[i++] = BEZIER; + + float x = dfx, y = dfy; + for (int n = i + BEZIER_SIZE - 1; i < n; i += 2) + { + curves[i] = x; + curves[i + 1] = y; + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + x += dfx; + y += dfy; + } + } + + public float GetCurvePercent(int frameIndex, float percent) + { + float[] curves = this.curves; + int i = frameIndex * BEZIER_SIZE; + float type = curves[i]; + if (type == LINEAR) return percent; + if (type == STEPPED) return 0; + i++; + float x = 0; + for (int start = i, n = i + BEZIER_SIZE - 1; i < n; i += 2) + { + x = curves[i]; + if (x >= percent) + { + float prevX, prevY; + if (i == start) + { + prevX = 0; + prevY = 0; + } + else + { + prevX = curves[i - 2]; + prevY = curves[i - 1]; + } + return prevY + (curves[i + 1] - prevY) * (percent - prevX) / (x - prevX); + } + } + float y = curves[i - 1]; + return y + (1 - y) * (percent - x) / (1 - x); // Last point is 1,1. + } + public float GetCurveType(int frameIndex) + { + return curves[frameIndex * BEZIER_SIZE]; + } + } + + public class RotateTimeline : CurveTimeline + { + protected const int PREV_FRAME_TIME = -2; + protected const int FRAME_VALUE = 1; + + internal int boneIndex; + internal float[] frames; + + public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, value, ... + + public RotateTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount << 1]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, float angle) + { + frameIndex *= 2; + frames[frameIndex] = time; + frames[frameIndex + 1] = angle; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, List firedEvents, float alpha) + { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + Bone bone = skeleton.bones[boneIndex]; + + float amount; + + if (time >= frames[frames.Length - 2]) + { // Time is after last frame. + amount = bone.data.rotation + frames[frames.Length - 1] - bone.rotation; + while (amount > 180) + amount -= 360; + while (amount < -180) + amount += 360; + bone.rotation += amount * alpha; + return; + } + + // Interpolate between the previous frame and the current frame. + int frameIndex = Animation.binarySearch(frames, time, 2); + float prevFrameValue = frames[frameIndex - 1]; + float frameTime = frames[frameIndex]; + float percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime); + percent = GetCurvePercent((frameIndex >> 1) - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); + + amount = frames[frameIndex + FRAME_VALUE] - prevFrameValue; + while (amount > 180) + amount -= 360; + while (amount < -180) + amount += 360; + amount = bone.data.rotation + (prevFrameValue + amount * percent) - bone.rotation; + while (amount > 180) + amount -= 360; + while (amount < -180) + amount += 360; + bone.rotation += amount * alpha; + } + } + + public class TranslateTimeline : CurveTimeline + { + protected const int PREV_FRAME_TIME = -3; + protected const int FRAME_X = 1; + protected const int FRAME_Y = 2; + + internal int boneIndex; + internal float[] frames; + + public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, value, value, ... + + public TranslateTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * 3]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, float x, float y) + { + frameIndex *= 3; + frames[frameIndex] = time; + frames[frameIndex + 1] = x; + frames[frameIndex + 2] = y; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, List firedEvents, float alpha) + { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + Bone bone = skeleton.bones[boneIndex]; + + if (time >= frames[frames.Length - 3]) + { // Time is after last frame. + bone.x += (bone.data.x + frames[frames.Length - 2] - bone.x) * alpha; + bone.y += (bone.data.y + frames[frames.Length - 1] - bone.y) * alpha; + return; + } + + // Interpolate between the previous frame and the current frame. + int frameIndex = Animation.binarySearch(frames, time, 3); + float prevFrameX = frames[frameIndex - 2]; + float prevFrameY = frames[frameIndex - 1]; + float frameTime = frames[frameIndex]; + float percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime); + percent = GetCurvePercent(frameIndex / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); + + bone.x += (bone.data.x + prevFrameX + (frames[frameIndex + FRAME_X] - prevFrameX) * percent - bone.x) * alpha; + bone.y += (bone.data.y + prevFrameY + (frames[frameIndex + FRAME_Y] - prevFrameY) * percent - bone.y) * alpha; + } + } + + public class ScaleTimeline : TranslateTimeline + { + public ScaleTimeline(int frameCount) + : base(frameCount) + { + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, List firedEvents, float alpha) + { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + Bone bone = skeleton.bones[boneIndex]; + if (time >= frames[frames.Length - 3]) + { // Time is after last frame. + bone.scaleX += (bone.data.scaleX * frames[frames.Length - 2] - bone.scaleX) * alpha; + bone.scaleY += (bone.data.scaleY * frames[frames.Length - 1] - bone.scaleY) * alpha; + return; + } + + // Interpolate between the previous frame and the current frame. + int frameIndex = Animation.binarySearch(frames, time, 3); + float prevFrameX = frames[frameIndex - 2]; + float prevFrameY = frames[frameIndex - 1]; + float frameTime = frames[frameIndex]; + float percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime); + percent = GetCurvePercent(frameIndex / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); + + bone.scaleX += (bone.data.scaleX * (prevFrameX + (frames[frameIndex + FRAME_X] - prevFrameX) * percent) - bone.scaleX) * alpha; + bone.scaleY += (bone.data.scaleY * (prevFrameY + (frames[frameIndex + FRAME_Y] - prevFrameY) * percent) - bone.scaleY) * alpha; + } + } + + public class ColorTimeline : CurveTimeline + { + protected const int PREV_FRAME_TIME = -5; + protected const int FRAME_R = 1; + protected const int FRAME_G = 2; + protected const int FRAME_B = 3; + protected const int FRAME_A = 4; + + internal int slotIndex; + internal float[] frames; + + public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, r, g, b, a, ... + + public ColorTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * 5]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, float r, float g, float b, float a) + { + frameIndex *= 5; + frames[frameIndex] = time; + frames[frameIndex + 1] = r; + frames[frameIndex + 2] = g; + frames[frameIndex + 3] = b; + frames[frameIndex + 4] = a; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, List firedEvents, float alpha) + { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + float r, g, b, a; + if (time >= frames[frames.Length - 5]) + { + // Time is after last frame. + int i = frames.Length - 1; + r = frames[i - 3]; + g = frames[i - 2]; + b = frames[i - 1]; + a = frames[i]; + } + else + { + // Interpolate between the previous frame and the current frame. + int frameIndex = Animation.binarySearch(frames, time, 5); + float prevFrameR = frames[frameIndex - 4]; + float prevFrameG = frames[frameIndex - 3]; + float prevFrameB = frames[frameIndex - 2]; + float prevFrameA = frames[frameIndex - 1]; + float frameTime = frames[frameIndex]; + float percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime); + percent = GetCurvePercent(frameIndex / 5 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); + + r = prevFrameR + (frames[frameIndex + FRAME_R] - prevFrameR) * percent; + g = prevFrameG + (frames[frameIndex + FRAME_G] - prevFrameG) * percent; + b = prevFrameB + (frames[frameIndex + FRAME_B] - prevFrameB) * percent; + a = prevFrameA + (frames[frameIndex + FRAME_A] - prevFrameA) * percent; + } + Slot slot = skeleton.slots[slotIndex]; + if (alpha < 1) + { + slot.r += (r - slot.r) * alpha; + slot.g += (g - slot.g) * alpha; + slot.b += (b - slot.b) * alpha; + slot.a += (a - slot.a) * alpha; + } + else + { + slot.r = r; + slot.g = g; + slot.b = b; + slot.a = a; + } + } + } + + public class AttachmentTimeline : Timeline + { + internal int slotIndex; + internal float[] frames; + private String[] attachmentNames; + + public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, ... + public String[] AttachmentNames { get { return attachmentNames; } set { attachmentNames = value; } } + public int FrameCount { get { return frames.Length; } } + + public AttachmentTimeline(int frameCount) + { + frames = new float[frameCount]; + attachmentNames = new String[frameCount]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, String attachmentName) + { + frames[frameIndex] = time; + attachmentNames[frameIndex] = attachmentName; + } + + public void Apply(Skeleton skeleton, float lastTime, float time, List firedEvents, float alpha) + { + float[] frames = this.frames; + if (time < frames[0]) + { + if (lastTime > time) Apply(skeleton, lastTime, int.MaxValue, null, 0); + return; + } + else if (lastTime > time) // + lastTime = -1; + + int frameIndex = (time >= frames[frames.Length - 1] ? frames.Length : Animation.binarySearch(frames, time)) - 1; + if (frames[frameIndex] < lastTime) return; + + String attachmentName = attachmentNames[frameIndex]; + skeleton.slots[slotIndex].Attachment = + attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); + } + } + + public class EventTimeline : Timeline + { + internal float[] frames; + private Event[] events; + + public float[] Frames { get { return frames; } set { frames = value; } } // time, ... + public Event[] Events { get { return events; } set { events = value; } } + public int FrameCount { get { return frames.Length; } } + + public EventTimeline(int frameCount) + { + frames = new float[frameCount]; + events = new Event[frameCount]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, Event e) + { + frames[frameIndex] = time; + events[frameIndex] = e; + } + + /// Fires events for frames > lastTime and <= time. + public void Apply(Skeleton skeleton, float lastTime, float time, List firedEvents, float alpha) + { + if (firedEvents == null) return; + float[] frames = this.frames; + int frameCount = frames.Length; + + if (lastTime > time) + { // Fire events after last time for looped animations. + Apply(skeleton, lastTime, int.MaxValue, firedEvents, alpha); + lastTime = -1f; + } + else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame. + return; + if (time < frames[0]) return; // Time is before first frame. + + int frameIndex; + if (lastTime < frames[0]) + frameIndex = 0; + else + { + frameIndex = Animation.binarySearch(frames, lastTime); + float frame = frames[frameIndex]; + while (frameIndex > 0) + { // Fire multiple events with the same frame. + if (frames[frameIndex - 1] != frame) break; + frameIndex--; + } + } + for (; frameIndex < frameCount && time >= frames[frameIndex]; frameIndex++) + firedEvents.Add(events[frameIndex]); + } + } + + public class DrawOrderTimeline : Timeline + { + internal float[] frames; + private int[][] drawOrders; + + public float[] Frames { get { return frames; } set { frames = value; } } // time, ... + public int[][] DrawOrders { get { return drawOrders; } set { drawOrders = value; } } + public int FrameCount { get { return frames.Length; } } + + public DrawOrderTimeline(int frameCount) + { + frames = new float[frameCount]; + drawOrders = new int[frameCount][]; + } + + /// Sets the time and value of the specified keyframe. + /// May be null to use bind pose draw order. + public void SetFrame(int frameIndex, float time, int[] drawOrder) + { + frames[frameIndex] = time; + drawOrders[frameIndex] = drawOrder; + } + + public void Apply(Skeleton skeleton, float lastTime, float time, List firedEvents, float alpha) + { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + int frameIndex; + if (time >= frames[frames.Length - 1]) // Time is after last frame. + frameIndex = frames.Length - 1; + else + frameIndex = Animation.binarySearch(frames, time) - 1; + + List drawOrder = skeleton.drawOrder; + List slots = skeleton.slots; + int[] drawOrderToSetupIndex = drawOrders[frameIndex]; + if (drawOrderToSetupIndex == null) + { + drawOrder.Clear(); + drawOrder.AddRange(slots); + } + else + { + for (int i = 0, n = drawOrderToSetupIndex.Length; i < n; i++) + drawOrder[i] = slots[drawOrderToSetupIndex[i]]; + } + } + } + + public class FFDTimeline : CurveTimeline + { + internal int slotIndex; + internal float[] frames; + private float[][] frameVertices; + internal Attachment attachment; + + public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, ... + public float[][] Vertices { get { return frameVertices; } set { frameVertices = value; } } + public Attachment Attachment { get { return attachment; } set { attachment = value; } } + + public FFDTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount]; + frameVertices = new float[frameCount][]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, float[] vertices) + { + frames[frameIndex] = time; + frameVertices[frameIndex] = vertices; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, List firedEvents, float alpha) + { + Slot slot = skeleton.slots[slotIndex]; + if (slot.attachment != attachment) return; + + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + float[][] frameVertices = this.frameVertices; + int vertexCount = frameVertices[0].Length; + + float[] vertices = slot.attachmentVertices; + if (vertices.Length < vertexCount) + { + vertices = new float[vertexCount]; + slot.attachmentVertices = vertices; + } + if (vertices.Length != vertexCount) alpha = 1; // Don't mix from uninitialized slot vertices. + slot.attachmentVerticesCount = vertexCount; + + if (time >= frames[frames.Length - 1]) + { // Time is after last frame. + float[] lastVertices = frameVertices[frames.Length - 1]; + if (alpha < 1) + { + for (int i = 0; i < vertexCount; i++) + { + float vertex = vertices[i]; + vertices[i] = vertex + (lastVertices[i] - vertex) * alpha; + } + } + else + Array.Copy(lastVertices, 0, vertices, 0, vertexCount); + return; + } + + // Interpolate between the previous frame and the current frame. + int frameIndex = Animation.binarySearch(frames, time); + float frameTime = frames[frameIndex]; + float percent = 1 - (time - frameTime) / (frames[frameIndex - 1] - frameTime); + percent = GetCurvePercent(frameIndex - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); + + float[] prevVertices = frameVertices[frameIndex - 1]; + float[] nextVertices = frameVertices[frameIndex]; + + if (alpha < 1) + { + for (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i]; + float vertex = vertices[i]; + vertices[i] = vertex + (prev + (nextVertices[i] - prev) * percent - vertex) * alpha; + } + } + else + { + for (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i]; + vertices[i] = prev + (nextVertices[i] - prev) * percent; + } + } + } + } + + public class IkConstraintTimeline : CurveTimeline + { + private const int PREV_FRAME_TIME = -3; + private const int PREV_FRAME_MIX = -2; + private const int PREV_FRAME_BEND_DIRECTION = -1; + private const int FRAME_MIX = 1; + + internal int ikConstraintIndex; + internal float[] frames; + + public int IkConstraintIndex { get { return ikConstraintIndex; } set { ikConstraintIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, mix, bendDirection, ... + + public IkConstraintTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * 3]; + } + + /** Sets the time, mix and bend direction of the specified keyframe. */ + public void SetFrame(int frameIndex, float time, float mix, int bendDirection) + { + frameIndex *= 3; + frames[frameIndex] = time; + frames[frameIndex + 1] = mix; + frames[frameIndex + 2] = bendDirection; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, List firedEvents, float alpha) + { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + IkConstraint ikConstraint = skeleton.ikConstraints[ikConstraintIndex]; + + if (time >= frames[frames.Length - 3]) + { // Time is after last frame. + ikConstraint.mix += (frames[frames.Length - 2] - ikConstraint.mix) * alpha; + ikConstraint.bendDirection = (int)frames[frames.Length - 1]; + return; + } + + // Interpolate between the previous frame and the current frame. + int frameIndex = Animation.binarySearch(frames, time, 3); + float prevFrameMix = frames[frameIndex + PREV_FRAME_MIX]; + float frameTime = frames[frameIndex]; + float percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime); + percent = GetCurvePercent(frameIndex / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); + + float mix = prevFrameMix + (frames[frameIndex + FRAME_MIX] - prevFrameMix) * percent; + ikConstraint.mix += (mix - ikConstraint.mix) * alpha; + ikConstraint.bendDirection = (int)frames[frameIndex + PREV_FRAME_BEND_DIRECTION]; + } + } + + public class FlipXTimeline : Timeline + { + internal int boneIndex; + internal float[] frames; + + public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, flip, ... + public int FrameCount { get { return frames.Length >> 1; } } + + public FlipXTimeline(int frameCount) + { + frames = new float[frameCount << 1]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, bool flip) + { + frameIndex *= 2; + frames[frameIndex] = time; + frames[frameIndex + 1] = flip ? 1 : 0; + } + + public void Apply(Skeleton skeleton, float lastTime, float time, List firedEvents, float alpha) + { + float[] frames = this.frames; + if (time < frames[0]) + { + if (lastTime > time) Apply(skeleton, lastTime, int.MaxValue, null, 0); + return; + } + else if (lastTime > time) // + lastTime = -1; + + int frameIndex = (time >= frames[frames.Length - 2] ? frames.Length : Animation.binarySearch(frames, time, 2)) - 2; + if (frames[frameIndex] < lastTime) return; + + SetFlip(skeleton.bones[boneIndex], frames[frameIndex + 1] != 0); + } + + virtual protected void SetFlip(Bone bone, bool flip) + { + bone.flipX = flip; + } + } + + public class FlipYTimeline : FlipXTimeline + { + public FlipYTimeline(int frameCount) + : base(frameCount) + { + } + + override protected void SetFlip(Bone bone, bool flip) + { + bone.flipY = flip; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/AnimationState.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/AnimationState.cs index 67b8778..76e8081 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/AnimationState.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/AnimationState.cs @@ -32,268 +32,307 @@ using System.Collections.Generic; using System.Text; -namespace Spine2_1_25 { - public class AnimationState { - private AnimationStateData data; - private List tracks = new List(); - private List events = new List(); - private float timeScale = 1; - - public AnimationStateData Data { get { return data; } } - public float TimeScale { get { return timeScale; } set { timeScale = value; } } - - public delegate void StartEndDelegate(AnimationState state, int trackIndex); - public event StartEndDelegate Start; - public event StartEndDelegate End; - - public delegate void EventDelegate(AnimationState state, int trackIndex, Event e); - public event EventDelegate Event; - - public delegate void CompleteDelegate(AnimationState state, int trackIndex, int loopCount); - public event CompleteDelegate Complete; - - public AnimationState (AnimationStateData data) { - if (data == null) throw new ArgumentNullException("data cannot be null."); - this.data = data; - } - - public void Update (float delta) { - delta *= timeScale; - for (int i = 0; i < tracks.Count; i++) { - TrackEntry current = tracks[i]; - if (current == null) continue; - - float trackDelta = delta * current.timeScale; - float time = current.time + trackDelta; - float endTime = current.endTime; - - current.time = time; - if (current.previous != null) { - current.previous.time += trackDelta; - current.mixTime += trackDelta; - } - - // Check if completed the animation or a loop iteration. - if (current.loop ? (current.lastTime % endTime > time % endTime) : (current.lastTime < endTime && time >= endTime)) { - int count = (int)(time / endTime); - current.OnComplete(this, i, count); - if (Complete != null) Complete(this, i, count); - } - - TrackEntry next = current.next; - if (next != null) { - next.time = current.lastTime - next.delay; - if (next.time >= 0) SetCurrent(i, next); - } else { - // End non-looping animation when it reaches its end time and there is no next entry. - if (!current.loop && current.lastTime >= current.endTime) ClearTrack(i); - } - } - } - - public void Apply (Skeleton skeleton) { - List events = this.events; - - for (int i = 0; i < tracks.Count; i++) { - TrackEntry current = tracks[i]; - if (current == null) continue; - - events.Clear(); - - float time = current.time; - bool loop = current.loop; - if (!loop && time > current.endTime) time = current.endTime; - - TrackEntry previous = current.previous; - if (previous == null) { - if (current.mix == 1) - current.animation.Apply(skeleton, current.lastTime, time, loop, events); - else - current.animation.Mix(skeleton, current.lastTime, time, loop, events, current.mix); - } else { - float previousTime = previous.time; - if (!previous.loop && previousTime > previous.endTime) previousTime = previous.endTime; - previous.animation.Apply(skeleton, previousTime, previousTime, previous.loop, null); - - float alpha = current.mixTime / current.mixDuration * current.mix; - if (alpha >= 1) { - alpha = 1; - current.previous = null; - } - current.animation.Mix(skeleton, current.lastTime, time, loop, events, alpha); - } - - for (int ii = 0, nn = events.Count; ii < nn; ii++) { - Event e = events[ii]; - current.OnEvent(this, i, e); - if (Event != null) Event(this, i, e); - } - - current.lastTime = current.time; - } - } - - public void ClearTracks () { - for (int i = 0, n = tracks.Count; i < n; i++) - ClearTrack(i); - tracks.Clear(); - } - - public void ClearTrack (int trackIndex) { - if (trackIndex >= tracks.Count) return; - TrackEntry current = tracks[trackIndex]; - if (current == null) return; - - current.OnEnd(this, trackIndex); - if (End != null) End(this, trackIndex); - - tracks[trackIndex] = null; - } - - private TrackEntry ExpandToIndex (int index) { - if (index < tracks.Count) return tracks[index]; - while (index >= tracks.Count) - tracks.Add(null); - return null; - } - - private void SetCurrent (int index, TrackEntry entry) { - TrackEntry current = ExpandToIndex(index); - if (current != null) { - TrackEntry previous = current.previous; - current.previous = null; - - current.OnEnd(this, index); - if (End != null) End(this, index); - - entry.mixDuration = data.GetMix(current.animation, entry.animation); - if (entry.mixDuration > 0) { - entry.mixTime = 0; - // If a mix is in progress, mix from the closest animation. - if (previous != null && current.mixTime / current.mixDuration < 0.5f) - entry.previous = previous; - else - entry.previous = current; - } - } - - tracks[index] = entry; - - entry.OnStart(this, index); - if (Start != null) Start(this, index); - } - - public TrackEntry SetAnimation (int trackIndex, String animationName, bool loop) { - Animation animation = data.skeletonData.FindAnimation(animationName); - if (animation == null) throw new ArgumentException("Animation not found: " + animationName); - return SetAnimation(trackIndex, animation, loop); - } - - /// Set the current animation. Any queued animations are cleared. - public TrackEntry SetAnimation (int trackIndex, Animation animation, bool loop) { - if (animation == null) throw new ArgumentException("animation cannot be null."); - TrackEntry entry = new TrackEntry(); - entry.animation = animation; - entry.loop = loop; - entry.time = 0; - entry.endTime = animation.Duration; - SetCurrent(trackIndex, entry); - return entry; - } - - public TrackEntry AddAnimation (int trackIndex, String animationName, bool loop, float delay) { - Animation animation = data.skeletonData.FindAnimation(animationName); - if (animation == null) throw new ArgumentException("Animation not found: " + animationName); - return AddAnimation(trackIndex, animation, loop, delay); - } - - /// Adds an animation to be played delay seconds after the current or last queued animation. - /// May be <= 0 to use duration of previous animation minus any mix duration plus the negative delay. - public TrackEntry AddAnimation (int trackIndex, Animation animation, bool loop, float delay) { - if (animation == null) throw new ArgumentException("animation cannot be null."); - TrackEntry entry = new TrackEntry(); - entry.animation = animation; - entry.loop = loop; - entry.time = 0; - entry.endTime = animation.Duration; - - TrackEntry last = ExpandToIndex(trackIndex); - if (last != null) { - while (last.next != null) - last = last.next; - last.next = entry; - } else - tracks[trackIndex] = entry; - - if (delay <= 0) { - if (last != null) - delay += last.endTime - data.GetMix(last.animation, animation); - else - delay = 0; - } - entry.delay = delay; - - return entry; - } - - /// May be null. - public TrackEntry GetCurrent (int trackIndex) { - if (trackIndex >= tracks.Count) return null; - return tracks[trackIndex]; - } - - override public String ToString () { - StringBuilder buffer = new StringBuilder(); - for (int i = 0, n = tracks.Count; i < n; i++) { - TrackEntry entry = tracks[i]; - if (entry == null) continue; - if (buffer.Length > 0) buffer.Append(", "); - buffer.Append(entry.ToString()); - } - if (buffer.Length == 0) return ""; - return buffer.ToString(); - } - } - - public class TrackEntry { - internal TrackEntry next, previous; - internal Animation animation; - internal bool loop; - internal float delay, time, lastTime = -1, endTime, timeScale = 1; - internal float mixTime, mixDuration, mix = 1; - - public Animation Animation { get { return animation; } } - public float Delay { get { return delay; } set { delay = value; } } - public float Time { get { return time; } set { time = value; } } - public float LastTime { get { return lastTime; } set { lastTime = value; } } - public float EndTime { get { return endTime; } set { endTime = value; } } - public float TimeScale { get { return timeScale; } set { timeScale = value; } } - public float Mix { get { return mix; } set { mix = value; } } - public bool Loop { get { return loop; } set { loop = value; } } - - public event AnimationState.StartEndDelegate Start; - public event AnimationState.StartEndDelegate End; - public event AnimationState.EventDelegate Event; - public event AnimationState.CompleteDelegate Complete; - - internal void OnStart (AnimationState state, int index) { - if (Start != null) Start(state, index); - } - - internal void OnEnd (AnimationState state, int index) { - if (End != null) End(state, index); - } - - internal void OnEvent (AnimationState state, int index, Event e) { - if (Event != null) Event(state, index, e); - } - - internal void OnComplete (AnimationState state, int index, int loopCount) { - if (Complete != null) Complete(state, index, loopCount); - } - - override public String ToString () { - return animation == null ? "" : animation.name; - } - } +namespace Spine2_1_25 +{ + public class AnimationState + { + private AnimationStateData data; + private List tracks = new List(); + private List events = new List(); + private float timeScale = 1; + + public AnimationStateData Data { get { return data; } } + public float TimeScale { get { return timeScale; } set { timeScale = value; } } + + public delegate void StartEndDelegate(AnimationState state, int trackIndex); + public event StartEndDelegate Start; + public event StartEndDelegate End; + + public delegate void EventDelegate(AnimationState state, int trackIndex, Event e); + public event EventDelegate Event; + + public delegate void CompleteDelegate(AnimationState state, int trackIndex, int loopCount); + public event CompleteDelegate Complete; + + public AnimationState(AnimationStateData data) + { + if (data == null) throw new ArgumentNullException("data cannot be null."); + this.data = data; + } + + public void Update(float delta) + { + delta *= timeScale; + for (int i = 0; i < tracks.Count; i++) + { + TrackEntry current = tracks[i]; + if (current == null) continue; + + float trackDelta = delta * current.timeScale; + float time = current.time + trackDelta; + float endTime = current.endTime; + + current.time = time; + if (current.previous != null) + { + current.previous.time += trackDelta; + current.mixTime += trackDelta; + } + + // Check if completed the animation or a loop iteration. + if (current.loop ? (current.lastTime % endTime > time % endTime) : (current.lastTime < endTime && time >= endTime)) + { + int count = (int)(time / endTime); + current.OnComplete(this, i, count); + if (Complete != null) Complete(this, i, count); + } + + TrackEntry next = current.next; + if (next != null) + { + next.time = current.lastTime - next.delay; + if (next.time >= 0) SetCurrent(i, next); + } + else + { + // End non-looping animation when it reaches its end time and there is no next entry. + if (!current.loop && current.lastTime >= current.endTime) ClearTrack(i); + } + } + } + + public void Apply(Skeleton skeleton) + { + List events = this.events; + + for (int i = 0; i < tracks.Count; i++) + { + TrackEntry current = tracks[i]; + if (current == null) continue; + + events.Clear(); + + float time = current.time; + bool loop = current.loop; + if (!loop && time > current.endTime) time = current.endTime; + + TrackEntry previous = current.previous; + if (previous == null) + { + if (current.mix == 1) + current.animation.Apply(skeleton, current.lastTime, time, loop, events); + else + current.animation.Mix(skeleton, current.lastTime, time, loop, events, current.mix); + } + else + { + float previousTime = previous.time; + if (!previous.loop && previousTime > previous.endTime) previousTime = previous.endTime; + previous.animation.Apply(skeleton, previousTime, previousTime, previous.loop, null); + + float alpha = current.mixTime / current.mixDuration * current.mix; + if (alpha >= 1) + { + alpha = 1; + current.previous = null; + } + current.animation.Mix(skeleton, current.lastTime, time, loop, events, alpha); + } + + for (int ii = 0, nn = events.Count; ii < nn; ii++) + { + Event e = events[ii]; + current.OnEvent(this, i, e); + if (Event != null) Event(this, i, e); + } + + current.lastTime = current.time; + } + } + + public void ClearTracks() + { + for (int i = 0, n = tracks.Count; i < n; i++) + ClearTrack(i); + tracks.Clear(); + } + + public void ClearTrack(int trackIndex) + { + if (trackIndex >= tracks.Count) return; + TrackEntry current = tracks[trackIndex]; + if (current == null) return; + + current.OnEnd(this, trackIndex); + if (End != null) End(this, trackIndex); + + tracks[trackIndex] = null; + } + + private TrackEntry ExpandToIndex(int index) + { + if (index < tracks.Count) return tracks[index]; + while (index >= tracks.Count) + tracks.Add(null); + return null; + } + + private void SetCurrent(int index, TrackEntry entry) + { + TrackEntry current = ExpandToIndex(index); + if (current != null) + { + TrackEntry previous = current.previous; + current.previous = null; + + current.OnEnd(this, index); + if (End != null) End(this, index); + + entry.mixDuration = data.GetMix(current.animation, entry.animation); + if (entry.mixDuration > 0) + { + entry.mixTime = 0; + // If a mix is in progress, mix from the closest animation. + if (previous != null && current.mixTime / current.mixDuration < 0.5f) + entry.previous = previous; + else + entry.previous = current; + } + } + + tracks[index] = entry; + + entry.OnStart(this, index); + if (Start != null) Start(this, index); + } + + public TrackEntry SetAnimation(int trackIndex, String animationName, bool loop) + { + Animation animation = data.skeletonData.FindAnimation(animationName); + if (animation == null) throw new ArgumentException("Animation not found: " + animationName); + return SetAnimation(trackIndex, animation, loop); + } + + /// Set the current animation. Any queued animations are cleared. + public TrackEntry SetAnimation(int trackIndex, Animation animation, bool loop) + { + if (animation == null) throw new ArgumentException("animation cannot be null."); + TrackEntry entry = new TrackEntry(); + entry.animation = animation; + entry.loop = loop; + entry.time = 0; + entry.endTime = animation.Duration; + SetCurrent(trackIndex, entry); + return entry; + } + + public TrackEntry AddAnimation(int trackIndex, String animationName, bool loop, float delay) + { + Animation animation = data.skeletonData.FindAnimation(animationName); + if (animation == null) throw new ArgumentException("Animation not found: " + animationName); + return AddAnimation(trackIndex, animation, loop, delay); + } + + /// Adds an animation to be played delay seconds after the current or last queued animation. + /// May be <= 0 to use duration of previous animation minus any mix duration plus the negative delay. + public TrackEntry AddAnimation(int trackIndex, Animation animation, bool loop, float delay) + { + if (animation == null) throw new ArgumentException("animation cannot be null."); + TrackEntry entry = new TrackEntry(); + entry.animation = animation; + entry.loop = loop; + entry.time = 0; + entry.endTime = animation.Duration; + + TrackEntry last = ExpandToIndex(trackIndex); + if (last != null) + { + while (last.next != null) + last = last.next; + last.next = entry; + } + else + tracks[trackIndex] = entry; + + if (delay <= 0) + { + if (last != null) + delay += last.endTime - data.GetMix(last.animation, animation); + else + delay = 0; + } + entry.delay = delay; + + return entry; + } + + /// May be null. + public TrackEntry GetCurrent(int trackIndex) + { + if (trackIndex >= tracks.Count) return null; + return tracks[trackIndex]; + } + + override public String ToString() + { + StringBuilder buffer = new StringBuilder(); + for (int i = 0, n = tracks.Count; i < n; i++) + { + TrackEntry entry = tracks[i]; + if (entry == null) continue; + if (buffer.Length > 0) buffer.Append(", "); + buffer.Append(entry.ToString()); + } + if (buffer.Length == 0) return ""; + return buffer.ToString(); + } + } + + public class TrackEntry + { + internal TrackEntry next, previous; + internal Animation animation; + internal bool loop; + internal float delay, time, lastTime = -1, endTime, timeScale = 1; + internal float mixTime, mixDuration, mix = 1; + + public Animation Animation { get { return animation; } } + public float Delay { get { return delay; } set { delay = value; } } + public float Time { get { return time; } set { time = value; } } + public float LastTime { get { return lastTime; } set { lastTime = value; } } + public float EndTime { get { return endTime; } set { endTime = value; } } + public float TimeScale { get { return timeScale; } set { timeScale = value; } } + public float Mix { get { return mix; } set { mix = value; } } + public bool Loop { get { return loop; } set { loop = value; } } + + public event AnimationState.StartEndDelegate Start; + public event AnimationState.StartEndDelegate End; + public event AnimationState.EventDelegate Event; + public event AnimationState.CompleteDelegate Complete; + + internal void OnStart(AnimationState state, int index) + { + if (Start != null) Start(state, index); + } + + internal void OnEnd(AnimationState state, int index) + { + if (End != null) End(state, index); + } + + internal void OnEvent(AnimationState state, int index, Event e) + { + if (Event != null) Event(state, index, e); + } + + internal void OnComplete(AnimationState state, int index, int loopCount) + { + if (Complete != null) Complete(state, index, loopCount); + } + + override public String ToString() + { + return animation == null ? "" : animation.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/AnimationStateData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/AnimationStateData.cs index 3871d02..6c699a8 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/AnimationStateData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/AnimationStateData.cs @@ -31,40 +31,46 @@ using System; using System.Collections.Generic; -namespace Spine2_1_25 { - public class AnimationStateData { - internal SkeletonData skeletonData; - private Dictionary, float> animationToMixTime = new Dictionary, float>(); - internal float defaultMix; +namespace Spine2_1_25 +{ + public class AnimationStateData + { + internal SkeletonData skeletonData; + private Dictionary, float> animationToMixTime = new Dictionary, float>(); + internal float defaultMix; - public SkeletonData SkeletonData { get { return skeletonData; } } - public float DefaultMix { get { return defaultMix; } set { defaultMix = value; } } + public SkeletonData SkeletonData { get { return skeletonData; } } + public float DefaultMix { get { return defaultMix; } set { defaultMix = value; } } - public AnimationStateData (SkeletonData skeletonData) { - this.skeletonData = skeletonData; - } + public AnimationStateData(SkeletonData skeletonData) + { + this.skeletonData = skeletonData; + } - public void SetMix (String fromName, String toName, float duration) { - Animation from = skeletonData.FindAnimation(fromName); - if (from == null) throw new ArgumentException("Animation not found: " + fromName); - Animation to = skeletonData.FindAnimation(toName); - if (to == null) throw new ArgumentException("Animation not found: " + toName); - SetMix(from, to, duration); - } + public void SetMix(String fromName, String toName, float duration) + { + Animation from = skeletonData.FindAnimation(fromName); + if (from == null) throw new ArgumentException("Animation not found: " + fromName); + Animation to = skeletonData.FindAnimation(toName); + if (to == null) throw new ArgumentException("Animation not found: " + toName); + SetMix(from, to, duration); + } - public void SetMix (Animation from, Animation to, float duration) { - if (from == null) throw new ArgumentNullException("from cannot be null."); - if (to == null) throw new ArgumentNullException("to cannot be null."); - KeyValuePair key = new KeyValuePair(from, to); - animationToMixTime.Remove(key); - animationToMixTime.Add(key, duration); - } + public void SetMix(Animation from, Animation to, float duration) + { + if (from == null) throw new ArgumentNullException("from cannot be null."); + if (to == null) throw new ArgumentNullException("to cannot be null."); + KeyValuePair key = new KeyValuePair(from, to); + animationToMixTime.Remove(key); + animationToMixTime.Add(key, duration); + } - public float GetMix (Animation from, Animation to) { - KeyValuePair key = new KeyValuePair(from, to); - float duration; - if (animationToMixTime.TryGetValue(key, out duration)) return duration; - return defaultMix; - } - } + public float GetMix(Animation from, Animation to) + { + KeyValuePair key = new KeyValuePair(from, to); + float duration; + if (animationToMixTime.TryGetValue(key, out duration)) return duration; + return defaultMix; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Atlas.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Atlas.cs index 808ff6d..775a2e0 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Atlas.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Atlas.cs @@ -31,18 +31,19 @@ using System; using System.Collections.Generic; using System.IO; -using System.Reflection; #if WINDOWS_STOREAPP using System.Threading.Tasks; using Windows.Storage; #endif -namespace Spine2_1_25 { - public class Atlas { - List pages = new List(); - List regions = new List(); - TextureLoader textureLoader; +namespace Spine2_1_25 +{ + public class Atlas + { + List pages = new List(); + List regions = new List(); + TextureLoader textureLoader; #if WINDOWS_STOREAPP private async Task ReadFile(string path, TextureLoader textureLoader) { @@ -61,228 +62,259 @@ public Atlas(String path, TextureLoader textureLoader) { this.ReadFile(path, textureLoader).Wait(); } #else - public Atlas (String path, TextureLoader textureLoader) { + public Atlas(String path, TextureLoader textureLoader) + { #if WINDOWS_PHONE Stream stream = Microsoft.Xna.Framework.TitleContainer.OpenStream(path); using (StreamReader reader = new StreamReader(stream)) { #else - using (StreamReader reader = new StreamReader(path)) { + using (StreamReader reader = new StreamReader(path)) + { #endif - try { - Load(reader, Path.GetDirectoryName(path), textureLoader); - } catch (Exception ex) { - throw new Exception("Error reading atlas file: " + path, ex); - } - } - } + try + { + Load(reader, Path.GetDirectoryName(path), textureLoader); + } + catch (Exception ex) + { + throw new Exception("Error reading atlas file: " + path, ex); + } + } + } #endif - public Atlas (TextReader reader, String dir, TextureLoader textureLoader) { - Load(reader, dir, textureLoader); - } - - public Atlas (List pages, List regions) { - this.pages = pages; - this.regions = regions; - this.textureLoader = null; - } - - private void Load (TextReader reader, String imagesDir, TextureLoader textureLoader) { - if (textureLoader == null) throw new ArgumentNullException("textureLoader cannot be null."); - this.textureLoader = textureLoader; - - String[] tuple = new String[4]; - AtlasPage page = null; - while (true) { - String line = reader.ReadLine(); - if (line == null) break; - if (line.Trim().Length == 0) - page = null; - else if (page == null) { - page = new AtlasPage(); - page.name = line; - - if (readTuple(reader, tuple) == 2) { // size is only optional for an atlas packed with an old TexturePacker. - page.width = int.Parse(tuple[0]); - page.height = int.Parse(tuple[1]); - readTuple(reader, tuple); - } - page.format = (Format)Enum.Parse(typeof(Format), tuple[0], false); - - readTuple(reader, tuple); - page.minFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[0], false); - page.magFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[1], false); - - String direction = readValue(reader); - page.uWrap = TextureWrap.ClampToEdge; - page.vWrap = TextureWrap.ClampToEdge; - if (direction == "x") - page.uWrap = TextureWrap.Repeat; - else if (direction == "y") - page.vWrap = TextureWrap.Repeat; - else if (direction == "xy") - page.uWrap = page.vWrap = TextureWrap.Repeat; - - textureLoader.Load(page, Path.Combine(imagesDir, line)); - - pages.Add(page); - - } else { - AtlasRegion region = new AtlasRegion(); - region.name = line; - region.page = page; - - region.rotate = Boolean.Parse(readValue(reader)); - - readTuple(reader, tuple); - int x = int.Parse(tuple[0]); - int y = int.Parse(tuple[1]); - - readTuple(reader, tuple); - int width = int.Parse(tuple[0]); - int height = int.Parse(tuple[1]); - - region.u = x / (float)page.width; - region.v = y / (float)page.height; - if (region.rotate) { - region.u2 = (x + height) / (float)page.width; - region.v2 = (y + width) / (float)page.height; - } else { - region.u2 = (x + width) / (float)page.width; - region.v2 = (y + height) / (float)page.height; - } - region.x = x; - region.y = y; - region.width = Math.Abs(width); - region.height = Math.Abs(height); - - if (readTuple(reader, tuple) == 4) { // split is optional - region.splits = new int[] {int.Parse(tuple[0]), int.Parse(tuple[1]), - int.Parse(tuple[2]), int.Parse(tuple[3])}; - - if (readTuple(reader, tuple) == 4) { // pad is optional, but only present with splits - region.pads = new int[] {int.Parse(tuple[0]), int.Parse(tuple[1]), - int.Parse(tuple[2]), int.Parse(tuple[3])}; - - readTuple(reader, tuple); - } - } - - region.originalWidth = int.Parse(tuple[0]); - region.originalHeight = int.Parse(tuple[1]); - - readTuple(reader, tuple); - region.offsetX = int.Parse(tuple[0]); - region.offsetY = int.Parse(tuple[1]); - - region.index = int.Parse(readValue(reader)); - - regions.Add(region); - } - } - } - - static String readValue (TextReader reader) { - String line = reader.ReadLine(); - int colon = line.IndexOf(':'); - if (colon == -1) throw new Exception("Invalid line: " + line); - return line.Substring(colon + 1).Trim(); - } - - /// Returns the number of tuple values read (1, 2 or 4). - static int readTuple (TextReader reader, String[] tuple) { - String line = reader.ReadLine(); - int colon = line.IndexOf(':'); - if (colon == -1) throw new Exception("Invalid line: " + line); - int i = 0, lastMatch = colon + 1; - for (; i < 3; i++) { - int comma = line.IndexOf(',', lastMatch); - if (comma == -1) break; - tuple[i] = line.Substring(lastMatch, comma - lastMatch).Trim(); - lastMatch = comma + 1; - } - tuple[i] = line.Substring(lastMatch).Trim(); - return i + 1; - } - - public void FlipV () { - for (int i = 0, n = regions.Count; i < n; i++) { - AtlasRegion region = regions[i]; - region.v = 1 - region.v; - region.v2 = 1 - region.v2; - } - } - - /// 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. - /// The region, or null. - public AtlasRegion FindRegion (String name) { - for (int i = 0, n = regions.Count; i < n; i++) - if (regions[i].name == name) return regions[i]; - return null; - } - - public void Dispose () { - if (textureLoader == null) return; - for (int i = 0, n = pages.Count; i < n; i++) - textureLoader.Unload(pages[i].rendererObject); - } - } - - public enum Format { - Alpha, - Intensity, - LuminanceAlpha, - RGB565, - RGBA4444, - RGB888, - RGBA8888 - } - - public enum TextureFilter { - Nearest, - Linear, - MipMap, - MipMapNearestNearest, - MipMapLinearNearest, - MipMapNearestLinear, - MipMapLinearLinear - } - - public enum TextureWrap { - MirroredRepeat, - ClampToEdge, - Repeat - } - - public class AtlasPage { - public String name; - public Format format; - public TextureFilter minFilter; - public TextureFilter magFilter; - public TextureWrap uWrap; - public TextureWrap vWrap; - public Object rendererObject; - public int width, height; - } - - public class AtlasRegion { - public AtlasPage page; - public String name; - public int x, y, width, height; - public float u, v, u2, v2; - public float offsetX, offsetY; - public int originalWidth, originalHeight; - public int index; - public bool rotate; - public int[] splits; - public int[] pads; - } - - public interface TextureLoader { - void Load (AtlasPage page, String path); - void Unload (Object texture); - } + public Atlas(TextReader reader, String dir, TextureLoader textureLoader) + { + Load(reader, dir, textureLoader); + } + + public Atlas(List pages, List regions) + { + this.pages = pages; + this.regions = regions; + this.textureLoader = null; + } + + private void Load(TextReader reader, String imagesDir, TextureLoader textureLoader) + { + if (textureLoader == null) throw new ArgumentNullException("textureLoader cannot be null."); + this.textureLoader = textureLoader; + + String[] tuple = new String[4]; + AtlasPage page = null; + while (true) + { + String line = reader.ReadLine(); + if (line == null) break; + if (line.Trim().Length == 0) + page = null; + else if (page == null) + { + page = new AtlasPage(); + page.name = line; + + if (readTuple(reader, tuple) == 2) + { // size is only optional for an atlas packed with an old TexturePacker. + page.width = int.Parse(tuple[0]); + page.height = int.Parse(tuple[1]); + readTuple(reader, tuple); + } + page.format = (Format)Enum.Parse(typeof(Format), tuple[0], false); + + readTuple(reader, tuple); + page.minFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[0], false); + page.magFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[1], false); + + String direction = readValue(reader); + page.uWrap = TextureWrap.ClampToEdge; + page.vWrap = TextureWrap.ClampToEdge; + if (direction == "x") + page.uWrap = TextureWrap.Repeat; + else if (direction == "y") + page.vWrap = TextureWrap.Repeat; + else if (direction == "xy") + page.uWrap = page.vWrap = TextureWrap.Repeat; + + textureLoader.Load(page, Path.Combine(imagesDir, line)); + + pages.Add(page); + + } + else + { + AtlasRegion region = new AtlasRegion(); + region.name = line; + region.page = page; + + region.rotate = Boolean.Parse(readValue(reader)); + + readTuple(reader, tuple); + int x = int.Parse(tuple[0]); + int y = int.Parse(tuple[1]); + + readTuple(reader, tuple); + int width = int.Parse(tuple[0]); + int height = int.Parse(tuple[1]); + + region.u = x / (float)page.width; + region.v = y / (float)page.height; + if (region.rotate) + { + region.u2 = (x + height) / (float)page.width; + region.v2 = (y + width) / (float)page.height; + } + else + { + region.u2 = (x + width) / (float)page.width; + region.v2 = (y + height) / (float)page.height; + } + region.x = x; + region.y = y; + region.width = Math.Abs(width); + region.height = Math.Abs(height); + + if (readTuple(reader, tuple) == 4) + { // split is optional + region.splits = new int[] {int.Parse(tuple[0]), int.Parse(tuple[1]), + int.Parse(tuple[2]), int.Parse(tuple[3])}; + + if (readTuple(reader, tuple) == 4) + { // pad is optional, but only present with splits + region.pads = new int[] {int.Parse(tuple[0]), int.Parse(tuple[1]), + int.Parse(tuple[2]), int.Parse(tuple[3])}; + + readTuple(reader, tuple); + } + } + + region.originalWidth = int.Parse(tuple[0]); + region.originalHeight = int.Parse(tuple[1]); + + readTuple(reader, tuple); + region.offsetX = int.Parse(tuple[0]); + region.offsetY = int.Parse(tuple[1]); + + region.index = int.Parse(readValue(reader)); + + regions.Add(region); + } + } + } + + static String readValue(TextReader reader) + { + String line = reader.ReadLine(); + int colon = line.IndexOf(':'); + if (colon == -1) throw new Exception("Invalid line: " + line); + return line.Substring(colon + 1).Trim(); + } + + /// Returns the number of tuple values read (1, 2 or 4). + static int readTuple(TextReader reader, String[] tuple) + { + String line = reader.ReadLine(); + int colon = line.IndexOf(':'); + if (colon == -1) throw new Exception("Invalid line: " + line); + int i = 0, lastMatch = colon + 1; + for (; i < 3; i++) + { + int comma = line.IndexOf(',', lastMatch); + if (comma == -1) break; + tuple[i] = line.Substring(lastMatch, comma - lastMatch).Trim(); + lastMatch = comma + 1; + } + tuple[i] = line.Substring(lastMatch).Trim(); + return i + 1; + } + + public void FlipV() + { + for (int i = 0, n = regions.Count; i < n; i++) + { + AtlasRegion region = regions[i]; + region.v = 1 - region.v; + region.v2 = 1 - region.v2; + } + } + + /// 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. + /// The region, or null. + public AtlasRegion FindRegion(String name) + { + for (int i = 0, n = regions.Count; i < n; i++) + if (regions[i].name == name) return regions[i]; + return null; + } + + public void Dispose() + { + if (textureLoader == null) return; + for (int i = 0, n = pages.Count; i < n; i++) + textureLoader.Unload(pages[i].rendererObject); + } + } + + public enum Format + { + Alpha, + Intensity, + LuminanceAlpha, + RGB565, + RGBA4444, + RGB888, + RGBA8888 + } + + public enum TextureFilter + { + Nearest, + Linear, + MipMap, + MipMapNearestNearest, + MipMapLinearNearest, + MipMapNearestLinear, + MipMapLinearLinear + } + + public enum TextureWrap + { + MirroredRepeat, + ClampToEdge, + Repeat + } + + public class AtlasPage + { + public String name; + public Format format; + public TextureFilter minFilter; + public TextureFilter magFilter; + public TextureWrap uWrap; + public TextureWrap vWrap; + public Object rendererObject; + public int width, height; + } + + public class AtlasRegion + { + public AtlasPage page; + public String name; + public int x, y, width, height; + public float u, v, u2, v2; + public float offsetX, offsetY; + public int originalWidth, originalHeight; + public int index; + public bool rotate; + public int[] splits; + public int[] pads; + } + + public interface TextureLoader + { + void Load(AtlasPage page, String path); + void Unload(Object texture); + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Attachments/AtlasAttachmentLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Attachments/AtlasAttachmentLoader.cs index 3cc485f..8f637eb 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Attachments/AtlasAttachmentLoader.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Attachments/AtlasAttachmentLoader.cs @@ -30,82 +30,91 @@ using System; -namespace Spine2_1_25 { - public class AtlasAttachmentLoader : AttachmentLoader { - private Atlas[] atlasArray; +namespace Spine2_1_25 +{ + public class AtlasAttachmentLoader : AttachmentLoader + { + private Atlas[] atlasArray; - public AtlasAttachmentLoader (params Atlas[] atlasArray) { - if (atlasArray == null) throw new ArgumentNullException("atlas array cannot be null."); - this.atlasArray = atlasArray; - } + public AtlasAttachmentLoader(params Atlas[] atlasArray) + { + if (atlasArray == null) throw new ArgumentNullException("atlas array cannot be null."); + this.atlasArray = atlasArray; + } - public RegionAttachment NewRegionAttachment (Skin skin, String name, String path) { - AtlasRegion region = FindRegion(path); - if (region == null) throw new Exception("Region not found in atlas: " + path + " (region attachment: " + name + ")"); - RegionAttachment attachment = new RegionAttachment(name); - attachment.RendererObject = region; - attachment.SetUVs(region.u, region.v, region.u2, region.v2, region.rotate); - attachment.regionOffsetX = region.offsetX; - attachment.regionOffsetY = region.offsetY; - attachment.regionWidth = region.width; - attachment.regionHeight = region.height; - attachment.regionOriginalWidth = region.originalWidth; - attachment.regionOriginalHeight = region.originalHeight; - return attachment; - } + public RegionAttachment NewRegionAttachment(Skin skin, String name, String path) + { + AtlasRegion region = FindRegion(path); + if (region == null) throw new Exception("Region not found in atlas: " + path + " (region attachment: " + name + ")"); + RegionAttachment attachment = new RegionAttachment(name); + attachment.RendererObject = region; + attachment.SetUVs(region.u, region.v, region.u2, region.v2, region.rotate); + attachment.regionOffsetX = region.offsetX; + attachment.regionOffsetY = region.offsetY; + attachment.regionWidth = region.width; + attachment.regionHeight = region.height; + attachment.regionOriginalWidth = region.originalWidth; + attachment.regionOriginalHeight = region.originalHeight; + return attachment; + } - public MeshAttachment NewMeshAttachment (Skin skin, String name, String path) { - AtlasRegion region = FindRegion(path); - if (region == null) throw new Exception("Region not found in atlas: " + path + " (mesh attachment: " + name + ")"); - MeshAttachment attachment = new MeshAttachment(name); - attachment.RendererObject = region; - attachment.RegionU = region.u; - attachment.RegionV = region.v; - attachment.RegionU2 = region.u2; - attachment.RegionV2 = region.v2; - attachment.RegionRotate = region.rotate; - attachment.regionOffsetX = region.offsetX; - attachment.regionOffsetY = region.offsetY; - attachment.regionWidth = region.width; - attachment.regionHeight = region.height; - attachment.regionOriginalWidth = region.originalWidth; - attachment.regionOriginalHeight = region.originalHeight; - return attachment; - } + public MeshAttachment NewMeshAttachment(Skin skin, String name, String path) + { + AtlasRegion region = FindRegion(path); + if (region == null) throw new Exception("Region not found in atlas: " + path + " (mesh attachment: " + name + ")"); + MeshAttachment attachment = new MeshAttachment(name); + attachment.RendererObject = region; + attachment.RegionU = region.u; + attachment.RegionV = region.v; + attachment.RegionU2 = region.u2; + attachment.RegionV2 = region.v2; + attachment.RegionRotate = region.rotate; + attachment.regionOffsetX = region.offsetX; + attachment.regionOffsetY = region.offsetY; + attachment.regionWidth = region.width; + attachment.regionHeight = region.height; + attachment.regionOriginalWidth = region.originalWidth; + attachment.regionOriginalHeight = region.originalHeight; + return attachment; + } - public SkinnedMeshAttachment NewSkinnedMeshAttachment (Skin skin, String name, String path) { - AtlasRegion region = FindRegion(path); - if (region == null) throw new Exception("Region not found in atlas: " + path + " (skinned mesh attachment: " + name + ")"); - SkinnedMeshAttachment attachment = new SkinnedMeshAttachment(name); - attachment.RendererObject = region; - attachment.RegionU = region.u; - attachment.RegionV = region.v; - attachment.RegionU2 = region.u2; - attachment.RegionV2 = region.v2; - attachment.RegionRotate = region.rotate; - attachment.regionOffsetX = region.offsetX; - attachment.regionOffsetY = region.offsetY; - attachment.regionWidth = region.width; - attachment.regionHeight = region.height; - attachment.regionOriginalWidth = region.originalWidth; - attachment.regionOriginalHeight = region.originalHeight; - return attachment; - } + public SkinnedMeshAttachment NewSkinnedMeshAttachment(Skin skin, String name, String path) + { + AtlasRegion region = FindRegion(path); + if (region == null) throw new Exception("Region not found in atlas: " + path + " (skinned mesh attachment: " + name + ")"); + SkinnedMeshAttachment attachment = new SkinnedMeshAttachment(name); + attachment.RendererObject = region; + attachment.RegionU = region.u; + attachment.RegionV = region.v; + attachment.RegionU2 = region.u2; + attachment.RegionV2 = region.v2; + attachment.RegionRotate = region.rotate; + attachment.regionOffsetX = region.offsetX; + attachment.regionOffsetY = region.offsetY; + attachment.regionWidth = region.width; + attachment.regionHeight = region.height; + attachment.regionOriginalWidth = region.originalWidth; + attachment.regionOriginalHeight = region.originalHeight; + return attachment; + } - public BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, String name) { - return new BoundingBoxAttachment(name); - } + public BoundingBoxAttachment NewBoundingBoxAttachment(Skin skin, String name) + { + return new BoundingBoxAttachment(name); + } - public AtlasRegion FindRegion(string name) { - AtlasRegion region; + public AtlasRegion FindRegion(string name) + { + AtlasRegion region; - for (int i = 0; i < atlasArray.Length; i++) { - region = atlasArray[i].FindRegion(name); - if (region != null) - return region; - } + for (int i = 0; i < atlasArray.Length; i++) + { + region = atlasArray[i].FindRegion(name); + if (region != null) + return region; + } - return null; - } - } + return null; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Attachments/Attachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Attachments/Attachment.cs index 41eee7f..4832438 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Attachments/Attachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Attachments/Attachment.cs @@ -30,17 +30,21 @@ using System; -namespace Spine2_1_25 { - abstract public class Attachment { - public String Name { get; private set; } +namespace Spine2_1_25 +{ + abstract public class Attachment + { + public String Name { get; private set; } - public Attachment (String name) { - if (name == null) throw new ArgumentNullException("name cannot be null."); - Name = name; - } + public Attachment(String name) + { + if (name == null) throw new ArgumentNullException("name cannot be null."); + Name = name; + } - override public String ToString () { - return Name; - } - } + override public String ToString() + { + return Name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Attachments/AttachmentLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Attachments/AttachmentLoader.cs index 512416a..c9fa676 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Attachments/AttachmentLoader.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Attachments/AttachmentLoader.cs @@ -30,18 +30,20 @@ using System; -namespace Spine2_1_25 { - public interface AttachmentLoader { - /// May be null to not load any attachment. - RegionAttachment NewRegionAttachment (Skin skin, String name, String path); +namespace Spine2_1_25 +{ + public interface AttachmentLoader + { + /// May be null to not load any attachment. + RegionAttachment NewRegionAttachment(Skin skin, String name, String path); - /// May be null to not load any attachment. - MeshAttachment NewMeshAttachment (Skin skin, String name, String path); + /// May be null to not load any attachment. + MeshAttachment NewMeshAttachment(Skin skin, String name, String path); - /// May be null to not load any attachment. - SkinnedMeshAttachment NewSkinnedMeshAttachment (Skin skin, String name, String path); + /// May be null to not load any attachment. + SkinnedMeshAttachment NewSkinnedMeshAttachment(Skin skin, String name, String path); - /// May be null to not load any attachment. - BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, String name); - } + /// May be null to not load any attachment. + BoundingBoxAttachment NewBoundingBoxAttachment(Skin skin, String name); + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Attachments/AttachmentType.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Attachments/AttachmentType.cs index d85b910..eb79b42 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Attachments/AttachmentType.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Attachments/AttachmentType.cs @@ -28,8 +28,10 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine2_1_25 { - public enum AttachmentType { - region, boundingbox, mesh, skinnedmesh - } +namespace Spine2_1_25 +{ + public enum AttachmentType + { + region, boundingbox, mesh, skinnedmesh + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Attachments/BoundingBoxAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Attachments/BoundingBoxAttachment.cs index 4930044..dbb554b 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Attachments/BoundingBoxAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Attachments/BoundingBoxAttachment.cs @@ -28,33 +28,36 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; +namespace Spine2_1_25 +{ + /// Attachment that has a polygon for bounds checking. + public class BoundingBoxAttachment : Attachment + { + internal float[] vertices; -namespace Spine2_1_25 { - /// Attachment that has a polygon for bounds checking. - public class BoundingBoxAttachment : Attachment { - internal float[] vertices; + public float[] Vertices { get { return vertices; } set { vertices = value; } } - public float[] Vertices { get { return vertices; } set { vertices = value; } } + public BoundingBoxAttachment(string name) + : base(name) + { + } - public BoundingBoxAttachment (string name) - : base(name) { - } - - /// Must have at least the same length as this attachment's vertices. - public void ComputeWorldVertices (Bone bone, float[] worldVertices) { - float x = bone.skeleton.x + bone.worldX, y = bone.skeleton.y + bone.worldY; - float m00 = bone.m00; - float m01 = bone.m01; - float m10 = bone.m10; - float m11 = bone.m11; - float[] vertices = this.vertices; - for (int i = 0, n = vertices.Length; i < n; i += 2) { - float px = vertices[i]; - float py = vertices[i + 1]; - worldVertices[i] = px * m00 + py * m01 + x; - worldVertices[i + 1] = px * m10 + py * m11 + y; - } - } - } + /// Must have at least the same length as this attachment's vertices. + public void ComputeWorldVertices(Bone bone, float[] worldVertices) + { + float x = bone.skeleton.x + bone.worldX, y = bone.skeleton.y + bone.worldY; + float m00 = bone.m00; + float m01 = bone.m01; + float m10 = bone.m10; + float m11 = bone.m11; + float[] vertices = this.vertices; + for (int i = 0, n = vertices.Length; i < n; i += 2) + { + float px = vertices[i]; + float py = vertices[i + 1]; + worldVertices[i] = px * m00 + py * m01 + x; + worldVertices[i + 1] = px * m10 + py * m11 + y; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Attachments/MeshAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Attachments/MeshAttachment.cs index 594e4a2..7ae5112 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Attachments/MeshAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Attachments/MeshAttachment.cs @@ -30,79 +30,90 @@ using System; -namespace Spine2_1_25 { - /// Attachment that displays a texture region. - public class MeshAttachment : Attachment { - internal float[] vertices, uvs, regionUVs; - internal int[] triangles; - internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; - internal float r = 1, g = 1, b = 1, a = 1; +namespace Spine2_1_25 +{ + /// Attachment that displays a texture region. + public class MeshAttachment : Attachment + { + internal float[] vertices, uvs, regionUVs; + internal int[] triangles; + internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; + internal float r = 1, g = 1, b = 1, a = 1; - public int HullLength { get; set; } - public float[] Vertices { get { return vertices; } set { vertices = value; } } - public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } } - public float[] UVs { get { return uvs; } set { uvs = value; } } - public int[] Triangles { get { return triangles; } set { triangles = value; } } + public int HullLength { get; set; } + public float[] Vertices { get { return vertices; } set { vertices = value; } } + public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } } + public float[] UVs { get { return uvs; } set { uvs = value; } } + public int[] Triangles { get { return triangles; } set { triangles = value; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } - public String Path { get; set; } - public Object RendererObject { get; set; } - public float RegionU { get; set; } - public float RegionV { get; set; } - public float RegionU2 { get; set; } - public float RegionV2 { get; set; } - public bool RegionRotate { get; set; } - public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } - public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. - public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } - public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. - public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } - public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. + public String Path { get; set; } + public Object RendererObject { get; set; } + public float RegionU { get; set; } + public float RegionV { get; set; } + public float RegionU2 { get; set; } + public float RegionV2 { get; set; } + public bool RegionRotate { get; set; } + public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } + public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. + public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } + public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. + public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } + public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. - // Nonessential. - public int[] Edges { get; set; } - public float Width { get; set; } - public float Height { get; set; } + // Nonessential. + public int[] Edges { get; set; } + public float Width { get; set; } + public float Height { get; set; } - public MeshAttachment (string name) - : base(name) { - } + public MeshAttachment(string name) + : base(name) + { + } - public void UpdateUVs () { - float u = RegionU, v = RegionV, width = RegionU2 - RegionU, height = RegionV2 - RegionV; - float[] regionUVs = this.regionUVs; - if (this.uvs == null || this.uvs.Length != regionUVs.Length) this.uvs = new float[regionUVs.Length]; - float[] uvs = this.uvs; - if (RegionRotate) { - for (int i = 0, n = uvs.Length; i < n; i += 2) { - uvs[i] = u + regionUVs[i + 1] * width; - uvs[i + 1] = v + height - regionUVs[i] * height; - } - } else { - for (int i = 0, n = uvs.Length; i < n; i += 2) { - uvs[i] = u + regionUVs[i] * width; - uvs[i + 1] = v + regionUVs[i + 1] * height; - } - } - } + public void UpdateUVs() + { + float u = RegionU, v = RegionV, width = RegionU2 - RegionU, height = RegionV2 - RegionV; + float[] regionUVs = this.regionUVs; + if (this.uvs == null || this.uvs.Length != regionUVs.Length) this.uvs = new float[regionUVs.Length]; + float[] uvs = this.uvs; + if (RegionRotate) + { + for (int i = 0, n = uvs.Length; i < n; i += 2) + { + uvs[i] = u + regionUVs[i + 1] * width; + uvs[i + 1] = v + height - regionUVs[i] * height; + } + } + else + { + for (int i = 0, n = uvs.Length; i < n; i += 2) + { + uvs[i] = u + regionUVs[i] * width; + uvs[i + 1] = v + regionUVs[i + 1] * height; + } + } + } - public void ComputeWorldVertices (Slot slot, float[] worldVertices) { - Bone bone = slot.bone; - float x = bone.skeleton.x + bone.worldX, y = bone.skeleton.y + bone.worldY; - float m00 = bone.m00, m01 = bone.m01, m10 = bone.m10, m11 = bone.m11; - float[] vertices = this.vertices; - int verticesCount = vertices.Length; - if (slot.attachmentVerticesCount == verticesCount) vertices = slot.AttachmentVertices; - for (int i = 0; i < verticesCount; i += 2) { - float vx = vertices[i]; - float vy = vertices[i + 1]; - worldVertices[i] = vx * m00 + vy * m01 + x; - worldVertices[i + 1] = vx * m10 + vy * m11 + y; - } - } - } + public void ComputeWorldVertices(Slot slot, float[] worldVertices) + { + Bone bone = slot.bone; + float x = bone.skeleton.x + bone.worldX, y = bone.skeleton.y + bone.worldY; + float m00 = bone.m00, m01 = bone.m01, m10 = bone.m10, m11 = bone.m11; + float[] vertices = this.vertices; + int verticesCount = vertices.Length; + if (slot.attachmentVerticesCount == verticesCount) vertices = slot.AttachmentVertices; + for (int i = 0; i < verticesCount; i += 2) + { + float vx = vertices[i]; + float vy = vertices[i + 1]; + worldVertices[i] = vx * m00 + vy * m01 + x; + worldVertices[i + 1] = vx * m10 + vy * m11 + y; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Attachments/RegionAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Attachments/RegionAttachment.cs index 70faeca..83765ce 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Attachments/RegionAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Attachments/RegionAttachment.cs @@ -30,122 +30,131 @@ using System; -namespace Spine2_1_25 { - /// Attachment that displays a texture region. - public class RegionAttachment : Attachment { - public const int X1 = 0; - public const int Y1 = 1; - public const int X2 = 2; - public const int Y2 = 3; - public const int X3 = 4; - public const int Y3 = 5; - public const int X4 = 6; - public const int Y4 = 7; +namespace Spine2_1_25 +{ + /// Attachment that displays a texture region. + public class RegionAttachment : Attachment + { + public const int X1 = 0; + public const int Y1 = 1; + public const int X2 = 2; + public const int Y2 = 3; + public const int X3 = 4; + public const int Y3 = 5; + public const int X4 = 6; + public const int Y4 = 7; - internal float x, y, rotation, scaleX = 1, scaleY = 1, width, height; - internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; - internal float[] offset = new float[8], uvs = new float[8]; - internal float r = 1, g = 1, b = 1, a = 1; + internal float x, y, rotation, scaleX = 1, scaleY = 1, width, height; + internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; + internal float[] offset = new float[8], uvs = new float[8]; + internal float r = 1, g = 1, b = 1, a = 1; - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public float Rotation { get { return rotation; } set { rotation = value; } } - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - public float ScaleY { get { return scaleY; } set { scaleY = value; } } - public float Width { get { return width; } set { width = value; } } - public float Height { get { return height; } set { height = value; } } + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public float Rotation { get { return rotation; } set { rotation = value; } } + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + public float Width { get { return width; } set { width = value; } } + public float Height { get { return height; } set { height = value; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } - public String Path { get; set; } - public Object RendererObject { get; set; } - public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } - public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. - public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } - public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. - public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } - public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. + public String Path { get; set; } + public Object RendererObject { get; set; } + public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } + public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. + public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } + public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. + public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } + public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. - public float[] Offset { get { return offset; } } - public float[] UVs { get { return uvs; } } + public float[] Offset { get { return offset; } } + public float[] UVs { get { return uvs; } } - public RegionAttachment (string name) - : base(name) { - } + public RegionAttachment(string name) + : base(name) + { + } - public void SetUVs (float u, float v, float u2, float v2, bool rotate) { - float[] uvs = this.uvs; - if (rotate) { - uvs[X2] = u; - uvs[Y2] = v2; - uvs[X3] = u; - uvs[Y3] = v; - uvs[X4] = u2; - uvs[Y4] = v; - uvs[X1] = u2; - uvs[Y1] = v2; - } else { - uvs[X1] = u; - uvs[Y1] = v2; - uvs[X2] = u; - uvs[Y2] = v; - uvs[X3] = u2; - uvs[Y3] = v; - uvs[X4] = u2; - uvs[Y4] = v2; - } - } + public void SetUVs(float u, float v, float u2, float v2, bool rotate) + { + float[] uvs = this.uvs; + if (rotate) + { + uvs[X2] = u; + uvs[Y2] = v2; + uvs[X3] = u; + uvs[Y3] = v; + uvs[X4] = u2; + uvs[Y4] = v; + uvs[X1] = u2; + uvs[Y1] = v2; + } + else + { + uvs[X1] = u; + uvs[Y1] = v2; + uvs[X2] = u; + uvs[Y2] = v; + uvs[X3] = u2; + uvs[Y3] = v; + uvs[X4] = u2; + uvs[Y4] = v2; + } + } - public void UpdateOffset () { - float width = this.width; - float height = this.height; - float scaleX = this.scaleX; - float scaleY = this.scaleY; - float regionScaleX = width / regionOriginalWidth * scaleX; - float regionScaleY = height / regionOriginalHeight * scaleY; - float localX = -width / 2 * scaleX + regionOffsetX * regionScaleX; - float localY = -height / 2 * scaleY + regionOffsetY * regionScaleY; - float localX2 = localX + regionWidth * regionScaleX; - float localY2 = localY + regionHeight * regionScaleY; - float radians = rotation * (float)Math.PI / 180; - float cos = (float)Math.Cos(radians); - float sin = (float)Math.Sin(radians); - float x = this.x; - float y = this.y; - 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; - float[] offset = this.offset; - offset[X1] = localXCos - localYSin; - offset[Y1] = localYCos + localXSin; - offset[X2] = localXCos - localY2Sin; - offset[Y2] = localY2Cos + localXSin; - offset[X3] = localX2Cos - localY2Sin; - offset[Y3] = localY2Cos + localX2Sin; - offset[X4] = localX2Cos - localYSin; - offset[Y4] = localYCos + localX2Sin; - } + public void UpdateOffset() + { + float width = this.width; + float height = this.height; + float scaleX = this.scaleX; + float scaleY = this.scaleY; + float regionScaleX = width / regionOriginalWidth * scaleX; + float regionScaleY = height / regionOriginalHeight * scaleY; + float localX = -width / 2 * scaleX + regionOffsetX * regionScaleX; + float localY = -height / 2 * scaleY + regionOffsetY * regionScaleY; + float localX2 = localX + regionWidth * regionScaleX; + float localY2 = localY + regionHeight * regionScaleY; + float radians = rotation * (float)Math.PI / 180; + float cos = (float)Math.Cos(radians); + float sin = (float)Math.Sin(radians); + float x = this.x; + float y = this.y; + 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; + float[] offset = this.offset; + offset[X1] = localXCos - localYSin; + offset[Y1] = localYCos + localXSin; + offset[X2] = localXCos - localY2Sin; + offset[Y2] = localY2Cos + localXSin; + offset[X3] = localX2Cos - localY2Sin; + offset[Y3] = localY2Cos + localX2Sin; + offset[X4] = localX2Cos - localYSin; + offset[Y4] = localYCos + localX2Sin; + } - public void ComputeWorldVertices (Bone bone, float[] worldVertices) { - float x = bone.skeleton.x + bone.worldX, y = bone.skeleton.y + bone.worldY; - float m00 = bone.m00, m01 = bone.m01, m10 = bone.m10, m11 = bone.m11; - float[] offset = this.offset; - worldVertices[X1] = offset[X1] * m00 + offset[Y1] * m01 + x; - worldVertices[Y1] = offset[X1] * m10 + offset[Y1] * m11 + y; - worldVertices[X2] = offset[X2] * m00 + offset[Y2] * m01 + x; - worldVertices[Y2] = offset[X2] * m10 + offset[Y2] * m11 + y; - worldVertices[X3] = offset[X3] * m00 + offset[Y3] * m01 + x; - worldVertices[Y3] = offset[X3] * m10 + offset[Y3] * m11 + y; - worldVertices[X4] = offset[X4] * m00 + offset[Y4] * m01 + x; - worldVertices[Y4] = offset[X4] * m10 + offset[Y4] * m11 + y; - } - } + public void ComputeWorldVertices(Bone bone, float[] worldVertices) + { + float x = bone.skeleton.x + bone.worldX, y = bone.skeleton.y + bone.worldY; + float m00 = bone.m00, m01 = bone.m01, m10 = bone.m10, m11 = bone.m11; + float[] offset = this.offset; + worldVertices[X1] = offset[X1] * m00 + offset[Y1] * m01 + x; + worldVertices[Y1] = offset[X1] * m10 + offset[Y1] * m11 + y; + worldVertices[X2] = offset[X2] * m00 + offset[Y2] * m01 + x; + worldVertices[Y2] = offset[X2] * m10 + offset[Y2] * m11 + y; + worldVertices[X3] = offset[X3] * m00 + offset[Y3] * m01 + x; + worldVertices[Y3] = offset[X3] * m10 + offset[Y3] * m11 + y; + worldVertices[X4] = offset[X4] * m00 + offset[Y4] * m01 + x; + worldVertices[Y4] = offset[X4] * m10 + offset[Y4] * m11 + y; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Attachments/SkinnedMeshAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Attachments/SkinnedMeshAttachment.cs index a22d4c9..55a0268 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Attachments/SkinnedMeshAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Attachments/SkinnedMeshAttachment.cs @@ -31,102 +31,119 @@ using System; using System.Collections.Generic; -namespace Spine2_1_25 { - /// Attachment that displays a texture region. - public class SkinnedMeshAttachment : Attachment { - internal int[] bones; - internal float[] weights, uvs, regionUVs; - internal int[] triangles; - internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; - internal float r = 1, g = 1, b = 1, a = 1; +namespace Spine2_1_25 +{ + /// Attachment that displays a texture region. + public class SkinnedMeshAttachment : Attachment + { + internal int[] bones; + internal float[] weights, uvs, regionUVs; + internal int[] triangles; + internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; + internal float r = 1, g = 1, b = 1, a = 1; - public int HullLength { get; set; } - public int[] Bones { get { return bones; } set { bones = value; } } - public float[] Weights { get { return weights; } set { weights = value; } } - public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } } - public float[] UVs { get { return uvs; } set { uvs = value; } } - public int[] Triangles { get { return triangles; } set { triangles = value; } } + public int HullLength { get; set; } + public int[] Bones { get { return bones; } set { bones = value; } } + public float[] Weights { get { return weights; } set { weights = value; } } + public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } } + public float[] UVs { get { return uvs; } set { uvs = value; } } + public int[] Triangles { get { return triangles; } set { triangles = value; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } - public String Path { get; set; } - public Object RendererObject { get; set; } - public float RegionU { get; set; } - public float RegionV { get; set; } - public float RegionU2 { get; set; } - public float RegionV2 { get; set; } - public bool RegionRotate { get; set; } - public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } - public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. - public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } - public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. - public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } - public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. + public String Path { get; set; } + public Object RendererObject { get; set; } + public float RegionU { get; set; } + public float RegionV { get; set; } + public float RegionU2 { get; set; } + public float RegionV2 { get; set; } + public bool RegionRotate { get; set; } + public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } + public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. + public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } + public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. + public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } + public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. - // Nonessential. - public int[] Edges { get; set; } - public float Width { get; set; } - public float Height { get; set; } + // Nonessential. + public int[] Edges { get; set; } + public float Width { get; set; } + public float Height { get; set; } - public SkinnedMeshAttachment (string name) - : base(name) { - } + public SkinnedMeshAttachment(string name) + : base(name) + { + } - public void UpdateUVs () { - float u = RegionU, v = RegionV, width = RegionU2 - RegionU, height = RegionV2 - RegionV; - float[] regionUVs = this.regionUVs; - if (this.uvs == null || this.uvs.Length != regionUVs.Length) this.uvs = new float[regionUVs.Length]; - float[] uvs = this.uvs; - if (RegionRotate) { - for (int i = 0, n = uvs.Length; i < n; i += 2) { - uvs[i] = u + regionUVs[i + 1] * width; - uvs[i + 1] = v + height - regionUVs[i] * height; - } - } else { - for (int i = 0, n = uvs.Length; i < n; i += 2) { - uvs[i] = u + regionUVs[i] * width; - uvs[i + 1] = v + regionUVs[i + 1] * height; - } - } - } + public void UpdateUVs() + { + float u = RegionU, v = RegionV, width = RegionU2 - RegionU, height = RegionV2 - RegionV; + float[] regionUVs = this.regionUVs; + if (this.uvs == null || this.uvs.Length != regionUVs.Length) this.uvs = new float[regionUVs.Length]; + float[] uvs = this.uvs; + if (RegionRotate) + { + for (int i = 0, n = uvs.Length; i < n; i += 2) + { + uvs[i] = u + regionUVs[i + 1] * width; + uvs[i + 1] = v + height - regionUVs[i] * height; + } + } + else + { + for (int i = 0, n = uvs.Length; i < n; i += 2) + { + uvs[i] = u + regionUVs[i] * width; + uvs[i + 1] = v + regionUVs[i + 1] * height; + } + } + } - public void ComputeWorldVertices (Slot slot, float[] worldVertices) { - Skeleton skeleton = slot.bone.skeleton; - List skeletonBones = skeleton.bones; - float x = skeleton.x, y = skeleton.y; - float[] weights = this.weights; - int[] bones = this.bones; - if (slot.attachmentVerticesCount == 0) { - for (int w = 0, v = 0, b = 0, n = bones.Length; v < n; w += 2) { - float wx = 0, wy = 0; - int nn = bones[v++] + v; - for (; v < nn; v++, b += 3) { - Bone bone = skeletonBones[bones[v]]; - float vx = weights[b], vy = weights[b + 1], weight = weights[b + 2]; - wx += (vx * bone.m00 + vy * bone.m01 + bone.worldX) * weight; - wy += (vx * bone.m10 + vy * bone.m11 + bone.worldY) * weight; - } - worldVertices[w] = wx + x; - worldVertices[w + 1] = wy + y; - } - } else { - float[] ffd = slot.AttachmentVertices; - for (int w = 0, v = 0, b = 0, f = 0, n = bones.Length; v < n; w += 2) { - float wx = 0, wy = 0; - int nn = bones[v++] + v; - for (; v < nn; v++, b += 3, f += 2) { - Bone bone = skeletonBones[bones[v]]; - float vx = weights[b] + ffd[f], vy = weights[b + 1] + ffd[f + 1], weight = weights[b + 2]; - wx += (vx * bone.m00 + vy * bone.m01 + bone.worldX) * weight; - wy += (vx * bone.m10 + vy * bone.m11 + bone.worldY) * weight; - } - worldVertices[w] = wx + x; - worldVertices[w + 1] = wy + y; - } - } - } - } + public void ComputeWorldVertices(Slot slot, float[] worldVertices) + { + Skeleton skeleton = slot.bone.skeleton; + List skeletonBones = skeleton.bones; + float x = skeleton.x, y = skeleton.y; + float[] weights = this.weights; + int[] bones = this.bones; + if (slot.attachmentVerticesCount == 0) + { + for (int w = 0, v = 0, b = 0, n = bones.Length; v < n; w += 2) + { + float wx = 0, wy = 0; + int nn = bones[v++] + v; + for (; v < nn; v++, b += 3) + { + Bone bone = skeletonBones[bones[v]]; + float vx = weights[b], vy = weights[b + 1], weight = weights[b + 2]; + wx += (vx * bone.m00 + vy * bone.m01 + bone.worldX) * weight; + wy += (vx * bone.m10 + vy * bone.m11 + bone.worldY) * weight; + } + worldVertices[w] = wx + x; + worldVertices[w + 1] = wy + y; + } + } + else + { + float[] ffd = slot.AttachmentVertices; + for (int w = 0, v = 0, b = 0, f = 0, n = bones.Length; v < n; w += 2) + { + float wx = 0, wy = 0; + int nn = bones[v++] + v; + for (; v < nn; v++, b += 3, f += 2) + { + Bone bone = skeletonBones[bones[v]]; + float vx = weights[b] + ffd[f], vy = weights[b + 1] + ffd[f + 1], weight = weights[b + 2]; + wx += (vx * bone.m00 + vy * bone.m01 + bone.worldX) * weight; + wy += (vx * bone.m10 + vy * bone.m11 + bone.worldY) * weight; + } + worldVertices[w] = wx + x; + worldVertices[w + 1] = wy + y; + } + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Bone.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Bone.cs index 6893420..3e39d7c 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Bone.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Bone.cs @@ -31,135 +31,156 @@ using System; using System.Collections.Generic; -namespace Spine2_1_25 { - public class Bone{ - static public bool yDown; +namespace Spine2_1_25 +{ + public class Bone + { + static public bool yDown; - internal BoneData data; - internal Skeleton skeleton; - internal Bone parent; - internal List children = new List(); - internal float x, y, rotation, rotationIK, scaleX, scaleY; - internal bool flipX, flipY; - internal float m00, m01, m10, m11; - internal float worldX, worldY, worldRotation, worldScaleX, worldScaleY; - internal bool worldFlipX, worldFlipY; + internal BoneData data; + internal Skeleton skeleton; + internal Bone parent; + internal List children = new List(); + internal float x, y, rotation, rotationIK, scaleX, scaleY; + internal bool flipX, flipY; + internal float m00, m01, m10, m11; + internal float worldX, worldY, worldRotation, worldScaleX, worldScaleY; + internal bool worldFlipX, worldFlipY; - public BoneData Data { get { return data; } } - public Skeleton Skeleton { get { return skeleton; } } - public Bone Parent { get { return parent; } } - public List Children { get { return children; } } - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - /// The forward kinetics rotation. - public float Rotation { get { return rotation; } set { rotation = value; } } - /// The inverse kinetics rotation, as calculated by any IK constraints. - public float RotationIK { get { return rotationIK; } set { rotationIK = value; } } - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - public float ScaleY { get { return scaleY; } set { scaleY = value; } } - public bool FlipX { get { return flipX; } set { flipX = value; } } - public bool FlipY { get { return flipY; } set { flipY = value; } } + public BoneData Data { get { return data; } } + public Skeleton Skeleton { get { return skeleton; } } + public Bone Parent { get { return parent; } } + public List Children { get { return children; } } + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + /// The forward kinetics rotation. + public float Rotation { get { return rotation; } set { rotation = value; } } + /// The inverse kinetics rotation, as calculated by any IK constraints. + public float RotationIK { get { return rotationIK; } set { rotationIK = value; } } + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + public bool FlipX { get { return flipX; } set { flipX = value; } } + public bool FlipY { get { return flipY; } set { flipY = value; } } - public float M00 { get { return m00; } } - public float M01 { get { return m01; } } - public float M10 { get { return m10; } } - public float M11 { get { return m11; } } - public float WorldX { get { return worldX; } } - public float WorldY { get { return worldY; } } - public float WorldRotation { get { return worldRotation; } } - public float WorldScaleX { get { return worldScaleX; } } - public float WorldScaleY { get { return worldScaleY; } } - public bool WorldFlipX { get { return worldFlipX; } set { worldFlipX = value; } } - public bool WorldFlipY { get { return worldFlipY; } set { worldFlipY = value; } } + public float M00 { get { return m00; } } + public float M01 { get { return m01; } } + public float M10 { get { return m10; } } + public float M11 { get { return m11; } } + public float WorldX { get { return worldX; } } + public float WorldY { get { return worldY; } } + public float WorldRotation { get { return worldRotation; } } + public float WorldScaleX { get { return worldScaleX; } } + public float WorldScaleY { get { return worldScaleY; } } + public bool WorldFlipX { get { return worldFlipX; } set { worldFlipX = value; } } + public bool WorldFlipY { get { return worldFlipY; } set { worldFlipY = value; } } - /// May be null. - public Bone (BoneData data, Skeleton skeleton, Bone parent) { - if (data == null) throw new ArgumentNullException("data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); - this.data = data; - this.skeleton = skeleton; - this.parent = parent; - SetToSetupPose(); - } + /// May be null. + public Bone(BoneData data, Skeleton skeleton, Bone parent) + { + if (data == null) throw new ArgumentNullException("data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); + this.data = data; + this.skeleton = skeleton; + this.parent = parent; + SetToSetupPose(); + } - /// Computes the world SRT using the parent bone and the local SRT. - public void UpdateWorldTransform () { - Bone parent = this.parent; - float x = this.x, y = this.y; - if (parent != null) { - worldX = x * parent.m00 + y * parent.m01 + parent.worldX; - worldY = x * parent.m10 + y * parent.m11 + parent.worldY; - if (data.inheritScale) { - worldScaleX = parent.worldScaleX * scaleX; - worldScaleY = parent.worldScaleY * scaleY; - } else { - worldScaleX = scaleX; - worldScaleY = scaleY; - } - worldRotation = data.inheritRotation ? parent.worldRotation + rotationIK : rotationIK; - worldFlipX = parent.worldFlipX != flipX; - worldFlipY = parent.worldFlipY != flipY; - } else { - Skeleton skeleton = this.skeleton; - bool skeletonFlipX = skeleton.flipX, skeletonFlipY = skeleton.flipY; - worldX = skeletonFlipX ? -x : x; - worldY = skeletonFlipY != yDown ? -y : y; - worldScaleX = scaleX; - worldScaleY = scaleY; - worldRotation = rotationIK; - worldFlipX = skeletonFlipX != flipX; - worldFlipY = skeletonFlipY != flipY; - } - float radians = worldRotation * (float)Math.PI / 180; - float cos = (float)Math.Cos(radians); - float sin = (float)Math.Sin(radians); - if (worldFlipX) { - m00 = -cos * worldScaleX; - m01 = sin * worldScaleY; - } else { - m00 = cos * worldScaleX; - m01 = -sin * worldScaleY; - } - if (worldFlipY != yDown) { - m10 = -sin * worldScaleX; - m11 = -cos * worldScaleY; - } else { - m10 = sin * worldScaleX; - m11 = cos * worldScaleY; - } - } + /// Computes the world SRT using the parent bone and the local SRT. + public void UpdateWorldTransform() + { + Bone parent = this.parent; + float x = this.x, y = this.y; + if (parent != null) + { + worldX = x * parent.m00 + y * parent.m01 + parent.worldX; + worldY = x * parent.m10 + y * parent.m11 + parent.worldY; + if (data.inheritScale) + { + worldScaleX = parent.worldScaleX * scaleX; + worldScaleY = parent.worldScaleY * scaleY; + } + else + { + worldScaleX = scaleX; + worldScaleY = scaleY; + } + worldRotation = data.inheritRotation ? parent.worldRotation + rotationIK : rotationIK; + worldFlipX = parent.worldFlipX != flipX; + worldFlipY = parent.worldFlipY != flipY; + } + else + { + Skeleton skeleton = this.skeleton; + bool skeletonFlipX = skeleton.flipX, skeletonFlipY = skeleton.flipY; + worldX = skeletonFlipX ? -x : x; + worldY = skeletonFlipY != yDown ? -y : y; + worldScaleX = scaleX; + worldScaleY = scaleY; + worldRotation = rotationIK; + worldFlipX = skeletonFlipX != flipX; + worldFlipY = skeletonFlipY != flipY; + } + float radians = worldRotation * (float)Math.PI / 180; + float cos = (float)Math.Cos(radians); + float sin = (float)Math.Sin(radians); + if (worldFlipX) + { + m00 = -cos * worldScaleX; + m01 = sin * worldScaleY; + } + else + { + m00 = cos * worldScaleX; + m01 = -sin * worldScaleY; + } + if (worldFlipY != yDown) + { + m10 = -sin * worldScaleX; + m11 = -cos * worldScaleY; + } + else + { + m10 = sin * worldScaleX; + m11 = cos * worldScaleY; + } + } - public void SetToSetupPose () { - BoneData data = this.data; - x = data.x; - y = data.y; - rotation = data.rotation; - rotationIK = rotation; - scaleX = data.scaleX; - scaleY = data.scaleY; - flipX = data.flipX; - flipY = data.flipY; - } + public void SetToSetupPose() + { + BoneData data = this.data; + x = data.x; + y = data.y; + rotation = data.rotation; + rotationIK = rotation; + scaleX = data.scaleX; + scaleY = data.scaleY; + flipX = data.flipX; + flipY = data.flipY; + } - public void worldToLocal (float worldX, float worldY, out float localX, out float localY) { - float dx = worldX - this.worldX, dy = worldY - this.worldY; - float m00 = this.m00, m10 = this.m10, m01 = this.m01, m11 = this.m11; - if (worldFlipX != (worldFlipY != yDown)) { - m00 = -m00; - m11 = -m11; - } - float invDet = 1 / (m00 * m11 - m01 * m10); - localX = (dx * m00 * invDet - dy * m01 * invDet); - localY = (dy * m11 * invDet - dx * m10 * invDet); - } + public void worldToLocal(float worldX, float worldY, out float localX, out float localY) + { + float dx = worldX - this.worldX, dy = worldY - this.worldY; + float m00 = this.m00, m10 = this.m10, m01 = this.m01, m11 = this.m11; + if (worldFlipX != (worldFlipY != yDown)) + { + m00 = -m00; + m11 = -m11; + } + float invDet = 1 / (m00 * m11 - m01 * m10); + localX = (dx * m00 * invDet - dy * m01 * invDet); + localY = (dy * m11 * invDet - dx * m10 * invDet); + } - public void localToWorld (float localX, float localY, out float worldX, out float worldY) { - worldX = localX * m00 + localY * m01 + this.worldX; - worldY = localX * m10 + localY * m11 + this.worldY; - } + public void localToWorld(float localX, float localY, out float worldX, out float worldY) + { + worldX = localX * m00 + localY * m01 + this.worldX; + worldY = localX * m10 + localY * m11 + this.worldY; + } - override public String ToString () { - return data.name; - } - } + override public String ToString() + { + return data.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/BoneData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/BoneData.cs index 7ad5eb7..0a041ed 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/BoneData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/BoneData.cs @@ -30,37 +30,41 @@ using System; -namespace Spine2_1_25 { - public class BoneData { - internal BoneData parent; - internal String name; - internal float length, x, y, rotation, scaleX = 1, scaleY = 1; - internal bool flipX, flipY; - internal bool inheritScale = true, inheritRotation = true; +namespace Spine2_1_25 +{ + public class BoneData + { + internal BoneData parent; + internal String name; + internal float length, x, y, rotation, scaleX = 1, scaleY = 1; + internal bool flipX, flipY; + internal bool inheritScale = true, inheritRotation = true; - /// May be null. - public BoneData Parent { get { return parent; } } - public String Name { get { return name; } } - public float Length { get { return length; } set { length = value; } } - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public float Rotation { get { return rotation; } set { rotation = value; } } - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - public float ScaleY { get { return scaleY; } set { scaleY = value; } } - public bool FlipX { get { return flipX; } set { flipX = value; } } - public bool FlipY { get { return flipY; } set { flipY = value; } } - public bool InheritScale { get { return inheritScale; } set { inheritScale = value; } } - public bool InheritRotation { get { return inheritRotation; } set { inheritRotation = value; } } + /// May be null. + public BoneData Parent { get { return parent; } } + public String Name { get { return name; } } + public float Length { get { return length; } set { length = value; } } + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public float Rotation { get { return rotation; } set { rotation = value; } } + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + public bool FlipX { get { return flipX; } set { flipX = value; } } + public bool FlipY { get { return flipY; } set { flipY = value; } } + public bool InheritScale { get { return inheritScale; } set { inheritScale = value; } } + public bool InheritRotation { get { return inheritRotation; } set { inheritRotation = value; } } - /// May be null. - public BoneData (String name, BoneData parent) { - if (name == null) throw new ArgumentNullException("name cannot be null."); - this.name = name; - this.parent = parent; - } + /// May be null. + public BoneData(String name, BoneData parent) + { + if (name == null) throw new ArgumentNullException("name cannot be null."); + this.name = name; + this.parent = parent; + } - override public String ToString () { - return name; - } - } + override public String ToString() + { + return name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Event.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Event.cs index db1f38f..a8415a8 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Event.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Event.cs @@ -30,19 +30,23 @@ using System; -namespace Spine2_1_25 { - public class Event { - public EventData Data { get; private set; } - public int Int { get; set; } - public float Float { get; set; } - public String String { get; set; } +namespace Spine2_1_25 +{ + public class Event + { + public EventData Data { get; private set; } + public int Int { get; set; } + public float Float { get; set; } + public String String { get; set; } - public Event (EventData data) { - Data = data; - } + public Event(EventData data) + { + Data = data; + } - override public String ToString () { - return Data.Name; - } - } + override public String ToString() + { + return Data.Name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/EventData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/EventData.cs index a3849e7..a6ea305 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/EventData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/EventData.cs @@ -30,22 +30,26 @@ using System; -namespace Spine2_1_25 { - public class EventData { - internal String name; +namespace Spine2_1_25 +{ + public class EventData + { + internal String name; - public String Name { get { return name; } } - public int Int { get; set; } - public float Float { get; set; } - public String String { get; set; } + public String Name { get { return name; } } + public int Int { get; set; } + public float Float { get; set; } + public String String { get; set; } - public EventData (String name) { - if (name == null) throw new ArgumentNullException("name cannot be null."); - this.name = name; - } + public EventData(String name) + { + if (name == null) throw new ArgumentNullException("name cannot be null."); + this.name = name; + } - override public String ToString () { - return Name; - } - } + override public String ToString() + { + return Name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/IkConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/IkConstraint.cs index 0f5e570..094d0f8 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/IkConstraint.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/IkConstraint.cs @@ -31,120 +31,136 @@ using System; using System.Collections.Generic; -namespace Spine2_1_25 { - public class IkConstraint { - private const float radDeg = 180 / (float)Math.PI; +namespace Spine2_1_25 +{ + public class IkConstraint + { + private const float radDeg = 180 / (float)Math.PI; - internal IkConstraintData data; - internal List bones = new List(); - internal Bone target; - internal int bendDirection; - internal float mix; + internal IkConstraintData data; + internal List bones = new List(); + internal Bone target; + internal int bendDirection; + internal float mix; - public IkConstraintData Data { get { return data; } } - public List Bones { get { return bones; } } - public Bone Target { get { return target; } set { target = value; } } - public int BendDirection { get { return bendDirection; } set { bendDirection = value; } } - public float Mix { get { return mix; } set { mix = value; } } + public IkConstraintData Data { get { return data; } } + public List Bones { get { return bones; } } + public Bone Target { get { return target; } set { target = value; } } + public int BendDirection { get { return bendDirection; } set { bendDirection = value; } } + public float Mix { get { return mix; } set { mix = value; } } - public IkConstraint (IkConstraintData data, Skeleton skeleton) { - if (data == null) throw new ArgumentNullException("data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); - this.data = data; - mix = data.mix; - bendDirection = data.bendDirection; + public IkConstraint(IkConstraintData data, Skeleton skeleton) + { + if (data == null) throw new ArgumentNullException("data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); + this.data = data; + mix = data.mix; + bendDirection = data.bendDirection; - bones = new List(data.bones.Count); - foreach (BoneData boneData in data.bones) - bones.Add(skeleton.FindBone(boneData.name)); - target = skeleton.FindBone(data.target.name); - } + bones = new List(data.bones.Count); + foreach (BoneData boneData in data.bones) + bones.Add(skeleton.FindBone(boneData.name)); + target = skeleton.FindBone(data.target.name); + } - public void apply () { - Bone target = this.target; - List bones = this.bones; - switch (bones.Count) { - case 1: - apply(bones[0], target.worldX, target.worldY, mix); - break; - case 2: - apply(bones[0], bones[1], target.worldX, target.worldY, bendDirection, mix); - break; - } - } + public void apply() + { + Bone target = this.target; + List bones = this.bones; + switch (bones.Count) + { + case 1: + apply(bones[0], target.worldX, target.worldY, mix); + break; + case 2: + apply(bones[0], bones[1], target.worldX, target.worldY, bendDirection, mix); + break; + } + } - override public String ToString () { - return data.name; - } + override public String ToString() + { + return data.name; + } - /// 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 public void apply (Bone bone, float targetX, float targetY, float alpha) { - float parentRotation = (!bone.data.inheritRotation || bone.parent == null) ? 0 : bone.parent.worldRotation; - float rotation = bone.rotation; - float rotationIK = (float)Math.Atan2(targetY - bone.worldY, targetX - bone.worldX) * radDeg; - if (bone.worldFlipX != (bone.worldFlipY != Bone.yDown)) rotationIK = -rotationIK; - rotationIK -= parentRotation; - bone.rotationIK = rotation + (rotationIK - rotation) * alpha; - } + /// 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 public void apply(Bone bone, float targetX, float targetY, float alpha) + { + float parentRotation = (!bone.data.inheritRotation || bone.parent == null) ? 0 : bone.parent.worldRotation; + float rotation = bone.rotation; + float rotationIK = (float)Math.Atan2(targetY - bone.worldY, targetX - bone.worldX) * radDeg; + if (bone.worldFlipX != (bone.worldFlipY != Bone.yDown)) rotationIK = -rotationIK; + rotationIK -= parentRotation; + bone.rotationIK = rotation + (rotationIK - rotation) * 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. - /// Any descendant bone of the parent. - static public void apply (Bone parent, Bone child, float targetX, float targetY, int bendDirection, float alpha) { - float childRotation = child.rotation, parentRotation = parent.rotation; - if (alpha == 0) { - child.rotationIK = childRotation; - parent.rotationIK = parentRotation; - return; - } - float positionX, positionY; - Bone parentParent = parent.parent; - if (parentParent != null) { - parentParent.worldToLocal(targetX, targetY, out positionX, out positionY); - targetX = (positionX - parent.x) * parentParent.worldScaleX; - targetY = (positionY - parent.y) * parentParent.worldScaleY; - } else { - targetX -= parent.x; - targetY -= parent.y; - } - if (child.parent == parent) { - positionX = child.x; - positionY = child.y; - } else { - child.parent.localToWorld(child.x, child.y, out positionX, out positionY); - parent.worldToLocal(positionX, positionY, out positionX, out positionY); - } - float childX = positionX * parent.worldScaleX, childY = positionY * parent.worldScaleY; - float offset = (float)Math.Atan2(childY, childX); - float len1 = (float)Math.Sqrt(childX * childX + childY * childY), len2 = child.data.length * child.worldScaleX; - // Based on code by Ryan Juckett with permission: Copyright (c) 2008-2009 Ryan Juckett, http://www.ryanjuckett.com/ - float cosDenom = 2 * len1 * len2; - if (cosDenom < 0.0001f) { - child.rotationIK = childRotation + ((float)Math.Atan2(targetY, targetX) * radDeg - parentRotation - childRotation) - * alpha; - return; - } - float cos = (targetX * targetX + targetY * targetY - len1 * len1 - len2 * len2) / cosDenom; - if (cos < -1) - cos = -1; - else if (cos > 1) - cos = 1; - float childAngle = (float)Math.Acos(cos) * bendDirection; - float adjacent = len1 + len2 * cos, opposite = len2 * (float)Math.Sin(childAngle); - float parentAngle = (float)Math.Atan2(targetY * adjacent - targetX * opposite, targetX * adjacent + targetY * opposite); - float rotation = (parentAngle - offset) * radDeg - parentRotation; - if (rotation > 180) - rotation -= 360; - else if (rotation < -180) // - rotation += 360; - parent.rotationIK = parentRotation + rotation * alpha; - rotation = (childAngle + offset) * radDeg - childRotation; - if (rotation > 180) - rotation -= 360; - else if (rotation < -180) // - rotation += 360; - child.rotationIK = childRotation + (rotation + parent.worldRotation - child.parent.worldRotation) * 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. + /// Any descendant bone of the parent. + static public void apply(Bone parent, Bone child, float targetX, float targetY, int bendDirection, float alpha) + { + float childRotation = child.rotation, parentRotation = parent.rotation; + if (alpha == 0) + { + child.rotationIK = childRotation; + parent.rotationIK = parentRotation; + return; + } + float positionX, positionY; + Bone parentParent = parent.parent; + if (parentParent != null) + { + parentParent.worldToLocal(targetX, targetY, out positionX, out positionY); + targetX = (positionX - parent.x) * parentParent.worldScaleX; + targetY = (positionY - parent.y) * parentParent.worldScaleY; + } + else + { + targetX -= parent.x; + targetY -= parent.y; + } + if (child.parent == parent) + { + positionX = child.x; + positionY = child.y; + } + else + { + child.parent.localToWorld(child.x, child.y, out positionX, out positionY); + parent.worldToLocal(positionX, positionY, out positionX, out positionY); + } + float childX = positionX * parent.worldScaleX, childY = positionY * parent.worldScaleY; + float offset = (float)Math.Atan2(childY, childX); + float len1 = (float)Math.Sqrt(childX * childX + childY * childY), len2 = child.data.length * child.worldScaleX; + // Based on code by Ryan Juckett with permission: Copyright (c) 2008-2009 Ryan Juckett, http://www.ryanjuckett.com/ + float cosDenom = 2 * len1 * len2; + if (cosDenom < 0.0001f) + { + child.rotationIK = childRotation + ((float)Math.Atan2(targetY, targetX) * radDeg - parentRotation - childRotation) + * alpha; + return; + } + float cos = (targetX * targetX + targetY * targetY - len1 * len1 - len2 * len2) / cosDenom; + if (cos < -1) + cos = -1; + else if (cos > 1) + cos = 1; + float childAngle = (float)Math.Acos(cos) * bendDirection; + float adjacent = len1 + len2 * cos, opposite = len2 * (float)Math.Sin(childAngle); + float parentAngle = (float)Math.Atan2(targetY * adjacent - targetX * opposite, targetX * adjacent + targetY * opposite); + float rotation = (parentAngle - offset) * radDeg - parentRotation; + if (rotation > 180) + rotation -= 360; + else if (rotation < -180) // + rotation += 360; + parent.rotationIK = parentRotation + rotation * alpha; + rotation = (childAngle + offset) * radDeg - childRotation; + if (rotation > 180) + rotation -= 360; + else if (rotation < -180) // + rotation += 360; + child.rotationIK = childRotation + (rotation + parent.worldRotation - child.parent.worldRotation) * alpha; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/IkConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/IkConstraintData.cs index a58a06d..8eede7d 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/IkConstraintData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/IkConstraintData.cs @@ -31,27 +31,31 @@ using System; using System.Collections.Generic; -namespace Spine2_1_25 { - public class IkConstraintData { - internal String name; - internal List bones = new List(); - internal BoneData target; - internal int bendDirection = 1; - internal float mix = 1; +namespace Spine2_1_25 +{ + public class IkConstraintData + { + internal String name; + internal List bones = new List(); + internal BoneData target; + internal int bendDirection = 1; + internal float mix = 1; - public String Name { get { return name; } } - public List Bones { get { return bones; } } - public BoneData Target { get { return target; } set { target = value; } } - public int BendDirection { get { return bendDirection; } set { bendDirection = value; } } - public float Mix { get { return mix; } set { mix = value; } } + public String Name { get { return name; } } + public List Bones { get { return bones; } } + public BoneData Target { get { return target; } set { target = value; } } + public int BendDirection { get { return bendDirection; } set { bendDirection = value; } } + public float Mix { get { return mix; } set { mix = value; } } - public IkConstraintData (String name) { - if (name == null) throw new ArgumentNullException("name cannot be null."); - this.name = name; - } + public IkConstraintData(String name) + { + if (name == null) throw new ArgumentNullException("name cannot be null."); + this.name = name; + } - override public String ToString () { - return name; - } - } + override public String ToString() + { + return name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Json.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Json.cs index 963feaa..ab644fd 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Json.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Json.cs @@ -29,10 +29,10 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Text; -using System.Globalization; - + namespace Spine2_1_25 { // Example usage: @@ -58,7 +58,7 @@ namespace Spine2_1_25 // Debug.Log("deserialized: " + dict.GetType()); // Debug.Log("dict['array'][0]: " + ((List) dict["array"])[0]); // Debug.Log("dict['string']: " + (string) dict["string"]); - // Debug.Log("dict['float']: " + (float) dict["float"]); + // Debug.Log("dict['float']: " + (float) dict["float"]); // Debug.Log("dict['int']: " + (long) dict["int"]); // ints come out as longs // Debug.Log("dict['unicode']: " + (string) dict["unicode"]); // @@ -67,7 +67,7 @@ namespace Spine2_1_25 // Debug.Log("serialized: " + str); // } // } - + /// /// This class encodes and decodes JSON strings. /// Spec. details, see http://www.json.org/ @@ -75,24 +75,29 @@ namespace Spine2_1_25 /// JSON uses Arrays and Objects. These correspond here to the datatypes IList and IDictionary. /// All numbers are parsed to floats. /// - public static class Json { + public static class Json + { /// /// Parses the string json into a value /// /// A JSON string. - /// An List<object>, a Dictionary<string, object>, a float, an integer,a string, null, true, or false - public static object Deserialize (TextReader json) { - if (json == null) { + /// An List<object>, a Dictionary<string, object>, a float, an integer,a string, null, true, or false + public static object Deserialize(TextReader json) + { + if (json == null) + { return null; - } + } return Parser.Parse(json); } - - sealed class Parser : IDisposable { + + sealed class Parser : IDisposable + { const string WHITE_SPACE = " \t\n\r"; const string WORD_BREAK = " \t\n\r{}[],:\""; - - enum TOKEN { + + enum TOKEN + { NONE, CURLY_OPEN, CURLY_CLOSE, @@ -106,434 +111,499 @@ enum TOKEN { FALSE, NULL }; - + TextReader json; - - Parser(TextReader reader) { - json = reader; + + Parser(TextReader reader) + { + json = reader; } - public static object Parse (TextReader reader) { - using (var instance = new Parser(reader)) { + public static object Parse(TextReader reader) + { + using (var instance = new Parser(reader)) + { return instance.ParseValue(); } } - - public void Dispose() { + + public void Dispose() + { json.Dispose(); json = null; } - - Dictionary ParseObject() { + + Dictionary ParseObject() + { Dictionary table = new Dictionary(); - + // ditch opening brace json.Read(); - + // { - while (true) { - switch (NextToken) { - case TOKEN.NONE: - return null; - case TOKEN.COMMA: - continue; - case TOKEN.CURLY_CLOSE: - return table; - default: - // name - string name = ParseString(); - if (name == null) { - return null; - } - - // : - if (NextToken != TOKEN.COLON) { + while (true) + { + switch (NextToken) + { + case TOKEN.NONE: return null; - } - // ditch the colon - json.Read(); - - // value - table[name] = ParseValue(); - break; + case TOKEN.COMMA: + continue; + case TOKEN.CURLY_CLOSE: + return table; + default: + // name + string name = ParseString(); + if (name == null) + { + return null; + } + + // : + if (NextToken != TOKEN.COLON) + { + return null; + } + // ditch the colon + json.Read(); + + // value + table[name] = ParseValue(); + break; } } } - - List ParseArray() { + + List ParseArray() + { List array = new List(); - + // ditch opening bracket json.Read(); - + // [ var parsing = true; - while (parsing) { + while (parsing) + { TOKEN nextToken = NextToken; - - switch (nextToken) { - case TOKEN.NONE: - return null; - case TOKEN.COMMA: - continue; - case TOKEN.SQUARED_CLOSE: - parsing = false; - break; - default: - object value = ParseByToken(nextToken); - - array.Add(value); - break; + + switch (nextToken) + { + case TOKEN.NONE: + return null; + case TOKEN.COMMA: + continue; + case TOKEN.SQUARED_CLOSE: + parsing = false; + break; + default: + object value = ParseByToken(nextToken); + + array.Add(value); + break; } } - + return array; } - - object ParseValue() { + + object ParseValue() + { TOKEN nextToken = NextToken; return ParseByToken(nextToken); } - - object ParseByToken(TOKEN token) { - switch (token) { - case TOKEN.STRING: - return ParseString(); - case TOKEN.NUMBER: - return ParseNumber(); - case TOKEN.CURLY_OPEN: - return ParseObject(); - case TOKEN.SQUARED_OPEN: - return ParseArray(); - case TOKEN.TRUE: - return true; - case TOKEN.FALSE: - return false; - case TOKEN.NULL: - return null; - default: - return null; + + object ParseByToken(TOKEN token) + { + switch (token) + { + case TOKEN.STRING: + return ParseString(); + case TOKEN.NUMBER: + return ParseNumber(); + case TOKEN.CURLY_OPEN: + return ParseObject(); + case TOKEN.SQUARED_OPEN: + return ParseArray(); + case TOKEN.TRUE: + return true; + case TOKEN.FALSE: + return false; + case TOKEN.NULL: + return null; + default: + return null; } } - - string ParseString() { + + string ParseString() + { StringBuilder s = new StringBuilder(); char c; - + // ditch opening quote json.Read(); - + bool parsing = true; - while (parsing) { - - if (json.Peek() == -1) { + while (parsing) + { + + if (json.Peek() == -1) + { parsing = false; break; } - + c = NextChar; - switch (c) { - case '"': - parsing = false; - break; - case '\\': - if (json.Peek() == -1) { + switch (c) + { + case '"': parsing = false; break; - } - - c = NextChar; - switch (c) { - case '"': case '\\': - case '/': - s.Append(c); - break; - case 'b': - s.Append('\b'); - break; - case 'f': - s.Append('\f'); - break; - case 'n': - s.Append('\n'); - break; - case 'r': - s.Append('\r'); - break; - case 't': - s.Append('\t'); - break; - case 'u': - var hex = new StringBuilder(); - - for (int i=0; i< 4; i++) { - hex.Append(NextChar); + if (json.Peek() == -1) + { + parsing = false; + break; + } + + c = NextChar; + switch (c) + { + case '"': + case '\\': + case '/': + s.Append(c); + break; + case 'b': + s.Append('\b'); + break; + case 'f': + s.Append('\f'); + break; + case 'n': + s.Append('\n'); + break; + case 'r': + s.Append('\r'); + break; + case 't': + s.Append('\t'); + break; + case 'u': + var hex = new StringBuilder(); + + for (int i = 0; i < 4; i++) + { + hex.Append(NextChar); + } + + s.Append((char)Convert.ToInt32(hex.ToString(), 16)); + break; } - - s.Append((char) Convert.ToInt32(hex.ToString(), 16)); break; - } - break; - default: - s.Append(c); - break; + default: + s.Append(c); + break; } } - + return s.ToString(); } - - object ParseNumber() { + + object ParseNumber() + { string number = NextWord; - float parsedFloat; - float.TryParse(number, NumberStyles.Float, CultureInfo.InvariantCulture, out parsedFloat); - return parsedFloat; + float parsedFloat; + float.TryParse(number, NumberStyles.Float, CultureInfo.InvariantCulture, out parsedFloat); + return parsedFloat; } - - void EatWhitespace() { - while (WHITE_SPACE.IndexOf(PeekChar) != -1) { + + void EatWhitespace() + { + while (WHITE_SPACE.IndexOf(PeekChar) != -1) + { json.Read(); - - if (json.Peek() == -1) { + + if (json.Peek() == -1) + { break; } } } - - char PeekChar { - get { + + char PeekChar + { + get + { return Convert.ToChar(json.Peek()); } } - - char NextChar { - get { + + char NextChar + { + get + { return Convert.ToChar(json.Read()); } } - - string NextWord { - get { + + string NextWord + { + get + { StringBuilder word = new StringBuilder(); - - while (WORD_BREAK.IndexOf(PeekChar) == -1) { + + while (WORD_BREAK.IndexOf(PeekChar) == -1) + { word.Append(NextChar); - - if (json.Peek() == -1) { + + if (json.Peek() == -1) + { break; } } - + return word.ToString(); } } - - TOKEN NextToken { - get { + + TOKEN NextToken + { + get + { EatWhitespace(); - - if (json.Peek() == -1) { + + if (json.Peek() == -1) + { return TOKEN.NONE; } - + char c = PeekChar; - switch (c) { - case '{': - return TOKEN.CURLY_OPEN; - case '}': - json.Read(); - return TOKEN.CURLY_CLOSE; - case '[': - return TOKEN.SQUARED_OPEN; - case ']': - json.Read(); - return TOKEN.SQUARED_CLOSE; - case ',': - json.Read(); - return TOKEN.COMMA; - case '"': - return TOKEN.STRING; - case ':': - return TOKEN.COLON; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case '-': - return TOKEN.NUMBER; + switch (c) + { + case '{': + return TOKEN.CURLY_OPEN; + case '}': + json.Read(); + return TOKEN.CURLY_CLOSE; + case '[': + return TOKEN.SQUARED_OPEN; + case ']': + json.Read(); + return TOKEN.SQUARED_CLOSE; + case ',': + json.Read(); + return TOKEN.COMMA; + case '"': + return TOKEN.STRING; + case ':': + return TOKEN.COLON; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + return TOKEN.NUMBER; } - + string word = NextWord; - - switch (word) { - case "false": - return TOKEN.FALSE; - case "true": - return TOKEN.TRUE; - case "null": - return TOKEN.NULL; + + switch (word) + { + case "false": + return TOKEN.FALSE; + case "true": + return TOKEN.TRUE; + case "null": + return TOKEN.NULL; } - + return TOKEN.NONE; } } } - + /// /// Converts a IDictionary / IList object or a simple type (string, int, etc.) into a JSON string /// /// A Dictionary<string, object> / List<object> /// A JSON encoded string, or null if object 'json' is not serializable - public static string Serialize(object obj) { + public static string Serialize(object obj) + { return Serializer.Serialize(obj); } - - sealed class Serializer { + + sealed class Serializer + { StringBuilder builder; - - Serializer() { + + Serializer() + { builder = new StringBuilder(); } - - public static string Serialize(object obj) { + + public static string Serialize(object obj) + { var instance = new Serializer(); - + instance.SerializeValue(obj); - + return instance.builder.ToString(); } - - void SerializeValue(object value) { + + void SerializeValue(object value) + { IList asList; IDictionary asDict; string asStr; - - if (value == null) { + + if (value == null) + { builder.Append("null"); } - else if ((asStr = value as string) != null) { + else if ((asStr = value as string) != null) + { SerializeString(asStr); } - else if (value is bool) { + else if (value is bool) + { builder.Append(value.ToString().ToLower()); } - else if ((asList = value as IList) != null) { + else if ((asList = value as IList) != null) + { SerializeArray(asList); } - else if ((asDict = value as IDictionary) != null) { + else if ((asDict = value as IDictionary) != null) + { SerializeObject(asDict); } - else if (value is char) { + else if (value is char) + { SerializeString(value.ToString()); } - else { + else + { SerializeOther(value); } } - - void SerializeObject(IDictionary obj) { + + void SerializeObject(IDictionary obj) + { bool first = true; - + builder.Append('{'); - - foreach (object e in obj.Keys) { - if (!first) { + + foreach (object e in obj.Keys) + { + if (!first) + { builder.Append(','); } - + SerializeString(e.ToString()); builder.Append(':'); - + SerializeValue(obj[e]); - + first = false; } - + builder.Append('}'); } - - void SerializeArray(IList anArray) { + + void SerializeArray(IList anArray) + { builder.Append('['); - + bool first = true; - - foreach (object obj in anArray) { - if (!first) { + + foreach (object obj in anArray) + { + if (!first) + { builder.Append(','); } - + SerializeValue(obj); - + first = false; } - + builder.Append(']'); } - - void SerializeString(string str) { + + void SerializeString(string str) + { builder.Append('\"'); - + char[] charArray = str.ToCharArray(); - foreach (var c in charArray) { - switch (c) { - case '"': - builder.Append("\\\""); - break; - case '\\': - builder.Append("\\\\"); - break; - case '\b': - builder.Append("\\b"); - break; - case '\f': - builder.Append("\\f"); - break; - case '\n': - builder.Append("\\n"); - break; - case '\r': - builder.Append("\\r"); - break; - case '\t': - builder.Append("\\t"); - break; - default: - int codepoint = Convert.ToInt32(c); - if ((codepoint >= 32) && (codepoint <= 126)) { - builder.Append(c); - } - else { - builder.Append("\\u" + Convert.ToString(codepoint, 16).PadLeft(4, '0')); - } - break; + foreach (var c in charArray) + { + switch (c) + { + case '"': + builder.Append("\\\""); + break; + case '\\': + builder.Append("\\\\"); + break; + case '\b': + builder.Append("\\b"); + break; + case '\f': + builder.Append("\\f"); + break; + case '\n': + builder.Append("\\n"); + break; + case '\r': + builder.Append("\\r"); + break; + case '\t': + builder.Append("\\t"); + break; + default: + int codepoint = Convert.ToInt32(c); + if ((codepoint >= 32) && (codepoint <= 126)) + { + builder.Append(c); + } + else + { + builder.Append("\\u" + Convert.ToString(codepoint, 16).PadLeft(4, '0')); + } + break; } } - + builder.Append('\"'); } - - void SerializeOther(object value) { + + void SerializeOther(object value) + { if (value is float || value is int || value is uint || value is long - || value is float + || value is float || value is sbyte || value is byte || value is short || value is ushort || value is ulong - || value is decimal) { + || value is decimal) + { builder.Append(value.ToString()); } - else { + else + { SerializeString(value.ToString()); } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Skeleton.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Skeleton.cs index 2058cfa..52c0cca 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Skeleton.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Skeleton.cs @@ -31,276 +31,318 @@ using System; using System.Collections.Generic; -namespace Spine2_1_25 { - public class Skeleton { - internal SkeletonData data; - internal List bones; - internal List slots; - internal List drawOrder; - internal List ikConstraints; - private List> boneCache = new List>(); - internal Skin skin; - internal float r = 1, g = 1, b = 1, a = 1; - internal float time; - internal bool flipX, flipY; - internal float x, y; +namespace Spine2_1_25 +{ + public class Skeleton + { + internal SkeletonData data; + internal List bones; + internal List slots; + internal List drawOrder; + internal List ikConstraints; + private List> boneCache = new List>(); + internal Skin skin; + internal float r = 1, g = 1, b = 1, a = 1; + internal float time; + internal bool flipX, flipY; + internal float x, y; - public SkeletonData Data { get { return data; } } - public List Bones { get { return bones; } } - public List Slots { get { return slots; } } - public List DrawOrder { get { return drawOrder; } } - public List IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } } - public Skin Skin { get { return skin; } set { skin = value; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } - public float Time { get { return time; } set { time = value; } } - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public bool FlipX { get { return flipX; } set { flipX = value; } } - public bool FlipY { get { return flipY; } set { flipY = value; } } + public SkeletonData Data { get { return data; } } + public List Bones { get { return bones; } } + public List Slots { get { return slots; } } + public List DrawOrder { get { return drawOrder; } } + public List IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } } + public Skin Skin { get { return skin; } set { skin = value; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } + public float Time { get { return time; } set { time = value; } } + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public bool FlipX { get { return flipX; } set { flipX = value; } } + public bool FlipY { get { return flipY; } set { flipY = value; } } - public Bone RootBone { - get { - return bones.Count == 0 ? null : bones[0]; - } - } + public Bone RootBone + { + get + { + return bones.Count == 0 ? null : bones[0]; + } + } - public Skeleton (SkeletonData data) { - if (data == null) throw new ArgumentNullException("data cannot be null."); - this.data = data; + public Skeleton(SkeletonData data) + { + if (data == null) throw new ArgumentNullException("data cannot be null."); + this.data = data; - bones = new List(data.bones.Count); - foreach (BoneData boneData in data.bones) { - Bone parent = boneData.parent == null ? null : bones[data.bones.IndexOf(boneData.parent)]; - Bone bone = new Bone(boneData, this, parent); - if (parent != null) parent.children.Add(bone); - bones.Add(bone); - } + bones = new List(data.bones.Count); + foreach (BoneData boneData in data.bones) + { + Bone parent = boneData.parent == null ? null : bones[data.bones.IndexOf(boneData.parent)]; + Bone bone = new Bone(boneData, this, parent); + if (parent != null) parent.children.Add(bone); + bones.Add(bone); + } - slots = new List(data.slots.Count); - drawOrder = new List(data.slots.Count); - foreach (SlotData slotData in data.slots) { - Bone bone = bones[data.bones.IndexOf(slotData.boneData)]; - Slot slot = new Slot(slotData, bone); - slots.Add(slot); - drawOrder.Add(slot); - } + slots = new List(data.slots.Count); + drawOrder = new List(data.slots.Count); + foreach (SlotData slotData in data.slots) + { + Bone bone = bones[data.bones.IndexOf(slotData.boneData)]; + Slot slot = new Slot(slotData, bone); + slots.Add(slot); + drawOrder.Add(slot); + } - ikConstraints = new List(data.ikConstraints.Count); - foreach (IkConstraintData ikConstraintData in data.ikConstraints) - ikConstraints.Add(new IkConstraint(ikConstraintData, this)); + ikConstraints = new List(data.ikConstraints.Count); + foreach (IkConstraintData ikConstraintData in data.ikConstraints) + ikConstraints.Add(new IkConstraint(ikConstraintData, this)); - UpdateCache(); - } + UpdateCache(); + } - /// Caches information about bones and IK constraints. Must be called if bones or IK constraints are added or - /// removed. - public void UpdateCache () { - List> boneCache = this.boneCache; - List ikConstraints = this.ikConstraints; - int ikConstraintsCount = ikConstraints.Count; + /// Caches information about bones and IK constraints. Must be called if bones or IK constraints are added or + /// removed. + public void UpdateCache() + { + List> boneCache = this.boneCache; + List ikConstraints = this.ikConstraints; + int ikConstraintsCount = ikConstraints.Count; - int arrayCount = ikConstraintsCount + 1; - if (boneCache.Count > arrayCount) boneCache.RemoveRange(arrayCount, boneCache.Count - arrayCount); - for (int i = 0, n = boneCache.Count; i < n; i++) - boneCache[i].Clear(); - while (boneCache.Count < arrayCount) - boneCache.Add(new List()); + int arrayCount = ikConstraintsCount + 1; + if (boneCache.Count > arrayCount) boneCache.RemoveRange(arrayCount, boneCache.Count - arrayCount); + for (int i = 0, n = boneCache.Count; i < n; i++) + boneCache[i].Clear(); + while (boneCache.Count < arrayCount) + boneCache.Add(new List()); - List nonIkBones = boneCache[0]; + List nonIkBones = boneCache[0]; - for (int i = 0, n = bones.Count; i < n; i++) { - Bone bone = bones[i]; - Bone current = bone; - do { - for (int ii = 0; ii < ikConstraintsCount; ii++) { - IkConstraint ikConstraint = ikConstraints[ii]; - Bone parent = ikConstraint.bones[0]; - Bone child = ikConstraint.bones[ikConstraint.bones.Count - 1]; - while (true) { - if (current == child) { - boneCache[ii].Add(bone); - boneCache[ii + 1].Add(bone); - goto outer; - } - if (child == parent) break; - child = child.parent; - } - } - current = current.parent; - } while (current != null); - nonIkBones.Add(bone); - outer: {} - } - } + for (int i = 0, n = bones.Count; i < n; i++) + { + Bone bone = bones[i]; + Bone current = bone; + do + { + for (int ii = 0; ii < ikConstraintsCount; ii++) + { + IkConstraint ikConstraint = ikConstraints[ii]; + Bone parent = ikConstraint.bones[0]; + Bone child = ikConstraint.bones[ikConstraint.bones.Count - 1]; + while (true) + { + if (current == child) + { + boneCache[ii].Add(bone); + boneCache[ii + 1].Add(bone); + goto outer; + } + if (child == parent) break; + child = child.parent; + } + } + current = current.parent; + } while (current != null); + nonIkBones.Add(bone); + outer: { } + } + } - /// Updates the world transform for each bone and applies IK constraints. - public void UpdateWorldTransform () { - List bones = this.bones; - for (int ii = 0, nn = bones.Count; ii < nn; ii++) { - Bone bone = bones[ii]; - bone.rotationIK = bone.rotation; - } - List> boneCache = this.boneCache; - List ikConstraints = this.ikConstraints; - int i = 0, last = boneCache.Count - 1; - while (true) { - List updateBones = boneCache[i]; - for (int ii = 0, nn = updateBones.Count; ii < nn; ii++) - updateBones[ii].UpdateWorldTransform(); - if (i == last) break; - ikConstraints[i].apply(); - i++; - } - } + /// Updates the world transform for each bone and applies IK constraints. + public void UpdateWorldTransform() + { + List bones = this.bones; + for (int ii = 0, nn = bones.Count; ii < nn; ii++) + { + Bone bone = bones[ii]; + bone.rotationIK = bone.rotation; + } + List> boneCache = this.boneCache; + List ikConstraints = this.ikConstraints; + int i = 0, last = boneCache.Count - 1; + while (true) + { + List updateBones = boneCache[i]; + for (int ii = 0, nn = updateBones.Count; ii < nn; ii++) + updateBones[ii].UpdateWorldTransform(); + if (i == last) break; + ikConstraints[i].apply(); + i++; + } + } - /// Sets the bones and slots to their setup pose values. - public void SetToSetupPose () { - SetBonesToSetupPose(); - SetSlotsToSetupPose(); - } + /// Sets the bones and slots to their setup pose values. + public void SetToSetupPose() + { + SetBonesToSetupPose(); + SetSlotsToSetupPose(); + } - public void SetBonesToSetupPose () { - List bones = this.bones; - for (int i = 0, n = bones.Count; i < n; i++) - bones[i].SetToSetupPose(); + public void SetBonesToSetupPose() + { + List bones = this.bones; + for (int i = 0, n = bones.Count; i < n; i++) + bones[i].SetToSetupPose(); - List ikConstraints = this.ikConstraints; - for (int i = 0, n = ikConstraints.Count; i < n; i++) { - IkConstraint ikConstraint = ikConstraints[i]; - ikConstraint.bendDirection = ikConstraint.data.bendDirection; - ikConstraint.mix = ikConstraint.data.mix; - } - } + List ikConstraints = this.ikConstraints; + for (int i = 0, n = ikConstraints.Count; i < n; i++) + { + IkConstraint ikConstraint = ikConstraints[i]; + ikConstraint.bendDirection = ikConstraint.data.bendDirection; + ikConstraint.mix = ikConstraint.data.mix; + } + } - public void SetSlotsToSetupPose () { - List slots = this.slots; - drawOrder.Clear(); - drawOrder.AddRange(slots); - for (int i = 0, n = slots.Count; i < n; i++) - slots[i].SetToSetupPose(i); - } + public void SetSlotsToSetupPose() + { + List slots = this.slots; + drawOrder.Clear(); + drawOrder.AddRange(slots); + for (int i = 0, n = slots.Count; i < n; i++) + slots[i].SetToSetupPose(i); + } - /// May be null. - public Bone FindBone (String boneName) { - if (boneName == null) throw new ArgumentNullException("boneName cannot be null."); - List bones = this.bones; - for (int i = 0, n = bones.Count; i < n; i++) { - Bone bone = bones[i]; - if (bone.data.name == boneName) return bone; - } - return null; - } + /// May be null. + public Bone FindBone(String boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName cannot be null."); + List bones = this.bones; + for (int i = 0, n = bones.Count; i < n; i++) + { + Bone bone = bones[i]; + if (bone.data.name == boneName) return bone; + } + return null; + } - /// -1 if the bone was not found. - public int FindBoneIndex (String boneName) { - if (boneName == null) throw new ArgumentNullException("boneName cannot be null."); - List bones = this.bones; - for (int i = 0, n = bones.Count; i < n; i++) - if (bones[i].data.name == boneName) return i; - return -1; - } + /// -1 if the bone was not found. + public int FindBoneIndex(String boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName cannot be null."); + List bones = this.bones; + for (int i = 0, n = bones.Count; i < n; i++) + if (bones[i].data.name == boneName) return i; + return -1; + } - /// May be null. - public Slot FindSlot (String slotName) { - if (slotName == null) throw new ArgumentNullException("slotName cannot be null."); - List slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) { - Slot slot = slots[i]; - if (slot.data.name == slotName) return slot; - } - return null; - } + /// May be null. + public Slot FindSlot(String slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName cannot be null."); + List slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + { + Slot slot = slots[i]; + if (slot.data.name == slotName) return slot; + } + return null; + } - /// -1 if the bone was not found. - public int FindSlotIndex (String slotName) { - if (slotName == null) throw new ArgumentNullException("slotName cannot be null."); - List slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) - if (slots[i].data.name.Equals(slotName)) return i; - return -1; - } + /// -1 if the bone was not found. + public int FindSlotIndex(String slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName cannot be null."); + List slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + if (slots[i].data.name.Equals(slotName)) return i; + return -1; + } - /// Sets a skin by name (see SetSkin). - public void SetSkin (String skinName) { - Skin skin = data.FindSkin(skinName); - if (skin == null) throw new ArgumentException("Skin not found: " + skinName); - SetSkin(skin); - } + /// Sets a skin by name (see SetSkin). + public void SetSkin(String skinName) + { + Skin skin = data.FindSkin(skinName); + if (skin == null) throw new ArgumentException("Skin not found: " + skinName); + SetSkin(skin); + } - /// Sets the skin used to look up attachments before looking in the {@link SkeletonData#getDefaultSkin() default - /// skin}. Attachmentsfrom 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. - /// May be null. - public void SetSkin (Skin newSkin) { - if (newSkin != null) { - if (skin != null) - newSkin.AttachAll(this, skin); - else { - List slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) { - Slot slot = slots[i]; - String name = slot.data.attachmentName; - if (name != null) { - Attachment attachment = newSkin.GetAttachment(i, name); - if (attachment != null) slot.Attachment = attachment; - } - } - } - } - skin = newSkin; - } + /// Sets the skin used to look up attachments before looking in the {@link SkeletonData#getDefaultSkin() default + /// skin}. Attachmentsfrom 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. + /// May be null. + public void SetSkin(Skin newSkin) + { + if (newSkin != null) + { + if (skin != null) + newSkin.AttachAll(this, skin); + else + { + List slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + { + Slot slot = slots[i]; + String name = slot.data.attachmentName; + if (name != null) + { + Attachment attachment = newSkin.GetAttachment(i, name); + if (attachment != null) slot.Attachment = attachment; + } + } + } + } + skin = newSkin; + } - /// May be null. - public Attachment GetAttachment (String slotName, String attachmentName) { - return GetAttachment(data.FindSlotIndex(slotName), attachmentName); - } + /// May be null. + public Attachment GetAttachment(String slotName, String attachmentName) + { + return GetAttachment(data.FindSlotIndex(slotName), attachmentName); + } - /// May be null. - public Attachment GetAttachment (int slotIndex, String attachmentName) { - if (attachmentName == null) throw new ArgumentNullException("attachmentName cannot be null."); - if (skin != null) { - Attachment attachment = skin.GetAttachment(slotIndex, attachmentName); - if (attachment != null) return attachment; - } - if (data.defaultSkin != null) return data.defaultSkin.GetAttachment(slotIndex, attachmentName); - return null; - } + /// May be null. + public Attachment GetAttachment(int slotIndex, String attachmentName) + { + if (attachmentName == null) throw new ArgumentNullException("attachmentName cannot be null."); + if (skin != null) + { + Attachment attachment = skin.GetAttachment(slotIndex, attachmentName); + if (attachment != null) return attachment; + } + if (data.defaultSkin != null) return data.defaultSkin.GetAttachment(slotIndex, attachmentName); + return null; + } - /// May be null. - public void SetAttachment (String slotName, String attachmentName) { - if (slotName == null) throw new ArgumentNullException("slotName cannot be null."); - List slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) { - Slot slot = slots[i]; - if (slot.data.name == slotName) { - Attachment attachment = null; - if (attachmentName != null) { - attachment = GetAttachment(i, attachmentName); - if (attachment == null) throw new Exception("Attachment not found: " + attachmentName + ", for slot: " + slotName); - } - slot.Attachment = attachment; - return; - } - } - throw new Exception("Slot not found: " + slotName); - } + /// May be null. + public void SetAttachment(String slotName, String attachmentName) + { + if (slotName == null) throw new ArgumentNullException("slotName cannot be null."); + List slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + { + Slot slot = slots[i]; + if (slot.data.name == slotName) + { + Attachment attachment = null; + if (attachmentName != null) + { + attachment = GetAttachment(i, attachmentName); + if (attachment == null) throw new Exception("Attachment not found: " + attachmentName + ", for slot: " + slotName); + } + slot.Attachment = attachment; + return; + } + } + throw new Exception("Slot not found: " + slotName); + } - /** @return May be null. */ - public IkConstraint FindIkConstraint (String ikConstraintName) { - if (ikConstraintName == null) throw new ArgumentNullException("ikConstraintName cannot be null."); - List ikConstraints = this.ikConstraints; - for (int i = 0, n = ikConstraints.Count; i < n; i++) { - IkConstraint ikConstraint = ikConstraints[i]; - if (ikConstraint.data.name == ikConstraintName) return ikConstraint; - } - return null; - } + /** @return May be null. */ + public IkConstraint FindIkConstraint(String ikConstraintName) + { + if (ikConstraintName == null) throw new ArgumentNullException("ikConstraintName cannot be null."); + List ikConstraints = this.ikConstraints; + for (int i = 0, n = ikConstraints.Count; i < n; i++) + { + IkConstraint ikConstraint = ikConstraints[i]; + if (ikConstraint.data.name == ikConstraintName) return ikConstraint; + } + return null; + } - public void Update (float delta) { - time += delta; - } - } + public void Update(float delta) + { + time += delta; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/SkeletonBinary.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/SkeletonBinary.cs index 20b1d30..71296f9 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/SkeletonBinary.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/SkeletonBinary.cs @@ -29,42 +29,46 @@ *****************************************************************************/ using System; -using System.IO; using System.Collections.Generic; +using System.IO; #if WINDOWS_STOREAPP using System.Threading.Tasks; using Windows.Storage; #endif -namespace Spine2_1_25 { - public class SkeletonBinary { - public const int TIMELINE_SCALE = 0; - public const int TIMELINE_ROTATE = 1; - public const int TIMELINE_TRANSLATE = 2; - public const int TIMELINE_ATTACHMENT = 3; - public const int TIMELINE_COLOR = 4; - public const int TIMELINE_FLIPX = 5; - public const int TIMELINE_FLIPY = 6; - - public const int CURVE_LINEAR = 0; - public const int CURVE_STEPPED = 1; - public const int CURVE_BEZIER = 2; - - private AttachmentLoader attachmentLoader; - public float Scale { get; set; } - private char[] chars = new char[32]; - private byte[] buffer = new byte[4]; - - public SkeletonBinary (params Atlas[] atlasArray) - : this(new AtlasAttachmentLoader(atlasArray)) { - } - - public SkeletonBinary (AttachmentLoader attachmentLoader) { - if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader cannot be null."); - this.attachmentLoader = attachmentLoader; - Scale = 1; - } +namespace Spine2_1_25 +{ + public class SkeletonBinary + { + public const int TIMELINE_SCALE = 0; + public const int TIMELINE_ROTATE = 1; + public const int TIMELINE_TRANSLATE = 2; + public const int TIMELINE_ATTACHMENT = 3; + public const int TIMELINE_COLOR = 4; + public const int TIMELINE_FLIPX = 5; + public const int TIMELINE_FLIPY = 6; + + public const int CURVE_LINEAR = 0; + public const int CURVE_STEPPED = 1; + public const int CURVE_BEZIER = 2; + + private AttachmentLoader attachmentLoader; + public float Scale { get; set; } + private char[] chars = new char[32]; + private byte[] buffer = new byte[4]; + + public SkeletonBinary(params Atlas[] atlasArray) + : this(new AtlasAttachmentLoader(atlasArray)) + { + } + + public SkeletonBinary(AttachmentLoader attachmentLoader) + { + if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader cannot be null."); + this.attachmentLoader = attachmentLoader; + Scale = 1; + } #if WINDOWS_STOREAPP private async Task ReadFile(string path) { @@ -80,584 +84,663 @@ public SkeletonData ReadSkeletonData (String path) { return this.ReadFile(path).Result; } #else - public SkeletonData ReadSkeletonData (String path) { + public SkeletonData ReadSkeletonData(String path) + { #if WINDOWS_PHONE using (var input = new BufferedStream(Microsoft.Xna.Framework.TitleContainer.OpenStream(path))) { #else - using (var input = new BufferedStream(new FileStream(path, FileMode.Open))) { + using (var input = new BufferedStream(new FileStream(path, FileMode.Open))) + { #endif - SkeletonData skeletonData = ReadSkeletonData(input); - skeletonData.name = Path.GetFileNameWithoutExtension(path); - return skeletonData; - } - } + SkeletonData skeletonData = ReadSkeletonData(input); + skeletonData.name = Path.GetFileNameWithoutExtension(path); + return skeletonData; + } + } #endif - public SkeletonData ReadSkeletonData (Stream input) { - if (input == null) throw new ArgumentNullException("input cannot be null."); - float scale = Scale; - - var skeletonData = new SkeletonData(); - skeletonData.hash = ReadString(input); - if (skeletonData.hash.Length == 0) skeletonData.hash = null; - skeletonData.version = ReadString(input); - if (skeletonData.version.Length == 0) skeletonData.version = null; - skeletonData.width = ReadFloat(input); - skeletonData.height = ReadFloat(input); - - bool nonessential = ReadBoolean(input); - - if (nonessential) { - skeletonData.imagesPath = ReadString(input); - if (skeletonData.imagesPath.Length == 0) skeletonData.imagesPath = null; - } - - // Bones. - for (int i = 0, n = ReadInt(input, true); i < n; i++) { - String name = ReadString(input); - BoneData parent = null; - int parentIndex = ReadInt(input, true) - 1; - if (parentIndex != -1) parent = skeletonData.bones[parentIndex]; - BoneData boneData = new BoneData(name, parent); - boneData.x = ReadFloat(input) * scale; - boneData.y = ReadFloat(input) * scale; - boneData.scaleX = ReadFloat(input); - boneData.scaleY = ReadFloat(input); - boneData.rotation = ReadFloat(input); - boneData.length = ReadFloat(input) * scale; - boneData.flipX = ReadBoolean(input); - boneData.flipY = ReadBoolean(input); - boneData.inheritScale = ReadBoolean(input); - boneData.inheritRotation = ReadBoolean(input); - if (nonessential) ReadInt(input); // Skip bone color. - skeletonData.bones.Add(boneData); - } - - // IK constraints. - for (int i = 0, n = ReadInt(input, true); i < n; i++) { - IkConstraintData ikConstraintData = new IkConstraintData(ReadString(input)); - for (int ii = 0, nn = ReadInt(input, true); ii < nn; ii++) - ikConstraintData.bones.Add(skeletonData.bones[ReadInt(input, true)]); - ikConstraintData.target = skeletonData.bones[ReadInt(input, true)]; - ikConstraintData.mix = ReadFloat(input); - ikConstraintData.bendDirection = ReadSByte(input); - skeletonData.ikConstraints.Add(ikConstraintData); - } - - // Slots. - for (int i = 0, n = ReadInt(input, true); i < n; i++) { - String slotName = ReadString(input); - BoneData boneData = skeletonData.bones[ReadInt(input, true)]; - SlotData slotData = new SlotData(slotName, boneData); - int color = ReadInt(input); - slotData.r = ((color & 0xff000000) >> 24) / 255f; - slotData.g = ((color & 0x00ff0000) >> 16) / 255f; - slotData.b = ((color & 0x0000ff00) >> 8) / 255f; - slotData.a = ((color & 0x000000ff)) / 255f; - slotData.attachmentName = ReadString(input); - slotData.additiveBlending = ReadBoolean(input); - skeletonData.slots.Add(slotData); - } - - // Default skin. - Skin defaultSkin = ReadSkin(input, "default", nonessential); - if (defaultSkin != null) { - skeletonData.defaultSkin = defaultSkin; - skeletonData.skins.Add(defaultSkin); - } - - // Skins. - for (int i = 0, n = ReadInt(input, true); i < n; i++) - skeletonData.skins.Add(ReadSkin(input, ReadString(input), nonessential)); - - // Events. - for (int i = 0, n = ReadInt(input, true); i < n; i++) { - EventData eventData = new EventData(ReadString(input)); - eventData.Int = ReadInt(input, false); - eventData.Float = ReadFloat(input); - eventData.String = ReadString(input); - skeletonData.events.Add(eventData); - } - - // Animations. - for (int i = 0, n = ReadInt(input, true); i < n; i++) - ReadAnimation(ReadString(input), input, skeletonData); - - skeletonData.bones.TrimExcess(); - skeletonData.slots.TrimExcess(); - skeletonData.skins.TrimExcess(); - skeletonData.events.TrimExcess(); - skeletonData.animations.TrimExcess(); - skeletonData.ikConstraints.TrimExcess(); - return skeletonData; - } - - /** @return May be null. */ - private Skin ReadSkin (Stream input, String skinName, bool nonessential) { - int slotCount = ReadInt(input, true); - if (slotCount == 0) return null; - Skin skin = new Skin(skinName); - for (int i = 0; i < slotCount; i++) { - int slotIndex = ReadInt(input, true); - for (int ii = 0, nn = ReadInt(input, true); ii < nn; ii++) { - String name = ReadString(input); - skin.AddAttachment(slotIndex, name, ReadAttachment(input, skin, name, nonessential)); - } - } - return skin; - } - - private Attachment ReadAttachment (Stream input, Skin skin, String attachmentName, bool nonessential) { - float scale = Scale; - - String name = ReadString(input); - if (name == null) name = attachmentName; - - switch ((AttachmentType)input.ReadByte()) { - case AttachmentType.region: { - String path = ReadString(input); - if (path == null) path = name; - RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); - if (region == null) return null; - region.Path = path; - region.x = ReadFloat(input) * scale; - region.y = ReadFloat(input) * scale; - region.scaleX = ReadFloat(input); - region.scaleY = ReadFloat(input); - region.rotation = ReadFloat(input); - region.width = ReadFloat(input) * scale; - region.height = ReadFloat(input) * scale; - int color = ReadInt(input); - region.r = ((color & 0xff000000) >> 24) / 255f; - region.g = ((color & 0x00ff0000) >> 16) / 255f; - region.b = ((color & 0x0000ff00) >> 8) / 255f; - region.a = ((color & 0x000000ff)) / 255f; - region.UpdateOffset(); - return region; - } - case AttachmentType.boundingbox: { - BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); - if (box == null) return null; - box.vertices = ReadFloatArray(input, scale); - return box; - } - case AttachmentType.mesh: { - String path = ReadString(input); - if (path == null) path = name; - MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); - if (mesh == null) return null; - mesh.Path = path; - mesh.regionUVs = ReadFloatArray(input, 1); - mesh.triangles = ReadShortArray(input); - mesh.vertices = ReadFloatArray(input, scale); - mesh.UpdateUVs(); - int color = ReadInt(input); - mesh.r = ((color & 0xff000000) >> 24) / 255f; - mesh.g = ((color & 0x00ff0000) >> 16) / 255f; - mesh.b = ((color & 0x0000ff00) >> 8) / 255f; - mesh.a = ((color & 0x000000ff)) / 255f; - mesh.HullLength = ReadInt(input, true) * 2; - if (nonessential) { - mesh.Edges = ReadIntArray(input); - mesh.Width = ReadFloat(input) * scale; - mesh.Height = ReadFloat(input) * scale; - } - return mesh; - } - case AttachmentType.skinnedmesh: { - String path = ReadString(input); - if (path == null) path = name; - SkinnedMeshAttachment mesh = attachmentLoader.NewSkinnedMeshAttachment(skin, name, path); - if (mesh == null) return null; - mesh.Path = path; - float[] uvs = ReadFloatArray(input, 1); - int[] triangles = ReadShortArray(input); - - int vertexCount = ReadInt(input, true); - var weights = new List(uvs.Length * 3 * 3); - var bones = new List(uvs.Length * 3); - for (int i = 0; i < vertexCount; i++) { - int boneCount = (int)ReadFloat(input); - bones.Add(boneCount); - for (int nn = i + boneCount * 4; i < nn; i += 4) { - bones.Add((int)ReadFloat(input)); - weights.Add(ReadFloat(input) * scale); - weights.Add(ReadFloat(input) * scale); - weights.Add(ReadFloat(input)); - } - } - mesh.bones = bones.ToArray(); - mesh.weights = weights.ToArray(); - mesh.triangles = triangles; - mesh.regionUVs = uvs; - mesh.UpdateUVs(); - int color = ReadInt(input); - mesh.r = ((color & 0xff000000) >> 24) / 255f; - mesh.g = ((color & 0x00ff0000) >> 16) / 255f; - mesh.b = ((color & 0x0000ff00) >> 8) / 255f; - mesh.a = ((color & 0x000000ff)) / 255f; - mesh.HullLength = ReadInt(input, true) * 2; - if (nonessential) { - mesh.Edges = ReadIntArray(input); - mesh.Width = ReadFloat(input) * scale; - mesh.Height = ReadFloat(input) * scale; - } - return mesh; - } - } - return null; - } - - private float[] ReadFloatArray (Stream input, float scale) { - int n = ReadInt(input, true); - float[] array = new float[n]; - if (scale == 1) { - for (int i = 0; i < n; i++) - array[i] = ReadFloat(input); - } else { - for (int i = 0; i < n; i++) - array[i] = ReadFloat(input) * scale; - } - return array; - } - - private int[] ReadShortArray (Stream input) { - int n = ReadInt(input, true); - int[] array = new int[n]; - for (int i = 0; i < n; i++) - array[i] = (input.ReadByte() << 8) + input.ReadByte(); - return array; - } - - private int[] ReadIntArray (Stream input) { - int n = ReadInt(input, true); - int[] array = new int[n]; - for (int i = 0; i < n; i++) - array[i] = ReadInt(input, true); - return array; - } - - private void ReadAnimation (String name, Stream input, SkeletonData skeletonData) { - var timelines = new List(); - float scale = Scale; - float duration = 0; - - // Slot timelines. - for (int i = 0, n = ReadInt(input, true); i < n; i++) { - int slotIndex = ReadInt(input, true); - for (int ii = 0, nn = ReadInt(input, true); ii < nn; ii++) { - int timelineType = input.ReadByte(); - int frameCount = ReadInt(input, true); - switch (timelineType) { - case TIMELINE_COLOR: { - ColorTimeline timeline = new ColorTimeline(frameCount); - timeline.slotIndex = slotIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - float time = ReadFloat(input); - int color = ReadInt(input); - float r = ((color & 0xff000000) >> 24) / 255f; - float g = ((color & 0x00ff0000) >> 16) / 255f; - float b = ((color & 0x0000ff00) >> 8) / 255f; - float a = ((color & 0x000000ff)) / 255f; - timeline.SetFrame(frameIndex, time, r, g, b, a); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[frameCount * 5 - 5]); - break; - } - case TIMELINE_ATTACHMENT: { - AttachmentTimeline timeline = new AttachmentTimeline(frameCount); - timeline.slotIndex = slotIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) - timeline.SetFrame(frameIndex, ReadFloat(input), ReadString(input)); - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[frameCount - 1]); - break; - } - } - } - } - - // Bone timelines. - for (int i = 0, n = ReadInt(input, true); i < n; i++) { - int boneIndex = ReadInt(input, true); - for (int ii = 0, nn = ReadInt(input, true); ii < nn; ii++) { - int timelineType = input.ReadByte(); - int frameCount = ReadInt(input, true); - switch (timelineType) { - case TIMELINE_ROTATE: { - RotateTimeline timeline = new RotateTimeline(frameCount); - timeline.boneIndex = boneIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input)); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[frameCount * 2 - 2]); - break; - } - case TIMELINE_TRANSLATE: - case TIMELINE_SCALE: { - TranslateTimeline timeline; - float timelineScale = 1; - if (timelineType == TIMELINE_SCALE) - timeline = new ScaleTimeline(frameCount); - else { - timeline = new TranslateTimeline(frameCount); - timelineScale = scale; - } - timeline.boneIndex = boneIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input) * timelineScale, ReadFloat(input) - * timelineScale); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[frameCount * 3 - 3]); - break; - } - case TIMELINE_FLIPX: - case TIMELINE_FLIPY: { - FlipXTimeline timeline = timelineType == TIMELINE_FLIPX ? new FlipXTimeline(frameCount) : new FlipYTimeline( - frameCount); - timeline.boneIndex = boneIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) - timeline.SetFrame(frameIndex, ReadFloat(input), ReadBoolean(input)); - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[frameCount * 2 - 2]); - break; - } - } - } - } - - // IK timelines. - for (int i = 0, n = ReadInt(input, true); i < n; i++) { - IkConstraintData ikConstraint = skeletonData.ikConstraints[ReadInt(input, true)]; - int frameCount = ReadInt(input, true); - IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount); - timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(ikConstraint); - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadSByte(input)); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[frameCount * 3 - 3]); - } - - // FFD timelines. - for (int i = 0, n = ReadInt(input, true); i < n; i++) { - Skin skin = skeletonData.skins[ReadInt(input, true)]; - for (int ii = 0, nn = ReadInt(input, true); ii < nn; ii++) { - int slotIndex = ReadInt(input, true); - for (int iii = 0, nnn = ReadInt(input, true); iii < nnn; iii++) { - Attachment attachment = skin.GetAttachment(slotIndex, ReadString(input)); - int frameCount = ReadInt(input, true); - FFDTimeline timeline = new FFDTimeline(frameCount); - timeline.slotIndex = slotIndex; - timeline.attachment = attachment; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - float time = ReadFloat(input); - - float[] vertices; - int vertexCount; - if (attachment is MeshAttachment) - vertexCount = ((MeshAttachment)attachment).vertices.Length; - else - vertexCount = ((SkinnedMeshAttachment)attachment).weights.Length / 3 * 2; - - int end = ReadInt(input, true); - if (end == 0) { - if (attachment is MeshAttachment) - vertices = ((MeshAttachment)attachment).vertices; - else - vertices = new float[vertexCount]; - } else { - vertices = new float[vertexCount]; - int start = ReadInt(input, true); - end += start; - if (scale == 1) { - for (int v = start; v < end; v++) - vertices[v] = ReadFloat(input); - } else { - for (int v = start; v < end; v++) - vertices[v] = ReadFloat(input) * scale; - } - if (attachment is MeshAttachment) { - float[] meshVertices = ((MeshAttachment)attachment).vertices; - for (int v = 0, vn = vertices.Length; v < vn; v++) - vertices[v] += meshVertices[v]; - } - } - - timeline.SetFrame(frameIndex, time, vertices); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[frameCount - 1]); - } - } - } - - // Draw order timeline. - int drawOrderCount = ReadInt(input, true); - if (drawOrderCount > 0) { - DrawOrderTimeline timeline = new DrawOrderTimeline(drawOrderCount); - int slotCount = skeletonData.slots.Count; - for (int i = 0; i < drawOrderCount; i++) { - int offsetCount = ReadInt(input, true); - int[] drawOrder = new int[slotCount]; - for (int ii = slotCount - 1; ii >= 0; ii--) - drawOrder[ii] = -1; - int[] unchanged = new int[slotCount - offsetCount]; - int originalIndex = 0, unchangedIndex = 0; - for (int ii = 0; ii < offsetCount; ii++) { - int slotIndex = ReadInt(input, true); - // Collect unchanged items. - while (originalIndex != slotIndex) - unchanged[unchangedIndex++] = originalIndex++; - // Set changed items. - drawOrder[originalIndex + ReadInt(input, true)] = originalIndex++; - } - // Collect remaining unchanged items. - while (originalIndex < slotCount) - unchanged[unchangedIndex++] = originalIndex++; - // Fill in unchanged items. - for (int ii = slotCount - 1; ii >= 0; ii--) - if (drawOrder[ii] == -1) drawOrder[ii] = unchanged[--unchangedIndex]; - timeline.SetFrame(i, ReadFloat(input), drawOrder); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[drawOrderCount - 1]); - } - - // Event timeline. - int eventCount = ReadInt(input, true); - if (eventCount > 0) { - EventTimeline timeline = new EventTimeline(eventCount); - for (int i = 0; i < eventCount; i++) { - float time = ReadFloat(input); - EventData eventData = skeletonData.events[ReadInt(input, true)]; - Event e = new Event(eventData); - e.Int = ReadInt(input, false); - e.Float = ReadFloat(input); - e.String = ReadBoolean(input) ? ReadString(input) : eventData.String; - timeline.SetFrame(i, time, e); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[eventCount - 1]); - } - - timelines.TrimExcess(); - skeletonData.animations.Add(new Animation(name, timelines, duration)); - } - - private void ReadCurve (Stream input, int frameIndex, CurveTimeline timeline) { - switch (input.ReadByte()) { - case CURVE_STEPPED: - timeline.SetStepped(frameIndex); - break; - case CURVE_BEZIER: - timeline.SetCurve(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input)); - break; - } - } - - private sbyte ReadSByte (Stream input) { - int value = input.ReadByte(); - if (value == -1) throw new EndOfStreamException(); - return (sbyte)value; - } - - private bool ReadBoolean (Stream input) { - return input.ReadByte() != 0; - } - - private float ReadFloat (Stream input) { - buffer[3] = (byte)input.ReadByte(); - buffer[2] = (byte)input.ReadByte(); - buffer[1] = (byte)input.ReadByte(); - buffer[0] = (byte)input.ReadByte(); - return BitConverter.ToSingle(buffer, 0); - } - - private int ReadInt (Stream input) { - return (input.ReadByte() << 24) + (input.ReadByte() << 16) + (input.ReadByte() << 8) + input.ReadByte(); - } - - private int ReadInt (Stream input, bool optimizePositive) { - int b = input.ReadByte(); - int result = b & 0x7F; - if ((b & 0x80) != 0) { - b = input.ReadByte(); - result |= (b & 0x7F) << 7; - if ((b & 0x80) != 0) { - b = input.ReadByte(); - result |= (b & 0x7F) << 14; - if ((b & 0x80) != 0) { - b = input.ReadByte(); - result |= (b & 0x7F) << 21; - if ((b & 0x80) != 0) { - b = input.ReadByte(); - result |= (b & 0x7F) << 28; - } - } - } - } - return optimizePositive ? result : ((result >> 1) ^ -(result & 1)); - } - - private string ReadString (Stream input) { - int charCount = ReadInt(input, true); - switch (charCount) { - case 0: - return null; - case 1: - return ""; - } - charCount--; - char[] chars = this.chars; - if (chars.Length < charCount) this.chars = chars = new char[charCount]; - // Try to read 7 bit ASCII chars. - int charIndex = 0; - int b = 0; - while (charIndex < charCount) { - b = input.ReadByte(); - if (b > 127) break; - chars[charIndex++] = (char)b; - } - // If a char was not ASCII, finish with slow path. - if (charIndex < charCount) ReadUtf8_slow(input, charCount, charIndex, b); - return new String(chars, 0, charCount); - } - - private void ReadUtf8_slow (Stream input, int charCount, int charIndex, int b) { - char[] chars = this.chars; - while (true) { - switch (b >> 4) { - case 0: - case 1: - case 2: - case 3: - case 4: - case 5: - case 6: - case 7: - chars[charIndex] = (char)b; - break; - case 12: - case 13: - chars[charIndex] = (char)((b & 0x1F) << 6 | input.ReadByte() & 0x3F); - break; - case 14: - chars[charIndex] = (char)((b & 0x0F) << 12 | (input.ReadByte() & 0x3F) << 6 | input.ReadByte() & 0x3F); - break; - } - if (++charIndex >= charCount) break; - b = input.ReadByte() & 0xFF; - } - } - } + public SkeletonData ReadSkeletonData(Stream input) + { + if (input == null) throw new ArgumentNullException("input cannot be null."); + float scale = Scale; + + var skeletonData = new SkeletonData(); + skeletonData.hash = ReadString(input); + if (skeletonData.hash.Length == 0) skeletonData.hash = null; + skeletonData.version = ReadString(input); + if (skeletonData.version.Length == 0) skeletonData.version = null; + skeletonData.width = ReadFloat(input); + skeletonData.height = ReadFloat(input); + + bool nonessential = ReadBoolean(input); + + if (nonessential) + { + skeletonData.imagesPath = ReadString(input); + if (skeletonData.imagesPath.Length == 0) skeletonData.imagesPath = null; + } + + // Bones. + for (int i = 0, n = ReadInt(input, true); i < n; i++) + { + String name = ReadString(input); + BoneData parent = null; + int parentIndex = ReadInt(input, true) - 1; + if (parentIndex != -1) parent = skeletonData.bones[parentIndex]; + BoneData boneData = new BoneData(name, parent); + boneData.x = ReadFloat(input) * scale; + boneData.y = ReadFloat(input) * scale; + boneData.scaleX = ReadFloat(input); + boneData.scaleY = ReadFloat(input); + boneData.rotation = ReadFloat(input); + boneData.length = ReadFloat(input) * scale; + boneData.flipX = ReadBoolean(input); + boneData.flipY = ReadBoolean(input); + boneData.inheritScale = ReadBoolean(input); + boneData.inheritRotation = ReadBoolean(input); + if (nonessential) ReadInt(input); // Skip bone color. + skeletonData.bones.Add(boneData); + } + + // IK constraints. + for (int i = 0, n = ReadInt(input, true); i < n; i++) + { + IkConstraintData ikConstraintData = new IkConstraintData(ReadString(input)); + for (int ii = 0, nn = ReadInt(input, true); ii < nn; ii++) + ikConstraintData.bones.Add(skeletonData.bones[ReadInt(input, true)]); + ikConstraintData.target = skeletonData.bones[ReadInt(input, true)]; + ikConstraintData.mix = ReadFloat(input); + ikConstraintData.bendDirection = ReadSByte(input); + skeletonData.ikConstraints.Add(ikConstraintData); + } + + // Slots. + for (int i = 0, n = ReadInt(input, true); i < n; i++) + { + String slotName = ReadString(input); + BoneData boneData = skeletonData.bones[ReadInt(input, true)]; + SlotData slotData = new SlotData(slotName, boneData); + int color = ReadInt(input); + slotData.r = ((color & 0xff000000) >> 24) / 255f; + slotData.g = ((color & 0x00ff0000) >> 16) / 255f; + slotData.b = ((color & 0x0000ff00) >> 8) / 255f; + slotData.a = ((color & 0x000000ff)) / 255f; + slotData.attachmentName = ReadString(input); + slotData.additiveBlending = ReadBoolean(input); + skeletonData.slots.Add(slotData); + } + + // Default skin. + Skin defaultSkin = ReadSkin(input, "default", nonessential); + if (defaultSkin != null) + { + skeletonData.defaultSkin = defaultSkin; + skeletonData.skins.Add(defaultSkin); + } + + // Skins. + for (int i = 0, n = ReadInt(input, true); i < n; i++) + skeletonData.skins.Add(ReadSkin(input, ReadString(input), nonessential)); + + // Events. + for (int i = 0, n = ReadInt(input, true); i < n; i++) + { + EventData eventData = new EventData(ReadString(input)); + eventData.Int = ReadInt(input, false); + eventData.Float = ReadFloat(input); + eventData.String = ReadString(input); + skeletonData.events.Add(eventData); + } + + // Animations. + for (int i = 0, n = ReadInt(input, true); i < n; i++) + ReadAnimation(ReadString(input), input, skeletonData); + + skeletonData.bones.TrimExcess(); + skeletonData.slots.TrimExcess(); + skeletonData.skins.TrimExcess(); + skeletonData.events.TrimExcess(); + skeletonData.animations.TrimExcess(); + skeletonData.ikConstraints.TrimExcess(); + return skeletonData; + } + + /** @return May be null. */ + private Skin ReadSkin(Stream input, String skinName, bool nonessential) + { + int slotCount = ReadInt(input, true); + if (slotCount == 0) return null; + Skin skin = new Skin(skinName); + for (int i = 0; i < slotCount; i++) + { + int slotIndex = ReadInt(input, true); + for (int ii = 0, nn = ReadInt(input, true); ii < nn; ii++) + { + String name = ReadString(input); + skin.AddAttachment(slotIndex, name, ReadAttachment(input, skin, name, nonessential)); + } + } + return skin; + } + + private Attachment ReadAttachment(Stream input, Skin skin, String attachmentName, bool nonessential) + { + float scale = Scale; + + String name = ReadString(input); + if (name == null) name = attachmentName; + + switch ((AttachmentType)input.ReadByte()) + { + case AttachmentType.region: + { + String path = ReadString(input); + if (path == null) path = name; + RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); + if (region == null) return null; + region.Path = path; + region.x = ReadFloat(input) * scale; + region.y = ReadFloat(input) * scale; + region.scaleX = ReadFloat(input); + region.scaleY = ReadFloat(input); + region.rotation = ReadFloat(input); + region.width = ReadFloat(input) * scale; + region.height = ReadFloat(input) * scale; + int color = ReadInt(input); + region.r = ((color & 0xff000000) >> 24) / 255f; + region.g = ((color & 0x00ff0000) >> 16) / 255f; + region.b = ((color & 0x0000ff00) >> 8) / 255f; + region.a = ((color & 0x000000ff)) / 255f; + region.UpdateOffset(); + return region; + } + case AttachmentType.boundingbox: + { + BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); + if (box == null) return null; + box.vertices = ReadFloatArray(input, scale); + return box; + } + case AttachmentType.mesh: + { + String path = ReadString(input); + if (path == null) path = name; + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); + if (mesh == null) return null; + mesh.Path = path; + mesh.regionUVs = ReadFloatArray(input, 1); + mesh.triangles = ReadShortArray(input); + mesh.vertices = ReadFloatArray(input, scale); + mesh.UpdateUVs(); + int color = ReadInt(input); + mesh.r = ((color & 0xff000000) >> 24) / 255f; + mesh.g = ((color & 0x00ff0000) >> 16) / 255f; + mesh.b = ((color & 0x0000ff00) >> 8) / 255f; + mesh.a = ((color & 0x000000ff)) / 255f; + mesh.HullLength = ReadInt(input, true) * 2; + if (nonessential) + { + mesh.Edges = ReadIntArray(input); + mesh.Width = ReadFloat(input) * scale; + mesh.Height = ReadFloat(input) * scale; + } + return mesh; + } + case AttachmentType.skinnedmesh: + { + String path = ReadString(input); + if (path == null) path = name; + SkinnedMeshAttachment mesh = attachmentLoader.NewSkinnedMeshAttachment(skin, name, path); + if (mesh == null) return null; + mesh.Path = path; + float[] uvs = ReadFloatArray(input, 1); + int[] triangles = ReadShortArray(input); + + int vertexCount = ReadInt(input, true); + var weights = new List(uvs.Length * 3 * 3); + var bones = new List(uvs.Length * 3); + for (int i = 0; i < vertexCount; i++) + { + int boneCount = (int)ReadFloat(input); + bones.Add(boneCount); + for (int nn = i + boneCount * 4; i < nn; i += 4) + { + bones.Add((int)ReadFloat(input)); + weights.Add(ReadFloat(input) * scale); + weights.Add(ReadFloat(input) * scale); + weights.Add(ReadFloat(input)); + } + } + mesh.bones = bones.ToArray(); + mesh.weights = weights.ToArray(); + mesh.triangles = triangles; + mesh.regionUVs = uvs; + mesh.UpdateUVs(); + int color = ReadInt(input); + mesh.r = ((color & 0xff000000) >> 24) / 255f; + mesh.g = ((color & 0x00ff0000) >> 16) / 255f; + mesh.b = ((color & 0x0000ff00) >> 8) / 255f; + mesh.a = ((color & 0x000000ff)) / 255f; + mesh.HullLength = ReadInt(input, true) * 2; + if (nonessential) + { + mesh.Edges = ReadIntArray(input); + mesh.Width = ReadFloat(input) * scale; + mesh.Height = ReadFloat(input) * scale; + } + return mesh; + } + } + return null; + } + + private float[] ReadFloatArray(Stream input, float scale) + { + int n = ReadInt(input, true); + float[] array = new float[n]; + if (scale == 1) + { + for (int i = 0; i < n; i++) + array[i] = ReadFloat(input); + } + else + { + for (int i = 0; i < n; i++) + array[i] = ReadFloat(input) * scale; + } + return array; + } + + private int[] ReadShortArray(Stream input) + { + int n = ReadInt(input, true); + int[] array = new int[n]; + for (int i = 0; i < n; i++) + array[i] = (input.ReadByte() << 8) + input.ReadByte(); + return array; + } + + private int[] ReadIntArray(Stream input) + { + int n = ReadInt(input, true); + int[] array = new int[n]; + for (int i = 0; i < n; i++) + array[i] = ReadInt(input, true); + return array; + } + + private void ReadAnimation(String name, Stream input, SkeletonData skeletonData) + { + var timelines = new List(); + float scale = Scale; + float duration = 0; + + // Slot timelines. + for (int i = 0, n = ReadInt(input, true); i < n; i++) + { + int slotIndex = ReadInt(input, true); + for (int ii = 0, nn = ReadInt(input, true); ii < nn; ii++) + { + int timelineType = input.ReadByte(); + int frameCount = ReadInt(input, true); + switch (timelineType) + { + case TIMELINE_COLOR: + { + ColorTimeline timeline = new ColorTimeline(frameCount); + timeline.slotIndex = slotIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + float time = ReadFloat(input); + int color = ReadInt(input); + float r = ((color & 0xff000000) >> 24) / 255f; + float g = ((color & 0x00ff0000) >> 16) / 255f; + float b = ((color & 0x0000ff00) >> 8) / 255f; + float a = ((color & 0x000000ff)) / 255f; + timeline.SetFrame(frameIndex, time, r, g, b, a); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[frameCount * 5 - 5]); + break; + } + case TIMELINE_ATTACHMENT: + { + AttachmentTimeline timeline = new AttachmentTimeline(frameCount); + timeline.slotIndex = slotIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + timeline.SetFrame(frameIndex, ReadFloat(input), ReadString(input)); + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[frameCount - 1]); + break; + } + } + } + } + + // Bone timelines. + for (int i = 0, n = ReadInt(input, true); i < n; i++) + { + int boneIndex = ReadInt(input, true); + for (int ii = 0, nn = ReadInt(input, true); ii < nn; ii++) + { + int timelineType = input.ReadByte(); + int frameCount = ReadInt(input, true); + switch (timelineType) + { + case TIMELINE_ROTATE: + { + RotateTimeline timeline = new RotateTimeline(frameCount); + timeline.boneIndex = boneIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input)); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[frameCount * 2 - 2]); + break; + } + case TIMELINE_TRANSLATE: + case TIMELINE_SCALE: + { + TranslateTimeline timeline; + float timelineScale = 1; + if (timelineType == TIMELINE_SCALE) + timeline = new ScaleTimeline(frameCount); + else + { + timeline = new TranslateTimeline(frameCount); + timelineScale = scale; + } + timeline.boneIndex = boneIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input) * timelineScale, ReadFloat(input) + * timelineScale); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[frameCount * 3 - 3]); + break; + } + case TIMELINE_FLIPX: + case TIMELINE_FLIPY: + { + FlipXTimeline timeline = timelineType == TIMELINE_FLIPX ? new FlipXTimeline(frameCount) : new FlipYTimeline( + frameCount); + timeline.boneIndex = boneIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + timeline.SetFrame(frameIndex, ReadFloat(input), ReadBoolean(input)); + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[frameCount * 2 - 2]); + break; + } + } + } + } + + // IK timelines. + for (int i = 0, n = ReadInt(input, true); i < n; i++) + { + IkConstraintData ikConstraint = skeletonData.ikConstraints[ReadInt(input, true)]; + int frameCount = ReadInt(input, true); + IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount); + timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(ikConstraint); + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadSByte(input)); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[frameCount * 3 - 3]); + } + + // FFD timelines. + for (int i = 0, n = ReadInt(input, true); i < n; i++) + { + Skin skin = skeletonData.skins[ReadInt(input, true)]; + for (int ii = 0, nn = ReadInt(input, true); ii < nn; ii++) + { + int slotIndex = ReadInt(input, true); + for (int iii = 0, nnn = ReadInt(input, true); iii < nnn; iii++) + { + Attachment attachment = skin.GetAttachment(slotIndex, ReadString(input)); + int frameCount = ReadInt(input, true); + FFDTimeline timeline = new FFDTimeline(frameCount); + timeline.slotIndex = slotIndex; + timeline.attachment = attachment; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + float time = ReadFloat(input); + + float[] vertices; + int vertexCount; + if (attachment is MeshAttachment) + vertexCount = ((MeshAttachment)attachment).vertices.Length; + else + vertexCount = ((SkinnedMeshAttachment)attachment).weights.Length / 3 * 2; + + int end = ReadInt(input, true); + if (end == 0) + { + if (attachment is MeshAttachment) + vertices = ((MeshAttachment)attachment).vertices; + else + vertices = new float[vertexCount]; + } + else + { + vertices = new float[vertexCount]; + int start = ReadInt(input, true); + end += start; + if (scale == 1) + { + for (int v = start; v < end; v++) + vertices[v] = ReadFloat(input); + } + else + { + for (int v = start; v < end; v++) + vertices[v] = ReadFloat(input) * scale; + } + if (attachment is MeshAttachment) + { + float[] meshVertices = ((MeshAttachment)attachment).vertices; + for (int v = 0, vn = vertices.Length; v < vn; v++) + vertices[v] += meshVertices[v]; + } + } + + timeline.SetFrame(frameIndex, time, vertices); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[frameCount - 1]); + } + } + } + + // Draw order timeline. + int drawOrderCount = ReadInt(input, true); + if (drawOrderCount > 0) + { + DrawOrderTimeline timeline = new DrawOrderTimeline(drawOrderCount); + int slotCount = skeletonData.slots.Count; + for (int i = 0; i < drawOrderCount; i++) + { + int offsetCount = ReadInt(input, true); + int[] drawOrder = new int[slotCount]; + for (int ii = slotCount - 1; ii >= 0; ii--) + drawOrder[ii] = -1; + int[] unchanged = new int[slotCount - offsetCount]; + int originalIndex = 0, unchangedIndex = 0; + for (int ii = 0; ii < offsetCount; ii++) + { + int slotIndex = ReadInt(input, true); + // Collect unchanged items. + while (originalIndex != slotIndex) + unchanged[unchangedIndex++] = originalIndex++; + // Set changed items. + drawOrder[originalIndex + ReadInt(input, true)] = originalIndex++; + } + // Collect remaining unchanged items. + while (originalIndex < slotCount) + unchanged[unchangedIndex++] = originalIndex++; + // Fill in unchanged items. + for (int ii = slotCount - 1; ii >= 0; ii--) + if (drawOrder[ii] == -1) drawOrder[ii] = unchanged[--unchangedIndex]; + timeline.SetFrame(i, ReadFloat(input), drawOrder); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[drawOrderCount - 1]); + } + + // Event timeline. + int eventCount = ReadInt(input, true); + if (eventCount > 0) + { + EventTimeline timeline = new EventTimeline(eventCount); + for (int i = 0; i < eventCount; i++) + { + float time = ReadFloat(input); + EventData eventData = skeletonData.events[ReadInt(input, true)]; + Event e = new Event(eventData); + e.Int = ReadInt(input, false); + e.Float = ReadFloat(input); + e.String = ReadBoolean(input) ? ReadString(input) : eventData.String; + timeline.SetFrame(i, time, e); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[eventCount - 1]); + } + + timelines.TrimExcess(); + skeletonData.animations.Add(new Animation(name, timelines, duration)); + } + + private void ReadCurve(Stream input, int frameIndex, CurveTimeline timeline) + { + switch (input.ReadByte()) + { + case CURVE_STEPPED: + timeline.SetStepped(frameIndex); + break; + case CURVE_BEZIER: + timeline.SetCurve(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input)); + break; + } + } + + private sbyte ReadSByte(Stream input) + { + int value = input.ReadByte(); + if (value == -1) throw new EndOfStreamException(); + return (sbyte)value; + } + + private bool ReadBoolean(Stream input) + { + return input.ReadByte() != 0; + } + + private float ReadFloat(Stream input) + { + buffer[3] = (byte)input.ReadByte(); + buffer[2] = (byte)input.ReadByte(); + buffer[1] = (byte)input.ReadByte(); + buffer[0] = (byte)input.ReadByte(); + return BitConverter.ToSingle(buffer, 0); + } + + private int ReadInt(Stream input) + { + return (input.ReadByte() << 24) + (input.ReadByte() << 16) + (input.ReadByte() << 8) + input.ReadByte(); + } + + private int ReadInt(Stream input, bool optimizePositive) + { + int b = input.ReadByte(); + int result = b & 0x7F; + if ((b & 0x80) != 0) + { + b = input.ReadByte(); + result |= (b & 0x7F) << 7; + if ((b & 0x80) != 0) + { + b = input.ReadByte(); + result |= (b & 0x7F) << 14; + if ((b & 0x80) != 0) + { + b = input.ReadByte(); + result |= (b & 0x7F) << 21; + if ((b & 0x80) != 0) + { + b = input.ReadByte(); + result |= (b & 0x7F) << 28; + } + } + } + } + return optimizePositive ? result : ((result >> 1) ^ -(result & 1)); + } + + private string ReadString(Stream input) + { + int charCount = ReadInt(input, true); + switch (charCount) + { + case 0: + return null; + case 1: + return ""; + } + charCount--; + char[] chars = this.chars; + if (chars.Length < charCount) this.chars = chars = new char[charCount]; + // Try to read 7 bit ASCII chars. + int charIndex = 0; + int b = 0; + while (charIndex < charCount) + { + b = input.ReadByte(); + if (b > 127) break; + chars[charIndex++] = (char)b; + } + // If a char was not ASCII, finish with slow path. + if (charIndex < charCount) ReadUtf8_slow(input, charCount, charIndex, b); + return new String(chars, 0, charCount); + } + + private void ReadUtf8_slow(Stream input, int charCount, int charIndex, int b) + { + char[] chars = this.chars; + while (true) + { + switch (b >> 4) + { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + chars[charIndex] = (char)b; + break; + case 12: + case 13: + chars[charIndex] = (char)((b & 0x1F) << 6 | input.ReadByte() & 0x3F); + break; + case 14: + chars[charIndex] = (char)((b & 0x0F) << 12 | (input.ReadByte() & 0x3F) << 6 | input.ReadByte() & 0x3F); + break; + } + if (++charIndex >= charCount) break; + b = input.ReadByte() & 0xFF; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/SkeletonBounds.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/SkeletonBounds.cs index e525f83..2ae58f4 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/SkeletonBounds.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/SkeletonBounds.cs @@ -31,185 +31,209 @@ using System; using System.Collections.Generic; -namespace Spine2_1_25 { - public class SkeletonBounds { - private List polygonPool = new List(); - private float minX, minY, maxX, maxY; - - public List BoundingBoxes { get; private set; } - public List Polygons { get; private set; } - public float MinX { get { return minX; } set { minX = value; } } - public float MinY { get { return minY; } set { minY = value; } } - public float MaxX { get { return maxX; } set { maxX = value; } } - public float MaxY { get { return maxY; } set { maxY = value; } } - public float Width { get { return maxX - minX; } } - public float Height { get { return maxY - minY; } } - - public SkeletonBounds () { - BoundingBoxes = new List(); - Polygons = new List(); - } - - public void Update (Skeleton skeleton, bool updateAabb) { - List boundingBoxes = BoundingBoxes; - List polygons = Polygons; - List slots = skeleton.slots; - int slotCount = slots.Count; - - boundingBoxes.Clear(); - foreach (Polygon polygon in polygons) - polygonPool.Add(polygon); - polygons.Clear(); - - for (int i = 0; i < slotCount; i++) { - Slot slot = slots[i]; - BoundingBoxAttachment boundingBox = slot.attachment as BoundingBoxAttachment; - if (boundingBox == null) continue; - boundingBoxes.Add(boundingBox); - - Polygon polygon = null; - int poolCount = polygonPool.Count; - if (poolCount > 0) { - polygon = polygonPool[poolCount - 1]; - polygonPool.RemoveAt(poolCount - 1); - } else - polygon = new Polygon(); - polygons.Add(polygon); - - int count = boundingBox.Vertices.Length; - polygon.Count = count; - if (polygon.Vertices.Length < count) polygon.Vertices = new float[count]; - boundingBox.ComputeWorldVertices(slot.bone, polygon.Vertices); - } - - if (updateAabb) aabbCompute(); - } - - private void aabbCompute () { - float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; - List polygons = Polygons; - for (int i = 0, n = polygons.Count; i < n; i++) { - Polygon polygon = polygons[i]; - float[] vertices = polygon.Vertices; - for (int ii = 0, nn = polygon.Count; ii < nn; ii += 2) { - float x = vertices[ii]; - float y = vertices[ii + 1]; - minX = Math.Min(minX, x); - minY = Math.Min(minY, y); - maxX = Math.Max(maxX, x); - maxY = Math.Max(maxY, y); - } - } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; - } - - - /// Returns true if the axis aligned bounding box contains the point. - public bool AabbContainsPoint (float x, float y) { - return x >= minX && x <= maxX && y >= minY && y <= maxY; - } - - /// Returns true if the axis aligned bounding box intersects the line segment. - public bool AabbIntersectsSegment (float x1, float y1, float x2, float y2) { - float minX = this.minX; - float minY = this.minY; - float maxX = this.maxX; - float maxY = this.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; - } - - /// Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds. - public bool AabbIntersectsSkeleton (SkeletonBounds bounds) { - return minX < bounds.maxX && maxX > bounds.minX && minY < bounds.maxY && maxY > bounds.minY; - } - - /// Returns true if the polygon contains the point. - public bool ContainsPoint (Polygon polygon, float x, float y) { - float[] 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; - } - - /// 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. - public BoundingBoxAttachment ContainsPoint (float x, float y) { - List polygons = Polygons; - for (int i = 0, n = polygons.Count; i < n; i++) - if (ContainsPoint(polygons[i], x, y)) return BoundingBoxes[i]; - return null; - } - - /// 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. - public BoundingBoxAttachment IntersectsSegment (float x1, float y1, float x2, float y2) { - List polygons = Polygons; - for (int i = 0, n = polygons.Count; i < n; i++) - if (IntersectsSegment(polygons[i], x1, y1, x2, y2)) return BoundingBoxes[i]; - return null; - } - - /// Returns true if the polygon contains the line segment. - public bool IntersectsSegment (Polygon polygon, float x1, float y1, float x2, float y2) { - float[] vertices = polygon.Vertices; - int 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 (int 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; - } - - public Polygon getPolygon (BoundingBoxAttachment attachment) { - int index = BoundingBoxes.IndexOf(attachment); - return index == -1 ? null : Polygons[index]; - } - } - - public class Polygon { - public float[] Vertices { get; set; } - public int Count { get; set; } - - public Polygon () { - Vertices = new float[16]; - } - } +namespace Spine2_1_25 +{ + public class SkeletonBounds + { + private List polygonPool = new List(); + private float minX, minY, maxX, maxY; + + public List BoundingBoxes { get; private set; } + public List Polygons { get; private set; } + public float MinX { get { return minX; } set { minX = value; } } + public float MinY { get { return minY; } set { minY = value; } } + public float MaxX { get { return maxX; } set { maxX = value; } } + public float MaxY { get { return maxY; } set { maxY = value; } } + public float Width { get { return maxX - minX; } } + public float Height { get { return maxY - minY; } } + + public SkeletonBounds() + { + BoundingBoxes = new List(); + Polygons = new List(); + } + + public void Update(Skeleton skeleton, bool updateAabb) + { + List boundingBoxes = BoundingBoxes; + List polygons = Polygons; + List slots = skeleton.slots; + int slotCount = slots.Count; + + boundingBoxes.Clear(); + foreach (Polygon polygon in polygons) + polygonPool.Add(polygon); + polygons.Clear(); + + for (int i = 0; i < slotCount; i++) + { + Slot slot = slots[i]; + BoundingBoxAttachment boundingBox = slot.attachment as BoundingBoxAttachment; + if (boundingBox == null) continue; + boundingBoxes.Add(boundingBox); + + Polygon polygon = null; + int poolCount = polygonPool.Count; + if (poolCount > 0) + { + polygon = polygonPool[poolCount - 1]; + polygonPool.RemoveAt(poolCount - 1); + } + else + polygon = new Polygon(); + polygons.Add(polygon); + + int count = boundingBox.Vertices.Length; + polygon.Count = count; + if (polygon.Vertices.Length < count) polygon.Vertices = new float[count]; + boundingBox.ComputeWorldVertices(slot.bone, polygon.Vertices); + } + + if (updateAabb) aabbCompute(); + } + + private void aabbCompute() + { + float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; + List polygons = Polygons; + for (int i = 0, n = polygons.Count; i < n; i++) + { + Polygon polygon = polygons[i]; + float[] vertices = polygon.Vertices; + for (int ii = 0, nn = polygon.Count; ii < nn; ii += 2) + { + float x = vertices[ii]; + float y = vertices[ii + 1]; + minX = Math.Min(minX, x); + minY = Math.Min(minY, y); + maxX = Math.Max(maxX, x); + maxY = Math.Max(maxY, y); + } + } + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + + /// Returns true if the axis aligned bounding box contains the point. + public bool AabbContainsPoint(float x, float y) + { + return x >= minX && x <= maxX && y >= minY && y <= maxY; + } + + /// Returns true if the axis aligned bounding box intersects the line segment. + public bool AabbIntersectsSegment(float x1, float y1, float x2, float y2) + { + float minX = this.minX; + float minY = this.minY; + float maxX = this.maxX; + float maxY = this.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; + } + + /// Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds. + public bool AabbIntersectsSkeleton(SkeletonBounds bounds) + { + return minX < bounds.maxX && maxX > bounds.minX && minY < bounds.maxY && maxY > bounds.minY; + } + + /// Returns true if the polygon contains the point. + public bool ContainsPoint(Polygon polygon, float x, float y) + { + float[] 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; + } + + /// 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. + public BoundingBoxAttachment ContainsPoint(float x, float y) + { + List polygons = Polygons; + for (int i = 0, n = polygons.Count; i < n; i++) + if (ContainsPoint(polygons[i], x, y)) return BoundingBoxes[i]; + return null; + } + + /// 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. + public BoundingBoxAttachment IntersectsSegment(float x1, float y1, float x2, float y2) + { + List polygons = Polygons; + for (int i = 0, n = polygons.Count; i < n; i++) + if (IntersectsSegment(polygons[i], x1, y1, x2, y2)) return BoundingBoxes[i]; + return null; + } + + /// Returns true if the polygon contains the line segment. + public bool IntersectsSegment(Polygon polygon, float x1, float y1, float x2, float y2) + { + float[] vertices = polygon.Vertices; + int 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 (int 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; + } + + public Polygon getPolygon(BoundingBoxAttachment attachment) + { + int index = BoundingBoxes.IndexOf(attachment); + return index == -1 ? null : Polygons[index]; + } + } + + public class Polygon + { + public float[] Vertices { get; set; } + public int Count { get; set; } + + public Polygon() + { + Vertices = new float[16]; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/SkeletonData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/SkeletonData.cs index 6d52e47..c24a2b3 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/SkeletonData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/SkeletonData.cs @@ -31,128 +31,143 @@ using System; using System.Collections.Generic; -namespace Spine2_1_25 { - public class SkeletonData { - internal String name; - internal List bones = new List(); - internal List slots = new List(); - internal List skins = new List(); - internal Skin defaultSkin; - internal List events = new List(); - internal List animations = new List(); - internal List ikConstraints = new List(); - internal float width, height; - internal String version, hash, imagesPath; - - public String Name { get { return name; } set { name = value; } } - public List Bones { get { return bones; } } // Ordered parents first. - public List Slots { get { return slots; } } // Setup pose draw order. - public List Skins { get { return skins; } set { skins = value; } } - /// May be null. - public Skin DefaultSkin { get { return defaultSkin; } set { defaultSkin = value; } } - public List Events { get { return events; } set { events = value; } } - public List Animations { get { return animations; } set { animations = value; } } - public List IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } } - public float Width { get { return width; } set { width = value; } } - public float Height { get { return height; } set { height = value; } } - /// The Spine version used to export this data. - public String Version { get { return version; } set { version = value; } } - public String Hash { get { return hash; } set { hash = value; } } - - // --- Bones. - - /// May be null. - public BoneData FindBone (String boneName) { - if (boneName == null) throw new ArgumentNullException("boneName cannot be null."); - List bones = this.bones; - for (int i = 0, n = bones.Count; i < n; i++) { - BoneData bone = bones[i]; - if (bone.name == boneName) return bone; - } - return null; - } - - /// -1 if the bone was not found. - public int FindBoneIndex (String boneName) { - if (boneName == null) throw new ArgumentNullException("boneName cannot be null."); - List bones = this.bones; - for (int i = 0, n = bones.Count; i < n; i++) - if (bones[i].name == boneName) return i; - return -1; - } - - // --- Slots. - - /// May be null. - public SlotData FindSlot (String slotName) { - if (slotName == null) throw new ArgumentNullException("slotName cannot be null."); - List slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) { - SlotData slot = slots[i]; - if (slot.name == slotName) return slot; - } - return null; - } - - /// -1 if the bone was not found. - public int FindSlotIndex (String slotName) { - if (slotName == null) throw new ArgumentNullException("slotName cannot be null."); - List slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) - if (slots[i].name == slotName) return i; - return -1; - } - - // --- Skins. - - /// May be null. - public Skin FindSkin (String skinName) { - if (skinName == null) throw new ArgumentNullException("skinName cannot be null."); - foreach (Skin skin in skins) - if (skin.name == skinName) return skin; - return null; - } - - // --- Events. - - /// May be null. - public EventData FindEvent (String eventDataName) { - if (eventDataName == null) throw new ArgumentNullException("eventDataName cannot be null."); - foreach (EventData eventData in events) - if (eventData.name == eventDataName) return eventData; - return null; - } - - // --- Animations. - - /// May be null. - public Animation FindAnimation (String animationName) { - if (animationName == null) throw new ArgumentNullException("animationName cannot be null."); - List animations = this.animations; - for (int i = 0, n = animations.Count; i < n; i++) { - Animation animation = animations[i]; - if (animation.name == animationName) return animation; - } - return null; - } - - // --- IK constraints. - - /// May be null. - public IkConstraintData FindIkConstraint (String ikConstraintName) { - if (ikConstraintName == null) throw new ArgumentNullException("ikConstraintName cannot be null."); - List ikConstraints = this.ikConstraints; - for (int i = 0, n = ikConstraints.Count; i < n; i++) { - IkConstraintData ikConstraint = ikConstraints[i]; - if (ikConstraint.name == ikConstraintName) return ikConstraint; - } - return null; - } - - // --- - - override public String ToString () { - return name ?? base.ToString(); - } - } +namespace Spine2_1_25 +{ + public class SkeletonData + { + internal String name; + internal List bones = new List(); + internal List slots = new List(); + internal List skins = new List(); + internal Skin defaultSkin; + internal List events = new List(); + internal List animations = new List(); + internal List ikConstraints = new List(); + internal float width, height; + internal String version, hash, imagesPath; + + public String Name { get { return name; } set { name = value; } } + public List Bones { get { return bones; } } // Ordered parents first. + public List Slots { get { return slots; } } // Setup pose draw order. + public List Skins { get { return skins; } set { skins = value; } } + /// May be null. + public Skin DefaultSkin { get { return defaultSkin; } set { defaultSkin = value; } } + public List Events { get { return events; } set { events = value; } } + public List Animations { get { return animations; } set { animations = value; } } + public List IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } } + public float Width { get { return width; } set { width = value; } } + public float Height { get { return height; } set { height = value; } } + /// The Spine version used to export this data. + public String Version { get { return version; } set { version = value; } } + public String Hash { get { return hash; } set { hash = value; } } + + // --- Bones. + + /// May be null. + public BoneData FindBone(String boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName cannot be null."); + List bones = this.bones; + for (int i = 0, n = bones.Count; i < n; i++) + { + BoneData bone = bones[i]; + if (bone.name == boneName) return bone; + } + return null; + } + + /// -1 if the bone was not found. + public int FindBoneIndex(String boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName cannot be null."); + List bones = this.bones; + for (int i = 0, n = bones.Count; i < n; i++) + if (bones[i].name == boneName) return i; + return -1; + } + + // --- Slots. + + /// May be null. + public SlotData FindSlot(String slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName cannot be null."); + List slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + { + SlotData slot = slots[i]; + if (slot.name == slotName) return slot; + } + return null; + } + + /// -1 if the bone was not found. + public int FindSlotIndex(String slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName cannot be null."); + List slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + if (slots[i].name == slotName) return i; + return -1; + } + + // --- Skins. + + /// May be null. + public Skin FindSkin(String skinName) + { + if (skinName == null) throw new ArgumentNullException("skinName cannot be null."); + foreach (Skin skin in skins) + if (skin.name == skinName) return skin; + return null; + } + + // --- Events. + + /// May be null. + public EventData FindEvent(String eventDataName) + { + if (eventDataName == null) throw new ArgumentNullException("eventDataName cannot be null."); + foreach (EventData eventData in events) + if (eventData.name == eventDataName) return eventData; + return null; + } + + // --- Animations. + + /// May be null. + public Animation FindAnimation(String animationName) + { + if (animationName == null) throw new ArgumentNullException("animationName cannot be null."); + List animations = this.animations; + for (int i = 0, n = animations.Count; i < n; i++) + { + Animation animation = animations[i]; + if (animation.name == animationName) return animation; + } + return null; + } + + // --- IK constraints. + + /// May be null. + public IkConstraintData FindIkConstraint(String ikConstraintName) + { + if (ikConstraintName == null) throw new ArgumentNullException("ikConstraintName cannot be null."); + List ikConstraints = this.ikConstraints; + for (int i = 0, n = ikConstraints.Count; i < n; i++) + { + IkConstraintData ikConstraint = ikConstraints[i]; + if (ikConstraint.name == ikConstraintName) return ikConstraint; + } + return null; + } + + // --- + + override public String ToString() + { + return name ?? base.ToString(); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/SkeletonJson.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/SkeletonJson.cs index 8cccaa3..e534dfb 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/SkeletonJson.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/SkeletonJson.cs @@ -29,28 +29,32 @@ *****************************************************************************/ using System; -using System.IO; using System.Collections.Generic; +using System.IO; #if WINDOWS_STOREAPP using System.Threading.Tasks; using Windows.Storage; #endif -namespace Spine2_1_25 { - public class SkeletonJson { - private AttachmentLoader attachmentLoader; - public float Scale { get; set; } - - public SkeletonJson (params Atlas[] atlasArray) - : this(new AtlasAttachmentLoader(atlasArray)) { - } - - public SkeletonJson (AttachmentLoader attachmentLoader) { - if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader cannot be null."); - this.attachmentLoader = attachmentLoader; - Scale = 1; - } +namespace Spine2_1_25 +{ + public class SkeletonJson + { + private AttachmentLoader attachmentLoader; + public float Scale { get; set; } + + public SkeletonJson(params Atlas[] atlasArray) + : this(new AtlasAttachmentLoader(atlasArray)) + { + } + + public SkeletonJson(AttachmentLoader attachmentLoader) + { + if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader cannot be null."); + this.attachmentLoader = attachmentLoader; + Scale = 1; + } #if WINDOWS_STOREAPP private async Task ReadFile(string path) { @@ -67,575 +71,659 @@ public SkeletonData ReadSkeletonData (String path) { return this.ReadFile(path).Result; } #else - public SkeletonData ReadSkeletonData (String path) { + public SkeletonData ReadSkeletonData(String path) + { #if WINDOWS_PHONE Stream stream = Microsoft.Xna.Framework.TitleContainer.OpenStream(path); using (StreamReader reader = new StreamReader(stream)) { #else - using (StreamReader reader = new StreamReader(path)) { + using (StreamReader reader = new StreamReader(path)) + { #endif - SkeletonData skeletonData = ReadSkeletonData(reader); - skeletonData.name = Path.GetFileNameWithoutExtension(path); - return skeletonData; - } - } + SkeletonData skeletonData = ReadSkeletonData(reader); + skeletonData.name = Path.GetFileNameWithoutExtension(path); + return skeletonData; + } + } #endif - public SkeletonData ReadSkeletonData (TextReader reader) { - if (reader == null) throw new ArgumentNullException("reader cannot be null."); - - var skeletonData = new SkeletonData(); - - var root = Json.Deserialize(reader) as Dictionary; - if (root == null) throw new Exception("Invalid JSON."); - - // Skeleton. - if (root.ContainsKey("skeleton")) { - var skeletonMap = (Dictionary)root["skeleton"]; - skeletonData.hash = (String)skeletonMap["hash"]; - skeletonData.version = (String)skeletonMap["spine"]; - skeletonData.width = GetFloat(skeletonMap, "width", 0); - skeletonData.height = GetFloat(skeletonMap, "height", 0); - } - - // Bones. - foreach (Dictionary boneMap in (List)root["bones"]) { - BoneData parent = null; - if (boneMap.ContainsKey("parent")) { - parent = skeletonData.FindBone((String)boneMap["parent"]); - if (parent == null) - throw new Exception("Parent bone not found: " + boneMap["parent"]); - } - var boneData = new BoneData((String)boneMap["name"], parent); - boneData.length = GetFloat(boneMap, "length", 0) * Scale; - boneData.x = GetFloat(boneMap, "x", 0) * Scale; - boneData.y = GetFloat(boneMap, "y", 0) * Scale; - boneData.rotation = GetFloat(boneMap, "rotation", 0); - boneData.scaleX = GetFloat(boneMap, "scaleX", 1); - boneData.scaleY = GetFloat(boneMap, "scaleY", 1); - boneData.flipX = GetBoolean(boneMap, "flipX", false); - boneData.flipY = GetBoolean(boneMap, "flipY", false); - boneData.inheritScale = GetBoolean(boneMap, "inheritScale", true); - boneData.inheritRotation = GetBoolean(boneMap, "inheritRotation", true); - skeletonData.bones.Add(boneData); - } - - // IK constraints. - if (root.ContainsKey("ik")) { - foreach (Dictionary ikMap in (List)root["ik"]) { - IkConstraintData ikConstraintData = new IkConstraintData((String)ikMap["name"]); - - foreach (String boneName in (List)ikMap["bones"]) { - BoneData bone = skeletonData.FindBone(boneName); - if (bone == null) throw new Exception("IK bone not found: " + boneName); - ikConstraintData.bones.Add(bone); - } - - String targetName = (String)ikMap["target"]; - ikConstraintData.target = skeletonData.FindBone(targetName); - if (ikConstraintData.target == null) throw new Exception("Target bone not found: " + targetName); - - ikConstraintData.bendDirection = GetBoolean(ikMap, "bendPositive", true) ? 1 : -1; - ikConstraintData.mix = GetFloat(ikMap, "mix", 1); - - skeletonData.ikConstraints.Add(ikConstraintData); - } - } - - // Slots. - if (root.ContainsKey("slots")) { - foreach (Dictionary slotMap in (List)root["slots"]) { - var slotName = (String)slotMap["name"]; - var boneName = (String)slotMap["bone"]; - BoneData boneData = skeletonData.FindBone(boneName); - if (boneData == null) - throw new Exception("Slot bone not found: " + boneName); - var slotData = new SlotData(slotName, boneData); - - if (slotMap.ContainsKey("color")) { - var color = (String)slotMap["color"]; - slotData.r = ToColor(color, 0); - slotData.g = ToColor(color, 1); - slotData.b = ToColor(color, 2); - slotData.a = ToColor(color, 3); - } - - if (slotMap.ContainsKey("attachment")) - slotData.attachmentName = (String)slotMap["attachment"]; - - if (slotMap.ContainsKey("additive")) - slotData.additiveBlending = (bool)slotMap["additive"]; - - skeletonData.slots.Add(slotData); - } - } - - // Skins. - if (root.ContainsKey("skins")) { - foreach (KeyValuePair entry in (Dictionary)root["skins"]) { - var skin = new Skin(entry.Key); - foreach (KeyValuePair slotEntry in (Dictionary)entry.Value) { - int slotIndex = skeletonData.FindSlotIndex(slotEntry.Key); - foreach (KeyValuePair attachmentEntry in ((Dictionary)slotEntry.Value)) { - Attachment attachment = ReadAttachment(skin, attachmentEntry.Key, (Dictionary)attachmentEntry.Value); - if (attachment != null) skin.AddAttachment(slotIndex, attachmentEntry.Key, attachment); - } - } - skeletonData.skins.Add(skin); - if (skin.name == "default") - skeletonData.defaultSkin = skin; - } - } - - // Events. - if (root.ContainsKey("events")) { - foreach (KeyValuePair entry in (Dictionary)root["events"]) { - var entryMap = (Dictionary)entry.Value; - var eventData = new EventData(entry.Key); - eventData.Int = GetInt(entryMap, "int", 0); - eventData.Float = GetFloat(entryMap, "float", 0); - eventData.String = GetString(entryMap, "string", null); - skeletonData.events.Add(eventData); - } - } - - // Animations. - if (root.ContainsKey("animations")) { - foreach (KeyValuePair entry in (Dictionary)root["animations"]) - ReadAnimation(entry.Key, (Dictionary)entry.Value, skeletonData); - } - - skeletonData.bones.TrimExcess(); - skeletonData.slots.TrimExcess(); - skeletonData.skins.TrimExcess(); - skeletonData.events.TrimExcess(); - skeletonData.animations.TrimExcess(); - skeletonData.ikConstraints.TrimExcess(); - return skeletonData; - } - - private Attachment ReadAttachment (Skin skin, String name, Dictionary map) { - if (map.ContainsKey("name")) - name = (String)map["name"]; - - var type = AttachmentType.region; - if (map.ContainsKey("type")) - type = (AttachmentType)Enum.Parse(typeof(AttachmentType), (String)map["type"], false); - - String path = name; - if (map.ContainsKey("path")) - path = (String)map["path"]; - - switch (type) { - case AttachmentType.region: - RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); - if (region == null) return null; - region.Path = path; - region.x = GetFloat(map, "x", 0) * Scale; - region.y = GetFloat(map, "y", 0) * Scale; - region.scaleX = GetFloat(map, "scaleX", 1); - region.scaleY = GetFloat(map, "scaleY", 1); - region.rotation = GetFloat(map, "rotation", 0); - region.width = GetFloat(map, "width", 32) * Scale; - region.height = GetFloat(map, "height", 32) * Scale; - region.UpdateOffset(); - - if (map.ContainsKey("color")) { - var color = (String)map["color"]; - region.r = ToColor(color, 0); - region.g = ToColor(color, 1); - region.b = ToColor(color, 2); - region.a = ToColor(color, 3); - } - - return region; - case AttachmentType.mesh: { - MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); - if (mesh == null) return null; - - mesh.Path = path; - mesh.vertices = GetFloatArray(map, "vertices", Scale); - mesh.triangles = GetIntArray(map, "triangles"); - mesh.regionUVs = GetFloatArray(map, "uvs", 1); - mesh.UpdateUVs(); - - if (map.ContainsKey("color")) { - var color = (String)map["color"]; - mesh.r = ToColor(color, 0); - mesh.g = ToColor(color, 1); - mesh.b = ToColor(color, 2); - mesh.a = ToColor(color, 3); - } - - mesh.HullLength = GetInt(map, "hull", 0) * 2; - if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges"); - mesh.Width = GetInt(map, "width", 0) * Scale; - mesh.Height = GetInt(map, "height", 0) * Scale; - - return mesh; - } - case AttachmentType.skinnedmesh: { - SkinnedMeshAttachment mesh = attachmentLoader.NewSkinnedMeshAttachment(skin, name, path); - if (mesh == null) return null; - - mesh.Path = path; - float[] uvs = GetFloatArray(map, "uvs", 1); - float[] vertices = GetFloatArray(map, "vertices", 1); - var weights = new List(uvs.Length * 3 * 3); - var bones = new List(uvs.Length * 3); - float scale = Scale; - for (int i = 0, n = vertices.Length; i < n; ) { - int boneCount = (int)vertices[i++]; - bones.Add(boneCount); - for (int nn = i + boneCount * 4; i < nn; ) { - bones.Add((int)vertices[i]); - weights.Add(vertices[i + 1] * scale); - weights.Add(vertices[i + 2] * scale); - weights.Add(vertices[i + 3]); - i += 4; - } - } - mesh.bones = bones.ToArray(); - mesh.weights = weights.ToArray(); - mesh.triangles = GetIntArray(map, "triangles"); - mesh.regionUVs = uvs; - mesh.UpdateUVs(); - - if (map.ContainsKey("color")) { - var color = (String)map["color"]; - mesh.r = ToColor(color, 0); - mesh.g = ToColor(color, 1); - mesh.b = ToColor(color, 2); - mesh.a = ToColor(color, 3); - } - - mesh.HullLength = GetInt(map, "hull", 0) * 2; - if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges"); - mesh.Width = GetInt(map, "width", 0) * Scale; - mesh.Height = GetInt(map, "height", 0) * Scale; - - return mesh; - } - case AttachmentType.boundingbox: - BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); - if (box == null) return null; - box.vertices = GetFloatArray(map, "vertices", Scale); - return box; - } - return null; - } - - private float[] GetFloatArray (Dictionary map, String name, float scale) { - var list = (List)map[name]; - var values = new float[list.Count]; - if (scale == 1) { - for (int i = 0, n = list.Count; i < n; i++) - values[i] = (float)list[i]; - } else { - for (int i = 0, n = list.Count; i < n; i++) - values[i] = (float)list[i] * scale; - } - return values; - } - - private int[] GetIntArray (Dictionary map, String name) { - var list = (List)map[name]; - var values = new int[list.Count]; - for (int i = 0, n = list.Count; i < n; i++) - values[i] = (int)(float)list[i]; - return values; - } - - private float GetFloat (Dictionary map, String name, float defaultValue) { - if (!map.ContainsKey(name)) - return defaultValue; - return (float)map[name]; - } - - private int GetInt (Dictionary map, String name, int defaultValue) { - if (!map.ContainsKey(name)) - return defaultValue; - return (int)(float)map[name]; - } - - private bool GetBoolean (Dictionary map, String name, bool defaultValue) { - if (!map.ContainsKey(name)) - return defaultValue; - return (bool)map[name]; - } - - private String GetString (Dictionary map, String name, String defaultValue) { - if (!map.ContainsKey(name)) - return defaultValue; - return (String)map[name]; - } - - private float ToColor (String hexString, int colorIndex) { - if (hexString.Length != 8) - throw new ArgumentException("Color hexidecimal length must be 8, recieved: " + hexString); - return Convert.ToInt32(hexString.Substring(colorIndex * 2, 2), 16) / (float)255; - } - - private void ReadAnimation (String name, Dictionary map, SkeletonData skeletonData) { - var timelines = new List(); - float duration = 0; - float scale = Scale; - - if (map.ContainsKey("slots")) { - foreach (KeyValuePair entry in (Dictionary)map["slots"]) { - String slotName = entry.Key; - int slotIndex = skeletonData.FindSlotIndex(slotName); - var timelineMap = (Dictionary)entry.Value; - - foreach (KeyValuePair timelineEntry in timelineMap) { - var values = (List)timelineEntry.Value; - var timelineName = (String)timelineEntry.Key; - if (timelineName == "color") { - var timeline = new ColorTimeline(values.Count); - timeline.slotIndex = slotIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - String c = (String)valueMap["color"]; - timeline.SetFrame(frameIndex, time, ToColor(c, 0), ToColor(c, 1), ToColor(c, 2), ToColor(c, 3)); - ReadCurve(timeline, frameIndex, valueMap); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 5 - 5]); - - } else if (timelineName == "attachment") { - var timeline = new AttachmentTimeline(values.Count); - timeline.slotIndex = slotIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - timeline.SetFrame(frameIndex++, time, (String)valueMap["name"]); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); - - } else - throw new Exception("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")"); - } - } - } - - if (map.ContainsKey("bones")) { - foreach (KeyValuePair entry in (Dictionary)map["bones"]) { - String boneName = entry.Key; - int boneIndex = skeletonData.FindBoneIndex(boneName); - if (boneIndex == -1) - throw new Exception("Bone not found: " + boneName); - - var timelineMap = (Dictionary)entry.Value; - foreach (KeyValuePair timelineEntry in timelineMap) { - var values = (List)timelineEntry.Value; - var timelineName = (String)timelineEntry.Key; - if (timelineName == "rotate") { - var timeline = new RotateTimeline(values.Count); - timeline.boneIndex = boneIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - timeline.SetFrame(frameIndex, time, (float)valueMap["angle"]); - ReadCurve(timeline, frameIndex, valueMap); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 2 - 2]); - - } else if (timelineName == "translate" || timelineName == "scale") { - TranslateTimeline timeline; - float timelineScale = 1; - if (timelineName == "scale") - timeline = new ScaleTimeline(values.Count); - else { - timeline = new TranslateTimeline(values.Count); - timelineScale = scale; - } - timeline.boneIndex = boneIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - float x = valueMap.ContainsKey("x") ? (float)valueMap["x"] : 0; - float y = valueMap.ContainsKey("y") ? (float)valueMap["y"] : 0; - timeline.SetFrame(frameIndex, time, (float)x * timelineScale, (float)y * timelineScale); - ReadCurve(timeline, frameIndex, valueMap); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 3 - 3]); - - } else if (timelineName == "flipX" || timelineName == "flipY") { - bool x = timelineName == "flipX"; - var timeline = x ? new FlipXTimeline(values.Count) : new FlipYTimeline(values.Count); - timeline.boneIndex = boneIndex; - - String field = x ? "x" : "y"; - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - timeline.SetFrame(frameIndex, time, valueMap.ContainsKey(field) ? (bool)valueMap[field] : false); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 2 - 2]); - - } else - throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")"); - } - } - } - - if (map.ContainsKey("ik")) { - foreach (KeyValuePair ikMap in (Dictionary)map["ik"]) { - IkConstraintData ikConstraint = skeletonData.FindIkConstraint(ikMap.Key); - var values = (List)ikMap.Value; - var timeline = new IkConstraintTimeline(values.Count); - timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(ikConstraint); - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - float mix = valueMap.ContainsKey("mix") ? (float)valueMap["mix"] : 1; - bool bendPositive = valueMap.ContainsKey("bendPositive") ? (bool)valueMap["bendPositive"] : true; - timeline.SetFrame(frameIndex, time, mix, bendPositive ? 1 : -1); - ReadCurve(timeline, frameIndex, valueMap); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 3 - 3]); - } - } - - if (map.ContainsKey("ffd")) { - foreach (KeyValuePair ffdMap in (Dictionary)map["ffd"]) { - Skin skin = skeletonData.FindSkin(ffdMap.Key); - foreach (KeyValuePair slotMap in (Dictionary)ffdMap.Value) { - int slotIndex = skeletonData.FindSlotIndex(slotMap.Key); - foreach (KeyValuePair meshMap in (Dictionary)slotMap.Value) { - var values = (List)meshMap.Value; - var timeline = new FFDTimeline(values.Count); - Attachment attachment = skin.GetAttachment(slotIndex, meshMap.Key); - if (attachment == null) throw new Exception("FFD attachment not found: " + meshMap.Key); - timeline.slotIndex = slotIndex; - timeline.attachment = attachment; - - int vertexCount; - if (attachment is MeshAttachment) - vertexCount = ((MeshAttachment)attachment).vertices.Length; - else - vertexCount = ((SkinnedMeshAttachment)attachment).Weights.Length / 3 * 2; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float[] vertices; - if (!valueMap.ContainsKey("vertices")) { - if (attachment is MeshAttachment) - vertices = ((MeshAttachment)attachment).vertices; - else - vertices = new float[vertexCount]; - } else { - var verticesValue = (List)valueMap["vertices"]; - vertices = new float[vertexCount]; - int start = GetInt(valueMap, "offset", 0); - if (scale == 1) { - for (int i = 0, n = verticesValue.Count; i < n; i++) - vertices[i + start] = (float)verticesValue[i]; - } else { - for (int i = 0, n = verticesValue.Count; i < n; i++) - vertices[i + start] = (float)verticesValue[i] * scale; - } - if (attachment is MeshAttachment) { - float[] meshVertices = ((MeshAttachment)attachment).vertices; - for (int i = 0; i < vertexCount; i++) - vertices[i] += meshVertices[i]; - } - } - - timeline.SetFrame(frameIndex, (float)valueMap["time"], vertices); - ReadCurve(timeline, frameIndex, valueMap); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); - } - } - } - } - - if (map.ContainsKey("drawOrder") || map.ContainsKey("draworder")) { - var values = (List)map[map.ContainsKey("drawOrder") ? "drawOrder" : "draworder"]; - var timeline = new DrawOrderTimeline(values.Count); - int slotCount = skeletonData.slots.Count; - int frameIndex = 0; - foreach (Dictionary drawOrderMap in values) { - int[] drawOrder = null; - if (drawOrderMap.ContainsKey("offsets")) { - drawOrder = new int[slotCount]; - for (int i = slotCount - 1; i >= 0; i--) - drawOrder[i] = -1; - var offsets = (List)drawOrderMap["offsets"]; - int[] unchanged = new int[slotCount - offsets.Count]; - int originalIndex = 0, unchangedIndex = 0; - foreach (Dictionary offsetMap in offsets) { - int slotIndex = skeletonData.FindSlotIndex((String)offsetMap["slot"]); - if (slotIndex == -1) throw new Exception("Slot not found: " + offsetMap["slot"]); - // Collect unchanged items. - while (originalIndex != slotIndex) - unchanged[unchangedIndex++] = originalIndex++; - // Set changed items. - int index = originalIndex + (int)(float)offsetMap["offset"]; - drawOrder[index] = originalIndex++; - } - // Collect remaining unchanged items. - while (originalIndex < slotCount) - unchanged[unchangedIndex++] = originalIndex++; - // Fill in unchanged items. - for (int i = slotCount - 1; i >= 0; i--) - if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex]; - } - timeline.SetFrame(frameIndex++, (float)drawOrderMap["time"], drawOrder); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); - } - - if (map.ContainsKey("events")) { - var eventsMap = (List)map["events"]; - var timeline = new EventTimeline(eventsMap.Count); - int frameIndex = 0; - foreach (Dictionary eventMap in eventsMap) { - EventData eventData = skeletonData.FindEvent((String)eventMap["name"]); - if (eventData == null) throw new Exception("Event not found: " + eventMap["name"]); - var e = new Event(eventData); - e.Int = GetInt(eventMap, "int", eventData.Int); - e.Float = GetFloat(eventMap, "float", eventData.Float); - e.String = GetString(eventMap, "string", eventData.String); - timeline.SetFrame(frameIndex++, (float)eventMap["time"], e); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); - } - - timelines.TrimExcess(); - skeletonData.animations.Add(new Animation(name, timelines, duration)); - } - - private void ReadCurve (CurveTimeline timeline, int frameIndex, Dictionary valueMap) { - if (!valueMap.ContainsKey("curve")) - return; - Object curveObject = valueMap["curve"]; - if (curveObject.Equals("stepped")) - timeline.SetStepped(frameIndex); - else if (curveObject is List) { - var curve = (List)curveObject; - timeline.SetCurve(frameIndex, (float)curve[0], (float)curve[1], (float)curve[2], (float)curve[3]); - } - } - } + public SkeletonData ReadSkeletonData(TextReader reader) + { + if (reader == null) throw new ArgumentNullException("reader cannot be null."); + + var skeletonData = new SkeletonData(); + + var root = Json.Deserialize(reader) as Dictionary; + if (root == null) throw new Exception("Invalid JSON."); + + // Skeleton. + if (root.ContainsKey("skeleton")) + { + var skeletonMap = (Dictionary)root["skeleton"]; + skeletonData.hash = (String)skeletonMap["hash"]; + skeletonData.version = (String)skeletonMap["spine"]; + skeletonData.width = GetFloat(skeletonMap, "width", 0); + skeletonData.height = GetFloat(skeletonMap, "height", 0); + } + + // Bones. + foreach (Dictionary boneMap in (List)root["bones"]) + { + BoneData parent = null; + if (boneMap.ContainsKey("parent")) + { + parent = skeletonData.FindBone((String)boneMap["parent"]); + if (parent == null) + throw new Exception("Parent bone not found: " + boneMap["parent"]); + } + var boneData = new BoneData((String)boneMap["name"], parent); + boneData.length = GetFloat(boneMap, "length", 0) * Scale; + boneData.x = GetFloat(boneMap, "x", 0) * Scale; + boneData.y = GetFloat(boneMap, "y", 0) * Scale; + boneData.rotation = GetFloat(boneMap, "rotation", 0); + boneData.scaleX = GetFloat(boneMap, "scaleX", 1); + boneData.scaleY = GetFloat(boneMap, "scaleY", 1); + boneData.flipX = GetBoolean(boneMap, "flipX", false); + boneData.flipY = GetBoolean(boneMap, "flipY", false); + boneData.inheritScale = GetBoolean(boneMap, "inheritScale", true); + boneData.inheritRotation = GetBoolean(boneMap, "inheritRotation", true); + skeletonData.bones.Add(boneData); + } + + // IK constraints. + if (root.ContainsKey("ik")) + { + foreach (Dictionary ikMap in (List)root["ik"]) + { + IkConstraintData ikConstraintData = new IkConstraintData((String)ikMap["name"]); + + foreach (String boneName in (List)ikMap["bones"]) + { + BoneData bone = skeletonData.FindBone(boneName); + if (bone == null) throw new Exception("IK bone not found: " + boneName); + ikConstraintData.bones.Add(bone); + } + + String targetName = (String)ikMap["target"]; + ikConstraintData.target = skeletonData.FindBone(targetName); + if (ikConstraintData.target == null) throw new Exception("Target bone not found: " + targetName); + + ikConstraintData.bendDirection = GetBoolean(ikMap, "bendPositive", true) ? 1 : -1; + ikConstraintData.mix = GetFloat(ikMap, "mix", 1); + + skeletonData.ikConstraints.Add(ikConstraintData); + } + } + + // Slots. + if (root.ContainsKey("slots")) + { + foreach (Dictionary slotMap in (List)root["slots"]) + { + var slotName = (String)slotMap["name"]; + var boneName = (String)slotMap["bone"]; + BoneData boneData = skeletonData.FindBone(boneName); + if (boneData == null) + throw new Exception("Slot bone not found: " + boneName); + var slotData = new SlotData(slotName, boneData); + + if (slotMap.ContainsKey("color")) + { + var color = (String)slotMap["color"]; + slotData.r = ToColor(color, 0); + slotData.g = ToColor(color, 1); + slotData.b = ToColor(color, 2); + slotData.a = ToColor(color, 3); + } + + if (slotMap.ContainsKey("attachment")) + slotData.attachmentName = (String)slotMap["attachment"]; + + if (slotMap.ContainsKey("additive")) + slotData.additiveBlending = (bool)slotMap["additive"]; + + skeletonData.slots.Add(slotData); + } + } + + // Skins. + if (root.ContainsKey("skins")) + { + foreach (KeyValuePair entry in (Dictionary)root["skins"]) + { + var skin = new Skin(entry.Key); + foreach (KeyValuePair slotEntry in (Dictionary)entry.Value) + { + int slotIndex = skeletonData.FindSlotIndex(slotEntry.Key); + foreach (KeyValuePair attachmentEntry in ((Dictionary)slotEntry.Value)) + { + Attachment attachment = ReadAttachment(skin, attachmentEntry.Key, (Dictionary)attachmentEntry.Value); + if (attachment != null) skin.AddAttachment(slotIndex, attachmentEntry.Key, attachment); + } + } + skeletonData.skins.Add(skin); + if (skin.name == "default") + skeletonData.defaultSkin = skin; + } + } + + // Events. + if (root.ContainsKey("events")) + { + foreach (KeyValuePair entry in (Dictionary)root["events"]) + { + var entryMap = (Dictionary)entry.Value; + var eventData = new EventData(entry.Key); + eventData.Int = GetInt(entryMap, "int", 0); + eventData.Float = GetFloat(entryMap, "float", 0); + eventData.String = GetString(entryMap, "string", null); + skeletonData.events.Add(eventData); + } + } + + // Animations. + if (root.ContainsKey("animations")) + { + foreach (KeyValuePair entry in (Dictionary)root["animations"]) + ReadAnimation(entry.Key, (Dictionary)entry.Value, skeletonData); + } + + skeletonData.bones.TrimExcess(); + skeletonData.slots.TrimExcess(); + skeletonData.skins.TrimExcess(); + skeletonData.events.TrimExcess(); + skeletonData.animations.TrimExcess(); + skeletonData.ikConstraints.TrimExcess(); + return skeletonData; + } + + private Attachment ReadAttachment(Skin skin, String name, Dictionary map) + { + if (map.ContainsKey("name")) + name = (String)map["name"]; + + var type = AttachmentType.region; + if (map.ContainsKey("type")) + type = (AttachmentType)Enum.Parse(typeof(AttachmentType), (String)map["type"], false); + + String path = name; + if (map.ContainsKey("path")) + path = (String)map["path"]; + + switch (type) + { + case AttachmentType.region: + RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); + if (region == null) return null; + region.Path = path; + region.x = GetFloat(map, "x", 0) * Scale; + region.y = GetFloat(map, "y", 0) * Scale; + region.scaleX = GetFloat(map, "scaleX", 1); + region.scaleY = GetFloat(map, "scaleY", 1); + region.rotation = GetFloat(map, "rotation", 0); + region.width = GetFloat(map, "width", 32) * Scale; + region.height = GetFloat(map, "height", 32) * Scale; + region.UpdateOffset(); + + if (map.ContainsKey("color")) + { + var color = (String)map["color"]; + region.r = ToColor(color, 0); + region.g = ToColor(color, 1); + region.b = ToColor(color, 2); + region.a = ToColor(color, 3); + } + + return region; + case AttachmentType.mesh: + { + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); + if (mesh == null) return null; + + mesh.Path = path; + mesh.vertices = GetFloatArray(map, "vertices", Scale); + mesh.triangles = GetIntArray(map, "triangles"); + mesh.regionUVs = GetFloatArray(map, "uvs", 1); + mesh.UpdateUVs(); + + if (map.ContainsKey("color")) + { + var color = (String)map["color"]; + mesh.r = ToColor(color, 0); + mesh.g = ToColor(color, 1); + mesh.b = ToColor(color, 2); + mesh.a = ToColor(color, 3); + } + + mesh.HullLength = GetInt(map, "hull", 0) * 2; + if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges"); + mesh.Width = GetInt(map, "width", 0) * Scale; + mesh.Height = GetInt(map, "height", 0) * Scale; + + return mesh; + } + case AttachmentType.skinnedmesh: + { + SkinnedMeshAttachment mesh = attachmentLoader.NewSkinnedMeshAttachment(skin, name, path); + if (mesh == null) return null; + + mesh.Path = path; + float[] uvs = GetFloatArray(map, "uvs", 1); + float[] vertices = GetFloatArray(map, "vertices", 1); + var weights = new List(uvs.Length * 3 * 3); + var bones = new List(uvs.Length * 3); + float scale = Scale; + for (int i = 0, n = vertices.Length; i < n;) + { + int boneCount = (int)vertices[i++]; + bones.Add(boneCount); + for (int nn = i + boneCount * 4; i < nn;) + { + bones.Add((int)vertices[i]); + weights.Add(vertices[i + 1] * scale); + weights.Add(vertices[i + 2] * scale); + weights.Add(vertices[i + 3]); + i += 4; + } + } + mesh.bones = bones.ToArray(); + mesh.weights = weights.ToArray(); + mesh.triangles = GetIntArray(map, "triangles"); + mesh.regionUVs = uvs; + mesh.UpdateUVs(); + + if (map.ContainsKey("color")) + { + var color = (String)map["color"]; + mesh.r = ToColor(color, 0); + mesh.g = ToColor(color, 1); + mesh.b = ToColor(color, 2); + mesh.a = ToColor(color, 3); + } + + mesh.HullLength = GetInt(map, "hull", 0) * 2; + if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges"); + mesh.Width = GetInt(map, "width", 0) * Scale; + mesh.Height = GetInt(map, "height", 0) * Scale; + + return mesh; + } + case AttachmentType.boundingbox: + BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); + if (box == null) return null; + box.vertices = GetFloatArray(map, "vertices", Scale); + return box; + } + return null; + } + + private float[] GetFloatArray(Dictionary map, String name, float scale) + { + var list = (List)map[name]; + var values = new float[list.Count]; + if (scale == 1) + { + for (int i = 0, n = list.Count; i < n; i++) + values[i] = (float)list[i]; + } + else + { + for (int i = 0, n = list.Count; i < n; i++) + values[i] = (float)list[i] * scale; + } + return values; + } + + private int[] GetIntArray(Dictionary map, String name) + { + var list = (List)map[name]; + var values = new int[list.Count]; + for (int i = 0, n = list.Count; i < n; i++) + values[i] = (int)(float)list[i]; + return values; + } + + private float GetFloat(Dictionary map, String name, float defaultValue) + { + if (!map.ContainsKey(name)) + return defaultValue; + return (float)map[name]; + } + + private int GetInt(Dictionary map, String name, int defaultValue) + { + if (!map.ContainsKey(name)) + return defaultValue; + return (int)(float)map[name]; + } + + private bool GetBoolean(Dictionary map, String name, bool defaultValue) + { + if (!map.ContainsKey(name)) + return defaultValue; + return (bool)map[name]; + } + + private String GetString(Dictionary map, String name, String defaultValue) + { + if (!map.ContainsKey(name)) + return defaultValue; + return (String)map[name]; + } + + private float ToColor(String hexString, int colorIndex) + { + if (hexString.Length != 8) + throw new ArgumentException("Color hexidecimal length must be 8, recieved: " + hexString); + return Convert.ToInt32(hexString.Substring(colorIndex * 2, 2), 16) / (float)255; + } + + private void ReadAnimation(String name, Dictionary map, SkeletonData skeletonData) + { + var timelines = new List(); + float duration = 0; + float scale = Scale; + + if (map.ContainsKey("slots")) + { + foreach (KeyValuePair entry in (Dictionary)map["slots"]) + { + String slotName = entry.Key; + int slotIndex = skeletonData.FindSlotIndex(slotName); + var timelineMap = (Dictionary)entry.Value; + + foreach (KeyValuePair timelineEntry in timelineMap) + { + var values = (List)timelineEntry.Value; + var timelineName = timelineEntry.Key; + if (timelineName == "color") + { + var timeline = new ColorTimeline(values.Count); + timeline.slotIndex = slotIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + String c = (String)valueMap["color"]; + timeline.SetFrame(frameIndex, time, ToColor(c, 0), ToColor(c, 1), ToColor(c, 2), ToColor(c, 3)); + ReadCurve(timeline, frameIndex, valueMap); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 5 - 5]); + + } + else if (timelineName == "attachment") + { + var timeline = new AttachmentTimeline(values.Count); + timeline.slotIndex = slotIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + timeline.SetFrame(frameIndex++, time, (String)valueMap["name"]); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); + + } + else + throw new Exception("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")"); + } + } + } + + if (map.ContainsKey("bones")) + { + foreach (KeyValuePair entry in (Dictionary)map["bones"]) + { + String boneName = entry.Key; + int boneIndex = skeletonData.FindBoneIndex(boneName); + if (boneIndex == -1) + throw new Exception("Bone not found: " + boneName); + + var timelineMap = (Dictionary)entry.Value; + foreach (KeyValuePair timelineEntry in timelineMap) + { + var values = (List)timelineEntry.Value; + var timelineName = timelineEntry.Key; + if (timelineName == "rotate") + { + var timeline = new RotateTimeline(values.Count); + timeline.boneIndex = boneIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + timeline.SetFrame(frameIndex, time, (float)valueMap["angle"]); + ReadCurve(timeline, frameIndex, valueMap); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 2 - 2]); + + } + else if (timelineName == "translate" || timelineName == "scale") + { + TranslateTimeline timeline; + float timelineScale = 1; + if (timelineName == "scale") + timeline = new ScaleTimeline(values.Count); + else + { + timeline = new TranslateTimeline(values.Count); + timelineScale = scale; + } + timeline.boneIndex = boneIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + float x = valueMap.ContainsKey("x") ? (float)valueMap["x"] : 0; + float y = valueMap.ContainsKey("y") ? (float)valueMap["y"] : 0; + timeline.SetFrame(frameIndex, time, (float)x * timelineScale, (float)y * timelineScale); + ReadCurve(timeline, frameIndex, valueMap); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 3 - 3]); + + } + else if (timelineName == "flipX" || timelineName == "flipY") + { + bool x = timelineName == "flipX"; + var timeline = x ? new FlipXTimeline(values.Count) : new FlipYTimeline(values.Count); + timeline.boneIndex = boneIndex; + + String field = x ? "x" : "y"; + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + timeline.SetFrame(frameIndex, time, valueMap.ContainsKey(field) ? (bool)valueMap[field] : false); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 2 - 2]); + + } + else + throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")"); + } + } + } + + if (map.ContainsKey("ik")) + { + foreach (KeyValuePair ikMap in (Dictionary)map["ik"]) + { + IkConstraintData ikConstraint = skeletonData.FindIkConstraint(ikMap.Key); + var values = (List)ikMap.Value; + var timeline = new IkConstraintTimeline(values.Count); + timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(ikConstraint); + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + float mix = valueMap.ContainsKey("mix") ? (float)valueMap["mix"] : 1; + bool bendPositive = valueMap.ContainsKey("bendPositive") ? (bool)valueMap["bendPositive"] : true; + timeline.SetFrame(frameIndex, time, mix, bendPositive ? 1 : -1); + ReadCurve(timeline, frameIndex, valueMap); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 3 - 3]); + } + } + + if (map.ContainsKey("ffd")) + { + foreach (KeyValuePair ffdMap in (Dictionary)map["ffd"]) + { + Skin skin = skeletonData.FindSkin(ffdMap.Key); + foreach (KeyValuePair slotMap in (Dictionary)ffdMap.Value) + { + int slotIndex = skeletonData.FindSlotIndex(slotMap.Key); + foreach (KeyValuePair meshMap in (Dictionary)slotMap.Value) + { + var values = (List)meshMap.Value; + var timeline = new FFDTimeline(values.Count); + Attachment attachment = skin.GetAttachment(slotIndex, meshMap.Key); + if (attachment == null) throw new Exception("FFD attachment not found: " + meshMap.Key); + timeline.slotIndex = slotIndex; + timeline.attachment = attachment; + + int vertexCount; + if (attachment is MeshAttachment) + vertexCount = ((MeshAttachment)attachment).vertices.Length; + else + vertexCount = ((SkinnedMeshAttachment)attachment).Weights.Length / 3 * 2; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float[] vertices; + if (!valueMap.ContainsKey("vertices")) + { + if (attachment is MeshAttachment) + vertices = ((MeshAttachment)attachment).vertices; + else + vertices = new float[vertexCount]; + } + else + { + var verticesValue = (List)valueMap["vertices"]; + vertices = new float[vertexCount]; + int start = GetInt(valueMap, "offset", 0); + if (scale == 1) + { + for (int i = 0, n = verticesValue.Count; i < n; i++) + vertices[i + start] = (float)verticesValue[i]; + } + else + { + for (int i = 0, n = verticesValue.Count; i < n; i++) + vertices[i + start] = (float)verticesValue[i] * scale; + } + if (attachment is MeshAttachment) + { + float[] meshVertices = ((MeshAttachment)attachment).vertices; + for (int i = 0; i < vertexCount; i++) + vertices[i] += meshVertices[i]; + } + } + + timeline.SetFrame(frameIndex, (float)valueMap["time"], vertices); + ReadCurve(timeline, frameIndex, valueMap); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); + } + } + } + } + + if (map.ContainsKey("drawOrder") || map.ContainsKey("draworder")) + { + var values = (List)map[map.ContainsKey("drawOrder") ? "drawOrder" : "draworder"]; + var timeline = new DrawOrderTimeline(values.Count); + int slotCount = skeletonData.slots.Count; + int frameIndex = 0; + foreach (Dictionary drawOrderMap in values) + { + int[] drawOrder = null; + if (drawOrderMap.ContainsKey("offsets")) + { + drawOrder = new int[slotCount]; + for (int i = slotCount - 1; i >= 0; i--) + drawOrder[i] = -1; + var offsets = (List)drawOrderMap["offsets"]; + int[] unchanged = new int[slotCount - offsets.Count]; + int originalIndex = 0, unchangedIndex = 0; + foreach (Dictionary offsetMap in offsets) + { + int slotIndex = skeletonData.FindSlotIndex((String)offsetMap["slot"]); + if (slotIndex == -1) throw new Exception("Slot not found: " + offsetMap["slot"]); + // Collect unchanged items. + while (originalIndex != slotIndex) + unchanged[unchangedIndex++] = originalIndex++; + // Set changed items. + int index = originalIndex + (int)(float)offsetMap["offset"]; + drawOrder[index] = originalIndex++; + } + // Collect remaining unchanged items. + while (originalIndex < slotCount) + unchanged[unchangedIndex++] = originalIndex++; + // Fill in unchanged items. + for (int i = slotCount - 1; i >= 0; i--) + if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex]; + } + timeline.SetFrame(frameIndex++, (float)drawOrderMap["time"], drawOrder); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); + } + + if (map.ContainsKey("events")) + { + var eventsMap = (List)map["events"]; + var timeline = new EventTimeline(eventsMap.Count); + int frameIndex = 0; + foreach (Dictionary eventMap in eventsMap) + { + EventData eventData = skeletonData.FindEvent((String)eventMap["name"]); + if (eventData == null) throw new Exception("Event not found: " + eventMap["name"]); + var e = new Event(eventData); + e.Int = GetInt(eventMap, "int", eventData.Int); + e.Float = GetFloat(eventMap, "float", eventData.Float); + e.String = GetString(eventMap, "string", eventData.String); + timeline.SetFrame(frameIndex++, (float)eventMap["time"], e); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); + } + + timelines.TrimExcess(); + skeletonData.animations.Add(new Animation(name, timelines, duration)); + } + + private void ReadCurve(CurveTimeline timeline, int frameIndex, Dictionary valueMap) + { + if (!valueMap.ContainsKey("curve")) + return; + Object curveObject = valueMap["curve"]; + if (curveObject.Equals("stepped")) + timeline.SetStepped(frameIndex); + else if (curveObject is List) + { + var curve = (List)curveObject; + timeline.SetCurve(frameIndex, (float)curve[0], (float)curve[1], (float)curve[2], (float)curve[3]); + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Skin.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Skin.cs index e590bec..5271250 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Skin.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Skin.cs @@ -31,71 +31,85 @@ using System; using System.Collections.Generic; -namespace Spine2_1_25 { - /// Stores attachments by slot index and attachment name. - public class Skin { - internal String name; - private Dictionary, Attachment> attachments = - new Dictionary, Attachment>(AttachmentComparer.Instance); +namespace Spine2_1_25 +{ + /// Stores attachments by slot index and attachment name. + public class Skin + { + internal String name; + private Dictionary, Attachment> attachments = + new Dictionary, Attachment>(AttachmentComparer.Instance); - public String Name { get { return name; } } + public String Name { get { return name; } } - public Skin (String name) { - if (name == null) throw new ArgumentNullException("name cannot be null."); - this.name = name; - } + public Skin(String name) + { + if (name == null) throw new ArgumentNullException("name cannot be null."); + this.name = name; + } - public void AddAttachment (int slotIndex, String name, Attachment attachment) { - if (attachment == null) throw new ArgumentNullException("attachment cannot be null."); - attachments[new KeyValuePair(slotIndex, name)] = attachment; - } + public void AddAttachment(int slotIndex, String name, Attachment attachment) + { + if (attachment == null) throw new ArgumentNullException("attachment cannot be null."); + attachments[new KeyValuePair(slotIndex, name)] = attachment; + } - /// May be null. - public Attachment GetAttachment (int slotIndex, String name) { - Attachment attachment; - attachments.TryGetValue(new KeyValuePair(slotIndex, name), out attachment); - return attachment; - } + /// May be null. + public Attachment GetAttachment(int slotIndex, String name) + { + Attachment attachment; + attachments.TryGetValue(new KeyValuePair(slotIndex, name), out attachment); + return attachment; + } - public void FindNamesForSlot (int slotIndex, List names) { - if (names == null) throw new ArgumentNullException("names cannot be null."); - foreach (KeyValuePair key in attachments.Keys) - if (key.Key == slotIndex) names.Add(key.Value); - } + public void FindNamesForSlot(int slotIndex, List names) + { + if (names == null) throw new ArgumentNullException("names cannot be null."); + foreach (KeyValuePair key in attachments.Keys) + if (key.Key == slotIndex) names.Add(key.Value); + } - public void FindAttachmentsForSlot (int slotIndex, List attachments) { - if (attachments == null) throw new ArgumentNullException("attachments cannot be null."); - foreach (KeyValuePair, Attachment> entry in this.attachments) - if (entry.Key.Key == slotIndex) attachments.Add(entry.Value); - } + public void FindAttachmentsForSlot(int slotIndex, List attachments) + { + if (attachments == null) throw new ArgumentNullException("attachments cannot be null."); + foreach (KeyValuePair, Attachment> entry in this.attachments) + if (entry.Key.Key == slotIndex) attachments.Add(entry.Value); + } - override public String ToString () { - return name; - } + override public String ToString() + { + return name; + } - /// Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached. - internal void AttachAll (Skeleton skeleton, Skin oldSkin) { - foreach (KeyValuePair, Attachment> entry in oldSkin.attachments) { - int slotIndex = entry.Key.Key; - Slot slot = skeleton.slots[slotIndex]; - if (slot.attachment == entry.Value) { - Attachment attachment = GetAttachment(slotIndex, entry.Key.Value); - if (attachment != null) slot.Attachment = attachment; - } - } - } + /// Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached. + internal void AttachAll(Skeleton skeleton, Skin oldSkin) + { + foreach (KeyValuePair, Attachment> entry in oldSkin.attachments) + { + int slotIndex = entry.Key.Key; + Slot slot = skeleton.slots[slotIndex]; + if (slot.attachment == entry.Value) + { + Attachment attachment = GetAttachment(slotIndex, entry.Key.Value); + if (attachment != null) slot.Attachment = attachment; + } + } + } - // Avoids boxing in the dictionary. - private class AttachmentComparer : IEqualityComparer> { - internal static readonly AttachmentComparer Instance = new AttachmentComparer(); + // Avoids boxing in the dictionary. + private class AttachmentComparer : IEqualityComparer> + { + internal static readonly AttachmentComparer Instance = new AttachmentComparer(); - bool IEqualityComparer>.Equals (KeyValuePair o1, KeyValuePair o2) { - return o1.Key == o2.Key && o1.Value == o2.Value; - } + bool IEqualityComparer>.Equals(KeyValuePair o1, KeyValuePair o2) + { + return o1.Key == o2.Key && o1.Value == o2.Value; + } - int IEqualityComparer>.GetHashCode (KeyValuePair o) { - return o.Key; - } - } - } + int IEqualityComparer>.GetHashCode(KeyValuePair o) + { + return o.Key; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Slot.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Slot.cs index 973858b..7077602 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Slot.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/Slot.cs @@ -30,70 +30,82 @@ using System; -namespace Spine2_1_25 { - public class Slot { - internal SlotData data; - internal Bone bone; - internal float r, g, b, a; - internal Attachment attachment; - internal float attachmentTime; - internal float[] attachmentVertices = new float[0]; - internal int attachmentVerticesCount; +namespace Spine2_1_25 +{ + public class Slot + { + internal SlotData data; + internal Bone bone; + internal float r, g, b, a; + internal Attachment attachment; + internal float attachmentTime; + internal float[] attachmentVertices = new float[0]; + internal int attachmentVerticesCount; - public SlotData Data { get { return data; } } - public Bone Bone { get { return bone; } } - public Skeleton Skeleton { get { return bone.skeleton; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } + public SlotData Data { get { return data; } } + public Bone Bone { get { return bone; } } + public Skeleton Skeleton { get { return bone.skeleton; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } - /// May be null. - public Attachment Attachment { - get { - return attachment; - } - set { - attachment = value; - attachmentTime = bone.skeleton.time; - attachmentVerticesCount = 0; - } - } + /// May be null. + public Attachment Attachment + { + get + { + return attachment; + } + set + { + attachment = value; + attachmentTime = bone.skeleton.time; + attachmentVerticesCount = 0; + } + } - public float AttachmentTime { - get { - return bone.skeleton.time - attachmentTime; - } - set { - attachmentTime = bone.skeleton.time - value; - } - } + public float AttachmentTime + { + get + { + return bone.skeleton.time - attachmentTime; + } + set + { + attachmentTime = bone.skeleton.time - value; + } + } - public float[] AttachmentVertices { get { return attachmentVertices; } set { attachmentVertices = value; } } - public int AttachmentVerticesCount { get { return attachmentVerticesCount; } set { attachmentVerticesCount = value; } } + public float[] AttachmentVertices { get { return attachmentVertices; } set { attachmentVertices = value; } } + public int AttachmentVerticesCount { get { return attachmentVerticesCount; } set { attachmentVerticesCount = value; } } - public Slot (SlotData data, Bone bone) { - if (data == null) throw new ArgumentNullException("data cannot be null."); - if (bone == null) throw new ArgumentNullException("bone cannot be null."); - this.data = data; - this.bone = bone; - SetToSetupPose(); - } + public Slot(SlotData data, Bone bone) + { + if (data == null) throw new ArgumentNullException("data cannot be null."); + if (bone == null) throw new ArgumentNullException("bone cannot be null."); + this.data = data; + this.bone = bone; + SetToSetupPose(); + } - internal void SetToSetupPose (int slotIndex) { - r = data.r; - g = data.g; - b = data.b; - a = data.a; - Attachment = data.attachmentName == null ? null : bone.skeleton.GetAttachment(slotIndex, data.attachmentName); - } + internal void SetToSetupPose(int slotIndex) + { + r = data.r; + g = data.g; + b = data.b; + a = data.a; + Attachment = data.attachmentName == null ? null : bone.skeleton.GetAttachment(slotIndex, data.attachmentName); + } - public void SetToSetupPose () { - SetToSetupPose(bone.skeleton.data.slots.IndexOf(data)); - } + public void SetToSetupPose() + { + SetToSetupPose(bone.skeleton.data.slots.IndexOf(data)); + } - override public String ToString () { - return data.name; - } - } + override public String ToString() + { + return data.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/SlotData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/SlotData.cs index d4e41a1..7be6c86 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/SlotData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/SlotData.cs @@ -30,33 +30,37 @@ using System; -namespace Spine2_1_25 { - public class SlotData { - internal String name; - internal BoneData boneData; - internal float r = 1, g = 1, b = 1, a = 1; - internal String attachmentName; - internal bool additiveBlending; +namespace Spine2_1_25 +{ + public class SlotData + { + internal String name; + internal BoneData boneData; + internal float r = 1, g = 1, b = 1, a = 1; + internal String attachmentName; + internal bool additiveBlending; - public String Name { get { return name; } } - public BoneData BoneData { get { return boneData; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } - /// May be null. - public String AttachmentName { get { return attachmentName; } set { attachmentName = value; } } - public bool AdditiveBlending { get { return additiveBlending; } set { additiveBlending = value; } } + public String Name { get { return name; } } + public BoneData BoneData { get { return boneData; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } + /// May be null. + public String AttachmentName { get { return attachmentName; } set { attachmentName = value; } } + public bool AdditiveBlending { get { return additiveBlending; } set { additiveBlending = value; } } - public SlotData (String name, BoneData boneData) { - if (name == null) throw new ArgumentNullException("name cannot be null."); - if (boneData == null) throw new ArgumentNullException("boneData cannot be null."); - this.name = name; - this.boneData = boneData; - } + public SlotData(String name, BoneData boneData) + { + if (name == null) throw new ArgumentNullException("name cannot be null."); + if (boneData == null) throw new ArgumentNullException("boneData cannot be null."); + this.name = name; + this.boneData = boneData; + } - override public String ToString () { - return name; - } - } + override public String ToString() + { + return name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/XnaLoader/MeshBatcher.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/XnaLoader/MeshBatcher.cs index 416fe35..a81ea29 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/XnaLoader/MeshBatcher.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/XnaLoader/MeshBatcher.cs @@ -31,136 +31,146 @@ using System; using System.Collections.Generic; using Microsoft.Xna.Framework.Graphics; -using Microsoft.Xna.Framework; -namespace Spine2_1_25 { - // #region License - // /* - // Microsoft Public License (Ms-PL) - // MonoGame - Copyright 2009 The MonoGame Team - // - // All rights reserved. - // - // This license governs use of the accompanying software. If you use the software, you accept this license. If you do not - // accept the license, do not use the software. - // - // 1. Definitions - // The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under - // U.S. copyright law. - // - // A "contribution" is the original software, or any additions or changes to the software. - // A "contributor" is any person that distributes its contribution under this license. - // "Licensed patents" are a contributor's patent claims that read directly on its contribution. - // - // 2. Grant of Rights - // (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, - // each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. - // (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, - // each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. - // - // 3. Conditions and Limitations - // (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. - // (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, - // your patent license from such contributor to the software ends automatically. - // (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution - // notices that are present in the software. - // (D) If you distribute any portion of the software in source code form, you may do so only under this license by including - // a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object - // code form, you may only do so under a license that complies with this license. - // (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees - // or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent - // permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular - // purpose and non-infringement. - // */ - // #endregion License - // +namespace Spine2_1_25 +{ + // #region License + // /* + // Microsoft Public License (Ms-PL) + // MonoGame - Copyright 2009 The MonoGame Team + // + // All rights reserved. + // + // This license governs use of the accompanying software. If you use the software, you accept this license. If you do not + // accept the license, do not use the software. + // + // 1. Definitions + // The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under + // U.S. copyright law. + // + // A "contribution" is the original software, or any additions or changes to the software. + // A "contributor" is any person that distributes its contribution under this license. + // "Licensed patents" are a contributor's patent claims that read directly on its contribution. + // + // 2. Grant of Rights + // (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, + // each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. + // (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, + // each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. + // + // 3. Conditions and Limitations + // (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. + // (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, + // your patent license from such contributor to the software ends automatically. + // (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution + // notices that are present in the software. + // (D) If you distribute any portion of the software in source code form, you may do so only under this license by including + // a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object + // code form, you may only do so under a license that complies with this license. + // (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees + // or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent + // permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular + // purpose and non-infringement. + // */ + // #endregion License + // - /// Draws batched meshes. - public class MeshBatcher { - private readonly List items; - private readonly Queue freeItems; - private VertexPositionColorTexture[] vertexArray = { }; - private short[] triangles = { }; + /// Draws batched meshes. + public class MeshBatcher + { + private readonly List items; + private readonly Queue freeItems; + private VertexPositionColorTexture[] vertexArray = { }; + private short[] triangles = { }; - public MeshBatcher () { - items = new List(256); - freeItems = new Queue(256); - EnsureCapacity(256, 512); - } + public MeshBatcher() + { + items = new List(256); + freeItems = new Queue(256); + EnsureCapacity(256, 512); + } - /// Returns a pooled MeshItem. - public MeshItem NextItem (int vertexCount, int triangleCount) { - MeshItem item = freeItems.Count > 0 ? freeItems.Dequeue() : new MeshItem(); - if (item.vertices.Length < vertexCount) item.vertices = new VertexPositionColorTexture[vertexCount]; - if (item.triangles.Length < triangleCount) item.triangles = new int[triangleCount]; - item.vertexCount = vertexCount; - item.triangleCount = triangleCount; - items.Add(item); - return item; - } + /// Returns a pooled MeshItem. + public MeshItem NextItem(int vertexCount, int triangleCount) + { + MeshItem item = freeItems.Count > 0 ? freeItems.Dequeue() : new MeshItem(); + if (item.vertices.Length < vertexCount) item.vertices = new VertexPositionColorTexture[vertexCount]; + if (item.triangles.Length < triangleCount) item.triangles = new int[triangleCount]; + item.vertexCount = vertexCount; + item.triangleCount = triangleCount; + items.Add(item); + return item; + } - private void EnsureCapacity (int vertexCount, int triangleCount) { - if (vertexArray.Length < vertexCount) vertexArray = new VertexPositionColorTexture[vertexCount]; - if (triangles.Length < triangleCount) triangles = new short[triangleCount]; - } + private void EnsureCapacity(int vertexCount, int triangleCount) + { + if (vertexArray.Length < vertexCount) vertexArray = new VertexPositionColorTexture[vertexCount]; + if (triangles.Length < triangleCount) triangles = new short[triangleCount]; + } - public void Draw (GraphicsDevice device) { - if (items.Count == 0) return; + public void Draw(GraphicsDevice device) + { + if (items.Count == 0) return; - int itemCount = items.Count; - int vertexCount = 0, triangleCount = 0; - for (int i = 0; i < itemCount; i++) { - MeshItem item = items[i]; - vertexCount += item.vertexCount; - triangleCount += item.triangleCount; - } - EnsureCapacity(vertexCount, triangleCount); + int itemCount = items.Count; + int vertexCount = 0, triangleCount = 0; + for (int i = 0; i < itemCount; i++) + { + MeshItem item = items[i]; + vertexCount += item.vertexCount; + triangleCount += item.triangleCount; + } + EnsureCapacity(vertexCount, triangleCount); - vertexCount = 0; - triangleCount = 0; - Texture2D lastTexture = null; - for (int i = 0; i < itemCount; i++) { - MeshItem item = items[i]; - int itemVertexCount = item.vertexCount; + vertexCount = 0; + triangleCount = 0; + Texture2D lastTexture = null; + for (int i = 0; i < itemCount; i++) + { + MeshItem item = items[i]; + int itemVertexCount = item.vertexCount; - if (item.texture != lastTexture || vertexCount + itemVertexCount > short.MaxValue) { - FlushVertexArray(device, vertexCount, triangleCount); - vertexCount = 0; - triangleCount = 0; - lastTexture = item.texture; - device.Textures[0] = lastTexture; - } + if (item.texture != lastTexture || vertexCount + itemVertexCount > short.MaxValue) + { + FlushVertexArray(device, vertexCount, triangleCount); + vertexCount = 0; + triangleCount = 0; + lastTexture = item.texture; + device.Textures[0] = lastTexture; + } - int[] itemTriangles = item.triangles; - int itemTriangleCount = item.triangleCount; - for (int ii = 0, t = triangleCount; ii < itemTriangleCount; ii++, t++) - triangles[t] = (short)(itemTriangles[ii] + vertexCount); - triangleCount += itemTriangleCount; + int[] itemTriangles = item.triangles; + int itemTriangleCount = item.triangleCount; + for (int ii = 0, t = triangleCount; ii < itemTriangleCount; ii++, t++) + triangles[t] = (short)(itemTriangles[ii] + vertexCount); + triangleCount += itemTriangleCount; - Array.Copy(item.vertices, 0, vertexArray, vertexCount, itemVertexCount); - vertexCount += itemVertexCount; + Array.Copy(item.vertices, 0, vertexArray, vertexCount, itemVertexCount); + vertexCount += itemVertexCount; - item.texture = null; - freeItems.Enqueue(item); - } - FlushVertexArray(device, vertexCount, triangleCount); - items.Clear(); - } + item.texture = null; + freeItems.Enqueue(item); + } + FlushVertexArray(device, vertexCount, triangleCount); + items.Clear(); + } - private void FlushVertexArray (GraphicsDevice device, int vertexCount, int triangleCount) { - if (vertexCount == 0) return; - device.DrawUserIndexedPrimitives( - PrimitiveType.TriangleList, - vertexArray, 0, vertexCount, - triangles, 0, triangleCount / 3, - VertexPositionColorTexture.VertexDeclaration); - } - } + private void FlushVertexArray(GraphicsDevice device, int vertexCount, int triangleCount) + { + if (vertexCount == 0) return; + device.DrawUserIndexedPrimitives( + PrimitiveType.TriangleList, + vertexArray, 0, vertexCount, + triangles, 0, triangleCount / 3, + VertexPositionColorTexture.VertexDeclaration); + } + } - public class MeshItem { - public Texture2D texture; - public int vertexCount, triangleCount; - public VertexPositionColorTexture[] vertices = { }; - public int[] triangles = { }; - } + public class MeshItem + { + public Texture2D texture; + public int vertexCount, triangleCount; + public VertexPositionColorTexture[] vertices = { }; + public int[] triangles = { }; + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/XnaLoader/RegionBatcher.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/XnaLoader/RegionBatcher.cs index 18cfc94..107e073 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/XnaLoader/RegionBatcher.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/XnaLoader/RegionBatcher.cs @@ -31,151 +31,163 @@ using System; using System.Collections.Generic; using Microsoft.Xna.Framework.Graphics; -using Microsoft.Xna.Framework; -namespace Spine2_1_25 { - // #region License - // /* - // Microsoft Public License (Ms-PL) - // MonoGame - Copyright 2009 The MonoGame Team - // - // All rights reserved. - // - // This license governs use of the accompanying software. If you use the software, you accept this license. If you do not - // accept the license, do not use the software. - // - // 1. Definitions - // The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under - // U.S. copyright law. - // - // A "contribution" is the original software, or any additions or changes to the software. - // A "contributor" is any person that distributes its contribution under this license. - // "Licensed patents" are a contributor's patent claims that read directly on its contribution. - // - // 2. Grant of Rights - // (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, - // each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. - // (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, - // each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. - // - // 3. Conditions and Limitations - // (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. - // (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, - // your patent license from such contributor to the software ends automatically. - // (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution - // notices that are present in the software. - // (D) If you distribute any portion of the software in source code form, you may do so only under this license by including - // a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object - // code form, you may only do so under a license that complies with this license. - // (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees - // or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent - // permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular - // purpose and non-infringement. - // */ - // #endregion License - // +namespace Spine2_1_25 +{ + // #region License + // /* + // Microsoft Public License (Ms-PL) + // MonoGame - Copyright 2009 The MonoGame Team + // + // All rights reserved. + // + // This license governs use of the accompanying software. If you use the software, you accept this license. If you do not + // accept the license, do not use the software. + // + // 1. Definitions + // The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under + // U.S. copyright law. + // + // A "contribution" is the original software, or any additions or changes to the software. + // A "contributor" is any person that distributes its contribution under this license. + // "Licensed patents" are a contributor's patent claims that read directly on its contribution. + // + // 2. Grant of Rights + // (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, + // each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. + // (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, + // each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. + // + // 3. Conditions and Limitations + // (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. + // (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, + // your patent license from such contributor to the software ends automatically. + // (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution + // notices that are present in the software. + // (D) If you distribute any portion of the software in source code form, you may do so only under this license by including + // a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object + // code form, you may only do so under a license that complies with this license. + // (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees + // or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent + // permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular + // purpose and non-infringement. + // */ + // #endregion License + // - /// Draws batched quads using indices. - public class RegionBatcher { - private const int maxBatchSize = short.MaxValue / 6; // 6 = 4 vertices unique and 2 shared, per quad - private readonly List items; - private readonly Queue freeItems; - private VertexPositionColorTexture[] vertexArray; - private short[] indices; + /// Draws batched quads using indices. + public class RegionBatcher + { + private const int maxBatchSize = short.MaxValue / 6; // 6 = 4 vertices unique and 2 shared, per quad + private readonly List items; + private readonly Queue freeItems; + private VertexPositionColorTexture[] vertexArray; + private short[] indices; - public RegionBatcher () { - items = new List(256); - freeItems = new Queue(256); - EnsureArrayCapacity(256); - } + public RegionBatcher() + { + items = new List(256); + freeItems = new Queue(256); + EnsureArrayCapacity(256); + } - /// Returns a pooled RegionItem. - public RegionItem NextItem () { - RegionItem item = freeItems.Count > 0 ? freeItems.Dequeue() : new RegionItem(); - items.Add(item); - return item; - } + /// Returns a pooled RegionItem. + public RegionItem NextItem() + { + RegionItem item = freeItems.Count > 0 ? freeItems.Dequeue() : new RegionItem(); + items.Add(item); + return item; + } - /// Resize and recreate the indices and vertex position color buffers. - private void EnsureArrayCapacity (int itemCount) { - if (indices != null && indices.Length >= 6 * itemCount) return; + /// Resize and recreate the indices and vertex position color buffers. + private void EnsureArrayCapacity(int itemCount) + { + if (indices != null && indices.Length >= 6 * itemCount) return; - short[] newIndices = new short[6 * itemCount]; - int start = 0; - if (indices != null) { - indices.CopyTo(newIndices, 0); - start = indices.Length / 6; - } - for (var i = start; i < itemCount; i++) { - /* TL TR + short[] newIndices = new short[6 * itemCount]; + int start = 0; + if (indices != null) + { + indices.CopyTo(newIndices, 0); + start = indices.Length / 6; + } + for (var i = start; i < itemCount; i++) + { + /* TL TR * 0----1 0,1,2,3 = index offsets for vertex indices * | | TL,TR,BL,BR are vertex references in RegionItem. * 2----3 * BL BR */ - newIndices[i * 6 + 0] = (short)(i * 4); - newIndices[i * 6 + 1] = (short)(i * 4 + 1); - newIndices[i * 6 + 2] = (short)(i * 4 + 2); - newIndices[i * 6 + 3] = (short)(i * 4 + 1); - newIndices[i * 6 + 4] = (short)(i * 4 + 3); - newIndices[i * 6 + 5] = (short)(i * 4 + 2); - } - indices = newIndices; + newIndices[i * 6 + 0] = (short)(i * 4); + newIndices[i * 6 + 1] = (short)(i * 4 + 1); + newIndices[i * 6 + 2] = (short)(i * 4 + 2); + newIndices[i * 6 + 3] = (short)(i * 4 + 1); + newIndices[i * 6 + 4] = (short)(i * 4 + 3); + newIndices[i * 6 + 5] = (short)(i * 4 + 2); + } + indices = newIndices; - vertexArray = new VertexPositionColorTexture[4 * itemCount]; - } + vertexArray = new VertexPositionColorTexture[4 * itemCount]; + } - public void Draw (GraphicsDevice device) { - if (items.Count == 0) return; + public void Draw(GraphicsDevice device) + { + if (items.Count == 0) return; - int itemIndex = 0; - int itemCount = items.Count; - while (itemCount > 0) { - int itemsToProcess = Math.Min(itemCount, maxBatchSize); - EnsureArrayCapacity(itemsToProcess); + int itemIndex = 0; + int itemCount = items.Count; + while (itemCount > 0) + { + int itemsToProcess = Math.Min(itemCount, maxBatchSize); + EnsureArrayCapacity(itemsToProcess); - var count = 0; - Texture2D texture = null; - for (int i = 0; i < itemsToProcess; i++, itemIndex++) { - RegionItem item = items[itemIndex]; - if (item.texture != texture) { - FlushVertexArray(device, count); - texture = item.texture; - count = 0; - device.Textures[0] = texture; - } + var count = 0; + Texture2D texture = null; + for (int i = 0; i < itemsToProcess; i++, itemIndex++) + { + RegionItem item = items[itemIndex]; + if (item.texture != texture) + { + FlushVertexArray(device, count); + texture = item.texture; + count = 0; + device.Textures[0] = texture; + } - vertexArray[count++] = item.vertexTL; - vertexArray[count++] = item.vertexTR; - vertexArray[count++] = item.vertexBL; - vertexArray[count++] = item.vertexBR; + vertexArray[count++] = item.vertexTL; + vertexArray[count++] = item.vertexTR; + vertexArray[count++] = item.vertexBL; + vertexArray[count++] = item.vertexBR; - item.texture = null; - freeItems.Enqueue(item); - } - FlushVertexArray(device, count); - itemCount -= itemsToProcess; - } - items.Clear(); - } + item.texture = null; + freeItems.Enqueue(item); + } + FlushVertexArray(device, count); + itemCount -= itemsToProcess; + } + items.Clear(); + } - /// Sends the triangle list to the graphics device. - /// Start index of vertices to draw. Not used except to compute the count of vertices to draw. - /// End index of vertices to draw. Not used except to compute the count of vertices to draw. - private void FlushVertexArray (GraphicsDevice device, int count) { - if (count == 0) return; - device.DrawUserIndexedPrimitives( - PrimitiveType.TriangleList, - vertexArray, 0, count, - indices, 0, (count / 4) * 2, - VertexPositionColorTexture.VertexDeclaration); - } - } + /// Sends the triangle list to the graphics device. + /// Start index of vertices to draw. Not used except to compute the count of vertices to draw. + /// End index of vertices to draw. Not used except to compute the count of vertices to draw. + private void FlushVertexArray(GraphicsDevice device, int count) + { + if (count == 0) return; + device.DrawUserIndexedPrimitives( + PrimitiveType.TriangleList, + vertexArray, 0, count, + indices, 0, (count / 4) * 2, + VertexPositionColorTexture.VertexDeclaration); + } + } - public class RegionItem { - public Texture2D texture; - public VertexPositionColorTexture vertexTL; - public VertexPositionColorTexture vertexTR; - public VertexPositionColorTexture vertexBL; - public VertexPositionColorTexture vertexBR; - } + public class RegionItem + { + public Texture2D texture; + public VertexPositionColorTexture vertexTL; + public VertexPositionColorTexture vertexTR; + public VertexPositionColorTexture vertexBL; + public VertexPositionColorTexture vertexBR; + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/XnaLoader/SkeletonMeshRenderer.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/XnaLoader/SkeletonMeshRenderer.cs index 4ba9bed..997e4cd 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/XnaLoader/SkeletonMeshRenderer.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/XnaLoader/SkeletonMeshRenderer.cs @@ -28,204 +28,228 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; using System.Collections.Generic; -using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; -namespace Spine2_1_25 { - /// Draws region and mesh attachments. - public class SkeletonMeshRenderer { - private const int TL = 0; - private const int TR = 1; - private const int BL = 2; - private const int BR = 3; - - GraphicsDevice device; - MeshBatcher batcher; - RasterizerState rasterizerState; - float[] vertices = new float[8]; - int[] quadTriangles = { 0, 1, 2, 1, 3, 2 }; - BlendState defaultBlendState; - - BasicEffect effect; - public BasicEffect Effect { get { return effect; } set { effect = value; } } - - private bool premultipliedAlpha; - public bool PremultipliedAlpha { get { return premultipliedAlpha; } set { premultipliedAlpha = value; } } - - public SkeletonMeshRenderer (GraphicsDevice device) { - this.device = device; - - batcher = new MeshBatcher(); - - effect = new BasicEffect(device); - effect.World = Matrix.Identity; - effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); - effect.TextureEnabled = true; - effect.VertexColorEnabled = true; - - rasterizerState = new RasterizerState(); - rasterizerState.CullMode = CullMode.None; - - Bone.yDown = true; - } - - public void Begin () { - defaultBlendState = premultipliedAlpha ? BlendState.AlphaBlend : BlendState.NonPremultiplied; - - device.RasterizerState = rasterizerState; - device.BlendState = defaultBlendState; - - effect.Projection = Matrix.CreateOrthographicOffCenter(0, device.Viewport.Width, device.Viewport.Height, 0, 1, 0); - } - - public void End () { - foreach (EffectPass pass in effect.CurrentTechnique.Passes) { - pass.Apply(); - batcher.Draw(device); - } - } - - public void Draw (Skeleton skeleton) { - float[] vertices = this.vertices; - List drawOrder = skeleton.DrawOrder; - float skeletonR = skeleton.R, skeletonG = skeleton.G, skeletonB = skeleton.B, skeletonA = skeleton.A; - for (int i = 0, n = drawOrder.Count; i < n; i++) { - Slot slot = drawOrder[i]; - Attachment attachment = slot.Attachment; - if (attachment is RegionAttachment) { - RegionAttachment regionAttachment = (RegionAttachment)attachment; - BlendState blend = slot.Data.AdditiveBlending ? BlendState.Additive : defaultBlendState; - if (device.BlendState != blend) { - End(); - device.BlendState = blend; - } - - MeshItem item = batcher.NextItem(4, 6); - item.triangles = quadTriangles; - VertexPositionColorTexture[] itemVertices = item.vertices; - - AtlasRegion region = (AtlasRegion)regionAttachment.RendererObject; - item.texture = (Texture2D)region.page.rendererObject; - - Color color; - float a = skeletonA * slot.A * regionAttachment.A; - if (premultipliedAlpha) { - color = new Color( - skeletonR * slot.R * regionAttachment.R * a, - skeletonG * slot.G * regionAttachment.G * a, - skeletonB * slot.B * regionAttachment.B * a, a); - } else { - color = new Color( - skeletonR * slot.R * regionAttachment.R, - skeletonG * slot.G * regionAttachment.G, - skeletonB * slot.B * regionAttachment.B, a); - } - itemVertices[TL].Color = color; - itemVertices[BL].Color = color; - itemVertices[BR].Color = color; - itemVertices[TR].Color = color; - - regionAttachment.ComputeWorldVertices(slot.Bone, vertices); - itemVertices[TL].Position.X = vertices[RegionAttachment.X1]; - itemVertices[TL].Position.Y = vertices[RegionAttachment.Y1]; - itemVertices[TL].Position.Z = 0; - itemVertices[BL].Position.X = vertices[RegionAttachment.X2]; - itemVertices[BL].Position.Y = vertices[RegionAttachment.Y2]; - itemVertices[BL].Position.Z = 0; - itemVertices[BR].Position.X = vertices[RegionAttachment.X3]; - itemVertices[BR].Position.Y = vertices[RegionAttachment.Y3]; - itemVertices[BR].Position.Z = 0; - itemVertices[TR].Position.X = vertices[RegionAttachment.X4]; - itemVertices[TR].Position.Y = vertices[RegionAttachment.Y4]; - itemVertices[TR].Position.Z = 0; - - float[] uvs = regionAttachment.UVs; - itemVertices[TL].TextureCoordinate.X = uvs[RegionAttachment.X1]; - itemVertices[TL].TextureCoordinate.Y = uvs[RegionAttachment.Y1]; - itemVertices[BL].TextureCoordinate.X = uvs[RegionAttachment.X2]; - itemVertices[BL].TextureCoordinate.Y = uvs[RegionAttachment.Y2]; - itemVertices[BR].TextureCoordinate.X = uvs[RegionAttachment.X3]; - itemVertices[BR].TextureCoordinate.Y = uvs[RegionAttachment.Y3]; - itemVertices[TR].TextureCoordinate.X = uvs[RegionAttachment.X4]; - itemVertices[TR].TextureCoordinate.Y = uvs[RegionAttachment.Y4]; - } else if (attachment is MeshAttachment) { - MeshAttachment mesh = (MeshAttachment)attachment; - int vertexCount = mesh.Vertices.Length; - if (vertices.Length < vertexCount) vertices = new float[vertexCount]; - mesh.ComputeWorldVertices(slot, vertices); - - int[] triangles = mesh.Triangles; - MeshItem item = batcher.NextItem(vertexCount, triangles.Length); - item.triangles = triangles; - - AtlasRegion region = (AtlasRegion)mesh.RendererObject; - item.texture = (Texture2D)region.page.rendererObject; - - Color color; - float a = skeletonA * slot.A * mesh.A; - if (premultipliedAlpha) { - color = new Color( - skeletonR * slot.R * mesh.R * a, - skeletonG * slot.G * mesh.G * a, - skeletonB * slot.B * mesh.B * a, a); - } else { - color = new Color( - skeletonR * slot.R * mesh.R, - skeletonG * slot.G * mesh.G, - skeletonB * slot.B * mesh.B, a); - } - - float[] uvs = mesh.UVs; - VertexPositionColorTexture[] itemVertices = item.vertices; - for (int ii = 0, v = 0; v < vertexCount; ii++, v += 2) { - itemVertices[ii].Color = color; - itemVertices[ii].Position.X = vertices[v]; - itemVertices[ii].Position.Y = vertices[v + 1]; - itemVertices[ii].Position.Z = 0; - itemVertices[ii].TextureCoordinate.X = uvs[v]; - itemVertices[ii].TextureCoordinate.Y = uvs[v + 1]; - } - } else if (attachment is SkinnedMeshAttachment) { - SkinnedMeshAttachment mesh = (SkinnedMeshAttachment)attachment; - int vertexCount = mesh.UVs.Length; - if (vertices.Length < vertexCount) vertices = new float[vertexCount]; - mesh.ComputeWorldVertices(slot, vertices); - - int[] triangles = mesh.Triangles; - MeshItem item = batcher.NextItem(vertexCount, triangles.Length); - item.triangles = triangles; - - AtlasRegion region = (AtlasRegion)mesh.RendererObject; - item.texture = (Texture2D)region.page.rendererObject; - - Color color; - float a = skeletonA * slot.A * mesh.A; - if (premultipliedAlpha) { - color = new Color( - skeletonR * slot.R * mesh.R * a, - skeletonG * slot.G * mesh.G * a, - skeletonB * slot.B * mesh.B * a, a); - } else { - color = new Color( - skeletonR * slot.R * mesh.R, - skeletonG * slot.G * mesh.G, - skeletonB * slot.B * mesh.B, a); - } - - float[] uvs = mesh.UVs; - VertexPositionColorTexture[] itemVertices = item.vertices; - for (int ii = 0, v = 0; v < vertexCount; ii++, v += 2) { - itemVertices[ii].Color = color; - itemVertices[ii].Position.X = vertices[v]; - itemVertices[ii].Position.Y = vertices[v + 1]; - itemVertices[ii].Position.Z = 0; - itemVertices[ii].TextureCoordinate.X = uvs[v]; - itemVertices[ii].TextureCoordinate.Y = uvs[v + 1]; - } - } - } - } - } +namespace Spine2_1_25 +{ + /// Draws region and mesh attachments. + public class SkeletonMeshRenderer + { + private const int TL = 0; + private const int TR = 1; + private const int BL = 2; + private const int BR = 3; + + GraphicsDevice device; + MeshBatcher batcher; + RasterizerState rasterizerState; + float[] vertices = new float[8]; + int[] quadTriangles = { 0, 1, 2, 1, 3, 2 }; + BlendState defaultBlendState; + + BasicEffect effect; + public BasicEffect Effect { get { return effect; } set { effect = value; } } + + private bool premultipliedAlpha; + public bool PremultipliedAlpha { get { return premultipliedAlpha; } set { premultipliedAlpha = value; } } + + public SkeletonMeshRenderer(GraphicsDevice device) + { + this.device = device; + + batcher = new MeshBatcher(); + + effect = new BasicEffect(device); + effect.World = Matrix.Identity; + effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); + effect.TextureEnabled = true; + effect.VertexColorEnabled = true; + + rasterizerState = new RasterizerState(); + rasterizerState.CullMode = CullMode.None; + + Bone.yDown = true; + } + + public void Begin() + { + defaultBlendState = premultipliedAlpha ? BlendState.AlphaBlend : BlendState.NonPremultiplied; + + device.RasterizerState = rasterizerState; + device.BlendState = defaultBlendState; + + effect.Projection = Matrix.CreateOrthographicOffCenter(0, device.Viewport.Width, device.Viewport.Height, 0, 1, 0); + } + + public void End() + { + foreach (EffectPass pass in effect.CurrentTechnique.Passes) + { + pass.Apply(); + batcher.Draw(device); + } + } + + public void Draw(Skeleton skeleton) + { + float[] vertices = this.vertices; + List drawOrder = skeleton.DrawOrder; + float skeletonR = skeleton.R, skeletonG = skeleton.G, skeletonB = skeleton.B, skeletonA = skeleton.A; + for (int i = 0, n = drawOrder.Count; i < n; i++) + { + Slot slot = drawOrder[i]; + Attachment attachment = slot.Attachment; + if (attachment is RegionAttachment) + { + RegionAttachment regionAttachment = (RegionAttachment)attachment; + BlendState blend = slot.Data.AdditiveBlending ? BlendState.Additive : defaultBlendState; + if (device.BlendState != blend) + { + End(); + device.BlendState = blend; + } + + MeshItem item = batcher.NextItem(4, 6); + item.triangles = quadTriangles; + VertexPositionColorTexture[] itemVertices = item.vertices; + + AtlasRegion region = (AtlasRegion)regionAttachment.RendererObject; + item.texture = (Texture2D)region.page.rendererObject; + + Color color; + float a = skeletonA * slot.A * regionAttachment.A; + if (premultipliedAlpha) + { + color = new Color( + skeletonR * slot.R * regionAttachment.R * a, + skeletonG * slot.G * regionAttachment.G * a, + skeletonB * slot.B * regionAttachment.B * a, a); + } + else + { + color = new Color( + skeletonR * slot.R * regionAttachment.R, + skeletonG * slot.G * regionAttachment.G, + skeletonB * slot.B * regionAttachment.B, a); + } + itemVertices[TL].Color = color; + itemVertices[BL].Color = color; + itemVertices[BR].Color = color; + itemVertices[TR].Color = color; + + regionAttachment.ComputeWorldVertices(slot.Bone, vertices); + itemVertices[TL].Position.X = vertices[RegionAttachment.X1]; + itemVertices[TL].Position.Y = vertices[RegionAttachment.Y1]; + itemVertices[TL].Position.Z = 0; + itemVertices[BL].Position.X = vertices[RegionAttachment.X2]; + itemVertices[BL].Position.Y = vertices[RegionAttachment.Y2]; + itemVertices[BL].Position.Z = 0; + itemVertices[BR].Position.X = vertices[RegionAttachment.X3]; + itemVertices[BR].Position.Y = vertices[RegionAttachment.Y3]; + itemVertices[BR].Position.Z = 0; + itemVertices[TR].Position.X = vertices[RegionAttachment.X4]; + itemVertices[TR].Position.Y = vertices[RegionAttachment.Y4]; + itemVertices[TR].Position.Z = 0; + + float[] uvs = regionAttachment.UVs; + itemVertices[TL].TextureCoordinate.X = uvs[RegionAttachment.X1]; + itemVertices[TL].TextureCoordinate.Y = uvs[RegionAttachment.Y1]; + itemVertices[BL].TextureCoordinate.X = uvs[RegionAttachment.X2]; + itemVertices[BL].TextureCoordinate.Y = uvs[RegionAttachment.Y2]; + itemVertices[BR].TextureCoordinate.X = uvs[RegionAttachment.X3]; + itemVertices[BR].TextureCoordinate.Y = uvs[RegionAttachment.Y3]; + itemVertices[TR].TextureCoordinate.X = uvs[RegionAttachment.X4]; + itemVertices[TR].TextureCoordinate.Y = uvs[RegionAttachment.Y4]; + } + else if (attachment is MeshAttachment) + { + MeshAttachment mesh = (MeshAttachment)attachment; + int vertexCount = mesh.Vertices.Length; + if (vertices.Length < vertexCount) vertices = new float[vertexCount]; + mesh.ComputeWorldVertices(slot, vertices); + + int[] triangles = mesh.Triangles; + MeshItem item = batcher.NextItem(vertexCount, triangles.Length); + item.triangles = triangles; + + AtlasRegion region = (AtlasRegion)mesh.RendererObject; + item.texture = (Texture2D)region.page.rendererObject; + + Color color; + float a = skeletonA * slot.A * mesh.A; + if (premultipliedAlpha) + { + color = new Color( + skeletonR * slot.R * mesh.R * a, + skeletonG * slot.G * mesh.G * a, + skeletonB * slot.B * mesh.B * a, a); + } + else + { + color = new Color( + skeletonR * slot.R * mesh.R, + skeletonG * slot.G * mesh.G, + skeletonB * slot.B * mesh.B, a); + } + + float[] uvs = mesh.UVs; + VertexPositionColorTexture[] itemVertices = item.vertices; + for (int ii = 0, v = 0; v < vertexCount; ii++, v += 2) + { + itemVertices[ii].Color = color; + itemVertices[ii].Position.X = vertices[v]; + itemVertices[ii].Position.Y = vertices[v + 1]; + itemVertices[ii].Position.Z = 0; + itemVertices[ii].TextureCoordinate.X = uvs[v]; + itemVertices[ii].TextureCoordinate.Y = uvs[v + 1]; + } + } + else if (attachment is SkinnedMeshAttachment) + { + SkinnedMeshAttachment mesh = (SkinnedMeshAttachment)attachment; + int vertexCount = mesh.UVs.Length; + if (vertices.Length < vertexCount) vertices = new float[vertexCount]; + mesh.ComputeWorldVertices(slot, vertices); + + int[] triangles = mesh.Triangles; + MeshItem item = batcher.NextItem(vertexCount, triangles.Length); + item.triangles = triangles; + + AtlasRegion region = (AtlasRegion)mesh.RendererObject; + item.texture = (Texture2D)region.page.rendererObject; + + Color color; + float a = skeletonA * slot.A * mesh.A; + if (premultipliedAlpha) + { + color = new Color( + skeletonR * slot.R * mesh.R * a, + skeletonG * slot.G * mesh.G * a, + skeletonB * slot.B * mesh.B * a, a); + } + else + { + color = new Color( + skeletonR * slot.R * mesh.R, + skeletonG * slot.G * mesh.G, + skeletonB * slot.B * mesh.B, a); + } + + float[] uvs = mesh.UVs; + VertexPositionColorTexture[] itemVertices = item.vertices; + for (int ii = 0, v = 0; v < vertexCount; ii++, v += 2) + { + itemVertices[ii].Color = color; + itemVertices[ii].Position.X = vertices[v]; + itemVertices[ii].Position.Y = vertices[v + 1]; + itemVertices[ii].Position.Z = 0; + itemVertices[ii].TextureCoordinate.X = uvs[v]; + itemVertices[ii].TextureCoordinate.Y = uvs[v + 1]; + } + } + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/XnaLoader/SkeletonRegionRenderer.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/XnaLoader/SkeletonRegionRenderer.cs index 2d35981..01e59c9 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/XnaLoader/SkeletonRegionRenderer.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/XnaLoader/SkeletonRegionRenderer.cs @@ -28,114 +28,123 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; using System.Collections.Generic; -using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; -namespace Spine2_1_25 { - /// Draws region attachments. - public class SkeletonRegionRenderer { - GraphicsDevice device; - RegionBatcher batcher; - RasterizerState rasterizerState; - float[] vertices = new float[8]; - BlendState defaultBlendState; - - BasicEffect effect; - public BasicEffect Effect { get { return effect; } set { effect = value; } } - - private bool premultipliedAlpha; - public bool PremultipliedAlpha { get { return premultipliedAlpha; } set { premultipliedAlpha = value; } } - - public SkeletonRegionRenderer (GraphicsDevice device) { - this.device = device; - - batcher = new RegionBatcher(); - - effect = new BasicEffect(device); - effect.World = Matrix.Identity; - effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); - effect.TextureEnabled = true; - effect.VertexColorEnabled = true; - - rasterizerState = new RasterizerState(); - rasterizerState.CullMode = CullMode.None; - - Bone.yDown = true; - } - - public void Begin () { - defaultBlendState = premultipliedAlpha ? BlendState.AlphaBlend : BlendState.NonPremultiplied; - - device.RasterizerState = rasterizerState; - device.BlendState = defaultBlendState; - - effect.Projection = Matrix.CreateOrthographicOffCenter(0, device.Viewport.Width, device.Viewport.Height, 0, 1, 0); - } - - public void End () { - foreach (EffectPass pass in effect.CurrentTechnique.Passes) { - pass.Apply(); - batcher.Draw(device); - } - } - - public void Draw (Skeleton skeleton) { - List drawOrder = skeleton.DrawOrder; - float skeletonR = skeleton.R, skeletonG = skeleton.G, skeletonB = skeleton.B, skeletonA = skeleton.A; - for (int i = 0, n = drawOrder.Count; i < n; i++) { - Slot slot = drawOrder[i]; - RegionAttachment regionAttachment = slot.Attachment as RegionAttachment; - if (regionAttachment != null) { - BlendState blend = slot.Data.AdditiveBlending ? BlendState.Additive : defaultBlendState; - if (device.BlendState != blend) { - End(); - device.BlendState = blend; - } - - RegionItem item = batcher.NextItem(); - - AtlasRegion region = (AtlasRegion)regionAttachment.RendererObject; - item.texture = (Texture2D)region.page.rendererObject; - - Color color; - float a = skeletonA * slot.A; - if (premultipliedAlpha) - color = new Color(skeletonR * slot.R * a, skeletonG * slot.G * a, skeletonB * slot.B * a, a); - else - color = new Color(skeletonR * slot.R, skeletonG * slot.G, skeletonB * slot.B, a); - item.vertexTL.Color = color; - item.vertexBL.Color = color; - item.vertexBR.Color = color; - item.vertexTR.Color = color; - - float[] vertices = this.vertices; - regionAttachment.ComputeWorldVertices(slot.Bone, vertices); - item.vertexTL.Position.X = vertices[RegionAttachment.X1]; - item.vertexTL.Position.Y = vertices[RegionAttachment.Y1]; - item.vertexTL.Position.Z = 0; - item.vertexBL.Position.X = vertices[RegionAttachment.X2]; - item.vertexBL.Position.Y = vertices[RegionAttachment.Y2]; - item.vertexBL.Position.Z = 0; - item.vertexBR.Position.X = vertices[RegionAttachment.X3]; - item.vertexBR.Position.Y = vertices[RegionAttachment.Y3]; - item.vertexBR.Position.Z = 0; - item.vertexTR.Position.X = vertices[RegionAttachment.X4]; - item.vertexTR.Position.Y = vertices[RegionAttachment.Y4]; - item.vertexTR.Position.Z = 0; - - float[] uvs = regionAttachment.UVs; - item.vertexTL.TextureCoordinate.X = uvs[RegionAttachment.X1]; - item.vertexTL.TextureCoordinate.Y = uvs[RegionAttachment.Y1]; - item.vertexBL.TextureCoordinate.X = uvs[RegionAttachment.X2]; - item.vertexBL.TextureCoordinate.Y = uvs[RegionAttachment.Y2]; - item.vertexBR.TextureCoordinate.X = uvs[RegionAttachment.X3]; - item.vertexBR.TextureCoordinate.Y = uvs[RegionAttachment.Y3]; - item.vertexTR.TextureCoordinate.X = uvs[RegionAttachment.X4]; - item.vertexTR.TextureCoordinate.Y = uvs[RegionAttachment.Y4]; - } - } - } - } +namespace Spine2_1_25 +{ + /// Draws region attachments. + public class SkeletonRegionRenderer + { + GraphicsDevice device; + RegionBatcher batcher; + RasterizerState rasterizerState; + float[] vertices = new float[8]; + BlendState defaultBlendState; + + BasicEffect effect; + public BasicEffect Effect { get { return effect; } set { effect = value; } } + + private bool premultipliedAlpha; + public bool PremultipliedAlpha { get { return premultipliedAlpha; } set { premultipliedAlpha = value; } } + + public SkeletonRegionRenderer(GraphicsDevice device) + { + this.device = device; + + batcher = new RegionBatcher(); + + effect = new BasicEffect(device); + effect.World = Matrix.Identity; + effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); + effect.TextureEnabled = true; + effect.VertexColorEnabled = true; + + rasterizerState = new RasterizerState(); + rasterizerState.CullMode = CullMode.None; + + Bone.yDown = true; + } + + public void Begin() + { + defaultBlendState = premultipliedAlpha ? BlendState.AlphaBlend : BlendState.NonPremultiplied; + + device.RasterizerState = rasterizerState; + device.BlendState = defaultBlendState; + + effect.Projection = Matrix.CreateOrthographicOffCenter(0, device.Viewport.Width, device.Viewport.Height, 0, 1, 0); + } + + public void End() + { + foreach (EffectPass pass in effect.CurrentTechnique.Passes) + { + pass.Apply(); + batcher.Draw(device); + } + } + + public void Draw(Skeleton skeleton) + { + List drawOrder = skeleton.DrawOrder; + float skeletonR = skeleton.R, skeletonG = skeleton.G, skeletonB = skeleton.B, skeletonA = skeleton.A; + for (int i = 0, n = drawOrder.Count; i < n; i++) + { + Slot slot = drawOrder[i]; + RegionAttachment regionAttachment = slot.Attachment as RegionAttachment; + if (regionAttachment != null) + { + BlendState blend = slot.Data.AdditiveBlending ? BlendState.Additive : defaultBlendState; + if (device.BlendState != blend) + { + End(); + device.BlendState = blend; + } + + RegionItem item = batcher.NextItem(); + + AtlasRegion region = (AtlasRegion)regionAttachment.RendererObject; + item.texture = (Texture2D)region.page.rendererObject; + + Color color; + float a = skeletonA * slot.A; + if (premultipliedAlpha) + color = new Color(skeletonR * slot.R * a, skeletonG * slot.G * a, skeletonB * slot.B * a, a); + else + color = new Color(skeletonR * slot.R, skeletonG * slot.G, skeletonB * slot.B, a); + item.vertexTL.Color = color; + item.vertexBL.Color = color; + item.vertexBR.Color = color; + item.vertexTR.Color = color; + + float[] vertices = this.vertices; + regionAttachment.ComputeWorldVertices(slot.Bone, vertices); + item.vertexTL.Position.X = vertices[RegionAttachment.X1]; + item.vertexTL.Position.Y = vertices[RegionAttachment.Y1]; + item.vertexTL.Position.Z = 0; + item.vertexBL.Position.X = vertices[RegionAttachment.X2]; + item.vertexBL.Position.Y = vertices[RegionAttachment.Y2]; + item.vertexBL.Position.Z = 0; + item.vertexBR.Position.X = vertices[RegionAttachment.X3]; + item.vertexBR.Position.Y = vertices[RegionAttachment.Y3]; + item.vertexBR.Position.Z = 0; + item.vertexTR.Position.X = vertices[RegionAttachment.X4]; + item.vertexTR.Position.Y = vertices[RegionAttachment.Y4]; + item.vertexTR.Position.Z = 0; + + float[] uvs = regionAttachment.UVs; + item.vertexTL.TextureCoordinate.X = uvs[RegionAttachment.X1]; + item.vertexTL.TextureCoordinate.Y = uvs[RegionAttachment.Y1]; + item.vertexBL.TextureCoordinate.X = uvs[RegionAttachment.X2]; + item.vertexBL.TextureCoordinate.Y = uvs[RegionAttachment.Y2]; + item.vertexBR.TextureCoordinate.X = uvs[RegionAttachment.X3]; + item.vertexBR.TextureCoordinate.Y = uvs[RegionAttachment.Y3]; + item.vertexTR.TextureCoordinate.X = uvs[RegionAttachment.X4]; + item.vertexTR.TextureCoordinate.Y = uvs[RegionAttachment.Y4]; + } + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/XnaLoader/XnaTextureLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/XnaLoader/XnaTextureLoader.cs index e85c411..305bf17 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/XnaLoader/XnaTextureLoader.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-2.1.25/XnaLoader/XnaTextureLoader.cs @@ -28,22 +28,24 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; -using System.IO; -using Microsoft.Xna.Framework; +using System; using Microsoft.Xna.Framework.Graphics; -namespace Spine2_1_25 { - public class XnaTextureLoader : TextureLoader { - GraphicsDevice device; +namespace Spine2_1_25 +{ + public class XnaTextureLoader : TextureLoader + { + GraphicsDevice device; - public XnaTextureLoader (GraphicsDevice device) { - this.device = device; - } + public XnaTextureLoader(GraphicsDevice device) + { + this.device = device; + } - public void Load (AtlasPage page, String path) { - Texture2D texture = Util.LoadTexture(device, path); - page.rendererObject = texture; + public void Load(AtlasPage page, String path) + { + Texture2D texture = Util.LoadTexture(device, path); + page.rendererObject = texture; if (page.width == 0 || page.height == 0) { page.width = texture.Width; @@ -51,8 +53,9 @@ public void Load (AtlasPage page, String path) { } } - public void Unload (Object texture) { - ((Texture2D)texture).Dispose(); - } - } + public void Unload(Object texture) + { + ((Texture2D)texture).Dispose(); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Animation.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Animation.cs index 2ea150b..2c5a4a1 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Animation.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Animation.cs @@ -30,651 +30,740 @@ *****************************************************************************/ using System; -using System.Collections.Generic; - -namespace Spine3_1_07 { - public class Animation { - internal ExposedList timelines; - internal float duration; - internal String name; - - public String Name { get { return name; } } - public ExposedList Timelines { get { return timelines; } set { timelines = value; } } - public float Duration { get { return duration; } set { duration = value; } } - - public Animation (String name, ExposedList timelines, float duration) { - if (name == null) throw new ArgumentNullException("name cannot be null."); - if (timelines == null) throw new ArgumentNullException("timelines cannot be null."); - this.name = name; - this.timelines = timelines; - this.duration = duration; - } - - /// Poses the skeleton at the specified time for this animation. - /// The last time the animation was applied. - /// Any triggered events are added. - public void Apply (Skeleton skeleton, float lastTime, float time, bool loop, ExposedList events) { - if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); - - if (loop && duration != 0) { - time %= duration; - if (lastTime > 0) lastTime %= duration; - } - - ExposedList timelines = this.timelines; - for (int i = 0, n = timelines.Count; i < n; i++) - timelines.Items[i].Apply(skeleton, lastTime, time, events, 1); - } - - /// Poses the skeleton at the specified time for this animation mixed with the current pose. - /// The last time the animation was applied. - /// Any triggered events are added. - /// The amount of this animation that affects the current pose. - public void Mix (Skeleton skeleton, float lastTime, float time, bool loop, ExposedList events, float alpha) { - if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); - - if (loop && duration != 0) { - time %= duration; - if (lastTime > 0) lastTime %= duration; - } - - ExposedList timelines = this.timelines; - for (int i = 0, n = timelines.Count; i < n; i++) - timelines.Items[i].Apply(skeleton, lastTime, time, events, alpha); - } - - /// After the first and before the last entry. - internal static int binarySearch (float[] values, float target, int step) { - int low = 0; - int high = values.Length / step - 2; - if (high == 0) return step; - int current = (int)((uint)high >> 1); - while (true) { - if (values[(current + 1) * step] <= target) - low = current + 1; - else - high = current; - if (low == high) return (low + 1) * step; - current = (int)((uint)(low + high) >> 1); - } - } - - /// After the first and before the last entry. - internal static int binarySearch (float[] values, float target) { - int low = 0; - int high = values.Length - 2; - if (high == 0) return 1; - int current = (int)((uint)high >> 1); - while (true) { - if (values[(current + 1)] <= target) - low = current + 1; - else - high = current; - if (low == high) return (low + 1); - current = (int)((uint)(low + high) >> 1); - } - } - - internal static int linearSearch (float[] values, float target, int step) { - for (int i = 0, last = values.Length - step; i <= last; i += step) - if (values[i] > target) return i; - return -1; - } - } - - public interface Timeline { - /// Sets the value(s) for the specified time. - /// May be null to not collect fired events. - void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha); - } - - /// Base class for frames that use an interpolation bezier curve. - abstract public class CurveTimeline : Timeline { - protected const float LINEAR = 0, STEPPED = 1, BEZIER = 2; - protected const int BEZIER_SEGMENTS = 10, BEZIER_SIZE = BEZIER_SEGMENTS * 2 - 1; - - private float[] curves; // type, x, y, ... - public int FrameCount { get { return curves.Length / BEZIER_SIZE + 1; } } - - public CurveTimeline (int frameCount) { - curves = new float[(frameCount - 1) * BEZIER_SIZE]; - } - - abstract public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha); - - public void SetLinear (int frameIndex) { - curves[frameIndex * BEZIER_SIZE] = LINEAR; - } - - public void SetStepped (int frameIndex) { - curves[frameIndex * BEZIER_SIZE] = STEPPED; - } - - /// Sets the control handle positions for an interpolation bezier curve used to transition from this keyframe to the next. - /// cx1 and cx2 are from 0 to 1, representing the percent of time between the two keyframes. cy1 and cy2 are the percent of - /// the difference between the keyframe's values. - public void SetCurve (int frameIndex, float cx1, float cy1, float cx2, float cy2) { - float subdiv1 = 1f / BEZIER_SEGMENTS, subdiv2 = subdiv1 * subdiv1, subdiv3 = subdiv2 * subdiv1; - float pre1 = 3 * subdiv1, pre2 = 3 * subdiv2, pre4 = 6 * subdiv2, pre5 = 6 * subdiv3; - float tmp1x = -cx1 * 2 + cx2, tmp1y = -cy1 * 2 + cy2, tmp2x = (cx1 - cx2) * 3 + 1, tmp2y = (cy1 - cy2) * 3 + 1; - float dfx = cx1 * pre1 + tmp1x * pre2 + tmp2x * subdiv3, dfy = cy1 * pre1 + tmp1y * pre2 + tmp2y * subdiv3; - float ddfx = tmp1x * pre4 + tmp2x * pre5, ddfy = tmp1y * pre4 + tmp2y * pre5; - float dddfx = tmp2x * pre5, dddfy = tmp2y * pre5; - - int i = frameIndex * BEZIER_SIZE; - float[] curves = this.curves; - curves[i++] = BEZIER; - - float x = dfx, y = dfy; - for (int n = i + BEZIER_SIZE - 1; i < n; i += 2) { - curves[i] = x; - curves[i + 1] = y; - dfx += ddfx; - dfy += ddfy; - ddfx += dddfx; - ddfy += dddfy; - x += dfx; - y += dfy; - } - } - - public float GetCurvePercent (int frameIndex, float percent) { - float[] curves = this.curves; - int i = frameIndex * BEZIER_SIZE; - float type = curves[i]; - if (type == LINEAR) return percent; - if (type == STEPPED) return 0; - i++; - float x = 0; - for (int start = i, n = i + BEZIER_SIZE - 1; i < n; i += 2) { - x = curves[i]; - if (x >= percent) { - float prevX, prevY; - if (i == start) { - prevX = 0; - prevY = 0; - } else { - prevX = curves[i - 2]; - prevY = curves[i - 1]; - } - return prevY + (curves[i + 1] - prevY) * (percent - prevX) / (x - prevX); - } - } - float y = curves[i - 1]; - return y + (1 - y) * (percent - x) / (1 - x); // Last point is 1,1. - } - public float GetCurveType (int frameIndex) { - return curves[frameIndex * BEZIER_SIZE]; - } - } - - public class RotateTimeline : CurveTimeline { - protected const int PREV_FRAME_TIME = -2; - protected const int FRAME_VALUE = 1; - - internal int boneIndex; - internal float[] frames; - - public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, value, ... - - public RotateTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount << 1]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, float angle) { - frameIndex *= 2; - frames[frameIndex] = time; - frames[frameIndex + 1] = angle; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) { - float[] frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - Bone bone = skeleton.bones.Items[boneIndex]; - - float amount; - - if (time >= frames[frames.Length - 2]) { // Time is after last frame. - amount = bone.data.rotation + frames[frames.Length - 1] - bone.rotation; - while (amount > 180) - amount -= 360; - while (amount < -180) - amount += 360; - bone.rotation += amount * alpha; - return; - } - - // Interpolate between the previous frame and the current frame. - int frameIndex = Animation.binarySearch(frames, time, 2); - float prevFrameValue = frames[frameIndex - 1]; - float frameTime = frames[frameIndex]; - float percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime); - percent = GetCurvePercent((frameIndex >> 1) - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); - - amount = frames[frameIndex + FRAME_VALUE] - prevFrameValue; - while (amount > 180) - amount -= 360; - while (amount < -180) - amount += 360; - amount = bone.data.rotation + (prevFrameValue + amount * percent) - bone.rotation; - while (amount > 180) - amount -= 360; - while (amount < -180) - amount += 360; - bone.rotation += amount * alpha; - } - } - - public class TranslateTimeline : CurveTimeline { - protected const int PREV_FRAME_TIME = -3; - protected const int FRAME_X = 1; - protected const int FRAME_Y = 2; - - internal int boneIndex; - internal float[] frames; - - public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, value, value, ... - - public TranslateTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * 3]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, float x, float y) { - frameIndex *= 3; - frames[frameIndex] = time; - frames[frameIndex + 1] = x; - frames[frameIndex + 2] = y; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) { - float[] frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - Bone bone = skeleton.bones.Items[boneIndex]; - - if (time >= frames[frames.Length - 3]) { // Time is after last frame. - bone.x += (bone.data.x + frames[frames.Length - 2] - bone.x) * alpha; - bone.y += (bone.data.y + frames[frames.Length - 1] - bone.y) * alpha; - return; - } - - // Interpolate between the previous frame and the current frame. - int frameIndex = Animation.binarySearch(frames, time, 3); - float prevFrameX = frames[frameIndex - 2]; - float prevFrameY = frames[frameIndex - 1]; - float frameTime = frames[frameIndex]; - float percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime); - percent = GetCurvePercent(frameIndex / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); - - bone.x += (bone.data.x + prevFrameX + (frames[frameIndex + FRAME_X] - prevFrameX) * percent - bone.x) * alpha; - bone.y += (bone.data.y + prevFrameY + (frames[frameIndex + FRAME_Y] - prevFrameY) * percent - bone.y) * alpha; - } - } - - public class ScaleTimeline : TranslateTimeline { - public ScaleTimeline (int frameCount) - : base(frameCount) { - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) { - float[] frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - Bone bone = skeleton.bones.Items[boneIndex]; - if (time >= frames[frames.Length - 3]) { // Time is after last frame. - bone.scaleX += (bone.data.scaleX * frames[frames.Length - 2] - bone.scaleX) * alpha; - bone.scaleY += (bone.data.scaleY * frames[frames.Length - 1] - bone.scaleY) * alpha; - return; - } - - // Interpolate between the previous frame and the current frame. - int frameIndex = Animation.binarySearch(frames, time, 3); - float prevFrameX = frames[frameIndex - 2]; - float prevFrameY = frames[frameIndex - 1]; - float frameTime = frames[frameIndex]; - float percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime); - percent = GetCurvePercent(frameIndex / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); - - bone.scaleX += (bone.data.scaleX * (prevFrameX + (frames[frameIndex + FRAME_X] - prevFrameX) * percent) - bone.scaleX) * alpha; - bone.scaleY += (bone.data.scaleY * (prevFrameY + (frames[frameIndex + FRAME_Y] - prevFrameY) * percent) - bone.scaleY) * alpha; - } - } - - public class ColorTimeline : CurveTimeline { - protected const int PREV_FRAME_TIME = -5; - protected const int FRAME_R = 1; - protected const int FRAME_G = 2; - protected const int FRAME_B = 3; - protected const int FRAME_A = 4; - - internal int slotIndex; - internal float[] frames; - - public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, r, g, b, a, ... - - public ColorTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * 5]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, float r, float g, float b, float a) { - frameIndex *= 5; - frames[frameIndex] = time; - frames[frameIndex + 1] = r; - frames[frameIndex + 2] = g; - frames[frameIndex + 3] = b; - frames[frameIndex + 4] = a; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) { - float[] frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - float r, g, b, a; - if (time >= frames[frames.Length - 5]) { - // Time is after last frame. - int i = frames.Length - 1; - r = frames[i - 3]; - g = frames[i - 2]; - b = frames[i - 1]; - a = frames[i]; - } else { - // Interpolate between the previous frame and the current frame. - int frameIndex = Animation.binarySearch(frames, time, 5); - float prevFrameR = frames[frameIndex - 4]; - float prevFrameG = frames[frameIndex - 3]; - float prevFrameB = frames[frameIndex - 2]; - float prevFrameA = frames[frameIndex - 1]; - float frameTime = frames[frameIndex]; - float percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime); - percent = GetCurvePercent(frameIndex / 5 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); - - r = prevFrameR + (frames[frameIndex + FRAME_R] - prevFrameR) * percent; - g = prevFrameG + (frames[frameIndex + FRAME_G] - prevFrameG) * percent; - b = prevFrameB + (frames[frameIndex + FRAME_B] - prevFrameB) * percent; - a = prevFrameA + (frames[frameIndex + FRAME_A] - prevFrameA) * percent; - } - Slot slot = skeleton.slots.Items[slotIndex]; - if (alpha < 1) { - slot.r += (r - slot.r) * alpha; - slot.g += (g - slot.g) * alpha; - slot.b += (b - slot.b) * alpha; - slot.a += (a - slot.a) * alpha; - } else { - slot.r = r; - slot.g = g; - slot.b = b; - slot.a = a; - } - } - } - - public class AttachmentTimeline : Timeline { - internal int slotIndex; - internal float[] frames; - private String[] attachmentNames; - - public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, ... - public String[] AttachmentNames { get { return attachmentNames; } set { attachmentNames = value; } } - public int FrameCount { get { return frames.Length; } } - - public AttachmentTimeline (int frameCount) { - frames = new float[frameCount]; - attachmentNames = new String[frameCount]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, String attachmentName) { - frames[frameIndex] = time; - attachmentNames[frameIndex] = attachmentName; - } - - public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) { - float[] frames = this.frames; - if (time < frames[0]) { - if (lastTime > time) Apply(skeleton, lastTime, int.MaxValue, null, 0); - return; - } else if (lastTime > time) // - lastTime = -1; - - int frameIndex = (time >= frames[frames.Length - 1] ? frames.Length : Animation.binarySearch(frames, time)) - 1; - if (frames[frameIndex] < lastTime) return; - - String attachmentName = attachmentNames[frameIndex]; - skeleton.slots.Items[slotIndex].Attachment = - attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); - } - } - - public class EventTimeline : Timeline { - internal float[] frames; - private Event[] events; - - public float[] Frames { get { return frames; } set { frames = value; } } // time, ... - public Event[] Events { get { return events; } set { events = value; } } - public int FrameCount { get { return frames.Length; } } - - public EventTimeline (int frameCount) { - frames = new float[frameCount]; - events = new Event[frameCount]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, Event e) { - frames[frameIndex] = e.Time; - events[frameIndex] = e; - } - - /// Fires events for frames > lastTime and <= time. - public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) { - if (firedEvents == null) return; - float[] frames = this.frames; - int frameCount = frames.Length; - - if (lastTime > time) { // Fire events after last time for looped animations. - Apply(skeleton, lastTime, int.MaxValue, firedEvents, alpha); - lastTime = -1f; - } else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame. - return; - if (time < frames[0]) return; // Time is before first frame. - - int frameIndex; - if (lastTime < frames[0]) - frameIndex = 0; - else { - frameIndex = Animation.binarySearch(frames, lastTime); - float frame = frames[frameIndex]; - while (frameIndex > 0) { // Fire multiple events with the same frame. - if (frames[frameIndex - 1] != frame) break; - frameIndex--; - } - } - for (; frameIndex < frameCount && time >= frames[frameIndex]; frameIndex++) - firedEvents.Add(events[frameIndex]); - } - } - - public class DrawOrderTimeline : Timeline { - internal float[] frames; - private int[][] drawOrders; - - public float[] Frames { get { return frames; } set { frames = value; } } // time, ... - public int[][] DrawOrders { get { return drawOrders; } set { drawOrders = value; } } - public int FrameCount { get { return frames.Length; } } - - public DrawOrderTimeline (int frameCount) { - frames = new float[frameCount]; - drawOrders = new int[frameCount][]; - } - - /// Sets the time and value of the specified keyframe. - /// May be null to use bind pose draw order. - public void SetFrame (int frameIndex, float time, int[] drawOrder) { - frames[frameIndex] = time; - drawOrders[frameIndex] = drawOrder; - } - - public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) { - float[] frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - int frameIndex; - if (time >= frames[frames.Length - 1]) // Time is after last frame. - frameIndex = frames.Length - 1; - else - frameIndex = Animation.binarySearch(frames, time) - 1; - - ExposedList drawOrder = skeleton.drawOrder; - ExposedList slots = skeleton.slots; - int[] drawOrderToSetupIndex = drawOrders[frameIndex]; - if (drawOrderToSetupIndex == null) { - drawOrder.Clear(); - for (int i = 0, n = slots.Count; i < n; i++) - drawOrder.Add(slots.Items[i]); - } else { - for (int i = 0, n = drawOrderToSetupIndex.Length; i < n; i++) - drawOrder.Items[i] = slots.Items[drawOrderToSetupIndex[i]]; - } - } - } - - public class FfdTimeline : CurveTimeline { - internal int slotIndex; - internal float[] frames; - private float[][] frameVertices; - internal Attachment attachment; - - public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, ... - public float[][] Vertices { get { return frameVertices; } set { frameVertices = value; } } - public Attachment Attachment { get { return attachment; } set { attachment = value; } } - - public FfdTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount]; - frameVertices = new float[frameCount][]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, float[] vertices) { - frames[frameIndex] = time; - frameVertices[frameIndex] = vertices; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) { - Slot slot = skeleton.slots.Items[slotIndex]; - IFfdAttachment ffdAttachment = slot.attachment as IFfdAttachment; - if (ffdAttachment == null || !ffdAttachment.ApplyFFD(attachment)) return; - - float[] frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - float[][] frameVertices = this.frameVertices; - int vertexCount = frameVertices[0].Length; - - float[] vertices = slot.attachmentVertices; - if (slot.attachmentVerticesCount != vertexCount) alpha = 1; // Don't mix from uninitialized slot vertices. - - // Ensure capacity - if (vertices.Length < vertexCount) { - vertices = new float[vertexCount]; - slot.attachmentVertices = vertices; - } - slot.attachmentVerticesCount = vertexCount; - - if (time >= frames[frames.Length - 1]) { // Time is after last frame. - float[] lastVertices = frameVertices[frames.Length - 1]; - if (alpha < 1) { - for (int i = 0; i < vertexCount; i++) { - float vertex = vertices[i]; - vertices[i] = vertex + (lastVertices[i] - vertex) * alpha; - } - } else - Array.Copy(lastVertices, 0, vertices, 0, vertexCount); - return; - } - - // Interpolate between the previous frame and the current frame. - int frameIndex = Animation.binarySearch(frames, time); - float frameTime = frames[frameIndex]; - float percent = 1 - (time - frameTime) / (frames[frameIndex - 1] - frameTime); - percent = GetCurvePercent(frameIndex - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); - - float[] prevVertices = frameVertices[frameIndex - 1]; - float[] nextVertices = frameVertices[frameIndex]; - - if (alpha < 1) { - for (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - float vertex = vertices[i]; - vertices[i] = vertex + (prev + (nextVertices[i] - prev) * percent - vertex) * alpha; - } - } else { - for (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - vertices[i] = prev + (nextVertices[i] - prev) * percent; - } - } - } - } - - public class IkConstraintTimeline : CurveTimeline { - private const int PREV_FRAME_TIME = -3; - private const int PREV_FRAME_MIX = -2; - private const int PREV_FRAME_BEND_DIRECTION = -1; - private const int FRAME_MIX = 1; - - internal int ikConstraintIndex; - internal float[] frames; - - public int IkConstraintIndex { get { return ikConstraintIndex; } set { ikConstraintIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, mix, bendDirection, ... - - public IkConstraintTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * 3]; - } - - /** Sets the time, mix and bend direction of the specified keyframe. */ - public void SetFrame (int frameIndex, float time, float mix, int bendDirection) { - frameIndex *= 3; - frames[frameIndex] = time; - frames[frameIndex + 1] = mix; - frames[frameIndex + 2] = bendDirection; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) { - float[] frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - IkConstraint ikConstraint = skeleton.ikConstraints.Items[ikConstraintIndex]; - - if (time >= frames[frames.Length - 3]) { // Time is after last frame. - ikConstraint.mix += (frames[frames.Length - 2] - ikConstraint.mix) * alpha; - ikConstraint.bendDirection = (int)frames[frames.Length - 1]; - return; - } - - // Interpolate between the previous frame and the current frame. - int frameIndex = Animation.binarySearch(frames, time, 3); - float prevFrameMix = frames[frameIndex + PREV_FRAME_MIX]; - float frameTime = frames[frameIndex]; - float percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime); - percent = GetCurvePercent(frameIndex / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); - - float mix = prevFrameMix + (frames[frameIndex + FRAME_MIX] - prevFrameMix) * percent; - ikConstraint.mix += (mix - ikConstraint.mix) * alpha; - ikConstraint.bendDirection = (int)frames[frameIndex + PREV_FRAME_BEND_DIRECTION]; - } - } + +namespace Spine3_1_07 +{ + public class Animation + { + internal ExposedList timelines; + internal float duration; + internal String name; + + public String Name { get { return name; } } + public ExposedList Timelines { get { return timelines; } set { timelines = value; } } + public float Duration { get { return duration; } set { duration = value; } } + + public Animation(String name, ExposedList timelines, float duration) + { + if (name == null) throw new ArgumentNullException("name cannot be null."); + if (timelines == null) throw new ArgumentNullException("timelines cannot be null."); + this.name = name; + this.timelines = timelines; + this.duration = duration; + } + + /// Poses the skeleton at the specified time for this animation. + /// The last time the animation was applied. + /// Any triggered events are added. + public void Apply(Skeleton skeleton, float lastTime, float time, bool loop, ExposedList events) + { + if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); + + if (loop && duration != 0) + { + time %= duration; + if (lastTime > 0) lastTime %= duration; + } + + ExposedList timelines = this.timelines; + for (int i = 0, n = timelines.Count; i < n; i++) + timelines.Items[i].Apply(skeleton, lastTime, time, events, 1); + } + + /// Poses the skeleton at the specified time for this animation mixed with the current pose. + /// The last time the animation was applied. + /// Any triggered events are added. + /// The amount of this animation that affects the current pose. + public void Mix(Skeleton skeleton, float lastTime, float time, bool loop, ExposedList events, float alpha) + { + if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); + + if (loop && duration != 0) + { + time %= duration; + if (lastTime > 0) lastTime %= duration; + } + + ExposedList timelines = this.timelines; + for (int i = 0, n = timelines.Count; i < n; i++) + timelines.Items[i].Apply(skeleton, lastTime, time, events, alpha); + } + + /// After the first and before the last entry. + internal static int binarySearch(float[] values, float target, int step) + { + int low = 0; + int high = values.Length / step - 2; + if (high == 0) return step; + int current = (int)((uint)high >> 1); + while (true) + { + if (values[(current + 1) * step] <= target) + low = current + 1; + else + high = current; + if (low == high) return (low + 1) * step; + current = (int)((uint)(low + high) >> 1); + } + } + + /// After the first and before the last entry. + internal static int binarySearch(float[] values, float target) + { + int low = 0; + int high = values.Length - 2; + if (high == 0) return 1; + int current = (int)((uint)high >> 1); + while (true) + { + if (values[(current + 1)] <= target) + low = current + 1; + else + high = current; + if (low == high) return (low + 1); + current = (int)((uint)(low + high) >> 1); + } + } + + internal static int linearSearch(float[] values, float target, int step) + { + for (int i = 0, last = values.Length - step; i <= last; i += step) + if (values[i] > target) return i; + return -1; + } + } + + public interface Timeline + { + /// Sets the value(s) for the specified time. + /// May be null to not collect fired events. + void Apply(Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha); + } + + /// Base class for frames that use an interpolation bezier curve. + abstract public class CurveTimeline : Timeline + { + protected const float LINEAR = 0, STEPPED = 1, BEZIER = 2; + protected const int BEZIER_SEGMENTS = 10, BEZIER_SIZE = BEZIER_SEGMENTS * 2 - 1; + + private float[] curves; // type, x, y, ... + public int FrameCount { get { return curves.Length / BEZIER_SIZE + 1; } } + + public CurveTimeline(int frameCount) + { + curves = new float[(frameCount - 1) * BEZIER_SIZE]; + } + + abstract public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha); + + public void SetLinear(int frameIndex) + { + curves[frameIndex * BEZIER_SIZE] = LINEAR; + } + + public void SetStepped(int frameIndex) + { + curves[frameIndex * BEZIER_SIZE] = STEPPED; + } + + /// Sets the control handle positions for an interpolation bezier curve used to transition from this keyframe to the next. + /// cx1 and cx2 are from 0 to 1, representing the percent of time between the two keyframes. cy1 and cy2 are the percent of + /// the difference between the keyframe's values. + public void SetCurve(int frameIndex, float cx1, float cy1, float cx2, float cy2) + { + float subdiv1 = 1f / BEZIER_SEGMENTS, subdiv2 = subdiv1 * subdiv1, subdiv3 = subdiv2 * subdiv1; + float pre1 = 3 * subdiv1, pre2 = 3 * subdiv2, pre4 = 6 * subdiv2, pre5 = 6 * subdiv3; + float tmp1x = -cx1 * 2 + cx2, tmp1y = -cy1 * 2 + cy2, tmp2x = (cx1 - cx2) * 3 + 1, tmp2y = (cy1 - cy2) * 3 + 1; + float dfx = cx1 * pre1 + tmp1x * pre2 + tmp2x * subdiv3, dfy = cy1 * pre1 + tmp1y * pre2 + tmp2y * subdiv3; + float ddfx = tmp1x * pre4 + tmp2x * pre5, ddfy = tmp1y * pre4 + tmp2y * pre5; + float dddfx = tmp2x * pre5, dddfy = tmp2y * pre5; + + int i = frameIndex * BEZIER_SIZE; + float[] curves = this.curves; + curves[i++] = BEZIER; + + float x = dfx, y = dfy; + for (int n = i + BEZIER_SIZE - 1; i < n; i += 2) + { + curves[i] = x; + curves[i + 1] = y; + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + x += dfx; + y += dfy; + } + } + + public float GetCurvePercent(int frameIndex, float percent) + { + float[] curves = this.curves; + int i = frameIndex * BEZIER_SIZE; + float type = curves[i]; + if (type == LINEAR) return percent; + if (type == STEPPED) return 0; + i++; + float x = 0; + for (int start = i, n = i + BEZIER_SIZE - 1; i < n; i += 2) + { + x = curves[i]; + if (x >= percent) + { + float prevX, prevY; + if (i == start) + { + prevX = 0; + prevY = 0; + } + else + { + prevX = curves[i - 2]; + prevY = curves[i - 1]; + } + return prevY + (curves[i + 1] - prevY) * (percent - prevX) / (x - prevX); + } + } + float y = curves[i - 1]; + return y + (1 - y) * (percent - x) / (1 - x); // Last point is 1,1. + } + public float GetCurveType(int frameIndex) + { + return curves[frameIndex * BEZIER_SIZE]; + } + } + + public class RotateTimeline : CurveTimeline + { + protected const int PREV_FRAME_TIME = -2; + protected const int FRAME_VALUE = 1; + + internal int boneIndex; + internal float[] frames; + + public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, value, ... + + public RotateTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount << 1]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, float angle) + { + frameIndex *= 2; + frames[frameIndex] = time; + frames[frameIndex + 1] = angle; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) + { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + Bone bone = skeleton.bones.Items[boneIndex]; + + float amount; + + if (time >= frames[frames.Length - 2]) + { // Time is after last frame. + amount = bone.data.rotation + frames[frames.Length - 1] - bone.rotation; + while (amount > 180) + amount -= 360; + while (amount < -180) + amount += 360; + bone.rotation += amount * alpha; + return; + } + + // Interpolate between the previous frame and the current frame. + int frameIndex = Animation.binarySearch(frames, time, 2); + float prevFrameValue = frames[frameIndex - 1]; + float frameTime = frames[frameIndex]; + float percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime); + percent = GetCurvePercent((frameIndex >> 1) - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); + + amount = frames[frameIndex + FRAME_VALUE] - prevFrameValue; + while (amount > 180) + amount -= 360; + while (amount < -180) + amount += 360; + amount = bone.data.rotation + (prevFrameValue + amount * percent) - bone.rotation; + while (amount > 180) + amount -= 360; + while (amount < -180) + amount += 360; + bone.rotation += amount * alpha; + } + } + + public class TranslateTimeline : CurveTimeline + { + protected const int PREV_FRAME_TIME = -3; + protected const int FRAME_X = 1; + protected const int FRAME_Y = 2; + + internal int boneIndex; + internal float[] frames; + + public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, value, value, ... + + public TranslateTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * 3]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, float x, float y) + { + frameIndex *= 3; + frames[frameIndex] = time; + frames[frameIndex + 1] = x; + frames[frameIndex + 2] = y; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) + { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + Bone bone = skeleton.bones.Items[boneIndex]; + + if (time >= frames[frames.Length - 3]) + { // Time is after last frame. + bone.x += (bone.data.x + frames[frames.Length - 2] - bone.x) * alpha; + bone.y += (bone.data.y + frames[frames.Length - 1] - bone.y) * alpha; + return; + } + + // Interpolate between the previous frame and the current frame. + int frameIndex = Animation.binarySearch(frames, time, 3); + float prevFrameX = frames[frameIndex - 2]; + float prevFrameY = frames[frameIndex - 1]; + float frameTime = frames[frameIndex]; + float percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime); + percent = GetCurvePercent(frameIndex / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); + + bone.x += (bone.data.x + prevFrameX + (frames[frameIndex + FRAME_X] - prevFrameX) * percent - bone.x) * alpha; + bone.y += (bone.data.y + prevFrameY + (frames[frameIndex + FRAME_Y] - prevFrameY) * percent - bone.y) * alpha; + } + } + + public class ScaleTimeline : TranslateTimeline + { + public ScaleTimeline(int frameCount) + : base(frameCount) + { + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) + { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + Bone bone = skeleton.bones.Items[boneIndex]; + if (time >= frames[frames.Length - 3]) + { // Time is after last frame. + bone.scaleX += (bone.data.scaleX * frames[frames.Length - 2] - bone.scaleX) * alpha; + bone.scaleY += (bone.data.scaleY * frames[frames.Length - 1] - bone.scaleY) * alpha; + return; + } + + // Interpolate between the previous frame and the current frame. + int frameIndex = Animation.binarySearch(frames, time, 3); + float prevFrameX = frames[frameIndex - 2]; + float prevFrameY = frames[frameIndex - 1]; + float frameTime = frames[frameIndex]; + float percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime); + percent = GetCurvePercent(frameIndex / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); + + bone.scaleX += (bone.data.scaleX * (prevFrameX + (frames[frameIndex + FRAME_X] - prevFrameX) * percent) - bone.scaleX) * alpha; + bone.scaleY += (bone.data.scaleY * (prevFrameY + (frames[frameIndex + FRAME_Y] - prevFrameY) * percent) - bone.scaleY) * alpha; + } + } + + public class ColorTimeline : CurveTimeline + { + protected const int PREV_FRAME_TIME = -5; + protected const int FRAME_R = 1; + protected const int FRAME_G = 2; + protected const int FRAME_B = 3; + protected const int FRAME_A = 4; + + internal int slotIndex; + internal float[] frames; + + public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, r, g, b, a, ... + + public ColorTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * 5]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, float r, float g, float b, float a) + { + frameIndex *= 5; + frames[frameIndex] = time; + frames[frameIndex + 1] = r; + frames[frameIndex + 2] = g; + frames[frameIndex + 3] = b; + frames[frameIndex + 4] = a; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) + { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + float r, g, b, a; + if (time >= frames[frames.Length - 5]) + { + // Time is after last frame. + int i = frames.Length - 1; + r = frames[i - 3]; + g = frames[i - 2]; + b = frames[i - 1]; + a = frames[i]; + } + else + { + // Interpolate between the previous frame and the current frame. + int frameIndex = Animation.binarySearch(frames, time, 5); + float prevFrameR = frames[frameIndex - 4]; + float prevFrameG = frames[frameIndex - 3]; + float prevFrameB = frames[frameIndex - 2]; + float prevFrameA = frames[frameIndex - 1]; + float frameTime = frames[frameIndex]; + float percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime); + percent = GetCurvePercent(frameIndex / 5 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); + + r = prevFrameR + (frames[frameIndex + FRAME_R] - prevFrameR) * percent; + g = prevFrameG + (frames[frameIndex + FRAME_G] - prevFrameG) * percent; + b = prevFrameB + (frames[frameIndex + FRAME_B] - prevFrameB) * percent; + a = prevFrameA + (frames[frameIndex + FRAME_A] - prevFrameA) * percent; + } + Slot slot = skeleton.slots.Items[slotIndex]; + if (alpha < 1) + { + slot.r += (r - slot.r) * alpha; + slot.g += (g - slot.g) * alpha; + slot.b += (b - slot.b) * alpha; + slot.a += (a - slot.a) * alpha; + } + else + { + slot.r = r; + slot.g = g; + slot.b = b; + slot.a = a; + } + } + } + + public class AttachmentTimeline : Timeline + { + internal int slotIndex; + internal float[] frames; + private String[] attachmentNames; + + public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, ... + public String[] AttachmentNames { get { return attachmentNames; } set { attachmentNames = value; } } + public int FrameCount { get { return frames.Length; } } + + public AttachmentTimeline(int frameCount) + { + frames = new float[frameCount]; + attachmentNames = new String[frameCount]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, String attachmentName) + { + frames[frameIndex] = time; + attachmentNames[frameIndex] = attachmentName; + } + + public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) + { + float[] frames = this.frames; + if (time < frames[0]) + { + if (lastTime > time) Apply(skeleton, lastTime, int.MaxValue, null, 0); + return; + } + else if (lastTime > time) // + lastTime = -1; + + int frameIndex = (time >= frames[frames.Length - 1] ? frames.Length : Animation.binarySearch(frames, time)) - 1; + if (frames[frameIndex] < lastTime) return; + + String attachmentName = attachmentNames[frameIndex]; + skeleton.slots.Items[slotIndex].Attachment = + attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); + } + } + + public class EventTimeline : Timeline + { + internal float[] frames; + private Event[] events; + + public float[] Frames { get { return frames; } set { frames = value; } } // time, ... + public Event[] Events { get { return events; } set { events = value; } } + public int FrameCount { get { return frames.Length; } } + + public EventTimeline(int frameCount) + { + frames = new float[frameCount]; + events = new Event[frameCount]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, Event e) + { + frames[frameIndex] = e.Time; + events[frameIndex] = e; + } + + /// Fires events for frames > lastTime and <= time. + public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) + { + if (firedEvents == null) return; + float[] frames = this.frames; + int frameCount = frames.Length; + + if (lastTime > time) + { // Fire events after last time for looped animations. + Apply(skeleton, lastTime, int.MaxValue, firedEvents, alpha); + lastTime = -1f; + } + else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame. + return; + if (time < frames[0]) return; // Time is before first frame. + + int frameIndex; + if (lastTime < frames[0]) + frameIndex = 0; + else + { + frameIndex = Animation.binarySearch(frames, lastTime); + float frame = frames[frameIndex]; + while (frameIndex > 0) + { // Fire multiple events with the same frame. + if (frames[frameIndex - 1] != frame) break; + frameIndex--; + } + } + for (; frameIndex < frameCount && time >= frames[frameIndex]; frameIndex++) + firedEvents.Add(events[frameIndex]); + } + } + + public class DrawOrderTimeline : Timeline + { + internal float[] frames; + private int[][] drawOrders; + + public float[] Frames { get { return frames; } set { frames = value; } } // time, ... + public int[][] DrawOrders { get { return drawOrders; } set { drawOrders = value; } } + public int FrameCount { get { return frames.Length; } } + + public DrawOrderTimeline(int frameCount) + { + frames = new float[frameCount]; + drawOrders = new int[frameCount][]; + } + + /// Sets the time and value of the specified keyframe. + /// May be null to use bind pose draw order. + public void SetFrame(int frameIndex, float time, int[] drawOrder) + { + frames[frameIndex] = time; + drawOrders[frameIndex] = drawOrder; + } + + public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) + { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + int frameIndex; + if (time >= frames[frames.Length - 1]) // Time is after last frame. + frameIndex = frames.Length - 1; + else + frameIndex = Animation.binarySearch(frames, time) - 1; + + ExposedList drawOrder = skeleton.drawOrder; + ExposedList slots = skeleton.slots; + int[] drawOrderToSetupIndex = drawOrders[frameIndex]; + if (drawOrderToSetupIndex == null) + { + drawOrder.Clear(); + for (int i = 0, n = slots.Count; i < n; i++) + drawOrder.Add(slots.Items[i]); + } + else + { + for (int i = 0, n = drawOrderToSetupIndex.Length; i < n; i++) + drawOrder.Items[i] = slots.Items[drawOrderToSetupIndex[i]]; + } + } + } + + public class FfdTimeline : CurveTimeline + { + internal int slotIndex; + internal float[] frames; + private float[][] frameVertices; + internal Attachment attachment; + + public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, ... + public float[][] Vertices { get { return frameVertices; } set { frameVertices = value; } } + public Attachment Attachment { get { return attachment; } set { attachment = value; } } + + public FfdTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount]; + frameVertices = new float[frameCount][]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, float[] vertices) + { + frames[frameIndex] = time; + frameVertices[frameIndex] = vertices; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) + { + Slot slot = skeleton.slots.Items[slotIndex]; + IFfdAttachment ffdAttachment = slot.attachment as IFfdAttachment; + if (ffdAttachment == null || !ffdAttachment.ApplyFFD(attachment)) return; + + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + float[][] frameVertices = this.frameVertices; + int vertexCount = frameVertices[0].Length; + + float[] vertices = slot.attachmentVertices; + if (slot.attachmentVerticesCount != vertexCount) alpha = 1; // Don't mix from uninitialized slot vertices. + + // Ensure capacity + if (vertices.Length < vertexCount) + { + vertices = new float[vertexCount]; + slot.attachmentVertices = vertices; + } + slot.attachmentVerticesCount = vertexCount; + + if (time >= frames[frames.Length - 1]) + { // Time is after last frame. + float[] lastVertices = frameVertices[frames.Length - 1]; + if (alpha < 1) + { + for (int i = 0; i < vertexCount; i++) + { + float vertex = vertices[i]; + vertices[i] = vertex + (lastVertices[i] - vertex) * alpha; + } + } + else + Array.Copy(lastVertices, 0, vertices, 0, vertexCount); + return; + } + + // Interpolate between the previous frame and the current frame. + int frameIndex = Animation.binarySearch(frames, time); + float frameTime = frames[frameIndex]; + float percent = 1 - (time - frameTime) / (frames[frameIndex - 1] - frameTime); + percent = GetCurvePercent(frameIndex - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); + + float[] prevVertices = frameVertices[frameIndex - 1]; + float[] nextVertices = frameVertices[frameIndex]; + + if (alpha < 1) + { + for (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i]; + float vertex = vertices[i]; + vertices[i] = vertex + (prev + (nextVertices[i] - prev) * percent - vertex) * alpha; + } + } + else + { + for (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i]; + vertices[i] = prev + (nextVertices[i] - prev) * percent; + } + } + } + } + + public class IkConstraintTimeline : CurveTimeline + { + private const int PREV_FRAME_TIME = -3; + private const int PREV_FRAME_MIX = -2; + private const int PREV_FRAME_BEND_DIRECTION = -1; + private const int FRAME_MIX = 1; + + internal int ikConstraintIndex; + internal float[] frames; + + public int IkConstraintIndex { get { return ikConstraintIndex; } set { ikConstraintIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, mix, bendDirection, ... + + public IkConstraintTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * 3]; + } + + /** Sets the time, mix and bend direction of the specified keyframe. */ + public void SetFrame(int frameIndex, float time, float mix, int bendDirection) + { + frameIndex *= 3; + frames[frameIndex] = time; + frames[frameIndex + 1] = mix; + frames[frameIndex + 2] = bendDirection; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) + { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + IkConstraint ikConstraint = skeleton.ikConstraints.Items[ikConstraintIndex]; + + if (time >= frames[frames.Length - 3]) + { // Time is after last frame. + ikConstraint.mix += (frames[frames.Length - 2] - ikConstraint.mix) * alpha; + ikConstraint.bendDirection = (int)frames[frames.Length - 1]; + return; + } + + // Interpolate between the previous frame and the current frame. + int frameIndex = Animation.binarySearch(frames, time, 3); + float prevFrameMix = frames[frameIndex + PREV_FRAME_MIX]; + float frameTime = frames[frameIndex]; + float percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime); + percent = GetCurvePercent(frameIndex / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); + + float mix = prevFrameMix + (frames[frameIndex + FRAME_MIX] - prevFrameMix) * percent; + ikConstraint.mix += (mix - ikConstraint.mix) * alpha; + ikConstraint.bendDirection = (int)frames[frameIndex + PREV_FRAME_BEND_DIRECTION]; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/AnimationState.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/AnimationState.cs index 04f1e6c..0a938c9 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/AnimationState.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/AnimationState.cs @@ -30,274 +30,312 @@ *****************************************************************************/ using System; -using System.Collections.Generic; using System.Text; -namespace Spine3_1_07 { - public class AnimationState { - private AnimationStateData data; - private ExposedList tracks = new ExposedList(); - private ExposedList events = new ExposedList(); - private float timeScale = 1; - - public AnimationStateData Data { get { return data; } } - public float TimeScale { get { return timeScale; } set { timeScale = value; } } - - public delegate void StartEndDelegate (AnimationState state, int trackIndex); - public event StartEndDelegate Start; - public event StartEndDelegate End; - - public delegate void EventDelegate (AnimationState state, int trackIndex, Event e); - public event EventDelegate Event; - - public delegate void CompleteDelegate (AnimationState state, int trackIndex, int loopCount); - public event CompleteDelegate Complete; - - public AnimationState (AnimationStateData data) { - if (data == null) throw new ArgumentNullException("data cannot be null."); - this.data = data; - } - - public void Update (float delta) { - delta *= timeScale; - for (int i = 0; i < tracks.Count; i++) { - TrackEntry current = tracks.Items[i]; - if (current == null) continue; - - float trackDelta = delta * current.timeScale; - float time = current.time + trackDelta; - float endTime = current.endTime; - - current.time = time; - if (current.previous != null) { - current.previous.time += trackDelta; - current.mixTime += trackDelta; - } - - // Check if completed the animation or a loop iteration. - if (current.loop ? (current.lastTime % endTime > time % endTime) : (current.lastTime < endTime && time >= endTime)) { - int count = (int)(time / endTime); - current.OnComplete(this, i, count); - if (Complete != null) Complete(this, i, count); - } - - TrackEntry next = current.next; - if (next != null) { - next.time = current.lastTime - next.delay; - if (next.time >= 0) SetCurrent(i, next); - } else { - // End non-looping animation when it reaches its end time and there is no next entry. - if (!current.loop && current.lastTime >= current.endTime) ClearTrack(i); - } - } - } - - public void Apply (Skeleton skeleton) { - ExposedList events = this.events; - - for (int i = 0; i < tracks.Count; i++) { - TrackEntry current = tracks.Items[i]; - if (current == null) continue; - - events.Clear(); - - float time = current.time; - bool loop = current.loop; - if (!loop && time > current.endTime) time = current.endTime; - - TrackEntry previous = current.previous; - if (previous == null) { - if (current.mix == 1) - current.animation.Apply(skeleton, current.lastTime, time, loop, events); - else - current.animation.Mix(skeleton, current.lastTime, time, loop, events, current.mix); - } else { - float previousTime = previous.time; - if (!previous.loop && previousTime > previous.endTime) previousTime = previous.endTime; - previous.animation.Apply(skeleton, previous.lastTime, previousTime, previous.loop, null); - // Remove the line above, and uncomment the line below, to allow previous animations to fire events during mixing. - //previous.animation.Apply(skeleton, previous.lastTime, previousTime, previous.loop, events); - previous.lastTime = previousTime; - - float alpha = current.mixTime / current.mixDuration * current.mix; - if (alpha >= 1) { - alpha = 1; - current.previous = null; - } - current.animation.Mix(skeleton, current.lastTime, time, loop, events, alpha); - } - - for (int ii = 0, nn = events.Count; ii < nn; ii++) { - Event e = events.Items[ii]; - current.OnEvent(this, i, e); - if (Event != null) Event(this, i, e); - } - - current.lastTime = current.time; - } - } - - public void ClearTracks () { - for (int i = 0, n = tracks.Count; i < n; i++) - ClearTrack(i); - tracks.Clear(); - } - - public void ClearTrack (int trackIndex) { - if (trackIndex >= tracks.Count) return; - TrackEntry current = tracks.Items[trackIndex]; - if (current == null) return; - - current.OnEnd(this, trackIndex); - if (End != null) End(this, trackIndex); - - tracks.Items[trackIndex] = null; - } - - private TrackEntry ExpandToIndex (int index) { - if (index < tracks.Count) return tracks.Items[index]; - while (index >= tracks.Count) - tracks.Add(null); - return null; - } - - private void SetCurrent (int index, TrackEntry entry) { - TrackEntry current = ExpandToIndex(index); - if (current != null) { - TrackEntry previous = current.previous; - current.previous = null; - - current.OnEnd(this, index); - if (End != null) End(this, index); - - entry.mixDuration = data.GetMix(current.animation, entry.animation); - if (entry.mixDuration > 0) { - entry.mixTime = 0; - // If a mix is in progress, mix from the closest animation. - if (previous != null && current.mixTime / current.mixDuration < 0.5f) - entry.previous = previous; - else - entry.previous = current; - } - } - - tracks.Items[index] = entry; - - entry.OnStart(this, index); - if (Start != null) Start(this, index); - } - - public TrackEntry SetAnimation (int trackIndex, String animationName, bool loop) { - Animation animation = data.skeletonData.FindAnimation(animationName); - if (animation == null) throw new ArgumentException("Animation not found: " + animationName); - return SetAnimation(trackIndex, animation, loop); - } - - /// Set the current animation. Any queued animations are cleared. - public TrackEntry SetAnimation (int trackIndex, Animation animation, bool loop) { - if (animation == null) throw new ArgumentException("animation cannot be null."); - TrackEntry entry = new TrackEntry(); - entry.animation = animation; - entry.loop = loop; - entry.time = 0; - entry.endTime = animation.Duration; - SetCurrent(trackIndex, entry); - return entry; - } - - public TrackEntry AddAnimation (int trackIndex, String animationName, bool loop, float delay) { - Animation animation = data.skeletonData.FindAnimation(animationName); - if (animation == null) throw new ArgumentException("Animation not found: " + animationName); - return AddAnimation(trackIndex, animation, loop, delay); - } - - /// Adds an animation to be played delay seconds after the current or last queued animation. - /// May be <= 0 to use duration of previous animation minus any mix duration plus the negative delay. - public TrackEntry AddAnimation (int trackIndex, Animation animation, bool loop, float delay) { - if (animation == null) throw new ArgumentException("animation cannot be null."); - TrackEntry entry = new TrackEntry(); - entry.animation = animation; - entry.loop = loop; - entry.time = 0; - entry.endTime = animation.Duration; - - TrackEntry last = ExpandToIndex(trackIndex); - if (last != null) { - while (last.next != null) - last = last.next; - last.next = entry; - } else - tracks.Items[trackIndex] = entry; - - if (delay <= 0) { - if (last != null) - delay += last.endTime - data.GetMix(last.animation, animation); - else - delay = 0; - } - entry.delay = delay; - - return entry; - } - - /// May be null. - public TrackEntry GetCurrent (int trackIndex) { - if (trackIndex >= tracks.Count) return null; - return tracks.Items[trackIndex]; - } - - override public String ToString () { - StringBuilder buffer = new StringBuilder(); - for (int i = 0, n = tracks.Count; i < n; i++) { - TrackEntry entry = tracks.Items[i]; - if (entry == null) continue; - if (buffer.Length > 0) buffer.Append(", "); - buffer.Append(entry.ToString()); - } - if (buffer.Length == 0) return ""; - return buffer.ToString(); - } - } - - public class TrackEntry { - internal TrackEntry next, previous; - internal Animation animation; - internal bool loop; - internal float delay, time, lastTime = -1, endTime, timeScale = 1; - internal float mixTime, mixDuration, mix = 1; - - public Animation Animation { get { return animation; } } - public float Delay { get { return delay; } set { delay = value; } } - public float Time { get { return time; } set { time = value; } } - public float LastTime { get { return lastTime; } set { lastTime = value; } } - public float EndTime { get { return endTime; } set { endTime = value; } } - public float TimeScale { get { return timeScale; } set { timeScale = value; } } - public float Mix { get { return mix; } set { mix = value; } } - public bool Loop { get { return loop; } set { loop = value; } } - - public event AnimationState.StartEndDelegate Start; - public event AnimationState.StartEndDelegate End; - public event AnimationState.EventDelegate Event; - public event AnimationState.CompleteDelegate Complete; - - internal void OnStart (AnimationState state, int index) { - if (Start != null) Start(state, index); - } - - internal void OnEnd (AnimationState state, int index) { - if (End != null) End(state, index); - } - - internal void OnEvent (AnimationState state, int index, Event e) { - if (Event != null) Event(state, index, e); - } - - internal void OnComplete (AnimationState state, int index, int loopCount) { - if (Complete != null) Complete(state, index, loopCount); - } - - override public String ToString () { - return animation == null ? "" : animation.name; - } - } +namespace Spine3_1_07 +{ + public class AnimationState + { + private AnimationStateData data; + private ExposedList tracks = new ExposedList(); + private ExposedList events = new ExposedList(); + private float timeScale = 1; + + public AnimationStateData Data { get { return data; } } + public float TimeScale { get { return timeScale; } set { timeScale = value; } } + + public delegate void StartEndDelegate(AnimationState state, int trackIndex); + public event StartEndDelegate Start; + public event StartEndDelegate End; + + public delegate void EventDelegate(AnimationState state, int trackIndex, Event e); + public event EventDelegate Event; + + public delegate void CompleteDelegate(AnimationState state, int trackIndex, int loopCount); + public event CompleteDelegate Complete; + + public AnimationState(AnimationStateData data) + { + if (data == null) throw new ArgumentNullException("data cannot be null."); + this.data = data; + } + + public void Update(float delta) + { + delta *= timeScale; + for (int i = 0; i < tracks.Count; i++) + { + TrackEntry current = tracks.Items[i]; + if (current == null) continue; + + float trackDelta = delta * current.timeScale; + float time = current.time + trackDelta; + float endTime = current.endTime; + + current.time = time; + if (current.previous != null) + { + current.previous.time += trackDelta; + current.mixTime += trackDelta; + } + + // Check if completed the animation or a loop iteration. + if (current.loop ? (current.lastTime % endTime > time % endTime) : (current.lastTime < endTime && time >= endTime)) + { + int count = (int)(time / endTime); + current.OnComplete(this, i, count); + if (Complete != null) Complete(this, i, count); + } + + TrackEntry next = current.next; + if (next != null) + { + next.time = current.lastTime - next.delay; + if (next.time >= 0) SetCurrent(i, next); + } + else + { + // End non-looping animation when it reaches its end time and there is no next entry. + if (!current.loop && current.lastTime >= current.endTime) ClearTrack(i); + } + } + } + + public void Apply(Skeleton skeleton) + { + ExposedList events = this.events; + + for (int i = 0; i < tracks.Count; i++) + { + TrackEntry current = tracks.Items[i]; + if (current == null) continue; + + events.Clear(); + + float time = current.time; + bool loop = current.loop; + if (!loop && time > current.endTime) time = current.endTime; + + TrackEntry previous = current.previous; + if (previous == null) + { + if (current.mix == 1) + current.animation.Apply(skeleton, current.lastTime, time, loop, events); + else + current.animation.Mix(skeleton, current.lastTime, time, loop, events, current.mix); + } + else + { + float previousTime = previous.time; + if (!previous.loop && previousTime > previous.endTime) previousTime = previous.endTime; + previous.animation.Apply(skeleton, previous.lastTime, previousTime, previous.loop, null); + // Remove the line above, and uncomment the line below, to allow previous animations to fire events during mixing. + //previous.animation.Apply(skeleton, previous.lastTime, previousTime, previous.loop, events); + previous.lastTime = previousTime; + + float alpha = current.mixTime / current.mixDuration * current.mix; + if (alpha >= 1) + { + alpha = 1; + current.previous = null; + } + current.animation.Mix(skeleton, current.lastTime, time, loop, events, alpha); + } + + for (int ii = 0, nn = events.Count; ii < nn; ii++) + { + Event e = events.Items[ii]; + current.OnEvent(this, i, e); + if (Event != null) Event(this, i, e); + } + + current.lastTime = current.time; + } + } + + public void ClearTracks() + { + for (int i = 0, n = tracks.Count; i < n; i++) + ClearTrack(i); + tracks.Clear(); + } + + public void ClearTrack(int trackIndex) + { + if (trackIndex >= tracks.Count) return; + TrackEntry current = tracks.Items[trackIndex]; + if (current == null) return; + + current.OnEnd(this, trackIndex); + if (End != null) End(this, trackIndex); + + tracks.Items[trackIndex] = null; + } + + private TrackEntry ExpandToIndex(int index) + { + if (index < tracks.Count) return tracks.Items[index]; + while (index >= tracks.Count) + tracks.Add(null); + return null; + } + + private void SetCurrent(int index, TrackEntry entry) + { + TrackEntry current = ExpandToIndex(index); + if (current != null) + { + TrackEntry previous = current.previous; + current.previous = null; + + current.OnEnd(this, index); + if (End != null) End(this, index); + + entry.mixDuration = data.GetMix(current.animation, entry.animation); + if (entry.mixDuration > 0) + { + entry.mixTime = 0; + // If a mix is in progress, mix from the closest animation. + if (previous != null && current.mixTime / current.mixDuration < 0.5f) + entry.previous = previous; + else + entry.previous = current; + } + } + + tracks.Items[index] = entry; + + entry.OnStart(this, index); + if (Start != null) Start(this, index); + } + + public TrackEntry SetAnimation(int trackIndex, String animationName, bool loop) + { + Animation animation = data.skeletonData.FindAnimation(animationName); + if (animation == null) throw new ArgumentException("Animation not found: " + animationName); + return SetAnimation(trackIndex, animation, loop); + } + + /// Set the current animation. Any queued animations are cleared. + public TrackEntry SetAnimation(int trackIndex, Animation animation, bool loop) + { + if (animation == null) throw new ArgumentException("animation cannot be null."); + TrackEntry entry = new TrackEntry(); + entry.animation = animation; + entry.loop = loop; + entry.time = 0; + entry.endTime = animation.Duration; + SetCurrent(trackIndex, entry); + return entry; + } + + public TrackEntry AddAnimation(int trackIndex, String animationName, bool loop, float delay) + { + Animation animation = data.skeletonData.FindAnimation(animationName); + if (animation == null) throw new ArgumentException("Animation not found: " + animationName); + return AddAnimation(trackIndex, animation, loop, delay); + } + + /// Adds an animation to be played delay seconds after the current or last queued animation. + /// May be <= 0 to use duration of previous animation minus any mix duration plus the negative delay. + public TrackEntry AddAnimation(int trackIndex, Animation animation, bool loop, float delay) + { + if (animation == null) throw new ArgumentException("animation cannot be null."); + TrackEntry entry = new TrackEntry(); + entry.animation = animation; + entry.loop = loop; + entry.time = 0; + entry.endTime = animation.Duration; + + TrackEntry last = ExpandToIndex(trackIndex); + if (last != null) + { + while (last.next != null) + last = last.next; + last.next = entry; + } + else + tracks.Items[trackIndex] = entry; + + if (delay <= 0) + { + if (last != null) + delay += last.endTime - data.GetMix(last.animation, animation); + else + delay = 0; + } + entry.delay = delay; + + return entry; + } + + /// May be null. + public TrackEntry GetCurrent(int trackIndex) + { + if (trackIndex >= tracks.Count) return null; + return tracks.Items[trackIndex]; + } + + override public String ToString() + { + StringBuilder buffer = new StringBuilder(); + for (int i = 0, n = tracks.Count; i < n; i++) + { + TrackEntry entry = tracks.Items[i]; + if (entry == null) continue; + if (buffer.Length > 0) buffer.Append(", "); + buffer.Append(entry.ToString()); + } + if (buffer.Length == 0) return ""; + return buffer.ToString(); + } + } + + public class TrackEntry + { + internal TrackEntry next, previous; + internal Animation animation; + internal bool loop; + internal float delay, time, lastTime = -1, endTime, timeScale = 1; + internal float mixTime, mixDuration, mix = 1; + + public Animation Animation { get { return animation; } } + public float Delay { get { return delay; } set { delay = value; } } + public float Time { get { return time; } set { time = value; } } + public float LastTime { get { return lastTime; } set { lastTime = value; } } + public float EndTime { get { return endTime; } set { endTime = value; } } + public float TimeScale { get { return timeScale; } set { timeScale = value; } } + public float Mix { get { return mix; } set { mix = value; } } + public bool Loop { get { return loop; } set { loop = value; } } + + public event AnimationState.StartEndDelegate Start; + public event AnimationState.StartEndDelegate End; + public event AnimationState.EventDelegate Event; + public event AnimationState.CompleteDelegate Complete; + + internal void OnStart(AnimationState state, int index) + { + if (Start != null) Start(state, index); + } + + internal void OnEnd(AnimationState state, int index) + { + if (End != null) End(state, index); + } + + internal void OnEvent(AnimationState state, int index, Event e) + { + if (Event != null) Event(state, index, e); + } + + internal void OnComplete(AnimationState state, int index, int loopCount) + { + if (Complete != null) Complete(state, index, loopCount); + } + + override public String ToString() + { + return animation == null ? "" : animation.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/AnimationStateData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/AnimationStateData.cs index 0d6c33a..e84fb0d 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/AnimationStateData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/AnimationStateData.cs @@ -32,65 +32,76 @@ using System; using System.Collections.Generic; -namespace Spine3_1_07 { - public class AnimationStateData { - internal SkeletonData skeletonData; - private Dictionary animationToMixTime = new Dictionary(AnimationPairComparer.Instance); - internal float defaultMix; +namespace Spine3_1_07 +{ + public class AnimationStateData + { + internal SkeletonData skeletonData; + private Dictionary animationToMixTime = new Dictionary(AnimationPairComparer.Instance); + internal float defaultMix; - public SkeletonData SkeletonData { get { return skeletonData; } } - public float DefaultMix { get { return defaultMix; } set { defaultMix = value; } } + public SkeletonData SkeletonData { get { return skeletonData; } } + public float DefaultMix { get { return defaultMix; } set { defaultMix = value; } } - public AnimationStateData (SkeletonData skeletonData) { - this.skeletonData = skeletonData; - } + public AnimationStateData(SkeletonData skeletonData) + { + this.skeletonData = skeletonData; + } - public void SetMix (String fromName, String toName, float duration) { - Animation from = skeletonData.FindAnimation(fromName); - if (from == null) throw new ArgumentException("Animation not found: " + fromName); - Animation to = skeletonData.FindAnimation(toName); - if (to == null) throw new ArgumentException("Animation not found: " + toName); - SetMix(from, to, duration); - } + public void SetMix(String fromName, String toName, float duration) + { + Animation from = skeletonData.FindAnimation(fromName); + if (from == null) throw new ArgumentException("Animation not found: " + fromName); + Animation to = skeletonData.FindAnimation(toName); + if (to == null) throw new ArgumentException("Animation not found: " + toName); + SetMix(from, to, duration); + } - public void SetMix (Animation from, Animation to, float duration) { - if (from == null) throw new ArgumentNullException("from cannot be null."); - if (to == null) throw new ArgumentNullException("to cannot be null."); - AnimationPair key = new AnimationPair(from, to); - animationToMixTime.Remove(key); - animationToMixTime.Add(key, duration); - } + public void SetMix(Animation from, Animation to, float duration) + { + if (from == null) throw new ArgumentNullException("from cannot be null."); + if (to == null) throw new ArgumentNullException("to cannot be null."); + AnimationPair key = new AnimationPair(from, to); + animationToMixTime.Remove(key); + animationToMixTime.Add(key, duration); + } - public float GetMix (Animation from, Animation to) { - AnimationPair key = new AnimationPair(from, to); - float duration; - if (animationToMixTime.TryGetValue(key, out duration)) return duration; - return defaultMix; - } + public float GetMix(Animation from, Animation to) + { + AnimationPair key = new AnimationPair(from, to); + float duration; + if (animationToMixTime.TryGetValue(key, out duration)) return duration; + return defaultMix; + } - struct AnimationPair { - public readonly Animation a1; - public readonly Animation a2; + struct AnimationPair + { + public readonly Animation a1; + public readonly Animation a2; - public AnimationPair (Animation a1, Animation a2) { - this.a1 = a1; - this.a2 = a2; - } - } + public AnimationPair(Animation a1, Animation a2) + { + this.a1 = a1; + this.a2 = a2; + } + } - // Avoids boxing in the dictionary. - class AnimationPairComparer : IEqualityComparer { - internal static readonly AnimationPairComparer Instance = new AnimationPairComparer(); + // Avoids boxing in the dictionary. + class AnimationPairComparer : IEqualityComparer + { + internal static readonly AnimationPairComparer Instance = new AnimationPairComparer(); - bool IEqualityComparer.Equals (AnimationPair x, AnimationPair y) { - return ReferenceEquals(x.a1, y.a1) && ReferenceEquals(x.a2, y.a2); - } + bool IEqualityComparer.Equals(AnimationPair x, AnimationPair y) + { + return ReferenceEquals(x.a1, y.a1) && ReferenceEquals(x.a2, y.a2); + } - int IEqualityComparer.GetHashCode (AnimationPair obj) { - // from Tuple.CombineHashCodes // return (((h1 << 5) + h1) ^ h2); - int h1 = obj.a1.GetHashCode(); - return (((h1 << 5) + h1) ^ obj.a2.GetHashCode()); - } - } - } + int IEqualityComparer.GetHashCode(AnimationPair obj) + { + // from Tuple.CombineHashCodes // return (((h1 << 5) + h1) ^ h2); + int h1 = obj.a1.GetHashCode(); + return (((h1 << 5) + h1) ^ obj.a2.GetHashCode()); + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Atlas.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Atlas.cs index cf9b5bc..29a0882 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Atlas.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Atlas.cs @@ -32,21 +32,22 @@ using System; using System.Collections.Generic; using System.IO; -using System.Reflection; #if WINDOWS_STOREAPP using System.Threading.Tasks; using Windows.Storage; #endif -namespace Spine3_1_07 { - public class Atlas { - List pages = new List(); - List regions = new List(); - TextureLoader textureLoader; +namespace Spine3_1_07 +{ + public class Atlas + { + List pages = new List(); + List regions = new List(); + TextureLoader textureLoader; - #if !(UNITY_5 || UNITY_4 || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1) // !UNITY - #if WINDOWS_STOREAPP +#if !(UNITY_5 || UNITY_4 || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1) // !UNITY +#if WINDOWS_STOREAPP private async Task ReadFile(string path, TextureLoader textureLoader) { var folder = Windows.ApplicationModel.Package.Current.InstalledLocation; var file = await folder.GetFileAsync(path).AsTask().ConfigureAwait(false); @@ -62,233 +63,264 @@ private async Task ReadFile(string path, TextureLoader textureLoader) { public Atlas(String path, TextureLoader textureLoader) { this.ReadFile(path, textureLoader).Wait(); } - #else +#else - public Atlas (String path, TextureLoader textureLoader) { + public Atlas(String path, TextureLoader textureLoader) + { - #if WINDOWS_PHONE +#if WINDOWS_PHONE Stream stream = Microsoft.Xna.Framework.TitleContainer.OpenStream(path); using (StreamReader reader = new StreamReader(stream)) { - #else - using (StreamReader reader = new StreamReader(path)) { - #endif // WINDOWS_PHONE - - try { - Load(reader, Path.GetDirectoryName(path), textureLoader); - } catch (Exception ex) { - throw new Exception("Error reading atlas file: " + path, ex); - } - - } - } - #endif // WINDOWS_STOREAPP - - #endif // !(UNITY) - - public Atlas (TextReader reader, String dir, TextureLoader textureLoader) { - Load(reader, dir, textureLoader); - } - - public Atlas (List pages, List regions) { - this.pages = pages; - this.regions = regions; - this.textureLoader = null; - } - - private void Load (TextReader reader, String imagesDir, TextureLoader textureLoader) { - if (textureLoader == null) throw new ArgumentNullException("textureLoader cannot be null."); - this.textureLoader = textureLoader; - - String[] tuple = new String[4]; - AtlasPage page = null; - while (true) { - String line = reader.ReadLine(); - if (line == null) break; - if (line.Trim().Length == 0) - page = null; - else if (page == null) { - page = new AtlasPage(); - page.name = line; - - if (ReadTuple(reader, tuple) == 2) { // size is only optional for an atlas packed with an old TexturePacker. - page.width = int.Parse(tuple[0]); - page.height = int.Parse(tuple[1]); - ReadTuple(reader, tuple); - } - page.format = (Format)Enum.Parse(typeof(Format), tuple[0], false); - - ReadTuple(reader, tuple); - page.minFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[0], false); - page.magFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[1], false); - - String direction = ReadValue(reader); - page.uWrap = TextureWrap.ClampToEdge; - page.vWrap = TextureWrap.ClampToEdge; - if (direction == "x") - page.uWrap = TextureWrap.Repeat; - else if (direction == "y") - page.vWrap = TextureWrap.Repeat; - else if (direction == "xy") - page.uWrap = page.vWrap = TextureWrap.Repeat; - - textureLoader.Load(page, Path.Combine(imagesDir, line)); - - pages.Add(page); - - } else { - AtlasRegion region = new AtlasRegion(); - region.name = line; - region.page = page; - - region.rotate = Boolean.Parse(ReadValue(reader)); - - ReadTuple(reader, tuple); - int x = int.Parse(tuple[0]); - int y = int.Parse(tuple[1]); - - ReadTuple(reader, tuple); - int width = int.Parse(tuple[0]); - int height = int.Parse(tuple[1]); - - region.u = x / (float)page.width; - region.v = y / (float)page.height; - if (region.rotate) { - region.u2 = (x + height) / (float)page.width; - region.v2 = (y + width) / (float)page.height; - } else { - region.u2 = (x + width) / (float)page.width; - region.v2 = (y + height) / (float)page.height; - } - region.x = x; - region.y = y; - region.width = Math.Abs(width); - region.height = Math.Abs(height); - - if (ReadTuple(reader, tuple) == 4) { // split is optional - region.splits = new int[] {int.Parse(tuple[0]), int.Parse(tuple[1]), - int.Parse(tuple[2]), int.Parse(tuple[3])}; - - if (ReadTuple(reader, tuple) == 4) { // pad is optional, but only present with splits - region.pads = new int[] {int.Parse(tuple[0]), int.Parse(tuple[1]), - int.Parse(tuple[2]), int.Parse(tuple[3])}; - - ReadTuple(reader, tuple); - } - } - - region.originalWidth = int.Parse(tuple[0]); - region.originalHeight = int.Parse(tuple[1]); - - ReadTuple(reader, tuple); - region.offsetX = int.Parse(tuple[0]); - region.offsetY = int.Parse(tuple[1]); - - region.index = int.Parse(ReadValue(reader)); - - regions.Add(region); - } - } - } - - static String ReadValue (TextReader reader) { - String line = reader.ReadLine(); - int colon = line.IndexOf(':'); - if (colon == -1) throw new Exception("Invalid line: " + line); - return line.Substring(colon + 1).Trim(); - } - - /// Returns the number of tuple values read (1, 2 or 4). - static int ReadTuple (TextReader reader, String[] tuple) { - String line = reader.ReadLine(); - int colon = line.IndexOf(':'); - if (colon == -1) throw new Exception("Invalid line: " + line); - int i = 0, lastMatch = colon + 1; - for (; i < 3; i++) { - int comma = line.IndexOf(',', lastMatch); - if (comma == -1) break; - tuple[i] = line.Substring(lastMatch, comma - lastMatch).Trim(); - lastMatch = comma + 1; - } - tuple[i] = line.Substring(lastMatch).Trim(); - return i + 1; - } - - public void FlipV () { - for (int i = 0, n = regions.Count; i < n; i++) { - AtlasRegion region = regions[i]; - region.v = 1 - region.v; - region.v2 = 1 - region.v2; - } - } - - /// 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. - /// The region, or null. - public AtlasRegion FindRegion (String name) { - for (int i = 0, n = regions.Count; i < n; i++) - if (regions[i].name == name) return regions[i]; - return null; - } - - public void Dispose () { - if (textureLoader == null) return; - for (int i = 0, n = pages.Count; i < n; i++) - textureLoader.Unload(pages[i].rendererObject); - } - } - - public enum Format { - Alpha, - Intensity, - LuminanceAlpha, - RGB565, - RGBA4444, - RGB888, - RGBA8888 - } - - public enum TextureFilter { - Nearest, - Linear, - MipMap, - MipMapNearestNearest, - MipMapLinearNearest, - MipMapNearestLinear, - MipMapLinearLinear - } - - public enum TextureWrap { - MirroredRepeat, - ClampToEdge, - Repeat - } - - public class AtlasPage { - public String name; - public Format format; - public TextureFilter minFilter; - public TextureFilter magFilter; - public TextureWrap uWrap; - public TextureWrap vWrap; - public Object rendererObject; - public int width, height; - } - - public class AtlasRegion { - public AtlasPage page; - public String name; - public int x, y, width, height; - public float u, v, u2, v2; - public float offsetX, offsetY; - public int originalWidth, originalHeight; - public int index; - public bool rotate; - public int[] splits; - public int[] pads; - } - - public interface TextureLoader { - void Load (AtlasPage page, String path); - void Unload (Object texture); - } +#else + using (StreamReader reader = new StreamReader(path)) + { +#endif // WINDOWS_PHONE + + try + { + Load(reader, Path.GetDirectoryName(path), textureLoader); + } + catch (Exception ex) + { + throw new Exception("Error reading atlas file: " + path, ex); + } + + } + } +#endif // WINDOWS_STOREAPP + +#endif // !(UNITY) + + public Atlas(TextReader reader, String dir, TextureLoader textureLoader) + { + Load(reader, dir, textureLoader); + } + + public Atlas(List pages, List regions) + { + this.pages = pages; + this.regions = regions; + this.textureLoader = null; + } + + private void Load(TextReader reader, String imagesDir, TextureLoader textureLoader) + { + if (textureLoader == null) throw new ArgumentNullException("textureLoader cannot be null."); + this.textureLoader = textureLoader; + + String[] tuple = new String[4]; + AtlasPage page = null; + while (true) + { + String line = reader.ReadLine(); + if (line == null) break; + if (line.Trim().Length == 0) + page = null; + else if (page == null) + { + page = new AtlasPage(); + page.name = line; + + if (ReadTuple(reader, tuple) == 2) + { // size is only optional for an atlas packed with an old TexturePacker. + page.width = int.Parse(tuple[0]); + page.height = int.Parse(tuple[1]); + ReadTuple(reader, tuple); + } + page.format = (Format)Enum.Parse(typeof(Format), tuple[0], false); + + ReadTuple(reader, tuple); + page.minFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[0], false); + page.magFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[1], false); + + String direction = ReadValue(reader); + page.uWrap = TextureWrap.ClampToEdge; + page.vWrap = TextureWrap.ClampToEdge; + if (direction == "x") + page.uWrap = TextureWrap.Repeat; + else if (direction == "y") + page.vWrap = TextureWrap.Repeat; + else if (direction == "xy") + page.uWrap = page.vWrap = TextureWrap.Repeat; + + textureLoader.Load(page, Path.Combine(imagesDir, line)); + + pages.Add(page); + + } + else + { + AtlasRegion region = new AtlasRegion(); + region.name = line; + region.page = page; + + region.rotate = Boolean.Parse(ReadValue(reader)); + + ReadTuple(reader, tuple); + int x = int.Parse(tuple[0]); + int y = int.Parse(tuple[1]); + + ReadTuple(reader, tuple); + int width = int.Parse(tuple[0]); + int height = int.Parse(tuple[1]); + + region.u = x / (float)page.width; + region.v = y / (float)page.height; + if (region.rotate) + { + region.u2 = (x + height) / (float)page.width; + region.v2 = (y + width) / (float)page.height; + } + else + { + region.u2 = (x + width) / (float)page.width; + region.v2 = (y + height) / (float)page.height; + } + region.x = x; + region.y = y; + region.width = Math.Abs(width); + region.height = Math.Abs(height); + + if (ReadTuple(reader, tuple) == 4) + { // split is optional + region.splits = new int[] {int.Parse(tuple[0]), int.Parse(tuple[1]), + int.Parse(tuple[2]), int.Parse(tuple[3])}; + + if (ReadTuple(reader, tuple) == 4) + { // pad is optional, but only present with splits + region.pads = new int[] {int.Parse(tuple[0]), int.Parse(tuple[1]), + int.Parse(tuple[2]), int.Parse(tuple[3])}; + + ReadTuple(reader, tuple); + } + } + + region.originalWidth = int.Parse(tuple[0]); + region.originalHeight = int.Parse(tuple[1]); + + ReadTuple(reader, tuple); + region.offsetX = int.Parse(tuple[0]); + region.offsetY = int.Parse(tuple[1]); + + region.index = int.Parse(ReadValue(reader)); + + regions.Add(region); + } + } + } + + static String ReadValue(TextReader reader) + { + String line = reader.ReadLine(); + int colon = line.IndexOf(':'); + if (colon == -1) throw new Exception("Invalid line: " + line); + return line.Substring(colon + 1).Trim(); + } + + /// Returns the number of tuple values read (1, 2 or 4). + static int ReadTuple(TextReader reader, String[] tuple) + { + String line = reader.ReadLine(); + int colon = line.IndexOf(':'); + if (colon == -1) throw new Exception("Invalid line: " + line); + int i = 0, lastMatch = colon + 1; + for (; i < 3; i++) + { + int comma = line.IndexOf(',', lastMatch); + if (comma == -1) break; + tuple[i] = line.Substring(lastMatch, comma - lastMatch).Trim(); + lastMatch = comma + 1; + } + tuple[i] = line.Substring(lastMatch).Trim(); + return i + 1; + } + + public void FlipV() + { + for (int i = 0, n = regions.Count; i < n; i++) + { + AtlasRegion region = regions[i]; + region.v = 1 - region.v; + region.v2 = 1 - region.v2; + } + } + + /// 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. + /// The region, or null. + public AtlasRegion FindRegion(String name) + { + for (int i = 0, n = regions.Count; i < n; i++) + if (regions[i].name == name) return regions[i]; + return null; + } + + public void Dispose() + { + if (textureLoader == null) return; + for (int i = 0, n = pages.Count; i < n; i++) + textureLoader.Unload(pages[i].rendererObject); + } + } + + public enum Format + { + Alpha, + Intensity, + LuminanceAlpha, + RGB565, + RGBA4444, + RGB888, + RGBA8888 + } + + public enum TextureFilter + { + Nearest, + Linear, + MipMap, + MipMapNearestNearest, + MipMapLinearNearest, + MipMapNearestLinear, + MipMapLinearLinear + } + + public enum TextureWrap + { + MirroredRepeat, + ClampToEdge, + Repeat + } + + public class AtlasPage + { + public String name; + public Format format; + public TextureFilter minFilter; + public TextureFilter magFilter; + public TextureWrap uWrap; + public TextureWrap vWrap; + public Object rendererObject; + public int width, height; + } + + public class AtlasRegion + { + public AtlasPage page; + public String name; + public int x, y, width, height; + public float u, v, u2, v2; + public float offsetX, offsetY; + public int originalWidth, originalHeight; + public int index; + public bool rotate; + public int[] splits; + public int[] pads; + } + + public interface TextureLoader + { + void Load(AtlasPage page, String path); + void Unload(Object texture); + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Attachments/AtlasAttachmentLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Attachments/AtlasAttachmentLoader.cs index 53d5aae..0c75353 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Attachments/AtlasAttachmentLoader.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Attachments/AtlasAttachmentLoader.cs @@ -31,82 +31,91 @@ using System; -namespace Spine3_1_07 { - public class AtlasAttachmentLoader : AttachmentLoader { - private Atlas[] atlasArray; +namespace Spine3_1_07 +{ + public class AtlasAttachmentLoader : AttachmentLoader + { + private Atlas[] atlasArray; - public AtlasAttachmentLoader (params Atlas[] atlasArray) { - if (atlasArray == null) throw new ArgumentNullException("atlas array cannot be null."); - this.atlasArray = atlasArray; - } + public AtlasAttachmentLoader(params Atlas[] atlasArray) + { + if (atlasArray == null) throw new ArgumentNullException("atlas array cannot be null."); + this.atlasArray = atlasArray; + } - public RegionAttachment NewRegionAttachment (Skin skin, String name, String path) { - AtlasRegion region = FindRegion(path); - if (region == null) throw new Exception("Region not found in atlas: " + path + " (region attachment: " + name + ")"); - RegionAttachment attachment = new RegionAttachment(name); - attachment.RendererObject = region; - attachment.SetUVs(region.u, region.v, region.u2, region.v2, region.rotate); - attachment.regionOffsetX = region.offsetX; - attachment.regionOffsetY = region.offsetY; - attachment.regionWidth = region.width; - attachment.regionHeight = region.height; - attachment.regionOriginalWidth = region.originalWidth; - attachment.regionOriginalHeight = region.originalHeight; - return attachment; - } + public RegionAttachment NewRegionAttachment(Skin skin, String name, String path) + { + AtlasRegion region = FindRegion(path); + if (region == null) throw new Exception("Region not found in atlas: " + path + " (region attachment: " + name + ")"); + RegionAttachment attachment = new RegionAttachment(name); + attachment.RendererObject = region; + attachment.SetUVs(region.u, region.v, region.u2, region.v2, region.rotate); + attachment.regionOffsetX = region.offsetX; + attachment.regionOffsetY = region.offsetY; + attachment.regionWidth = region.width; + attachment.regionHeight = region.height; + attachment.regionOriginalWidth = region.originalWidth; + attachment.regionOriginalHeight = region.originalHeight; + return attachment; + } - public MeshAttachment NewMeshAttachment (Skin skin, String name, String path) { - AtlasRegion region = FindRegion(path); - if (region == null) throw new Exception("Region not found in atlas: " + path + " (mesh attachment: " + name + ")"); - MeshAttachment attachment = new MeshAttachment(name); - attachment.RendererObject = region; - attachment.RegionU = region.u; - attachment.RegionV = region.v; - attachment.RegionU2 = region.u2; - attachment.RegionV2 = region.v2; - attachment.RegionRotate = region.rotate; - attachment.regionOffsetX = region.offsetX; - attachment.regionOffsetY = region.offsetY; - attachment.regionWidth = region.width; - attachment.regionHeight = region.height; - attachment.regionOriginalWidth = region.originalWidth; - attachment.regionOriginalHeight = region.originalHeight; - return attachment; - } + public MeshAttachment NewMeshAttachment(Skin skin, String name, String path) + { + AtlasRegion region = FindRegion(path); + if (region == null) throw new Exception("Region not found in atlas: " + path + " (mesh attachment: " + name + ")"); + MeshAttachment attachment = new MeshAttachment(name); + attachment.RendererObject = region; + attachment.RegionU = region.u; + attachment.RegionV = region.v; + attachment.RegionU2 = region.u2; + attachment.RegionV2 = region.v2; + attachment.RegionRotate = region.rotate; + attachment.regionOffsetX = region.offsetX; + attachment.regionOffsetY = region.offsetY; + attachment.regionWidth = region.width; + attachment.regionHeight = region.height; + attachment.regionOriginalWidth = region.originalWidth; + attachment.regionOriginalHeight = region.originalHeight; + return attachment; + } - public WeightedMeshAttachment NewWeightedMeshAttachment (Skin skin, String name, String path) { - AtlasRegion region = FindRegion(path); - if (region == null) throw new Exception("Region not found in atlas: " + path + " (weighted mesh attachment: " + name + ")"); - WeightedMeshAttachment attachment = new WeightedMeshAttachment(name); - attachment.RendererObject = region; - attachment.RegionU = region.u; - attachment.RegionV = region.v; - attachment.RegionU2 = region.u2; - attachment.RegionV2 = region.v2; - attachment.RegionRotate = region.rotate; - attachment.regionOffsetX = region.offsetX; - attachment.regionOffsetY = region.offsetY; - attachment.regionWidth = region.width; - attachment.regionHeight = region.height; - attachment.regionOriginalWidth = region.originalWidth; - attachment.regionOriginalHeight = region.originalHeight; - return attachment; - } + public WeightedMeshAttachment NewWeightedMeshAttachment(Skin skin, String name, String path) + { + AtlasRegion region = FindRegion(path); + if (region == null) throw new Exception("Region not found in atlas: " + path + " (weighted mesh attachment: " + name + ")"); + WeightedMeshAttachment attachment = new WeightedMeshAttachment(name); + attachment.RendererObject = region; + attachment.RegionU = region.u; + attachment.RegionV = region.v; + attachment.RegionU2 = region.u2; + attachment.RegionV2 = region.v2; + attachment.RegionRotate = region.rotate; + attachment.regionOffsetX = region.offsetX; + attachment.regionOffsetY = region.offsetY; + attachment.regionWidth = region.width; + attachment.regionHeight = region.height; + attachment.regionOriginalWidth = region.originalWidth; + attachment.regionOriginalHeight = region.originalHeight; + return attachment; + } - public BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, String name) { - return new BoundingBoxAttachment(name); - } + public BoundingBoxAttachment NewBoundingBoxAttachment(Skin skin, String name) + { + return new BoundingBoxAttachment(name); + } - public AtlasRegion FindRegion (string name) { - AtlasRegion region; + public AtlasRegion FindRegion(string name) + { + AtlasRegion region; - for (int i = 0; i < atlasArray.Length; i++) { - region = atlasArray[i].FindRegion(name); - if (region != null) - return region; - } + for (int i = 0; i < atlasArray.Length; i++) + { + region = atlasArray[i].FindRegion(name); + if (region != null) + return region; + } - return null; - } - } + return null; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Attachments/Attachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Attachments/Attachment.cs index 69b5aba..b1337a6 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Attachments/Attachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Attachments/Attachment.cs @@ -31,17 +31,21 @@ using System; -namespace Spine3_1_07 { - abstract public class Attachment { - public String Name { get; private set; } +namespace Spine3_1_07 +{ + abstract public class Attachment + { + public String Name { get; private set; } - public Attachment (String name) { - if (name == null) throw new ArgumentNullException("name cannot be null."); - Name = name; - } + public Attachment(String name) + { + if (name == null) throw new ArgumentNullException("name cannot be null."); + Name = name; + } - override public String ToString () { - return Name; - } - } + override public String ToString() + { + return Name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Attachments/AttachmentLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Attachments/AttachmentLoader.cs index 820061f..4d510dc 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Attachments/AttachmentLoader.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Attachments/AttachmentLoader.cs @@ -31,18 +31,20 @@ using System; -namespace Spine3_1_07 { - public interface AttachmentLoader { - /// May be null to not load any attachment. - RegionAttachment NewRegionAttachment (Skin skin, String name, String path); +namespace Spine3_1_07 +{ + public interface AttachmentLoader + { + /// May be null to not load any attachment. + RegionAttachment NewRegionAttachment(Skin skin, String name, String path); - /// May be null to not load any attachment. - MeshAttachment NewMeshAttachment (Skin skin, String name, String path); + /// May be null to not load any attachment. + MeshAttachment NewMeshAttachment(Skin skin, String name, String path); - /// May be null to not load any attachment. - WeightedMeshAttachment NewWeightedMeshAttachment (Skin skin, String name, String path); + /// May be null to not load any attachment. + WeightedMeshAttachment NewWeightedMeshAttachment(Skin skin, String name, String path); - /// May be null to not load any attachment. - BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, String name); - } + /// May be null to not load any attachment. + BoundingBoxAttachment NewBoundingBoxAttachment(Skin skin, String name); + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Attachments/AttachmentType.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Attachments/AttachmentType.cs index 733e596..4b30403 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Attachments/AttachmentType.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Attachments/AttachmentType.cs @@ -29,8 +29,10 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine3_1_07 { - public enum AttachmentType { - region, boundingbox, mesh, weightedmesh, linkedmesh, weightedlinkedmesh - } +namespace Spine3_1_07 +{ + public enum AttachmentType + { + region, boundingbox, mesh, weightedmesh, linkedmesh, weightedlinkedmesh + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Attachments/BoundingBoxAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Attachments/BoundingBoxAttachment.cs index eabfeb2..f726666 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Attachments/BoundingBoxAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Attachments/BoundingBoxAttachment.cs @@ -29,33 +29,36 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; +namespace Spine3_1_07 +{ + /// Attachment that has a polygon for bounds checking. + public class BoundingBoxAttachment : Attachment + { + internal float[] vertices; -namespace Spine3_1_07 { - /// Attachment that has a polygon for bounds checking. - public class BoundingBoxAttachment : Attachment { - internal float[] vertices; + public float[] Vertices { get { return vertices; } set { vertices = value; } } - public float[] Vertices { get { return vertices; } set { vertices = value; } } + public BoundingBoxAttachment(string name) + : base(name) + { + } - public BoundingBoxAttachment (string name) - : base(name) { - } - - /// Must have at least the same length as this attachment's vertices. - public void ComputeWorldVertices (Bone bone, float[] worldVertices) { - float x = bone.skeleton.x + bone.worldX, y = bone.skeleton.y + bone.worldY; - float m00 = bone.a; - float m01 = bone.b; - float m10 = bone.c; - float m11 = bone.d; - float[] vertices = this.vertices; - for (int i = 0, n = vertices.Length; i < n; i += 2) { - float px = vertices[i]; - float py = vertices[i + 1]; - worldVertices[i] = px * m00 + py * m01 + x; - worldVertices[i + 1] = px * m10 + py * m11 + y; - } - } - } + /// Must have at least the same length as this attachment's vertices. + public void ComputeWorldVertices(Bone bone, float[] worldVertices) + { + float x = bone.skeleton.x + bone.worldX, y = bone.skeleton.y + bone.worldY; + float m00 = bone.a; + float m01 = bone.b; + float m10 = bone.c; + float m11 = bone.d; + float[] vertices = this.vertices; + for (int i = 0, n = vertices.Length; i < n; i += 2) + { + float px = vertices[i]; + float py = vertices[i + 1]; + worldVertices[i] = px * m00 + py * m01 + x; + worldVertices[i + 1] = px * m10 + py * m11 + y; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Attachments/IFfdAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Attachments/IFfdAttachment.cs index c5eacbf..a80b3df 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Attachments/IFfdAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Attachments/IFfdAttachment.cs @@ -29,8 +29,10 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine3_1_07 { - public interface IFfdAttachment { - bool ApplyFFD (Attachment sourceAttachment); - } +namespace Spine3_1_07 +{ + public interface IFfdAttachment + { + bool ApplyFFD(Attachment sourceAttachment); + } } \ No newline at end of file diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Attachments/MeshAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Attachments/MeshAttachment.cs index c3fea6d..c1bf63d 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Attachments/MeshAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Attachments/MeshAttachment.cs @@ -31,103 +31,118 @@ using System; -namespace Spine3_1_07 { - /// Attachment that displays a texture region using a mesh. - public class MeshAttachment : Attachment, IFfdAttachment { - internal float[] vertices, uvs, regionUVs; - internal int[] triangles; - internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; - internal float r = 1, g = 1, b = 1, a = 1; - internal MeshAttachment parentMesh; - internal bool inheritFFD; +namespace Spine3_1_07 +{ + /// Attachment that displays a texture region using a mesh. + public class MeshAttachment : Attachment, IFfdAttachment + { + internal float[] vertices, uvs, regionUVs; + internal int[] triangles; + internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; + internal float r = 1, g = 1, b = 1, a = 1; + internal MeshAttachment parentMesh; + internal bool inheritFFD; - public int HullLength { get; set; } - public float[] Vertices { get { return vertices; } set { vertices = value; } } - public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } } - public float[] UVs { get { return uvs; } set { uvs = value; } } - public int[] Triangles { get { return triangles; } set { triangles = value; } } + public int HullLength { get; set; } + public float[] Vertices { get { return vertices; } set { vertices = value; } } + public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } } + public float[] UVs { get { return uvs; } set { uvs = value; } } + public int[] Triangles { get { return triangles; } set { triangles = value; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } - public String Path { get; set; } - public Object RendererObject { get; set; } - public float RegionU { get; set; } - public float RegionV { get; set; } - public float RegionU2 { get; set; } - public float RegionV2 { get; set; } - public bool RegionRotate { get; set; } - public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } - public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. - public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } - public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. - public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } - public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. + public String Path { get; set; } + public Object RendererObject { get; set; } + public float RegionU { get; set; } + public float RegionV { get; set; } + public float RegionU2 { get; set; } + public float RegionV2 { get; set; } + public bool RegionRotate { get; set; } + public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } + public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. + public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } + public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. + public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } + public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. - public bool InheritFFD { get { return inheritFFD; } set { inheritFFD = value; } } + public bool InheritFFD { get { return inheritFFD; } set { inheritFFD = value; } } - public MeshAttachment ParentMesh { - get { return parentMesh; } - set { - parentMesh = value; - if (value != null) { - vertices = value.vertices; - regionUVs = value.regionUVs; - triangles = value.triangles; - HullLength = value.HullLength; - Edges = value.Edges; - Width = value.Width; - Height = value.Height; - } - } - } + public MeshAttachment ParentMesh + { + get { return parentMesh; } + set + { + parentMesh = value; + if (value != null) + { + vertices = value.vertices; + regionUVs = value.regionUVs; + triangles = value.triangles; + HullLength = value.HullLength; + Edges = value.Edges; + Width = value.Width; + Height = value.Height; + } + } + } - // Nonessential. - public int[] Edges { get; set; } - public float Width { get; set; } - public float Height { get; set; } + // Nonessential. + public int[] Edges { get; set; } + public float Width { get; set; } + public float Height { get; set; } - public MeshAttachment (string name) - : base(name) { - } + public MeshAttachment(string name) + : base(name) + { + } - public void UpdateUVs () { - float u = RegionU, v = RegionV, width = RegionU2 - RegionU, height = RegionV2 - RegionV; - float[] regionUVs = this.regionUVs; - if (this.uvs == null || this.uvs.Length != regionUVs.Length) this.uvs = new float[regionUVs.Length]; - float[] uvs = this.uvs; - if (RegionRotate) { - for (int i = 0, n = uvs.Length; i < n; i += 2) { - uvs[i] = u + regionUVs[i + 1] * width; - uvs[i + 1] = v + height - regionUVs[i] * height; - } - } else { - for (int i = 0, n = uvs.Length; i < n; i += 2) { - uvs[i] = u + regionUVs[i] * width; - uvs[i + 1] = v + regionUVs[i + 1] * height; - } - } - } + public void UpdateUVs() + { + float u = RegionU, v = RegionV, width = RegionU2 - RegionU, height = RegionV2 - RegionV; + float[] regionUVs = this.regionUVs; + if (this.uvs == null || this.uvs.Length != regionUVs.Length) this.uvs = new float[regionUVs.Length]; + float[] uvs = this.uvs; + if (RegionRotate) + { + for (int i = 0, n = uvs.Length; i < n; i += 2) + { + uvs[i] = u + regionUVs[i + 1] * width; + uvs[i + 1] = v + height - regionUVs[i] * height; + } + } + else + { + for (int i = 0, n = uvs.Length; i < n; i += 2) + { + uvs[i] = u + regionUVs[i] * width; + uvs[i + 1] = v + regionUVs[i + 1] * height; + } + } + } - public void ComputeWorldVertices (Slot slot, float[] worldVertices) { - Bone bone = slot.bone; - float x = bone.skeleton.x + bone.worldX, y = bone.skeleton.y + bone.worldY; - float m00 = bone.a, m01 = bone.b, m10 = bone.c, m11 = bone.d; - float[] vertices = this.vertices; - int verticesCount = vertices.Length; - if (slot.attachmentVerticesCount == verticesCount) vertices = slot.AttachmentVertices; - for (int i = 0; i < verticesCount; i += 2) { - float vx = vertices[i]; - float vy = vertices[i + 1]; - worldVertices[i] = vx * m00 + vy * m01 + x; - worldVertices[i + 1] = vx * m10 + vy * m11 + y; - } - } + public void ComputeWorldVertices(Slot slot, float[] worldVertices) + { + Bone bone = slot.bone; + float x = bone.skeleton.x + bone.worldX, y = bone.skeleton.y + bone.worldY; + float m00 = bone.a, m01 = bone.b, m10 = bone.c, m11 = bone.d; + float[] vertices = this.vertices; + int verticesCount = vertices.Length; + if (slot.attachmentVerticesCount == verticesCount) vertices = slot.AttachmentVertices; + for (int i = 0; i < verticesCount; i += 2) + { + float vx = vertices[i]; + float vy = vertices[i + 1]; + worldVertices[i] = vx * m00 + vy * m01 + x; + worldVertices[i + 1] = vx * m10 + vy * m11 + y; + } + } - public bool ApplyFFD (Attachment sourceAttachment) { - return this == sourceAttachment || (inheritFFD && parentMesh == sourceAttachment); - } - } + public bool ApplyFFD(Attachment sourceAttachment) + { + return this == sourceAttachment || (inheritFFD && parentMesh == sourceAttachment); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Attachments/RegionAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Attachments/RegionAttachment.cs index 18bbff3..299c997 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Attachments/RegionAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Attachments/RegionAttachment.cs @@ -31,122 +31,131 @@ using System; -namespace Spine3_1_07 { - /// Attachment that displays a texture region. - public class RegionAttachment : Attachment { - public const int X1 = 0; - public const int Y1 = 1; - public const int X2 = 2; - public const int Y2 = 3; - public const int X3 = 4; - public const int Y3 = 5; - public const int X4 = 6; - public const int Y4 = 7; +namespace Spine3_1_07 +{ + /// Attachment that displays a texture region. + public class RegionAttachment : Attachment + { + public const int X1 = 0; + public const int Y1 = 1; + public const int X2 = 2; + public const int Y2 = 3; + public const int X3 = 4; + public const int Y3 = 5; + public const int X4 = 6; + public const int Y4 = 7; - internal float x, y, rotation, scaleX = 1, scaleY = 1, width, height; - internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; - internal float[] offset = new float[8], uvs = new float[8]; - internal float r = 1, g = 1, b = 1, a = 1; + internal float x, y, rotation, scaleX = 1, scaleY = 1, width, height; + internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; + internal float[] offset = new float[8], uvs = new float[8]; + internal float r = 1, g = 1, b = 1, a = 1; - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public float Rotation { get { return rotation; } set { rotation = value; } } - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - public float ScaleY { get { return scaleY; } set { scaleY = value; } } - public float Width { get { return width; } set { width = value; } } - public float Height { get { return height; } set { height = value; } } + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public float Rotation { get { return rotation; } set { rotation = value; } } + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + public float Width { get { return width; } set { width = value; } } + public float Height { get { return height; } set { height = value; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } - public String Path { get; set; } - public Object RendererObject { get; set; } - public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } - public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. - public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } - public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. - public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } - public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. + public String Path { get; set; } + public Object RendererObject { get; set; } + public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } + public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. + public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } + public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. + public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } + public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. - public float[] Offset { get { return offset; } } - public float[] UVs { get { return uvs; } } + public float[] Offset { get { return offset; } } + public float[] UVs { get { return uvs; } } - public RegionAttachment (string name) - : base(name) { - } + public RegionAttachment(string name) + : base(name) + { + } - public void SetUVs (float u, float v, float u2, float v2, bool rotate) { - float[] uvs = this.uvs; - if (rotate) { - uvs[X2] = u; - uvs[Y2] = v2; - uvs[X3] = u; - uvs[Y3] = v; - uvs[X4] = u2; - uvs[Y4] = v; - uvs[X1] = u2; - uvs[Y1] = v2; - } else { - uvs[X1] = u; - uvs[Y1] = v2; - uvs[X2] = u; - uvs[Y2] = v; - uvs[X3] = u2; - uvs[Y3] = v; - uvs[X4] = u2; - uvs[Y4] = v2; - } - } + public void SetUVs(float u, float v, float u2, float v2, bool rotate) + { + float[] uvs = this.uvs; + if (rotate) + { + uvs[X2] = u; + uvs[Y2] = v2; + uvs[X3] = u; + uvs[Y3] = v; + uvs[X4] = u2; + uvs[Y4] = v; + uvs[X1] = u2; + uvs[Y1] = v2; + } + else + { + uvs[X1] = u; + uvs[Y1] = v2; + uvs[X2] = u; + uvs[Y2] = v; + uvs[X3] = u2; + uvs[Y3] = v; + uvs[X4] = u2; + uvs[Y4] = v2; + } + } - public void UpdateOffset () { - float width = this.width; - float height = this.height; - float scaleX = this.scaleX; - float scaleY = this.scaleY; - float regionScaleX = width / regionOriginalWidth * scaleX; - float regionScaleY = height / regionOriginalHeight * scaleY; - float localX = -width / 2 * scaleX + regionOffsetX * regionScaleX; - float localY = -height / 2 * scaleY + regionOffsetY * regionScaleY; - float localX2 = localX + regionWidth * regionScaleX; - float localY2 = localY + regionHeight * regionScaleY; - float radians = rotation * (float)Math.PI / 180; - float cos = (float)Math.Cos(radians); - float sin = (float)Math.Sin(radians); - float x = this.x; - float y = this.y; - 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; - float[] offset = this.offset; - offset[X1] = localXCos - localYSin; - offset[Y1] = localYCos + localXSin; - offset[X2] = localXCos - localY2Sin; - offset[Y2] = localY2Cos + localXSin; - offset[X3] = localX2Cos - localY2Sin; - offset[Y3] = localY2Cos + localX2Sin; - offset[X4] = localX2Cos - localYSin; - offset[Y4] = localYCos + localX2Sin; - } + public void UpdateOffset() + { + float width = this.width; + float height = this.height; + float scaleX = this.scaleX; + float scaleY = this.scaleY; + float regionScaleX = width / regionOriginalWidth * scaleX; + float regionScaleY = height / regionOriginalHeight * scaleY; + float localX = -width / 2 * scaleX + regionOffsetX * regionScaleX; + float localY = -height / 2 * scaleY + regionOffsetY * regionScaleY; + float localX2 = localX + regionWidth * regionScaleX; + float localY2 = localY + regionHeight * regionScaleY; + float radians = rotation * (float)Math.PI / 180; + float cos = (float)Math.Cos(radians); + float sin = (float)Math.Sin(radians); + float x = this.x; + float y = this.y; + 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; + float[] offset = this.offset; + offset[X1] = localXCos - localYSin; + offset[Y1] = localYCos + localXSin; + offset[X2] = localXCos - localY2Sin; + offset[Y2] = localY2Cos + localXSin; + offset[X3] = localX2Cos - localY2Sin; + offset[Y3] = localY2Cos + localX2Sin; + offset[X4] = localX2Cos - localYSin; + offset[Y4] = localYCos + localX2Sin; + } - public void ComputeWorldVertices (Bone bone, float[] worldVertices) { - float x = bone.skeleton.x + bone.worldX, y = bone.skeleton.y + bone.worldY; - float m00 = bone.a, m01 = bone.b, m10 = bone.c, m11 = bone.d; - float[] offset = this.offset; - worldVertices[X1] = offset[X1] * m00 + offset[Y1] * m01 + x; - worldVertices[Y1] = offset[X1] * m10 + offset[Y1] * m11 + y; - worldVertices[X2] = offset[X2] * m00 + offset[Y2] * m01 + x; - worldVertices[Y2] = offset[X2] * m10 + offset[Y2] * m11 + y; - worldVertices[X3] = offset[X3] * m00 + offset[Y3] * m01 + x; - worldVertices[Y3] = offset[X3] * m10 + offset[Y3] * m11 + y; - worldVertices[X4] = offset[X4] * m00 + offset[Y4] * m01 + x; - worldVertices[Y4] = offset[X4] * m10 + offset[Y4] * m11 + y; - } - } + public void ComputeWorldVertices(Bone bone, float[] worldVertices) + { + float x = bone.skeleton.x + bone.worldX, y = bone.skeleton.y + bone.worldY; + float m00 = bone.a, m01 = bone.b, m10 = bone.c, m11 = bone.d; + float[] offset = this.offset; + worldVertices[X1] = offset[X1] * m00 + offset[Y1] * m01 + x; + worldVertices[Y1] = offset[X1] * m10 + offset[Y1] * m11 + y; + worldVertices[X2] = offset[X2] * m00 + offset[Y2] * m01 + x; + worldVertices[Y2] = offset[X2] * m10 + offset[Y2] * m11 + y; + worldVertices[X3] = offset[X3] * m00 + offset[Y3] * m01 + x; + worldVertices[Y3] = offset[X3] * m10 + offset[Y3] * m11 + y; + worldVertices[X4] = offset[X4] * m00 + offset[Y4] * m01 + x; + worldVertices[Y4] = offset[X4] * m10 + offset[Y4] * m11 + y; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Attachments/WeightedMeshAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Attachments/WeightedMeshAttachment.cs index 8b882e3..2f0f63d 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Attachments/WeightedMeshAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Attachments/WeightedMeshAttachment.cs @@ -30,129 +30,149 @@ *****************************************************************************/ using System; -using System.Collections.Generic; -namespace Spine3_1_07 { - /// Attachment that displays a texture region using a mesh which can be deformed by bones. - public class WeightedMeshAttachment : Attachment, IFfdAttachment { - internal int[] bones; - internal float[] weights, uvs, regionUVs; - internal int[] triangles; - internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; - internal float r = 1, g = 1, b = 1, a = 1; - internal WeightedMeshAttachment parentMesh; - internal bool inheritFFD; +namespace Spine3_1_07 +{ + /// Attachment that displays a texture region using a mesh which can be deformed by bones. + public class WeightedMeshAttachment : Attachment, IFfdAttachment + { + internal int[] bones; + internal float[] weights, uvs, regionUVs; + internal int[] triangles; + internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; + internal float r = 1, g = 1, b = 1, a = 1; + internal WeightedMeshAttachment parentMesh; + internal bool inheritFFD; - public int HullLength { get; set; } - public int[] Bones { get { return bones; } set { bones = value; } } - public float[] Weights { get { return weights; } set { weights = value; } } - public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } } - public float[] UVs { get { return uvs; } set { uvs = value; } } - public int[] Triangles { get { return triangles; } set { triangles = value; } } + public int HullLength { get; set; } + public int[] Bones { get { return bones; } set { bones = value; } } + public float[] Weights { get { return weights; } set { weights = value; } } + public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } } + public float[] UVs { get { return uvs; } set { uvs = value; } } + public int[] Triangles { get { return triangles; } set { triangles = value; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } - public String Path { get; set; } - public Object RendererObject { get; set; } - public float RegionU { get; set; } - public float RegionV { get; set; } - public float RegionU2 { get; set; } - public float RegionV2 { get; set; } - public bool RegionRotate { get; set; } - public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } - public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. - public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } - public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. - public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } - public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. + public String Path { get; set; } + public Object RendererObject { get; set; } + public float RegionU { get; set; } + public float RegionV { get; set; } + public float RegionU2 { get; set; } + public float RegionV2 { get; set; } + public bool RegionRotate { get; set; } + public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } + public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. + public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } + public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. + public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } + public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. - public bool InheritFFD { get { return inheritFFD; } set { inheritFFD = value; } } + public bool InheritFFD { get { return inheritFFD; } set { inheritFFD = value; } } - public WeightedMeshAttachment ParentMesh { - get { return parentMesh; } - set { - parentMesh = value; - if (value != null) { - bones = value.bones; - weights = value.weights; - regionUVs = value.regionUVs; - triangles = value.triangles; - HullLength = value.HullLength; - Edges = value.Edges; - Width = value.Width; - Height = value.Height; - } - } - } + public WeightedMeshAttachment ParentMesh + { + get { return parentMesh; } + set + { + parentMesh = value; + if (value != null) + { + bones = value.bones; + weights = value.weights; + regionUVs = value.regionUVs; + triangles = value.triangles; + HullLength = value.HullLength; + Edges = value.Edges; + Width = value.Width; + Height = value.Height; + } + } + } - // Nonessential. - public int[] Edges { get; set; } - public float Width { get; set; } - public float Height { get; set; } + // Nonessential. + public int[] Edges { get; set; } + public float Width { get; set; } + public float Height { get; set; } - public WeightedMeshAttachment (string name) - : base(name) { - } + public WeightedMeshAttachment(string name) + : base(name) + { + } - public void UpdateUVs () { - float u = RegionU, v = RegionV, width = RegionU2 - RegionU, height = RegionV2 - RegionV; - float[] regionUVs = this.regionUVs; - if (this.uvs == null || this.uvs.Length != regionUVs.Length) this.uvs = new float[regionUVs.Length]; - float[] uvs = this.uvs; - if (RegionRotate) { - for (int i = 0, n = uvs.Length; i < n; i += 2) { - uvs[i] = u + regionUVs[i + 1] * width; - uvs[i + 1] = v + height - regionUVs[i] * height; - } - } else { - for (int i = 0, n = uvs.Length; i < n; i += 2) { - uvs[i] = u + regionUVs[i] * width; - uvs[i + 1] = v + regionUVs[i + 1] * height; - } - } - } + public void UpdateUVs() + { + float u = RegionU, v = RegionV, width = RegionU2 - RegionU, height = RegionV2 - RegionV; + float[] regionUVs = this.regionUVs; + if (this.uvs == null || this.uvs.Length != regionUVs.Length) this.uvs = new float[regionUVs.Length]; + float[] uvs = this.uvs; + if (RegionRotate) + { + for (int i = 0, n = uvs.Length; i < n; i += 2) + { + uvs[i] = u + regionUVs[i + 1] * width; + uvs[i + 1] = v + height - regionUVs[i] * height; + } + } + else + { + for (int i = 0, n = uvs.Length; i < n; i += 2) + { + uvs[i] = u + regionUVs[i] * width; + uvs[i + 1] = v + regionUVs[i + 1] * height; + } + } + } - public void ComputeWorldVertices (Slot slot, float[] worldVertices) { - Skeleton skeleton = slot.bone.skeleton; - ExposedList skeletonBones = skeleton.bones; - float x = skeleton.x, y = skeleton.y; - float[] weights = this.weights; - int[] bones = this.bones; - if (slot.attachmentVerticesCount == 0) { - for (int w = 0, v = 0, b = 0, n = bones.Length; v < n; w += 2) { - float wx = 0, wy = 0; - int nn = bones[v++] + v; - for (; v < nn; v++, b += 3) { - Bone bone = skeletonBones.Items[bones[v]]; - float vx = weights[b], vy = weights[b + 1], weight = weights[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 + x; - worldVertices[w + 1] = wy + y; - } - } else { - float[] ffd = slot.attachmentVertices; - for (int w = 0, v = 0, b = 0, f = 0, n = bones.Length; v < n; w += 2) { - float wx = 0, wy = 0; - int nn = bones[v++] + v; - for (; v < nn; v++, b += 3, f += 2) { - Bone bone = skeletonBones.Items[bones[v]]; - float vx = weights[b] + ffd[f], vy = weights[b + 1] + ffd[f + 1], weight = weights[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 + x; - worldVertices[w + 1] = wy + y; - } - } - } + public void ComputeWorldVertices(Slot slot, float[] worldVertices) + { + Skeleton skeleton = slot.bone.skeleton; + ExposedList skeletonBones = skeleton.bones; + float x = skeleton.x, y = skeleton.y; + float[] weights = this.weights; + int[] bones = this.bones; + if (slot.attachmentVerticesCount == 0) + { + for (int w = 0, v = 0, b = 0, n = bones.Length; v < n; w += 2) + { + float wx = 0, wy = 0; + int nn = bones[v++] + v; + for (; v < nn; v++, b += 3) + { + Bone bone = skeletonBones.Items[bones[v]]; + float vx = weights[b], vy = weights[b + 1], weight = weights[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 + x; + worldVertices[w + 1] = wy + y; + } + } + else + { + float[] ffd = slot.attachmentVertices; + for (int w = 0, v = 0, b = 0, f = 0, n = bones.Length; v < n; w += 2) + { + float wx = 0, wy = 0; + int nn = bones[v++] + v; + for (; v < nn; v++, b += 3, f += 2) + { + Bone bone = skeletonBones.Items[bones[v]]; + float vx = weights[b] + ffd[f], vy = weights[b + 1] + ffd[f + 1], weight = weights[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 + x; + worldVertices[w + 1] = wy + y; + } + } + } - public bool ApplyFFD (Attachment sourceAttachment) { - return this == sourceAttachment || (inheritFFD && parentMesh == sourceAttachment); - } - } + public bool ApplyFFD(Attachment sourceAttachment) + { + return this == sourceAttachment || (inheritFFD && parentMesh == sourceAttachment); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/BlendMode.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/BlendMode.cs index 0e73767..0927e83 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/BlendMode.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/BlendMode.cs @@ -29,8 +29,10 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine3_1_07 { - public enum BlendMode { - normal, additive, multiply, screen - } +namespace Spine3_1_07 +{ + public enum BlendMode + { + normal, additive, multiply, screen + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Bone.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Bone.cs index 6f5dc4b..b0b8c29 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Bone.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Bone.cs @@ -30,214 +30,238 @@ *****************************************************************************/ using System; -using System.Collections.Generic; - -namespace Spine3_1_07 { - public class Bone : IUpdatable { - static public bool yDown; - - internal BoneData data; - internal Skeleton skeleton; - internal Bone parent; - internal ExposedList children = new ExposedList(); - internal float x, y, rotation, scaleX, scaleY; - internal float appliedRotation, appliedScaleX, appliedScaleY; - - internal float a, b, worldX; - internal float c, d, worldY; - internal float worldSignX, worldSignY; - - public BoneData Data { get { return data; } } - public Skeleton Skeleton { get { return skeleton; } } - public Bone Parent { get { return parent; } } - public ExposedList Children { get { return children; } } - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public float Rotation { get { return rotation; } set { rotation = value; } } - /// The rotation, as calculated by any constraints. - public float AppliedRotation { get { return appliedRotation; } set { appliedRotation = value; } } - /// The scale X, as calculated by any constraints. - public float AppliedScaleX { get { return appliedScaleX; } set { appliedScaleX = value; } } - /// The scale Y, as calculated by any constraints. - public float AppliedScaleY { get { return appliedScaleY; } set { appliedScaleY = value; } } - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - public float ScaleY { get { return scaleY; } set { scaleY = value; } } - - public float A { get { return a; } } - public float B { get { return b; } } - public float C { get { return c; } } - public float D { get { return d; } } - public float WorldX { get { return worldX; } } - public float WorldY { get { return worldY; } } - public float WorldSignX { get { return worldSignX; } } - public float WorldSignY { get { return worldSignY; } } - public float WorldRotationX { get { return MathUtils.Atan2(c, a) * MathUtils.radDeg; } } - public float WorldRotationY { get { return MathUtils.Atan2(d, b) * MathUtils.radDeg; } } - public float WorldScaleX { get { return (float)Math.Sqrt(a * a + b * b) * worldSignX; } } - public float WorldScaleY { get { return (float)Math.Sqrt(c * c + d * d) * worldSignY; } } - - /// May be null. - public Bone (BoneData data, Skeleton skeleton, Bone parent) { - if (data == null) throw new ArgumentNullException("data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); - this.data = data; - this.skeleton = skeleton; - this.parent = parent; - SetToSetupPose(); - } - - /// Same as {@link #updateWorldTransform()}. This method exists for Bone to implement {@link Updatable}. - public void Update () { - UpdateWorldTransform(x, y, rotation, scaleX, scaleY); - } - - /// Computes the world SRT using the parent bone and this bone's local SRT. - public void UpdateWorldTransform () { - UpdateWorldTransform(x, y, rotation, scaleX, scaleY); - } - - /// Computes the world SRT using the parent bone and the specified local SRT. - public void UpdateWorldTransform (float x, float y, float rotation, float scaleX, float scaleY) { - appliedRotation = rotation; - appliedScaleX = scaleX; - appliedScaleY = scaleY; - - float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation); - float la = cos * scaleX, lb = -sin * scaleY, lc = sin * scaleX, ld = cos * scaleY; - Bone parent = this.parent; - if (parent == null) { // Root bone. - Skeleton skeleton = this.skeleton; - if (skeleton.flipX) { - x = -x; - la = -la; - lb = -lb; - } - if (skeleton.flipY != yDown) { - y = -y; - lc = -lc; - ld = -ld; - } - a = la; - b = lb; - c = lc; - d = ld; - worldX = x; - worldY = y; - worldSignX = Math.Sign(scaleX); - worldSignY = Math.Sign(scaleY); - return; - } - - float 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; - worldSignX = parent.worldSignX * Math.Sign(scaleX); - worldSignY = parent.worldSignY * Math.Sign(scaleY); - - if (data.inheritRotation && data.inheritScale) { - a = pa * la + pb * lc; - b = pa * lb + pb * ld; - c = pc * la + pd * lc; - d = pc * lb + pd * ld; - } else { - if (data.inheritRotation) { // No scale inheritance. - pa = 1; - pb = 0; - pc = 0; - pd = 1; - do { - cos = MathUtils.CosDeg(parent.appliedRotation); - sin = MathUtils.SinDeg(parent.appliedRotation); - float temp = pa * cos + pb * sin; - pb = pa * -sin + pb * cos; - pa = temp; - temp = pc * cos + pd * sin; - pd = pc * -sin + pd * cos; - pc = temp; - - if (!parent.data.inheritRotation) break; - parent = parent.parent; - } while (parent != null); - a = pa * la + pb * lc; - b = pa * lb + pb * ld; - c = pc * la + pd * lc; - d = pc * lb + pd * ld; - } else if (data.inheritScale) { // No rotation inheritance. - pa = 1; - pb = 0; - pc = 0; - pd = 1; - do { - float r = parent.rotation; - cos = MathUtils.CosDeg(r); - sin = MathUtils.SinDeg(r); - float psx = parent.appliedScaleX, psy = parent.appliedScaleY; - float za = cos * psx, zb = -sin * psy, zc = sin * psx, zd = cos * psy; - float temp = pa * za + pb * zc; - pb = pa * zb + pb * zd; - pa = temp; - temp = pc * za + pd * zc; - pd = pc * zb + pd * zd; - pc = temp; - - if (psx < 0) r = -r; - cos = MathUtils.CosDeg(-r); - sin = MathUtils.SinDeg(-r); - temp = pa * cos + pb * sin; - pb = pa * -sin + pb * cos; - pa = temp; - temp = pc * cos + pd * sin; - pd = pc * -sin + pd * cos; - pc = temp; - - if (!parent.data.inheritScale) break; - parent = parent.parent; - } while (parent != null); - a = pa * la + pb * lc; - b = pa * lb + pb * ld; - c = pc * la + pd * lc; - d = pc * lb + pd * ld; - } else { - a = la; - b = lb; - c = lc; - d = ld; - } - if (skeleton.flipX) { - a = -a; - b = -b; - } - if (skeleton.flipY != yDown) { - c = -c; - d = -d; - } - } - } - - public void SetToSetupPose () { - BoneData data = this.data; - x = data.x; - y = data.y; - rotation = data.rotation; - scaleX = data.scaleX; - scaleY = data.scaleY; - } - - public void WorldToLocal (float worldX, float worldY, out float localX, out float localY) { - float x = worldX - this.worldX, y = worldY - this.worldY; - float a = this.a, b = this.b, c = this.c, d = this.d; - float invDet = 1 / (a * d - b * c); - localX = (x * d * invDet - y * b * invDet); - localY = (y * a * invDet - x * c * invDet); - } - - public void LocalToWorld (float localX, float localY, out float worldX, out float worldY) { - worldX = localX * a + localY * b + this.worldX; - worldY = localX * c + localY * d + this.worldY; - } - - override public String ToString () { - return data.name; - } - } + +namespace Spine3_1_07 +{ + public class Bone : IUpdatable + { + static public bool yDown; + + internal BoneData data; + internal Skeleton skeleton; + internal Bone parent; + internal ExposedList children = new ExposedList(); + internal float x, y, rotation, scaleX, scaleY; + internal float appliedRotation, appliedScaleX, appliedScaleY; + + internal float a, b, worldX; + internal float c, d, worldY; + internal float worldSignX, worldSignY; + + public BoneData Data { get { return data; } } + public Skeleton Skeleton { get { return skeleton; } } + public Bone Parent { get { return parent; } } + public ExposedList Children { get { return children; } } + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public float Rotation { get { return rotation; } set { rotation = value; } } + /// The rotation, as calculated by any constraints. + public float AppliedRotation { get { return appliedRotation; } set { appliedRotation = value; } } + /// The scale X, as calculated by any constraints. + public float AppliedScaleX { get { return appliedScaleX; } set { appliedScaleX = value; } } + /// The scale Y, as calculated by any constraints. + public float AppliedScaleY { get { return appliedScaleY; } set { appliedScaleY = value; } } + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + + public float A { get { return a; } } + public float B { get { return b; } } + public float C { get { return c; } } + public float D { get { return d; } } + public float WorldX { get { return worldX; } } + public float WorldY { get { return worldY; } } + public float WorldSignX { get { return worldSignX; } } + public float WorldSignY { get { return worldSignY; } } + public float WorldRotationX { get { return MathUtils.Atan2(c, a) * MathUtils.radDeg; } } + public float WorldRotationY { get { return MathUtils.Atan2(d, b) * MathUtils.radDeg; } } + public float WorldScaleX { get { return (float)Math.Sqrt(a * a + b * b) * worldSignX; } } + public float WorldScaleY { get { return (float)Math.Sqrt(c * c + d * d) * worldSignY; } } + + /// May be null. + public Bone(BoneData data, Skeleton skeleton, Bone parent) + { + if (data == null) throw new ArgumentNullException("data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); + this.data = data; + this.skeleton = skeleton; + this.parent = parent; + SetToSetupPose(); + } + + /// Same as {@link #updateWorldTransform()}. This method exists for Bone to implement {@link Updatable}. + public void Update() + { + UpdateWorldTransform(x, y, rotation, scaleX, scaleY); + } + + /// Computes the world SRT using the parent bone and this bone's local SRT. + public void UpdateWorldTransform() + { + UpdateWorldTransform(x, y, rotation, scaleX, scaleY); + } + + /// Computes the world SRT using the parent bone and the specified local SRT. + public void UpdateWorldTransform(float x, float y, float rotation, float scaleX, float scaleY) + { + appliedRotation = rotation; + appliedScaleX = scaleX; + appliedScaleY = scaleY; + + float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation); + float la = cos * scaleX, lb = -sin * scaleY, lc = sin * scaleX, ld = cos * scaleY; + Bone parent = this.parent; + if (parent == null) + { // Root bone. + Skeleton skeleton = this.skeleton; + if (skeleton.flipX) + { + x = -x; + la = -la; + lb = -lb; + } + if (skeleton.flipY != yDown) + { + y = -y; + lc = -lc; + ld = -ld; + } + a = la; + b = lb; + c = lc; + d = ld; + worldX = x; + worldY = y; + worldSignX = Math.Sign(scaleX); + worldSignY = Math.Sign(scaleY); + return; + } + + float 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; + worldSignX = parent.worldSignX * Math.Sign(scaleX); + worldSignY = parent.worldSignY * Math.Sign(scaleY); + + if (data.inheritRotation && data.inheritScale) + { + a = pa * la + pb * lc; + b = pa * lb + pb * ld; + c = pc * la + pd * lc; + d = pc * lb + pd * ld; + } + else + { + if (data.inheritRotation) + { // No scale inheritance. + pa = 1; + pb = 0; + pc = 0; + pd = 1; + do + { + cos = MathUtils.CosDeg(parent.appliedRotation); + sin = MathUtils.SinDeg(parent.appliedRotation); + float temp = pa * cos + pb * sin; + pb = pa * -sin + pb * cos; + pa = temp; + temp = pc * cos + pd * sin; + pd = pc * -sin + pd * cos; + pc = temp; + + if (!parent.data.inheritRotation) break; + parent = parent.parent; + } while (parent != null); + a = pa * la + pb * lc; + b = pa * lb + pb * ld; + c = pc * la + pd * lc; + d = pc * lb + pd * ld; + } + else if (data.inheritScale) + { // No rotation inheritance. + pa = 1; + pb = 0; + pc = 0; + pd = 1; + do + { + float r = parent.rotation; + cos = MathUtils.CosDeg(r); + sin = MathUtils.SinDeg(r); + float psx = parent.appliedScaleX, psy = parent.appliedScaleY; + float za = cos * psx, zb = -sin * psy, zc = sin * psx, zd = cos * psy; + float temp = pa * za + pb * zc; + pb = pa * zb + pb * zd; + pa = temp; + temp = pc * za + pd * zc; + pd = pc * zb + pd * zd; + pc = temp; + + if (psx < 0) r = -r; + cos = MathUtils.CosDeg(-r); + sin = MathUtils.SinDeg(-r); + temp = pa * cos + pb * sin; + pb = pa * -sin + pb * cos; + pa = temp; + temp = pc * cos + pd * sin; + pd = pc * -sin + pd * cos; + pc = temp; + + if (!parent.data.inheritScale) break; + parent = parent.parent; + } while (parent != null); + a = pa * la + pb * lc; + b = pa * lb + pb * ld; + c = pc * la + pd * lc; + d = pc * lb + pd * ld; + } + else + { + a = la; + b = lb; + c = lc; + d = ld; + } + if (skeleton.flipX) + { + a = -a; + b = -b; + } + if (skeleton.flipY != yDown) + { + c = -c; + d = -d; + } + } + } + + public void SetToSetupPose() + { + BoneData data = this.data; + x = data.x; + y = data.y; + rotation = data.rotation; + scaleX = data.scaleX; + scaleY = data.scaleY; + } + + public void WorldToLocal(float worldX, float worldY, out float localX, out float localY) + { + float x = worldX - this.worldX, y = worldY - this.worldY; + float a = this.a, b = this.b, c = this.c, d = this.d; + float invDet = 1 / (a * d - b * c); + localX = (x * d * invDet - y * b * invDet); + localY = (y * a * invDet - x * c * invDet); + } + + public void LocalToWorld(float localX, float localY, out float worldX, out float worldY) + { + worldX = localX * a + localY * b + this.worldX; + worldY = localX * c + localY * d + this.worldY; + } + + override public String ToString() + { + return data.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/BoneData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/BoneData.cs index b67d947..890f07a 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/BoneData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/BoneData.cs @@ -31,34 +31,38 @@ using System; -namespace Spine3_1_07 { - public class BoneData { - internal BoneData parent; - internal String name; - internal float length, x, y, rotation, scaleX = 1, scaleY = 1; - internal bool inheritScale = true, inheritRotation = true; +namespace Spine3_1_07 +{ + public class BoneData + { + internal BoneData parent; + internal String name; + internal float length, x, y, rotation, scaleX = 1, scaleY = 1; + internal bool inheritScale = true, inheritRotation = true; - /// May be null. - public BoneData Parent { get { return parent; } } - public String Name { get { return name; } } - public float Length { get { return length; } set { length = value; } } - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public float Rotation { get { return rotation; } set { rotation = value; } } - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - public float ScaleY { get { return scaleY; } set { scaleY = value; } } - public bool InheritScale { get { return inheritScale; } set { inheritScale = value; } } - public bool InheritRotation { get { return inheritRotation; } set { inheritRotation = value; } } + /// May be null. + public BoneData Parent { get { return parent; } } + public String Name { get { return name; } } + public float Length { get { return length; } set { length = value; } } + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public float Rotation { get { return rotation; } set { rotation = value; } } + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + public bool InheritScale { get { return inheritScale; } set { inheritScale = value; } } + public bool InheritRotation { get { return inheritRotation; } set { inheritRotation = value; } } - /// May be null. - public BoneData (String name, BoneData parent) { - if (name == null) throw new ArgumentNullException("name cannot be null."); - this.name = name; - this.parent = parent; - } + /// May be null. + public BoneData(String name, BoneData parent) + { + if (name == null) throw new ArgumentNullException("name cannot be null."); + this.name = name; + this.parent = parent; + } - override public String ToString () { - return name; - } - } + override public String ToString() + { + return name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Event.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Event.cs index a70864a..ff4c6fd 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Event.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Event.cs @@ -31,21 +31,25 @@ using System; -namespace Spine3_1_07 { - public class Event { - public EventData Data { get; private set; } - public int Int { get; set; } - public float Float { get; set; } - public String String { get; set; } - public float Time { get; private set; } +namespace Spine3_1_07 +{ + public class Event + { + public EventData Data { get; private set; } + public int Int { get; set; } + public float Float { get; set; } + public String String { get; set; } + public float Time { get; private set; } - public Event (float time, EventData data) { - Time = time; - Data = data; - } + public Event(float time, EventData data) + { + Time = time; + Data = data; + } - override public String ToString () { - return Data.Name; - } - } + override public String ToString() + { + return Data.Name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/EventData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/EventData.cs index 8821eb9..4a69c52 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/EventData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/EventData.cs @@ -31,22 +31,26 @@ using System; -namespace Spine3_1_07 { - public class EventData { - internal String name; +namespace Spine3_1_07 +{ + public class EventData + { + internal String name; - public String Name { get { return name; } } - public int Int { get; set; } - public float Float { get; set; } - public String String { get; set; } + public String Name { get { return name; } } + public int Int { get; set; } + public float Float { get; set; } + public String String { get; set; } - public EventData (String name) { - if (name == null) throw new ArgumentNullException("name cannot be null."); - this.name = name; - } + public EventData(String name) + { + if (name == null) throw new ArgumentNullException("name cannot be null."); + this.name = name; + } - override public String ToString () { - return Name; - } - } + override public String ToString() + { + return Name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/ExposedList.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/ExposedList.cs index 1db5b6b..335140a 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/ExposedList.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/ExposedList.cs @@ -35,549 +35,638 @@ using System.Collections.Generic; using System.Diagnostics; -namespace Spine3_1_07 { - [Serializable] - [DebuggerDisplay("Count={Count}")] - public class ExposedList : IEnumerable { - public T[] Items; - public int Count; - private const int DefaultCapacity = 4; - private static readonly T[] EmptyArray = new T[0]; - private int version; - - public ExposedList () { - Items = EmptyArray; - } - - public ExposedList (IEnumerable collection) { - CheckCollection(collection); - - // initialize to needed size (if determinable) - ICollection c = collection as ICollection; - if (c == null) { - Items = EmptyArray; - AddEnumerable(collection); - } else { - Items = new T[c.Count]; - AddCollection(c); - } - } - - public ExposedList (int capacity) { - if (capacity < 0) - throw new ArgumentOutOfRangeException("capacity"); - Items = new T[capacity]; - } - - internal ExposedList (T[] data, int size) { - Items = data; - Count = size; - } - - public void Add (T item) { - // If we check to see if we need to grow before trying to grow - // we can speed things up by 25% - if (Count == Items.Length) - GrowIfNeeded(1); - Items[Count++] = item; - version++; - } - - public void GrowIfNeeded (int newCount) { - int minimumSize = Count + newCount; - if (minimumSize > Items.Length) - Capacity = Math.Max(Math.Max(Capacity * 2, DefaultCapacity), minimumSize); - } - - private void CheckRange (int idx, int count) { - if (idx < 0) - throw new ArgumentOutOfRangeException("index"); - - if (count < 0) - throw new ArgumentOutOfRangeException("count"); - - if ((uint)idx + (uint)count > (uint)Count) - throw new ArgumentException("index and count exceed length of list"); - } - - private void AddCollection (ICollection collection) { - int collectionCount = collection.Count; - if (collectionCount == 0) - return; - - GrowIfNeeded(collectionCount); - collection.CopyTo(Items, Count); - Count += collectionCount; - } - - private void AddEnumerable (IEnumerable enumerable) { - foreach (T t in enumerable) { - Add(t); - } - } - - public void AddRange (IEnumerable collection) { - CheckCollection(collection); - - ICollection c = collection as ICollection; - if (c != null) - AddCollection(c); - else - AddEnumerable(collection); - version++; - } - - public int BinarySearch (T item) { - return Array.BinarySearch(Items, 0, Count, item); - } - - public int BinarySearch (T item, IComparer comparer) { - return Array.BinarySearch(Items, 0, Count, item, comparer); - } - - public int BinarySearch (int index, int count, T item, IComparer comparer) { - CheckRange(index, count); - return Array.BinarySearch(Items, index, count, item, comparer); - } - - public void Clear (bool clearArray = true) { - if (clearArray) - Array.Clear(Items, 0, Items.Length); - - Count = 0; - version++; - } - - public bool Contains (T item) { - return Array.IndexOf(Items, item, 0, Count) != -1; - } - - public ExposedList ConvertAll (Converter converter) { - if (converter == null) - throw new ArgumentNullException("converter"); - ExposedList u = new ExposedList(Count); - for (int i = 0; i < Count; i++) - u.Items[i] = converter(Items[i]); - - u.Count = Count; - return u; - } - - public void CopyTo (T[] array) { - Array.Copy(Items, 0, array, 0, Count); - } - - public void CopyTo (T[] array, int arrayIndex) { - Array.Copy(Items, 0, array, arrayIndex, Count); - } - - public void CopyTo (int index, T[] array, int arrayIndex, int count) { - CheckRange(index, count); - Array.Copy(Items, index, array, arrayIndex, count); - } - - public bool Exists (Predicate match) { - CheckMatch(match); - return GetIndex(0, Count, match) != -1; - } - - public T Find (Predicate match) { - CheckMatch(match); - int i = GetIndex(0, Count, match); - return (i != -1) ? Items[i] : default(T); - } - - private static void CheckMatch (Predicate match) { - if (match == null) - throw new ArgumentNullException("match"); - } - - public ExposedList FindAll (Predicate match) { - CheckMatch(match); - return FindAllList(match); - } - - private ExposedList FindAllList (Predicate match) { - ExposedList results = new ExposedList(); - for (int i = 0; i < Count; i++) - if (match(Items[i])) - results.Add(Items[i]); - - return results; - } - - public int FindIndex (Predicate match) { - CheckMatch(match); - return GetIndex(0, Count, match); - } - - public int FindIndex (int startIndex, Predicate match) { - CheckMatch(match); - CheckIndex(startIndex); - return GetIndex(startIndex, Count - startIndex, match); - } - - public int FindIndex (int startIndex, int count, Predicate match) { - CheckMatch(match); - CheckRange(startIndex, count); - return GetIndex(startIndex, count, match); - } - - private int GetIndex (int startIndex, int count, Predicate match) { - int end = startIndex + count; - for (int i = startIndex; i < end; i++) - if (match(Items[i])) - return i; - - return -1; - } - - public T FindLast (Predicate match) { - CheckMatch(match); - int i = GetLastIndex(0, Count, match); - return i == -1 ? default(T) : Items[i]; - } - - public int FindLastIndex (Predicate match) { - CheckMatch(match); - return GetLastIndex(0, Count, match); - } - - public int FindLastIndex (int startIndex, Predicate match) { - CheckMatch(match); - CheckIndex(startIndex); - return GetLastIndex(0, startIndex + 1, match); - } - - public int FindLastIndex (int startIndex, int count, Predicate match) { - CheckMatch(match); - int start = startIndex - count + 1; - CheckRange(start, count); - return GetLastIndex(start, count, match); - } - - private int GetLastIndex (int startIndex, int count, Predicate match) { - // unlike FindLastIndex, takes regular params for search range - for (int i = startIndex + count; i != startIndex; ) - if (match(Items[--i])) - return i; - return -1; - } - - public void ForEach (Action action) { - if (action == null) - throw new ArgumentNullException("action"); - for (int i = 0; i < Count; i++) - action(Items[i]); - } - - public Enumerator GetEnumerator () { - return new Enumerator(this); - } - - public ExposedList GetRange (int index, int count) { - CheckRange(index, count); - T[] tmpArray = new T[count]; - Array.Copy(Items, index, tmpArray, 0, count); - return new ExposedList(tmpArray, count); - } - - public int IndexOf (T item) { - return Array.IndexOf(Items, item, 0, Count); - } - - public int IndexOf (T item, int index) { - CheckIndex(index); - return Array.IndexOf(Items, item, index, Count - index); - } - - public int IndexOf (T item, int index, int count) { - if (index < 0) - throw new ArgumentOutOfRangeException("index"); - - if (count < 0) - throw new ArgumentOutOfRangeException("count"); - - if ((uint)index + (uint)count > (uint)Count) - throw new ArgumentOutOfRangeException("index and count exceed length of list"); - - return Array.IndexOf(Items, item, index, count); - } - - private void Shift (int start, int delta) { - if (delta < 0) - start -= delta; - - if (start < Count) - Array.Copy(Items, start, Items, start + delta, Count - start); - - Count += delta; - - if (delta < 0) - Array.Clear(Items, Count, -delta); - } - - private void CheckIndex (int index) { - if (index < 0 || (uint)index > (uint)Count) - throw new ArgumentOutOfRangeException("index"); - } - - public void Insert (int index, T item) { - CheckIndex(index); - if (Count == Items.Length) - GrowIfNeeded(1); - Shift(index, 1); - Items[index] = item; - version++; - } - - private void CheckCollection (IEnumerable collection) { - if (collection == null) - throw new ArgumentNullException("collection"); - } - - public void InsertRange (int index, IEnumerable collection) { - CheckCollection(collection); - CheckIndex(index); - if (collection == this) { - T[] buffer = new T[Count]; - CopyTo(buffer, 0); - GrowIfNeeded(Count); - Shift(index, buffer.Length); - Array.Copy(buffer, 0, Items, index, buffer.Length); - } else { - ICollection c = collection as ICollection; - if (c != null) - InsertCollection(index, c); - else - InsertEnumeration(index, collection); - } - version++; - } - - private void InsertCollection (int index, ICollection collection) { - int collectionCount = collection.Count; - GrowIfNeeded(collectionCount); - - Shift(index, collectionCount); - collection.CopyTo(Items, index); - } - - private void InsertEnumeration (int index, IEnumerable enumerable) { - foreach (T t in enumerable) - Insert(index++, t); - } - - public int LastIndexOf (T item) { - return Array.LastIndexOf(Items, item, Count - 1, Count); - } - - public int LastIndexOf (T item, int index) { - CheckIndex(index); - return Array.LastIndexOf(Items, item, index, index + 1); - } - - public int LastIndexOf (T item, int index, int count) { - if (index < 0) - throw new ArgumentOutOfRangeException("index", index, "index is negative"); - - if (count < 0) - throw new ArgumentOutOfRangeException("count", count, "count is negative"); - - if (index - count + 1 < 0) - throw new ArgumentOutOfRangeException("count", count, "count is too large"); - - return Array.LastIndexOf(Items, item, index, count); - } - - public bool Remove (T item) { - int loc = IndexOf(item); - if (loc != -1) - RemoveAt(loc); - - return loc != -1; - } - - public int RemoveAll (Predicate match) { - CheckMatch(match); - int i = 0; - int j = 0; - - // Find the first item to remove - for (i = 0; i < Count; i++) - if (match(Items[i])) - break; - - if (i == Count) - return 0; - - version++; - - // Remove any additional items - for (j = i + 1; j < Count; j++) { - if (!match(Items[j])) - Items[i++] = Items[j]; - } - if (j - i > 0) - Array.Clear(Items, i, j - i); - - Count = i; - return (j - i); - } - - public void RemoveAt (int index) { - if (index < 0 || (uint)index >= (uint)Count) - throw new ArgumentOutOfRangeException("index"); - Shift(index, -1); - Array.Clear(Items, Count, 1); - version++; - } - - public void RemoveRange (int index, int count) { - CheckRange(index, count); - if (count > 0) { - Shift(index, -count); - Array.Clear(Items, Count, count); - version++; - } - } - - public void Reverse () { - Array.Reverse(Items, 0, Count); - version++; - } - - public void Reverse (int index, int count) { - CheckRange(index, count); - Array.Reverse(Items, index, count); - version++; - } - - public void Sort () { - Array.Sort(Items, 0, Count, Comparer.Default); - version++; - } - - public void Sort (IComparer comparer) { - Array.Sort(Items, 0, Count, comparer); - version++; - } - - public void Sort (Comparison comparison) { - Array.Sort(Items, comparison); - version++; - } - - public void Sort (int index, int count, IComparer comparer) { - CheckRange(index, count); - Array.Sort(Items, index, count, comparer); - version++; - } - - public T[] ToArray () { - T[] t = new T[Count]; - Array.Copy(Items, t, Count); - - return t; - } - - public void TrimExcess () { - Capacity = Count; - } - - public bool TrueForAll (Predicate match) { - CheckMatch(match); - - for (int i = 0; i < Count; i++) - if (!match(Items[i])) - return false; - - return true; - } - - public int Capacity { - get { - return Items.Length; - } - set { - if ((uint)value < (uint)Count) - throw new ArgumentOutOfRangeException(); - - Array.Resize(ref Items, value); - } - } - - #region Interface implementations. - - IEnumerator IEnumerable.GetEnumerator () { - return GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator () { - return GetEnumerator(); - } - - #endregion - - [Serializable] - public struct Enumerator : IEnumerator, IDisposable { - private ExposedList l; - private int next; - private int ver; - private T current; - - internal Enumerator (ExposedList l) - : this() { - this.l = l; - ver = l.version; - } - - public void Dispose () { - l = null; - } - - private void VerifyState () { - if (l == null) - throw new ObjectDisposedException(GetType().FullName); - if (ver != l.version) - throw new InvalidOperationException( - "Collection was modified; enumeration operation may not execute."); - } - - public bool MoveNext () { - VerifyState(); - - if (next < 0) - return false; - - if (next < l.Count) { - current = l.Items[next++]; - return true; - } - - next = -1; - return false; - } - - public T Current { - get { - return current; - } - } - - void IEnumerator.Reset () { - VerifyState(); - next = 0; - } - - object IEnumerator.Current { - get { - VerifyState(); - if (next <= 0) - throw new InvalidOperationException(); - return current; - } - } - } - } +namespace Spine3_1_07 +{ + [Serializable] + [DebuggerDisplay("Count={Count}")] + public class ExposedList : IEnumerable + { + public T[] Items; + public int Count; + private const int DefaultCapacity = 4; + private static readonly T[] EmptyArray = new T[0]; + private int version; + + public ExposedList() + { + Items = EmptyArray; + } + + public ExposedList(IEnumerable collection) + { + CheckCollection(collection); + + // initialize to needed size (if determinable) + ICollection c = collection as ICollection; + if (c == null) + { + Items = EmptyArray; + AddEnumerable(collection); + } + else + { + Items = new T[c.Count]; + AddCollection(c); + } + } + + public ExposedList(int capacity) + { + if (capacity < 0) + throw new ArgumentOutOfRangeException("capacity"); + Items = new T[capacity]; + } + + internal ExposedList(T[] data, int size) + { + Items = data; + Count = size; + } + + public void Add(T item) + { + // If we check to see if we need to grow before trying to grow + // we can speed things up by 25% + if (Count == Items.Length) + GrowIfNeeded(1); + Items[Count++] = item; + version++; + } + + public void GrowIfNeeded(int newCount) + { + int minimumSize = Count + newCount; + if (minimumSize > Items.Length) + Capacity = Math.Max(Math.Max(Capacity * 2, DefaultCapacity), minimumSize); + } + + private void CheckRange(int idx, int count) + { + if (idx < 0) + throw new ArgumentOutOfRangeException("index"); + + if (count < 0) + throw new ArgumentOutOfRangeException("count"); + + if ((uint)idx + (uint)count > (uint)Count) + throw new ArgumentException("index and count exceed length of list"); + } + + private void AddCollection(ICollection collection) + { + int collectionCount = collection.Count; + if (collectionCount == 0) + return; + + GrowIfNeeded(collectionCount); + collection.CopyTo(Items, Count); + Count += collectionCount; + } + + private void AddEnumerable(IEnumerable enumerable) + { + foreach (T t in enumerable) + { + Add(t); + } + } + + public void AddRange(IEnumerable collection) + { + CheckCollection(collection); + + ICollection c = collection as ICollection; + if (c != null) + AddCollection(c); + else + AddEnumerable(collection); + version++; + } + + public int BinarySearch(T item) + { + return Array.BinarySearch(Items, 0, Count, item); + } + + public int BinarySearch(T item, IComparer comparer) + { + return Array.BinarySearch(Items, 0, Count, item, comparer); + } + + public int BinarySearch(int index, int count, T item, IComparer comparer) + { + CheckRange(index, count); + return Array.BinarySearch(Items, index, count, item, comparer); + } + + public void Clear(bool clearArray = true) + { + if (clearArray) + Array.Clear(Items, 0, Items.Length); + + Count = 0; + version++; + } + + public bool Contains(T item) + { + return Array.IndexOf(Items, item, 0, Count) != -1; + } + + public ExposedList ConvertAll(Converter converter) + { + if (converter == null) + throw new ArgumentNullException("converter"); + ExposedList u = new ExposedList(Count); + for (int i = 0; i < Count; i++) + u.Items[i] = converter(Items[i]); + + u.Count = Count; + return u; + } + + public void CopyTo(T[] array) + { + Array.Copy(Items, 0, array, 0, Count); + } + + public void CopyTo(T[] array, int arrayIndex) + { + Array.Copy(Items, 0, array, arrayIndex, Count); + } + + public void CopyTo(int index, T[] array, int arrayIndex, int count) + { + CheckRange(index, count); + Array.Copy(Items, index, array, arrayIndex, count); + } + + public bool Exists(Predicate match) + { + CheckMatch(match); + return GetIndex(0, Count, match) != -1; + } + + public T Find(Predicate match) + { + CheckMatch(match); + int i = GetIndex(0, Count, match); + return (i != -1) ? Items[i] : default(T); + } + + private static void CheckMatch(Predicate match) + { + if (match == null) + throw new ArgumentNullException("match"); + } + + public ExposedList FindAll(Predicate match) + { + CheckMatch(match); + return FindAllList(match); + } + + private ExposedList FindAllList(Predicate match) + { + ExposedList results = new ExposedList(); + for (int i = 0; i < Count; i++) + if (match(Items[i])) + results.Add(Items[i]); + + return results; + } + + public int FindIndex(Predicate match) + { + CheckMatch(match); + return GetIndex(0, Count, match); + } + + public int FindIndex(int startIndex, Predicate match) + { + CheckMatch(match); + CheckIndex(startIndex); + return GetIndex(startIndex, Count - startIndex, match); + } + + public int FindIndex(int startIndex, int count, Predicate match) + { + CheckMatch(match); + CheckRange(startIndex, count); + return GetIndex(startIndex, count, match); + } + + private int GetIndex(int startIndex, int count, Predicate match) + { + int end = startIndex + count; + for (int i = startIndex; i < end; i++) + if (match(Items[i])) + return i; + + return -1; + } + + public T FindLast(Predicate match) + { + CheckMatch(match); + int i = GetLastIndex(0, Count, match); + return i == -1 ? default(T) : Items[i]; + } + + public int FindLastIndex(Predicate match) + { + CheckMatch(match); + return GetLastIndex(0, Count, match); + } + + public int FindLastIndex(int startIndex, Predicate match) + { + CheckMatch(match); + CheckIndex(startIndex); + return GetLastIndex(0, startIndex + 1, match); + } + + public int FindLastIndex(int startIndex, int count, Predicate match) + { + CheckMatch(match); + int start = startIndex - count + 1; + CheckRange(start, count); + return GetLastIndex(start, count, match); + } + + private int GetLastIndex(int startIndex, int count, Predicate match) + { + // unlike FindLastIndex, takes regular params for search range + for (int i = startIndex + count; i != startIndex;) + if (match(Items[--i])) + return i; + return -1; + } + + public void ForEach(Action action) + { + if (action == null) + throw new ArgumentNullException("action"); + for (int i = 0; i < Count; i++) + action(Items[i]); + } + + public Enumerator GetEnumerator() + { + return new Enumerator(this); + } + + public ExposedList GetRange(int index, int count) + { + CheckRange(index, count); + T[] tmpArray = new T[count]; + Array.Copy(Items, index, tmpArray, 0, count); + return new ExposedList(tmpArray, count); + } + + public int IndexOf(T item) + { + return Array.IndexOf(Items, item, 0, Count); + } + + public int IndexOf(T item, int index) + { + CheckIndex(index); + return Array.IndexOf(Items, item, index, Count - index); + } + + public int IndexOf(T item, int index, int count) + { + if (index < 0) + throw new ArgumentOutOfRangeException("index"); + + if (count < 0) + throw new ArgumentOutOfRangeException("count"); + + if ((uint)index + (uint)count > (uint)Count) + throw new ArgumentOutOfRangeException("index and count exceed length of list"); + + return Array.IndexOf(Items, item, index, count); + } + + private void Shift(int start, int delta) + { + if (delta < 0) + start -= delta; + + if (start < Count) + Array.Copy(Items, start, Items, start + delta, Count - start); + + Count += delta; + + if (delta < 0) + Array.Clear(Items, Count, -delta); + } + + private void CheckIndex(int index) + { + if (index < 0 || (uint)index > (uint)Count) + throw new ArgumentOutOfRangeException("index"); + } + + public void Insert(int index, T item) + { + CheckIndex(index); + if (Count == Items.Length) + GrowIfNeeded(1); + Shift(index, 1); + Items[index] = item; + version++; + } + + private void CheckCollection(IEnumerable collection) + { + if (collection == null) + throw new ArgumentNullException("collection"); + } + + public void InsertRange(int index, IEnumerable collection) + { + CheckCollection(collection); + CheckIndex(index); + if (collection == this) + { + T[] buffer = new T[Count]; + CopyTo(buffer, 0); + GrowIfNeeded(Count); + Shift(index, buffer.Length); + Array.Copy(buffer, 0, Items, index, buffer.Length); + } + else + { + ICollection c = collection as ICollection; + if (c != null) + InsertCollection(index, c); + else + InsertEnumeration(index, collection); + } + version++; + } + + private void InsertCollection(int index, ICollection collection) + { + int collectionCount = collection.Count; + GrowIfNeeded(collectionCount); + + Shift(index, collectionCount); + collection.CopyTo(Items, index); + } + + private void InsertEnumeration(int index, IEnumerable enumerable) + { + foreach (T t in enumerable) + Insert(index++, t); + } + + public int LastIndexOf(T item) + { + return Array.LastIndexOf(Items, item, Count - 1, Count); + } + + public int LastIndexOf(T item, int index) + { + CheckIndex(index); + return Array.LastIndexOf(Items, item, index, index + 1); + } + + public int LastIndexOf(T item, int index, int count) + { + if (index < 0) + throw new ArgumentOutOfRangeException("index", index, "index is negative"); + + if (count < 0) + throw new ArgumentOutOfRangeException("count", count, "count is negative"); + + if (index - count + 1 < 0) + throw new ArgumentOutOfRangeException("count", count, "count is too large"); + + return Array.LastIndexOf(Items, item, index, count); + } + + public bool Remove(T item) + { + int loc = IndexOf(item); + if (loc != -1) + RemoveAt(loc); + + return loc != -1; + } + + public int RemoveAll(Predicate match) + { + CheckMatch(match); + int i = 0; + int j = 0; + + // Find the first item to remove + for (i = 0; i < Count; i++) + if (match(Items[i])) + break; + + if (i == Count) + return 0; + + version++; + + // Remove any additional items + for (j = i + 1; j < Count; j++) + { + if (!match(Items[j])) + Items[i++] = Items[j]; + } + if (j - i > 0) + Array.Clear(Items, i, j - i); + + Count = i; + return (j - i); + } + + public void RemoveAt(int index) + { + if (index < 0 || (uint)index >= (uint)Count) + throw new ArgumentOutOfRangeException("index"); + Shift(index, -1); + Array.Clear(Items, Count, 1); + version++; + } + + public void RemoveRange(int index, int count) + { + CheckRange(index, count); + if (count > 0) + { + Shift(index, -count); + Array.Clear(Items, Count, count); + version++; + } + } + + public void Reverse() + { + Array.Reverse(Items, 0, Count); + version++; + } + + public void Reverse(int index, int count) + { + CheckRange(index, count); + Array.Reverse(Items, index, count); + version++; + } + + public void Sort() + { + Array.Sort(Items, 0, Count, Comparer.Default); + version++; + } + + public void Sort(IComparer comparer) + { + Array.Sort(Items, 0, Count, comparer); + version++; + } + + public void Sort(Comparison comparison) + { + Array.Sort(Items, comparison); + version++; + } + + public void Sort(int index, int count, IComparer comparer) + { + CheckRange(index, count); + Array.Sort(Items, index, count, comparer); + version++; + } + + public T[] ToArray() + { + T[] t = new T[Count]; + Array.Copy(Items, t, Count); + + return t; + } + + public void TrimExcess() + { + Capacity = Count; + } + + public bool TrueForAll(Predicate match) + { + CheckMatch(match); + + for (int i = 0; i < Count; i++) + if (!match(Items[i])) + return false; + + return true; + } + + public int Capacity + { + get + { + return Items.Length; + } + set + { + if ((uint)value < (uint)Count) + throw new ArgumentOutOfRangeException(); + + Array.Resize(ref Items, value); + } + } + + #region Interface implementations. + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion + + [Serializable] + public struct Enumerator : IEnumerator, IDisposable + { + private ExposedList l; + private int next; + private int ver; + private T current; + + internal Enumerator(ExposedList l) + : this() + { + this.l = l; + ver = l.version; + } + + public void Dispose() + { + l = null; + } + + private void VerifyState() + { + if (l == null) + throw new ObjectDisposedException(GetType().FullName); + if (ver != l.version) + throw new InvalidOperationException( + "Collection was modified; enumeration operation may not execute."); + } + + public bool MoveNext() + { + VerifyState(); + + if (next < 0) + return false; + + if (next < l.Count) + { + current = l.Items[next++]; + return true; + } + + next = -1; + return false; + } + + public T Current + { + get + { + return current; + } + } + + void IEnumerator.Reset() + { + VerifyState(); + next = 0; + } + + object IEnumerator.Current + { + get + { + VerifyState(); + if (next <= 0) + throw new InvalidOperationException(); + return current; + } + } + } + } } \ No newline at end of file diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/IUpdatable.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/IUpdatable.cs index 85fcd9b..20315be 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/IUpdatable.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/IUpdatable.cs @@ -29,10 +29,10 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; - -namespace Spine3_1_07 { - public interface IUpdatable { - void Update (); - } +namespace Spine3_1_07 +{ + public interface IUpdatable + { + void Update(); + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/IkConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/IkConstraint.cs index ff88dae..bb41617 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/IkConstraint.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/IkConstraint.cs @@ -30,195 +30,225 @@ *****************************************************************************/ using System; -using System.Collections.Generic; -namespace Spine3_1_07 { - public class IkConstraint : IUpdatable { - internal IkConstraintData data; - internal ExposedList bones = new ExposedList(); - internal Bone target; - internal int bendDirection; - internal float mix; +namespace Spine3_1_07 +{ + public class IkConstraint : IUpdatable + { + internal IkConstraintData data; + internal ExposedList bones = new ExposedList(); + internal Bone target; + internal int bendDirection; + internal float mix; - public IkConstraintData Data { get { return data; } } - public ExposedList Bones { get { return bones; } } - public Bone Target { get { return target; } set { target = value; } } - public int BendDirection { get { return bendDirection; } set { bendDirection = value; } } - public float Mix { get { return mix; } set { mix = value; } } + public IkConstraintData Data { get { return data; } } + public ExposedList Bones { get { return bones; } } + public Bone Target { get { return target; } set { target = value; } } + public int BendDirection { get { return bendDirection; } set { bendDirection = value; } } + public float Mix { get { return mix; } set { mix = value; } } - public IkConstraint (IkConstraintData data, Skeleton skeleton) { - if (data == null) throw new ArgumentNullException("data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); - this.data = data; - mix = data.mix; - bendDirection = data.bendDirection; + public IkConstraint(IkConstraintData data, Skeleton skeleton) + { + if (data == null) throw new ArgumentNullException("data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); + this.data = data; + mix = data.mix; + bendDirection = data.bendDirection; - bones = new ExposedList(data.bones.Count); - foreach (BoneData boneData in data.bones) - bones.Add(skeleton.FindBone(boneData.name)); - target = skeleton.FindBone(data.target.name); - } + bones = new ExposedList(data.bones.Count); + foreach (BoneData boneData in data.bones) + bones.Add(skeleton.FindBone(boneData.name)); + target = skeleton.FindBone(data.target.name); + } - public void Update () { - Apply(); - } + public void Update() + { + Apply(); + } - public void Apply () { - Bone target = this.target; - ExposedList bones = this.bones; - switch (bones.Count) { - case 1: - Apply(bones.Items[0], target.worldX, target.worldY, mix); - break; - case 2: - Apply(bones.Items[0], bones.Items[1], target.worldX, target.worldY, bendDirection, mix); - break; - } - } + public void Apply() + { + Bone target = this.target; + ExposedList bones = this.bones; + switch (bones.Count) + { + case 1: + Apply(bones.Items[0], target.worldX, target.worldY, mix); + break; + case 2: + Apply(bones.Items[0], bones.Items[1], target.worldX, target.worldY, bendDirection, mix); + break; + } + } - override public String ToString () { - return data.name; - } + override public String ToString() + { + return data.name; + } - /// 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 public void Apply (Bone bone, float targetX, float targetY, float alpha) { - float parentRotation = bone.parent == null ? 0 : bone.parent.WorldRotationX; - float rotation = bone.rotation; - float rotationIK = MathUtils.Atan2(targetY - bone.worldY, targetX - bone.worldX) * MathUtils.radDeg - parentRotation; - if ((bone.worldSignX != bone.worldSignY) != (bone.skeleton.flipX != (bone.skeleton.flipY != Bone.yDown))) - rotationIK = 360 - rotationIK; - if (rotationIK > 180) rotationIK -= 360; - else if (rotationIK < -180) rotationIK += 360; - bone.UpdateWorldTransform(bone.x, bone.y, rotation + (rotationIK - rotation) * alpha, bone.appliedScaleX, bone.appliedScaleY); - } + /// 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 public void Apply(Bone bone, float targetX, float targetY, float alpha) + { + float parentRotation = bone.parent == null ? 0 : bone.parent.WorldRotationX; + float rotation = bone.rotation; + float rotationIK = MathUtils.Atan2(targetY - bone.worldY, targetX - bone.worldX) * MathUtils.radDeg - parentRotation; + if ((bone.worldSignX != bone.worldSignY) != (bone.skeleton.flipX != (bone.skeleton.flipY != Bone.yDown))) + rotationIK = 360 - rotationIK; + if (rotationIK > 180) rotationIK -= 360; + else if (rotationIK < -180) rotationIK += 360; + bone.UpdateWorldTransform(bone.x, bone.y, rotation + (rotationIK - rotation) * alpha, bone.appliedScaleX, bone.appliedScaleY); + } - /// 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. - /// A direct descendant of the parent bone. - static public void Apply (Bone parent, Bone child, float targetX, float targetY, int bendDir, float alpha) { - if (alpha == 0) return; - float px = parent.x, py = parent.y, psx = parent.appliedScaleX, psy = parent.appliedScaleY; - int os1, os2, s2; - if (psx < 0) { - psx = -psx; - os1 = 180; - s2 = -1; - } else { - os1 = 0; - s2 = 1; - } - if (psy < 0) { - psy = -psy; - s2 = -s2; - } - float cx = child.x, cy = child.y, csx = child.appliedScaleX; - bool u = Math.Abs(psx - psy) <= 0.0001f; - if (!u && cy != 0) { - child.worldX = parent.a * cx + parent.worldX; - child.worldY = parent.c * cx + parent.worldY; - cy = 0; - } - if (csx < 0) { - csx = -csx; - os2 = 180; - } else - os2 = 0; - Bone pp = parent.parent; - float tx, ty, dx, dy; - if (pp == null) { - tx = targetX - px; - ty = targetY - py; - dx = child.worldX - px; - dy = child.worldY - py; - } else { - float a = pp.a, b = pp.b, c = pp.c, d = pp.d, invDet = 1 / (a * d - b * c); - float wx = pp.worldX, wy = pp.worldY, x = targetX - wx, y = targetY - wy; - tx = (x * d - y * b) * invDet - px; - ty = (y * a - x * c) * invDet - py; - x = child.worldX - wx; - y = child.worldY - wy; - dx = (x * d - y * b) * invDet - px; - dy = (y * a - x * c) * invDet - py; - } - float l1 = (float)Math.Sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2; - if (u) { - l2 *= psx; - float cos = (tx * tx + ty * ty - l1 * l1 - l2 * l2) / (2 * l1 * l2); - if (cos < -1) cos = -1; - else if (cos > 1) cos = 1; - a2 = (float)Math.Acos(cos) * bendDir; - float a = l1 + l2 * cos, o = l2 * MathUtils.Sin(a2); - a1 = MathUtils.Atan2(ty * a - tx * o, tx * a + ty * o); - } else { - float a = psx * l2, b = psy * l2, ta = MathUtils.Atan2(ty, tx); - float aa = a * a, bb = b * b, ll = l1 * l1, dd = tx * tx + ty * ty; - float c0 = bb * ll + aa * dd - aa * bb, c1 = -2 * bb * l1, c2 = bb - aa; - float d = c1 * c1 - 4 * c2 * c0; - if (d >= 0) { - float q = (float)Math.Sqrt(d); - if (c1 < 0) q = -q; - q = -(c1 + q) / 2; - float r0 = q / c2, r1 = c0 / q; - float r = Math.Abs(r0) < Math.Abs(r1) ? r0 : r1; - if (r * r <= dd) { - float y1 = (float)Math.Sqrt(dd - r * r) * bendDir; - a1 = ta - MathUtils.Atan2(y1, r); - a2 = MathUtils.Atan2(y1 / psy, (r - l1) / psx); - goto outer; - } - } - float minAngle = 0, minDist = float.MaxValue, minX = 0, minY = 0; - float maxAngle = 0, maxDist = 0, maxX = 0, maxY = 0; - float x = l1 + a, dist = x * x; - if (dist > maxDist) { - maxAngle = 0; - maxDist = dist; - maxX = x; - } - x = l1 - a; - dist = x * x; - if (dist < minDist) { - minAngle = MathUtils.PI; - minDist = dist; - minX = x; - } - float angle = (float)Math.Acos(-a * l1 / (aa - bb)); - x = a * MathUtils.Cos(angle) + l1; - float y = b * MathUtils.Sin(angle); - dist = x * x + y * y; - if (dist < minDist) { - minAngle = angle; - minDist = dist; - minX = x; - minY = y; - } - if (dist > maxDist) { - maxAngle = angle; - maxDist = dist; - maxX = x; - maxY = y; - } - if (dd <= (minDist + maxDist) / 2) { - a1 = ta - MathUtils.Atan2(minY * bendDir, minX); - a2 = minAngle * bendDir; - } else { - a1 = ta - MathUtils.Atan2(maxY * bendDir, maxX); - a2 = maxAngle * bendDir; - } - } - outer: - float os = MathUtils.Atan2(cy, cx) * s2; - a1 = (a1 - os) * MathUtils.radDeg + os1; - a2 = (a2 + os) * MathUtils.radDeg * s2 + os2; - if (a1 > 180) a1 -= 360; - else if (a1 < -180) a1 += 360; - if (a2 > 180) a2 -= 360; - else if (a2 < -180) a2 += 360; - float rotation = parent.rotation; - parent.UpdateWorldTransform(px, py, rotation + (a1 - rotation) * alpha, parent.appliedScaleX, parent.appliedScaleY); - rotation = child.rotation; - child.UpdateWorldTransform(cx, cy, rotation + (a2 - rotation) * alpha, child.appliedScaleX, child.appliedScaleY); - } - } + /// 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. + /// A direct descendant of the parent bone. + static public void Apply(Bone parent, Bone child, float targetX, float targetY, int bendDir, float alpha) + { + if (alpha == 0) return; + float px = parent.x, py = parent.y, psx = parent.appliedScaleX, psy = parent.appliedScaleY; + int os1, os2, s2; + if (psx < 0) + { + psx = -psx; + os1 = 180; + s2 = -1; + } + else + { + os1 = 0; + s2 = 1; + } + if (psy < 0) + { + psy = -psy; + s2 = -s2; + } + float cx = child.x, cy = child.y, csx = child.appliedScaleX; + bool u = Math.Abs(psx - psy) <= 0.0001f; + if (!u && cy != 0) + { + child.worldX = parent.a * cx + parent.worldX; + child.worldY = parent.c * cx + parent.worldY; + cy = 0; + } + if (csx < 0) + { + csx = -csx; + os2 = 180; + } + else + os2 = 0; + Bone pp = parent.parent; + float tx, ty, dx, dy; + if (pp == null) + { + tx = targetX - px; + ty = targetY - py; + dx = child.worldX - px; + dy = child.worldY - py; + } + else + { + float a = pp.a, b = pp.b, c = pp.c, d = pp.d, invDet = 1 / (a * d - b * c); + float wx = pp.worldX, wy = pp.worldY, x = targetX - wx, y = targetY - wy; + tx = (x * d - y * b) * invDet - px; + ty = (y * a - x * c) * invDet - py; + x = child.worldX - wx; + y = child.worldY - wy; + dx = (x * d - y * b) * invDet - px; + dy = (y * a - x * c) * invDet - py; + } + float l1 = (float)Math.Sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2; + if (u) + { + l2 *= psx; + float cos = (tx * tx + ty * ty - l1 * l1 - l2 * l2) / (2 * l1 * l2); + if (cos < -1) cos = -1; + else if (cos > 1) cos = 1; + a2 = (float)Math.Acos(cos) * bendDir; + float a = l1 + l2 * cos, o = l2 * MathUtils.Sin(a2); + a1 = MathUtils.Atan2(ty * a - tx * o, tx * a + ty * o); + } + else + { + float a = psx * l2, b = psy * l2, ta = MathUtils.Atan2(ty, tx); + float aa = a * a, bb = b * b, ll = l1 * l1, dd = tx * tx + ty * ty; + float c0 = bb * ll + aa * dd - aa * bb, c1 = -2 * bb * l1, c2 = bb - aa; + float d = c1 * c1 - 4 * c2 * c0; + if (d >= 0) + { + float q = (float)Math.Sqrt(d); + if (c1 < 0) q = -q; + q = -(c1 + q) / 2; + float r0 = q / c2, r1 = c0 / q; + float r = Math.Abs(r0) < Math.Abs(r1) ? r0 : r1; + if (r * r <= dd) + { + float y1 = (float)Math.Sqrt(dd - r * r) * bendDir; + a1 = ta - MathUtils.Atan2(y1, r); + a2 = MathUtils.Atan2(y1 / psy, (r - l1) / psx); + goto outer; + } + } + float minAngle = 0, minDist = float.MaxValue, minX = 0, minY = 0; + float maxAngle = 0, maxDist = 0, maxX = 0, maxY = 0; + float x = l1 + a, dist = x * x; + if (dist > maxDist) + { + maxAngle = 0; + maxDist = dist; + maxX = x; + } + x = l1 - a; + dist = x * x; + if (dist < minDist) + { + minAngle = MathUtils.PI; + minDist = dist; + minX = x; + } + float angle = (float)Math.Acos(-a * l1 / (aa - bb)); + x = a * MathUtils.Cos(angle) + l1; + float y = b * MathUtils.Sin(angle); + dist = x * x + y * y; + if (dist < minDist) + { + minAngle = angle; + minDist = dist; + minX = x; + minY = y; + } + if (dist > maxDist) + { + maxAngle = angle; + maxDist = dist; + maxX = x; + maxY = y; + } + if (dd <= (minDist + maxDist) / 2) + { + a1 = ta - MathUtils.Atan2(minY * bendDir, minX); + a2 = minAngle * bendDir; + } + else + { + a1 = ta - MathUtils.Atan2(maxY * bendDir, maxX); + a2 = maxAngle * bendDir; + } + } + outer: + float os = MathUtils.Atan2(cy, cx) * s2; + a1 = (a1 - os) * MathUtils.radDeg + os1; + a2 = (a2 + os) * MathUtils.radDeg * s2 + os2; + if (a1 > 180) a1 -= 360; + else if (a1 < -180) a1 += 360; + if (a2 > 180) a2 -= 360; + else if (a2 < -180) a2 += 360; + float rotation = parent.rotation; + parent.UpdateWorldTransform(px, py, rotation + (a1 - rotation) * alpha, parent.appliedScaleX, parent.appliedScaleY); + rotation = child.rotation; + child.UpdateWorldTransform(cx, cy, rotation + (a2 - rotation) * alpha, child.appliedScaleX, child.appliedScaleY); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/IkConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/IkConstraintData.cs index 6289b4a..ddb7609 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/IkConstraintData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/IkConstraintData.cs @@ -32,27 +32,31 @@ using System; using System.Collections.Generic; -namespace Spine3_1_07 { - public class IkConstraintData { - internal String name; - internal List bones = new List(); - internal BoneData target; - internal int bendDirection = 1; - internal float mix = 1; +namespace Spine3_1_07 +{ + public class IkConstraintData + { + internal String name; + internal List bones = new List(); + internal BoneData target; + internal int bendDirection = 1; + internal float mix = 1; - public String Name { get { return name; } } - public List Bones { get { return bones; } } - public BoneData Target { get { return target; } set { target = value; } } - public int BendDirection { get { return bendDirection; } set { bendDirection = value; } } - public float Mix { get { return mix; } set { mix = value; } } + public String Name { get { return name; } } + public List Bones { get { return bones; } } + public BoneData Target { get { return target; } set { target = value; } } + public int BendDirection { get { return bendDirection; } set { bendDirection = value; } } + public float Mix { get { return mix; } set { mix = value; } } - public IkConstraintData (String name) { - if (name == null) throw new ArgumentNullException("name cannot be null."); - this.name = name; - } + public IkConstraintData(String name) + { + if (name == null) throw new ArgumentNullException("name cannot be null."); + this.name = name; + } - override public String ToString () { - return name; - } - } + override public String ToString() + { + return name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Json.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Json.cs index ae7ceb5..ff683f1 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Json.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Json.cs @@ -30,20 +30,22 @@ *****************************************************************************/ using System; +using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Text; -using System.Collections; -using System.Globalization; -using System.Collections.Generic; -namespace Spine3_1_07 { - public static class Json { - public static object Deserialize (TextReader text) { - var parser = new SharpJson3_1_07.JsonDecoder(); - parser.parseNumbersAsFloat = true; - return parser.Decode(text.ReadToEnd()); - } - } +namespace Spine3_1_07 +{ + public static class Json + { + public static object Deserialize(TextReader text) + { + var parser = new SharpJson3_1_07.JsonDecoder(); + parser.parseNumbersAsFloat = true; + return parser.Decode(text.ReadToEnd()); + } + } } /** @@ -77,460 +79,502 @@ public static object Deserialize (TextReader text) { */ namespace SharpJson3_1_07 { - class Lexer - { - public enum Token { - None, - Null, - True, - False, - Colon, - Comma, - String, - Number, - CurlyOpen, - CurlyClose, - SquaredOpen, - SquaredClose, - }; - - public bool hasError { - get { - return !success; - } - } - - public int lineNumber { - get; - private set; - } - - public bool parseNumbersAsFloat { - get; - set; - } - - char[] json; - int index = 0; - bool success = true; - char[] stringBuffer = new char[4096]; - - public Lexer(string text) - { - Reset(); - - json = text.ToCharArray(); - parseNumbersAsFloat = false; - } - - public void Reset() - { - index = 0; - lineNumber = 1; - success = true; - } - - public string ParseString() - { - int idx = 0; - StringBuilder builder = null; - - SkipWhiteSpaces(); - - // " - char c = json[index++]; - - bool failed = false; - bool complete = false; - - while (!complete && !failed) { - if (index == json.Length) - break; - - c = json[index++]; - if (c == '"') { - complete = true; - break; - } else if (c == '\\') { - if (index == json.Length) - break; - - c = json[index++]; - - switch (c) { - case '"': - stringBuffer[idx++] = '"'; - break; - case '\\': - stringBuffer[idx++] = '\\'; - break; - case '/': - stringBuffer[idx++] = '/'; - break; - case 'b': - stringBuffer[idx++] = '\b'; - break; - case'f': - stringBuffer[idx++] = '\f'; - break; - case 'n': - stringBuffer[idx++] = '\n'; - break; - case 'r': - stringBuffer[idx++] = '\r'; - break; - case 't': - stringBuffer[idx++] = '\t'; - break; - case 'u': - int remainingLength = json.Length - index; - if (remainingLength >= 4) { - var hex = new string(json, index, 4); - - // XXX: handle UTF - stringBuffer[idx++] = (char) Convert.ToInt32(hex, 16); - - // skip 4 chars - index += 4; - } else { - failed = true; - } - break; - } - } else { - stringBuffer[idx++] = c; - } - - if (idx >= stringBuffer.Length) { - if (builder == null) - builder = new StringBuilder(); - - builder.Append(stringBuffer, 0, idx); - idx = 0; - } - } - - if (!complete) { - success = false; - return null; - } - - if (builder != null) - return builder.ToString (); - else - return new string (stringBuffer, 0, idx); - } - - string GetNumberString() - { - SkipWhiteSpaces(); - - int lastIndex = GetLastIndexOfNumber(index); - int charLength = (lastIndex - index) + 1; - - var result = new string (json, index, charLength); - - index = lastIndex + 1; - - return result; - } - - public float ParseFloatNumber() - { - float number; - var str = GetNumberString (); - - if (!float.TryParse (str, NumberStyles.Float, CultureInfo.InvariantCulture, out number)) - return 0; - - return number; - } - - public double ParseDoubleNumber() - { - double number; - var str = GetNumberString (); - - if (!double.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out number)) - return 0; - - return number; - } - - int GetLastIndexOfNumber(int index) - { - int lastIndex; - - for (lastIndex = index; lastIndex < json.Length; lastIndex++) { - char ch = json[lastIndex]; - - if ((ch < '0' || ch > '9') && ch != '+' && ch != '-' - && ch != '.' && ch != 'e' && ch != 'E') - break; - } - - return lastIndex - 1; - } - - void SkipWhiteSpaces() - { - for (; index < json.Length; index++) { - char ch = json[index]; - - if (ch == '\n') - lineNumber++; - - if (!char.IsWhiteSpace(json[index])) - break; - } - } - - public Token LookAhead() - { - SkipWhiteSpaces(); - - int savedIndex = index; - return NextToken(json, ref savedIndex); - } - - public Token NextToken() - { - SkipWhiteSpaces(); - return NextToken(json, ref index); - } - - static Token NextToken(char[] json, ref int index) - { - if (index == json.Length) - return Token.None; - - char c = json[index++]; - - switch (c) { - case '{': - return Token.CurlyOpen; - case '}': - return Token.CurlyClose; - case '[': - return Token.SquaredOpen; - case ']': - return Token.SquaredClose; - case ',': - return Token.Comma; - case '"': - return Token.String; - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - case '-': - return Token.Number; - case ':': - return Token.Colon; - } - - index--; - - int remainingLength = json.Length - index; - - // false - if (remainingLength >= 5) { - if (json[index] == 'f' && - json[index + 1] == 'a' && - json[index + 2] == 'l' && - json[index + 3] == 's' && - json[index + 4] == 'e') { - index += 5; - return Token.False; - } - } - - // true - if (remainingLength >= 4) { - if (json[index] == 't' && - json[index + 1] == 'r' && - json[index + 2] == 'u' && - json[index + 3] == 'e') { - index += 4; - return Token.True; - } - } - - // null - if (remainingLength >= 4) { - if (json[index] == 'n' && - json[index + 1] == 'u' && - json[index + 2] == 'l' && - json[index + 3] == 'l') { - index += 4; - return Token.Null; - } - } - - return Token.None; - } - } - - public class JsonDecoder - { - public string errorMessage { - get; - private set; - } - - public bool parseNumbersAsFloat { - get; - set; - } - - Lexer lexer; - - public JsonDecoder() - { - errorMessage = null; - parseNumbersAsFloat = false; - } - - public object Decode(string text) - { - errorMessage = null; - - lexer = new Lexer(text); - lexer.parseNumbersAsFloat = parseNumbersAsFloat; - - return ParseValue(); - } - - public static object DecodeText(string text) - { - var builder = new JsonDecoder(); - return builder.Decode(text); - } - - IDictionary ParseObject() - { - var table = new Dictionary(); - - // { - lexer.NextToken(); - - while (true) { - var token = lexer.LookAhead(); - - switch (token) { - case Lexer.Token.None: - TriggerError("Invalid token"); - return null; - case Lexer.Token.Comma: - lexer.NextToken(); - break; - case Lexer.Token.CurlyClose: - lexer.NextToken(); - return table; - default: - // name - string name = EvalLexer(lexer.ParseString()); - - if (errorMessage != null) - return null; - - // : - token = lexer.NextToken(); - - if (token != Lexer.Token.Colon) { - TriggerError("Invalid token; expected ':'"); - return null; - } - - // value - object value = ParseValue(); - - if (errorMessage != null) - return null; - - table[name] = value; - break; - } - } - - //return null; // Unreachable code - } - - IList ParseArray() - { - var array = new List(); - - // [ - lexer.NextToken(); - - while (true) { - var token = lexer.LookAhead(); - - switch (token) { - case Lexer.Token.None: - TriggerError("Invalid token"); - return null; - case Lexer.Token.Comma: - lexer.NextToken(); - break; - case Lexer.Token.SquaredClose: - lexer.NextToken(); - return array; - default: - object value = ParseValue(); - - if (errorMessage != null) - return null; - - array.Add(value); - break; - } - } - - //return null; // Unreachable code - } - - object ParseValue() - { - switch (lexer.LookAhead()) { - case Lexer.Token.String: - return EvalLexer(lexer.ParseString()); - case Lexer.Token.Number: - if (parseNumbersAsFloat) - return EvalLexer(lexer.ParseFloatNumber()); - else - return EvalLexer(lexer.ParseDoubleNumber()); - case Lexer.Token.CurlyOpen: - return ParseObject(); - case Lexer.Token.SquaredOpen: - return ParseArray(); - case Lexer.Token.True: - lexer.NextToken(); - return true; - case Lexer.Token.False: - lexer.NextToken(); - return false; - case Lexer.Token.Null: - lexer.NextToken(); - return null; - case Lexer.Token.None: - break; - } - - TriggerError("Unable to parse value"); - return null; - } - - void TriggerError(string message) - { - errorMessage = string.Format("Error: '{0}' at line {1}", - message, lexer.lineNumber); - } - - T EvalLexer(T value) - { - if (lexer.hasError) - TriggerError("Lexical error ocurred"); - - return value; - } - } + class Lexer + { + public enum Token + { + None, + Null, + True, + False, + Colon, + Comma, + String, + Number, + CurlyOpen, + CurlyClose, + SquaredOpen, + SquaredClose, + }; + + public bool hasError + { + get + { + return !success; + } + } + + public int lineNumber + { + get; + private set; + } + + public bool parseNumbersAsFloat + { + get; + set; + } + + char[] json; + int index = 0; + bool success = true; + char[] stringBuffer = new char[4096]; + + public Lexer(string text) + { + Reset(); + + json = text.ToCharArray(); + parseNumbersAsFloat = false; + } + + public void Reset() + { + index = 0; + lineNumber = 1; + success = true; + } + + public string ParseString() + { + int idx = 0; + StringBuilder builder = null; + + SkipWhiteSpaces(); + + // " + char c = json[index++]; + + bool failed = false; + bool complete = false; + + while (!complete && !failed) + { + if (index == json.Length) + break; + + c = json[index++]; + if (c == '"') + { + complete = true; + break; + } + else if (c == '\\') + { + if (index == json.Length) + break; + + c = json[index++]; + + switch (c) + { + case '"': + stringBuffer[idx++] = '"'; + break; + case '\\': + stringBuffer[idx++] = '\\'; + break; + case '/': + stringBuffer[idx++] = '/'; + break; + case 'b': + stringBuffer[idx++] = '\b'; + break; + case 'f': + stringBuffer[idx++] = '\f'; + break; + case 'n': + stringBuffer[idx++] = '\n'; + break; + case 'r': + stringBuffer[idx++] = '\r'; + break; + case 't': + stringBuffer[idx++] = '\t'; + break; + case 'u': + int remainingLength = json.Length - index; + if (remainingLength >= 4) + { + var hex = new string(json, index, 4); + + // XXX: handle UTF + stringBuffer[idx++] = (char)Convert.ToInt32(hex, 16); + + // skip 4 chars + index += 4; + } + else + { + failed = true; + } + break; + } + } + else + { + stringBuffer[idx++] = c; + } + + if (idx >= stringBuffer.Length) + { + if (builder == null) + builder = new StringBuilder(); + + builder.Append(stringBuffer, 0, idx); + idx = 0; + } + } + + if (!complete) + { + success = false; + return null; + } + + if (builder != null) + return builder.ToString(); + else + return new string(stringBuffer, 0, idx); + } + + string GetNumberString() + { + SkipWhiteSpaces(); + + int lastIndex = GetLastIndexOfNumber(index); + int charLength = (lastIndex - index) + 1; + + var result = new string(json, index, charLength); + + index = lastIndex + 1; + + return result; + } + + public float ParseFloatNumber() + { + float number; + var str = GetNumberString(); + + if (!float.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out number)) + return 0; + + return number; + } + + public double ParseDoubleNumber() + { + double number; + var str = GetNumberString(); + + if (!double.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out number)) + return 0; + + return number; + } + + int GetLastIndexOfNumber(int index) + { + int lastIndex; + + for (lastIndex = index; lastIndex < json.Length; lastIndex++) + { + char ch = json[lastIndex]; + + if ((ch < '0' || ch > '9') && ch != '+' && ch != '-' + && ch != '.' && ch != 'e' && ch != 'E') + break; + } + + return lastIndex - 1; + } + + void SkipWhiteSpaces() + { + for (; index < json.Length; index++) + { + char ch = json[index]; + + if (ch == '\n') + lineNumber++; + + if (!char.IsWhiteSpace(json[index])) + break; + } + } + + public Token LookAhead() + { + SkipWhiteSpaces(); + + int savedIndex = index; + return NextToken(json, ref savedIndex); + } + + public Token NextToken() + { + SkipWhiteSpaces(); + return NextToken(json, ref index); + } + + static Token NextToken(char[] json, ref int index) + { + if (index == json.Length) + return Token.None; + + char c = json[index++]; + + switch (c) + { + case '{': + return Token.CurlyOpen; + case '}': + return Token.CurlyClose; + case '[': + return Token.SquaredOpen; + case ']': + return Token.SquaredClose; + case ',': + return Token.Comma; + case '"': + return Token.String; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + return Token.Number; + case ':': + return Token.Colon; + } + + index--; + + int remainingLength = json.Length - index; + + // false + if (remainingLength >= 5) + { + if (json[index] == 'f' && + json[index + 1] == 'a' && + json[index + 2] == 'l' && + json[index + 3] == 's' && + json[index + 4] == 'e') + { + index += 5; + return Token.False; + } + } + + // true + if (remainingLength >= 4) + { + if (json[index] == 't' && + json[index + 1] == 'r' && + json[index + 2] == 'u' && + json[index + 3] == 'e') + { + index += 4; + return Token.True; + } + } + + // null + if (remainingLength >= 4) + { + if (json[index] == 'n' && + json[index + 1] == 'u' && + json[index + 2] == 'l' && + json[index + 3] == 'l') + { + index += 4; + return Token.Null; + } + } + + return Token.None; + } + } + + public class JsonDecoder + { + public string errorMessage + { + get; + private set; + } + + public bool parseNumbersAsFloat + { + get; + set; + } + + Lexer lexer; + + public JsonDecoder() + { + errorMessage = null; + parseNumbersAsFloat = false; + } + + public object Decode(string text) + { + errorMessage = null; + + lexer = new Lexer(text); + lexer.parseNumbersAsFloat = parseNumbersAsFloat; + + return ParseValue(); + } + + public static object DecodeText(string text) + { + var builder = new JsonDecoder(); + return builder.Decode(text); + } + + IDictionary ParseObject() + { + var table = new Dictionary(); + + // { + lexer.NextToken(); + + while (true) + { + var token = lexer.LookAhead(); + + switch (token) + { + case Lexer.Token.None: + TriggerError("Invalid token"); + return null; + case Lexer.Token.Comma: + lexer.NextToken(); + break; + case Lexer.Token.CurlyClose: + lexer.NextToken(); + return table; + default: + // name + string name = EvalLexer(lexer.ParseString()); + + if (errorMessage != null) + return null; + + // : + token = lexer.NextToken(); + + if (token != Lexer.Token.Colon) + { + TriggerError("Invalid token; expected ':'"); + return null; + } + + // value + object value = ParseValue(); + + if (errorMessage != null) + return null; + + table[name] = value; + break; + } + } + + //return null; // Unreachable code + } + + IList ParseArray() + { + var array = new List(); + + // [ + lexer.NextToken(); + + while (true) + { + var token = lexer.LookAhead(); + + switch (token) + { + case Lexer.Token.None: + TriggerError("Invalid token"); + return null; + case Lexer.Token.Comma: + lexer.NextToken(); + break; + case Lexer.Token.SquaredClose: + lexer.NextToken(); + return array; + default: + object value = ParseValue(); + + if (errorMessage != null) + return null; + + array.Add(value); + break; + } + } + + //return null; // Unreachable code + } + + object ParseValue() + { + switch (lexer.LookAhead()) + { + case Lexer.Token.String: + return EvalLexer(lexer.ParseString()); + case Lexer.Token.Number: + if (parseNumbersAsFloat) + return EvalLexer(lexer.ParseFloatNumber()); + else + return EvalLexer(lexer.ParseDoubleNumber()); + case Lexer.Token.CurlyOpen: + return ParseObject(); + case Lexer.Token.SquaredOpen: + return ParseArray(); + case Lexer.Token.True: + lexer.NextToken(); + return true; + case Lexer.Token.False: + lexer.NextToken(); + return false; + case Lexer.Token.Null: + lexer.NextToken(); + return null; + case Lexer.Token.None: + break; + } + + TriggerError("Unable to parse value"); + return null; + } + + void TriggerError(string message) + { + errorMessage = string.Format("Error: '{0}' at line {1}", + message, lexer.lineNumber); + } + + T EvalLexer(T value) + { + if (lexer.hasError) + TriggerError("Lexical error ocurred"); + + return value; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/MathUtils.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/MathUtils.cs index 95de8c1..1b71e33 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/MathUtils.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/MathUtils.cs @@ -31,64 +31,74 @@ using System; -namespace Spine3_1_07 { - public static class MathUtils { - public const float PI = 3.1415927f; - public const float radDeg = 180f / PI; - public const float degRad = PI / 180; +namespace Spine3_1_07 +{ + public static class MathUtils + { + public const float PI = 3.1415927f; + public const float radDeg = 180f / PI; + public const float degRad = PI / 180; - const int SIN_BITS = 14; // 16KB. Adjust for accuracy. - const int SIN_MASK = ~(-1 << SIN_BITS); - const int SIN_COUNT = SIN_MASK + 1; - const float radFull = PI * 2; - const float degFull = 360; - const float radToIndex = SIN_COUNT / radFull; - const float degToIndex = SIN_COUNT / degFull; - static float[] sin = new float[SIN_COUNT]; + const int SIN_BITS = 14; // 16KB. Adjust for accuracy. + const int SIN_MASK = ~(-1 << SIN_BITS); + const int SIN_COUNT = SIN_MASK + 1; + const float radFull = PI * 2; + const float degFull = 360; + const float radToIndex = SIN_COUNT / radFull; + const float degToIndex = SIN_COUNT / degFull; + static float[] sin = new float[SIN_COUNT]; - static MathUtils () { - for (int i = 0; i < SIN_COUNT; i++) - sin[i] = (float)Math.Sin((i + 0.5f) / SIN_COUNT * radFull); - for (int i = 0; i < 360; i += 90) - sin[(int)(i * degToIndex) & SIN_MASK] = (float)Math.Sin(i * degRad); - } + static MathUtils() + { + for (int i = 0; i < SIN_COUNT; i++) + sin[i] = (float)Math.Sin((i + 0.5f) / SIN_COUNT * radFull); + for (int i = 0; i < 360; i += 90) + sin[(int)(i * degToIndex) & SIN_MASK] = (float)Math.Sin(i * degRad); + } - /** Returns the sine in radians from a lookup table. */ - static public float Sin (float radians) { - return sin[(int)(radians * radToIndex) & SIN_MASK]; - } + /** Returns the sine in radians from a lookup table. */ + static public float Sin(float radians) + { + return sin[(int)(radians * radToIndex) & SIN_MASK]; + } - /** Returns the cosine in radians from a lookup table. */ - static public float Cos (float radians) { - return sin[(int)((radians + PI / 2) * radToIndex) & SIN_MASK]; - } + /** Returns the cosine in radians from a lookup table. */ + static public float Cos(float radians) + { + return sin[(int)((radians + PI / 2) * radToIndex) & SIN_MASK]; + } - /** Returns the sine in radians from a lookup table. */ - static public float SinDeg (float degrees) { - return sin[(int)(degrees * degToIndex) & SIN_MASK]; - } + /** Returns the sine in radians from a lookup table. */ + static public float SinDeg(float degrees) + { + return sin[(int)(degrees * degToIndex) & SIN_MASK]; + } - /** Returns the cosine in radians from a lookup table. */ - static public float CosDeg (float degrees) { - return sin[(int)((degrees + 90) * degToIndex) & SIN_MASK]; - } + /** Returns the cosine in radians from a lookup table. */ + static public float CosDeg(float degrees) + { + return sin[(int)((degrees + 90) * degToIndex) & SIN_MASK]; + } - /// 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 public float Atan2 (float y, float x) { - if (x == 0f) { - if (y > 0f) return PI / 2; - if (y == 0f) return 0f; - return -PI / 2; - } - float atan, z = y / x; - if (Math.Abs(z) < 1f) { - atan = z / (1f + 0.28f * z * z); - if (x < 0f) return atan + (y < 0f ? -PI : PI); - return atan; - } - atan = PI / 2 - z / (z * z + 0.28f); - return y < 0f ? atan - PI : atan; - } - } + /// 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 public float Atan2(float y, float x) + { + if (x == 0f) + { + if (y > 0f) return PI / 2; + if (y == 0f) return 0f; + return -PI / 2; + } + float atan, z = y / x; + if (Math.Abs(z) < 1f) + { + atan = z / (1f + 0.28f * z * z); + if (x < 0f) return atan + (y < 0f ? -PI : PI); + return atan; + } + atan = PI / 2 - z / (z * z + 0.28f); + return y < 0f ? atan - PI : atan; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Skeleton.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Skeleton.cs index 0940f60..fb3921a 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Skeleton.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Skeleton.cs @@ -30,286 +30,329 @@ *****************************************************************************/ using System; -using System.Collections.Generic; -namespace Spine3_1_07 { - public class Skeleton { - internal SkeletonData data; - internal ExposedList bones; - internal ExposedList slots; - internal ExposedList drawOrder; - internal ExposedList ikConstraints; - internal ExposedList transformConstraints; - private ExposedList updateCache = new ExposedList(); - internal Skin skin; - internal float r = 1, g = 1, b = 1, a = 1; - internal float time; - internal bool flipX, flipY; - internal float x, y; +namespace Spine3_1_07 +{ + public class Skeleton + { + internal SkeletonData data; + internal ExposedList bones; + internal ExposedList slots; + internal ExposedList drawOrder; + internal ExposedList ikConstraints; + internal ExposedList transformConstraints; + private ExposedList updateCache = new ExposedList(); + internal Skin skin; + internal float r = 1, g = 1, b = 1, a = 1; + internal float time; + internal bool flipX, flipY; + internal float x, y; - public SkeletonData Data { get { return data; } } - public ExposedList Bones { get { return bones; } } - public ExposedList Slots { get { return slots; } } - public ExposedList DrawOrder { get { return drawOrder; } } - public ExposedList IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } } - public Skin Skin { get { return skin; } set { skin = value; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } - public float Time { get { return time; } set { time = value; } } - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public bool FlipX { get { return flipX; } set { flipX = value; } } - public bool FlipY { get { return flipY; } set { flipY = value; } } + public SkeletonData Data { get { return data; } } + public ExposedList Bones { get { return bones; } } + public ExposedList Slots { get { return slots; } } + public ExposedList DrawOrder { get { return drawOrder; } } + public ExposedList IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } } + public Skin Skin { get { return skin; } set { skin = value; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } + public float Time { get { return time; } set { time = value; } } + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public bool FlipX { get { return flipX; } set { flipX = value; } } + public bool FlipY { get { return flipY; } set { flipY = value; } } - public Bone RootBone { - get { - return bones.Count == 0 ? null : bones.Items[0]; - } - } + public Bone RootBone + { + get + { + return bones.Count == 0 ? null : bones.Items[0]; + } + } - public Skeleton (SkeletonData data) { - if (data == null) throw new ArgumentNullException("data cannot be null."); - this.data = data; + public Skeleton(SkeletonData data) + { + if (data == null) throw new ArgumentNullException("data cannot be null."); + this.data = data; - bones = new ExposedList(data.bones.Count); - foreach (BoneData boneData in data.bones) { - Bone parent = boneData.parent == null ? null : bones.Items[data.bones.IndexOf(boneData.parent)]; - Bone bone = new Bone(boneData, this, parent); - if (parent != null) parent.children.Add(bone); - bones.Add(bone); - } + bones = new ExposedList(data.bones.Count); + foreach (BoneData boneData in data.bones) + { + Bone parent = boneData.parent == null ? null : bones.Items[data.bones.IndexOf(boneData.parent)]; + Bone bone = new Bone(boneData, this, parent); + if (parent != null) parent.children.Add(bone); + bones.Add(bone); + } - slots = new ExposedList(data.slots.Count); - drawOrder = new ExposedList(data.slots.Count); - foreach (SlotData slotData in data.slots) { - Bone bone = bones.Items[data.bones.IndexOf(slotData.boneData)]; - Slot slot = new Slot(slotData, bone); - slots.Add(slot); - drawOrder.Add(slot); - } + slots = new ExposedList(data.slots.Count); + drawOrder = new ExposedList(data.slots.Count); + foreach (SlotData slotData in data.slots) + { + Bone bone = bones.Items[data.bones.IndexOf(slotData.boneData)]; + Slot slot = new Slot(slotData, bone); + slots.Add(slot); + drawOrder.Add(slot); + } - ikConstraints = new ExposedList(data.ikConstraints.Count); - foreach (IkConstraintData ikConstraintData in data.ikConstraints) - ikConstraints.Add(new IkConstraint(ikConstraintData, this)); + ikConstraints = new ExposedList(data.ikConstraints.Count); + foreach (IkConstraintData ikConstraintData in data.ikConstraints) + ikConstraints.Add(new IkConstraint(ikConstraintData, this)); - transformConstraints = new ExposedList(data.transformConstraints.Count); - foreach (TransformConstraintData transformConstraintData in data.transformConstraints) - transformConstraints.Add(new TransformConstraint(transformConstraintData, this)); + transformConstraints = new ExposedList(data.transformConstraints.Count); + foreach (TransformConstraintData transformConstraintData in data.transformConstraints) + transformConstraints.Add(new TransformConstraint(transformConstraintData, this)); - UpdateCache(); - UpdateWorldTransform(); - } + UpdateCache(); + UpdateWorldTransform(); + } - /// Caches information about bones and constraints. Must be called if bones or constraints are added - /// or removed. - public void UpdateCache () { - ExposedList bones = this.bones; - ExposedList updateCache = this.updateCache; - ExposedList ikConstraints = this.ikConstraints; - ExposedList transformConstraints = this.transformConstraints; - int ikConstraintsCount = ikConstraints.Count; - int transformConstraintsCount = transformConstraints.Count; - updateCache.Clear(); - for (int i = 0, n = bones.Count; i < n; i++) { - Bone bone = bones.Items[i]; - updateCache.Add(bone); - for (int ii = 0; ii < ikConstraintsCount; ii++) { - IkConstraint ikConstraint = ikConstraints.Items[ii]; - if (bone == ikConstraint.bones.Items[ikConstraint.bones.Count - 1]) { - updateCache.Add(ikConstraint); - break; - } - } - } + /// Caches information about bones and constraints. Must be called if bones or constraints are added + /// or removed. + public void UpdateCache() + { + ExposedList bones = this.bones; + ExposedList updateCache = this.updateCache; + ExposedList ikConstraints = this.ikConstraints; + ExposedList transformConstraints = this.transformConstraints; + int ikConstraintsCount = ikConstraints.Count; + int transformConstraintsCount = transformConstraints.Count; + updateCache.Clear(); + for (int i = 0, n = bones.Count; i < n; i++) + { + Bone bone = bones.Items[i]; + updateCache.Add(bone); + for (int ii = 0; ii < ikConstraintsCount; ii++) + { + IkConstraint ikConstraint = ikConstraints.Items[ii]; + if (bone == ikConstraint.bones.Items[ikConstraint.bones.Count - 1]) + { + updateCache.Add(ikConstraint); + break; + } + } + } - for (int i = 0; i < transformConstraintsCount; i++) { - TransformConstraint transformConstraint = transformConstraints.Items[i]; - for (int ii = updateCache.Count - 1; i >= 0; ii--) { - IUpdatable updateable = updateCache.Items[ii]; - if (updateable == transformConstraint.bone || updateable == transformConstraint.target) { - updateCache.Insert(ii + 1, transformConstraint); - break; - } - } - } - } + for (int i = 0; i < transformConstraintsCount; i++) + { + TransformConstraint transformConstraint = transformConstraints.Items[i]; + for (int ii = updateCache.Count - 1; i >= 0; ii--) + { + IUpdatable updateable = updateCache.Items[ii]; + if (updateable == transformConstraint.bone || updateable == transformConstraint.target) + { + updateCache.Insert(ii + 1, transformConstraint); + break; + } + } + } + } - /// Updates the world transform for each bone and applies constraints. - public void UpdateWorldTransform () { - ExposedList updateCache = this.updateCache; - for (int i = 0, n = updateCache.Count; i < n; i++) - updateCache.Items[i].Update(); - } + /// Updates the world transform for each bone and applies constraints. + public void UpdateWorldTransform() + { + ExposedList updateCache = this.updateCache; + for (int i = 0, n = updateCache.Count; i < n; i++) + updateCache.Items[i].Update(); + } - /// Sets the bones, constraints, and slots to their setup pose values. - public void SetToSetupPose () { - SetBonesToSetupPose(); - SetSlotsToSetupPose(); - } + /// Sets the bones, constraints, and slots to their setup pose values. + public void SetToSetupPose() + { + SetBonesToSetupPose(); + SetSlotsToSetupPose(); + } - /// Sets the bones and constraints to their setup pose values. - public void SetBonesToSetupPose () { - ExposedList bones = this.bones; - for (int i = 0, n = bones.Count; i < n; i++) - bones.Items[i].SetToSetupPose(); + /// Sets the bones and constraints to their setup pose values. + public void SetBonesToSetupPose() + { + ExposedList bones = this.bones; + for (int i = 0, n = bones.Count; i < n; i++) + bones.Items[i].SetToSetupPose(); - ExposedList ikConstraints = this.ikConstraints; - for (int i = 0, n = ikConstraints.Count; i < n; i++) { - IkConstraint constraint = ikConstraints.Items[i]; - constraint.bendDirection = constraint.data.bendDirection; - constraint.mix = constraint.data.mix; - } + ExposedList ikConstraints = this.ikConstraints; + for (int i = 0, n = ikConstraints.Count; i < n; i++) + { + IkConstraint constraint = ikConstraints.Items[i]; + constraint.bendDirection = constraint.data.bendDirection; + constraint.mix = constraint.data.mix; + } - ExposedList transformConstraints = this.transformConstraints; - for (int i = 0, n = transformConstraints.Count; i < n; i++) { - TransformConstraint constraint = transformConstraints.Items[i]; - constraint.translateMix = constraint.data.translateMix; - constraint.x = constraint.data.x; - constraint.y = constraint.data.y; - } - } + ExposedList transformConstraints = this.transformConstraints; + for (int i = 0, n = transformConstraints.Count; i < n; i++) + { + TransformConstraint constraint = transformConstraints.Items[i]; + constraint.translateMix = constraint.data.translateMix; + constraint.x = constraint.data.x; + constraint.y = constraint.data.y; + } + } - public void SetSlotsToSetupPose () { - ExposedList slots = this.slots; - drawOrder.Clear(); - for (int i = 0, n = slots.Count; i < n; i++) - drawOrder.Add(slots.Items[i]); + public void SetSlotsToSetupPose() + { + ExposedList slots = this.slots; + drawOrder.Clear(); + for (int i = 0, n = slots.Count; i < n; i++) + drawOrder.Add(slots.Items[i]); - for (int i = 0, n = slots.Count; i < n; i++) - slots.Items[i].SetToSetupPose(i); - } + for (int i = 0, n = slots.Count; i < n; i++) + slots.Items[i].SetToSetupPose(i); + } - /// May be null. - public Bone FindBone (String boneName) { - if (boneName == null) throw new ArgumentNullException("boneName cannot be null."); - ExposedList bones = this.bones; - for (int i = 0, n = bones.Count; i < n; i++) { - Bone bone = bones.Items[i]; - if (bone.data.name == boneName) return bone; - } - return null; - } + /// May be null. + public Bone FindBone(String boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName cannot be null."); + ExposedList bones = this.bones; + for (int i = 0, n = bones.Count; i < n; i++) + { + Bone bone = bones.Items[i]; + if (bone.data.name == boneName) return bone; + } + return null; + } - /// -1 if the bone was not found. - public int FindBoneIndex (String boneName) { - if (boneName == null) throw new ArgumentNullException("boneName cannot be null."); - ExposedList bones = this.bones; - for (int i = 0, n = bones.Count; i < n; i++) - if (bones.Items[i].data.name == boneName) return i; - return -1; - } + /// -1 if the bone was not found. + public int FindBoneIndex(String boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName cannot be null."); + ExposedList bones = this.bones; + for (int i = 0, n = bones.Count; i < n; i++) + if (bones.Items[i].data.name == boneName) return i; + return -1; + } - /// May be null. - public Slot FindSlot (String slotName) { - if (slotName == null) throw new ArgumentNullException("slotName cannot be null."); - ExposedList slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) { - Slot slot = slots.Items[i]; - if (slot.data.name == slotName) return slot; - } - return null; - } + /// May be null. + public Slot FindSlot(String slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName cannot be null."); + ExposedList slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + { + Slot slot = slots.Items[i]; + if (slot.data.name == slotName) return slot; + } + return null; + } - /// -1 if the bone was not found. - public int FindSlotIndex (String slotName) { - if (slotName == null) throw new ArgumentNullException("slotName cannot be null."); - ExposedList slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) - if (slots.Items[i].data.name.Equals(slotName)) return i; - return -1; - } + /// -1 if the bone was not found. + public int FindSlotIndex(String slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName cannot be null."); + ExposedList slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + if (slots.Items[i].data.name.Equals(slotName)) return i; + return -1; + } - /// Sets a skin by name (see SetSkin). - public void SetSkin (String skinName) { - Skin skin = data.FindSkin(skinName); - if (skin == null) throw new ArgumentException("Skin not found: " + skinName); - SetSkin(skin); - } + /// Sets a skin by name (see SetSkin). + public void SetSkin(String skinName) + { + Skin skin = data.FindSkin(skinName); + if (skin == null) throw new ArgumentException("Skin not found: " + skinName); + SetSkin(skin); + } - /// Sets the skin used to look up attachments before looking in the {@link SkeletonData#getDefaultSkin() default - /// skin}. Attachmentsfrom 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. - /// May be null. - public void SetSkin (Skin newSkin) { - if (newSkin != null) { - if (skin != null) - newSkin.AttachAll(this, skin); - else { - ExposedList slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) { - Slot slot = slots.Items[i]; - String name = slot.data.attachmentName; - if (name != null) { - Attachment attachment = newSkin.GetAttachment(i, name); - if (attachment != null) slot.Attachment = attachment; - } - } - } - } - skin = newSkin; - } + /// Sets the skin used to look up attachments before looking in the {@link SkeletonData#getDefaultSkin() default + /// skin}. Attachmentsfrom 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. + /// May be null. + public void SetSkin(Skin newSkin) + { + if (newSkin != null) + { + if (skin != null) + newSkin.AttachAll(this, skin); + else + { + ExposedList slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + { + Slot slot = slots.Items[i]; + String name = slot.data.attachmentName; + if (name != null) + { + Attachment attachment = newSkin.GetAttachment(i, name); + if (attachment != null) slot.Attachment = attachment; + } + } + } + } + skin = newSkin; + } - /// May be null. - public Attachment GetAttachment (String slotName, String attachmentName) { - return GetAttachment(data.FindSlotIndex(slotName), attachmentName); - } + /// May be null. + public Attachment GetAttachment(String slotName, String attachmentName) + { + return GetAttachment(data.FindSlotIndex(slotName), attachmentName); + } - /// May be null. - public Attachment GetAttachment (int slotIndex, String attachmentName) { - if (attachmentName == null) throw new ArgumentNullException("attachmentName cannot be null."); - if (skin != null) { - Attachment attachment = skin.GetAttachment(slotIndex, attachmentName); - if (attachment != null) return attachment; - } - if (data.defaultSkin != null) return data.defaultSkin.GetAttachment(slotIndex, attachmentName); - return null; - } + /// May be null. + public Attachment GetAttachment(int slotIndex, String attachmentName) + { + if (attachmentName == null) throw new ArgumentNullException("attachmentName cannot be null."); + if (skin != null) + { + Attachment attachment = skin.GetAttachment(slotIndex, attachmentName); + if (attachment != null) return attachment; + } + if (data.defaultSkin != null) return data.defaultSkin.GetAttachment(slotIndex, attachmentName); + return null; + } - /// May be null. - public void SetAttachment (String slotName, String attachmentName) { - if (slotName == null) throw new ArgumentNullException("slotName cannot be null."); - ExposedList slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) { - Slot slot = slots.Items[i]; - if (slot.data.name == slotName) { - Attachment attachment = null; - if (attachmentName != null) { - attachment = GetAttachment(i, attachmentName); - if (attachment == null) throw new Exception("Attachment not found: " + attachmentName + ", for slot: " + slotName); - } - slot.Attachment = attachment; - return; - } - } - throw new Exception("Slot not found: " + slotName); - } + /// May be null. + public void SetAttachment(String slotName, String attachmentName) + { + if (slotName == null) throw new ArgumentNullException("slotName cannot be null."); + ExposedList slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + { + Slot slot = slots.Items[i]; + if (slot.data.name == slotName) + { + Attachment attachment = null; + if (attachmentName != null) + { + attachment = GetAttachment(i, attachmentName); + if (attachment == null) throw new Exception("Attachment not found: " + attachmentName + ", for slot: " + slotName); + } + slot.Attachment = attachment; + return; + } + } + throw new Exception("Slot not found: " + slotName); + } - /** @return May be null. */ - public IkConstraint FindIkConstraint (String constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName cannot be null."); - ExposedList ikConstraints = this.ikConstraints; - for (int i = 0, n = ikConstraints.Count; i < n; i++) { - IkConstraint ikConstraint = ikConstraints.Items[i]; - if (ikConstraint.data.name == constraintName) return ikConstraint; - } - return null; - } + /** @return May be null. */ + public IkConstraint FindIkConstraint(String constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName cannot be null."); + ExposedList ikConstraints = this.ikConstraints; + for (int i = 0, n = ikConstraints.Count; i < n; i++) + { + IkConstraint ikConstraint = ikConstraints.Items[i]; + if (ikConstraint.data.name == constraintName) return ikConstraint; + } + return null; + } - /** @return May be null. */ - public TransformConstraint FindTransformConstraint (String constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName cannot be null."); - ExposedList transformConstraints = this.transformConstraints; - for (int i = 0, n = transformConstraints.Count; i < n; i++) { - TransformConstraint transformConstraint = transformConstraints.Items[i]; - if (transformConstraint.data.name == constraintName) return transformConstraint; - } - return null; - } + /** @return May be null. */ + public TransformConstraint FindTransformConstraint(String constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName cannot be null."); + ExposedList transformConstraints = this.transformConstraints; + for (int i = 0, n = transformConstraints.Count; i < n; i++) + { + TransformConstraint transformConstraint = transformConstraints.Items[i]; + if (transformConstraint.data.name == constraintName) return transformConstraint; + } + return null; + } - public void Update (float delta) { - time += delta; - } - } + public void Update(float delta) + { + time += delta; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/SkeletonBinary.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/SkeletonBinary.cs index f488ac2..400d19f 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/SkeletonBinary.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/SkeletonBinary.cs @@ -30,44 +30,48 @@ *****************************************************************************/ using System; -using System.IO; using System.Collections.Generic; +using System.IO; #if WINDOWS_STOREAPP using System.Threading.Tasks; using Windows.Storage; #endif -namespace Spine3_1_07 { - public class SkeletonBinary { - public const int TIMELINE_SCALE = 0; - public const int TIMELINE_ROTATE = 1; - public const int TIMELINE_TRANSLATE = 2; - public const int TIMELINE_ATTACHMENT = 3; - public const int TIMELINE_COLOR = 4; - - public const int CURVE_LINEAR = 0; - public const int CURVE_STEPPED = 1; - public const int CURVE_BEZIER = 2; - - public float Scale { get; set; } - - private AttachmentLoader attachmentLoader; - private byte[] buffer = new byte[32]; - private List linkedMeshes = new List(); - - public SkeletonBinary (params Atlas[] atlasArray) - : this(new AtlasAttachmentLoader(atlasArray)) { - } - - public SkeletonBinary (AttachmentLoader attachmentLoader) { - if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader"); - this.attachmentLoader = attachmentLoader; - Scale = 1; - } - - #if !(UNITY_5 || UNITY_4 || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1) - #if WINDOWS_STOREAPP +namespace Spine3_1_07 +{ + public class SkeletonBinary + { + public const int TIMELINE_SCALE = 0; + public const int TIMELINE_ROTATE = 1; + public const int TIMELINE_TRANSLATE = 2; + public const int TIMELINE_ATTACHMENT = 3; + public const int TIMELINE_COLOR = 4; + + public const int CURVE_LINEAR = 0; + public const int CURVE_STEPPED = 1; + public const int CURVE_BEZIER = 2; + + public float Scale { get; set; } + + private AttachmentLoader attachmentLoader; + private byte[] buffer = new byte[32]; + private List linkedMeshes = new List(); + + public SkeletonBinary(params Atlas[] atlasArray) + : this(new AtlasAttachmentLoader(atlasArray)) + { + } + + public SkeletonBinary(AttachmentLoader attachmentLoader) + { + if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader"); + this.attachmentLoader = attachmentLoader; + Scale = 1; + } + +#if !(UNITY_5 || UNITY_4 || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1) +#if WINDOWS_STOREAPP private async Task ReadFile(string path) { var folder = Windows.ApplicationModel.Package.Current.InstalledLocation; @@ -81,652 +85,739 @@ private async Task ReadFile(string path) { public SkeletonData ReadSkeletonData (String path) { return this.ReadFile(path).Result; } - #else - public SkeletonData ReadSkeletonData (String path) { - #if WINDOWS_PHONE +#else + public SkeletonData ReadSkeletonData(String path) + { +#if WINDOWS_PHONE using (var input = new BufferedStream(Microsoft.Xna.Framework.TitleContainer.OpenStream(path))) { - #else - using (var input = new BufferedStream(new FileStream(path, FileMode.Open))) { - #endif // WINDOWS_PHONE - SkeletonData skeletonData = ReadSkeletonData(input); - skeletonData.name = Path.GetFileNameWithoutExtension(path); - return skeletonData; - } - } - - #endif // WINDOWS_STOREAPP - #endif // !(UNITY) - - public SkeletonData ReadSkeletonData (Stream input) { - if (input == null) throw new ArgumentNullException("input"); - float scale = Scale; - - var skeletonData = new SkeletonData(); - skeletonData.hash = ReadString(input); - if (skeletonData.hash.Length == 0) skeletonData.hash = null; - skeletonData.version = ReadString(input); - if (skeletonData.version.Length == 0) skeletonData.version = null; - skeletonData.width = ReadFloat(input); - skeletonData.height = ReadFloat(input); - - bool nonessential = ReadBoolean(input); - - if (nonessential) { - skeletonData.imagesPath = ReadString(input); - if (skeletonData.imagesPath.Length == 0) skeletonData.imagesPath = null; - } - - // Bones. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - String name = ReadString(input); - BoneData parent = i == 0 ? null : skeletonData.bones.Items[ReadVarint(input, true)]; - BoneData boneData = new BoneData(name, parent); - boneData.x = ReadFloat(input) * scale; - boneData.y = ReadFloat(input) * scale; - boneData.scaleX = ReadFloat(input); - boneData.scaleY = ReadFloat(input); - boneData.rotation = ReadFloat(input); - boneData.length = ReadFloat(input) * scale; - boneData.inheritScale = ReadBoolean(input); - boneData.inheritRotation = ReadBoolean(input); - if (nonessential) ReadInt(input); // Skip bone color. - skeletonData.bones.Add(boneData); - } - - // IK constraints. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - IkConstraintData ikConstraintData = new IkConstraintData(ReadString(input)); - for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) - ikConstraintData.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]); - ikConstraintData.target = skeletonData.bones.Items[ReadVarint(input, true)]; - ikConstraintData.mix = ReadFloat(input); - ikConstraintData.bendDirection = ReadSByte(input); - skeletonData.ikConstraints.Add(ikConstraintData); - } - - // Transform constraints. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - TransformConstraintData transformConstraintData = new TransformConstraintData(ReadString(input)); - transformConstraintData.bone = skeletonData.bones.Items[ReadVarint(input, true)]; - transformConstraintData.target = skeletonData.bones.Items[ReadVarint(input, true)]; - transformConstraintData.translateMix = ReadFloat(input); - transformConstraintData.x = ReadFloat(input) * scale; - transformConstraintData.y = ReadFloat(input) * scale; - skeletonData.transformConstraints.Add(transformConstraintData); - } - - // Slots. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - String slotName = ReadString(input); - BoneData boneData = skeletonData.bones.Items[ReadVarint(input, true)]; - SlotData slotData = new SlotData(slotName, boneData); - int color = ReadInt(input); - slotData.r = ((color & 0xff000000) >> 24) / 255f; - slotData.g = ((color & 0x00ff0000) >> 16) / 255f; - slotData.b = ((color & 0x0000ff00) >> 8) / 255f; - slotData.a = ((color & 0x000000ff)) / 255f; - slotData.attachmentName = ReadString(input); - slotData.blendMode = (BlendMode)ReadVarint(input, true); - skeletonData.slots.Add(slotData); - } - - // Default skin. - Skin defaultSkin = ReadSkin(input, "default", nonessential); - if (defaultSkin != null) { - skeletonData.defaultSkin = defaultSkin; - skeletonData.skins.Add(defaultSkin); - } - - // Skins. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) - skeletonData.skins.Add(ReadSkin(input, ReadString(input), nonessential)); - - // Linked meshes. - for (int i = 0, n = linkedMeshes.Count; i < n; i++) { - SkeletonJson.LinkedMesh linkedMesh = linkedMeshes[i]; - Skin skin = linkedMesh.skin == null ? skeletonData.DefaultSkin : skeletonData.FindSkin(linkedMesh.skin); - if (skin == null) throw new Exception("Skin not found: " + linkedMesh.skin); - Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); - if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); - if (linkedMesh.mesh is MeshAttachment) { - MeshAttachment mesh = (MeshAttachment)linkedMesh.mesh; - mesh.ParentMesh = (MeshAttachment)parent; - mesh.UpdateUVs(); - } else { - WeightedMeshAttachment mesh = (WeightedMeshAttachment)linkedMesh.mesh; - mesh.ParentMesh = (WeightedMeshAttachment)parent; - mesh.UpdateUVs(); - } - } - linkedMeshes.Clear(); - - // Events. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - EventData eventData = new EventData(ReadString(input)); - eventData.Int = ReadVarint(input, false); - eventData.Float = ReadFloat(input); - eventData.String = ReadString(input); - skeletonData.events.Add(eventData); - } - - // Animations. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) - ReadAnimation(ReadString(input), input, skeletonData); - - skeletonData.bones.TrimExcess(); - skeletonData.slots.TrimExcess(); - skeletonData.skins.TrimExcess(); - skeletonData.events.TrimExcess(); - skeletonData.animations.TrimExcess(); - skeletonData.ikConstraints.TrimExcess(); - return skeletonData; - } - - /** @return May be null. */ - private Skin ReadSkin (Stream input, String skinName, bool nonessential) { - int slotCount = ReadVarint(input, true); - if (slotCount == 0) return null; - Skin skin = new Skin(skinName); - 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 = ReadString(input); - skin.AddAttachment(slotIndex, name, ReadAttachment(input, skin, slotIndex, name, nonessential)); - } - } - return skin; - } - - private Attachment ReadAttachment (Stream input, Skin skin, int slotIndex, String attachmentName, bool nonessential) { - float scale = Scale; - - String name = ReadString(input); - if (name == null) name = attachmentName; - - AttachmentType type = (AttachmentType)input.ReadByte(); - switch (type) { - case AttachmentType.region: { - String path = ReadString(input); - float x = ReadFloat(input); - float y = ReadFloat(input); - float scaleX = ReadFloat(input); - float scaleY = ReadFloat(input); - float rotation = ReadFloat(input); - float width = ReadFloat(input); - float height = ReadFloat(input); - int color = ReadInt(input); - - if (path == null) path = name; - RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); - if (region == null) return null; - region.Path = path; - region.x = x * scale; - region.y = y * scale; - region.scaleX = scaleX; - region.scaleY = scaleY; - region.rotation = rotation; - region.width = width * scale; - region.height = height * scale; - region.r = ((color & 0xff000000) >> 24) / 255f; - region.g = ((color & 0x00ff0000) >> 16) / 255f; - region.b = ((color & 0x0000ff00) >> 8) / 255f; - region.a = ((color & 0x000000ff)) / 255f; - region.UpdateOffset(); - return region; - } - case AttachmentType.boundingbox: { - float[] vertices = ReadFloatArray(input, ReadVarint(input, true) * 2, scale); - BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); - if (box == null) return null; - box.vertices = vertices; - return box; - } - case AttachmentType.mesh: { - String path = ReadString(input); - int color = ReadInt(input); - int hullLength = 0; - int verticesLength = ReadVarint(input, true) * 2; - float[] uvs = ReadFloatArray(input, verticesLength, 1); - int[] triangles = ReadShortArray(input); - float[] vertices = ReadFloatArray(input, verticesLength, scale); - hullLength = ReadVarint(input, true); - int[] edges = null; - float width = 0, height = 0; - if (nonessential) { - edges = ReadShortArray(input); - width = ReadFloat(input); - height = ReadFloat(input); - } - - if (path == null) path = name; - MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); - if (mesh == null) return null; - mesh.Path = path; - mesh.r = ((color & 0xff000000) >> 24) / 255f; - mesh.g = ((color & 0x00ff0000) >> 16) / 255f; - mesh.b = ((color & 0x0000ff00) >> 8) / 255f; - mesh.a = ((color & 0x000000ff)) / 255f; - mesh.vertices = vertices; - mesh.triangles = triangles; - mesh.regionUVs = uvs; - mesh.UpdateUVs(); - mesh.HullLength = hullLength; - if (nonessential) { - mesh.Edges = edges; - mesh.Width = width * scale; - mesh.Height = height * scale; - } - return mesh; - } - case AttachmentType.linkedmesh: { - String path = ReadString(input); - int color = ReadInt(input); - String skinName = ReadString(input); - String parent = ReadString(input); - bool inheritFFD = ReadBoolean(input); - float width = 0, height = 0; - if (nonessential) { - width = ReadFloat(input); - height = ReadFloat(input); - } - - if (path == null) path = name; - MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); - if (mesh == null) return null; - mesh.Path = path; - mesh.r = ((color & 0xff000000) >> 24) / 255f; - mesh.g = ((color & 0x00ff0000) >> 16) / 255f; - mesh.b = ((color & 0x0000ff00) >> 8) / 255f; - mesh.a = ((color & 0x000000ff)) / 255f; - mesh.inheritFFD = inheritFFD; - if (nonessential) { - mesh.Width = width * scale; - mesh.Height = height * scale; - } - linkedMeshes.Add(new SkeletonJson.LinkedMesh(mesh, skinName, slotIndex, parent)); - return mesh; - } - case AttachmentType.weightedmesh: { - String path = ReadString(input); - int color = ReadInt(input); - int vertexCount = ReadVarint(input, true); - float[] uvs = ReadFloatArray(input, vertexCount * 2, 1); - int[] triangles = ReadShortArray(input); - var weights = new List(uvs.Length * 3 * 3); - var bones = new List(uvs.Length * 3); - for (int i = 0; i < vertexCount; i++) { - int boneCount = (int)ReadFloat(input); - bones.Add(boneCount); - for (int ii = 0; ii < boneCount; ii++) { - bones.Add((int)ReadFloat(input)); - weights.Add(ReadFloat(input) * scale); - weights.Add(ReadFloat(input) * scale); - weights.Add(ReadFloat(input)); - } - } - int hullLength = ReadVarint(input, true); - int[] edges = null; - float width = 0, height = 0; - if (nonessential) { - edges = ReadShortArray(input); - width = ReadFloat(input); - height = ReadFloat(input); - } - - if (path == null) path = name; - WeightedMeshAttachment mesh = attachmentLoader.NewWeightedMeshAttachment(skin, name, path); - if (mesh == null) return null; - mesh.Path = path; - mesh.r = ((color & 0xff000000) >> 24) / 255f; - mesh.g = ((color & 0x00ff0000) >> 16) / 255f; - mesh.b = ((color & 0x0000ff00) >> 8) / 255f; - mesh.a = ((color & 0x000000ff)) / 255f; - mesh.bones = bones.ToArray(); - mesh.weights = weights.ToArray(); - mesh.triangles = triangles; - mesh.regionUVs = uvs; - mesh.UpdateUVs(); - mesh.HullLength = hullLength * 2; - if (nonessential) { - mesh.Edges = edges; - mesh.Width = width * scale; - mesh.Height = height * scale; - } - // - return mesh; - } - case AttachmentType.weightedlinkedmesh: { - String path = ReadString(input); - int color = ReadInt(input); - String skinName = ReadString(input); - String parent = ReadString(input); - bool inheritFFD = ReadBoolean(input); - float width = 0, height = 0; - if (nonessential) { - width = ReadFloat(input); - height = ReadFloat(input); - } - - if (path == null) path = name; - WeightedMeshAttachment mesh = attachmentLoader.NewWeightedMeshAttachment(skin, name, path); - if (mesh == null) return null; - mesh.Path = path; - mesh.r = ((color & 0xff000000) >> 24) / 255f; - mesh.g = ((color & 0x00ff0000) >> 16) / 255f; - mesh.b = ((color & 0x0000ff00) >> 8) / 255f; - mesh.a = ((color & 0x000000ff)) / 255f; - mesh.inheritFFD = inheritFFD; - if (nonessential) { - mesh.Width = width * scale; - mesh.Height = height * scale; - } - linkedMeshes.Add(new SkeletonJson.LinkedMesh(mesh, skinName, slotIndex, parent)); - return mesh; - } - } - return null; - } - - private float[] ReadFloatArray (Stream input, int n, float scale) { - float[] array = new float[n]; - if (scale == 1) { - for (int i = 0; i < n; i++) - array[i] = ReadFloat(input); - } else { - for (int i = 0; i < n; i++) - array[i] = ReadFloat(input) * scale; - } - return array; - } - - private int[] ReadShortArray (Stream input) { - int n = ReadVarint(input, true); - int[] array = new int[n]; - for (int i = 0; i < n; i++) - array[i] = (input.ReadByte() << 8) | input.ReadByte(); - return array; - } - - private void ReadAnimation (String name, Stream input, SkeletonData skeletonData) { - var timelines = new ExposedList(); - float scale = Scale; - float duration = 0; - - // 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++) { - int timelineType = input.ReadByte(); - int frameCount = ReadVarint(input, true); - switch (timelineType) { - case TIMELINE_COLOR: { - ColorTimeline timeline = new ColorTimeline(frameCount); - timeline.slotIndex = slotIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - float time = ReadFloat(input); - int color = ReadInt(input); - float r = ((color & 0xff000000) >> 24) / 255f; - float g = ((color & 0x00ff0000) >> 16) / 255f; - float b = ((color & 0x0000ff00) >> 8) / 255f; - float a = ((color & 0x000000ff)) / 255f; - timeline.SetFrame(frameIndex, time, r, g, b, a); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[frameCount * 5 - 5]); - break; - } - case TIMELINE_ATTACHMENT: { - AttachmentTimeline timeline = new AttachmentTimeline(frameCount); - timeline.slotIndex = slotIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) - timeline.SetFrame(frameIndex, ReadFloat(input), ReadString(input)); - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[frameCount - 1]); - break; - } - } - } - } - - // 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++) { - int timelineType = input.ReadByte(); - int frameCount = ReadVarint(input, true); - switch (timelineType) { - case TIMELINE_ROTATE: { - RotateTimeline timeline = new RotateTimeline(frameCount); - timeline.boneIndex = boneIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input)); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[frameCount * 2 - 2]); - break; - } - case TIMELINE_TRANSLATE: - case TIMELINE_SCALE: { - TranslateTimeline timeline; - float timelineScale = 1; - if (timelineType == TIMELINE_SCALE) - timeline = new ScaleTimeline(frameCount); - else { - timeline = new TranslateTimeline(frameCount); - timelineScale = scale; - } - timeline.boneIndex = boneIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input) * timelineScale, ReadFloat(input) - * timelineScale); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[frameCount * 3 - 3]); - break; - } - } - } - } - - // IK timelines. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - IkConstraintData ikConstraint = skeletonData.ikConstraints.Items[ReadVarint(input, true)]; - int frameCount = ReadVarint(input, true); - IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount); - timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(ikConstraint); - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadSByte(input)); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[frameCount * 3 - 3]); - } - - // FFD timelines. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - Skin skin = skeletonData.skins.Items[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++) { - Attachment attachment = skin.GetAttachment(slotIndex, ReadString(input)); - int frameCount = ReadVarint(input, true); - FfdTimeline timeline = new FfdTimeline(frameCount); - timeline.slotIndex = slotIndex; - timeline.attachment = attachment; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - float time = ReadFloat(input); - - float[] vertices; - int vertexCount; - if (attachment is MeshAttachment) - vertexCount = ((MeshAttachment)attachment).vertices.Length; - else - vertexCount = ((WeightedMeshAttachment)attachment).weights.Length / 3 * 2; - - int end = ReadVarint(input, true); - if (end == 0) { - if (attachment is MeshAttachment) - vertices = ((MeshAttachment)attachment).vertices; - else - vertices = new float[vertexCount]; - } else { - vertices = new float[vertexCount]; - int start = ReadVarint(input, true); - end += start; - if (scale == 1) { - for (int v = start; v < end; v++) - vertices[v] = ReadFloat(input); - } else { - for (int v = start; v < end; v++) - vertices[v] = ReadFloat(input) * scale; - } - if (attachment is MeshAttachment) { - float[] meshVertices = ((MeshAttachment)attachment).vertices; - for (int v = 0, vn = vertices.Length; v < vn; v++) - vertices[v] += meshVertices[v]; - } - } - - timeline.SetFrame(frameIndex, time, vertices); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[frameCount - 1]); - } - } - } - - // Draw order timeline. - int drawOrderCount = ReadVarint(input, true); - if (drawOrderCount > 0) { - DrawOrderTimeline timeline = new DrawOrderTimeline(drawOrderCount); - int slotCount = skeletonData.slots.Count; - for (int i = 0; i < drawOrderCount; i++) { - float time = ReadFloat(input); - int offsetCount = ReadVarint(input, true); - int[] drawOrder = new int[slotCount]; - for (int ii = slotCount - 1; ii >= 0; ii--) - drawOrder[ii] = -1; - int[] unchanged = new int[slotCount - offsetCount]; - int originalIndex = 0, unchangedIndex = 0; - for (int ii = 0; ii < offsetCount; ii++) { - int slotIndex = ReadVarint(input, true); - // Collect unchanged items. - while (originalIndex != slotIndex) - unchanged[unchangedIndex++] = originalIndex++; - // Set changed items. - drawOrder[originalIndex + ReadVarint(input, true)] = originalIndex++; - } - // Collect remaining unchanged items. - while (originalIndex < slotCount) - unchanged[unchangedIndex++] = originalIndex++; - // Fill in unchanged items. - for (int ii = slotCount - 1; ii >= 0; ii--) - if (drawOrder[ii] == -1) drawOrder[ii] = unchanged[--unchangedIndex]; - timeline.SetFrame(i, time, drawOrder); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[drawOrderCount - 1]); - } - - // Event timeline. - int eventCount = ReadVarint(input, true); - if (eventCount > 0) { - EventTimeline timeline = new EventTimeline(eventCount); - for (int i = 0; i < eventCount; i++) { - float time = ReadFloat(input); - EventData eventData = skeletonData.events.Items[ReadVarint(input, true)]; - Event e = new Event(time, eventData); - e.Int = ReadVarint(input, false); - e.Float = ReadFloat(input); - e.String = ReadBoolean(input) ? ReadString(input) : eventData.String; - timeline.SetFrame(i, e); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[eventCount - 1]); - } - - timelines.TrimExcess(); - skeletonData.animations.Add(new Animation(name, timelines, duration)); - } - - private void ReadCurve (Stream input, int frameIndex, CurveTimeline timeline) { - switch (input.ReadByte()) { - case CURVE_STEPPED: - timeline.SetStepped(frameIndex); - break; - case CURVE_BEZIER: - timeline.SetCurve(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input)); - break; - } - } - - private static sbyte ReadSByte (Stream input) { - int value = input.ReadByte(); - if (value == -1) throw new EndOfStreamException(); - return (sbyte)value; - } - - private static bool ReadBoolean (Stream input) { - return input.ReadByte() != 0; - } - - private float ReadFloat (Stream input) { - buffer[3] = (byte)input.ReadByte(); - buffer[2] = (byte)input.ReadByte(); - buffer[1] = (byte)input.ReadByte(); - buffer[0] = (byte)input.ReadByte(); - return BitConverter.ToSingle(buffer, 0); - } - - private static int ReadInt (Stream input) { - return (input.ReadByte() << 24) + (input.ReadByte() << 16) + (input.ReadByte() << 8) + input.ReadByte(); - } - - private static int ReadVarint (Stream input, bool optimizePositive) { - int b = input.ReadByte(); - int result = b & 0x7F; - if ((b & 0x80) != 0) { - b = input.ReadByte(); - result |= (b & 0x7F) << 7; - if ((b & 0x80) != 0) { - b = input.ReadByte(); - result |= (b & 0x7F) << 14; - if ((b & 0x80) != 0) { - b = input.ReadByte(); - result |= (b & 0x7F) << 21; - if ((b & 0x80) != 0) result |= (input.ReadByte() & 0x7F) << 28; - } - } - } - return optimizePositive ? result : ((result >> 1) ^ -(result & 1)); - } - - private string ReadString (Stream input) { - int byteCount = ReadVarint(input, true); - switch (byteCount) { - case 0: - return null; - case 1: - return ""; - } - byteCount--; - byte[] buffer = this.buffer; - if (buffer.Length < byteCount) buffer = new byte[byteCount]; - ReadFully(input, buffer, 0, byteCount); - return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount); - } - - private static void ReadFully (Stream input, byte[] buffer, int offset, int length) { - while (length > 0) { - int count = input.Read(buffer, offset, length); - if (count <= 0) throw new EndOfStreamException(); - offset += count; - length -= count; - } - } - } +#else + using (var input = new BufferedStream(new FileStream(path, FileMode.Open))) + { +#endif // WINDOWS_PHONE + SkeletonData skeletonData = ReadSkeletonData(input); + skeletonData.name = Path.GetFileNameWithoutExtension(path); + return skeletonData; + } + } + +#endif // WINDOWS_STOREAPP +#endif // !(UNITY) + + public SkeletonData ReadSkeletonData(Stream input) + { + if (input == null) throw new ArgumentNullException("input"); + float scale = Scale; + + var skeletonData = new SkeletonData(); + skeletonData.hash = ReadString(input); + if (skeletonData.hash.Length == 0) skeletonData.hash = null; + skeletonData.version = ReadString(input); + if (skeletonData.version.Length == 0) skeletonData.version = null; + skeletonData.width = ReadFloat(input); + skeletonData.height = ReadFloat(input); + + bool nonessential = ReadBoolean(input); + + if (nonessential) + { + skeletonData.imagesPath = ReadString(input); + if (skeletonData.imagesPath.Length == 0) skeletonData.imagesPath = null; + } + + // Bones. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + String name = ReadString(input); + BoneData parent = i == 0 ? null : skeletonData.bones.Items[ReadVarint(input, true)]; + BoneData boneData = new BoneData(name, parent); + boneData.x = ReadFloat(input) * scale; + boneData.y = ReadFloat(input) * scale; + boneData.scaleX = ReadFloat(input); + boneData.scaleY = ReadFloat(input); + boneData.rotation = ReadFloat(input); + boneData.length = ReadFloat(input) * scale; + boneData.inheritScale = ReadBoolean(input); + boneData.inheritRotation = ReadBoolean(input); + if (nonessential) ReadInt(input); // Skip bone color. + skeletonData.bones.Add(boneData); + } + + // IK constraints. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + IkConstraintData ikConstraintData = new IkConstraintData(ReadString(input)); + for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) + ikConstraintData.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]); + ikConstraintData.target = skeletonData.bones.Items[ReadVarint(input, true)]; + ikConstraintData.mix = ReadFloat(input); + ikConstraintData.bendDirection = ReadSByte(input); + skeletonData.ikConstraints.Add(ikConstraintData); + } + + // Transform constraints. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + TransformConstraintData transformConstraintData = new TransformConstraintData(ReadString(input)); + transformConstraintData.bone = skeletonData.bones.Items[ReadVarint(input, true)]; + transformConstraintData.target = skeletonData.bones.Items[ReadVarint(input, true)]; + transformConstraintData.translateMix = ReadFloat(input); + transformConstraintData.x = ReadFloat(input) * scale; + transformConstraintData.y = ReadFloat(input) * scale; + skeletonData.transformConstraints.Add(transformConstraintData); + } + + // Slots. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + String slotName = ReadString(input); + BoneData boneData = skeletonData.bones.Items[ReadVarint(input, true)]; + SlotData slotData = new SlotData(slotName, boneData); + int color = ReadInt(input); + slotData.r = ((color & 0xff000000) >> 24) / 255f; + slotData.g = ((color & 0x00ff0000) >> 16) / 255f; + slotData.b = ((color & 0x0000ff00) >> 8) / 255f; + slotData.a = ((color & 0x000000ff)) / 255f; + slotData.attachmentName = ReadString(input); + slotData.blendMode = (BlendMode)ReadVarint(input, true); + skeletonData.slots.Add(slotData); + } + + // Default skin. + Skin defaultSkin = ReadSkin(input, "default", nonessential); + if (defaultSkin != null) + { + skeletonData.defaultSkin = defaultSkin; + skeletonData.skins.Add(defaultSkin); + } + + // Skins. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + skeletonData.skins.Add(ReadSkin(input, ReadString(input), nonessential)); + + // Linked meshes. + for (int i = 0, n = linkedMeshes.Count; i < n; i++) + { + SkeletonJson.LinkedMesh linkedMesh = linkedMeshes[i]; + Skin skin = linkedMesh.skin == null ? skeletonData.DefaultSkin : skeletonData.FindSkin(linkedMesh.skin); + if (skin == null) throw new Exception("Skin not found: " + linkedMesh.skin); + Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); + if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); + if (linkedMesh.mesh is MeshAttachment) + { + MeshAttachment mesh = (MeshAttachment)linkedMesh.mesh; + mesh.ParentMesh = (MeshAttachment)parent; + mesh.UpdateUVs(); + } + else + { + WeightedMeshAttachment mesh = (WeightedMeshAttachment)linkedMesh.mesh; + mesh.ParentMesh = (WeightedMeshAttachment)parent; + mesh.UpdateUVs(); + } + } + linkedMeshes.Clear(); + + // Events. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + EventData eventData = new EventData(ReadString(input)); + eventData.Int = ReadVarint(input, false); + eventData.Float = ReadFloat(input); + eventData.String = ReadString(input); + skeletonData.events.Add(eventData); + } + + // Animations. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + ReadAnimation(ReadString(input), input, skeletonData); + + skeletonData.bones.TrimExcess(); + skeletonData.slots.TrimExcess(); + skeletonData.skins.TrimExcess(); + skeletonData.events.TrimExcess(); + skeletonData.animations.TrimExcess(); + skeletonData.ikConstraints.TrimExcess(); + return skeletonData; + } + + /** @return May be null. */ + private Skin ReadSkin(Stream input, String skinName, bool nonessential) + { + int slotCount = ReadVarint(input, true); + if (slotCount == 0) return null; + Skin skin = new Skin(skinName); + 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 = ReadString(input); + skin.AddAttachment(slotIndex, name, ReadAttachment(input, skin, slotIndex, name, nonessential)); + } + } + return skin; + } + + private Attachment ReadAttachment(Stream input, Skin skin, int slotIndex, String attachmentName, bool nonessential) + { + float scale = Scale; + + String name = ReadString(input); + if (name == null) name = attachmentName; + + AttachmentType type = (AttachmentType)input.ReadByte(); + switch (type) + { + case AttachmentType.region: + { + String path = ReadString(input); + float x = ReadFloat(input); + float y = ReadFloat(input); + float scaleX = ReadFloat(input); + float scaleY = ReadFloat(input); + float rotation = ReadFloat(input); + float width = ReadFloat(input); + float height = ReadFloat(input); + int color = ReadInt(input); + + if (path == null) path = name; + RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); + if (region == null) return null; + region.Path = path; + region.x = x * scale; + region.y = y * scale; + region.scaleX = scaleX; + region.scaleY = scaleY; + region.rotation = rotation; + region.width = width * scale; + region.height = height * scale; + region.r = ((color & 0xff000000) >> 24) / 255f; + region.g = ((color & 0x00ff0000) >> 16) / 255f; + region.b = ((color & 0x0000ff00) >> 8) / 255f; + region.a = ((color & 0x000000ff)) / 255f; + region.UpdateOffset(); + return region; + } + case AttachmentType.boundingbox: + { + float[] vertices = ReadFloatArray(input, ReadVarint(input, true) * 2, scale); + BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); + if (box == null) return null; + box.vertices = vertices; + return box; + } + case AttachmentType.mesh: + { + String path = ReadString(input); + int color = ReadInt(input); + int hullLength = 0; + int verticesLength = ReadVarint(input, true) * 2; + float[] uvs = ReadFloatArray(input, verticesLength, 1); + int[] triangles = ReadShortArray(input); + float[] vertices = ReadFloatArray(input, verticesLength, scale); + hullLength = ReadVarint(input, true); + int[] edges = null; + float width = 0, height = 0; + if (nonessential) + { + edges = ReadShortArray(input); + width = ReadFloat(input); + height = ReadFloat(input); + } + + if (path == null) path = name; + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); + if (mesh == null) return null; + mesh.Path = path; + mesh.r = ((color & 0xff000000) >> 24) / 255f; + mesh.g = ((color & 0x00ff0000) >> 16) / 255f; + mesh.b = ((color & 0x0000ff00) >> 8) / 255f; + mesh.a = ((color & 0x000000ff)) / 255f; + mesh.vertices = vertices; + mesh.triangles = triangles; + mesh.regionUVs = uvs; + mesh.UpdateUVs(); + mesh.HullLength = hullLength; + if (nonessential) + { + mesh.Edges = edges; + mesh.Width = width * scale; + mesh.Height = height * scale; + } + return mesh; + } + case AttachmentType.linkedmesh: + { + String path = ReadString(input); + int color = ReadInt(input); + String skinName = ReadString(input); + String parent = ReadString(input); + bool inheritFFD = ReadBoolean(input); + float width = 0, height = 0; + if (nonessential) + { + width = ReadFloat(input); + height = ReadFloat(input); + } + + if (path == null) path = name; + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); + if (mesh == null) return null; + mesh.Path = path; + mesh.r = ((color & 0xff000000) >> 24) / 255f; + mesh.g = ((color & 0x00ff0000) >> 16) / 255f; + mesh.b = ((color & 0x0000ff00) >> 8) / 255f; + mesh.a = ((color & 0x000000ff)) / 255f; + mesh.inheritFFD = inheritFFD; + if (nonessential) + { + mesh.Width = width * scale; + mesh.Height = height * scale; + } + linkedMeshes.Add(new SkeletonJson.LinkedMesh(mesh, skinName, slotIndex, parent)); + return mesh; + } + case AttachmentType.weightedmesh: + { + String path = ReadString(input); + int color = ReadInt(input); + int vertexCount = ReadVarint(input, true); + float[] uvs = ReadFloatArray(input, vertexCount * 2, 1); + int[] triangles = ReadShortArray(input); + var weights = new List(uvs.Length * 3 * 3); + var bones = new List(uvs.Length * 3); + for (int i = 0; i < vertexCount; i++) + { + int boneCount = (int)ReadFloat(input); + bones.Add(boneCount); + for (int ii = 0; ii < boneCount; ii++) + { + bones.Add((int)ReadFloat(input)); + weights.Add(ReadFloat(input) * scale); + weights.Add(ReadFloat(input) * scale); + weights.Add(ReadFloat(input)); + } + } + int hullLength = ReadVarint(input, true); + int[] edges = null; + float width = 0, height = 0; + if (nonessential) + { + edges = ReadShortArray(input); + width = ReadFloat(input); + height = ReadFloat(input); + } + + if (path == null) path = name; + WeightedMeshAttachment mesh = attachmentLoader.NewWeightedMeshAttachment(skin, name, path); + if (mesh == null) return null; + mesh.Path = path; + mesh.r = ((color & 0xff000000) >> 24) / 255f; + mesh.g = ((color & 0x00ff0000) >> 16) / 255f; + mesh.b = ((color & 0x0000ff00) >> 8) / 255f; + mesh.a = ((color & 0x000000ff)) / 255f; + mesh.bones = bones.ToArray(); + mesh.weights = weights.ToArray(); + mesh.triangles = triangles; + mesh.regionUVs = uvs; + mesh.UpdateUVs(); + mesh.HullLength = hullLength * 2; + if (nonessential) + { + mesh.Edges = edges; + mesh.Width = width * scale; + mesh.Height = height * scale; + } + // + return mesh; + } + case AttachmentType.weightedlinkedmesh: + { + String path = ReadString(input); + int color = ReadInt(input); + String skinName = ReadString(input); + String parent = ReadString(input); + bool inheritFFD = ReadBoolean(input); + float width = 0, height = 0; + if (nonessential) + { + width = ReadFloat(input); + height = ReadFloat(input); + } + + if (path == null) path = name; + WeightedMeshAttachment mesh = attachmentLoader.NewWeightedMeshAttachment(skin, name, path); + if (mesh == null) return null; + mesh.Path = path; + mesh.r = ((color & 0xff000000) >> 24) / 255f; + mesh.g = ((color & 0x00ff0000) >> 16) / 255f; + mesh.b = ((color & 0x0000ff00) >> 8) / 255f; + mesh.a = ((color & 0x000000ff)) / 255f; + mesh.inheritFFD = inheritFFD; + if (nonessential) + { + mesh.Width = width * scale; + mesh.Height = height * scale; + } + linkedMeshes.Add(new SkeletonJson.LinkedMesh(mesh, skinName, slotIndex, parent)); + return mesh; + } + } + return null; + } + + private float[] ReadFloatArray(Stream input, int n, float scale) + { + float[] array = new float[n]; + if (scale == 1) + { + for (int i = 0; i < n; i++) + array[i] = ReadFloat(input); + } + else + { + for (int i = 0; i < n; i++) + array[i] = ReadFloat(input) * scale; + } + return array; + } + + private int[] ReadShortArray(Stream input) + { + int n = ReadVarint(input, true); + int[] array = new int[n]; + for (int i = 0; i < n; i++) + array[i] = (input.ReadByte() << 8) | input.ReadByte(); + return array; + } + + private void ReadAnimation(String name, Stream input, SkeletonData skeletonData) + { + var timelines = new ExposedList(); + float scale = Scale; + float duration = 0; + + // 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++) + { + int timelineType = input.ReadByte(); + int frameCount = ReadVarint(input, true); + switch (timelineType) + { + case TIMELINE_COLOR: + { + ColorTimeline timeline = new ColorTimeline(frameCount); + timeline.slotIndex = slotIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + float time = ReadFloat(input); + int color = ReadInt(input); + float r = ((color & 0xff000000) >> 24) / 255f; + float g = ((color & 0x00ff0000) >> 16) / 255f; + float b = ((color & 0x0000ff00) >> 8) / 255f; + float a = ((color & 0x000000ff)) / 255f; + timeline.SetFrame(frameIndex, time, r, g, b, a); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[frameCount * 5 - 5]); + break; + } + case TIMELINE_ATTACHMENT: + { + AttachmentTimeline timeline = new AttachmentTimeline(frameCount); + timeline.slotIndex = slotIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + timeline.SetFrame(frameIndex, ReadFloat(input), ReadString(input)); + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[frameCount - 1]); + break; + } + } + } + } + + // 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++) + { + int timelineType = input.ReadByte(); + int frameCount = ReadVarint(input, true); + switch (timelineType) + { + case TIMELINE_ROTATE: + { + RotateTimeline timeline = new RotateTimeline(frameCount); + timeline.boneIndex = boneIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input)); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[frameCount * 2 - 2]); + break; + } + case TIMELINE_TRANSLATE: + case TIMELINE_SCALE: + { + TranslateTimeline timeline; + float timelineScale = 1; + if (timelineType == TIMELINE_SCALE) + timeline = new ScaleTimeline(frameCount); + else + { + timeline = new TranslateTimeline(frameCount); + timelineScale = scale; + } + timeline.boneIndex = boneIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input) * timelineScale, ReadFloat(input) + * timelineScale); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[frameCount * 3 - 3]); + break; + } + } + } + } + + // IK timelines. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + IkConstraintData ikConstraint = skeletonData.ikConstraints.Items[ReadVarint(input, true)]; + int frameCount = ReadVarint(input, true); + IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount); + timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(ikConstraint); + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadSByte(input)); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[frameCount * 3 - 3]); + } + + // FFD timelines. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + Skin skin = skeletonData.skins.Items[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++) + { + Attachment attachment = skin.GetAttachment(slotIndex, ReadString(input)); + int frameCount = ReadVarint(input, true); + FfdTimeline timeline = new FfdTimeline(frameCount); + timeline.slotIndex = slotIndex; + timeline.attachment = attachment; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + float time = ReadFloat(input); + + float[] vertices; + int vertexCount; + if (attachment is MeshAttachment) + vertexCount = ((MeshAttachment)attachment).vertices.Length; + else + vertexCount = ((WeightedMeshAttachment)attachment).weights.Length / 3 * 2; + + int end = ReadVarint(input, true); + if (end == 0) + { + if (attachment is MeshAttachment) + vertices = ((MeshAttachment)attachment).vertices; + else + vertices = new float[vertexCount]; + } + else + { + vertices = new float[vertexCount]; + int start = ReadVarint(input, true); + end += start; + if (scale == 1) + { + for (int v = start; v < end; v++) + vertices[v] = ReadFloat(input); + } + else + { + for (int v = start; v < end; v++) + vertices[v] = ReadFloat(input) * scale; + } + if (attachment is MeshAttachment) + { + float[] meshVertices = ((MeshAttachment)attachment).vertices; + for (int v = 0, vn = vertices.Length; v < vn; v++) + vertices[v] += meshVertices[v]; + } + } + + timeline.SetFrame(frameIndex, time, vertices); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[frameCount - 1]); + } + } + } + + // Draw order timeline. + int drawOrderCount = ReadVarint(input, true); + if (drawOrderCount > 0) + { + DrawOrderTimeline timeline = new DrawOrderTimeline(drawOrderCount); + int slotCount = skeletonData.slots.Count; + for (int i = 0; i < drawOrderCount; i++) + { + float time = ReadFloat(input); + int offsetCount = ReadVarint(input, true); + int[] drawOrder = new int[slotCount]; + for (int ii = slotCount - 1; ii >= 0; ii--) + drawOrder[ii] = -1; + int[] unchanged = new int[slotCount - offsetCount]; + int originalIndex = 0, unchangedIndex = 0; + for (int ii = 0; ii < offsetCount; ii++) + { + int slotIndex = ReadVarint(input, true); + // Collect unchanged items. + while (originalIndex != slotIndex) + unchanged[unchangedIndex++] = originalIndex++; + // Set changed items. + drawOrder[originalIndex + ReadVarint(input, true)] = originalIndex++; + } + // Collect remaining unchanged items. + while (originalIndex < slotCount) + unchanged[unchangedIndex++] = originalIndex++; + // Fill in unchanged items. + for (int ii = slotCount - 1; ii >= 0; ii--) + if (drawOrder[ii] == -1) drawOrder[ii] = unchanged[--unchangedIndex]; + timeline.SetFrame(i, time, drawOrder); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[drawOrderCount - 1]); + } + + // Event timeline. + int eventCount = ReadVarint(input, true); + if (eventCount > 0) + { + EventTimeline timeline = new EventTimeline(eventCount); + for (int i = 0; i < eventCount; i++) + { + float time = ReadFloat(input); + EventData eventData = skeletonData.events.Items[ReadVarint(input, true)]; + Event e = new Event(time, eventData); + e.Int = ReadVarint(input, false); + e.Float = ReadFloat(input); + e.String = ReadBoolean(input) ? ReadString(input) : eventData.String; + timeline.SetFrame(i, e); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[eventCount - 1]); + } + + timelines.TrimExcess(); + skeletonData.animations.Add(new Animation(name, timelines, duration)); + } + + private void ReadCurve(Stream input, int frameIndex, CurveTimeline timeline) + { + switch (input.ReadByte()) + { + case CURVE_STEPPED: + timeline.SetStepped(frameIndex); + break; + case CURVE_BEZIER: + timeline.SetCurve(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input)); + break; + } + } + + private static sbyte ReadSByte(Stream input) + { + int value = input.ReadByte(); + if (value == -1) throw new EndOfStreamException(); + return (sbyte)value; + } + + private static bool ReadBoolean(Stream input) + { + return input.ReadByte() != 0; + } + + private float ReadFloat(Stream input) + { + buffer[3] = (byte)input.ReadByte(); + buffer[2] = (byte)input.ReadByte(); + buffer[1] = (byte)input.ReadByte(); + buffer[0] = (byte)input.ReadByte(); + return BitConverter.ToSingle(buffer, 0); + } + + private static int ReadInt(Stream input) + { + return (input.ReadByte() << 24) + (input.ReadByte() << 16) + (input.ReadByte() << 8) + input.ReadByte(); + } + + private static int ReadVarint(Stream input, bool optimizePositive) + { + int b = input.ReadByte(); + int result = b & 0x7F; + if ((b & 0x80) != 0) + { + b = input.ReadByte(); + result |= (b & 0x7F) << 7; + if ((b & 0x80) != 0) + { + b = input.ReadByte(); + result |= (b & 0x7F) << 14; + if ((b & 0x80) != 0) + { + b = input.ReadByte(); + result |= (b & 0x7F) << 21; + if ((b & 0x80) != 0) result |= (input.ReadByte() & 0x7F) << 28; + } + } + } + return optimizePositive ? result : ((result >> 1) ^ -(result & 1)); + } + + private string ReadString(Stream input) + { + int byteCount = ReadVarint(input, true); + switch (byteCount) + { + case 0: + return null; + case 1: + return ""; + } + byteCount--; + byte[] buffer = this.buffer; + if (buffer.Length < byteCount) buffer = new byte[byteCount]; + ReadFully(input, buffer, 0, byteCount); + return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount); + } + + private static void ReadFully(Stream input, byte[] buffer, int offset, int length) + { + while (length > 0) + { + int count = input.Read(buffer, offset, length); + if (count <= 0) throw new EndOfStreamException(); + offset += count; + length -= count; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/SkeletonBounds.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/SkeletonBounds.cs index 21748fa..ab19b30 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/SkeletonBounds.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/SkeletonBounds.cs @@ -30,187 +30,210 @@ *****************************************************************************/ using System; -using System.Collections.Generic; - -namespace Spine3_1_07 { - public class SkeletonBounds { - private ExposedList polygonPool = new ExposedList(); - private float minX, minY, maxX, maxY; - - public ExposedList BoundingBoxes { get; private set; } - public ExposedList Polygons { get; private set; } - public float MinX { get { return minX; } set { minX = value; } } - public float MinY { get { return minY; } set { minY = value; } } - public float MaxX { get { return maxX; } set { maxX = value; } } - public float MaxY { get { return maxY; } set { maxY = value; } } - public float Width { get { return maxX - minX; } } - public float Height { get { return maxY - minY; } } - - public SkeletonBounds () { - BoundingBoxes = new ExposedList(); - Polygons = new ExposedList(); - } - - public void Update (Skeleton skeleton, bool updateAabb) { - ExposedList boundingBoxes = BoundingBoxes; - ExposedList polygons = Polygons; - ExposedList slots = skeleton.slots; - int slotCount = slots.Count; - - boundingBoxes.Clear(); - for (int i = 0, n = polygons.Count; i < n; i++) - polygonPool.Add(polygons.Items[i]); - polygons.Clear(); - - for (int i = 0; i < slotCount; i++) { - Slot slot = slots.Items[i]; - BoundingBoxAttachment boundingBox = slot.attachment as BoundingBoxAttachment; - if (boundingBox == null) continue; - boundingBoxes.Add(boundingBox); - - Polygon polygon = null; - int poolCount = polygonPool.Count; - if (poolCount > 0) { - polygon = polygonPool.Items[poolCount - 1]; - polygonPool.RemoveAt(poolCount - 1); - } else - polygon = new Polygon(); - polygons.Add(polygon); - - int count = boundingBox.Vertices.Length; - polygon.Count = count; - if (polygon.Vertices.Length < count) polygon.Vertices = new float[count]; - boundingBox.ComputeWorldVertices(slot.bone, polygon.Vertices); - } - - if (updateAabb) aabbCompute(); - } - - private void aabbCompute () { - float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; - ExposedList polygons = Polygons; - for (int i = 0, n = polygons.Count; i < n; i++) { - Polygon polygon = polygons.Items[i]; - float[] vertices = polygon.Vertices; - for (int ii = 0, nn = polygon.Count; ii < nn; ii += 2) { - float x = vertices[ii]; - float y = vertices[ii + 1]; - minX = Math.Min(minX, x); - minY = Math.Min(minY, y); - maxX = Math.Max(maxX, x); - maxY = Math.Max(maxY, y); - } - } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; - } - - - /// Returns true if the axis aligned bounding box contains the point. - public bool AabbContainsPoint (float x, float y) { - return x >= minX && x <= maxX && y >= minY && y <= maxY; - } - - /// Returns true if the axis aligned bounding box intersects the line segment. - public bool AabbIntersectsSegment (float x1, float y1, float x2, float y2) { - float minX = this.minX; - float minY = this.minY; - float maxX = this.maxX; - float maxY = this.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; - } - - /// Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds. - public bool AabbIntersectsSkeleton (SkeletonBounds bounds) { - return minX < bounds.maxX && maxX > bounds.minX && minY < bounds.maxY && maxY > bounds.minY; - } - - /// Returns true if the polygon contains the point. - public bool ContainsPoint (Polygon polygon, float x, float y) { - float[] 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; - } - - /// 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. - public BoundingBoxAttachment ContainsPoint (float x, float y) { - ExposedList polygons = Polygons; - for (int i = 0, n = polygons.Count; i < n; i++) - if (ContainsPoint(polygons.Items[i], x, y)) return BoundingBoxes.Items[i]; - return null; - } - - /// 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. - public BoundingBoxAttachment IntersectsSegment (float x1, float y1, float x2, float y2) { - ExposedList polygons = Polygons; - for (int i = 0, n = polygons.Count; i < n; i++) - if (IntersectsSegment(polygons.Items[i], x1, y1, x2, y2)) return BoundingBoxes.Items[i]; - return null; - } - - /// Returns true if the polygon contains the line segment. - public bool IntersectsSegment (Polygon polygon, float x1, float y1, float x2, float y2) { - float[] vertices = polygon.Vertices; - int 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 (int 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; - } - - public Polygon getPolygon (BoundingBoxAttachment attachment) { - int index = BoundingBoxes.IndexOf(attachment); - return index == -1 ? null : Polygons.Items[index]; - } - } - - public class Polygon { - public float[] Vertices { get; set; } - public int Count { get; set; } - - public Polygon () { - Vertices = new float[16]; - } - } + +namespace Spine3_1_07 +{ + public class SkeletonBounds + { + private ExposedList polygonPool = new ExposedList(); + private float minX, minY, maxX, maxY; + + public ExposedList BoundingBoxes { get; private set; } + public ExposedList Polygons { get; private set; } + public float MinX { get { return minX; } set { minX = value; } } + public float MinY { get { return minY; } set { minY = value; } } + public float MaxX { get { return maxX; } set { maxX = value; } } + public float MaxY { get { return maxY; } set { maxY = value; } } + public float Width { get { return maxX - minX; } } + public float Height { get { return maxY - minY; } } + + public SkeletonBounds() + { + BoundingBoxes = new ExposedList(); + Polygons = new ExposedList(); + } + + public void Update(Skeleton skeleton, bool updateAabb) + { + ExposedList boundingBoxes = BoundingBoxes; + ExposedList polygons = Polygons; + ExposedList slots = skeleton.slots; + int slotCount = slots.Count; + + boundingBoxes.Clear(); + for (int i = 0, n = polygons.Count; i < n; i++) + polygonPool.Add(polygons.Items[i]); + polygons.Clear(); + + for (int i = 0; i < slotCount; i++) + { + Slot slot = slots.Items[i]; + BoundingBoxAttachment boundingBox = slot.attachment as BoundingBoxAttachment; + if (boundingBox == null) continue; + boundingBoxes.Add(boundingBox); + + Polygon polygon = null; + int poolCount = polygonPool.Count; + if (poolCount > 0) + { + polygon = polygonPool.Items[poolCount - 1]; + polygonPool.RemoveAt(poolCount - 1); + } + else + polygon = new Polygon(); + polygons.Add(polygon); + + int count = boundingBox.Vertices.Length; + polygon.Count = count; + if (polygon.Vertices.Length < count) polygon.Vertices = new float[count]; + boundingBox.ComputeWorldVertices(slot.bone, polygon.Vertices); + } + + if (updateAabb) aabbCompute(); + } + + private void aabbCompute() + { + float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; + ExposedList polygons = Polygons; + for (int i = 0, n = polygons.Count; i < n; i++) + { + Polygon polygon = polygons.Items[i]; + float[] vertices = polygon.Vertices; + for (int ii = 0, nn = polygon.Count; ii < nn; ii += 2) + { + float x = vertices[ii]; + float y = vertices[ii + 1]; + minX = Math.Min(minX, x); + minY = Math.Min(minY, y); + maxX = Math.Max(maxX, x); + maxY = Math.Max(maxY, y); + } + } + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + + /// Returns true if the axis aligned bounding box contains the point. + public bool AabbContainsPoint(float x, float y) + { + return x >= minX && x <= maxX && y >= minY && y <= maxY; + } + + /// Returns true if the axis aligned bounding box intersects the line segment. + public bool AabbIntersectsSegment(float x1, float y1, float x2, float y2) + { + float minX = this.minX; + float minY = this.minY; + float maxX = this.maxX; + float maxY = this.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; + } + + /// Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds. + public bool AabbIntersectsSkeleton(SkeletonBounds bounds) + { + return minX < bounds.maxX && maxX > bounds.minX && minY < bounds.maxY && maxY > bounds.minY; + } + + /// Returns true if the polygon contains the point. + public bool ContainsPoint(Polygon polygon, float x, float y) + { + float[] 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; + } + + /// 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. + public BoundingBoxAttachment ContainsPoint(float x, float y) + { + ExposedList polygons = Polygons; + for (int i = 0, n = polygons.Count; i < n; i++) + if (ContainsPoint(polygons.Items[i], x, y)) return BoundingBoxes.Items[i]; + return null; + } + + /// 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. + public BoundingBoxAttachment IntersectsSegment(float x1, float y1, float x2, float y2) + { + ExposedList polygons = Polygons; + for (int i = 0, n = polygons.Count; i < n; i++) + if (IntersectsSegment(polygons.Items[i], x1, y1, x2, y2)) return BoundingBoxes.Items[i]; + return null; + } + + /// Returns true if the polygon contains the line segment. + public bool IntersectsSegment(Polygon polygon, float x1, float y1, float x2, float y2) + { + float[] vertices = polygon.Vertices; + int 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 (int 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; + } + + public Polygon getPolygon(BoundingBoxAttachment attachment) + { + int index = BoundingBoxes.IndexOf(attachment); + return index == -1 ? null : Polygons.Items[index]; + } + } + + public class Polygon + { + public float[] Vertices { get; set; } + public int Count { get; set; } + + public Polygon() + { + Vertices = new float[16]; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/SkeletonData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/SkeletonData.cs index 4b54b10..25d4fe4 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/SkeletonData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/SkeletonData.cs @@ -30,144 +30,160 @@ *****************************************************************************/ using System; -using System.Collections.Generic; - -namespace Spine3_1_07 { - public class SkeletonData { - internal String name; - internal ExposedList bones = new ExposedList(); - internal ExposedList slots = new ExposedList(); - internal ExposedList skins = new ExposedList(); - internal Skin defaultSkin; - internal ExposedList events = new ExposedList(); - internal ExposedList animations = new ExposedList(); - internal ExposedList ikConstraints = new ExposedList(); - internal ExposedList transformConstraints = new ExposedList(); - internal float width, height; - internal String version, hash, imagesPath; - - public String Name { get { return name; } set { name = value; } } - public ExposedList Bones { get { return bones; } } // Ordered parents first. - public ExposedList Slots { get { return slots; } } // Setup pose draw order. - public ExposedList Skins { get { return skins; } set { skins = value; } } - /// May be null. - public Skin DefaultSkin { get { return defaultSkin; } set { defaultSkin = value; } } - public ExposedList Events { get { return events; } set { events = value; } } - public ExposedList Animations { get { return animations; } set { animations = value; } } - public ExposedList IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } } - public float Width { get { return width; } set { width = value; } } - public float Height { get { return height; } set { height = value; } } - /// The Spine version used to export this data. - public String Version { get { return version; } set { version = value; } } - public String Hash { get { return hash; } set { hash = value; } } - - // --- Bones. - - /// May be null. - public BoneData FindBone (String boneName) { - if (boneName == null) throw new ArgumentNullException("boneName cannot be null."); - ExposedList bones = this.bones; - for (int i = 0, n = bones.Count; i < n; i++) { - BoneData bone = bones.Items[i]; - if (bone.name == boneName) return bone; - } - return null; - } - - /// -1 if the bone was not found. - public int FindBoneIndex (String boneName) { - if (boneName == null) throw new ArgumentNullException("boneName cannot be null."); - ExposedList bones = this.bones; - for (int i = 0, n = bones.Count; i < n; i++) - if (bones.Items[i].name == boneName) return i; - return -1; - } - - // --- Slots. - - /// May be null. - public SlotData FindSlot (String slotName) { - if (slotName == null) throw new ArgumentNullException("slotName cannot be null."); - ExposedList slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) { - SlotData slot = slots.Items[i]; - if (slot.name == slotName) return slot; - } - return null; - } - - /// -1 if the bone was not found. - public int FindSlotIndex (String slotName) { - if (slotName == null) throw new ArgumentNullException("slotName cannot be null."); - ExposedList slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) - if (slots.Items[i].name == slotName) return i; - return -1; - } - - // --- Skins. - - /// May be null. - public Skin FindSkin (String skinName) { - if (skinName == null) throw new ArgumentNullException("skinName cannot be null."); - foreach (Skin skin in skins) - if (skin.name == skinName) return skin; - return null; - } - - // --- Events. - - /// May be null. - public EventData FindEvent (String eventDataName) { - if (eventDataName == null) throw new ArgumentNullException("eventDataName cannot be null."); - foreach (EventData eventData in events) - if (eventData.name == eventDataName) return eventData; - return null; - } - - // --- Animations. - - /// May be null. - public Animation FindAnimation (String animationName) { - if (animationName == null) throw new ArgumentNullException("animationName cannot be null."); - ExposedList animations = this.animations; - for (int i = 0, n = animations.Count; i < n; i++) { - Animation animation = animations.Items[i]; - if (animation.name == animationName) return animation; - } - return null; - } - - // --- IK constraints. - - /// May be null. - public IkConstraintData FindIkConstraint (String constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName cannot be null."); - ExposedList ikConstraints = this.ikConstraints; - for (int i = 0, n = ikConstraints.Count; i < n; i++) { - IkConstraintData ikConstraint = ikConstraints.Items[i]; - if (ikConstraint.name == constraintName) return ikConstraint; - } - return null; - } - - // --- Transform constraints. - - /// May be null. - public TransformConstraintData FindTransformConstraint (String constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName cannot be null."); - ExposedList transformConstraints = this.transformConstraints; - for (int i = 0, n = transformConstraints.Count; i < n; i++) { - TransformConstraintData transformConstraint = transformConstraints.Items[i]; - if (transformConstraint.name == constraintName) return transformConstraint; - } - return null; - } - - // --- - - override public String ToString () { - return name ?? base.ToString(); - } - } + +namespace Spine3_1_07 +{ + public class SkeletonData + { + internal String name; + internal ExposedList bones = new ExposedList(); + internal ExposedList slots = new ExposedList(); + internal ExposedList skins = new ExposedList(); + internal Skin defaultSkin; + internal ExposedList events = new ExposedList(); + internal ExposedList animations = new ExposedList(); + internal ExposedList ikConstraints = new ExposedList(); + internal ExposedList transformConstraints = new ExposedList(); + internal float width, height; + internal String version, hash, imagesPath; + + public String Name { get { return name; } set { name = value; } } + public ExposedList Bones { get { return bones; } } // Ordered parents first. + public ExposedList Slots { get { return slots; } } // Setup pose draw order. + public ExposedList Skins { get { return skins; } set { skins = value; } } + /// May be null. + public Skin DefaultSkin { get { return defaultSkin; } set { defaultSkin = value; } } + public ExposedList Events { get { return events; } set { events = value; } } + public ExposedList Animations { get { return animations; } set { animations = value; } } + public ExposedList IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } } + public float Width { get { return width; } set { width = value; } } + public float Height { get { return height; } set { height = value; } } + /// The Spine version used to export this data. + public String Version { get { return version; } set { version = value; } } + public String Hash { get { return hash; } set { hash = value; } } + + // --- Bones. + + /// May be null. + public BoneData FindBone(String boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName cannot be null."); + ExposedList bones = this.bones; + for (int i = 0, n = bones.Count; i < n; i++) + { + BoneData bone = bones.Items[i]; + if (bone.name == boneName) return bone; + } + return null; + } + + /// -1 if the bone was not found. + public int FindBoneIndex(String boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName cannot be null."); + ExposedList bones = this.bones; + for (int i = 0, n = bones.Count; i < n; i++) + if (bones.Items[i].name == boneName) return i; + return -1; + } + + // --- Slots. + + /// May be null. + public SlotData FindSlot(String slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName cannot be null."); + ExposedList slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + { + SlotData slot = slots.Items[i]; + if (slot.name == slotName) return slot; + } + return null; + } + + /// -1 if the bone was not found. + public int FindSlotIndex(String slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName cannot be null."); + ExposedList slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + if (slots.Items[i].name == slotName) return i; + return -1; + } + + // --- Skins. + + /// May be null. + public Skin FindSkin(String skinName) + { + if (skinName == null) throw new ArgumentNullException("skinName cannot be null."); + foreach (Skin skin in skins) + if (skin.name == skinName) return skin; + return null; + } + + // --- Events. + + /// May be null. + public EventData FindEvent(String eventDataName) + { + if (eventDataName == null) throw new ArgumentNullException("eventDataName cannot be null."); + foreach (EventData eventData in events) + if (eventData.name == eventDataName) return eventData; + return null; + } + + // --- Animations. + + /// May be null. + public Animation FindAnimation(String animationName) + { + if (animationName == null) throw new ArgumentNullException("animationName cannot be null."); + ExposedList animations = this.animations; + for (int i = 0, n = animations.Count; i < n; i++) + { + Animation animation = animations.Items[i]; + if (animation.name == animationName) return animation; + } + return null; + } + + // --- IK constraints. + + /// May be null. + public IkConstraintData FindIkConstraint(String constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName cannot be null."); + ExposedList ikConstraints = this.ikConstraints; + for (int i = 0, n = ikConstraints.Count; i < n; i++) + { + IkConstraintData ikConstraint = ikConstraints.Items[i]; + if (ikConstraint.name == constraintName) return ikConstraint; + } + return null; + } + + // --- Transform constraints. + + /// May be null. + public TransformConstraintData FindTransformConstraint(String constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName cannot be null."); + ExposedList transformConstraints = this.transformConstraints; + for (int i = 0, n = transformConstraints.Count; i < n; i++) + { + TransformConstraintData transformConstraint = transformConstraints.Items[i]; + if (transformConstraint.name == constraintName) return transformConstraint; + } + return null; + } + + // --- + + override public String ToString() + { + return name ?? base.ToString(); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/SkeletonJson.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/SkeletonJson.cs index 678be62..a4d3e36 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/SkeletonJson.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/SkeletonJson.cs @@ -34,33 +34,37 @@ #endif using System; -using System.IO; using System.Collections.Generic; +using System.IO; #if WINDOWS_STOREAPP using System.Threading.Tasks; using Windows.Storage; #endif -namespace Spine3_1_07 { - public class SkeletonJson { - public float Scale { get; set; } +namespace Spine3_1_07 +{ + public class SkeletonJson + { + public float Scale { get; set; } - private AttachmentLoader attachmentLoader; - private List linkedMeshes = new List(); + private AttachmentLoader attachmentLoader; + private List linkedMeshes = new List(); - public SkeletonJson (params Atlas[] atlasArray) - : this(new AtlasAttachmentLoader(atlasArray)) { - } + public SkeletonJson(params Atlas[] atlasArray) + : this(new AtlasAttachmentLoader(atlasArray)) + { + } - public SkeletonJson (AttachmentLoader attachmentLoader) { - if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader cannot be null."); - this.attachmentLoader = attachmentLoader; - Scale = 1; - } + public SkeletonJson(AttachmentLoader attachmentLoader) + { + if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader cannot be null."); + this.attachmentLoader = attachmentLoader; + Scale = 1; + } - #if !(IS_UNITY) - #if WINDOWS_STOREAPP +#if !(IS_UNITY) +#if WINDOWS_STOREAPP private async Task ReadFile(string path) { var folder = Windows.ApplicationModel.Package.Current.InstalledLocation; @@ -75,637 +79,733 @@ private async Task ReadFile(string path) { public SkeletonData ReadSkeletonData (String path) { return this.ReadFile(path).Result; } - #else - public SkeletonData ReadSkeletonData (String path) { - #if WINDOWS_PHONE +#else + public SkeletonData ReadSkeletonData(String path) + { +#if WINDOWS_PHONE Stream stream = Microsoft.Xna.Framework.TitleContainer.OpenStream(path); using (StreamReader reader = new StreamReader(stream)) { - #else - using (StreamReader reader = new StreamReader(path)) { - #endif // WINDOWS_PHONE - SkeletonData skeletonData = ReadSkeletonData(reader); - skeletonData.name = Path.GetFileNameWithoutExtension(path); - return skeletonData; - } - } - - #endif // WINDOWS_STOREAPP - #endif // !UNITY - - public SkeletonData ReadSkeletonData (TextReader reader) { - if (reader == null) throw new ArgumentNullException("reader cannot be null."); - - var scale = this.Scale; - var skeletonData = new SkeletonData(); - - var root = Json.Deserialize(reader) as Dictionary; - if (root == null) throw new Exception("Invalid JSON."); - - // Skeleton. - if (root.ContainsKey("skeleton")) { - var skeletonMap = (Dictionary)root["skeleton"]; - skeletonData.hash = (String)skeletonMap["hash"]; - skeletonData.version = (String)skeletonMap["spine"]; - skeletonData.width = GetFloat(skeletonMap, "width", 0); - skeletonData.height = GetFloat(skeletonMap, "height", 0); - } - - // Bones. - foreach (Dictionary boneMap in (List)root["bones"]) { - BoneData parent = null; - if (boneMap.ContainsKey("parent")) { - parent = skeletonData.FindBone((String)boneMap["parent"]); - if (parent == null) - throw new Exception("Parent bone not found: " + boneMap["parent"]); - } - var boneData = new BoneData((String)boneMap["name"], parent); - boneData.length = GetFloat(boneMap, "length", 0) * scale; - boneData.x = GetFloat(boneMap, "x", 0) * scale; - boneData.y = GetFloat(boneMap, "y", 0) * scale; - boneData.rotation = GetFloat(boneMap, "rotation", 0); - boneData.scaleX = GetFloat(boneMap, "scaleX", 1); - boneData.scaleY = GetFloat(boneMap, "scaleY", 1); - boneData.inheritScale = GetBoolean(boneMap, "inheritScale", true); - boneData.inheritRotation = GetBoolean(boneMap, "inheritRotation", true); - skeletonData.bones.Add(boneData); - } - - // IK constraints. - if (root.ContainsKey("ik")) { - foreach (Dictionary ikMap in (List)root["ik"]) { - IkConstraintData ikConstraintData = new IkConstraintData((String)ikMap["name"]); - - foreach (String boneName in (List)ikMap["bones"]) { - BoneData bone = skeletonData.FindBone(boneName); - if (bone == null) throw new Exception("IK bone not found: " + boneName); - ikConstraintData.bones.Add(bone); - } - - String targetName = (String)ikMap["target"]; - ikConstraintData.target = skeletonData.FindBone(targetName); - if (ikConstraintData.target == null) throw new Exception("Target bone not found: " + targetName); - - ikConstraintData.bendDirection = GetBoolean(ikMap, "bendPositive", true) ? 1 : -1; - ikConstraintData.mix = GetFloat(ikMap, "mix", 1); - - skeletonData.ikConstraints.Add(ikConstraintData); - } - } - - // Transform constraints. - if (root.ContainsKey("transform")) { - foreach (Dictionary transformMap in (List)root["transform"]) { - TransformConstraintData transformConstraintData = new TransformConstraintData((String)transformMap["name"]); - - String boneName = (String)transformMap["bone"]; - transformConstraintData.bone = skeletonData.FindBone(boneName); - if (transformConstraintData.bone == null) throw new Exception("Bone not found: " + boneName); - - String targetName = (String)transformMap["target"]; - transformConstraintData.target = skeletonData.FindBone(targetName); - if (transformConstraintData.target == null) throw new Exception("Target bone not found: " + targetName); - - transformConstraintData.translateMix = GetFloat(transformMap, "translateMix", 1); - transformConstraintData.x = GetFloat(transformMap, "x", 0) * scale; - transformConstraintData.y = GetFloat(transformMap, "y", 0) * scale; - - skeletonData.transformConstraints.Add(transformConstraintData); - } - } - - // Slots. - if (root.ContainsKey("slots")) { - foreach (Dictionary slotMap in (List)root["slots"]) { - var slotName = (String)slotMap["name"]; - var boneName = (String)slotMap["bone"]; - BoneData boneData = skeletonData.FindBone(boneName); - if (boneData == null) - throw new Exception("Slot bone not found: " + boneName); - var slotData = new SlotData(slotName, boneData); - - if (slotMap.ContainsKey("color")) { - var color = (String)slotMap["color"]; - slotData.r = ToColor(color, 0); - slotData.g = ToColor(color, 1); - slotData.b = ToColor(color, 2); - slotData.a = ToColor(color, 3); - } - - if (slotMap.ContainsKey("attachment")) - slotData.attachmentName = (String)slotMap["attachment"]; - - if (slotMap.ContainsKey("blend")) - slotData.blendMode = (BlendMode)Enum.Parse(typeof(BlendMode), (String)slotMap["blend"], false); - else - slotData.blendMode = BlendMode.normal; - - skeletonData.slots.Add(slotData); - } - } - - // Skins. - if (root.ContainsKey("skins")) { - foreach (KeyValuePair entry in (Dictionary)root["skins"]) { - var skin = new Skin(entry.Key); - foreach (KeyValuePair slotEntry in (Dictionary)entry.Value) { - int slotIndex = skeletonData.FindSlotIndex(slotEntry.Key); - foreach (KeyValuePair attachmentEntry in ((Dictionary)slotEntry.Value)) { - Attachment attachment = ReadAttachment(skin, slotIndex, attachmentEntry.Key, (Dictionary)attachmentEntry.Value); - if (attachment != null) skin.AddAttachment(slotIndex, attachmentEntry.Key, attachment); - } - } - skeletonData.skins.Add(skin); - if (skin.name == "default") - skeletonData.defaultSkin = skin; - } - } - - // Linked meshes. - for (int i = 0, n = linkedMeshes.Count; i < n; i++) { - LinkedMesh linkedMesh = linkedMeshes[i]; - Skin skin = linkedMesh.skin == null ? skeletonData.defaultSkin : skeletonData.FindSkin(linkedMesh.skin); - if (skin == null) throw new Exception("Slot not found: " + linkedMesh.skin); - Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); - if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); - if (linkedMesh.mesh is MeshAttachment) { - MeshAttachment mesh = (MeshAttachment)linkedMesh.mesh; - mesh.ParentMesh = (MeshAttachment)parent; - mesh.UpdateUVs(); - } else { - WeightedMeshAttachment mesh = (WeightedMeshAttachment)linkedMesh.mesh; - mesh.ParentMesh = (WeightedMeshAttachment)parent; - mesh.UpdateUVs(); - } - } - linkedMeshes.Clear(); - - // Events. - if (root.ContainsKey("events")) { - foreach (KeyValuePair entry in (Dictionary)root["events"]) { - var entryMap = (Dictionary)entry.Value; - var eventData = new EventData(entry.Key); - eventData.Int = GetInt(entryMap, "int", 0); - eventData.Float = GetFloat(entryMap, "float", 0); - eventData.String = GetString(entryMap, "string", null); - skeletonData.events.Add(eventData); - } - } - - // Animations. - if (root.ContainsKey("animations")) { - foreach (KeyValuePair entry in (Dictionary)root["animations"]) - ReadAnimation(entry.Key, (Dictionary)entry.Value, skeletonData); - } - - skeletonData.bones.TrimExcess(); - skeletonData.slots.TrimExcess(); - skeletonData.skins.TrimExcess(); - skeletonData.events.TrimExcess(); - skeletonData.animations.TrimExcess(); - skeletonData.ikConstraints.TrimExcess(); - return skeletonData; - } - - private Attachment ReadAttachment (Skin skin, int slotIndex, String name, Dictionary map) { - if (map.ContainsKey("name")) - name = (String)map["name"]; - - var scale = this.Scale; - - var type = AttachmentType.region; - if (map.ContainsKey("type")) { - var typeName = (String)map["type"]; - if (typeName == "skinnedmesh") typeName = "weightedmesh"; - type = (AttachmentType)Enum.Parse(typeof(AttachmentType), typeName , false); - } - - String path = name; - if (map.ContainsKey("path")) - path = (String)map["path"]; - - switch (type) { - case AttachmentType.region: - RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); - if (region == null) return null; - region.Path = path; - region.x = GetFloat(map, "x", 0) * scale; - region.y = GetFloat(map, "y", 0) * scale; - region.scaleX = GetFloat(map, "scaleX", 1); - region.scaleY = GetFloat(map, "scaleY", 1); - region.rotation = GetFloat(map, "rotation", 0); - region.width = GetFloat(map, "width", 32) * scale; - region.height = GetFloat(map, "height", 32) * scale; - region.UpdateOffset(); - - if (map.ContainsKey("color")) { - var color = (String)map["color"]; - region.r = ToColor(color, 0); - region.g = ToColor(color, 1); - region.b = ToColor(color, 2); - region.a = ToColor(color, 3); - } - - return region; - case AttachmentType.mesh: - case AttachmentType.linkedmesh: { - MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); - if (mesh == null) return null; - mesh.Path = path; - - if (map.ContainsKey("color")) { - var color = (String)map["color"]; - mesh.r = ToColor(color, 0); - mesh.g = ToColor(color, 1); - mesh.b = ToColor(color, 2); - mesh.a = ToColor(color, 3); - } - - mesh.Width = GetInt(map, "width", 0) * scale; - mesh.Height = GetInt(map, "height", 0) * scale; - - String parent = GetString(map, "parent", null); - if (parent == null) { - mesh.vertices = GetFloatArray(map, "vertices", scale); - mesh.triangles = GetIntArray(map, "triangles"); - mesh.regionUVs = GetFloatArray(map, "uvs", 1); - mesh.UpdateUVs(); - - mesh.HullLength = GetInt(map, "hull", 0) * 2; - if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges"); - } else { - mesh.InheritFFD = GetBoolean(map, "ffd", true); - linkedMeshes.Add(new LinkedMesh(mesh, GetString(map, "skin", null), slotIndex, parent)); - } - - return mesh; - } - case AttachmentType.weightedmesh: - case AttachmentType.weightedlinkedmesh: { - WeightedMeshAttachment mesh = attachmentLoader.NewWeightedMeshAttachment(skin, name, path); - if (mesh == null) return null; - - mesh.Path = path; - - if (map.ContainsKey("color")) { - var color = (String)map["color"]; - mesh.r = ToColor(color, 0); - mesh.g = ToColor(color, 1); - mesh.b = ToColor(color, 2); - mesh.a = ToColor(color, 3); - } - - mesh.Width = GetInt(map, "width", 0) * scale; - mesh.Height = GetInt(map, "height", 0) * scale; - - String parent = GetString(map, "parent", null); - if (parent == null) { - float[] uvs = GetFloatArray(map, "uvs", 1); - float[] vertices = GetFloatArray(map, "vertices", 1); - var weights = new List(uvs.Length * 3 * 3); - var bones = new List(uvs.Length * 3); - for (int i = 0, n = vertices.Length; i < n;) { - int boneCount = (int)vertices[i++]; - bones.Add(boneCount); - for (int nn = i + boneCount * 4; i < nn; i += 4) { - bones.Add((int)vertices[i]); - weights.Add(vertices[i + 1] * scale); - weights.Add(vertices[i + 2] * scale); - weights.Add(vertices[i + 3]); - } - } - mesh.bones = bones.ToArray(); - mesh.weights = weights.ToArray(); - mesh.triangles = GetIntArray(map, "triangles"); - mesh.regionUVs = uvs; - mesh.UpdateUVs(); - - mesh.HullLength = GetInt(map, "hull", 0) * 2; - if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges"); - } else { - mesh.InheritFFD = GetBoolean(map, "ffd", true); - linkedMeshes.Add(new LinkedMesh(mesh, GetString(map, "skin", null), slotIndex, parent)); - } - - return mesh; - } - case AttachmentType.boundingbox: - BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); - if (box == null) return null; - box.vertices = GetFloatArray(map, "vertices", scale); - return box; - } - return null; - } - - private float[] GetFloatArray (Dictionary map, String name, float scale) { - var list = (List)map[name]; - var values = new float[list.Count]; - if (scale == 1) { - for (int i = 0, n = list.Count; i < n; i++) - values[i] = (float)list[i]; - } else { - for (int i = 0, n = list.Count; i < n; i++) - values[i] = (float)list[i] * scale; - } - return values; - } - - private int[] GetIntArray (Dictionary map, String name) { - var list = (List)map[name]; - var values = new int[list.Count]; - for (int i = 0, n = list.Count; i < n; i++) - values[i] = (int)(float)list[i]; - return values; - } - - private float GetFloat (Dictionary map, String name, float defaultValue) { - if (!map.ContainsKey(name)) - return defaultValue; - return (float)map[name]; - } - - private int GetInt (Dictionary map, String name, int defaultValue) { - if (!map.ContainsKey(name)) - return defaultValue; - return (int)(float)map[name]; - } - - private bool GetBoolean (Dictionary map, String name, bool defaultValue) { - if (!map.ContainsKey(name)) - return defaultValue; - return (bool)map[name]; - } - - private String GetString (Dictionary map, String name, String defaultValue) { - if (!map.ContainsKey(name)) - return defaultValue; - return (String)map[name]; - } - - private float ToColor (String hexString, int colorIndex) { - if (hexString.Length != 8) - throw new ArgumentException("Color hexidecimal length must be 8, recieved: " + hexString); - return Convert.ToInt32(hexString.Substring(colorIndex * 2, 2), 16) / (float)255; - } - - private void ReadAnimation (String name, Dictionary map, SkeletonData skeletonData) { - var timelines = new ExposedList(); - float duration = 0; - var scale = this.Scale; - - if (map.ContainsKey("slots")) { - foreach (KeyValuePair entry in (Dictionary)map["slots"]) { - String slotName = entry.Key; - int slotIndex = skeletonData.FindSlotIndex(slotName); - var timelineMap = (Dictionary)entry.Value; - - foreach (KeyValuePair timelineEntry in timelineMap) { - var values = (List)timelineEntry.Value; - var timelineName = (String)timelineEntry.Key; - if (timelineName == "color") { - var timeline = new ColorTimeline(values.Count); - timeline.slotIndex = slotIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - String c = (String)valueMap["color"]; - timeline.SetFrame(frameIndex, time, ToColor(c, 0), ToColor(c, 1), ToColor(c, 2), ToColor(c, 3)); - ReadCurve(timeline, frameIndex, valueMap); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 5 - 5]); - - } else if (timelineName == "attachment") { - var timeline = new AttachmentTimeline(values.Count); - timeline.slotIndex = slotIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - timeline.SetFrame(frameIndex++, time, (String)valueMap["name"]); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); - - } else - throw new Exception("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")"); - } - } - } - - if (map.ContainsKey("bones")) { - foreach (KeyValuePair entry in (Dictionary)map["bones"]) { - String boneName = entry.Key; - int boneIndex = skeletonData.FindBoneIndex(boneName); - if (boneIndex == -1) - throw new Exception("Bone not found: " + boneName); - - var timelineMap = (Dictionary)entry.Value; - foreach (KeyValuePair timelineEntry in timelineMap) { - var values = (List)timelineEntry.Value; - var timelineName = (String)timelineEntry.Key; - if (timelineName == "rotate") { - var timeline = new RotateTimeline(values.Count); - timeline.boneIndex = boneIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - timeline.SetFrame(frameIndex, time, (float)valueMap["angle"]); - ReadCurve(timeline, frameIndex, valueMap); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 2 - 2]); - - } else if (timelineName == "translate" || timelineName == "scale") { - TranslateTimeline timeline; - float timelineScale = 1; - if (timelineName == "scale") - timeline = new ScaleTimeline(values.Count); - else { - timeline = new TranslateTimeline(values.Count); - timelineScale = scale; - } - timeline.boneIndex = boneIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - float x = valueMap.ContainsKey("x") ? (float)valueMap["x"] : 0; - float y = valueMap.ContainsKey("y") ? (float)valueMap["y"] : 0; - timeline.SetFrame(frameIndex, time, (float)x * timelineScale, (float)y * timelineScale); - ReadCurve(timeline, frameIndex, valueMap); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 3 - 3]); - - } else - throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")"); - } - } - } - - if (map.ContainsKey("ik")) { - foreach (KeyValuePair ikMap in (Dictionary)map["ik"]) { - IkConstraintData ikConstraint = skeletonData.FindIkConstraint(ikMap.Key); - var values = (List)ikMap.Value; - var timeline = new IkConstraintTimeline(values.Count); - timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(ikConstraint); - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - float mix = valueMap.ContainsKey("mix") ? (float)valueMap["mix"] : 1; - bool bendPositive = valueMap.ContainsKey("bendPositive") ? (bool)valueMap["bendPositive"] : true; - timeline.SetFrame(frameIndex, time, mix, bendPositive ? 1 : -1); - ReadCurve(timeline, frameIndex, valueMap); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 3 - 3]); - } - } - - if (map.ContainsKey("ffd")) { - foreach (KeyValuePair ffdMap in (Dictionary)map["ffd"]) { - Skin skin = skeletonData.FindSkin(ffdMap.Key); - foreach (KeyValuePair slotMap in (Dictionary)ffdMap.Value) { - int slotIndex = skeletonData.FindSlotIndex(slotMap.Key); - foreach (KeyValuePair meshMap in (Dictionary)slotMap.Value) { - var values = (List)meshMap.Value; - var timeline = new FfdTimeline(values.Count); - Attachment attachment = skin.GetAttachment(slotIndex, meshMap.Key); - if (attachment == null) throw new Exception("FFD attachment not found: " + meshMap.Key); - timeline.slotIndex = slotIndex; - timeline.attachment = attachment; - - int vertexCount; - if (attachment is MeshAttachment) - vertexCount = ((MeshAttachment)attachment).vertices.Length; - else - vertexCount = ((WeightedMeshAttachment)attachment).Weights.Length / 3 * 2; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float[] vertices; - if (!valueMap.ContainsKey("vertices")) { - if (attachment is MeshAttachment) - vertices = ((MeshAttachment)attachment).vertices; - else - vertices = new float[vertexCount]; - } else { - var verticesValue = (List)valueMap["vertices"]; - vertices = new float[vertexCount]; - int start = GetInt(valueMap, "offset", 0); - if (scale == 1) { - for (int i = 0, n = verticesValue.Count; i < n; i++) - vertices[i + start] = (float)verticesValue[i]; - } else { - for (int i = 0, n = verticesValue.Count; i < n; i++) - vertices[i + start] = (float)verticesValue[i] * scale; - } - if (attachment is MeshAttachment) { - float[] meshVertices = ((MeshAttachment)attachment).vertices; - for (int i = 0; i < vertexCount; i++) - vertices[i] += meshVertices[i]; - } - } - - timeline.SetFrame(frameIndex, (float)valueMap["time"], vertices); - ReadCurve(timeline, frameIndex, valueMap); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); - } - } - } - } - - if (map.ContainsKey("drawOrder") || map.ContainsKey("draworder")) { - var values = (List)map[map.ContainsKey("drawOrder") ? "drawOrder" : "draworder"]; - var timeline = new DrawOrderTimeline(values.Count); - int slotCount = skeletonData.slots.Count; - int frameIndex = 0; - foreach (Dictionary drawOrderMap in values) { - int[] drawOrder = null; - if (drawOrderMap.ContainsKey("offsets")) { - drawOrder = new int[slotCount]; - for (int i = slotCount - 1; i >= 0; i--) - drawOrder[i] = -1; - var offsets = (List)drawOrderMap["offsets"]; - int[] unchanged = new int[slotCount - offsets.Count]; - int originalIndex = 0, unchangedIndex = 0; - foreach (Dictionary offsetMap in offsets) { - int slotIndex = skeletonData.FindSlotIndex((String)offsetMap["slot"]); - if (slotIndex == -1) throw new Exception("Slot not found: " + offsetMap["slot"]); - // Collect unchanged items. - while (originalIndex != slotIndex) - unchanged[unchangedIndex++] = originalIndex++; - // Set changed items. - int index = originalIndex + (int)(float)offsetMap["offset"]; - drawOrder[index] = originalIndex++; - } - // Collect remaining unchanged items. - while (originalIndex < slotCount) - unchanged[unchangedIndex++] = originalIndex++; - // Fill in unchanged items. - for (int i = slotCount - 1; i >= 0; i--) - if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex]; - } - timeline.SetFrame(frameIndex++, (float)drawOrderMap["time"], drawOrder); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); - } - - if (map.ContainsKey("events")) { - var eventsMap = (List)map["events"]; - var timeline = new EventTimeline(eventsMap.Count); - int frameIndex = 0; - foreach (Dictionary eventMap in eventsMap) { - EventData eventData = skeletonData.FindEvent((String)eventMap["name"]); - if (eventData == null) throw new Exception("Event not found: " + eventMap["name"]); - var e = new Event((float)eventMap["time"], eventData); - e.Int = GetInt(eventMap, "int", eventData.Int); - e.Float = GetFloat(eventMap, "float", eventData.Float); - e.String = GetString(eventMap, "string", eventData.String); - timeline.SetFrame(frameIndex++, e); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); - } - - timelines.TrimExcess(); - skeletonData.animations.Add(new Animation(name, timelines, duration)); - } - - private void ReadCurve (CurveTimeline timeline, int frameIndex, Dictionary valueMap) { - if (!valueMap.ContainsKey("curve")) - return; - Object curveObject = valueMap["curve"]; - if (curveObject.Equals("stepped")) - timeline.SetStepped(frameIndex); - else if (curveObject is List) { - var curve = (List)curveObject; - timeline.SetCurve(frameIndex, (float)curve[0], (float)curve[1], (float)curve[2], (float)curve[3]); - } - } - - internal class LinkedMesh { - internal String parent, skin; - internal int slotIndex; - internal Attachment mesh; - - public LinkedMesh (Attachment mesh, String skin, int slotIndex, String parent) { - this.mesh = mesh; - this.skin = skin; - this.slotIndex = slotIndex; - this.parent = parent; - } - } - } +#else + using (StreamReader reader = new StreamReader(path)) + { +#endif // WINDOWS_PHONE + SkeletonData skeletonData = ReadSkeletonData(reader); + skeletonData.name = Path.GetFileNameWithoutExtension(path); + return skeletonData; + } + } + +#endif // WINDOWS_STOREAPP +#endif // !UNITY + + public SkeletonData ReadSkeletonData(TextReader reader) + { + if (reader == null) throw new ArgumentNullException("reader cannot be null."); + + var scale = this.Scale; + var skeletonData = new SkeletonData(); + + var root = Json.Deserialize(reader) as Dictionary; + if (root == null) throw new Exception("Invalid JSON."); + + // Skeleton. + if (root.ContainsKey("skeleton")) + { + var skeletonMap = (Dictionary)root["skeleton"]; + skeletonData.hash = (String)skeletonMap["hash"]; + skeletonData.version = (String)skeletonMap["spine"]; + skeletonData.width = GetFloat(skeletonMap, "width", 0); + skeletonData.height = GetFloat(skeletonMap, "height", 0); + } + + // Bones. + foreach (Dictionary boneMap in (List)root["bones"]) + { + BoneData parent = null; + if (boneMap.ContainsKey("parent")) + { + parent = skeletonData.FindBone((String)boneMap["parent"]); + if (parent == null) + throw new Exception("Parent bone not found: " + boneMap["parent"]); + } + var boneData = new BoneData((String)boneMap["name"], parent); + boneData.length = GetFloat(boneMap, "length", 0) * scale; + boneData.x = GetFloat(boneMap, "x", 0) * scale; + boneData.y = GetFloat(boneMap, "y", 0) * scale; + boneData.rotation = GetFloat(boneMap, "rotation", 0); + boneData.scaleX = GetFloat(boneMap, "scaleX", 1); + boneData.scaleY = GetFloat(boneMap, "scaleY", 1); + boneData.inheritScale = GetBoolean(boneMap, "inheritScale", true); + boneData.inheritRotation = GetBoolean(boneMap, "inheritRotation", true); + skeletonData.bones.Add(boneData); + } + + // IK constraints. + if (root.ContainsKey("ik")) + { + foreach (Dictionary ikMap in (List)root["ik"]) + { + IkConstraintData ikConstraintData = new IkConstraintData((String)ikMap["name"]); + + foreach (String boneName in (List)ikMap["bones"]) + { + BoneData bone = skeletonData.FindBone(boneName); + if (bone == null) throw new Exception("IK bone not found: " + boneName); + ikConstraintData.bones.Add(bone); + } + + String targetName = (String)ikMap["target"]; + ikConstraintData.target = skeletonData.FindBone(targetName); + if (ikConstraintData.target == null) throw new Exception("Target bone not found: " + targetName); + + ikConstraintData.bendDirection = GetBoolean(ikMap, "bendPositive", true) ? 1 : -1; + ikConstraintData.mix = GetFloat(ikMap, "mix", 1); + + skeletonData.ikConstraints.Add(ikConstraintData); + } + } + + // Transform constraints. + if (root.ContainsKey("transform")) + { + foreach (Dictionary transformMap in (List)root["transform"]) + { + TransformConstraintData transformConstraintData = new TransformConstraintData((String)transformMap["name"]); + + String boneName = (String)transformMap["bone"]; + transformConstraintData.bone = skeletonData.FindBone(boneName); + if (transformConstraintData.bone == null) throw new Exception("Bone not found: " + boneName); + + String targetName = (String)transformMap["target"]; + transformConstraintData.target = skeletonData.FindBone(targetName); + if (transformConstraintData.target == null) throw new Exception("Target bone not found: " + targetName); + + transformConstraintData.translateMix = GetFloat(transformMap, "translateMix", 1); + transformConstraintData.x = GetFloat(transformMap, "x", 0) * scale; + transformConstraintData.y = GetFloat(transformMap, "y", 0) * scale; + + skeletonData.transformConstraints.Add(transformConstraintData); + } + } + + // Slots. + if (root.ContainsKey("slots")) + { + foreach (Dictionary slotMap in (List)root["slots"]) + { + var slotName = (String)slotMap["name"]; + var boneName = (String)slotMap["bone"]; + BoneData boneData = skeletonData.FindBone(boneName); + if (boneData == null) + throw new Exception("Slot bone not found: " + boneName); + var slotData = new SlotData(slotName, boneData); + + if (slotMap.ContainsKey("color")) + { + var color = (String)slotMap["color"]; + slotData.r = ToColor(color, 0); + slotData.g = ToColor(color, 1); + slotData.b = ToColor(color, 2); + slotData.a = ToColor(color, 3); + } + + if (slotMap.ContainsKey("attachment")) + slotData.attachmentName = (String)slotMap["attachment"]; + + if (slotMap.ContainsKey("blend")) + slotData.blendMode = (BlendMode)Enum.Parse(typeof(BlendMode), (String)slotMap["blend"], false); + else + slotData.blendMode = BlendMode.normal; + + skeletonData.slots.Add(slotData); + } + } + + // Skins. + if (root.ContainsKey("skins")) + { + foreach (KeyValuePair entry in (Dictionary)root["skins"]) + { + var skin = new Skin(entry.Key); + foreach (KeyValuePair slotEntry in (Dictionary)entry.Value) + { + int slotIndex = skeletonData.FindSlotIndex(slotEntry.Key); + foreach (KeyValuePair attachmentEntry in ((Dictionary)slotEntry.Value)) + { + Attachment attachment = ReadAttachment(skin, slotIndex, attachmentEntry.Key, (Dictionary)attachmentEntry.Value); + if (attachment != null) skin.AddAttachment(slotIndex, attachmentEntry.Key, attachment); + } + } + skeletonData.skins.Add(skin); + if (skin.name == "default") + skeletonData.defaultSkin = skin; + } + } + + // Linked meshes. + for (int i = 0, n = linkedMeshes.Count; i < n; i++) + { + LinkedMesh linkedMesh = linkedMeshes[i]; + Skin skin = linkedMesh.skin == null ? skeletonData.defaultSkin : skeletonData.FindSkin(linkedMesh.skin); + if (skin == null) throw new Exception("Slot not found: " + linkedMesh.skin); + Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); + if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); + if (linkedMesh.mesh is MeshAttachment) + { + MeshAttachment mesh = (MeshAttachment)linkedMesh.mesh; + mesh.ParentMesh = (MeshAttachment)parent; + mesh.UpdateUVs(); + } + else + { + WeightedMeshAttachment mesh = (WeightedMeshAttachment)linkedMesh.mesh; + mesh.ParentMesh = (WeightedMeshAttachment)parent; + mesh.UpdateUVs(); + } + } + linkedMeshes.Clear(); + + // Events. + if (root.ContainsKey("events")) + { + foreach (KeyValuePair entry in (Dictionary)root["events"]) + { + var entryMap = (Dictionary)entry.Value; + var eventData = new EventData(entry.Key); + eventData.Int = GetInt(entryMap, "int", 0); + eventData.Float = GetFloat(entryMap, "float", 0); + eventData.String = GetString(entryMap, "string", null); + skeletonData.events.Add(eventData); + } + } + + // Animations. + if (root.ContainsKey("animations")) + { + foreach (KeyValuePair entry in (Dictionary)root["animations"]) + ReadAnimation(entry.Key, (Dictionary)entry.Value, skeletonData); + } + + skeletonData.bones.TrimExcess(); + skeletonData.slots.TrimExcess(); + skeletonData.skins.TrimExcess(); + skeletonData.events.TrimExcess(); + skeletonData.animations.TrimExcess(); + skeletonData.ikConstraints.TrimExcess(); + return skeletonData; + } + + private Attachment ReadAttachment(Skin skin, int slotIndex, String name, Dictionary map) + { + if (map.ContainsKey("name")) + name = (String)map["name"]; + + var scale = this.Scale; + + var type = AttachmentType.region; + if (map.ContainsKey("type")) + { + var typeName = (String)map["type"]; + if (typeName == "skinnedmesh") typeName = "weightedmesh"; + type = (AttachmentType)Enum.Parse(typeof(AttachmentType), typeName, false); + } + + String path = name; + if (map.ContainsKey("path")) + path = (String)map["path"]; + + switch (type) + { + case AttachmentType.region: + RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); + if (region == null) return null; + region.Path = path; + region.x = GetFloat(map, "x", 0) * scale; + region.y = GetFloat(map, "y", 0) * scale; + region.scaleX = GetFloat(map, "scaleX", 1); + region.scaleY = GetFloat(map, "scaleY", 1); + region.rotation = GetFloat(map, "rotation", 0); + region.width = GetFloat(map, "width", 32) * scale; + region.height = GetFloat(map, "height", 32) * scale; + region.UpdateOffset(); + + if (map.ContainsKey("color")) + { + var color = (String)map["color"]; + region.r = ToColor(color, 0); + region.g = ToColor(color, 1); + region.b = ToColor(color, 2); + region.a = ToColor(color, 3); + } + + return region; + case AttachmentType.mesh: + case AttachmentType.linkedmesh: + { + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); + if (mesh == null) return null; + mesh.Path = path; + + if (map.ContainsKey("color")) + { + var color = (String)map["color"]; + mesh.r = ToColor(color, 0); + mesh.g = ToColor(color, 1); + mesh.b = ToColor(color, 2); + mesh.a = ToColor(color, 3); + } + + mesh.Width = GetInt(map, "width", 0) * scale; + mesh.Height = GetInt(map, "height", 0) * scale; + + String parent = GetString(map, "parent", null); + if (parent == null) + { + mesh.vertices = GetFloatArray(map, "vertices", scale); + mesh.triangles = GetIntArray(map, "triangles"); + mesh.regionUVs = GetFloatArray(map, "uvs", 1); + mesh.UpdateUVs(); + + mesh.HullLength = GetInt(map, "hull", 0) * 2; + if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges"); + } + else + { + mesh.InheritFFD = GetBoolean(map, "ffd", true); + linkedMeshes.Add(new LinkedMesh(mesh, GetString(map, "skin", null), slotIndex, parent)); + } + + return mesh; + } + case AttachmentType.weightedmesh: + case AttachmentType.weightedlinkedmesh: + { + WeightedMeshAttachment mesh = attachmentLoader.NewWeightedMeshAttachment(skin, name, path); + if (mesh == null) return null; + + mesh.Path = path; + + if (map.ContainsKey("color")) + { + var color = (String)map["color"]; + mesh.r = ToColor(color, 0); + mesh.g = ToColor(color, 1); + mesh.b = ToColor(color, 2); + mesh.a = ToColor(color, 3); + } + + mesh.Width = GetInt(map, "width", 0) * scale; + mesh.Height = GetInt(map, "height", 0) * scale; + + String parent = GetString(map, "parent", null); + if (parent == null) + { + float[] uvs = GetFloatArray(map, "uvs", 1); + float[] vertices = GetFloatArray(map, "vertices", 1); + var weights = new List(uvs.Length * 3 * 3); + var bones = new List(uvs.Length * 3); + for (int i = 0, n = vertices.Length; i < n;) + { + int boneCount = (int)vertices[i++]; + bones.Add(boneCount); + for (int nn = i + boneCount * 4; i < nn; i += 4) + { + bones.Add((int)vertices[i]); + weights.Add(vertices[i + 1] * scale); + weights.Add(vertices[i + 2] * scale); + weights.Add(vertices[i + 3]); + } + } + mesh.bones = bones.ToArray(); + mesh.weights = weights.ToArray(); + mesh.triangles = GetIntArray(map, "triangles"); + mesh.regionUVs = uvs; + mesh.UpdateUVs(); + + mesh.HullLength = GetInt(map, "hull", 0) * 2; + if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges"); + } + else + { + mesh.InheritFFD = GetBoolean(map, "ffd", true); + linkedMeshes.Add(new LinkedMesh(mesh, GetString(map, "skin", null), slotIndex, parent)); + } + + return mesh; + } + case AttachmentType.boundingbox: + BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); + if (box == null) return null; + box.vertices = GetFloatArray(map, "vertices", scale); + return box; + } + return null; + } + + private float[] GetFloatArray(Dictionary map, String name, float scale) + { + var list = (List)map[name]; + var values = new float[list.Count]; + if (scale == 1) + { + for (int i = 0, n = list.Count; i < n; i++) + values[i] = (float)list[i]; + } + else + { + for (int i = 0, n = list.Count; i < n; i++) + values[i] = (float)list[i] * scale; + } + return values; + } + + private int[] GetIntArray(Dictionary map, String name) + { + var list = (List)map[name]; + var values = new int[list.Count]; + for (int i = 0, n = list.Count; i < n; i++) + values[i] = (int)(float)list[i]; + return values; + } + + private float GetFloat(Dictionary map, String name, float defaultValue) + { + if (!map.ContainsKey(name)) + return defaultValue; + return (float)map[name]; + } + + private int GetInt(Dictionary map, String name, int defaultValue) + { + if (!map.ContainsKey(name)) + return defaultValue; + return (int)(float)map[name]; + } + + private bool GetBoolean(Dictionary map, String name, bool defaultValue) + { + if (!map.ContainsKey(name)) + return defaultValue; + return (bool)map[name]; + } + + private String GetString(Dictionary map, String name, String defaultValue) + { + if (!map.ContainsKey(name)) + return defaultValue; + return (String)map[name]; + } + + private float ToColor(String hexString, int colorIndex) + { + if (hexString.Length != 8) + throw new ArgumentException("Color hexidecimal length must be 8, recieved: " + hexString); + return Convert.ToInt32(hexString.Substring(colorIndex * 2, 2), 16) / (float)255; + } + + private void ReadAnimation(String name, Dictionary map, SkeletonData skeletonData) + { + var timelines = new ExposedList(); + float duration = 0; + var scale = this.Scale; + + if (map.ContainsKey("slots")) + { + foreach (KeyValuePair entry in (Dictionary)map["slots"]) + { + String slotName = entry.Key; + int slotIndex = skeletonData.FindSlotIndex(slotName); + var timelineMap = (Dictionary)entry.Value; + + foreach (KeyValuePair timelineEntry in timelineMap) + { + var values = (List)timelineEntry.Value; + var timelineName = timelineEntry.Key; + if (timelineName == "color") + { + var timeline = new ColorTimeline(values.Count); + timeline.slotIndex = slotIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + String c = (String)valueMap["color"]; + timeline.SetFrame(frameIndex, time, ToColor(c, 0), ToColor(c, 1), ToColor(c, 2), ToColor(c, 3)); + ReadCurve(timeline, frameIndex, valueMap); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 5 - 5]); + + } + else if (timelineName == "attachment") + { + var timeline = new AttachmentTimeline(values.Count); + timeline.slotIndex = slotIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + timeline.SetFrame(frameIndex++, time, (String)valueMap["name"]); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); + + } + else + throw new Exception("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")"); + } + } + } + + if (map.ContainsKey("bones")) + { + foreach (KeyValuePair entry in (Dictionary)map["bones"]) + { + String boneName = entry.Key; + int boneIndex = skeletonData.FindBoneIndex(boneName); + if (boneIndex == -1) + throw new Exception("Bone not found: " + boneName); + + var timelineMap = (Dictionary)entry.Value; + foreach (KeyValuePair timelineEntry in timelineMap) + { + var values = (List)timelineEntry.Value; + var timelineName = timelineEntry.Key; + if (timelineName == "rotate") + { + var timeline = new RotateTimeline(values.Count); + timeline.boneIndex = boneIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + timeline.SetFrame(frameIndex, time, (float)valueMap["angle"]); + ReadCurve(timeline, frameIndex, valueMap); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 2 - 2]); + + } + else if (timelineName == "translate" || timelineName == "scale") + { + TranslateTimeline timeline; + float timelineScale = 1; + if (timelineName == "scale") + timeline = new ScaleTimeline(values.Count); + else + { + timeline = new TranslateTimeline(values.Count); + timelineScale = scale; + } + timeline.boneIndex = boneIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + float x = valueMap.ContainsKey("x") ? (float)valueMap["x"] : 0; + float y = valueMap.ContainsKey("y") ? (float)valueMap["y"] : 0; + timeline.SetFrame(frameIndex, time, (float)x * timelineScale, (float)y * timelineScale); + ReadCurve(timeline, frameIndex, valueMap); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 3 - 3]); + + } + else + throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")"); + } + } + } + + if (map.ContainsKey("ik")) + { + foreach (KeyValuePair ikMap in (Dictionary)map["ik"]) + { + IkConstraintData ikConstraint = skeletonData.FindIkConstraint(ikMap.Key); + var values = (List)ikMap.Value; + var timeline = new IkConstraintTimeline(values.Count); + timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(ikConstraint); + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + float mix = valueMap.ContainsKey("mix") ? (float)valueMap["mix"] : 1; + bool bendPositive = valueMap.ContainsKey("bendPositive") ? (bool)valueMap["bendPositive"] : true; + timeline.SetFrame(frameIndex, time, mix, bendPositive ? 1 : -1); + ReadCurve(timeline, frameIndex, valueMap); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 3 - 3]); + } + } + + if (map.ContainsKey("ffd")) + { + foreach (KeyValuePair ffdMap in (Dictionary)map["ffd"]) + { + Skin skin = skeletonData.FindSkin(ffdMap.Key); + foreach (KeyValuePair slotMap in (Dictionary)ffdMap.Value) + { + int slotIndex = skeletonData.FindSlotIndex(slotMap.Key); + foreach (KeyValuePair meshMap in (Dictionary)slotMap.Value) + { + var values = (List)meshMap.Value; + var timeline = new FfdTimeline(values.Count); + Attachment attachment = skin.GetAttachment(slotIndex, meshMap.Key); + if (attachment == null) throw new Exception("FFD attachment not found: " + meshMap.Key); + timeline.slotIndex = slotIndex; + timeline.attachment = attachment; + + int vertexCount; + if (attachment is MeshAttachment) + vertexCount = ((MeshAttachment)attachment).vertices.Length; + else + vertexCount = ((WeightedMeshAttachment)attachment).Weights.Length / 3 * 2; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float[] vertices; + if (!valueMap.ContainsKey("vertices")) + { + if (attachment is MeshAttachment) + vertices = ((MeshAttachment)attachment).vertices; + else + vertices = new float[vertexCount]; + } + else + { + var verticesValue = (List)valueMap["vertices"]; + vertices = new float[vertexCount]; + int start = GetInt(valueMap, "offset", 0); + if (scale == 1) + { + for (int i = 0, n = verticesValue.Count; i < n; i++) + vertices[i + start] = (float)verticesValue[i]; + } + else + { + for (int i = 0, n = verticesValue.Count; i < n; i++) + vertices[i + start] = (float)verticesValue[i] * scale; + } + if (attachment is MeshAttachment) + { + float[] meshVertices = ((MeshAttachment)attachment).vertices; + for (int i = 0; i < vertexCount; i++) + vertices[i] += meshVertices[i]; + } + } + + timeline.SetFrame(frameIndex, (float)valueMap["time"], vertices); + ReadCurve(timeline, frameIndex, valueMap); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); + } + } + } + } + + if (map.ContainsKey("drawOrder") || map.ContainsKey("draworder")) + { + var values = (List)map[map.ContainsKey("drawOrder") ? "drawOrder" : "draworder"]; + var timeline = new DrawOrderTimeline(values.Count); + int slotCount = skeletonData.slots.Count; + int frameIndex = 0; + foreach (Dictionary drawOrderMap in values) + { + int[] drawOrder = null; + if (drawOrderMap.ContainsKey("offsets")) + { + drawOrder = new int[slotCount]; + for (int i = slotCount - 1; i >= 0; i--) + drawOrder[i] = -1; + var offsets = (List)drawOrderMap["offsets"]; + int[] unchanged = new int[slotCount - offsets.Count]; + int originalIndex = 0, unchangedIndex = 0; + foreach (Dictionary offsetMap in offsets) + { + int slotIndex = skeletonData.FindSlotIndex((String)offsetMap["slot"]); + if (slotIndex == -1) throw new Exception("Slot not found: " + offsetMap["slot"]); + // Collect unchanged items. + while (originalIndex != slotIndex) + unchanged[unchangedIndex++] = originalIndex++; + // Set changed items. + int index = originalIndex + (int)(float)offsetMap["offset"]; + drawOrder[index] = originalIndex++; + } + // Collect remaining unchanged items. + while (originalIndex < slotCount) + unchanged[unchangedIndex++] = originalIndex++; + // Fill in unchanged items. + for (int i = slotCount - 1; i >= 0; i--) + if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex]; + } + timeline.SetFrame(frameIndex++, (float)drawOrderMap["time"], drawOrder); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); + } + + if (map.ContainsKey("events")) + { + var eventsMap = (List)map["events"]; + var timeline = new EventTimeline(eventsMap.Count); + int frameIndex = 0; + foreach (Dictionary eventMap in eventsMap) + { + EventData eventData = skeletonData.FindEvent((String)eventMap["name"]); + if (eventData == null) throw new Exception("Event not found: " + eventMap["name"]); + var e = new Event((float)eventMap["time"], eventData); + e.Int = GetInt(eventMap, "int", eventData.Int); + e.Float = GetFloat(eventMap, "float", eventData.Float); + e.String = GetString(eventMap, "string", eventData.String); + timeline.SetFrame(frameIndex++, e); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); + } + + timelines.TrimExcess(); + skeletonData.animations.Add(new Animation(name, timelines, duration)); + } + + private void ReadCurve(CurveTimeline timeline, int frameIndex, Dictionary valueMap) + { + if (!valueMap.ContainsKey("curve")) + return; + Object curveObject = valueMap["curve"]; + if (curveObject.Equals("stepped")) + timeline.SetStepped(frameIndex); + else if (curveObject is List) + { + var curve = (List)curveObject; + timeline.SetCurve(frameIndex, (float)curve[0], (float)curve[1], (float)curve[2], (float)curve[3]); + } + } + + internal class LinkedMesh + { + internal String parent, skin; + internal int slotIndex; + internal Attachment mesh; + + public LinkedMesh(Attachment mesh, String skin, int slotIndex, String parent) + { + this.mesh = mesh; + this.skin = skin; + this.slotIndex = slotIndex; + this.parent = parent; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Skin.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Skin.cs index 4899ca2..3e17687 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Skin.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Skin.cs @@ -32,83 +32,99 @@ using System; using System.Collections.Generic; -namespace Spine3_1_07 { - /// Stores attachments by slot index and attachment name. - public class Skin { - internal String name; - private Dictionary attachments = - new Dictionary(AttachmentKeyTupleComparer.Instance); +namespace Spine3_1_07 +{ + /// Stores attachments by slot index and attachment name. + public class Skin + { + internal String name; + private Dictionary attachments = + new Dictionary(AttachmentKeyTupleComparer.Instance); - public String Name { get { return name; } } + public String Name { get { return name; } } - public Skin (String name) { - if (name == null) throw new ArgumentNullException("name cannot be null."); - this.name = name; - } + public Skin(String name) + { + if (name == null) throw new ArgumentNullException("name cannot be null."); + this.name = name; + } - public void AddAttachment (int slotIndex, String name, Attachment attachment) { - if (attachment == null) throw new ArgumentNullException("attachment cannot be null."); - attachments[new AttachmentKeyTuple(slotIndex, name)] = attachment; - } + public void AddAttachment(int slotIndex, String name, Attachment attachment) + { + if (attachment == null) throw new ArgumentNullException("attachment cannot be null."); + attachments[new AttachmentKeyTuple(slotIndex, name)] = attachment; + } - /// May be null. - public Attachment GetAttachment (int slotIndex, String name) { - Attachment attachment; - attachments.TryGetValue(new AttachmentKeyTuple(slotIndex, name), out attachment); - return attachment; - } + /// May be null. + public Attachment GetAttachment(int slotIndex, String name) + { + Attachment attachment; + attachments.TryGetValue(new AttachmentKeyTuple(slotIndex, name), out attachment); + return attachment; + } - public void FindNamesForSlot (int slotIndex, List names) { - if (names == null) throw new ArgumentNullException("names cannot be null."); - foreach (AttachmentKeyTuple key in attachments.Keys) - if (key.slotIndex == slotIndex) names.Add(key.name); - } + public void FindNamesForSlot(int slotIndex, List names) + { + if (names == null) throw new ArgumentNullException("names cannot be null."); + foreach (AttachmentKeyTuple key in attachments.Keys) + if (key.slotIndex == slotIndex) names.Add(key.name); + } - public void FindAttachmentsForSlot (int slotIndex, List attachments) { - if (attachments == null) throw new ArgumentNullException("attachments cannot be null."); - foreach (KeyValuePair entry in this.attachments) - if (entry.Key.slotIndex == slotIndex) attachments.Add(entry.Value); - } + public void FindAttachmentsForSlot(int slotIndex, List attachments) + { + if (attachments == null) throw new ArgumentNullException("attachments cannot be null."); + foreach (KeyValuePair entry in this.attachments) + if (entry.Key.slotIndex == slotIndex) attachments.Add(entry.Value); + } - override public String ToString () { - return name; - } + override public String ToString() + { + return name; + } - /// Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached. - internal void AttachAll (Skeleton skeleton, Skin oldSkin) { - foreach (KeyValuePair entry in oldSkin.attachments) { - int slotIndex = entry.Key.slotIndex; - Slot slot = skeleton.slots.Items[slotIndex]; - if (slot.attachment == entry.Value) { - Attachment attachment = GetAttachment(slotIndex, entry.Key.name); - if (attachment != null) slot.Attachment = attachment; - } - } - } + /// Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached. + internal void AttachAll(Skeleton skeleton, Skin oldSkin) + { + foreach (KeyValuePair entry in oldSkin.attachments) + { + int slotIndex = entry.Key.slotIndex; + Slot slot = skeleton.slots.Items[slotIndex]; + if (slot.attachment == entry.Value) + { + Attachment attachment = GetAttachment(slotIndex, entry.Key.name); + if (attachment != null) slot.Attachment = attachment; + } + } + } - struct AttachmentKeyTuple { - public readonly int slotIndex; - public readonly string name; - internal readonly int nameHashCode; + struct AttachmentKeyTuple + { + public readonly int slotIndex; + public readonly string name; + internal readonly int nameHashCode; - public AttachmentKeyTuple (int slotIndex, string name) { - this.slotIndex = slotIndex; - this.name = name; - nameHashCode = this.name.GetHashCode(); - } - } + public AttachmentKeyTuple(int slotIndex, string name) + { + this.slotIndex = slotIndex; + this.name = name; + nameHashCode = this.name.GetHashCode(); + } + } - // Avoids boxing in the dictionary. - class AttachmentKeyTupleComparer : IEqualityComparer { - internal static readonly AttachmentKeyTupleComparer Instance = new AttachmentKeyTupleComparer(); + // Avoids boxing in the dictionary. + class AttachmentKeyTupleComparer : IEqualityComparer + { + internal static readonly AttachmentKeyTupleComparer Instance = new AttachmentKeyTupleComparer(); - bool IEqualityComparer.Equals (AttachmentKeyTuple o1, AttachmentKeyTuple o2) { - return o1.slotIndex == o2.slotIndex && o1.nameHashCode == o2.nameHashCode && o1.name == o2.name; - } + bool IEqualityComparer.Equals(AttachmentKeyTuple o1, AttachmentKeyTuple o2) + { + return o1.slotIndex == o2.slotIndex && o1.nameHashCode == o2.nameHashCode && o1.name == o2.name; + } - int IEqualityComparer.GetHashCode (AttachmentKeyTuple o) { - return o.slotIndex; - } - } - } + int IEqualityComparer.GetHashCode(AttachmentKeyTuple o) + { + return o.slotIndex; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Slot.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Slot.cs index 34bee89..2fe8fb2 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Slot.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/Slot.cs @@ -31,76 +31,89 @@ using System; -namespace Spine3_1_07 { - public class Slot { - internal SlotData data; - internal Bone bone; - internal float r, g, b, a; - internal Attachment attachment; - internal float attachmentTime; - internal float[] attachmentVertices = new float[0]; - internal int attachmentVerticesCount; +namespace Spine3_1_07 +{ + public class Slot + { + internal SlotData data; + internal Bone bone; + internal float r, g, b, a; + internal Attachment attachment; + internal float attachmentTime; + internal float[] attachmentVertices = new float[0]; + internal int attachmentVerticesCount; - public SlotData Data { get { return data; } } - public Bone Bone { get { return bone; } } - public Skeleton Skeleton { get { return bone.skeleton; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } + public SlotData Data { get { return data; } } + public Bone Bone { get { return bone; } } + public Skeleton Skeleton { get { return bone.skeleton; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } - /// May be null. - public Attachment Attachment { - get { - return attachment; - } - set { - if (attachment == value) return; - attachment = value; - attachmentTime = bone.skeleton.time; - attachmentVerticesCount = 0; - } - } + /// May be null. + public Attachment Attachment + { + get + { + return attachment; + } + set + { + if (attachment == value) return; + attachment = value; + attachmentTime = bone.skeleton.time; + attachmentVerticesCount = 0; + } + } - public float AttachmentTime { - get { - return bone.skeleton.time - attachmentTime; - } - set { - attachmentTime = bone.skeleton.time - value; - } - } + public float AttachmentTime + { + get + { + return bone.skeleton.time - attachmentTime; + } + set + { + attachmentTime = bone.skeleton.time - value; + } + } - public float[] AttachmentVertices { get { return attachmentVertices; } set { attachmentVertices = value; } } - public int AttachmentVerticesCount { get { return attachmentVerticesCount; } set { attachmentVerticesCount = value; } } + public float[] AttachmentVertices { get { return attachmentVertices; } set { attachmentVertices = value; } } + public int AttachmentVerticesCount { get { return attachmentVerticesCount; } set { attachmentVerticesCount = value; } } - public Slot (SlotData data, Bone bone) { - if (data == null) throw new ArgumentNullException("data cannot be null."); - if (bone == null) throw new ArgumentNullException("bone cannot be null."); - this.data = data; - this.bone = bone; - SetToSetupPose(); - } + public Slot(SlotData data, Bone bone) + { + if (data == null) throw new ArgumentNullException("data cannot be null."); + if (bone == null) throw new ArgumentNullException("bone cannot be null."); + this.data = data; + this.bone = bone; + SetToSetupPose(); + } - internal void SetToSetupPose (int slotIndex) { - r = data.r; - g = data.g; - b = data.b; - a = data.a; - if (data.attachmentName == null) - Attachment = null; - else { - attachment = null; - Attachment = bone.skeleton.GetAttachment(slotIndex, data.attachmentName); - } - } + internal void SetToSetupPose(int slotIndex) + { + r = data.r; + g = data.g; + b = data.b; + a = data.a; + if (data.attachmentName == null) + Attachment = null; + else + { + attachment = null; + Attachment = bone.skeleton.GetAttachment(slotIndex, data.attachmentName); + } + } - public void SetToSetupPose () { - SetToSetupPose(bone.skeleton.data.slots.IndexOf(data)); - } + public void SetToSetupPose() + { + SetToSetupPose(bone.skeleton.data.slots.IndexOf(data)); + } - override public String ToString () { - return data.name; - } - } + override public String ToString() + { + return data.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/SlotData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/SlotData.cs index 88e522a..2173530 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/SlotData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/SlotData.cs @@ -31,33 +31,37 @@ using System; -namespace Spine3_1_07 { - public class SlotData { - internal String name; - internal BoneData boneData; - internal float r = 1, g = 1, b = 1, a = 1; - internal String attachmentName; - internal BlendMode blendMode; +namespace Spine3_1_07 +{ + public class SlotData + { + internal String name; + internal BoneData boneData; + internal float r = 1, g = 1, b = 1, a = 1; + internal String attachmentName; + internal BlendMode blendMode; - public String Name { get { return name; } } - public BoneData BoneData { get { return boneData; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } - /// May be null. - public String AttachmentName { get { return attachmentName; } set { attachmentName = value; } } - public BlendMode BlendMode { get { return blendMode; } set { blendMode = value; } } + public String Name { get { return name; } } + public BoneData BoneData { get { return boneData; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } + /// May be null. + public String AttachmentName { get { return attachmentName; } set { attachmentName = value; } } + public BlendMode BlendMode { get { return blendMode; } set { blendMode = value; } } - public SlotData (String name, BoneData boneData) { - if (name == null) throw new ArgumentNullException("name cannot be null."); - if (boneData == null) throw new ArgumentNullException("boneData cannot be null."); - this.name = name; - this.boneData = boneData; - } + public SlotData(String name, BoneData boneData) + { + if (name == null) throw new ArgumentNullException("name cannot be null."); + if (boneData == null) throw new ArgumentNullException("boneData cannot be null."); + this.name = name; + this.boneData = boneData; + } - override public String ToString () { - return name; - } - } + override public String ToString() + { + return name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/TransformConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/TransformConstraint.cs index 24f9474..4e32bca 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/TransformConstraint.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/TransformConstraint.cs @@ -30,51 +30,57 @@ *****************************************************************************/ using System; -using System.Collections.Generic; -namespace Spine3_1_07 { - public class TransformConstraint : IUpdatable { - internal TransformConstraintData data; - internal Bone bone, target; - internal float translateMix; - internal float x, y; +namespace Spine3_1_07 +{ + public class TransformConstraint : IUpdatable + { + internal TransformConstraintData data; + internal Bone bone, target; + internal float translateMix; + internal float x, y; - public TransformConstraintData Data { get { return data; } } - public Bone Bone { get { return bone; } set { bone = value; } } - public Bone Target { get { return target; } set { target = value; } } - public float TranslateMix { get { return translateMix; } set { translateMix = value; } } - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } + public TransformConstraintData Data { get { return data; } } + public Bone Bone { get { return bone; } set { bone = value; } } + public Bone Target { get { return target; } set { target = value; } } + public float TranslateMix { get { return translateMix; } set { translateMix = value; } } + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } - public TransformConstraint (TransformConstraintData data, Skeleton skeleton) { - if (data == null) throw new ArgumentNullException("data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); - this.data = data; - translateMix = data.translateMix; - x = data.x; - y = data.y; + public TransformConstraint(TransformConstraintData data, Skeleton skeleton) + { + if (data == null) throw new ArgumentNullException("data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); + this.data = data; + translateMix = data.translateMix; + x = data.x; + y = data.y; - bone = skeleton.FindBone(data.bone.name); - target = skeleton.FindBone(data.target.name); - } + bone = skeleton.FindBone(data.bone.name); + target = skeleton.FindBone(data.target.name); + } - public void Update () { - Apply(); - } + public void Update() + { + Apply(); + } - public void Apply () { - float translateMix = this.translateMix; - if (translateMix > 0) { - Bone bone = this.bone; - float tx, ty; - target.LocalToWorld(x, y, out tx, out ty); - bone.worldX += (tx - bone.worldX) * translateMix; - bone.worldY += (ty - bone.worldY) * translateMix; - } - } + public void Apply() + { + float translateMix = this.translateMix; + if (translateMix > 0) + { + Bone bone = this.bone; + float tx, ty; + target.LocalToWorld(x, y, out tx, out ty); + bone.worldX += (tx - bone.worldX) * translateMix; + bone.worldY += (ty - bone.worldY) * translateMix; + } + } - override public String ToString () { - return data.name; - } - } + override public String ToString() + { + return data.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/TransformConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/TransformConstraintData.cs index 2e2dacf..276fc8b 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/TransformConstraintData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/TransformConstraintData.cs @@ -30,29 +30,32 @@ *****************************************************************************/ using System; -using System.Collections.Generic; -namespace Spine3_1_07 { - public class TransformConstraintData { - internal String name; - internal BoneData bone, target; - internal float translateMix; - internal float x, y; +namespace Spine3_1_07 +{ + public class TransformConstraintData + { + internal String name; + internal BoneData bone, target; + internal float translateMix; + internal float x, y; - public String Name { get { return name; } } - public BoneData Bone { get { return bone; } set { bone = value; } } - public BoneData Target { get { return target; } set { target = value; } } - public float TranslateMix { get { return translateMix; } set { translateMix = value; } } - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } + public String Name { get { return name; } } + public BoneData Bone { get { return bone; } set { bone = value; } } + public BoneData Target { get { return target; } set { target = value; } } + public float TranslateMix { get { return translateMix; } set { translateMix = value; } } + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } - public TransformConstraintData (String name) { - if (name == null) throw new ArgumentNullException("name cannot be null."); - this.name = name; - } + public TransformConstraintData(String name) + { + if (name == null) throw new ArgumentNullException("name cannot be null."); + this.name = name; + } - override public String ToString () { - return name; - } - } + override public String ToString() + { + return name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/XnaLoader/MeshBatcher.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/XnaLoader/MeshBatcher.cs index f6755be..9e9028f 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/XnaLoader/MeshBatcher.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/XnaLoader/MeshBatcher.cs @@ -32,136 +32,146 @@ using System; using System.Collections.Generic; using Microsoft.Xna.Framework.Graphics; -using Microsoft.Xna.Framework; -namespace Spine3_1_07 { - // #region License - // /* - // Microsoft Public License (Ms-PL) - // MonoGame - Copyright 2009 The MonoGame Team - // - // All rights reserved. - // - // This license governs use of the accompanying software. If you use the software, you accept this license. If you do not - // accept the license, do not use the software. - // - // 1. Definitions - // The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under - // U.S. copyright law. - // - // A "contribution" is the original software, or any additions or changes to the software. - // A "contributor" is any person that distributes its contribution under this license. - // "Licensed patents" are a contributor's patent claims that read directly on its contribution. - // - // 2. Grant of Rights - // (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, - // each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. - // (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, - // each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. - // - // 3. Conditions and Limitations - // (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. - // (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, - // your patent license from such contributor to the software ends automatically. - // (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution - // notices that are present in the software. - // (D) If you distribute any portion of the software in source code form, you may do so only under this license by including - // a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object - // code form, you may only do so under a license that complies with this license. - // (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees - // or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent - // permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular - // purpose and non-infringement. - // */ - // #endregion License - // +namespace Spine3_1_07 +{ + // #region License + // /* + // Microsoft Public License (Ms-PL) + // MonoGame - Copyright 2009 The MonoGame Team + // + // All rights reserved. + // + // This license governs use of the accompanying software. If you use the software, you accept this license. If you do not + // accept the license, do not use the software. + // + // 1. Definitions + // The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under + // U.S. copyright law. + // + // A "contribution" is the original software, or any additions or changes to the software. + // A "contributor" is any person that distributes its contribution under this license. + // "Licensed patents" are a contributor's patent claims that read directly on its contribution. + // + // 2. Grant of Rights + // (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, + // each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. + // (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, + // each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. + // + // 3. Conditions and Limitations + // (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. + // (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, + // your patent license from such contributor to the software ends automatically. + // (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution + // notices that are present in the software. + // (D) If you distribute any portion of the software in source code form, you may do so only under this license by including + // a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object + // code form, you may only do so under a license that complies with this license. + // (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees + // or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent + // permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular + // purpose and non-infringement. + // */ + // #endregion License + // - /// Draws batched meshes. - public class MeshBatcher { - private readonly List items; - private readonly Queue freeItems; - private VertexPositionColorTexture[] vertexArray = { }; - private short[] triangles = { }; + /// Draws batched meshes. + public class MeshBatcher + { + private readonly List items; + private readonly Queue freeItems; + private VertexPositionColorTexture[] vertexArray = { }; + private short[] triangles = { }; - public MeshBatcher () { - items = new List(256); - freeItems = new Queue(256); - EnsureCapacity(256, 512); - } + public MeshBatcher() + { + items = new List(256); + freeItems = new Queue(256); + EnsureCapacity(256, 512); + } - /// Returns a pooled MeshItem. - public MeshItem NextItem (int vertexCount, int triangleCount) { - MeshItem item = freeItems.Count > 0 ? freeItems.Dequeue() : new MeshItem(); - if (item.vertices.Length < vertexCount) item.vertices = new VertexPositionColorTexture[vertexCount]; - if (item.triangles.Length < triangleCount) item.triangles = new int[triangleCount]; - item.vertexCount = vertexCount; - item.triangleCount = triangleCount; - items.Add(item); - return item; - } + /// Returns a pooled MeshItem. + public MeshItem NextItem(int vertexCount, int triangleCount) + { + MeshItem item = freeItems.Count > 0 ? freeItems.Dequeue() : new MeshItem(); + if (item.vertices.Length < vertexCount) item.vertices = new VertexPositionColorTexture[vertexCount]; + if (item.triangles.Length < triangleCount) item.triangles = new int[triangleCount]; + item.vertexCount = vertexCount; + item.triangleCount = triangleCount; + items.Add(item); + return item; + } - private void EnsureCapacity (int vertexCount, int triangleCount) { - if (vertexArray.Length < vertexCount) vertexArray = new VertexPositionColorTexture[vertexCount]; - if (triangles.Length < triangleCount) triangles = new short[triangleCount]; - } + private void EnsureCapacity(int vertexCount, int triangleCount) + { + if (vertexArray.Length < vertexCount) vertexArray = new VertexPositionColorTexture[vertexCount]; + if (triangles.Length < triangleCount) triangles = new short[triangleCount]; + } - public void Draw (GraphicsDevice device) { - if (items.Count == 0) return; + public void Draw(GraphicsDevice device) + { + if (items.Count == 0) return; - int itemCount = items.Count; - int vertexCount = 0, triangleCount = 0; - for (int i = 0; i < itemCount; i++) { - MeshItem item = items[i]; - vertexCount += item.vertexCount; - triangleCount += item.triangleCount; - } - EnsureCapacity(vertexCount, triangleCount); + int itemCount = items.Count; + int vertexCount = 0, triangleCount = 0; + for (int i = 0; i < itemCount; i++) + { + MeshItem item = items[i]; + vertexCount += item.vertexCount; + triangleCount += item.triangleCount; + } + EnsureCapacity(vertexCount, triangleCount); - vertexCount = 0; - triangleCount = 0; - Texture2D lastTexture = null; - for (int i = 0; i < itemCount; i++) { - MeshItem item = items[i]; - int itemVertexCount = item.vertexCount; + vertexCount = 0; + triangleCount = 0; + Texture2D lastTexture = null; + for (int i = 0; i < itemCount; i++) + { + MeshItem item = items[i]; + int itemVertexCount = item.vertexCount; - if (item.texture != lastTexture || vertexCount + itemVertexCount > short.MaxValue) { - FlushVertexArray(device, vertexCount, triangleCount); - vertexCount = 0; - triangleCount = 0; - lastTexture = item.texture; - device.Textures[0] = lastTexture; - } + if (item.texture != lastTexture || vertexCount + itemVertexCount > short.MaxValue) + { + FlushVertexArray(device, vertexCount, triangleCount); + vertexCount = 0; + triangleCount = 0; + lastTexture = item.texture; + device.Textures[0] = lastTexture; + } - int[] itemTriangles = item.triangles; - int itemTriangleCount = item.triangleCount; - for (int ii = 0, t = triangleCount; ii < itemTriangleCount; ii++, t++) - triangles[t] = (short)(itemTriangles[ii] + vertexCount); - triangleCount += itemTriangleCount; + int[] itemTriangles = item.triangles; + int itemTriangleCount = item.triangleCount; + for (int ii = 0, t = triangleCount; ii < itemTriangleCount; ii++, t++) + triangles[t] = (short)(itemTriangles[ii] + vertexCount); + triangleCount += itemTriangleCount; - Array.Copy(item.vertices, 0, vertexArray, vertexCount, itemVertexCount); - vertexCount += itemVertexCount; + Array.Copy(item.vertices, 0, vertexArray, vertexCount, itemVertexCount); + vertexCount += itemVertexCount; - item.texture = null; - freeItems.Enqueue(item); - } - FlushVertexArray(device, vertexCount, triangleCount); - items.Clear(); - } + item.texture = null; + freeItems.Enqueue(item); + } + FlushVertexArray(device, vertexCount, triangleCount); + items.Clear(); + } - private void FlushVertexArray (GraphicsDevice device, int vertexCount, int triangleCount) { - if (vertexCount == 0) return; - device.DrawUserIndexedPrimitives( - PrimitiveType.TriangleList, - vertexArray, 0, vertexCount, - triangles, 0, triangleCount / 3, - VertexPositionColorTexture.VertexDeclaration); - } - } + private void FlushVertexArray(GraphicsDevice device, int vertexCount, int triangleCount) + { + if (vertexCount == 0) return; + device.DrawUserIndexedPrimitives( + PrimitiveType.TriangleList, + vertexArray, 0, vertexCount, + triangles, 0, triangleCount / 3, + VertexPositionColorTexture.VertexDeclaration); + } + } - public class MeshItem { - public Texture2D texture; - public int vertexCount, triangleCount; - public VertexPositionColorTexture[] vertices = { }; - public int[] triangles = { }; - } + public class MeshItem + { + public Texture2D texture; + public int vertexCount, triangleCount; + public VertexPositionColorTexture[] vertices = { }; + public int[] triangles = { }; + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/XnaLoader/RegionBatcher.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/XnaLoader/RegionBatcher.cs index 5c7433c..255b7cb 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/XnaLoader/RegionBatcher.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/XnaLoader/RegionBatcher.cs @@ -32,151 +32,163 @@ using System; using System.Collections.Generic; using Microsoft.Xna.Framework.Graphics; -using Microsoft.Xna.Framework; -namespace Spine3_1_07 { - // #region License - // /* - // Microsoft Public License (Ms-PL) - // MonoGame - Copyright 2009 The MonoGame Team - // - // All rights reserved. - // - // This license governs use of the accompanying software. If you use the software, you accept this license. If you do not - // accept the license, do not use the software. - // - // 1. Definitions - // The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under - // U.S. copyright law. - // - // A "contribution" is the original software, or any additions or changes to the software. - // A "contributor" is any person that distributes its contribution under this license. - // "Licensed patents" are a contributor's patent claims that read directly on its contribution. - // - // 2. Grant of Rights - // (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, - // each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. - // (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, - // each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. - // - // 3. Conditions and Limitations - // (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. - // (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, - // your patent license from such contributor to the software ends automatically. - // (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution - // notices that are present in the software. - // (D) If you distribute any portion of the software in source code form, you may do so only under this license by including - // a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object - // code form, you may only do so under a license that complies with this license. - // (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees - // or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent - // permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular - // purpose and non-infringement. - // */ - // #endregion License - // +namespace Spine3_1_07 +{ + // #region License + // /* + // Microsoft Public License (Ms-PL) + // MonoGame - Copyright 2009 The MonoGame Team + // + // All rights reserved. + // + // This license governs use of the accompanying software. If you use the software, you accept this license. If you do not + // accept the license, do not use the software. + // + // 1. Definitions + // The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under + // U.S. copyright law. + // + // A "contribution" is the original software, or any additions or changes to the software. + // A "contributor" is any person that distributes its contribution under this license. + // "Licensed patents" are a contributor's patent claims that read directly on its contribution. + // + // 2. Grant of Rights + // (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, + // each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. + // (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, + // each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. + // + // 3. Conditions and Limitations + // (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. + // (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, + // your patent license from such contributor to the software ends automatically. + // (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution + // notices that are present in the software. + // (D) If you distribute any portion of the software in source code form, you may do so only under this license by including + // a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object + // code form, you may only do so under a license that complies with this license. + // (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees + // or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent + // permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular + // purpose and non-infringement. + // */ + // #endregion License + // - /// Draws batched quads using indices. - public class RegionBatcher { - private const int maxBatchSize = short.MaxValue / 6; // 6 = 4 vertices unique and 2 shared, per quad - private readonly List items; - private readonly Queue freeItems; - private VertexPositionColorTexture[] vertexArray; - private short[] indices; + /// Draws batched quads using indices. + public class RegionBatcher + { + private const int maxBatchSize = short.MaxValue / 6; // 6 = 4 vertices unique and 2 shared, per quad + private readonly List items; + private readonly Queue freeItems; + private VertexPositionColorTexture[] vertexArray; + private short[] indices; - public RegionBatcher () { - items = new List(256); - freeItems = new Queue(256); - EnsureArrayCapacity(256); - } + public RegionBatcher() + { + items = new List(256); + freeItems = new Queue(256); + EnsureArrayCapacity(256); + } - /// Returns a pooled RegionItem. - public RegionItem NextItem () { - RegionItem item = freeItems.Count > 0 ? freeItems.Dequeue() : new RegionItem(); - items.Add(item); - return item; - } + /// Returns a pooled RegionItem. + public RegionItem NextItem() + { + RegionItem item = freeItems.Count > 0 ? freeItems.Dequeue() : new RegionItem(); + items.Add(item); + return item; + } - /// Resize and recreate the indices and vertex position color buffers. - private void EnsureArrayCapacity (int itemCount) { - if (indices != null && indices.Length >= 6 * itemCount) return; + /// Resize and recreate the indices and vertex position color buffers. + private void EnsureArrayCapacity(int itemCount) + { + if (indices != null && indices.Length >= 6 * itemCount) return; - short[] newIndices = new short[6 * itemCount]; - int start = 0; - if (indices != null) { - indices.CopyTo(newIndices, 0); - start = indices.Length / 6; - } - for (var i = start; i < itemCount; i++) { - /* TL TR + short[] newIndices = new short[6 * itemCount]; + int start = 0; + if (indices != null) + { + indices.CopyTo(newIndices, 0); + start = indices.Length / 6; + } + for (var i = start; i < itemCount; i++) + { + /* TL TR * 0----1 0,1,2,3 = index offsets for vertex indices * | | TL,TR,BL,BR are vertex references in RegionItem. * 2----3 * BL BR */ - newIndices[i * 6 + 0] = (short)(i * 4); - newIndices[i * 6 + 1] = (short)(i * 4 + 1); - newIndices[i * 6 + 2] = (short)(i * 4 + 2); - newIndices[i * 6 + 3] = (short)(i * 4 + 1); - newIndices[i * 6 + 4] = (short)(i * 4 + 3); - newIndices[i * 6 + 5] = (short)(i * 4 + 2); - } - indices = newIndices; + newIndices[i * 6 + 0] = (short)(i * 4); + newIndices[i * 6 + 1] = (short)(i * 4 + 1); + newIndices[i * 6 + 2] = (short)(i * 4 + 2); + newIndices[i * 6 + 3] = (short)(i * 4 + 1); + newIndices[i * 6 + 4] = (short)(i * 4 + 3); + newIndices[i * 6 + 5] = (short)(i * 4 + 2); + } + indices = newIndices; - vertexArray = new VertexPositionColorTexture[4 * itemCount]; - } + vertexArray = new VertexPositionColorTexture[4 * itemCount]; + } - public void Draw (GraphicsDevice device) { - if (items.Count == 0) return; + public void Draw(GraphicsDevice device) + { + if (items.Count == 0) return; - int itemIndex = 0; - int itemCount = items.Count; - while (itemCount > 0) { - int itemsToProcess = Math.Min(itemCount, maxBatchSize); - EnsureArrayCapacity(itemsToProcess); + int itemIndex = 0; + int itemCount = items.Count; + while (itemCount > 0) + { + int itemsToProcess = Math.Min(itemCount, maxBatchSize); + EnsureArrayCapacity(itemsToProcess); - var count = 0; - Texture2D texture = null; - for (int i = 0; i < itemsToProcess; i++, itemIndex++) { - RegionItem item = items[itemIndex]; - if (item.texture != texture) { - FlushVertexArray(device, count); - texture = item.texture; - count = 0; - device.Textures[0] = texture; - } + var count = 0; + Texture2D texture = null; + for (int i = 0; i < itemsToProcess; i++, itemIndex++) + { + RegionItem item = items[itemIndex]; + if (item.texture != texture) + { + FlushVertexArray(device, count); + texture = item.texture; + count = 0; + device.Textures[0] = texture; + } - vertexArray[count++] = item.vertexTL; - vertexArray[count++] = item.vertexTR; - vertexArray[count++] = item.vertexBL; - vertexArray[count++] = item.vertexBR; + vertexArray[count++] = item.vertexTL; + vertexArray[count++] = item.vertexTR; + vertexArray[count++] = item.vertexBL; + vertexArray[count++] = item.vertexBR; - item.texture = null; - freeItems.Enqueue(item); - } - FlushVertexArray(device, count); - itemCount -= itemsToProcess; - } - items.Clear(); - } + item.texture = null; + freeItems.Enqueue(item); + } + FlushVertexArray(device, count); + itemCount -= itemsToProcess; + } + items.Clear(); + } - /// Sends the triangle list to the graphics device. - /// Start index of vertices to draw. Not used except to compute the count of vertices to draw. - /// End index of vertices to draw. Not used except to compute the count of vertices to draw. - private void FlushVertexArray (GraphicsDevice device, int count) { - if (count == 0) return; - device.DrawUserIndexedPrimitives( - PrimitiveType.TriangleList, - vertexArray, 0, count, - indices, 0, (count / 4) * 2, - VertexPositionColorTexture.VertexDeclaration); - } - } + /// Sends the triangle list to the graphics device. + /// Start index of vertices to draw. Not used except to compute the count of vertices to draw. + /// End index of vertices to draw. Not used except to compute the count of vertices to draw. + private void FlushVertexArray(GraphicsDevice device, int count) + { + if (count == 0) return; + device.DrawUserIndexedPrimitives( + PrimitiveType.TriangleList, + vertexArray, 0, count, + indices, 0, (count / 4) * 2, + VertexPositionColorTexture.VertexDeclaration); + } + } - public class RegionItem { - public Texture2D texture; - public VertexPositionColorTexture vertexTL; - public VertexPositionColorTexture vertexTR; - public VertexPositionColorTexture vertexBL; - public VertexPositionColorTexture vertexBR; - } + public class RegionItem + { + public Texture2D texture; + public VertexPositionColorTexture vertexTL; + public VertexPositionColorTexture vertexTR; + public VertexPositionColorTexture vertexBL; + public VertexPositionColorTexture vertexBR; + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/XnaLoader/SkeletonMeshRenderer.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/XnaLoader/SkeletonMeshRenderer.cs index 145c867..9e9cc73 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/XnaLoader/SkeletonMeshRenderer.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/XnaLoader/SkeletonMeshRenderer.cs @@ -29,205 +29,228 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; -using System.Collections.Generic; -using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Spine3_1_07 +{ + /// Draws region and mesh attachments. + public class SkeletonMeshRenderer + { + private const int TL = 0; + private const int TR = 1; + private const int BL = 2; + private const int BR = 3; + + GraphicsDevice device; + MeshBatcher batcher; + RasterizerState rasterizerState; + float[] vertices = new float[8]; + int[] quadTriangles = { 0, 1, 2, 1, 3, 2 }; + BlendState defaultBlendState; + + BasicEffect effect; + public BasicEffect Effect { get { return effect; } set { effect = value; } } + + private bool premultipliedAlpha; + public bool PremultipliedAlpha { get { return premultipliedAlpha; } set { premultipliedAlpha = value; } } + + public SkeletonMeshRenderer(GraphicsDevice device) + { + this.device = device; + + batcher = new MeshBatcher(); + + effect = new BasicEffect(device); + effect.World = Matrix.Identity; + effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); + effect.TextureEnabled = true; + effect.VertexColorEnabled = true; + + rasterizerState = new RasterizerState(); + rasterizerState.CullMode = CullMode.None; + + Bone.yDown = true; + } + + public void Begin() + { + defaultBlendState = premultipliedAlpha ? BlendState.AlphaBlend : BlendState.NonPremultiplied; + + device.RasterizerState = rasterizerState; + device.BlendState = defaultBlendState; + + effect.Projection = Matrix.CreateOrthographicOffCenter(0, device.Viewport.Width, device.Viewport.Height, 0, 1, 0); + } + + public void End() + { + foreach (EffectPass pass in effect.CurrentTechnique.Passes) + { + pass.Apply(); + batcher.Draw(device); + } + } + + public void Draw(Skeleton skeleton) + { + float[] vertices = this.vertices; + var drawOrder = skeleton.DrawOrder; + var drawOrderItems = skeleton.DrawOrder.Items; + float skeletonR = skeleton.R, skeletonG = skeleton.G, skeletonB = skeleton.B, skeletonA = skeleton.A; + for (int i = 0, n = drawOrder.Count; i < n; i++) + { + Slot slot = drawOrderItems[i]; + Attachment attachment = slot.Attachment; + if (attachment is RegionAttachment) + { + RegionAttachment regionAttachment = (RegionAttachment)attachment; + BlendState blend = slot.Data.BlendMode == BlendMode.additive ? BlendState.Additive : defaultBlendState; + if (device.BlendState != blend) + { + End(); + device.BlendState = blend; + } + + MeshItem item = batcher.NextItem(4, 6); + item.triangles = quadTriangles; + VertexPositionColorTexture[] itemVertices = item.vertices; + + AtlasRegion region = (AtlasRegion)regionAttachment.RendererObject; + item.texture = (Texture2D)region.page.rendererObject; + + Color color; + float a = skeletonA * slot.A * regionAttachment.A; + if (premultipliedAlpha) + { + color = new Color( + skeletonR * slot.R * regionAttachment.R * a, + skeletonG * slot.G * regionAttachment.G * a, + skeletonB * slot.B * regionAttachment.B * a, a); + } + else + { + color = new Color( + skeletonR * slot.R * regionAttachment.R, + skeletonG * slot.G * regionAttachment.G, + skeletonB * slot.B * regionAttachment.B, a); + } + itemVertices[TL].Color = color; + itemVertices[BL].Color = color; + itemVertices[BR].Color = color; + itemVertices[TR].Color = color; + + regionAttachment.ComputeWorldVertices(slot.Bone, vertices); + itemVertices[TL].Position.X = vertices[RegionAttachment.X1]; + itemVertices[TL].Position.Y = vertices[RegionAttachment.Y1]; + itemVertices[TL].Position.Z = 0; + itemVertices[BL].Position.X = vertices[RegionAttachment.X2]; + itemVertices[BL].Position.Y = vertices[RegionAttachment.Y2]; + itemVertices[BL].Position.Z = 0; + itemVertices[BR].Position.X = vertices[RegionAttachment.X3]; + itemVertices[BR].Position.Y = vertices[RegionAttachment.Y3]; + itemVertices[BR].Position.Z = 0; + itemVertices[TR].Position.X = vertices[RegionAttachment.X4]; + itemVertices[TR].Position.Y = vertices[RegionAttachment.Y4]; + itemVertices[TR].Position.Z = 0; + + float[] uvs = regionAttachment.UVs; + itemVertices[TL].TextureCoordinate.X = uvs[RegionAttachment.X1]; + itemVertices[TL].TextureCoordinate.Y = uvs[RegionAttachment.Y1]; + itemVertices[BL].TextureCoordinate.X = uvs[RegionAttachment.X2]; + itemVertices[BL].TextureCoordinate.Y = uvs[RegionAttachment.Y2]; + itemVertices[BR].TextureCoordinate.X = uvs[RegionAttachment.X3]; + itemVertices[BR].TextureCoordinate.Y = uvs[RegionAttachment.Y3]; + itemVertices[TR].TextureCoordinate.X = uvs[RegionAttachment.X4]; + itemVertices[TR].TextureCoordinate.Y = uvs[RegionAttachment.Y4]; + } + else if (attachment is MeshAttachment) + { + MeshAttachment mesh = (MeshAttachment)attachment; + int vertexCount = mesh.Vertices.Length; + if (vertices.Length < vertexCount) vertices = new float[vertexCount]; + mesh.ComputeWorldVertices(slot, vertices); + + int[] triangles = mesh.Triangles; + MeshItem item = batcher.NextItem(vertexCount, triangles.Length); + item.triangles = triangles; + + AtlasRegion region = (AtlasRegion)mesh.RendererObject; + item.texture = (Texture2D)region.page.rendererObject; + + Color color; + float a = skeletonA * slot.A * mesh.A; + if (premultipliedAlpha) + { + color = new Color( + skeletonR * slot.R * mesh.R * a, + skeletonG * slot.G * mesh.G * a, + skeletonB * slot.B * mesh.B * a, a); + } + else + { + color = new Color( + skeletonR * slot.R * mesh.R, + skeletonG * slot.G * mesh.G, + skeletonB * slot.B * mesh.B, a); + } + + float[] uvs = mesh.UVs; + VertexPositionColorTexture[] itemVertices = item.vertices; + for (int ii = 0, v = 0; v < vertexCount; ii++, v += 2) + { + itemVertices[ii].Color = color; + itemVertices[ii].Position.X = vertices[v]; + itemVertices[ii].Position.Y = vertices[v + 1]; + itemVertices[ii].Position.Z = 0; + itemVertices[ii].TextureCoordinate.X = uvs[v]; + itemVertices[ii].TextureCoordinate.Y = uvs[v + 1]; + } + } + else if (attachment is WeightedMeshAttachment) + { + WeightedMeshAttachment mesh = (WeightedMeshAttachment)attachment; + int vertexCount = mesh.UVs.Length; + if (vertices.Length < vertexCount) vertices = new float[vertexCount]; + mesh.ComputeWorldVertices(slot, vertices); + + int[] triangles = mesh.Triangles; + MeshItem item = batcher.NextItem(vertexCount, triangles.Length); + item.triangles = triangles; + + AtlasRegion region = (AtlasRegion)mesh.RendererObject; + item.texture = (Texture2D)region.page.rendererObject; + + Color color; + float a = skeletonA * slot.A * mesh.A; + if (premultipliedAlpha) + { + color = new Color( + skeletonR * slot.R * mesh.R * a, + skeletonG * slot.G * mesh.G * a, + skeletonB * slot.B * mesh.B * a, a); + } + else + { + color = new Color( + skeletonR * slot.R * mesh.R, + skeletonG * slot.G * mesh.G, + skeletonB * slot.B * mesh.B, a); + } -namespace Spine3_1_07 { - /// Draws region and mesh attachments. - public class SkeletonMeshRenderer { - private const int TL = 0; - private const int TR = 1; - private const int BL = 2; - private const int BR = 3; - - GraphicsDevice device; - MeshBatcher batcher; - RasterizerState rasterizerState; - float[] vertices = new float[8]; - int[] quadTriangles = { 0, 1, 2, 1, 3, 2 }; - BlendState defaultBlendState; - - BasicEffect effect; - public BasicEffect Effect { get { return effect; } set { effect = value; } } - - private bool premultipliedAlpha; - public bool PremultipliedAlpha { get { return premultipliedAlpha; } set { premultipliedAlpha = value; } } - - public SkeletonMeshRenderer (GraphicsDevice device) { - this.device = device; - - batcher = new MeshBatcher(); - - effect = new BasicEffect(device); - effect.World = Matrix.Identity; - effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); - effect.TextureEnabled = true; - effect.VertexColorEnabled = true; - - rasterizerState = new RasterizerState(); - rasterizerState.CullMode = CullMode.None; - - Bone.yDown = true; - } - - public void Begin () { - defaultBlendState = premultipliedAlpha ? BlendState.AlphaBlend : BlendState.NonPremultiplied; - - device.RasterizerState = rasterizerState; - device.BlendState = defaultBlendState; - - effect.Projection = Matrix.CreateOrthographicOffCenter(0, device.Viewport.Width, device.Viewport.Height, 0, 1, 0); - } - - public void End () { - foreach (EffectPass pass in effect.CurrentTechnique.Passes) { - pass.Apply(); - batcher.Draw(device); - } - } - - public void Draw (Skeleton skeleton) { - float[] vertices = this.vertices; - var drawOrder = skeleton.DrawOrder; - var drawOrderItems = skeleton.DrawOrder.Items; - float skeletonR = skeleton.R, skeletonG = skeleton.G, skeletonB = skeleton.B, skeletonA = skeleton.A; - for (int i = 0, n = drawOrder.Count; i < n; i++) { - Slot slot = drawOrderItems[i]; - Attachment attachment = slot.Attachment; - if (attachment is RegionAttachment) { - RegionAttachment regionAttachment = (RegionAttachment)attachment; - BlendState blend = slot.Data.BlendMode == BlendMode.additive ? BlendState.Additive : defaultBlendState; - if (device.BlendState != blend) { - End(); - device.BlendState = blend; - } - - MeshItem item = batcher.NextItem(4, 6); - item.triangles = quadTriangles; - VertexPositionColorTexture[] itemVertices = item.vertices; - - AtlasRegion region = (AtlasRegion)regionAttachment.RendererObject; - item.texture = (Texture2D)region.page.rendererObject; - - Color color; - float a = skeletonA * slot.A * regionAttachment.A; - if (premultipliedAlpha) { - color = new Color( - skeletonR * slot.R * regionAttachment.R * a, - skeletonG * slot.G * regionAttachment.G * a, - skeletonB * slot.B * regionAttachment.B * a, a); - } else { - color = new Color( - skeletonR * slot.R * regionAttachment.R, - skeletonG * slot.G * regionAttachment.G, - skeletonB * slot.B * regionAttachment.B, a); - } - itemVertices[TL].Color = color; - itemVertices[BL].Color = color; - itemVertices[BR].Color = color; - itemVertices[TR].Color = color; - - regionAttachment.ComputeWorldVertices(slot.Bone, vertices); - itemVertices[TL].Position.X = vertices[RegionAttachment.X1]; - itemVertices[TL].Position.Y = vertices[RegionAttachment.Y1]; - itemVertices[TL].Position.Z = 0; - itemVertices[BL].Position.X = vertices[RegionAttachment.X2]; - itemVertices[BL].Position.Y = vertices[RegionAttachment.Y2]; - itemVertices[BL].Position.Z = 0; - itemVertices[BR].Position.X = vertices[RegionAttachment.X3]; - itemVertices[BR].Position.Y = vertices[RegionAttachment.Y3]; - itemVertices[BR].Position.Z = 0; - itemVertices[TR].Position.X = vertices[RegionAttachment.X4]; - itemVertices[TR].Position.Y = vertices[RegionAttachment.Y4]; - itemVertices[TR].Position.Z = 0; - - float[] uvs = regionAttachment.UVs; - itemVertices[TL].TextureCoordinate.X = uvs[RegionAttachment.X1]; - itemVertices[TL].TextureCoordinate.Y = uvs[RegionAttachment.Y1]; - itemVertices[BL].TextureCoordinate.X = uvs[RegionAttachment.X2]; - itemVertices[BL].TextureCoordinate.Y = uvs[RegionAttachment.Y2]; - itemVertices[BR].TextureCoordinate.X = uvs[RegionAttachment.X3]; - itemVertices[BR].TextureCoordinate.Y = uvs[RegionAttachment.Y3]; - itemVertices[TR].TextureCoordinate.X = uvs[RegionAttachment.X4]; - itemVertices[TR].TextureCoordinate.Y = uvs[RegionAttachment.Y4]; - } else if (attachment is MeshAttachment) { - MeshAttachment mesh = (MeshAttachment)attachment; - int vertexCount = mesh.Vertices.Length; - if (vertices.Length < vertexCount) vertices = new float[vertexCount]; - mesh.ComputeWorldVertices(slot, vertices); - - int[] triangles = mesh.Triangles; - MeshItem item = batcher.NextItem(vertexCount, triangles.Length); - item.triangles = triangles; - - AtlasRegion region = (AtlasRegion)mesh.RendererObject; - item.texture = (Texture2D)region.page.rendererObject; - - Color color; - float a = skeletonA * slot.A * mesh.A; - if (premultipliedAlpha) { - color = new Color( - skeletonR * slot.R * mesh.R * a, - skeletonG * slot.G * mesh.G * a, - skeletonB * slot.B * mesh.B * a, a); - } else { - color = new Color( - skeletonR * slot.R * mesh.R, - skeletonG * slot.G * mesh.G, - skeletonB * slot.B * mesh.B, a); - } - - float[] uvs = mesh.UVs; - VertexPositionColorTexture[] itemVertices = item.vertices; - for (int ii = 0, v = 0; v < vertexCount; ii++, v += 2) { - itemVertices[ii].Color = color; - itemVertices[ii].Position.X = vertices[v]; - itemVertices[ii].Position.Y = vertices[v + 1]; - itemVertices[ii].Position.Z = 0; - itemVertices[ii].TextureCoordinate.X = uvs[v]; - itemVertices[ii].TextureCoordinate.Y = uvs[v + 1]; - } - } else if (attachment is WeightedMeshAttachment) { - WeightedMeshAttachment mesh = (WeightedMeshAttachment)attachment; - int vertexCount = mesh.UVs.Length; - if (vertices.Length < vertexCount) vertices = new float[vertexCount]; - mesh.ComputeWorldVertices(slot, vertices); - - int[] triangles = mesh.Triangles; - MeshItem item = batcher.NextItem(vertexCount, triangles.Length); - item.triangles = triangles; - - AtlasRegion region = (AtlasRegion)mesh.RendererObject; - item.texture = (Texture2D)region.page.rendererObject; - - Color color; - float a = skeletonA * slot.A * mesh.A; - if (premultipliedAlpha) { - color = new Color( - skeletonR * slot.R * mesh.R * a, - skeletonG * slot.G * mesh.G * a, - skeletonB * slot.B * mesh.B * a, a); - } else { - color = new Color( - skeletonR * slot.R * mesh.R, - skeletonG * slot.G * mesh.G, - skeletonB * slot.B * mesh.B, a); - } - - float[] uvs = mesh.UVs; - VertexPositionColorTexture[] itemVertices = item.vertices; - for (int ii = 0, v = 0; v < vertexCount; ii++, v += 2) { - itemVertices[ii].Color = color; - itemVertices[ii].Position.X = vertices[v]; - itemVertices[ii].Position.Y = vertices[v + 1]; - itemVertices[ii].Position.Z = 0; - itemVertices[ii].TextureCoordinate.X = uvs[v]; - itemVertices[ii].TextureCoordinate.Y = uvs[v + 1]; - } - } - } - } - } + float[] uvs = mesh.UVs; + VertexPositionColorTexture[] itemVertices = item.vertices; + for (int ii = 0, v = 0; v < vertexCount; ii++, v += 2) + { + itemVertices[ii].Color = color; + itemVertices[ii].Position.X = vertices[v]; + itemVertices[ii].Position.Y = vertices[v + 1]; + itemVertices[ii].Position.Z = 0; + itemVertices[ii].TextureCoordinate.X = uvs[v]; + itemVertices[ii].TextureCoordinate.Y = uvs[v + 1]; + } + } + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/XnaLoader/SkeletonRegionRenderer.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/XnaLoader/SkeletonRegionRenderer.cs index 3b11f3e..0fb2f41 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/XnaLoader/SkeletonRegionRenderer.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/XnaLoader/SkeletonRegionRenderer.cs @@ -29,67 +29,74 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; -using System.Collections.Generic; -using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; -namespace Spine3_1_07 { - /// Draws region attachments. - public class SkeletonRegionRenderer { - GraphicsDevice device; - RegionBatcher batcher; - RasterizerState rasterizerState; - float[] vertices = new float[8]; - BlendState defaultBlendState; - - BasicEffect effect; - public BasicEffect Effect { get { return effect; } set { effect = value; } } - - private bool premultipliedAlpha; - public bool PremultipliedAlpha { get { return premultipliedAlpha; } set { premultipliedAlpha = value; } } - - public SkeletonRegionRenderer (GraphicsDevice device) { - this.device = device; - - batcher = new RegionBatcher(); - - effect = new BasicEffect(device); - effect.World = Matrix.Identity; - effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); - effect.TextureEnabled = true; - effect.VertexColorEnabled = true; - - rasterizerState = new RasterizerState(); - rasterizerState.CullMode = CullMode.None; - - Bone.yDown = true; - } - - public void Begin () { - defaultBlendState = premultipliedAlpha ? BlendState.AlphaBlend : BlendState.NonPremultiplied; - - device.RasterizerState = rasterizerState; - device.BlendState = defaultBlendState; - - effect.Projection = Matrix.CreateOrthographicOffCenter(0, device.Viewport.Width, device.Viewport.Height, 0, 1, 0); - } - - public void End () { - foreach (EffectPass pass in effect.CurrentTechnique.Passes) { - pass.Apply(); - batcher.Draw(device); - } - } - - public void Draw (Skeleton skeleton) { - var drawOrder = skeleton.DrawOrder; - var drawOrderItems = skeleton.DrawOrder.Items; - float skeletonR = skeleton.R, skeletonG = skeleton.G, skeletonB = skeleton.B, skeletonA = skeleton.A; - for (int i = 0, n = drawOrder.Count; i < n; i++) { - Slot slot = drawOrderItems[i]; - RegionAttachment regionAttachment = slot.Attachment as RegionAttachment; - if (regionAttachment != null) { +namespace Spine3_1_07 +{ + /// Draws region attachments. + public class SkeletonRegionRenderer + { + GraphicsDevice device; + RegionBatcher batcher; + RasterizerState rasterizerState; + float[] vertices = new float[8]; + BlendState defaultBlendState; + + BasicEffect effect; + public BasicEffect Effect { get { return effect; } set { effect = value; } } + + private bool premultipliedAlpha; + public bool PremultipliedAlpha { get { return premultipliedAlpha; } set { premultipliedAlpha = value; } } + + public SkeletonRegionRenderer(GraphicsDevice device) + { + this.device = device; + + batcher = new RegionBatcher(); + + effect = new BasicEffect(device); + effect.World = Matrix.Identity; + effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); + effect.TextureEnabled = true; + effect.VertexColorEnabled = true; + + rasterizerState = new RasterizerState(); + rasterizerState.CullMode = CullMode.None; + + Bone.yDown = true; + } + + public void Begin() + { + defaultBlendState = premultipliedAlpha ? BlendState.AlphaBlend : BlendState.NonPremultiplied; + + device.RasterizerState = rasterizerState; + device.BlendState = defaultBlendState; + + effect.Projection = Matrix.CreateOrthographicOffCenter(0, device.Viewport.Width, device.Viewport.Height, 0, 1, 0); + } + + public void End() + { + foreach (EffectPass pass in effect.CurrentTechnique.Passes) + { + pass.Apply(); + batcher.Draw(device); + } + } + + public void Draw(Skeleton skeleton) + { + var drawOrder = skeleton.DrawOrder; + var drawOrderItems = skeleton.DrawOrder.Items; + float skeletonR = skeleton.R, skeletonG = skeleton.G, skeletonB = skeleton.B, skeletonA = skeleton.A; + for (int i = 0, n = drawOrder.Count; i < n; i++) + { + Slot slot = drawOrderItems[i]; + RegionAttachment regionAttachment = slot.Attachment as RegionAttachment; + if (regionAttachment != null) + { BlendState blendState = new BlendState(); Blend blendSrc; Blend blendDst; @@ -148,46 +155,46 @@ public void Draw (Skeleton skeleton) { RegionItem item = batcher.NextItem(); - AtlasRegion region = (AtlasRegion)regionAttachment.RendererObject; - item.texture = (Texture2D)region.page.rendererObject; - - Color color; - float a = skeletonA * slot.A; - if (premultipliedAlpha) - color = new Color(skeletonR * slot.R * a, skeletonG * slot.G * a, skeletonB * slot.B * a, a); - else - color = new Color(skeletonR * slot.R, skeletonG * slot.G, skeletonB * slot.B, a); - item.vertexTL.Color = color; - item.vertexBL.Color = color; - item.vertexBR.Color = color; - item.vertexTR.Color = color; - - float[] vertices = this.vertices; - regionAttachment.ComputeWorldVertices(slot.Bone, vertices); - item.vertexTL.Position.X = vertices[RegionAttachment.X1]; - item.vertexTL.Position.Y = vertices[RegionAttachment.Y1]; - item.vertexTL.Position.Z = 0; - item.vertexBL.Position.X = vertices[RegionAttachment.X2]; - item.vertexBL.Position.Y = vertices[RegionAttachment.Y2]; - item.vertexBL.Position.Z = 0; - item.vertexBR.Position.X = vertices[RegionAttachment.X3]; - item.vertexBR.Position.Y = vertices[RegionAttachment.Y3]; - item.vertexBR.Position.Z = 0; - item.vertexTR.Position.X = vertices[RegionAttachment.X4]; - item.vertexTR.Position.Y = vertices[RegionAttachment.Y4]; - item.vertexTR.Position.Z = 0; - - float[] uvs = regionAttachment.UVs; - item.vertexTL.TextureCoordinate.X = uvs[RegionAttachment.X1]; - item.vertexTL.TextureCoordinate.Y = uvs[RegionAttachment.Y1]; - item.vertexBL.TextureCoordinate.X = uvs[RegionAttachment.X2]; - item.vertexBL.TextureCoordinate.Y = uvs[RegionAttachment.Y2]; - item.vertexBR.TextureCoordinate.X = uvs[RegionAttachment.X3]; - item.vertexBR.TextureCoordinate.Y = uvs[RegionAttachment.Y3]; - item.vertexTR.TextureCoordinate.X = uvs[RegionAttachment.X4]; - item.vertexTR.TextureCoordinate.Y = uvs[RegionAttachment.Y4]; - } - } - } - } + AtlasRegion region = (AtlasRegion)regionAttachment.RendererObject; + item.texture = (Texture2D)region.page.rendererObject; + + Color color; + float a = skeletonA * slot.A; + if (premultipliedAlpha) + color = new Color(skeletonR * slot.R * a, skeletonG * slot.G * a, skeletonB * slot.B * a, a); + else + color = new Color(skeletonR * slot.R, skeletonG * slot.G, skeletonB * slot.B, a); + item.vertexTL.Color = color; + item.vertexBL.Color = color; + item.vertexBR.Color = color; + item.vertexTR.Color = color; + + float[] vertices = this.vertices; + regionAttachment.ComputeWorldVertices(slot.Bone, vertices); + item.vertexTL.Position.X = vertices[RegionAttachment.X1]; + item.vertexTL.Position.Y = vertices[RegionAttachment.Y1]; + item.vertexTL.Position.Z = 0; + item.vertexBL.Position.X = vertices[RegionAttachment.X2]; + item.vertexBL.Position.Y = vertices[RegionAttachment.Y2]; + item.vertexBL.Position.Z = 0; + item.vertexBR.Position.X = vertices[RegionAttachment.X3]; + item.vertexBR.Position.Y = vertices[RegionAttachment.Y3]; + item.vertexBR.Position.Z = 0; + item.vertexTR.Position.X = vertices[RegionAttachment.X4]; + item.vertexTR.Position.Y = vertices[RegionAttachment.Y4]; + item.vertexTR.Position.Z = 0; + + float[] uvs = regionAttachment.UVs; + item.vertexTL.TextureCoordinate.X = uvs[RegionAttachment.X1]; + item.vertexTL.TextureCoordinate.Y = uvs[RegionAttachment.Y1]; + item.vertexBL.TextureCoordinate.X = uvs[RegionAttachment.X2]; + item.vertexBL.TextureCoordinate.Y = uvs[RegionAttachment.Y2]; + item.vertexBR.TextureCoordinate.X = uvs[RegionAttachment.X3]; + item.vertexBR.TextureCoordinate.Y = uvs[RegionAttachment.Y3]; + item.vertexTR.TextureCoordinate.X = uvs[RegionAttachment.X4]; + item.vertexTR.TextureCoordinate.Y = uvs[RegionAttachment.Y4]; + } + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/XnaLoader/XnaTextureLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/XnaLoader/XnaTextureLoader.cs index 3186ddd..87c6386 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/XnaLoader/XnaTextureLoader.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.1.07/XnaLoader/XnaTextureLoader.cs @@ -30,21 +30,23 @@ *****************************************************************************/ using System; -using System.IO; -using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; -namespace Spine3_1_07 { - public class XnaTextureLoader : TextureLoader { - GraphicsDevice device; +namespace Spine3_1_07 +{ + public class XnaTextureLoader : TextureLoader + { + GraphicsDevice device; - public XnaTextureLoader (GraphicsDevice device) { - this.device = device; - } + public XnaTextureLoader(GraphicsDevice device) + { + this.device = device; + } - public void Load (AtlasPage page, String path) { - Texture2D texture = Util.LoadTexture(device, path); - page.rendererObject = texture; + public void Load(AtlasPage page, String path) + { + Texture2D texture = Util.LoadTexture(device, path); + page.rendererObject = texture; if (page.width == 0 || page.height == 0) { page.width = texture.Width; @@ -52,8 +54,9 @@ public void Load (AtlasPage page, String path) { } } - public void Unload (Object texture) { - ((Texture2D)texture).Dispose(); - } - } + public void Unload(Object texture) + { + ((Texture2D)texture).Dispose(); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Animation.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Animation.cs index 16d9d52..52af69f 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Animation.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Animation.cs @@ -30,741 +30,839 @@ *****************************************************************************/ using System; -using System.Collections.Generic; - -namespace Spine3_2_xx { - public class Animation { - internal ExposedList timelines; - internal float duration; - internal String name; - - public String Name { get { return name; } } - public ExposedList Timelines { get { return timelines; } set { timelines = value; } } - public float Duration { get { return duration; } set { duration = value; } } - - public Animation (String name, ExposedList timelines, float duration) { - if (name == null) throw new ArgumentNullException("name cannot be null."); - if (timelines == null) throw new ArgumentNullException("timelines cannot be null."); - this.name = name; - this.timelines = timelines; - this.duration = duration; - } - - /// Poses the skeleton at the specified time for this animation. - /// The last time the animation was applied. - /// Any triggered events are added. - public void Apply (Skeleton skeleton, float lastTime, float time, bool loop, ExposedList events) { - if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); - - if (loop && duration != 0) { - time %= duration; - if (lastTime > 0) lastTime %= duration; - } - - ExposedList timelines = this.timelines; - for (int i = 0, n = timelines.Count; i < n; i++) - timelines.Items[i].Apply(skeleton, lastTime, time, events, 1); - } - - /// Poses the skeleton at the specified time for this animation mixed with the current pose. - /// The last time the animation was applied. - /// Any triggered events are added. - /// The amount of this animation that affects the current pose. - public void Mix (Skeleton skeleton, float lastTime, float time, bool loop, ExposedList events, float alpha) { - if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); - - if (loop && duration != 0) { - time %= duration; - if (lastTime > 0) lastTime %= duration; - } - - ExposedList timelines = this.timelines; - for (int i = 0, n = timelines.Count; i < n; i++) - timelines.Items[i].Apply(skeleton, lastTime, time, events, alpha); - } - - /// After the first and before the last entry. - internal static int binarySearch (float[] values, float target, int step) { - int low = 0; - int high = values.Length / step - 2; - if (high == 0) return step; - int current = (int)((uint)high >> 1); - while (true) { - if (values[(current + 1) * step] <= target) - low = current + 1; - else - high = current; - if (low == high) return (low + 1) * step; - current = (int)((uint)(low + high) >> 1); - } - } - - /// After the first and before the last entry. - internal static int binarySearch (float[] values, float target) { - int low = 0; - int high = values.Length - 2; - if (high == 0) return 1; - int current = (int)((uint)high >> 1); - while (true) { - if (values[(current + 1)] <= target) - low = current + 1; - else - high = current; - if (low == high) return (low + 1); - current = (int)((uint)(low + high) >> 1); - } - } - - internal static int linearSearch (float[] values, float target, int step) { - for (int i = 0, last = values.Length - step; i <= last; i += step) - if (values[i] > target) return i; - return -1; - } - } - - public interface Timeline { - /// Sets the value(s) for the specified time. - /// May be null to not collect fired events. - void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha); - } - - /// Base class for frames that use an interpolation bezier curve. - abstract public class CurveTimeline : Timeline { - protected const float LINEAR = 0, STEPPED = 1, BEZIER = 2; - protected const int BEZIER_SEGMENTS = 10, BEZIER_SIZE = BEZIER_SEGMENTS * 2 - 1; - - private float[] curves; // type, x, y, ... - public int FrameCount { get { return curves.Length / BEZIER_SIZE + 1; } } - - public CurveTimeline (int frameCount) { - curves = new float[(frameCount - 1) * BEZIER_SIZE]; - } - - abstract public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha); - - public void SetLinear (int frameIndex) { - curves[frameIndex * BEZIER_SIZE] = LINEAR; - } - - public void SetStepped (int frameIndex) { - curves[frameIndex * BEZIER_SIZE] = STEPPED; - } - - /// Sets the control handle positions for an interpolation bezier curve used to transition from this keyframe to the next. - /// cx1 and cx2 are from 0 to 1, representing the percent of time between the two keyframes. cy1 and cy2 are the percent of - /// the difference between the keyframe's values. - public void SetCurve (int frameIndex, float cx1, float cy1, float cx2, float cy2) { - float subdiv1 = 1f / BEZIER_SEGMENTS, subdiv2 = subdiv1 * subdiv1, subdiv3 = subdiv2 * subdiv1; - float pre1 = 3 * subdiv1, pre2 = 3 * subdiv2, pre4 = 6 * subdiv2, pre5 = 6 * subdiv3; - float tmp1x = -cx1 * 2 + cx2, tmp1y = -cy1 * 2 + cy2, tmp2x = (cx1 - cx2) * 3 + 1, tmp2y = (cy1 - cy2) * 3 + 1; - float dfx = cx1 * pre1 + tmp1x * pre2 + tmp2x * subdiv3, dfy = cy1 * pre1 + tmp1y * pre2 + tmp2y * subdiv3; - float ddfx = tmp1x * pre4 + tmp2x * pre5, ddfy = tmp1y * pre4 + tmp2y * pre5; - float dddfx = tmp2x * pre5, dddfy = tmp2y * pre5; - - int i = frameIndex * BEZIER_SIZE; - float[] curves = this.curves; - curves[i++] = BEZIER; - - float x = dfx, y = dfy; - for (int n = i + BEZIER_SIZE - 1; i < n; i += 2) { - curves[i] = x; - curves[i + 1] = y; - dfx += ddfx; - dfy += ddfy; - ddfx += dddfx; - ddfy += dddfy; - x += dfx; - y += dfy; - } - } - - public float GetCurvePercent (int frameIndex, float percent) { - float[] curves = this.curves; - int i = frameIndex * BEZIER_SIZE; - float type = curves[i]; - if (type == LINEAR) return percent; - if (type == STEPPED) return 0; - i++; - float x = 0; - for (int start = i, n = i + BEZIER_SIZE - 1; i < n; i += 2) { - x = curves[i]; - if (x >= percent) { - float prevX, prevY; - if (i == start) { - prevX = 0; - prevY = 0; - } else { - prevX = curves[i - 2]; - prevY = curves[i - 1]; - } - return prevY + (curves[i + 1] - prevY) * (percent - prevX) / (x - prevX); - } - } - float y = curves[i - 1]; - return y + (1 - y) * (percent - x) / (1 - x); // Last point is 1,1. - } - public float GetCurveType (int frameIndex) { - return curves[frameIndex * BEZIER_SIZE]; - } - } - - public class RotateTimeline : CurveTimeline { - internal const int PREV_TIME = -2; - internal const int VALUE = 1; - - internal int boneIndex; - internal float[] frames; - - public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, angle, ... - - public RotateTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount << 1]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, float angle) { - frameIndex *= 2; - frames[frameIndex] = time; - frames[frameIndex + 1] = angle; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) { - float[] frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - Bone bone = skeleton.bones.Items[boneIndex]; - - float amount; - - if (time >= frames[frames.Length - 2]) { // Time is after last frame. - amount = bone.data.rotation + frames[frames.Length - 1] - bone.rotation; - while (amount > 180) - amount -= 360; - while (amount < -180) - amount += 360; - bone.rotation += amount * alpha; - return; - } - - // Interpolate between the previous frame and the current frame. - int frame = Animation.binarySearch(frames, time, 2); - float prevFrameValue = frames[frame - 1]; - float frameTime = frames[frame]; - float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime); - percent = GetCurvePercent((frame >> 1) - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); - - amount = frames[frame + VALUE] - prevFrameValue; - while (amount > 180) - amount -= 360; - while (amount < -180) - amount += 360; - amount = bone.data.rotation + (prevFrameValue + amount * percent) - bone.rotation; - while (amount > 180) - amount -= 360; - while (amount < -180) - amount += 360; - bone.rotation += amount * alpha; - } - } - - public class TranslateTimeline : CurveTimeline { - protected const int PREV_TIME = -3; - protected const int X = 1; - protected const int Y = 2; - - internal int boneIndex; - internal float[] frames; - - public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, value, value, ... - - public TranslateTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * 3]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, float x, float y) { - frameIndex *= 3; - frames[frameIndex] = time; - frames[frameIndex + 1] = x; - frames[frameIndex + 2] = y; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) { - float[] frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - Bone bone = skeleton.bones.Items[boneIndex]; - - if (time >= frames[frames.Length - 3]) { // Time is after last frame. - bone.x += (bone.data.x + frames[frames.Length - 2] - bone.x) * alpha; - bone.y += (bone.data.y + frames[frames.Length - 1] - bone.y) * alpha; - return; - } - - // Interpolate between the previous frame and the current frame. - int frame = Animation.binarySearch(frames, time, 3); - float prevFrameX = frames[frame - 2]; - float prevFrameY = frames[frame - 1]; - float frameTime = frames[frame]; - float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime); - percent = GetCurvePercent(frame / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); - - bone.x += (bone.data.x + prevFrameX + (frames[frame + X] - prevFrameX) * percent - bone.x) * alpha; - bone.y += (bone.data.y + prevFrameY + (frames[frame + Y] - prevFrameY) * percent - bone.y) * alpha; - } - } - - public class ScaleTimeline : TranslateTimeline { - public ScaleTimeline (int frameCount) - : base(frameCount) { - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) { - float[] frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - Bone bone = skeleton.bones.Items[boneIndex]; - if (time >= frames[frames.Length - 3]) { // Time is after last frame. - bone.scaleX += (bone.data.scaleX * frames[frames.Length - 2] - bone.scaleX) * alpha; - bone.scaleY += (bone.data.scaleY * frames[frames.Length - 1] - bone.scaleY) * alpha; - return; - } - - // Interpolate between the previous frame and the current frame. - int frame = Animation.binarySearch(frames, time, 3); - float prevFrameX = frames[frame - 2]; - float prevFrameY = frames[frame - 1]; - float frameTime = frames[frame]; - float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime); - percent = GetCurvePercent(frame / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); - - bone.scaleX += (bone.data.scaleX * (prevFrameX + (frames[frame + X] - prevFrameX) * percent) - bone.scaleX) * alpha; - bone.scaleY += (bone.data.scaleY * (prevFrameY + (frames[frame + Y] - prevFrameY) * percent) - bone.scaleY) * alpha; - } - } - - public class ShearTimeline : TranslateTimeline { - public ShearTimeline (int frameCount) - : base (frameCount) { - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) { - float[] frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - Bone bone = skeleton.bones.Items[boneIndex]; - if (time >= frames[frames.Length - 3]) { // Time is after last frame. - bone.shearX += (bone.data.shearX + frames[frames.Length - 2] - bone.shearX) * alpha; - bone.shearY += (bone.data.shearY + frames[frames.Length - 1] - bone.shearY) * alpha; - return; - } - - // Interpolate between the previous frame and the current frame. - int frame = Animation.binarySearch(frames, time, 3); - float prevFrameX = frames[frame - 2]; - float prevFrameY = frames[frame - 1]; - float frameTime = frames[frame]; - float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime); - percent = GetCurvePercent(frame / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); - - bone.shearX += (bone.data.shearX + (prevFrameX + (frames[frame + X] - prevFrameX) * percent) - bone.shearX) * alpha; - bone.shearY += (bone.data.shearY + (prevFrameY + (frames[frame + Y] - prevFrameY) * percent) - bone.shearY) * alpha; - } - } - - public class ColorTimeline : CurveTimeline { - protected const int PREV_TIME = -5; - protected const int R = 1; - protected const int G = 2; - protected const int B = 3; - protected const int A = 4; - - internal int slotIndex; - internal float[] frames; - - public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, r, g, b, a, ... - - public ColorTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * 5]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, float r, float g, float b, float a) { - frameIndex *= 5; - frames[frameIndex] = time; - frames[frameIndex + 1] = r; - frames[frameIndex + 2] = g; - frames[frameIndex + 3] = b; - frames[frameIndex + 4] = a; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) { - float[] frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - float r, g, b, a; - if (time >= frames[frames.Length - 5]) { // Time is after last frame. - int i = frames.Length - 1; - r = frames[i - 3]; - g = frames[i - 2]; - b = frames[i - 1]; - a = frames[i]; - } else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.binarySearch(frames, time, 5); - float frameTime = frames[frame]; - float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime); - percent = GetCurvePercent(frame / 5 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); - - r = frames[frame - 4]; - g = frames[frame - 3]; - b = frames[frame - 2]; - a = frames[frame - 1]; - r += (frames[frame + R] - r) * percent; - g += (frames[frame + G] - g) * percent; - b += (frames[frame + B] - b) * percent; - a += (frames[frame + A] - a) * percent; - } - Slot slot = skeleton.slots.Items[slotIndex]; - if (alpha < 1) { - slot.r += (r - slot.r) * alpha; - slot.g += (g - slot.g) * alpha; - slot.b += (b - slot.b) * alpha; - slot.a += (a - slot.a) * alpha; - } else { - slot.r = r; - slot.g = g; - slot.b = b; - slot.a = a; - } - } - } - - public class AttachmentTimeline : Timeline { - internal int slotIndex; - internal float[] frames; - private String[] attachmentNames; - - public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, ... - public String[] AttachmentNames { get { return attachmentNames; } set { attachmentNames = value; } } - public int FrameCount { get { return frames.Length; } } - - public AttachmentTimeline (int frameCount) { - frames = new float[frameCount]; - attachmentNames = new String[frameCount]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, String attachmentName) { - frames[frameIndex] = time; - attachmentNames[frameIndex] = attachmentName; - } - - public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) { - float[] frames = this.frames; - if (time < frames[0]) { - if (lastTime > time) Apply(skeleton, lastTime, int.MaxValue, null, 0); - return; - } else if (lastTime > time) // - lastTime = -1; - - int frameIndex = (time >= frames[frames.Length - 1] ? frames.Length : Animation.binarySearch(frames, time)) - 1; - if (frames[frameIndex] < lastTime) return; - - String attachmentName = attachmentNames[frameIndex]; - skeleton.slots.Items[slotIndex].Attachment = - attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); - } - } - - public class EventTimeline : Timeline { - internal float[] frames; - private Event[] events; - - public float[] Frames { get { return frames; } set { frames = value; } } // time, ... - public Event[] Events { get { return events; } set { events = value; } } - public int FrameCount { get { return frames.Length; } } - - public EventTimeline (int frameCount) { - frames = new float[frameCount]; - events = new Event[frameCount]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, Event e) { - frames[frameIndex] = e.Time; - events[frameIndex] = e; - } - - /// Fires events for frames > lastTime and <= time. - public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) { - if (firedEvents == null) return; - float[] frames = this.frames; - int frameCount = frames.Length; - - if (lastTime > time) { // Fire events after last time for looped animations. - Apply(skeleton, lastTime, int.MaxValue, firedEvents, alpha); - lastTime = -1f; - } else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame. - return; - if (time < frames[0]) return; // Time is before first frame. - - int frame; - if (lastTime < frames[0]) - frame = 0; - else { - frame = Animation.binarySearch(frames, lastTime); - float frameTime = frames[frame]; - while (frame > 0) { // Fire multiple events with the same frame. - if (frames[frame - 1] != frameTime) break; - frame--; - } - } - for (; frame < frameCount && time >= frames[frame]; frame++) - firedEvents.Add(events[frame]); - } - } - - public class DrawOrderTimeline : Timeline { - internal float[] frames; - private int[][] drawOrders; - - public float[] Frames { get { return frames; } set { frames = value; } } // time, ... - public int[][] DrawOrders { get { return drawOrders; } set { drawOrders = value; } } - public int FrameCount { get { return frames.Length; } } - - public DrawOrderTimeline (int frameCount) { - frames = new float[frameCount]; - drawOrders = new int[frameCount][]; - } - - /// Sets the time and value of the specified keyframe. - /// May be null to use bind pose draw order. - public void SetFrame (int frameIndex, float time, int[] drawOrder) { - frames[frameIndex] = time; - drawOrders[frameIndex] = drawOrder; - } - - public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) { - float[] frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - int frame; - if (time >= frames[frames.Length - 1]) // Time is after last frame. - frame = frames.Length - 1; - else - frame = Animation.binarySearch(frames, time) - 1; - - ExposedList drawOrder = skeleton.drawOrder; - ExposedList slots = skeleton.slots; - int[] drawOrderToSetupIndex = drawOrders[frame]; - if (drawOrderToSetupIndex == null) { - drawOrder.Clear(); - for (int i = 0, n = slots.Count; i < n; i++) - drawOrder.Add(slots.Items[i]); - } else { - for (int i = 0, n = drawOrderToSetupIndex.Length; i < n; i++) - drawOrder.Items[i] = slots.Items[drawOrderToSetupIndex[i]]; - } - } - } - - public class FfdTimeline : CurveTimeline { - internal int slotIndex; - internal float[] frames; - private float[][] frameVertices; - internal Attachment attachment; - - public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, ... - public float[][] Vertices { get { return frameVertices; } set { frameVertices = value; } } - public Attachment Attachment { get { return attachment; } set { attachment = value; } } - - public FfdTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount]; - frameVertices = new float[frameCount][]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, float[] vertices) { - frames[frameIndex] = time; - frameVertices[frameIndex] = vertices; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) { - Slot slot = skeleton.slots.Items[slotIndex]; - IFfdAttachment ffdAttachment = slot.attachment as IFfdAttachment; - if (ffdAttachment == null || !ffdAttachment.ApplyFFD(attachment)) return; - - float[] frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - float[][] frameVertices = this.frameVertices; - int vertexCount = frameVertices[0].Length; - - float[] vertices = slot.attachmentVertices; - if (slot.attachmentVerticesCount != vertexCount) alpha = 1; // Don't mix from uninitialized slot vertices. - - // Ensure capacity - if (vertices.Length < vertexCount) { - vertices = new float[vertexCount]; - slot.attachmentVertices = vertices; - } - slot.attachmentVerticesCount = vertexCount; - - if (time >= frames[frames.Length - 1]) { // Time is after last frame. - float[] lastVertices = frameVertices[frames.Length - 1]; - if (alpha < 1) { - for (int i = 0; i < vertexCount; i++) { - float vertex = vertices[i]; - vertices[i] = vertex + (lastVertices[i] - vertex) * alpha; - } - } else - Array.Copy(lastVertices, 0, vertices, 0, vertexCount); - return; - } - - // Interpolate between the previous frame and the current frame. - int frame = Animation.binarySearch(frames, time); - float frameTime = frames[frame]; - float percent = 1 - (time - frameTime) / (frames[frame - 1] - frameTime); - percent = GetCurvePercent(frame - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); - - float[] prevVertices = frameVertices[frame - 1]; - float[] nextVertices = frameVertices[frame]; - - if (alpha < 1) { - for (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - float vertex = vertices[i]; - vertices[i] = vertex + (prev + (nextVertices[i] - prev) * percent - vertex) * alpha; - } - } else { - for (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - vertices[i] = prev + (nextVertices[i] - prev) * percent; - } - } - } - } - - public class IkConstraintTimeline : CurveTimeline { - private const int PREV_TIME = -3; - private const int PREV_MIX = -2; - private const int PREV_BEND_DIRECTION = -1; - private const int MIX = 1; - - internal int ikConstraintIndex; - internal float[] frames; - - public int IkConstraintIndex { get { return ikConstraintIndex; } set { ikConstraintIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, mix, bendDirection, ... - - public IkConstraintTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * 3]; - } - - /// Sets the time, mix and bend direction of the specified keyframe. - public void SetFrame (int frameIndex, float time, float mix, int bendDirection) { - frameIndex *= 3; - frames[frameIndex] = time; - frames[frameIndex + 1] = mix; - frames[frameIndex + 2] = bendDirection; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) { - float[] frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - IkConstraint constraint = skeleton.ikConstraints.Items[ikConstraintIndex]; - - if (time >= frames[frames.Length - 3]) { // Time is after last frame. - constraint.mix += (frames[frames.Length + PREV_MIX] - constraint.mix) * alpha; - constraint.bendDirection = (int)frames[frames.Length + PREV_BEND_DIRECTION]; - return; - } - - // Interpolate between the previous frame and the current frame. - int frame = Animation.binarySearch(frames, time, 3); - float frameTime = frames[frame]; - float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime); - percent = GetCurvePercent(frame / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); - - float mix = frames[frame + PREV_MIX]; - constraint.mix += (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha; - constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION]; - } - } - - public class TransformConstraintTimeline : CurveTimeline { - private const int PREV_TIME = -5; - private const int PREV_ROTATE_MIX = -4; - private const int PREV_TRANSLATE_MIX = -3; - private const int PREV_SCALE_MIX = -2; - private const int PREV_SHEAR_MIX = -1; - private const int ROTATE_MIX = 1; - private const int TRANSLATE_MIX = 2; - private const int SCALE_MIX = 3; - private const int SHEAR_MIX = 4; - - internal int transformConstraintIndex; - internal float[] frames; - - public int TransformConstraintIndex { get { return transformConstraintIndex; } set { transformConstraintIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, scale mix, shear mix, ... - - public TransformConstraintTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * 5]; - } - - public void SetFrame (int frameIndex, float time, float rotateMix, float translateMix, float scaleMix, float shearMix) { - frameIndex *= 5; - frames[frameIndex] = time; - frames[frameIndex + 1] = rotateMix; - frames[frameIndex + 2] = translateMix; - frames[frameIndex + 3] = scaleMix; - frames[frameIndex + 4] = shearMix; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) { - float[] frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - TransformConstraint constraint = skeleton.transformConstraints.Items[transformConstraintIndex]; - - if (time >= frames[frames.Length - 5]) { // Time is after last frame. - int i = frames.Length - 1; - constraint.rotateMix += (frames[i - 3] - constraint.rotateMix) * alpha; - constraint.translateMix += (frames[i - 2] - constraint.translateMix) * alpha; - constraint.scaleMix += (frames[i - 1] - constraint.scaleMix) * alpha; - constraint.shearMix += (frames[i] - constraint.shearMix) * alpha; - return; - } - - // Interpolate between the previous frame and the current frame. - int frame = Animation.binarySearch(frames, time, 5); - float frameTime = frames[frame]; - float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime); - percent = GetCurvePercent(frame / 5 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); - - float rotate = frames[frame + PREV_ROTATE_MIX]; - float translate = frames[frame + PREV_TRANSLATE_MIX]; - float scale = frames[frame + PREV_SCALE_MIX]; - float shear = frames[frame + PREV_SHEAR_MIX]; - constraint.rotateMix += (rotate + (frames[frame + ROTATE_MIX] - rotate) * percent - constraint.rotateMix) * alpha; - constraint.translateMix += (translate + (frames[frame + TRANSLATE_MIX] - translate) * percent - constraint.translateMix) * alpha; - constraint.scaleMix += (scale + (frames[frame + SCALE_MIX] - scale) * percent - constraint.scaleMix) * alpha; - constraint.shearMix += (shear + (frames[frame + SHEAR_MIX] - shear) * percent - constraint.shearMix) * alpha; - } - } + +namespace Spine3_2_xx +{ + public class Animation + { + internal ExposedList timelines; + internal float duration; + internal String name; + + public String Name { get { return name; } } + public ExposedList Timelines { get { return timelines; } set { timelines = value; } } + public float Duration { get { return duration; } set { duration = value; } } + + public Animation(String name, ExposedList timelines, float duration) + { + if (name == null) throw new ArgumentNullException("name cannot be null."); + if (timelines == null) throw new ArgumentNullException("timelines cannot be null."); + this.name = name; + this.timelines = timelines; + this.duration = duration; + } + + /// Poses the skeleton at the specified time for this animation. + /// The last time the animation was applied. + /// Any triggered events are added. + public void Apply(Skeleton skeleton, float lastTime, float time, bool loop, ExposedList events) + { + if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); + + if (loop && duration != 0) + { + time %= duration; + if (lastTime > 0) lastTime %= duration; + } + + ExposedList timelines = this.timelines; + for (int i = 0, n = timelines.Count; i < n; i++) + timelines.Items[i].Apply(skeleton, lastTime, time, events, 1); + } + + /// Poses the skeleton at the specified time for this animation mixed with the current pose. + /// The last time the animation was applied. + /// Any triggered events are added. + /// The amount of this animation that affects the current pose. + public void Mix(Skeleton skeleton, float lastTime, float time, bool loop, ExposedList events, float alpha) + { + if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); + + if (loop && duration != 0) + { + time %= duration; + if (lastTime > 0) lastTime %= duration; + } + + ExposedList timelines = this.timelines; + for (int i = 0, n = timelines.Count; i < n; i++) + timelines.Items[i].Apply(skeleton, lastTime, time, events, alpha); + } + + /// After the first and before the last entry. + internal static int binarySearch(float[] values, float target, int step) + { + int low = 0; + int high = values.Length / step - 2; + if (high == 0) return step; + int current = (int)((uint)high >> 1); + while (true) + { + if (values[(current + 1) * step] <= target) + low = current + 1; + else + high = current; + if (low == high) return (low + 1) * step; + current = (int)((uint)(low + high) >> 1); + } + } + + /// After the first and before the last entry. + internal static int binarySearch(float[] values, float target) + { + int low = 0; + int high = values.Length - 2; + if (high == 0) return 1; + int current = (int)((uint)high >> 1); + while (true) + { + if (values[(current + 1)] <= target) + low = current + 1; + else + high = current; + if (low == high) return (low + 1); + current = (int)((uint)(low + high) >> 1); + } + } + + internal static int linearSearch(float[] values, float target, int step) + { + for (int i = 0, last = values.Length - step; i <= last; i += step) + if (values[i] > target) return i; + return -1; + } + } + + public interface Timeline + { + /// Sets the value(s) for the specified time. + /// May be null to not collect fired events. + void Apply(Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha); + } + + /// Base class for frames that use an interpolation bezier curve. + abstract public class CurveTimeline : Timeline + { + protected const float LINEAR = 0, STEPPED = 1, BEZIER = 2; + protected const int BEZIER_SEGMENTS = 10, BEZIER_SIZE = BEZIER_SEGMENTS * 2 - 1; + + private float[] curves; // type, x, y, ... + public int FrameCount { get { return curves.Length / BEZIER_SIZE + 1; } } + + public CurveTimeline(int frameCount) + { + curves = new float[(frameCount - 1) * BEZIER_SIZE]; + } + + abstract public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha); + + public void SetLinear(int frameIndex) + { + curves[frameIndex * BEZIER_SIZE] = LINEAR; + } + + public void SetStepped(int frameIndex) + { + curves[frameIndex * BEZIER_SIZE] = STEPPED; + } + + /// Sets the control handle positions for an interpolation bezier curve used to transition from this keyframe to the next. + /// cx1 and cx2 are from 0 to 1, representing the percent of time between the two keyframes. cy1 and cy2 are the percent of + /// the difference between the keyframe's values. + public void SetCurve(int frameIndex, float cx1, float cy1, float cx2, float cy2) + { + float subdiv1 = 1f / BEZIER_SEGMENTS, subdiv2 = subdiv1 * subdiv1, subdiv3 = subdiv2 * subdiv1; + float pre1 = 3 * subdiv1, pre2 = 3 * subdiv2, pre4 = 6 * subdiv2, pre5 = 6 * subdiv3; + float tmp1x = -cx1 * 2 + cx2, tmp1y = -cy1 * 2 + cy2, tmp2x = (cx1 - cx2) * 3 + 1, tmp2y = (cy1 - cy2) * 3 + 1; + float dfx = cx1 * pre1 + tmp1x * pre2 + tmp2x * subdiv3, dfy = cy1 * pre1 + tmp1y * pre2 + tmp2y * subdiv3; + float ddfx = tmp1x * pre4 + tmp2x * pre5, ddfy = tmp1y * pre4 + tmp2y * pre5; + float dddfx = tmp2x * pre5, dddfy = tmp2y * pre5; + + int i = frameIndex * BEZIER_SIZE; + float[] curves = this.curves; + curves[i++] = BEZIER; + + float x = dfx, y = dfy; + for (int n = i + BEZIER_SIZE - 1; i < n; i += 2) + { + curves[i] = x; + curves[i + 1] = y; + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + x += dfx; + y += dfy; + } + } + + public float GetCurvePercent(int frameIndex, float percent) + { + float[] curves = this.curves; + int i = frameIndex * BEZIER_SIZE; + float type = curves[i]; + if (type == LINEAR) return percent; + if (type == STEPPED) return 0; + i++; + float x = 0; + for (int start = i, n = i + BEZIER_SIZE - 1; i < n; i += 2) + { + x = curves[i]; + if (x >= percent) + { + float prevX, prevY; + if (i == start) + { + prevX = 0; + prevY = 0; + } + else + { + prevX = curves[i - 2]; + prevY = curves[i - 1]; + } + return prevY + (curves[i + 1] - prevY) * (percent - prevX) / (x - prevX); + } + } + float y = curves[i - 1]; + return y + (1 - y) * (percent - x) / (1 - x); // Last point is 1,1. + } + public float GetCurveType(int frameIndex) + { + return curves[frameIndex * BEZIER_SIZE]; + } + } + + public class RotateTimeline : CurveTimeline + { + internal const int PREV_TIME = -2; + internal const int VALUE = 1; + + internal int boneIndex; + internal float[] frames; + + public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, angle, ... + + public RotateTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount << 1]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, float angle) + { + frameIndex *= 2; + frames[frameIndex] = time; + frames[frameIndex + 1] = angle; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) + { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + Bone bone = skeleton.bones.Items[boneIndex]; + + float amount; + + if (time >= frames[frames.Length - 2]) + { // Time is after last frame. + amount = bone.data.rotation + frames[frames.Length - 1] - bone.rotation; + while (amount > 180) + amount -= 360; + while (amount < -180) + amount += 360; + bone.rotation += amount * alpha; + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = Animation.binarySearch(frames, time, 2); + float prevFrameValue = frames[frame - 1]; + float frameTime = frames[frame]; + float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime); + percent = GetCurvePercent((frame >> 1) - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); + + amount = frames[frame + VALUE] - prevFrameValue; + while (amount > 180) + amount -= 360; + while (amount < -180) + amount += 360; + amount = bone.data.rotation + (prevFrameValue + amount * percent) - bone.rotation; + while (amount > 180) + amount -= 360; + while (amount < -180) + amount += 360; + bone.rotation += amount * alpha; + } + } + + public class TranslateTimeline : CurveTimeline + { + protected const int PREV_TIME = -3; + protected const int X = 1; + protected const int Y = 2; + + internal int boneIndex; + internal float[] frames; + + public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, value, value, ... + + public TranslateTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * 3]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, float x, float y) + { + frameIndex *= 3; + frames[frameIndex] = time; + frames[frameIndex + 1] = x; + frames[frameIndex + 2] = y; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) + { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + Bone bone = skeleton.bones.Items[boneIndex]; + + if (time >= frames[frames.Length - 3]) + { // Time is after last frame. + bone.x += (bone.data.x + frames[frames.Length - 2] - bone.x) * alpha; + bone.y += (bone.data.y + frames[frames.Length - 1] - bone.y) * alpha; + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = Animation.binarySearch(frames, time, 3); + float prevFrameX = frames[frame - 2]; + float prevFrameY = frames[frame - 1]; + float frameTime = frames[frame]; + float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime); + percent = GetCurvePercent(frame / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); + + bone.x += (bone.data.x + prevFrameX + (frames[frame + X] - prevFrameX) * percent - bone.x) * alpha; + bone.y += (bone.data.y + prevFrameY + (frames[frame + Y] - prevFrameY) * percent - bone.y) * alpha; + } + } + + public class ScaleTimeline : TranslateTimeline + { + public ScaleTimeline(int frameCount) + : base(frameCount) + { + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) + { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + Bone bone = skeleton.bones.Items[boneIndex]; + if (time >= frames[frames.Length - 3]) + { // Time is after last frame. + bone.scaleX += (bone.data.scaleX * frames[frames.Length - 2] - bone.scaleX) * alpha; + bone.scaleY += (bone.data.scaleY * frames[frames.Length - 1] - bone.scaleY) * alpha; + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = Animation.binarySearch(frames, time, 3); + float prevFrameX = frames[frame - 2]; + float prevFrameY = frames[frame - 1]; + float frameTime = frames[frame]; + float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime); + percent = GetCurvePercent(frame / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); + + bone.scaleX += (bone.data.scaleX * (prevFrameX + (frames[frame + X] - prevFrameX) * percent) - bone.scaleX) * alpha; + bone.scaleY += (bone.data.scaleY * (prevFrameY + (frames[frame + Y] - prevFrameY) * percent) - bone.scaleY) * alpha; + } + } + + public class ShearTimeline : TranslateTimeline + { + public ShearTimeline(int frameCount) + : base(frameCount) + { + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) + { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + Bone bone = skeleton.bones.Items[boneIndex]; + if (time >= frames[frames.Length - 3]) + { // Time is after last frame. + bone.shearX += (bone.data.shearX + frames[frames.Length - 2] - bone.shearX) * alpha; + bone.shearY += (bone.data.shearY + frames[frames.Length - 1] - bone.shearY) * alpha; + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = Animation.binarySearch(frames, time, 3); + float prevFrameX = frames[frame - 2]; + float prevFrameY = frames[frame - 1]; + float frameTime = frames[frame]; + float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime); + percent = GetCurvePercent(frame / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); + + bone.shearX += (bone.data.shearX + (prevFrameX + (frames[frame + X] - prevFrameX) * percent) - bone.shearX) * alpha; + bone.shearY += (bone.data.shearY + (prevFrameY + (frames[frame + Y] - prevFrameY) * percent) - bone.shearY) * alpha; + } + } + + public class ColorTimeline : CurveTimeline + { + protected const int PREV_TIME = -5; + protected const int R = 1; + protected const int G = 2; + protected const int B = 3; + protected const int A = 4; + + internal int slotIndex; + internal float[] frames; + + public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, r, g, b, a, ... + + public ColorTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * 5]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, float r, float g, float b, float a) + { + frameIndex *= 5; + frames[frameIndex] = time; + frames[frameIndex + 1] = r; + frames[frameIndex + 2] = g; + frames[frameIndex + 3] = b; + frames[frameIndex + 4] = a; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) + { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + float r, g, b, a; + if (time >= frames[frames.Length - 5]) + { // Time is after last frame. + int i = frames.Length - 1; + r = frames[i - 3]; + g = frames[i - 2]; + b = frames[i - 1]; + a = frames[i]; + } + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.binarySearch(frames, time, 5); + float frameTime = frames[frame]; + float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime); + percent = GetCurvePercent(frame / 5 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); + + r = frames[frame - 4]; + g = frames[frame - 3]; + b = frames[frame - 2]; + a = frames[frame - 1]; + r += (frames[frame + R] - r) * percent; + g += (frames[frame + G] - g) * percent; + b += (frames[frame + B] - b) * percent; + a += (frames[frame + A] - a) * percent; + } + Slot slot = skeleton.slots.Items[slotIndex]; + if (alpha < 1) + { + slot.r += (r - slot.r) * alpha; + slot.g += (g - slot.g) * alpha; + slot.b += (b - slot.b) * alpha; + slot.a += (a - slot.a) * alpha; + } + else + { + slot.r = r; + slot.g = g; + slot.b = b; + slot.a = a; + } + } + } + + public class AttachmentTimeline : Timeline + { + internal int slotIndex; + internal float[] frames; + private String[] attachmentNames; + + public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, ... + public String[] AttachmentNames { get { return attachmentNames; } set { attachmentNames = value; } } + public int FrameCount { get { return frames.Length; } } + + public AttachmentTimeline(int frameCount) + { + frames = new float[frameCount]; + attachmentNames = new String[frameCount]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, String attachmentName) + { + frames[frameIndex] = time; + attachmentNames[frameIndex] = attachmentName; + } + + public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) + { + float[] frames = this.frames; + if (time < frames[0]) + { + if (lastTime > time) Apply(skeleton, lastTime, int.MaxValue, null, 0); + return; + } + else if (lastTime > time) // + lastTime = -1; + + int frameIndex = (time >= frames[frames.Length - 1] ? frames.Length : Animation.binarySearch(frames, time)) - 1; + if (frames[frameIndex] < lastTime) return; + + String attachmentName = attachmentNames[frameIndex]; + skeleton.slots.Items[slotIndex].Attachment = + attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); + } + } + + public class EventTimeline : Timeline + { + internal float[] frames; + private Event[] events; + + public float[] Frames { get { return frames; } set { frames = value; } } // time, ... + public Event[] Events { get { return events; } set { events = value; } } + public int FrameCount { get { return frames.Length; } } + + public EventTimeline(int frameCount) + { + frames = new float[frameCount]; + events = new Event[frameCount]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, Event e) + { + frames[frameIndex] = e.Time; + events[frameIndex] = e; + } + + /// Fires events for frames > lastTime and <= time. + public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) + { + if (firedEvents == null) return; + float[] frames = this.frames; + int frameCount = frames.Length; + + if (lastTime > time) + { // Fire events after last time for looped animations. + Apply(skeleton, lastTime, int.MaxValue, firedEvents, alpha); + lastTime = -1f; + } + else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame. + return; + if (time < frames[0]) return; // Time is before first frame. + + int frame; + if (lastTime < frames[0]) + frame = 0; + else + { + frame = Animation.binarySearch(frames, lastTime); + float frameTime = frames[frame]; + while (frame > 0) + { // Fire multiple events with the same frame. + if (frames[frame - 1] != frameTime) break; + frame--; + } + } + for (; frame < frameCount && time >= frames[frame]; frame++) + firedEvents.Add(events[frame]); + } + } + + public class DrawOrderTimeline : Timeline + { + internal float[] frames; + private int[][] drawOrders; + + public float[] Frames { get { return frames; } set { frames = value; } } // time, ... + public int[][] DrawOrders { get { return drawOrders; } set { drawOrders = value; } } + public int FrameCount { get { return frames.Length; } } + + public DrawOrderTimeline(int frameCount) + { + frames = new float[frameCount]; + drawOrders = new int[frameCount][]; + } + + /// Sets the time and value of the specified keyframe. + /// May be null to use bind pose draw order. + public void SetFrame(int frameIndex, float time, int[] drawOrder) + { + frames[frameIndex] = time; + drawOrders[frameIndex] = drawOrder; + } + + public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) + { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + int frame; + if (time >= frames[frames.Length - 1]) // Time is after last frame. + frame = frames.Length - 1; + else + frame = Animation.binarySearch(frames, time) - 1; + + ExposedList drawOrder = skeleton.drawOrder; + ExposedList slots = skeleton.slots; + int[] drawOrderToSetupIndex = drawOrders[frame]; + if (drawOrderToSetupIndex == null) + { + drawOrder.Clear(); + for (int i = 0, n = slots.Count; i < n; i++) + drawOrder.Add(slots.Items[i]); + } + else + { + for (int i = 0, n = drawOrderToSetupIndex.Length; i < n; i++) + drawOrder.Items[i] = slots.Items[drawOrderToSetupIndex[i]]; + } + } + } + + public class FfdTimeline : CurveTimeline + { + internal int slotIndex; + internal float[] frames; + private float[][] frameVertices; + internal Attachment attachment; + + public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, ... + public float[][] Vertices { get { return frameVertices; } set { frameVertices = value; } } + public Attachment Attachment { get { return attachment; } set { attachment = value; } } + + public FfdTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount]; + frameVertices = new float[frameCount][]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, float[] vertices) + { + frames[frameIndex] = time; + frameVertices[frameIndex] = vertices; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) + { + Slot slot = skeleton.slots.Items[slotIndex]; + IFfdAttachment ffdAttachment = slot.attachment as IFfdAttachment; + if (ffdAttachment == null || !ffdAttachment.ApplyFFD(attachment)) return; + + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + float[][] frameVertices = this.frameVertices; + int vertexCount = frameVertices[0].Length; + + float[] vertices = slot.attachmentVertices; + if (slot.attachmentVerticesCount != vertexCount) alpha = 1; // Don't mix from uninitialized slot vertices. + + // Ensure capacity + if (vertices.Length < vertexCount) + { + vertices = new float[vertexCount]; + slot.attachmentVertices = vertices; + } + slot.attachmentVerticesCount = vertexCount; + + if (time >= frames[frames.Length - 1]) + { // Time is after last frame. + float[] lastVertices = frameVertices[frames.Length - 1]; + if (alpha < 1) + { + for (int i = 0; i < vertexCount; i++) + { + float vertex = vertices[i]; + vertices[i] = vertex + (lastVertices[i] - vertex) * alpha; + } + } + else + Array.Copy(lastVertices, 0, vertices, 0, vertexCount); + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = Animation.binarySearch(frames, time); + float frameTime = frames[frame]; + float percent = 1 - (time - frameTime) / (frames[frame - 1] - frameTime); + percent = GetCurvePercent(frame - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); + + float[] prevVertices = frameVertices[frame - 1]; + float[] nextVertices = frameVertices[frame]; + + if (alpha < 1) + { + for (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i]; + float vertex = vertices[i]; + vertices[i] = vertex + (prev + (nextVertices[i] - prev) * percent - vertex) * alpha; + } + } + else + { + for (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i]; + vertices[i] = prev + (nextVertices[i] - prev) * percent; + } + } + } + } + + public class IkConstraintTimeline : CurveTimeline + { + private const int PREV_TIME = -3; + private const int PREV_MIX = -2; + private const int PREV_BEND_DIRECTION = -1; + private const int MIX = 1; + + internal int ikConstraintIndex; + internal float[] frames; + + public int IkConstraintIndex { get { return ikConstraintIndex; } set { ikConstraintIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, mix, bendDirection, ... + + public IkConstraintTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * 3]; + } + + /// Sets the time, mix and bend direction of the specified keyframe. + public void SetFrame(int frameIndex, float time, float mix, int bendDirection) + { + frameIndex *= 3; + frames[frameIndex] = time; + frames[frameIndex + 1] = mix; + frames[frameIndex + 2] = bendDirection; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) + { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + IkConstraint constraint = skeleton.ikConstraints.Items[ikConstraintIndex]; + + if (time >= frames[frames.Length - 3]) + { // Time is after last frame. + constraint.mix += (frames[frames.Length + PREV_MIX] - constraint.mix) * alpha; + constraint.bendDirection = (int)frames[frames.Length + PREV_BEND_DIRECTION]; + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = Animation.binarySearch(frames, time, 3); + float frameTime = frames[frame]; + float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime); + percent = GetCurvePercent(frame / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); + + float mix = frames[frame + PREV_MIX]; + constraint.mix += (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha; + constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION]; + } + } + + public class TransformConstraintTimeline : CurveTimeline + { + private const int PREV_TIME = -5; + private const int PREV_ROTATE_MIX = -4; + private const int PREV_TRANSLATE_MIX = -3; + private const int PREV_SCALE_MIX = -2; + private const int PREV_SHEAR_MIX = -1; + private const int ROTATE_MIX = 1; + private const int TRANSLATE_MIX = 2; + private const int SCALE_MIX = 3; + private const int SHEAR_MIX = 4; + + internal int transformConstraintIndex; + internal float[] frames; + + public int TransformConstraintIndex { get { return transformConstraintIndex; } set { transformConstraintIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, scale mix, shear mix, ... + + public TransformConstraintTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * 5]; + } + + public void SetFrame(int frameIndex, float time, float rotateMix, float translateMix, float scaleMix, float shearMix) + { + frameIndex *= 5; + frames[frameIndex] = time; + frames[frameIndex + 1] = rotateMix; + frames[frameIndex + 2] = translateMix; + frames[frameIndex + 3] = scaleMix; + frames[frameIndex + 4] = shearMix; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) + { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + TransformConstraint constraint = skeleton.transformConstraints.Items[transformConstraintIndex]; + + if (time >= frames[frames.Length - 5]) + { // Time is after last frame. + int i = frames.Length - 1; + constraint.rotateMix += (frames[i - 3] - constraint.rotateMix) * alpha; + constraint.translateMix += (frames[i - 2] - constraint.translateMix) * alpha; + constraint.scaleMix += (frames[i - 1] - constraint.scaleMix) * alpha; + constraint.shearMix += (frames[i] - constraint.shearMix) * alpha; + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = Animation.binarySearch(frames, time, 5); + float frameTime = frames[frame]; + float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime); + percent = GetCurvePercent(frame / 5 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent)); + + float rotate = frames[frame + PREV_ROTATE_MIX]; + float translate = frames[frame + PREV_TRANSLATE_MIX]; + float scale = frames[frame + PREV_SCALE_MIX]; + float shear = frames[frame + PREV_SHEAR_MIX]; + constraint.rotateMix += (rotate + (frames[frame + ROTATE_MIX] - rotate) * percent - constraint.rotateMix) * alpha; + constraint.translateMix += (translate + (frames[frame + TRANSLATE_MIX] - translate) * percent - constraint.translateMix) * alpha; + constraint.scaleMix += (scale + (frames[frame + SCALE_MIX] - scale) * percent - constraint.scaleMix) * alpha; + constraint.shearMix += (shear + (frames[frame + SHEAR_MIX] - shear) * percent - constraint.shearMix) * alpha; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/AnimationState.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/AnimationState.cs index 75fde25..f9664a3 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/AnimationState.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/AnimationState.cs @@ -30,274 +30,312 @@ *****************************************************************************/ using System; -using System.Collections.Generic; using System.Text; -namespace Spine3_2_xx { - public class AnimationState { - private AnimationStateData data; - private ExposedList tracks = new ExposedList(); - private ExposedList events = new ExposedList(); - private float timeScale = 1; - - public AnimationStateData Data { get { return data; } } - public float TimeScale { get { return timeScale; } set { timeScale = value; } } - - public delegate void StartEndDelegate (AnimationState state, int trackIndex); - public event StartEndDelegate Start; - public event StartEndDelegate End; - - public delegate void EventDelegate (AnimationState state, int trackIndex, Event e); - public event EventDelegate Event; - - public delegate void CompleteDelegate (AnimationState state, int trackIndex, int loopCount); - public event CompleteDelegate Complete; - - public AnimationState (AnimationStateData data) { - if (data == null) throw new ArgumentNullException("data cannot be null."); - this.data = data; - } - - public void Update (float delta) { - delta *= timeScale; - for (int i = 0; i < tracks.Count; i++) { - TrackEntry current = tracks.Items[i]; - if (current == null) continue; - - float trackDelta = delta * current.timeScale; - float time = current.time + trackDelta; - float endTime = current.endTime; - - current.time = time; - if (current.previous != null) { - current.previous.time += trackDelta; - current.mixTime += trackDelta; - } - - // Check if completed the animation or a loop iteration. - if (current.loop ? (current.lastTime % endTime > time % endTime) : (current.lastTime < endTime && time >= endTime)) { - int count = (int)(time / endTime); - current.OnComplete(this, i, count); - if (Complete != null) Complete(this, i, count); - } - - TrackEntry next = current.next; - if (next != null) { - next.time = current.lastTime - next.delay; - if (next.time >= 0) SetCurrent(i, next); - } else { - // End non-looping animation when it reaches its end time and there is no next entry. - if (!current.loop && current.lastTime >= current.endTime) ClearTrack(i); - } - } - } - - public void Apply (Skeleton skeleton) { - ExposedList events = this.events; - - for (int i = 0; i < tracks.Count; i++) { - TrackEntry current = tracks.Items[i]; - if (current == null) continue; - - events.Clear(); - - float time = current.time; - bool loop = current.loop; - if (!loop && time > current.endTime) time = current.endTime; - - TrackEntry previous = current.previous; - if (previous == null) { - if (current.mix == 1) - current.animation.Apply(skeleton, current.lastTime, time, loop, events); - else - current.animation.Mix(skeleton, current.lastTime, time, loop, events, current.mix); - } else { - float previousTime = previous.time; - if (!previous.loop && previousTime > previous.endTime) previousTime = previous.endTime; - previous.animation.Apply(skeleton, previous.lastTime, previousTime, previous.loop, null); - // Remove the line above, and uncomment the line below, to allow previous animations to fire events during mixing. - //previous.animation.Apply(skeleton, previous.lastTime, previousTime, previous.loop, events); - previous.lastTime = previousTime; - - float alpha = current.mixTime / current.mixDuration * current.mix; - if (alpha >= 1) { - alpha = 1; - current.previous = null; - } - current.animation.Mix(skeleton, current.lastTime, time, loop, events, alpha); - } - - for (int ii = 0, nn = events.Count; ii < nn; ii++) { - Event e = events.Items[ii]; - current.OnEvent(this, i, e); - if (Event != null) Event(this, i, e); - } - - current.lastTime = current.time; - } - } - - public void ClearTracks () { - for (int i = 0, n = tracks.Count; i < n; i++) - ClearTrack(i); - tracks.Clear(); - } - - public void ClearTrack (int trackIndex) { - if (trackIndex >= tracks.Count) return; - TrackEntry current = tracks.Items[trackIndex]; - if (current == null) return; - - current.OnEnd(this, trackIndex); - if (End != null) End(this, trackIndex); - - tracks.Items[trackIndex] = null; - } - - private TrackEntry ExpandToIndex (int index) { - if (index < tracks.Count) return tracks.Items[index]; - while (index >= tracks.Count) - tracks.Add(null); - return null; - } - - private void SetCurrent (int index, TrackEntry entry) { - TrackEntry current = ExpandToIndex(index); - if (current != null) { - TrackEntry previous = current.previous; - current.previous = null; - - current.OnEnd(this, index); - if (End != null) End(this, index); - - entry.mixDuration = data.GetMix(current.animation, entry.animation); - if (entry.mixDuration > 0) { - entry.mixTime = 0; - // If a mix is in progress, mix from the closest animation. - if (previous != null && current.mixTime / current.mixDuration < 0.5f) - entry.previous = previous; - else - entry.previous = current; - } - } - - tracks.Items[index] = entry; - - entry.OnStart(this, index); - if (Start != null) Start(this, index); - } - - public TrackEntry SetAnimation (int trackIndex, String animationName, bool loop) { - Animation animation = data.skeletonData.FindAnimation(animationName); - if (animation == null) throw new ArgumentException("Animation not found: " + animationName); - return SetAnimation(trackIndex, animation, loop); - } - - /// Set the current animation. Any queued animations are cleared. - public TrackEntry SetAnimation (int trackIndex, Animation animation, bool loop) { - if (animation == null) throw new ArgumentException("animation cannot be null."); - TrackEntry entry = new TrackEntry(); - entry.animation = animation; - entry.loop = loop; - entry.time = 0; - entry.endTime = animation.Duration; - SetCurrent(trackIndex, entry); - return entry; - } - - public TrackEntry AddAnimation (int trackIndex, String animationName, bool loop, float delay) { - Animation animation = data.skeletonData.FindAnimation(animationName); - if (animation == null) throw new ArgumentException("Animation not found: " + animationName); - return AddAnimation(trackIndex, animation, loop, delay); - } - - /// Adds an animation to be played delay seconds after the current or last queued animation. - /// May be <= 0 to use duration of previous animation minus any mix duration plus the negative delay. - public TrackEntry AddAnimation (int trackIndex, Animation animation, bool loop, float delay) { - if (animation == null) throw new ArgumentException("animation cannot be null."); - TrackEntry entry = new TrackEntry(); - entry.animation = animation; - entry.loop = loop; - entry.time = 0; - entry.endTime = animation.Duration; - - TrackEntry last = ExpandToIndex(trackIndex); - if (last != null) { - while (last.next != null) - last = last.next; - last.next = entry; - } else - tracks.Items[trackIndex] = entry; - - if (delay <= 0) { - if (last != null) - delay += last.endTime - data.GetMix(last.animation, animation); - else - delay = 0; - } - entry.delay = delay; - - return entry; - } - - /// May be null. - public TrackEntry GetCurrent (int trackIndex) { - if (trackIndex >= tracks.Count) return null; - return tracks.Items[trackIndex]; - } - - override public String ToString () { - StringBuilder buffer = new StringBuilder(); - for (int i = 0, n = tracks.Count; i < n; i++) { - TrackEntry entry = tracks.Items[i]; - if (entry == null) continue; - if (buffer.Length > 0) buffer.Append(", "); - buffer.Append(entry.ToString()); - } - if (buffer.Length == 0) return ""; - return buffer.ToString(); - } - } - - public class TrackEntry { - internal TrackEntry next, previous; - internal Animation animation; - internal bool loop; - internal float delay, time, lastTime = -1, endTime, timeScale = 1; - internal float mixTime, mixDuration, mix = 1; - - public Animation Animation { get { return animation; } } - public float Delay { get { return delay; } set { delay = value; } } - public float Time { get { return time; } set { time = value; } } - public float LastTime { get { return lastTime; } set { lastTime = value; } } - public float EndTime { get { return endTime; } set { endTime = value; } } - public float TimeScale { get { return timeScale; } set { timeScale = value; } } - public float Mix { get { return mix; } set { mix = value; } } - public bool Loop { get { return loop; } set { loop = value; } } - - public event AnimationState.StartEndDelegate Start; - public event AnimationState.StartEndDelegate End; - public event AnimationState.EventDelegate Event; - public event AnimationState.CompleteDelegate Complete; - - internal void OnStart (AnimationState state, int index) { - if (Start != null) Start(state, index); - } - - internal void OnEnd (AnimationState state, int index) { - if (End != null) End(state, index); - } - - internal void OnEvent (AnimationState state, int index, Event e) { - if (Event != null) Event(state, index, e); - } - - internal void OnComplete (AnimationState state, int index, int loopCount) { - if (Complete != null) Complete(state, index, loopCount); - } - - override public String ToString () { - return animation == null ? "" : animation.name; - } - } +namespace Spine3_2_xx +{ + public class AnimationState + { + private AnimationStateData data; + private ExposedList tracks = new ExposedList(); + private ExposedList events = new ExposedList(); + private float timeScale = 1; + + public AnimationStateData Data { get { return data; } } + public float TimeScale { get { return timeScale; } set { timeScale = value; } } + + public delegate void StartEndDelegate(AnimationState state, int trackIndex); + public event StartEndDelegate Start; + public event StartEndDelegate End; + + public delegate void EventDelegate(AnimationState state, int trackIndex, Event e); + public event EventDelegate Event; + + public delegate void CompleteDelegate(AnimationState state, int trackIndex, int loopCount); + public event CompleteDelegate Complete; + + public AnimationState(AnimationStateData data) + { + if (data == null) throw new ArgumentNullException("data cannot be null."); + this.data = data; + } + + public void Update(float delta) + { + delta *= timeScale; + for (int i = 0; i < tracks.Count; i++) + { + TrackEntry current = tracks.Items[i]; + if (current == null) continue; + + float trackDelta = delta * current.timeScale; + float time = current.time + trackDelta; + float endTime = current.endTime; + + current.time = time; + if (current.previous != null) + { + current.previous.time += trackDelta; + current.mixTime += trackDelta; + } + + // Check if completed the animation or a loop iteration. + if (current.loop ? (current.lastTime % endTime > time % endTime) : (current.lastTime < endTime && time >= endTime)) + { + int count = (int)(time / endTime); + current.OnComplete(this, i, count); + if (Complete != null) Complete(this, i, count); + } + + TrackEntry next = current.next; + if (next != null) + { + next.time = current.lastTime - next.delay; + if (next.time >= 0) SetCurrent(i, next); + } + else + { + // End non-looping animation when it reaches its end time and there is no next entry. + if (!current.loop && current.lastTime >= current.endTime) ClearTrack(i); + } + } + } + + public void Apply(Skeleton skeleton) + { + ExposedList events = this.events; + + for (int i = 0; i < tracks.Count; i++) + { + TrackEntry current = tracks.Items[i]; + if (current == null) continue; + + events.Clear(); + + float time = current.time; + bool loop = current.loop; + if (!loop && time > current.endTime) time = current.endTime; + + TrackEntry previous = current.previous; + if (previous == null) + { + if (current.mix == 1) + current.animation.Apply(skeleton, current.lastTime, time, loop, events); + else + current.animation.Mix(skeleton, current.lastTime, time, loop, events, current.mix); + } + else + { + float previousTime = previous.time; + if (!previous.loop && previousTime > previous.endTime) previousTime = previous.endTime; + previous.animation.Apply(skeleton, previous.lastTime, previousTime, previous.loop, null); + // Remove the line above, and uncomment the line below, to allow previous animations to fire events during mixing. + //previous.animation.Apply(skeleton, previous.lastTime, previousTime, previous.loop, events); + previous.lastTime = previousTime; + + float alpha = current.mixTime / current.mixDuration * current.mix; + if (alpha >= 1) + { + alpha = 1; + current.previous = null; + } + current.animation.Mix(skeleton, current.lastTime, time, loop, events, alpha); + } + + for (int ii = 0, nn = events.Count; ii < nn; ii++) + { + Event e = events.Items[ii]; + current.OnEvent(this, i, e); + if (Event != null) Event(this, i, e); + } + + current.lastTime = current.time; + } + } + + public void ClearTracks() + { + for (int i = 0, n = tracks.Count; i < n; i++) + ClearTrack(i); + tracks.Clear(); + } + + public void ClearTrack(int trackIndex) + { + if (trackIndex >= tracks.Count) return; + TrackEntry current = tracks.Items[trackIndex]; + if (current == null) return; + + current.OnEnd(this, trackIndex); + if (End != null) End(this, trackIndex); + + tracks.Items[trackIndex] = null; + } + + private TrackEntry ExpandToIndex(int index) + { + if (index < tracks.Count) return tracks.Items[index]; + while (index >= tracks.Count) + tracks.Add(null); + return null; + } + + private void SetCurrent(int index, TrackEntry entry) + { + TrackEntry current = ExpandToIndex(index); + if (current != null) + { + TrackEntry previous = current.previous; + current.previous = null; + + current.OnEnd(this, index); + if (End != null) End(this, index); + + entry.mixDuration = data.GetMix(current.animation, entry.animation); + if (entry.mixDuration > 0) + { + entry.mixTime = 0; + // If a mix is in progress, mix from the closest animation. + if (previous != null && current.mixTime / current.mixDuration < 0.5f) + entry.previous = previous; + else + entry.previous = current; + } + } + + tracks.Items[index] = entry; + + entry.OnStart(this, index); + if (Start != null) Start(this, index); + } + + public TrackEntry SetAnimation(int trackIndex, String animationName, bool loop) + { + Animation animation = data.skeletonData.FindAnimation(animationName); + if (animation == null) throw new ArgumentException("Animation not found: " + animationName); + return SetAnimation(trackIndex, animation, loop); + } + + /// Set the current animation. Any queued animations are cleared. + public TrackEntry SetAnimation(int trackIndex, Animation animation, bool loop) + { + if (animation == null) throw new ArgumentException("animation cannot be null."); + TrackEntry entry = new TrackEntry(); + entry.animation = animation; + entry.loop = loop; + entry.time = 0; + entry.endTime = animation.Duration; + SetCurrent(trackIndex, entry); + return entry; + } + + public TrackEntry AddAnimation(int trackIndex, String animationName, bool loop, float delay) + { + Animation animation = data.skeletonData.FindAnimation(animationName); + if (animation == null) throw new ArgumentException("Animation not found: " + animationName); + return AddAnimation(trackIndex, animation, loop, delay); + } + + /// Adds an animation to be played delay seconds after the current or last queued animation. + /// May be <= 0 to use duration of previous animation minus any mix duration plus the negative delay. + public TrackEntry AddAnimation(int trackIndex, Animation animation, bool loop, float delay) + { + if (animation == null) throw new ArgumentException("animation cannot be null."); + TrackEntry entry = new TrackEntry(); + entry.animation = animation; + entry.loop = loop; + entry.time = 0; + entry.endTime = animation.Duration; + + TrackEntry last = ExpandToIndex(trackIndex); + if (last != null) + { + while (last.next != null) + last = last.next; + last.next = entry; + } + else + tracks.Items[trackIndex] = entry; + + if (delay <= 0) + { + if (last != null) + delay += last.endTime - data.GetMix(last.animation, animation); + else + delay = 0; + } + entry.delay = delay; + + return entry; + } + + /// May be null. + public TrackEntry GetCurrent(int trackIndex) + { + if (trackIndex >= tracks.Count) return null; + return tracks.Items[trackIndex]; + } + + override public String ToString() + { + StringBuilder buffer = new StringBuilder(); + for (int i = 0, n = tracks.Count; i < n; i++) + { + TrackEntry entry = tracks.Items[i]; + if (entry == null) continue; + if (buffer.Length > 0) buffer.Append(", "); + buffer.Append(entry.ToString()); + } + if (buffer.Length == 0) return ""; + return buffer.ToString(); + } + } + + public class TrackEntry + { + internal TrackEntry next, previous; + internal Animation animation; + internal bool loop; + internal float delay, time, lastTime = -1, endTime, timeScale = 1; + internal float mixTime, mixDuration, mix = 1; + + public Animation Animation { get { return animation; } } + public float Delay { get { return delay; } set { delay = value; } } + public float Time { get { return time; } set { time = value; } } + public float LastTime { get { return lastTime; } set { lastTime = value; } } + public float EndTime { get { return endTime; } set { endTime = value; } } + public float TimeScale { get { return timeScale; } set { timeScale = value; } } + public float Mix { get { return mix; } set { mix = value; } } + public bool Loop { get { return loop; } set { loop = value; } } + + public event AnimationState.StartEndDelegate Start; + public event AnimationState.StartEndDelegate End; + public event AnimationState.EventDelegate Event; + public event AnimationState.CompleteDelegate Complete; + + internal void OnStart(AnimationState state, int index) + { + if (Start != null) Start(state, index); + } + + internal void OnEnd(AnimationState state, int index) + { + if (End != null) End(state, index); + } + + internal void OnEvent(AnimationState state, int index, Event e) + { + if (Event != null) Event(state, index, e); + } + + internal void OnComplete(AnimationState state, int index, int loopCount) + { + if (Complete != null) Complete(state, index, loopCount); + } + + override public String ToString() + { + return animation == null ? "" : animation.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/AnimationStateData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/AnimationStateData.cs index bd2a48c..2240a64 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/AnimationStateData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/AnimationStateData.cs @@ -32,65 +32,76 @@ using System; using System.Collections.Generic; -namespace Spine3_2_xx { - public class AnimationStateData { - internal SkeletonData skeletonData; - private Dictionary animationToMixTime = new Dictionary(AnimationPairComparer.Instance); - internal float defaultMix; +namespace Spine3_2_xx +{ + public class AnimationStateData + { + internal SkeletonData skeletonData; + private Dictionary animationToMixTime = new Dictionary(AnimationPairComparer.Instance); + internal float defaultMix; - public SkeletonData SkeletonData { get { return skeletonData; } } - public float DefaultMix { get { return defaultMix; } set { defaultMix = value; } } + public SkeletonData SkeletonData { get { return skeletonData; } } + public float DefaultMix { get { return defaultMix; } set { defaultMix = value; } } - public AnimationStateData (SkeletonData skeletonData) { - this.skeletonData = skeletonData; - } + public AnimationStateData(SkeletonData skeletonData) + { + this.skeletonData = skeletonData; + } - public void SetMix (String fromName, String toName, float duration) { - Animation from = skeletonData.FindAnimation(fromName); - if (from == null) throw new ArgumentException("Animation not found: " + fromName); - Animation to = skeletonData.FindAnimation(toName); - if (to == null) throw new ArgumentException("Animation not found: " + toName); - SetMix(from, to, duration); - } + public void SetMix(String fromName, String toName, float duration) + { + Animation from = skeletonData.FindAnimation(fromName); + if (from == null) throw new ArgumentException("Animation not found: " + fromName); + Animation to = skeletonData.FindAnimation(toName); + if (to == null) throw new ArgumentException("Animation not found: " + toName); + SetMix(from, to, duration); + } - public void SetMix (Animation from, Animation to, float duration) { - if (from == null) throw new ArgumentNullException("from cannot be null."); - if (to == null) throw new ArgumentNullException("to cannot be null."); - AnimationPair key = new AnimationPair(from, to); - animationToMixTime.Remove(key); - animationToMixTime.Add(key, duration); - } + public void SetMix(Animation from, Animation to, float duration) + { + if (from == null) throw new ArgumentNullException("from cannot be null."); + if (to == null) throw new ArgumentNullException("to cannot be null."); + AnimationPair key = new AnimationPair(from, to); + animationToMixTime.Remove(key); + animationToMixTime.Add(key, duration); + } - public float GetMix (Animation from, Animation to) { - AnimationPair key = new AnimationPair(from, to); - float duration; - if (animationToMixTime.TryGetValue(key, out duration)) return duration; - return defaultMix; - } + public float GetMix(Animation from, Animation to) + { + AnimationPair key = new AnimationPair(from, to); + float duration; + if (animationToMixTime.TryGetValue(key, out duration)) return duration; + return defaultMix; + } - struct AnimationPair { - public readonly Animation a1; - public readonly Animation a2; + struct AnimationPair + { + public readonly Animation a1; + public readonly Animation a2; - public AnimationPair (Animation a1, Animation a2) { - this.a1 = a1; - this.a2 = a2; - } - } + public AnimationPair(Animation a1, Animation a2) + { + this.a1 = a1; + this.a2 = a2; + } + } - // Avoids boxing in the dictionary. - class AnimationPairComparer : IEqualityComparer { - internal static readonly AnimationPairComparer Instance = new AnimationPairComparer(); + // Avoids boxing in the dictionary. + class AnimationPairComparer : IEqualityComparer + { + internal static readonly AnimationPairComparer Instance = new AnimationPairComparer(); - bool IEqualityComparer.Equals (AnimationPair x, AnimationPair y) { - return ReferenceEquals(x.a1, y.a1) && ReferenceEquals(x.a2, y.a2); - } + bool IEqualityComparer.Equals(AnimationPair x, AnimationPair y) + { + return ReferenceEquals(x.a1, y.a1) && ReferenceEquals(x.a2, y.a2); + } - int IEqualityComparer.GetHashCode (AnimationPair obj) { - // from Tuple.CombineHashCodes // return (((h1 << 5) + h1) ^ h2); - int h1 = obj.a1.GetHashCode(); - return (((h1 << 5) + h1) ^ obj.a2.GetHashCode()); - } - } - } + int IEqualityComparer.GetHashCode(AnimationPair obj) + { + // from Tuple.CombineHashCodes // return (((h1 << 5) + h1) ^ h2); + int h1 = obj.a1.GetHashCode(); + return (((h1 << 5) + h1) ^ obj.a2.GetHashCode()); + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Atlas.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Atlas.cs index be950cd..e065f3e 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Atlas.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Atlas.cs @@ -32,21 +32,22 @@ using System; using System.Collections.Generic; using System.IO; -using System.Reflection; #if WINDOWS_STOREAPP using System.Threading.Tasks; using Windows.Storage; #endif -namespace Spine3_2_xx { - public class Atlas { - List pages = new List(); - List regions = new List(); - TextureLoader textureLoader; +namespace Spine3_2_xx +{ + public class Atlas + { + List pages = new List(); + List regions = new List(); + TextureLoader textureLoader; - #if !(UNITY_5 || UNITY_4 || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1) // !UNITY - #if WINDOWS_STOREAPP +#if !(UNITY_5 || UNITY_4 || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1) // !UNITY +#if WINDOWS_STOREAPP private async Task ReadFile(string path, TextureLoader textureLoader) { var folder = Windows.ApplicationModel.Package.Current.InstalledLocation; var file = await folder.GetFileAsync(path).AsTask().ConfigureAwait(false); @@ -62,233 +63,264 @@ private async Task ReadFile(string path, TextureLoader textureLoader) { public Atlas(String path, TextureLoader textureLoader) { this.ReadFile(path, textureLoader).Wait(); } - #else +#else - public Atlas (String path, TextureLoader textureLoader) { + public Atlas(String path, TextureLoader textureLoader) + { - #if WINDOWS_PHONE +#if WINDOWS_PHONE Stream stream = Microsoft.Xna.Framework.TitleContainer.OpenStream(path); using (StreamReader reader = new StreamReader(stream)) { - #else - using (StreamReader reader = new StreamReader(path)) { - #endif // WINDOWS_PHONE - - try { - Load(reader, Path.GetDirectoryName(path), textureLoader); - } catch (Exception ex) { - throw new Exception("Error reading atlas file: " + path, ex); - } - - } - } - #endif // WINDOWS_STOREAPP - - #endif // !(UNITY) - - public Atlas (TextReader reader, String dir, TextureLoader textureLoader) { - Load(reader, dir, textureLoader); - } - - public Atlas (List pages, List regions) { - this.pages = pages; - this.regions = regions; - this.textureLoader = null; - } - - private void Load (TextReader reader, String imagesDir, TextureLoader textureLoader) { - if (textureLoader == null) throw new ArgumentNullException("textureLoader cannot be null."); - this.textureLoader = textureLoader; - - String[] tuple = new String[4]; - AtlasPage page = null; - while (true) { - String line = reader.ReadLine(); - if (line == null) break; - if (line.Trim().Length == 0) - page = null; - else if (page == null) { - page = new AtlasPage(); - page.name = line; - - if (ReadTuple(reader, tuple) == 2) { // size is only optional for an atlas packed with an old TexturePacker. - page.width = int.Parse(tuple[0]); - page.height = int.Parse(tuple[1]); - ReadTuple(reader, tuple); - } - page.format = (Format)Enum.Parse(typeof(Format), tuple[0], false); - - ReadTuple(reader, tuple); - page.minFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[0], false); - page.magFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[1], false); - - String direction = ReadValue(reader); - page.uWrap = TextureWrap.ClampToEdge; - page.vWrap = TextureWrap.ClampToEdge; - if (direction == "x") - page.uWrap = TextureWrap.Repeat; - else if (direction == "y") - page.vWrap = TextureWrap.Repeat; - else if (direction == "xy") - page.uWrap = page.vWrap = TextureWrap.Repeat; - - textureLoader.Load(page, Path.Combine(imagesDir, line)); - - pages.Add(page); - - } else { - AtlasRegion region = new AtlasRegion(); - region.name = line; - region.page = page; - - region.rotate = Boolean.Parse(ReadValue(reader)); - - ReadTuple(reader, tuple); - int x = int.Parse(tuple[0]); - int y = int.Parse(tuple[1]); - - ReadTuple(reader, tuple); - int width = int.Parse(tuple[0]); - int height = int.Parse(tuple[1]); - - region.u = x / (float)page.width; - region.v = y / (float)page.height; - if (region.rotate) { - region.u2 = (x + height) / (float)page.width; - region.v2 = (y + width) / (float)page.height; - } else { - region.u2 = (x + width) / (float)page.width; - region.v2 = (y + height) / (float)page.height; - } - region.x = x; - region.y = y; - region.width = Math.Abs(width); - region.height = Math.Abs(height); - - if (ReadTuple(reader, tuple) == 4) { // split is optional - region.splits = new int[] {int.Parse(tuple[0]), int.Parse(tuple[1]), - int.Parse(tuple[2]), int.Parse(tuple[3])}; - - if (ReadTuple(reader, tuple) == 4) { // pad is optional, but only present with splits - region.pads = new int[] {int.Parse(tuple[0]), int.Parse(tuple[1]), - int.Parse(tuple[2]), int.Parse(tuple[3])}; - - ReadTuple(reader, tuple); - } - } - - region.originalWidth = int.Parse(tuple[0]); - region.originalHeight = int.Parse(tuple[1]); - - ReadTuple(reader, tuple); - region.offsetX = int.Parse(tuple[0]); - region.offsetY = int.Parse(tuple[1]); - - region.index = int.Parse(ReadValue(reader)); - - regions.Add(region); - } - } - } - - static String ReadValue (TextReader reader) { - String line = reader.ReadLine(); - int colon = line.IndexOf(':'); - if (colon == -1) throw new Exception("Invalid line: " + line); - return line.Substring(colon + 1).Trim(); - } - - /// Returns the number of tuple values read (1, 2 or 4). - static int ReadTuple (TextReader reader, String[] tuple) { - String line = reader.ReadLine(); - int colon = line.IndexOf(':'); - if (colon == -1) throw new Exception("Invalid line: " + line); - int i = 0, lastMatch = colon + 1; - for (; i < 3; i++) { - int comma = line.IndexOf(',', lastMatch); - if (comma == -1) break; - tuple[i] = line.Substring(lastMatch, comma - lastMatch).Trim(); - lastMatch = comma + 1; - } - tuple[i] = line.Substring(lastMatch).Trim(); - return i + 1; - } - - public void FlipV () { - for (int i = 0, n = regions.Count; i < n; i++) { - AtlasRegion region = regions[i]; - region.v = 1 - region.v; - region.v2 = 1 - region.v2; - } - } - - /// 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. - /// The region, or null. - public AtlasRegion FindRegion (String name) { - for (int i = 0, n = regions.Count; i < n; i++) - if (regions[i].name == name) return regions[i]; - return null; - } - - public void Dispose () { - if (textureLoader == null) return; - for (int i = 0, n = pages.Count; i < n; i++) - textureLoader.Unload(pages[i].rendererObject); - } - } - - public enum Format { - Alpha, - Intensity, - LuminanceAlpha, - RGB565, - RGBA4444, - RGB888, - RGBA8888 - } - - public enum TextureFilter { - Nearest, - Linear, - MipMap, - MipMapNearestNearest, - MipMapLinearNearest, - MipMapNearestLinear, - MipMapLinearLinear - } - - public enum TextureWrap { - MirroredRepeat, - ClampToEdge, - Repeat - } - - public class AtlasPage { - public String name; - public Format format; - public TextureFilter minFilter; - public TextureFilter magFilter; - public TextureWrap uWrap; - public TextureWrap vWrap; - public Object rendererObject; - public int width, height; - } - - public class AtlasRegion { - public AtlasPage page; - public String name; - public int x, y, width, height; - public float u, v, u2, v2; - public float offsetX, offsetY; - public int originalWidth, originalHeight; - public int index; - public bool rotate; - public int[] splits; - public int[] pads; - } - - public interface TextureLoader { - void Load (AtlasPage page, String path); - void Unload (Object texture); - } +#else + using (StreamReader reader = new StreamReader(path)) + { +#endif // WINDOWS_PHONE + + try + { + Load(reader, Path.GetDirectoryName(path), textureLoader); + } + catch (Exception ex) + { + throw new Exception("Error reading atlas file: " + path, ex); + } + + } + } +#endif // WINDOWS_STOREAPP + +#endif // !(UNITY) + + public Atlas(TextReader reader, String dir, TextureLoader textureLoader) + { + Load(reader, dir, textureLoader); + } + + public Atlas(List pages, List regions) + { + this.pages = pages; + this.regions = regions; + this.textureLoader = null; + } + + private void Load(TextReader reader, String imagesDir, TextureLoader textureLoader) + { + if (textureLoader == null) throw new ArgumentNullException("textureLoader cannot be null."); + this.textureLoader = textureLoader; + + String[] tuple = new String[4]; + AtlasPage page = null; + while (true) + { + String line = reader.ReadLine(); + if (line == null) break; + if (line.Trim().Length == 0) + page = null; + else if (page == null) + { + page = new AtlasPage(); + page.name = line; + + if (ReadTuple(reader, tuple) == 2) + { // size is only optional for an atlas packed with an old TexturePacker. + page.width = int.Parse(tuple[0]); + page.height = int.Parse(tuple[1]); + ReadTuple(reader, tuple); + } + page.format = (Format)Enum.Parse(typeof(Format), tuple[0], false); + + ReadTuple(reader, tuple); + page.minFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[0], false); + page.magFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[1], false); + + String direction = ReadValue(reader); + page.uWrap = TextureWrap.ClampToEdge; + page.vWrap = TextureWrap.ClampToEdge; + if (direction == "x") + page.uWrap = TextureWrap.Repeat; + else if (direction == "y") + page.vWrap = TextureWrap.Repeat; + else if (direction == "xy") + page.uWrap = page.vWrap = TextureWrap.Repeat; + + textureLoader.Load(page, Path.Combine(imagesDir, line)); + + pages.Add(page); + + } + else + { + AtlasRegion region = new AtlasRegion(); + region.name = line; + region.page = page; + + region.rotate = Boolean.Parse(ReadValue(reader)); + + ReadTuple(reader, tuple); + int x = int.Parse(tuple[0]); + int y = int.Parse(tuple[1]); + + ReadTuple(reader, tuple); + int width = int.Parse(tuple[0]); + int height = int.Parse(tuple[1]); + + region.u = x / (float)page.width; + region.v = y / (float)page.height; + if (region.rotate) + { + region.u2 = (x + height) / (float)page.width; + region.v2 = (y + width) / (float)page.height; + } + else + { + region.u2 = (x + width) / (float)page.width; + region.v2 = (y + height) / (float)page.height; + } + region.x = x; + region.y = y; + region.width = Math.Abs(width); + region.height = Math.Abs(height); + + if (ReadTuple(reader, tuple) == 4) + { // split is optional + region.splits = new int[] {int.Parse(tuple[0]), int.Parse(tuple[1]), + int.Parse(tuple[2]), int.Parse(tuple[3])}; + + if (ReadTuple(reader, tuple) == 4) + { // pad is optional, but only present with splits + region.pads = new int[] {int.Parse(tuple[0]), int.Parse(tuple[1]), + int.Parse(tuple[2]), int.Parse(tuple[3])}; + + ReadTuple(reader, tuple); + } + } + + region.originalWidth = int.Parse(tuple[0]); + region.originalHeight = int.Parse(tuple[1]); + + ReadTuple(reader, tuple); + region.offsetX = int.Parse(tuple[0]); + region.offsetY = int.Parse(tuple[1]); + + region.index = int.Parse(ReadValue(reader)); + + regions.Add(region); + } + } + } + + static String ReadValue(TextReader reader) + { + String line = reader.ReadLine(); + int colon = line.IndexOf(':'); + if (colon == -1) throw new Exception("Invalid line: " + line); + return line.Substring(colon + 1).Trim(); + } + + /// Returns the number of tuple values read (1, 2 or 4). + static int ReadTuple(TextReader reader, String[] tuple) + { + String line = reader.ReadLine(); + int colon = line.IndexOf(':'); + if (colon == -1) throw new Exception("Invalid line: " + line); + int i = 0, lastMatch = colon + 1; + for (; i < 3; i++) + { + int comma = line.IndexOf(',', lastMatch); + if (comma == -1) break; + tuple[i] = line.Substring(lastMatch, comma - lastMatch).Trim(); + lastMatch = comma + 1; + } + tuple[i] = line.Substring(lastMatch).Trim(); + return i + 1; + } + + public void FlipV() + { + for (int i = 0, n = regions.Count; i < n; i++) + { + AtlasRegion region = regions[i]; + region.v = 1 - region.v; + region.v2 = 1 - region.v2; + } + } + + /// 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. + /// The region, or null. + public AtlasRegion FindRegion(String name) + { + for (int i = 0, n = regions.Count; i < n; i++) + if (regions[i].name == name) return regions[i]; + return null; + } + + public void Dispose() + { + if (textureLoader == null) return; + for (int i = 0, n = pages.Count; i < n; i++) + textureLoader.Unload(pages[i].rendererObject); + } + } + + public enum Format + { + Alpha, + Intensity, + LuminanceAlpha, + RGB565, + RGBA4444, + RGB888, + RGBA8888 + } + + public enum TextureFilter + { + Nearest, + Linear, + MipMap, + MipMapNearestNearest, + MipMapLinearNearest, + MipMapNearestLinear, + MipMapLinearLinear + } + + public enum TextureWrap + { + MirroredRepeat, + ClampToEdge, + Repeat + } + + public class AtlasPage + { + public String name; + public Format format; + public TextureFilter minFilter; + public TextureFilter magFilter; + public TextureWrap uWrap; + public TextureWrap vWrap; + public Object rendererObject; + public int width, height; + } + + public class AtlasRegion + { + public AtlasPage page; + public String name; + public int x, y, width, height; + public float u, v, u2, v2; + public float offsetX, offsetY; + public int originalWidth, originalHeight; + public int index; + public bool rotate; + public int[] splits; + public int[] pads; + } + + public interface TextureLoader + { + void Load(AtlasPage page, String path); + void Unload(Object texture); + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Attachments/AtlasAttachmentLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Attachments/AtlasAttachmentLoader.cs index f06a292..0f53c2d 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Attachments/AtlasAttachmentLoader.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Attachments/AtlasAttachmentLoader.cs @@ -31,82 +31,91 @@ using System; -namespace Spine3_2_xx { - public class AtlasAttachmentLoader : AttachmentLoader { - private Atlas[] atlasArray; +namespace Spine3_2_xx +{ + public class AtlasAttachmentLoader : AttachmentLoader + { + private Atlas[] atlasArray; - public AtlasAttachmentLoader (params Atlas[] atlasArray) { - if (atlasArray == null) throw new ArgumentNullException("atlas array cannot be null."); - this.atlasArray = atlasArray; - } + public AtlasAttachmentLoader(params Atlas[] atlasArray) + { + if (atlasArray == null) throw new ArgumentNullException("atlas array cannot be null."); + this.atlasArray = atlasArray; + } - public RegionAttachment NewRegionAttachment (Skin skin, String name, String path) { - AtlasRegion region = FindRegion(path); - if (region == null) throw new Exception("Region not found in atlas: " + path + " (region attachment: " + name + ")"); - RegionAttachment attachment = new RegionAttachment(name); - attachment.RendererObject = region; - attachment.SetUVs(region.u, region.v, region.u2, region.v2, region.rotate); - attachment.regionOffsetX = region.offsetX; - attachment.regionOffsetY = region.offsetY; - attachment.regionWidth = region.width; - attachment.regionHeight = region.height; - attachment.regionOriginalWidth = region.originalWidth; - attachment.regionOriginalHeight = region.originalHeight; - return attachment; - } + public RegionAttachment NewRegionAttachment(Skin skin, String name, String path) + { + AtlasRegion region = FindRegion(path); + if (region == null) throw new Exception("Region not found in atlas: " + path + " (region attachment: " + name + ")"); + RegionAttachment attachment = new RegionAttachment(name); + attachment.RendererObject = region; + attachment.SetUVs(region.u, region.v, region.u2, region.v2, region.rotate); + attachment.regionOffsetX = region.offsetX; + attachment.regionOffsetY = region.offsetY; + attachment.regionWidth = region.width; + attachment.regionHeight = region.height; + attachment.regionOriginalWidth = region.originalWidth; + attachment.regionOriginalHeight = region.originalHeight; + return attachment; + } - public MeshAttachment NewMeshAttachment (Skin skin, String name, String path) { - AtlasRegion region = FindRegion(path); - if (region == null) throw new Exception("Region not found in atlas: " + path + " (mesh attachment: " + name + ")"); - MeshAttachment attachment = new MeshAttachment(name); - attachment.RendererObject = region; - attachment.RegionU = region.u; - attachment.RegionV = region.v; - attachment.RegionU2 = region.u2; - attachment.RegionV2 = region.v2; - attachment.RegionRotate = region.rotate; - attachment.regionOffsetX = region.offsetX; - attachment.regionOffsetY = region.offsetY; - attachment.regionWidth = region.width; - attachment.regionHeight = region.height; - attachment.regionOriginalWidth = region.originalWidth; - attachment.regionOriginalHeight = region.originalHeight; - return attachment; - } + public MeshAttachment NewMeshAttachment(Skin skin, String name, String path) + { + AtlasRegion region = FindRegion(path); + if (region == null) throw new Exception("Region not found in atlas: " + path + " (mesh attachment: " + name + ")"); + MeshAttachment attachment = new MeshAttachment(name); + attachment.RendererObject = region; + attachment.RegionU = region.u; + attachment.RegionV = region.v; + attachment.RegionU2 = region.u2; + attachment.RegionV2 = region.v2; + attachment.RegionRotate = region.rotate; + attachment.regionOffsetX = region.offsetX; + attachment.regionOffsetY = region.offsetY; + attachment.regionWidth = region.width; + attachment.regionHeight = region.height; + attachment.regionOriginalWidth = region.originalWidth; + attachment.regionOriginalHeight = region.originalHeight; + return attachment; + } - public WeightedMeshAttachment NewWeightedMeshAttachment (Skin skin, String name, String path) { - AtlasRegion region = FindRegion(path); - if (region == null) throw new Exception("Region not found in atlas: " + path + " (weighted mesh attachment: " + name + ")"); - WeightedMeshAttachment attachment = new WeightedMeshAttachment(name); - attachment.RendererObject = region; - attachment.RegionU = region.u; - attachment.RegionV = region.v; - attachment.RegionU2 = region.u2; - attachment.RegionV2 = region.v2; - attachment.RegionRotate = region.rotate; - attachment.regionOffsetX = region.offsetX; - attachment.regionOffsetY = region.offsetY; - attachment.regionWidth = region.width; - attachment.regionHeight = region.height; - attachment.regionOriginalWidth = region.originalWidth; - attachment.regionOriginalHeight = region.originalHeight; - return attachment; - } + public WeightedMeshAttachment NewWeightedMeshAttachment(Skin skin, String name, String path) + { + AtlasRegion region = FindRegion(path); + if (region == null) throw new Exception("Region not found in atlas: " + path + " (weighted mesh attachment: " + name + ")"); + WeightedMeshAttachment attachment = new WeightedMeshAttachment(name); + attachment.RendererObject = region; + attachment.RegionU = region.u; + attachment.RegionV = region.v; + attachment.RegionU2 = region.u2; + attachment.RegionV2 = region.v2; + attachment.RegionRotate = region.rotate; + attachment.regionOffsetX = region.offsetX; + attachment.regionOffsetY = region.offsetY; + attachment.regionWidth = region.width; + attachment.regionHeight = region.height; + attachment.regionOriginalWidth = region.originalWidth; + attachment.regionOriginalHeight = region.originalHeight; + return attachment; + } - public BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, String name) { - return new BoundingBoxAttachment(name); - } + public BoundingBoxAttachment NewBoundingBoxAttachment(Skin skin, String name) + { + return new BoundingBoxAttachment(name); + } - public AtlasRegion FindRegion (string name) { - AtlasRegion region; + public AtlasRegion FindRegion(string name) + { + AtlasRegion region; - for (int i = 0; i < atlasArray.Length; i++) { - region = atlasArray[i].FindRegion(name); - if (region != null) - return region; - } + for (int i = 0; i < atlasArray.Length; i++) + { + region = atlasArray[i].FindRegion(name); + if (region != null) + return region; + } - return null; - } - } + return null; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Attachments/Attachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Attachments/Attachment.cs index 388950c..c8d0104 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Attachments/Attachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Attachments/Attachment.cs @@ -31,17 +31,21 @@ using System; -namespace Spine3_2_xx { - abstract public class Attachment { - public String Name { get; private set; } +namespace Spine3_2_xx +{ + abstract public class Attachment + { + public String Name { get; private set; } - public Attachment (String name) { - if (name == null) throw new ArgumentNullException("name cannot be null."); - Name = name; - } + public Attachment(String name) + { + if (name == null) throw new ArgumentNullException("name cannot be null."); + Name = name; + } - override public String ToString () { - return Name; - } - } + override public String ToString() + { + return Name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Attachments/AttachmentLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Attachments/AttachmentLoader.cs index 93fb715..25da5d5 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Attachments/AttachmentLoader.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Attachments/AttachmentLoader.cs @@ -31,18 +31,20 @@ using System; -namespace Spine3_2_xx { - public interface AttachmentLoader { - /// May be null to not load any attachment. - RegionAttachment NewRegionAttachment (Skin skin, String name, String path); +namespace Spine3_2_xx +{ + public interface AttachmentLoader + { + /// May be null to not load any attachment. + RegionAttachment NewRegionAttachment(Skin skin, String name, String path); - /// May be null to not load any attachment. - MeshAttachment NewMeshAttachment (Skin skin, String name, String path); + /// May be null to not load any attachment. + MeshAttachment NewMeshAttachment(Skin skin, String name, String path); - /// May be null to not load any attachment. - WeightedMeshAttachment NewWeightedMeshAttachment (Skin skin, String name, String path); + /// May be null to not load any attachment. + WeightedMeshAttachment NewWeightedMeshAttachment(Skin skin, String name, String path); - /// May be null to not load any attachment. - BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, String name); - } + /// May be null to not load any attachment. + BoundingBoxAttachment NewBoundingBoxAttachment(Skin skin, String name); + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Attachments/AttachmentType.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Attachments/AttachmentType.cs index 3b5c727..746df71 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Attachments/AttachmentType.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Attachments/AttachmentType.cs @@ -29,8 +29,10 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine3_2_xx { - public enum AttachmentType { - region, boundingbox, mesh, weightedmesh, linkedmesh, weightedlinkedmesh - } +namespace Spine3_2_xx +{ + public enum AttachmentType + { + region, boundingbox, mesh, weightedmesh, linkedmesh, weightedlinkedmesh + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Attachments/BoundingBoxAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Attachments/BoundingBoxAttachment.cs index 276cb07..ea35272 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Attachments/BoundingBoxAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Attachments/BoundingBoxAttachment.cs @@ -29,33 +29,36 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; +namespace Spine3_2_xx +{ + /// Attachment that has a polygon for bounds checking. + public class BoundingBoxAttachment : Attachment + { + internal float[] vertices; -namespace Spine3_2_xx { - /// Attachment that has a polygon for bounds checking. - public class BoundingBoxAttachment : Attachment { - internal float[] vertices; + public float[] Vertices { get { return vertices; } set { vertices = value; } } - public float[] Vertices { get { return vertices; } set { vertices = value; } } + public BoundingBoxAttachment(string name) + : base(name) + { + } - public BoundingBoxAttachment (string name) - : base(name) { - } - - /// Must have at least the same length as this attachment's vertices. - public void ComputeWorldVertices (Bone bone, float[] worldVertices) { - float x = bone.skeleton.x + bone.worldX, y = bone.skeleton.y + bone.worldY; - float m00 = bone.a; - float m01 = bone.b; - float m10 = bone.c; - float m11 = bone.d; - float[] vertices = this.vertices; - for (int i = 0, n = vertices.Length; i < n; i += 2) { - float px = vertices[i]; - float py = vertices[i + 1]; - worldVertices[i] = px * m00 + py * m01 + x; - worldVertices[i + 1] = px * m10 + py * m11 + y; - } - } - } + /// Must have at least the same length as this attachment's vertices. + public void ComputeWorldVertices(Bone bone, float[] worldVertices) + { + float x = bone.skeleton.x + bone.worldX, y = bone.skeleton.y + bone.worldY; + float m00 = bone.a; + float m01 = bone.b; + float m10 = bone.c; + float m11 = bone.d; + float[] vertices = this.vertices; + for (int i = 0, n = vertices.Length; i < n; i += 2) + { + float px = vertices[i]; + float py = vertices[i + 1]; + worldVertices[i] = px * m00 + py * m01 + x; + worldVertices[i + 1] = px * m10 + py * m11 + y; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Attachments/IFfdAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Attachments/IFfdAttachment.cs index 8980cdb..b1216a2 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Attachments/IFfdAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Attachments/IFfdAttachment.cs @@ -29,8 +29,10 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine3_2_xx { - public interface IFfdAttachment { - bool ApplyFFD (Attachment sourceAttachment); - } +namespace Spine3_2_xx +{ + public interface IFfdAttachment + { + bool ApplyFFD(Attachment sourceAttachment); + } } \ No newline at end of file diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Attachments/MeshAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Attachments/MeshAttachment.cs index 0e84382..c6b156d 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Attachments/MeshAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Attachments/MeshAttachment.cs @@ -31,103 +31,118 @@ using System; -namespace Spine3_2_xx { - /// Attachment that displays a texture region using a mesh. - public class MeshAttachment : Attachment, IFfdAttachment { - internal float[] vertices, uvs, regionUVs; - internal int[] triangles; - internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; - internal float r = 1, g = 1, b = 1, a = 1; - internal MeshAttachment parentMesh; - internal bool inheritFFD; +namespace Spine3_2_xx +{ + /// Attachment that displays a texture region using a mesh. + public class MeshAttachment : Attachment, IFfdAttachment + { + internal float[] vertices, uvs, regionUVs; + internal int[] triangles; + internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; + internal float r = 1, g = 1, b = 1, a = 1; + internal MeshAttachment parentMesh; + internal bool inheritFFD; - public int HullLength { get; set; } - public float[] Vertices { get { return vertices; } set { vertices = value; } } - public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } } - public float[] UVs { get { return uvs; } set { uvs = value; } } - public int[] Triangles { get { return triangles; } set { triangles = value; } } + public int HullLength { get; set; } + public float[] Vertices { get { return vertices; } set { vertices = value; } } + public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } } + public float[] UVs { get { return uvs; } set { uvs = value; } } + public int[] Triangles { get { return triangles; } set { triangles = value; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } - public String Path { get; set; } - public Object RendererObject { get; set; } - public float RegionU { get; set; } - public float RegionV { get; set; } - public float RegionU2 { get; set; } - public float RegionV2 { get; set; } - public bool RegionRotate { get; set; } - public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } - public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. - public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } - public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. - public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } - public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. + public String Path { get; set; } + public Object RendererObject { get; set; } + public float RegionU { get; set; } + public float RegionV { get; set; } + public float RegionU2 { get; set; } + public float RegionV2 { get; set; } + public bool RegionRotate { get; set; } + public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } + public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. + public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } + public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. + public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } + public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. - public bool InheritFFD { get { return inheritFFD; } set { inheritFFD = value; } } + public bool InheritFFD { get { return inheritFFD; } set { inheritFFD = value; } } - public MeshAttachment ParentMesh { - get { return parentMesh; } - set { - parentMesh = value; - if (value != null) { - vertices = value.vertices; - regionUVs = value.regionUVs; - triangles = value.triangles; - HullLength = value.HullLength; - Edges = value.Edges; - Width = value.Width; - Height = value.Height; - } - } - } + public MeshAttachment ParentMesh + { + get { return parentMesh; } + set + { + parentMesh = value; + if (value != null) + { + vertices = value.vertices; + regionUVs = value.regionUVs; + triangles = value.triangles; + HullLength = value.HullLength; + Edges = value.Edges; + Width = value.Width; + Height = value.Height; + } + } + } - // Nonessential. - public int[] Edges { get; set; } - public float Width { get; set; } - public float Height { get; set; } + // Nonessential. + public int[] Edges { get; set; } + public float Width { get; set; } + public float Height { get; set; } - public MeshAttachment (string name) - : base(name) { - } + public MeshAttachment(string name) + : base(name) + { + } - public void UpdateUVs () { - float u = RegionU, v = RegionV, width = RegionU2 - RegionU, height = RegionV2 - RegionV; - float[] regionUVs = this.regionUVs; - if (this.uvs == null || this.uvs.Length != regionUVs.Length) this.uvs = new float[regionUVs.Length]; - float[] uvs = this.uvs; - if (RegionRotate) { - for (int i = 0, n = uvs.Length; i < n; i += 2) { - uvs[i] = u + regionUVs[i + 1] * width; - uvs[i + 1] = v + height - regionUVs[i] * height; - } - } else { - for (int i = 0, n = uvs.Length; i < n; i += 2) { - uvs[i] = u + regionUVs[i] * width; - uvs[i + 1] = v + regionUVs[i + 1] * height; - } - } - } + public void UpdateUVs() + { + float u = RegionU, v = RegionV, width = RegionU2 - RegionU, height = RegionV2 - RegionV; + float[] regionUVs = this.regionUVs; + if (this.uvs == null || this.uvs.Length != regionUVs.Length) this.uvs = new float[regionUVs.Length]; + float[] uvs = this.uvs; + if (RegionRotate) + { + for (int i = 0, n = uvs.Length; i < n; i += 2) + { + uvs[i] = u + regionUVs[i + 1] * width; + uvs[i + 1] = v + height - regionUVs[i] * height; + } + } + else + { + for (int i = 0, n = uvs.Length; i < n; i += 2) + { + uvs[i] = u + regionUVs[i] * width; + uvs[i + 1] = v + regionUVs[i + 1] * height; + } + } + } - public void ComputeWorldVertices (Slot slot, float[] worldVertices) { - Bone bone = slot.bone; - float x = bone.skeleton.x + bone.worldX, y = bone.skeleton.y + bone.worldY; - float m00 = bone.a, m01 = bone.b, m10 = bone.c, m11 = bone.d; - float[] vertices = this.vertices; - int verticesCount = vertices.Length; - if (slot.attachmentVerticesCount == verticesCount) vertices = slot.AttachmentVertices; - for (int i = 0; i < verticesCount; i += 2) { - float vx = vertices[i]; - float vy = vertices[i + 1]; - worldVertices[i] = vx * m00 + vy * m01 + x; - worldVertices[i + 1] = vx * m10 + vy * m11 + y; - } - } + public void ComputeWorldVertices(Slot slot, float[] worldVertices) + { + Bone bone = slot.bone; + float x = bone.skeleton.x + bone.worldX, y = bone.skeleton.y + bone.worldY; + float m00 = bone.a, m01 = bone.b, m10 = bone.c, m11 = bone.d; + float[] vertices = this.vertices; + int verticesCount = vertices.Length; + if (slot.attachmentVerticesCount == verticesCount) vertices = slot.AttachmentVertices; + for (int i = 0; i < verticesCount; i += 2) + { + float vx = vertices[i]; + float vy = vertices[i + 1]; + worldVertices[i] = vx * m00 + vy * m01 + x; + worldVertices[i + 1] = vx * m10 + vy * m11 + y; + } + } - public bool ApplyFFD (Attachment sourceAttachment) { - return this == sourceAttachment || (inheritFFD && parentMesh == sourceAttachment); - } - } + public bool ApplyFFD(Attachment sourceAttachment) + { + return this == sourceAttachment || (inheritFFD && parentMesh == sourceAttachment); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Attachments/RegionAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Attachments/RegionAttachment.cs index 4c8fe35..1754685 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Attachments/RegionAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Attachments/RegionAttachment.cs @@ -31,122 +31,131 @@ using System; -namespace Spine3_2_xx { - /// Attachment that displays a texture region. - public class RegionAttachment : Attachment { - public const int X1 = 0; - public const int Y1 = 1; - public const int X2 = 2; - public const int Y2 = 3; - public const int X3 = 4; - public const int Y3 = 5; - public const int X4 = 6; - public const int Y4 = 7; +namespace Spine3_2_xx +{ + /// Attachment that displays a texture region. + public class RegionAttachment : Attachment + { + public const int X1 = 0; + public const int Y1 = 1; + public const int X2 = 2; + public const int Y2 = 3; + public const int X3 = 4; + public const int Y3 = 5; + public const int X4 = 6; + public const int Y4 = 7; - internal float x, y, rotation, scaleX = 1, scaleY = 1, width, height; - internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; - internal float[] offset = new float[8], uvs = new float[8]; - internal float r = 1, g = 1, b = 1, a = 1; + internal float x, y, rotation, scaleX = 1, scaleY = 1, width, height; + internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; + internal float[] offset = new float[8], uvs = new float[8]; + internal float r = 1, g = 1, b = 1, a = 1; - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public float Rotation { get { return rotation; } set { rotation = value; } } - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - public float ScaleY { get { return scaleY; } set { scaleY = value; } } - public float Width { get { return width; } set { width = value; } } - public float Height { get { return height; } set { height = value; } } + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public float Rotation { get { return rotation; } set { rotation = value; } } + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + public float Width { get { return width; } set { width = value; } } + public float Height { get { return height; } set { height = value; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } - public String Path { get; set; } - public Object RendererObject { get; set; } - public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } - public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. - public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } - public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. - public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } - public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. + public String Path { get; set; } + public Object RendererObject { get; set; } + public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } + public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. + public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } + public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. + public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } + public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. - public float[] Offset { get { return offset; } } - public float[] UVs { get { return uvs; } } + public float[] Offset { get { return offset; } } + public float[] UVs { get { return uvs; } } - public RegionAttachment (string name) - : base(name) { - } + public RegionAttachment(string name) + : base(name) + { + } - public void SetUVs (float u, float v, float u2, float v2, bool rotate) { - float[] uvs = this.uvs; - if (rotate) { - uvs[X2] = u; - uvs[Y2] = v2; - uvs[X3] = u; - uvs[Y3] = v; - uvs[X4] = u2; - uvs[Y4] = v; - uvs[X1] = u2; - uvs[Y1] = v2; - } else { - uvs[X1] = u; - uvs[Y1] = v2; - uvs[X2] = u; - uvs[Y2] = v; - uvs[X3] = u2; - uvs[Y3] = v; - uvs[X4] = u2; - uvs[Y4] = v2; - } - } + public void SetUVs(float u, float v, float u2, float v2, bool rotate) + { + float[] uvs = this.uvs; + if (rotate) + { + uvs[X2] = u; + uvs[Y2] = v2; + uvs[X3] = u; + uvs[Y3] = v; + uvs[X4] = u2; + uvs[Y4] = v; + uvs[X1] = u2; + uvs[Y1] = v2; + } + else + { + uvs[X1] = u; + uvs[Y1] = v2; + uvs[X2] = u; + uvs[Y2] = v; + uvs[X3] = u2; + uvs[Y3] = v; + uvs[X4] = u2; + uvs[Y4] = v2; + } + } - public void UpdateOffset () { - float width = this.width; - float height = this.height; - float scaleX = this.scaleX; - float scaleY = this.scaleY; - float regionScaleX = width / regionOriginalWidth * scaleX; - float regionScaleY = height / regionOriginalHeight * scaleY; - float localX = -width / 2 * scaleX + regionOffsetX * regionScaleX; - float localY = -height / 2 * scaleY + regionOffsetY * regionScaleY; - float localX2 = localX + regionWidth * regionScaleX; - float localY2 = localY + regionHeight * regionScaleY; - float radians = rotation * (float)Math.PI / 180; - float cos = (float)Math.Cos(radians); - float sin = (float)Math.Sin(radians); - float x = this.x; - float y = this.y; - 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; - float[] offset = this.offset; - offset[X1] = localXCos - localYSin; - offset[Y1] = localYCos + localXSin; - offset[X2] = localXCos - localY2Sin; - offset[Y2] = localY2Cos + localXSin; - offset[X3] = localX2Cos - localY2Sin; - offset[Y3] = localY2Cos + localX2Sin; - offset[X4] = localX2Cos - localYSin; - offset[Y4] = localYCos + localX2Sin; - } + public void UpdateOffset() + { + float width = this.width; + float height = this.height; + float scaleX = this.scaleX; + float scaleY = this.scaleY; + float regionScaleX = width / regionOriginalWidth * scaleX; + float regionScaleY = height / regionOriginalHeight * scaleY; + float localX = -width / 2 * scaleX + regionOffsetX * regionScaleX; + float localY = -height / 2 * scaleY + regionOffsetY * regionScaleY; + float localX2 = localX + regionWidth * regionScaleX; + float localY2 = localY + regionHeight * regionScaleY; + float radians = rotation * (float)Math.PI / 180; + float cos = (float)Math.Cos(radians); + float sin = (float)Math.Sin(radians); + float x = this.x; + float y = this.y; + 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; + float[] offset = this.offset; + offset[X1] = localXCos - localYSin; + offset[Y1] = localYCos + localXSin; + offset[X2] = localXCos - localY2Sin; + offset[Y2] = localY2Cos + localXSin; + offset[X3] = localX2Cos - localY2Sin; + offset[Y3] = localY2Cos + localX2Sin; + offset[X4] = localX2Cos - localYSin; + offset[Y4] = localYCos + localX2Sin; + } - public void ComputeWorldVertices (Bone bone, float[] worldVertices) { - float x = bone.skeleton.x + bone.worldX, y = bone.skeleton.y + bone.worldY; - float m00 = bone.a, m01 = bone.b, m10 = bone.c, m11 = bone.d; - float[] offset = this.offset; - worldVertices[X1] = offset[X1] * m00 + offset[Y1] * m01 + x; - worldVertices[Y1] = offset[X1] * m10 + offset[Y1] * m11 + y; - worldVertices[X2] = offset[X2] * m00 + offset[Y2] * m01 + x; - worldVertices[Y2] = offset[X2] * m10 + offset[Y2] * m11 + y; - worldVertices[X3] = offset[X3] * m00 + offset[Y3] * m01 + x; - worldVertices[Y3] = offset[X3] * m10 + offset[Y3] * m11 + y; - worldVertices[X4] = offset[X4] * m00 + offset[Y4] * m01 + x; - worldVertices[Y4] = offset[X4] * m10 + offset[Y4] * m11 + y; - } - } + public void ComputeWorldVertices(Bone bone, float[] worldVertices) + { + float x = bone.skeleton.x + bone.worldX, y = bone.skeleton.y + bone.worldY; + float m00 = bone.a, m01 = bone.b, m10 = bone.c, m11 = bone.d; + float[] offset = this.offset; + worldVertices[X1] = offset[X1] * m00 + offset[Y1] * m01 + x; + worldVertices[Y1] = offset[X1] * m10 + offset[Y1] * m11 + y; + worldVertices[X2] = offset[X2] * m00 + offset[Y2] * m01 + x; + worldVertices[Y2] = offset[X2] * m10 + offset[Y2] * m11 + y; + worldVertices[X3] = offset[X3] * m00 + offset[Y3] * m01 + x; + worldVertices[Y3] = offset[X3] * m10 + offset[Y3] * m11 + y; + worldVertices[X4] = offset[X4] * m00 + offset[Y4] * m01 + x; + worldVertices[Y4] = offset[X4] * m10 + offset[Y4] * m11 + y; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Attachments/WeightedMeshAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Attachments/WeightedMeshAttachment.cs index 2b51d0e..bbf1bcd 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Attachments/WeightedMeshAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Attachments/WeightedMeshAttachment.cs @@ -30,129 +30,149 @@ *****************************************************************************/ using System; -using System.Collections.Generic; -namespace Spine3_2_xx { - /// Attachment that displays a texture region using a mesh which can be deformed by bones. - public class WeightedMeshAttachment : Attachment, IFfdAttachment { - internal int[] bones; - internal float[] weights, uvs, regionUVs; - internal int[] triangles; - internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; - internal float r = 1, g = 1, b = 1, a = 1; - internal WeightedMeshAttachment parentMesh; - internal bool inheritFFD; +namespace Spine3_2_xx +{ + /// Attachment that displays a texture region using a mesh which can be deformed by bones. + public class WeightedMeshAttachment : Attachment, IFfdAttachment + { + internal int[] bones; + internal float[] weights, uvs, regionUVs; + internal int[] triangles; + internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; + internal float r = 1, g = 1, b = 1, a = 1; + internal WeightedMeshAttachment parentMesh; + internal bool inheritFFD; - public int HullLength { get; set; } - public int[] Bones { get { return bones; } set { bones = value; } } - public float[] Weights { get { return weights; } set { weights = value; } } - public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } } - public float[] UVs { get { return uvs; } set { uvs = value; } } - public int[] Triangles { get { return triangles; } set { triangles = value; } } + public int HullLength { get; set; } + public int[] Bones { get { return bones; } set { bones = value; } } + public float[] Weights { get { return weights; } set { weights = value; } } + public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } } + public float[] UVs { get { return uvs; } set { uvs = value; } } + public int[] Triangles { get { return triangles; } set { triangles = value; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } - public String Path { get; set; } - public Object RendererObject { get; set; } - public float RegionU { get; set; } - public float RegionV { get; set; } - public float RegionU2 { get; set; } - public float RegionV2 { get; set; } - public bool RegionRotate { get; set; } - public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } - public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. - public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } - public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. - public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } - public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. + public String Path { get; set; } + public Object RendererObject { get; set; } + public float RegionU { get; set; } + public float RegionV { get; set; } + public float RegionU2 { get; set; } + public float RegionV2 { get; set; } + public bool RegionRotate { get; set; } + public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } + public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. + public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } + public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. + public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } + public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. - public bool InheritFFD { get { return inheritFFD; } set { inheritFFD = value; } } + public bool InheritFFD { get { return inheritFFD; } set { inheritFFD = value; } } - public WeightedMeshAttachment ParentMesh { - get { return parentMesh; } - set { - parentMesh = value; - if (value != null) { - bones = value.bones; - weights = value.weights; - regionUVs = value.regionUVs; - triangles = value.triangles; - HullLength = value.HullLength; - Edges = value.Edges; - Width = value.Width; - Height = value.Height; - } - } - } + public WeightedMeshAttachment ParentMesh + { + get { return parentMesh; } + set + { + parentMesh = value; + if (value != null) + { + bones = value.bones; + weights = value.weights; + regionUVs = value.regionUVs; + triangles = value.triangles; + HullLength = value.HullLength; + Edges = value.Edges; + Width = value.Width; + Height = value.Height; + } + } + } - // Nonessential. - public int[] Edges { get; set; } - public float Width { get; set; } - public float Height { get; set; } + // Nonessential. + public int[] Edges { get; set; } + public float Width { get; set; } + public float Height { get; set; } - public WeightedMeshAttachment (string name) - : base(name) { - } + public WeightedMeshAttachment(string name) + : base(name) + { + } - public void UpdateUVs () { - float u = RegionU, v = RegionV, width = RegionU2 - RegionU, height = RegionV2 - RegionV; - float[] regionUVs = this.regionUVs; - if (this.uvs == null || this.uvs.Length != regionUVs.Length) this.uvs = new float[regionUVs.Length]; - float[] uvs = this.uvs; - if (RegionRotate) { - for (int i = 0, n = uvs.Length; i < n; i += 2) { - uvs[i] = u + regionUVs[i + 1] * width; - uvs[i + 1] = v + height - regionUVs[i] * height; - } - } else { - for (int i = 0, n = uvs.Length; i < n; i += 2) { - uvs[i] = u + regionUVs[i] * width; - uvs[i + 1] = v + regionUVs[i + 1] * height; - } - } - } + public void UpdateUVs() + { + float u = RegionU, v = RegionV, width = RegionU2 - RegionU, height = RegionV2 - RegionV; + float[] regionUVs = this.regionUVs; + if (this.uvs == null || this.uvs.Length != regionUVs.Length) this.uvs = new float[regionUVs.Length]; + float[] uvs = this.uvs; + if (RegionRotate) + { + for (int i = 0, n = uvs.Length; i < n; i += 2) + { + uvs[i] = u + regionUVs[i + 1] * width; + uvs[i + 1] = v + height - regionUVs[i] * height; + } + } + else + { + for (int i = 0, n = uvs.Length; i < n; i += 2) + { + uvs[i] = u + regionUVs[i] * width; + uvs[i + 1] = v + regionUVs[i + 1] * height; + } + } + } - public void ComputeWorldVertices (Slot slot, float[] worldVertices) { - Skeleton skeleton = slot.bone.skeleton; - ExposedList skeletonBones = skeleton.bones; - float x = skeleton.x, y = skeleton.y; - float[] weights = this.weights; - int[] bones = this.bones; - if (slot.attachmentVerticesCount == 0) { - for (int w = 0, v = 0, b = 0, n = bones.Length; v < n; w += 2) { - float wx = 0, wy = 0; - int nn = bones[v++] + v; - for (; v < nn; v++, b += 3) { - Bone bone = skeletonBones.Items[bones[v]]; - float vx = weights[b], vy = weights[b + 1], weight = weights[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 + x; - worldVertices[w + 1] = wy + y; - } - } else { - float[] ffd = slot.attachmentVertices; - for (int w = 0, v = 0, b = 0, f = 0, n = bones.Length; v < n; w += 2) { - float wx = 0, wy = 0; - int nn = bones[v++] + v; - for (; v < nn; v++, b += 3, f += 2) { - Bone bone = skeletonBones.Items[bones[v]]; - float vx = weights[b] + ffd[f], vy = weights[b + 1] + ffd[f + 1], weight = weights[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 + x; - worldVertices[w + 1] = wy + y; - } - } - } + public void ComputeWorldVertices(Slot slot, float[] worldVertices) + { + Skeleton skeleton = slot.bone.skeleton; + ExposedList skeletonBones = skeleton.bones; + float x = skeleton.x, y = skeleton.y; + float[] weights = this.weights; + int[] bones = this.bones; + if (slot.attachmentVerticesCount == 0) + { + for (int w = 0, v = 0, b = 0, n = bones.Length; v < n; w += 2) + { + float wx = 0, wy = 0; + int nn = bones[v++] + v; + for (; v < nn; v++, b += 3) + { + Bone bone = skeletonBones.Items[bones[v]]; + float vx = weights[b], vy = weights[b + 1], weight = weights[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 + x; + worldVertices[w + 1] = wy + y; + } + } + else + { + float[] ffd = slot.attachmentVertices; + for (int w = 0, v = 0, b = 0, f = 0, n = bones.Length; v < n; w += 2) + { + float wx = 0, wy = 0; + int nn = bones[v++] + v; + for (; v < nn; v++, b += 3, f += 2) + { + Bone bone = skeletonBones.Items[bones[v]]; + float vx = weights[b] + ffd[f], vy = weights[b + 1] + ffd[f + 1], weight = weights[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 + x; + worldVertices[w + 1] = wy + y; + } + } + } - public bool ApplyFFD (Attachment sourceAttachment) { - return this == sourceAttachment || (inheritFFD && parentMesh == sourceAttachment); - } - } + public bool ApplyFFD(Attachment sourceAttachment) + { + return this == sourceAttachment || (inheritFFD && parentMesh == sourceAttachment); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/BlendMode.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/BlendMode.cs index 50e162a..7ee8077 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/BlendMode.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/BlendMode.cs @@ -29,8 +29,10 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine3_2_xx { - public enum BlendMode { - normal, additive, multiply, screen - } +namespace Spine3_2_xx +{ + public enum BlendMode + { + normal, additive, multiply, screen + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Bone.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Bone.cs index f692aa4..373b7f1 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Bone.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Bone.cs @@ -30,217 +30,241 @@ *****************************************************************************/ using System; -using System.Collections.Generic; - -namespace Spine3_2_xx { - public class Bone : IUpdatable { - static public bool yDown; - - internal BoneData data; - internal Skeleton skeleton; - internal Bone parent; - internal ExposedList children = new ExposedList(); - internal float x, y, rotation, scaleX, scaleY, shearX, shearY; - internal float appliedRotation, appliedScaleX, appliedScaleY; - - internal float a, b, worldX; - internal float c, d, worldY; - internal float worldSignX, worldSignY; - - public BoneData Data { get { return data; } } - public Skeleton Skeleton { get { return skeleton; } } - public Bone Parent { get { return parent; } } - public ExposedList Children { get { return children; } } - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public float Rotation { get { return rotation; } set { rotation = value; } } - /// The rotation, as calculated by any constraints. - public float AppliedRotation { get { return appliedRotation; } set { appliedRotation = value; } } - /// The scale X, as calculated by any constraints. - public float AppliedScaleX { get { return appliedScaleX; } set { appliedScaleX = value; } } - /// The scale Y, as calculated by any constraints. - public float AppliedScaleY { get { return appliedScaleY; } set { appliedScaleY = value; } } - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - public float ScaleY { get { return scaleY; } set { scaleY = value; } } - public float ShearX { get { return shearX; } set { shearX = value; } } - public float ShearY { get { return shearY; } set { shearY = value; } } - - public float A { get { return a; } } - public float B { get { return b; } } - public float C { get { return c; } } - public float D { get { return d; } } - public float WorldX { get { return worldX; } } - public float WorldY { get { return worldY; } } - public float WorldSignX { get { return worldSignX; } } - public float WorldSignY { get { return worldSignY; } } - public float WorldRotationX { get { return MathUtils.Atan2(c, a) * MathUtils.radDeg; } } - public float WorldRotationY { get { return MathUtils.Atan2(d, b) * MathUtils.radDeg; } } - public float WorldScaleX { get { return (float)Math.Sqrt(a * a + b * b) * worldSignX; } } - public float WorldScaleY { get { return (float)Math.Sqrt(c * c + d * d) * worldSignY; } } - - /// May be null. - public Bone (BoneData data, Skeleton skeleton, Bone parent) { - if (data == null) throw new ArgumentNullException("data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); - this.data = data; - this.skeleton = skeleton; - this.parent = parent; - SetToSetupPose(); - } - - /// Same as {@link #updateWorldTransform()}. This method exists for Bone to implement {@link Updatable}. - public void Update () { - UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); - } - - /// Computes the world SRT using the parent bone and this bone's local SRT. - public void UpdateWorldTransform () { - UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); - } - - /// Computes the world SRT using the parent bone and the specified local SRT. - public void UpdateWorldTransform (float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY) { - appliedRotation = rotation; - appliedScaleX = scaleX; - appliedScaleY = scaleY; - - float rotationY = rotation + 90 + shearY; - float la = MathUtils.CosDeg(rotation + shearX) * scaleX, lb = MathUtils.CosDeg(rotationY) * scaleY; - float lc = MathUtils.SinDeg(rotation + shearX) * scaleX, ld = MathUtils.SinDeg(rotationY) * scaleY; - - Bone parent = this.parent; - if (parent == null) { // Root bone. - Skeleton skeleton = this.skeleton; - if (skeleton.flipX) { - x = -x; - la = -la; - lb = -lb; - } - if (skeleton.flipY != yDown) { - y = -y; - lc = -lc; - ld = -ld; - } - a = la; - b = lb; - c = lc; - d = ld; - worldX = x; - worldY = y; - worldSignX = Math.Sign(scaleX); - worldSignY = Math.Sign(scaleY); - return; - } - - float 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; - worldSignX = parent.worldSignX * Math.Sign(scaleX); - worldSignY = parent.worldSignY * Math.Sign(scaleY); - - if (data.inheritRotation && data.inheritScale) { - a = pa * la + pb * lc; - b = pa * lb + pb * ld; - c = pc * la + pd * lc; - d = pc * lb + pd * ld; - } else { - if (data.inheritRotation) { // No scale inheritance. - pa = 1; - pb = 0; - pc = 0; - pd = 1; - do { - float cos = MathUtils.CosDeg(parent.appliedRotation), sin = MathUtils.SinDeg(parent.appliedRotation); - float temp = pa * cos + pb * sin; - pb = pa * -sin + pb * cos; - pa = temp; - temp = pc * cos + pd * sin; - pd = pc * -sin + pd * cos; - pc = temp; - - if (!parent.data.inheritRotation) break; - parent = parent.parent; - } while (parent != null); - a = pa * la + pb * lc; - b = pa * lb + pb * ld; - c = pc * la + pd * lc; - d = pc * lb + pd * ld; - } else if (data.inheritScale) { // No rotation inheritance. - pa = 1; - pb = 0; - pc = 0; - pd = 1; - do { - float r = parent.appliedRotation, cos = MathUtils.CosDeg(r), sin = MathUtils.SinDeg(r); - float psx = parent.appliedScaleX, psy = parent.appliedScaleY; - float za = cos * psx, zb = -sin * psy, zc = sin * psx, zd = cos * psy; - float temp = pa * za + pb * zc; - pb = pa * zb + pb * zd; - pa = temp; - temp = pc * za + pd * zc; - pd = pc * zb + pd * zd; - pc = temp; - - if (psx < 0) r = -r; - cos = MathUtils.CosDeg(-r); - sin = MathUtils.SinDeg(-r); - temp = pa * cos + pb * sin; - pb = pa * -sin + pb * cos; - pa = temp; - temp = pc * cos + pd * sin; - pd = pc * -sin + pd * cos; - pc = temp; - - if (!parent.data.inheritScale) break; - parent = parent.parent; - } while (parent != null); - a = pa * la + pb * lc; - b = pa * lb + pb * ld; - c = pc * la + pd * lc; - d = pc * lb + pd * ld; - } else { - a = la; - b = lb; - c = lc; - d = ld; - } - if (skeleton.flipX) { - a = -a; - b = -b; - } - if (skeleton.flipY != yDown) { - c = -c; - d = -d; - } - } - } - - public void SetToSetupPose () { - BoneData data = this.data; - x = data.x; - y = data.y; - rotation = data.rotation; - scaleX = data.scaleX; - scaleY = data.scaleY; - shearX = data.shearX; - shearY = data.shearY; - } - - public void WorldToLocal (float worldX, float worldY, out float localX, out float localY) { - float x = worldX - this.worldX, y = worldY - this.worldY; - float a = this.a, b = this.b, c = this.c, d = this.d; - float invDet = 1 / (a * d - b * c); - localX = (x * d * invDet - y * b * invDet); - localY = (y * a * invDet - x * c * invDet); - } - - public void LocalToWorld (float localX, float localY, out float worldX, out float worldY) { - worldX = localX * a + localY * b + this.worldX; - worldY = localX * c + localY * d + this.worldY; - } - - override public String ToString () { - return data.name; - } - } + +namespace Spine3_2_xx +{ + public class Bone : IUpdatable + { + static public bool yDown; + + internal BoneData data; + internal Skeleton skeleton; + internal Bone parent; + internal ExposedList children = new ExposedList(); + internal float x, y, rotation, scaleX, scaleY, shearX, shearY; + internal float appliedRotation, appliedScaleX, appliedScaleY; + + internal float a, b, worldX; + internal float c, d, worldY; + internal float worldSignX, worldSignY; + + public BoneData Data { get { return data; } } + public Skeleton Skeleton { get { return skeleton; } } + public Bone Parent { get { return parent; } } + public ExposedList Children { get { return children; } } + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public float Rotation { get { return rotation; } set { rotation = value; } } + /// The rotation, as calculated by any constraints. + public float AppliedRotation { get { return appliedRotation; } set { appliedRotation = value; } } + /// The scale X, as calculated by any constraints. + public float AppliedScaleX { get { return appliedScaleX; } set { appliedScaleX = value; } } + /// The scale Y, as calculated by any constraints. + public float AppliedScaleY { get { return appliedScaleY; } set { appliedScaleY = value; } } + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + public float ShearX { get { return shearX; } set { shearX = value; } } + public float ShearY { get { return shearY; } set { shearY = value; } } + + public float A { get { return a; } } + public float B { get { return b; } } + public float C { get { return c; } } + public float D { get { return d; } } + public float WorldX { get { return worldX; } } + public float WorldY { get { return worldY; } } + public float WorldSignX { get { return worldSignX; } } + public float WorldSignY { get { return worldSignY; } } + public float WorldRotationX { get { return MathUtils.Atan2(c, a) * MathUtils.radDeg; } } + public float WorldRotationY { get { return MathUtils.Atan2(d, b) * MathUtils.radDeg; } } + public float WorldScaleX { get { return (float)Math.Sqrt(a * a + b * b) * worldSignX; } } + public float WorldScaleY { get { return (float)Math.Sqrt(c * c + d * d) * worldSignY; } } + + /// May be null. + public Bone(BoneData data, Skeleton skeleton, Bone parent) + { + if (data == null) throw new ArgumentNullException("data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); + this.data = data; + this.skeleton = skeleton; + this.parent = parent; + SetToSetupPose(); + } + + /// Same as {@link #updateWorldTransform()}. This method exists for Bone to implement {@link Updatable}. + public void Update() + { + UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); + } + + /// Computes the world SRT using the parent bone and this bone's local SRT. + public void UpdateWorldTransform() + { + UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); + } + + /// Computes the world SRT using the parent bone and the specified local SRT. + public void UpdateWorldTransform(float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY) + { + appliedRotation = rotation; + appliedScaleX = scaleX; + appliedScaleY = scaleY; + + float rotationY = rotation + 90 + shearY; + float la = MathUtils.CosDeg(rotation + shearX) * scaleX, lb = MathUtils.CosDeg(rotationY) * scaleY; + float lc = MathUtils.SinDeg(rotation + shearX) * scaleX, ld = MathUtils.SinDeg(rotationY) * scaleY; + + Bone parent = this.parent; + if (parent == null) + { // Root bone. + Skeleton skeleton = this.skeleton; + if (skeleton.flipX) + { + x = -x; + la = -la; + lb = -lb; + } + if (skeleton.flipY != yDown) + { + y = -y; + lc = -lc; + ld = -ld; + } + a = la; + b = lb; + c = lc; + d = ld; + worldX = x; + worldY = y; + worldSignX = Math.Sign(scaleX); + worldSignY = Math.Sign(scaleY); + return; + } + + float 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; + worldSignX = parent.worldSignX * Math.Sign(scaleX); + worldSignY = parent.worldSignY * Math.Sign(scaleY); + + if (data.inheritRotation && data.inheritScale) + { + a = pa * la + pb * lc; + b = pa * lb + pb * ld; + c = pc * la + pd * lc; + d = pc * lb + pd * ld; + } + else + { + if (data.inheritRotation) + { // No scale inheritance. + pa = 1; + pb = 0; + pc = 0; + pd = 1; + do + { + float cos = MathUtils.CosDeg(parent.appliedRotation), sin = MathUtils.SinDeg(parent.appliedRotation); + float temp = pa * cos + pb * sin; + pb = pa * -sin + pb * cos; + pa = temp; + temp = pc * cos + pd * sin; + pd = pc * -sin + pd * cos; + pc = temp; + + if (!parent.data.inheritRotation) break; + parent = parent.parent; + } while (parent != null); + a = pa * la + pb * lc; + b = pa * lb + pb * ld; + c = pc * la + pd * lc; + d = pc * lb + pd * ld; + } + else if (data.inheritScale) + { // No rotation inheritance. + pa = 1; + pb = 0; + pc = 0; + pd = 1; + do + { + float r = parent.appliedRotation, cos = MathUtils.CosDeg(r), sin = MathUtils.SinDeg(r); + float psx = parent.appliedScaleX, psy = parent.appliedScaleY; + float za = cos * psx, zb = -sin * psy, zc = sin * psx, zd = cos * psy; + float temp = pa * za + pb * zc; + pb = pa * zb + pb * zd; + pa = temp; + temp = pc * za + pd * zc; + pd = pc * zb + pd * zd; + pc = temp; + + if (psx < 0) r = -r; + cos = MathUtils.CosDeg(-r); + sin = MathUtils.SinDeg(-r); + temp = pa * cos + pb * sin; + pb = pa * -sin + pb * cos; + pa = temp; + temp = pc * cos + pd * sin; + pd = pc * -sin + pd * cos; + pc = temp; + + if (!parent.data.inheritScale) break; + parent = parent.parent; + } while (parent != null); + a = pa * la + pb * lc; + b = pa * lb + pb * ld; + c = pc * la + pd * lc; + d = pc * lb + pd * ld; + } + else + { + a = la; + b = lb; + c = lc; + d = ld; + } + if (skeleton.flipX) + { + a = -a; + b = -b; + } + if (skeleton.flipY != yDown) + { + c = -c; + d = -d; + } + } + } + + public void SetToSetupPose() + { + BoneData data = this.data; + x = data.x; + y = data.y; + rotation = data.rotation; + scaleX = data.scaleX; + scaleY = data.scaleY; + shearX = data.shearX; + shearY = data.shearY; + } + + public void WorldToLocal(float worldX, float worldY, out float localX, out float localY) + { + float x = worldX - this.worldX, y = worldY - this.worldY; + float a = this.a, b = this.b, c = this.c, d = this.d; + float invDet = 1 / (a * d - b * c); + localX = (x * d * invDet - y * b * invDet); + localY = (y * a * invDet - x * c * invDet); + } + + public void LocalToWorld(float localX, float localY, out float worldX, out float worldY) + { + worldX = localX * a + localY * b + this.worldX; + worldY = localX * c + localY * d + this.worldY; + } + + override public String ToString() + { + return data.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/BoneData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/BoneData.cs index ccdba02..85fd05a 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/BoneData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/BoneData.cs @@ -31,36 +31,40 @@ using System; -namespace Spine3_2_xx { - public class BoneData { - internal BoneData parent; - internal String name; - internal float length, x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY; - internal bool inheritScale = true, inheritRotation = true; +namespace Spine3_2_xx +{ + public class BoneData + { + internal BoneData parent; + internal String name; + internal float length, x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY; + internal bool inheritScale = true, inheritRotation = true; - /// May be null. - public BoneData Parent { get { return parent; } } - public String Name { get { return name; } } - public float Length { get { return length; } set { length = value; } } - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public float Rotation { get { return rotation; } set { rotation = value; } } - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - public float ScaleY { get { return scaleY; } set { scaleY = value; } } - public float ShearX { get { return shearX; } set { shearX = value; } } - public float ShearY { get { return shearY; } set { shearY = value; } } - public bool InheritScale { get { return inheritScale; } set { inheritScale = value; } } - public bool InheritRotation { get { return inheritRotation; } set { inheritRotation = value; } } + /// May be null. + public BoneData Parent { get { return parent; } } + public String Name { get { return name; } } + public float Length { get { return length; } set { length = value; } } + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public float Rotation { get { return rotation; } set { rotation = value; } } + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + public float ShearX { get { return shearX; } set { shearX = value; } } + public float ShearY { get { return shearY; } set { shearY = value; } } + public bool InheritScale { get { return inheritScale; } set { inheritScale = value; } } + public bool InheritRotation { get { return inheritRotation; } set { inheritRotation = value; } } - /// May be null. - public BoneData (String name, BoneData parent) { - if (name == null) throw new ArgumentNullException("name cannot be null."); - this.name = name; - this.parent = parent; - } + /// May be null. + public BoneData(String name, BoneData parent) + { + if (name == null) throw new ArgumentNullException("name cannot be null."); + this.name = name; + this.parent = parent; + } - override public String ToString () { - return name; - } - } + override public String ToString() + { + return name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Event.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Event.cs index d1a5d2d..bfa71b8 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Event.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Event.cs @@ -31,21 +31,25 @@ using System; -namespace Spine3_2_xx { - public class Event { - public EventData Data { get; private set; } - public int Int { get; set; } - public float Float { get; set; } - public String String { get; set; } - public float Time { get; private set; } +namespace Spine3_2_xx +{ + public class Event + { + public EventData Data { get; private set; } + public int Int { get; set; } + public float Float { get; set; } + public String String { get; set; } + public float Time { get; private set; } - public Event (float time, EventData data) { - Time = time; - Data = data; - } + public Event(float time, EventData data) + { + Time = time; + Data = data; + } - override public String ToString () { - return Data.Name; - } - } + override public String ToString() + { + return Data.Name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/EventData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/EventData.cs index b0ff1e8..bbc4086 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/EventData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/EventData.cs @@ -31,22 +31,26 @@ using System; -namespace Spine3_2_xx { - public class EventData { - internal String name; +namespace Spine3_2_xx +{ + public class EventData + { + internal String name; - public String Name { get { return name; } } - public int Int { get; set; } - public float Float { get; set; } - public String String { get; set; } + public String Name { get { return name; } } + public int Int { get; set; } + public float Float { get; set; } + public String String { get; set; } - public EventData (String name) { - if (name == null) throw new ArgumentNullException("name cannot be null."); - this.name = name; - } + public EventData(String name) + { + if (name == null) throw new ArgumentNullException("name cannot be null."); + this.name = name; + } - override public String ToString () { - return Name; - } - } + override public String ToString() + { + return Name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/ExposedList.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/ExposedList.cs index c0e98ff..9f58f86 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/ExposedList.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/ExposedList.cs @@ -35,549 +35,638 @@ using System.Collections.Generic; using System.Diagnostics; -namespace Spine3_2_xx { - [Serializable] - [DebuggerDisplay("Count={Count}")] - public class ExposedList : IEnumerable { - public T[] Items; - public int Count; - private const int DefaultCapacity = 4; - private static readonly T[] EmptyArray = new T[0]; - private int version; - - public ExposedList () { - Items = EmptyArray; - } - - public ExposedList (IEnumerable collection) { - CheckCollection(collection); - - // initialize to needed size (if determinable) - ICollection c = collection as ICollection; - if (c == null) { - Items = EmptyArray; - AddEnumerable(collection); - } else { - Items = new T[c.Count]; - AddCollection(c); - } - } - - public ExposedList (int capacity) { - if (capacity < 0) - throw new ArgumentOutOfRangeException("capacity"); - Items = new T[capacity]; - } - - internal ExposedList (T[] data, int size) { - Items = data; - Count = size; - } - - public void Add (T item) { - // If we check to see if we need to grow before trying to grow - // we can speed things up by 25% - if (Count == Items.Length) - GrowIfNeeded(1); - Items[Count++] = item; - version++; - } - - public void GrowIfNeeded (int newCount) { - int minimumSize = Count + newCount; - if (minimumSize > Items.Length) - Capacity = Math.Max(Math.Max(Capacity * 2, DefaultCapacity), minimumSize); - } - - private void CheckRange (int idx, int count) { - if (idx < 0) - throw new ArgumentOutOfRangeException("index"); - - if (count < 0) - throw new ArgumentOutOfRangeException("count"); - - if ((uint)idx + (uint)count > (uint)Count) - throw new ArgumentException("index and count exceed length of list"); - } - - private void AddCollection (ICollection collection) { - int collectionCount = collection.Count; - if (collectionCount == 0) - return; - - GrowIfNeeded(collectionCount); - collection.CopyTo(Items, Count); - Count += collectionCount; - } - - private void AddEnumerable (IEnumerable enumerable) { - foreach (T t in enumerable) { - Add(t); - } - } - - public void AddRange (IEnumerable collection) { - CheckCollection(collection); - - ICollection c = collection as ICollection; - if (c != null) - AddCollection(c); - else - AddEnumerable(collection); - version++; - } - - public int BinarySearch (T item) { - return Array.BinarySearch(Items, 0, Count, item); - } - - public int BinarySearch (T item, IComparer comparer) { - return Array.BinarySearch(Items, 0, Count, item, comparer); - } - - public int BinarySearch (int index, int count, T item, IComparer comparer) { - CheckRange(index, count); - return Array.BinarySearch(Items, index, count, item, comparer); - } - - public void Clear (bool clearArray = true) { - if (clearArray) - Array.Clear(Items, 0, Items.Length); - - Count = 0; - version++; - } - - public bool Contains (T item) { - return Array.IndexOf(Items, item, 0, Count) != -1; - } - - public ExposedList ConvertAll (Converter converter) { - if (converter == null) - throw new ArgumentNullException("converter"); - ExposedList u = new ExposedList(Count); - for (int i = 0; i < Count; i++) - u.Items[i] = converter(Items[i]); - - u.Count = Count; - return u; - } - - public void CopyTo (T[] array) { - Array.Copy(Items, 0, array, 0, Count); - } - - public void CopyTo (T[] array, int arrayIndex) { - Array.Copy(Items, 0, array, arrayIndex, Count); - } - - public void CopyTo (int index, T[] array, int arrayIndex, int count) { - CheckRange(index, count); - Array.Copy(Items, index, array, arrayIndex, count); - } - - public bool Exists (Predicate match) { - CheckMatch(match); - return GetIndex(0, Count, match) != -1; - } - - public T Find (Predicate match) { - CheckMatch(match); - int i = GetIndex(0, Count, match); - return (i != -1) ? Items[i] : default(T); - } - - private static void CheckMatch (Predicate match) { - if (match == null) - throw new ArgumentNullException("match"); - } - - public ExposedList FindAll (Predicate match) { - CheckMatch(match); - return FindAllList(match); - } - - private ExposedList FindAllList (Predicate match) { - ExposedList results = new ExposedList(); - for (int i = 0; i < Count; i++) - if (match(Items[i])) - results.Add(Items[i]); - - return results; - } - - public int FindIndex (Predicate match) { - CheckMatch(match); - return GetIndex(0, Count, match); - } - - public int FindIndex (int startIndex, Predicate match) { - CheckMatch(match); - CheckIndex(startIndex); - return GetIndex(startIndex, Count - startIndex, match); - } - - public int FindIndex (int startIndex, int count, Predicate match) { - CheckMatch(match); - CheckRange(startIndex, count); - return GetIndex(startIndex, count, match); - } - - private int GetIndex (int startIndex, int count, Predicate match) { - int end = startIndex + count; - for (int i = startIndex; i < end; i++) - if (match(Items[i])) - return i; - - return -1; - } - - public T FindLast (Predicate match) { - CheckMatch(match); - int i = GetLastIndex(0, Count, match); - return i == -1 ? default(T) : Items[i]; - } - - public int FindLastIndex (Predicate match) { - CheckMatch(match); - return GetLastIndex(0, Count, match); - } - - public int FindLastIndex (int startIndex, Predicate match) { - CheckMatch(match); - CheckIndex(startIndex); - return GetLastIndex(0, startIndex + 1, match); - } - - public int FindLastIndex (int startIndex, int count, Predicate match) { - CheckMatch(match); - int start = startIndex - count + 1; - CheckRange(start, count); - return GetLastIndex(start, count, match); - } - - private int GetLastIndex (int startIndex, int count, Predicate match) { - // unlike FindLastIndex, takes regular params for search range - for (int i = startIndex + count; i != startIndex; ) - if (match(Items[--i])) - return i; - return -1; - } - - public void ForEach (Action action) { - if (action == null) - throw new ArgumentNullException("action"); - for (int i = 0; i < Count; i++) - action(Items[i]); - } - - public Enumerator GetEnumerator () { - return new Enumerator(this); - } - - public ExposedList GetRange (int index, int count) { - CheckRange(index, count); - T[] tmpArray = new T[count]; - Array.Copy(Items, index, tmpArray, 0, count); - return new ExposedList(tmpArray, count); - } - - public int IndexOf (T item) { - return Array.IndexOf(Items, item, 0, Count); - } - - public int IndexOf (T item, int index) { - CheckIndex(index); - return Array.IndexOf(Items, item, index, Count - index); - } - - public int IndexOf (T item, int index, int count) { - if (index < 0) - throw new ArgumentOutOfRangeException("index"); - - if (count < 0) - throw new ArgumentOutOfRangeException("count"); - - if ((uint)index + (uint)count > (uint)Count) - throw new ArgumentOutOfRangeException("index and count exceed length of list"); - - return Array.IndexOf(Items, item, index, count); - } - - private void Shift (int start, int delta) { - if (delta < 0) - start -= delta; - - if (start < Count) - Array.Copy(Items, start, Items, start + delta, Count - start); - - Count += delta; - - if (delta < 0) - Array.Clear(Items, Count, -delta); - } - - private void CheckIndex (int index) { - if (index < 0 || (uint)index > (uint)Count) - throw new ArgumentOutOfRangeException("index"); - } - - public void Insert (int index, T item) { - CheckIndex(index); - if (Count == Items.Length) - GrowIfNeeded(1); - Shift(index, 1); - Items[index] = item; - version++; - } - - private void CheckCollection (IEnumerable collection) { - if (collection == null) - throw new ArgumentNullException("collection"); - } - - public void InsertRange (int index, IEnumerable collection) { - CheckCollection(collection); - CheckIndex(index); - if (collection == this) { - T[] buffer = new T[Count]; - CopyTo(buffer, 0); - GrowIfNeeded(Count); - Shift(index, buffer.Length); - Array.Copy(buffer, 0, Items, index, buffer.Length); - } else { - ICollection c = collection as ICollection; - if (c != null) - InsertCollection(index, c); - else - InsertEnumeration(index, collection); - } - version++; - } - - private void InsertCollection (int index, ICollection collection) { - int collectionCount = collection.Count; - GrowIfNeeded(collectionCount); - - Shift(index, collectionCount); - collection.CopyTo(Items, index); - } - - private void InsertEnumeration (int index, IEnumerable enumerable) { - foreach (T t in enumerable) - Insert(index++, t); - } - - public int LastIndexOf (T item) { - return Array.LastIndexOf(Items, item, Count - 1, Count); - } - - public int LastIndexOf (T item, int index) { - CheckIndex(index); - return Array.LastIndexOf(Items, item, index, index + 1); - } - - public int LastIndexOf (T item, int index, int count) { - if (index < 0) - throw new ArgumentOutOfRangeException("index", index, "index is negative"); - - if (count < 0) - throw new ArgumentOutOfRangeException("count", count, "count is negative"); - - if (index - count + 1 < 0) - throw new ArgumentOutOfRangeException("count", count, "count is too large"); - - return Array.LastIndexOf(Items, item, index, count); - } - - public bool Remove (T item) { - int loc = IndexOf(item); - if (loc != -1) - RemoveAt(loc); - - return loc != -1; - } - - public int RemoveAll (Predicate match) { - CheckMatch(match); - int i = 0; - int j = 0; - - // Find the first item to remove - for (i = 0; i < Count; i++) - if (match(Items[i])) - break; - - if (i == Count) - return 0; - - version++; - - // Remove any additional items - for (j = i + 1; j < Count; j++) { - if (!match(Items[j])) - Items[i++] = Items[j]; - } - if (j - i > 0) - Array.Clear(Items, i, j - i); - - Count = i; - return (j - i); - } - - public void RemoveAt (int index) { - if (index < 0 || (uint)index >= (uint)Count) - throw new ArgumentOutOfRangeException("index"); - Shift(index, -1); - Array.Clear(Items, Count, 1); - version++; - } - - public void RemoveRange (int index, int count) { - CheckRange(index, count); - if (count > 0) { - Shift(index, -count); - Array.Clear(Items, Count, count); - version++; - } - } - - public void Reverse () { - Array.Reverse(Items, 0, Count); - version++; - } - - public void Reverse (int index, int count) { - CheckRange(index, count); - Array.Reverse(Items, index, count); - version++; - } - - public void Sort () { - Array.Sort(Items, 0, Count, Comparer.Default); - version++; - } - - public void Sort (IComparer comparer) { - Array.Sort(Items, 0, Count, comparer); - version++; - } - - public void Sort (Comparison comparison) { - Array.Sort(Items, comparison); - version++; - } - - public void Sort (int index, int count, IComparer comparer) { - CheckRange(index, count); - Array.Sort(Items, index, count, comparer); - version++; - } - - public T[] ToArray () { - T[] t = new T[Count]; - Array.Copy(Items, t, Count); - - return t; - } - - public void TrimExcess () { - Capacity = Count; - } - - public bool TrueForAll (Predicate match) { - CheckMatch(match); - - for (int i = 0; i < Count; i++) - if (!match(Items[i])) - return false; - - return true; - } - - public int Capacity { - get { - return Items.Length; - } - set { - if ((uint)value < (uint)Count) - throw new ArgumentOutOfRangeException(); - - Array.Resize(ref Items, value); - } - } - - #region Interface implementations. - - IEnumerator IEnumerable.GetEnumerator () { - return GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator () { - return GetEnumerator(); - } - - #endregion - - [Serializable] - public struct Enumerator : IEnumerator, IDisposable { - private ExposedList l; - private int next; - private int ver; - private T current; - - internal Enumerator (ExposedList l) - : this() { - this.l = l; - ver = l.version; - } - - public void Dispose () { - l = null; - } - - private void VerifyState () { - if (l == null) - throw new ObjectDisposedException(GetType().FullName); - if (ver != l.version) - throw new InvalidOperationException( - "Collection was modified; enumeration operation may not execute."); - } - - public bool MoveNext () { - VerifyState(); - - if (next < 0) - return false; - - if (next < l.Count) { - current = l.Items[next++]; - return true; - } - - next = -1; - return false; - } - - public T Current { - get { - return current; - } - } - - void IEnumerator.Reset () { - VerifyState(); - next = 0; - } - - object IEnumerator.Current { - get { - VerifyState(); - if (next <= 0) - throw new InvalidOperationException(); - return current; - } - } - } - } +namespace Spine3_2_xx +{ + [Serializable] + [DebuggerDisplay("Count={Count}")] + public class ExposedList : IEnumerable + { + public T[] Items; + public int Count; + private const int DefaultCapacity = 4; + private static readonly T[] EmptyArray = new T[0]; + private int version; + + public ExposedList() + { + Items = EmptyArray; + } + + public ExposedList(IEnumerable collection) + { + CheckCollection(collection); + + // initialize to needed size (if determinable) + ICollection c = collection as ICollection; + if (c == null) + { + Items = EmptyArray; + AddEnumerable(collection); + } + else + { + Items = new T[c.Count]; + AddCollection(c); + } + } + + public ExposedList(int capacity) + { + if (capacity < 0) + throw new ArgumentOutOfRangeException("capacity"); + Items = new T[capacity]; + } + + internal ExposedList(T[] data, int size) + { + Items = data; + Count = size; + } + + public void Add(T item) + { + // If we check to see if we need to grow before trying to grow + // we can speed things up by 25% + if (Count == Items.Length) + GrowIfNeeded(1); + Items[Count++] = item; + version++; + } + + public void GrowIfNeeded(int newCount) + { + int minimumSize = Count + newCount; + if (minimumSize > Items.Length) + Capacity = Math.Max(Math.Max(Capacity * 2, DefaultCapacity), minimumSize); + } + + private void CheckRange(int idx, int count) + { + if (idx < 0) + throw new ArgumentOutOfRangeException("index"); + + if (count < 0) + throw new ArgumentOutOfRangeException("count"); + + if ((uint)idx + (uint)count > (uint)Count) + throw new ArgumentException("index and count exceed length of list"); + } + + private void AddCollection(ICollection collection) + { + int collectionCount = collection.Count; + if (collectionCount == 0) + return; + + GrowIfNeeded(collectionCount); + collection.CopyTo(Items, Count); + Count += collectionCount; + } + + private void AddEnumerable(IEnumerable enumerable) + { + foreach (T t in enumerable) + { + Add(t); + } + } + + public void AddRange(IEnumerable collection) + { + CheckCollection(collection); + + ICollection c = collection as ICollection; + if (c != null) + AddCollection(c); + else + AddEnumerable(collection); + version++; + } + + public int BinarySearch(T item) + { + return Array.BinarySearch(Items, 0, Count, item); + } + + public int BinarySearch(T item, IComparer comparer) + { + return Array.BinarySearch(Items, 0, Count, item, comparer); + } + + public int BinarySearch(int index, int count, T item, IComparer comparer) + { + CheckRange(index, count); + return Array.BinarySearch(Items, index, count, item, comparer); + } + + public void Clear(bool clearArray = true) + { + if (clearArray) + Array.Clear(Items, 0, Items.Length); + + Count = 0; + version++; + } + + public bool Contains(T item) + { + return Array.IndexOf(Items, item, 0, Count) != -1; + } + + public ExposedList ConvertAll(Converter converter) + { + if (converter == null) + throw new ArgumentNullException("converter"); + ExposedList u = new ExposedList(Count); + for (int i = 0; i < Count; i++) + u.Items[i] = converter(Items[i]); + + u.Count = Count; + return u; + } + + public void CopyTo(T[] array) + { + Array.Copy(Items, 0, array, 0, Count); + } + + public void CopyTo(T[] array, int arrayIndex) + { + Array.Copy(Items, 0, array, arrayIndex, Count); + } + + public void CopyTo(int index, T[] array, int arrayIndex, int count) + { + CheckRange(index, count); + Array.Copy(Items, index, array, arrayIndex, count); + } + + public bool Exists(Predicate match) + { + CheckMatch(match); + return GetIndex(0, Count, match) != -1; + } + + public T Find(Predicate match) + { + CheckMatch(match); + int i = GetIndex(0, Count, match); + return (i != -1) ? Items[i] : default(T); + } + + private static void CheckMatch(Predicate match) + { + if (match == null) + throw new ArgumentNullException("match"); + } + + public ExposedList FindAll(Predicate match) + { + CheckMatch(match); + return FindAllList(match); + } + + private ExposedList FindAllList(Predicate match) + { + ExposedList results = new ExposedList(); + for (int i = 0; i < Count; i++) + if (match(Items[i])) + results.Add(Items[i]); + + return results; + } + + public int FindIndex(Predicate match) + { + CheckMatch(match); + return GetIndex(0, Count, match); + } + + public int FindIndex(int startIndex, Predicate match) + { + CheckMatch(match); + CheckIndex(startIndex); + return GetIndex(startIndex, Count - startIndex, match); + } + + public int FindIndex(int startIndex, int count, Predicate match) + { + CheckMatch(match); + CheckRange(startIndex, count); + return GetIndex(startIndex, count, match); + } + + private int GetIndex(int startIndex, int count, Predicate match) + { + int end = startIndex + count; + for (int i = startIndex; i < end; i++) + if (match(Items[i])) + return i; + + return -1; + } + + public T FindLast(Predicate match) + { + CheckMatch(match); + int i = GetLastIndex(0, Count, match); + return i == -1 ? default(T) : Items[i]; + } + + public int FindLastIndex(Predicate match) + { + CheckMatch(match); + return GetLastIndex(0, Count, match); + } + + public int FindLastIndex(int startIndex, Predicate match) + { + CheckMatch(match); + CheckIndex(startIndex); + return GetLastIndex(0, startIndex + 1, match); + } + + public int FindLastIndex(int startIndex, int count, Predicate match) + { + CheckMatch(match); + int start = startIndex - count + 1; + CheckRange(start, count); + return GetLastIndex(start, count, match); + } + + private int GetLastIndex(int startIndex, int count, Predicate match) + { + // unlike FindLastIndex, takes regular params for search range + for (int i = startIndex + count; i != startIndex;) + if (match(Items[--i])) + return i; + return -1; + } + + public void ForEach(Action action) + { + if (action == null) + throw new ArgumentNullException("action"); + for (int i = 0; i < Count; i++) + action(Items[i]); + } + + public Enumerator GetEnumerator() + { + return new Enumerator(this); + } + + public ExposedList GetRange(int index, int count) + { + CheckRange(index, count); + T[] tmpArray = new T[count]; + Array.Copy(Items, index, tmpArray, 0, count); + return new ExposedList(tmpArray, count); + } + + public int IndexOf(T item) + { + return Array.IndexOf(Items, item, 0, Count); + } + + public int IndexOf(T item, int index) + { + CheckIndex(index); + return Array.IndexOf(Items, item, index, Count - index); + } + + public int IndexOf(T item, int index, int count) + { + if (index < 0) + throw new ArgumentOutOfRangeException("index"); + + if (count < 0) + throw new ArgumentOutOfRangeException("count"); + + if ((uint)index + (uint)count > (uint)Count) + throw new ArgumentOutOfRangeException("index and count exceed length of list"); + + return Array.IndexOf(Items, item, index, count); + } + + private void Shift(int start, int delta) + { + if (delta < 0) + start -= delta; + + if (start < Count) + Array.Copy(Items, start, Items, start + delta, Count - start); + + Count += delta; + + if (delta < 0) + Array.Clear(Items, Count, -delta); + } + + private void CheckIndex(int index) + { + if (index < 0 || (uint)index > (uint)Count) + throw new ArgumentOutOfRangeException("index"); + } + + public void Insert(int index, T item) + { + CheckIndex(index); + if (Count == Items.Length) + GrowIfNeeded(1); + Shift(index, 1); + Items[index] = item; + version++; + } + + private void CheckCollection(IEnumerable collection) + { + if (collection == null) + throw new ArgumentNullException("collection"); + } + + public void InsertRange(int index, IEnumerable collection) + { + CheckCollection(collection); + CheckIndex(index); + if (collection == this) + { + T[] buffer = new T[Count]; + CopyTo(buffer, 0); + GrowIfNeeded(Count); + Shift(index, buffer.Length); + Array.Copy(buffer, 0, Items, index, buffer.Length); + } + else + { + ICollection c = collection as ICollection; + if (c != null) + InsertCollection(index, c); + else + InsertEnumeration(index, collection); + } + version++; + } + + private void InsertCollection(int index, ICollection collection) + { + int collectionCount = collection.Count; + GrowIfNeeded(collectionCount); + + Shift(index, collectionCount); + collection.CopyTo(Items, index); + } + + private void InsertEnumeration(int index, IEnumerable enumerable) + { + foreach (T t in enumerable) + Insert(index++, t); + } + + public int LastIndexOf(T item) + { + return Array.LastIndexOf(Items, item, Count - 1, Count); + } + + public int LastIndexOf(T item, int index) + { + CheckIndex(index); + return Array.LastIndexOf(Items, item, index, index + 1); + } + + public int LastIndexOf(T item, int index, int count) + { + if (index < 0) + throw new ArgumentOutOfRangeException("index", index, "index is negative"); + + if (count < 0) + throw new ArgumentOutOfRangeException("count", count, "count is negative"); + + if (index - count + 1 < 0) + throw new ArgumentOutOfRangeException("count", count, "count is too large"); + + return Array.LastIndexOf(Items, item, index, count); + } + + public bool Remove(T item) + { + int loc = IndexOf(item); + if (loc != -1) + RemoveAt(loc); + + return loc != -1; + } + + public int RemoveAll(Predicate match) + { + CheckMatch(match); + int i = 0; + int j = 0; + + // Find the first item to remove + for (i = 0; i < Count; i++) + if (match(Items[i])) + break; + + if (i == Count) + return 0; + + version++; + + // Remove any additional items + for (j = i + 1; j < Count; j++) + { + if (!match(Items[j])) + Items[i++] = Items[j]; + } + if (j - i > 0) + Array.Clear(Items, i, j - i); + + Count = i; + return (j - i); + } + + public void RemoveAt(int index) + { + if (index < 0 || (uint)index >= (uint)Count) + throw new ArgumentOutOfRangeException("index"); + Shift(index, -1); + Array.Clear(Items, Count, 1); + version++; + } + + public void RemoveRange(int index, int count) + { + CheckRange(index, count); + if (count > 0) + { + Shift(index, -count); + Array.Clear(Items, Count, count); + version++; + } + } + + public void Reverse() + { + Array.Reverse(Items, 0, Count); + version++; + } + + public void Reverse(int index, int count) + { + CheckRange(index, count); + Array.Reverse(Items, index, count); + version++; + } + + public void Sort() + { + Array.Sort(Items, 0, Count, Comparer.Default); + version++; + } + + public void Sort(IComparer comparer) + { + Array.Sort(Items, 0, Count, comparer); + version++; + } + + public void Sort(Comparison comparison) + { + Array.Sort(Items, comparison); + version++; + } + + public void Sort(int index, int count, IComparer comparer) + { + CheckRange(index, count); + Array.Sort(Items, index, count, comparer); + version++; + } + + public T[] ToArray() + { + T[] t = new T[Count]; + Array.Copy(Items, t, Count); + + return t; + } + + public void TrimExcess() + { + Capacity = Count; + } + + public bool TrueForAll(Predicate match) + { + CheckMatch(match); + + for (int i = 0; i < Count; i++) + if (!match(Items[i])) + return false; + + return true; + } + + public int Capacity + { + get + { + return Items.Length; + } + set + { + if ((uint)value < (uint)Count) + throw new ArgumentOutOfRangeException(); + + Array.Resize(ref Items, value); + } + } + + #region Interface implementations. + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion + + [Serializable] + public struct Enumerator : IEnumerator, IDisposable + { + private ExposedList l; + private int next; + private int ver; + private T current; + + internal Enumerator(ExposedList l) + : this() + { + this.l = l; + ver = l.version; + } + + public void Dispose() + { + l = null; + } + + private void VerifyState() + { + if (l == null) + throw new ObjectDisposedException(GetType().FullName); + if (ver != l.version) + throw new InvalidOperationException( + "Collection was modified; enumeration operation may not execute."); + } + + public bool MoveNext() + { + VerifyState(); + + if (next < 0) + return false; + + if (next < l.Count) + { + current = l.Items[next++]; + return true; + } + + next = -1; + return false; + } + + public T Current + { + get + { + return current; + } + } + + void IEnumerator.Reset() + { + VerifyState(); + next = 0; + } + + object IEnumerator.Current + { + get + { + VerifyState(); + if (next <= 0) + throw new InvalidOperationException(); + return current; + } + } + } + } } \ No newline at end of file diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/IUpdatable.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/IUpdatable.cs index 29af12b..cbceefe 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/IUpdatable.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/IUpdatable.cs @@ -29,10 +29,10 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; - -namespace Spine3_2_xx { - public interface IUpdatable { - void Update (); - } +namespace Spine3_2_xx +{ + public interface IUpdatable + { + void Update(); + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/IkConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/IkConstraint.cs index ff82bb2..ee9dd5d 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/IkConstraint.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/IkConstraint.cs @@ -30,189 +30,216 @@ *****************************************************************************/ using System; -using System.Collections.Generic; -namespace Spine3_2_xx { - public class IkConstraint : IUpdatable { - internal IkConstraintData data; - internal ExposedList bones = new ExposedList(); - internal Bone target; - internal int bendDirection; - internal float mix; +namespace Spine3_2_xx +{ + public class IkConstraint : IUpdatable + { + internal IkConstraintData data; + internal ExposedList bones = new ExposedList(); + internal Bone target; + internal int bendDirection; + internal float mix; - public IkConstraintData Data { get { return data; } } - public ExposedList Bones { get { return bones; } } - public Bone Target { get { return target; } set { target = value; } } - public int BendDirection { get { return bendDirection; } set { bendDirection = value; } } - public float Mix { get { return mix; } set { mix = value; } } + public IkConstraintData Data { get { return data; } } + public ExposedList Bones { get { return bones; } } + public Bone Target { get { return target; } set { target = value; } } + public int BendDirection { get { return bendDirection; } set { bendDirection = value; } } + public float Mix { get { return mix; } set { mix = value; } } - public IkConstraint (IkConstraintData data, Skeleton skeleton) { - if (data == null) throw new ArgumentNullException("data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); - this.data = data; - mix = data.mix; - bendDirection = data.bendDirection; + public IkConstraint(IkConstraintData data, Skeleton skeleton) + { + if (data == null) throw new ArgumentNullException("data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); + this.data = data; + mix = data.mix; + bendDirection = data.bendDirection; - bones = new ExposedList(data.bones.Count); - foreach (BoneData boneData in data.bones) - bones.Add(skeleton.FindBone(boneData.name)); - target = skeleton.FindBone(data.target.name); - } + bones = new ExposedList(data.bones.Count); + foreach (BoneData boneData in data.bones) + bones.Add(skeleton.FindBone(boneData.name)); + target = skeleton.FindBone(data.target.name); + } - public void Update () { - Apply(); - } + public void Update() + { + Apply(); + } - public void Apply () { - Bone target = this.target; - ExposedList bones = this.bones; - switch (bones.Count) { - case 1: - Apply(bones.Items[0], target.worldX, target.worldY, mix); - break; - case 2: - Apply(bones.Items[0], bones.Items[1], target.worldX, target.worldY, bendDirection, mix); - break; - } - } + public void Apply() + { + Bone target = this.target; + ExposedList bones = this.bones; + switch (bones.Count) + { + case 1: + Apply(bones.Items[0], target.worldX, target.worldY, mix); + break; + case 2: + Apply(bones.Items[0], bones.Items[1], target.worldX, target.worldY, bendDirection, mix); + break; + } + } - override public String ToString () { - return data.name; - } + override public String ToString() + { + return data.name; + } - /// 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 public void Apply (Bone bone, float targetX, float targetY, float alpha) { - Bone pp = bone.parent; - float id = 1 / (pp.a * pp.d - pp.b * pp.c); - float x = targetX - pp.worldX, y = targetY - pp.worldY; - float tx = (x * pp.d - y * pp.b) * id - bone.x, ty = (y * pp.a - x * pp.c) * id - bone.y; - float rotationIK = MathUtils.Atan2(ty, tx) * MathUtils.radDeg - bone.shearX; - if (bone.scaleX < 0) rotationIK += 180; - if (rotationIK > 180) - rotationIK -= 360; - else if (rotationIK < -180) rotationIK += 360; - bone.UpdateWorldTransform(bone.x, bone.y, bone.rotation + (rotationIK - bone.rotation) * alpha, bone.appliedScaleX, - bone.appliedScaleY, bone.shearX, bone.shearY); - } + /// 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 public void Apply(Bone bone, float targetX, float targetY, float alpha) + { + Bone pp = bone.parent; + float id = 1 / (pp.a * pp.d - pp.b * pp.c); + float x = targetX - pp.worldX, y = targetY - pp.worldY; + float tx = (x * pp.d - y * pp.b) * id - bone.x, ty = (y * pp.a - x * pp.c) * id - bone.y; + float rotationIK = MathUtils.Atan2(ty, tx) * MathUtils.radDeg - bone.shearX; + if (bone.scaleX < 0) rotationIK += 180; + if (rotationIK > 180) + rotationIK -= 360; + else if (rotationIK < -180) rotationIK += 360; + bone.UpdateWorldTransform(bone.x, bone.y, bone.rotation + (rotationIK - bone.rotation) * alpha, bone.appliedScaleX, + bone.appliedScaleY, bone.shearX, bone.shearY); + } - /// 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. - /// A direct descendant of the parent bone. - static public void Apply (Bone parent, Bone child, float targetX, float targetY, int bendDir, float alpha) { - if (alpha == 0) return; - float px = parent.x, py = parent.y, psx = parent.appliedScaleX, psy = parent.appliedScaleY; - int os1, os2, s2; - if (psx < 0) { - psx = -psx; - os1 = 180; - s2 = -1; - } else { - os1 = 0; - s2 = 1; - } - if (psy < 0) { - psy = -psy; - s2 = -s2; - } - float cx = child.x, cy = child.y, csx = child.appliedScaleX; - bool u = Math.Abs(psx - psy) <= 0.0001f; - if (!u && cy != 0) { - child.worldX = parent.a * cx + parent.worldX; - child.worldY = parent.c * cx + parent.worldY; - cy = 0; - } - if (csx < 0) { - csx = -csx; - os2 = 180; - } else - os2 = 0; - Bone pp = parent.parent; - float ppa = pp.a, ppb = pp.b, ppc = pp.c, ppd = pp.d, id = 1 / (ppa * ppd - ppb * ppc); - float x = targetX - pp.worldX, y = targetY - pp.worldY; - float tx = (x * ppd - y * ppb) * id - px, ty = (y * ppa - x * ppc) * id - py; - x = child.worldX - pp.worldX; - y = child.worldY - pp.worldY; - float dx = (x * ppd - y * ppb) * id - px, dy = (y * ppa - x * ppc) * id - py; - float l1 = (float)Math.Sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2; - if (u) { - l2 *= psx; - float cos = (tx * tx + ty * ty - l1 * l1 - l2 * l2) / (2 * l1 * l2); - if (cos < -1) cos = -1; - else if (cos > 1) cos = 1; - a2 = (float)Math.Acos(cos) * bendDir; - float a = l1 + l2 * cos, o = l2 * MathUtils.Sin(a2); - a1 = MathUtils.Atan2(ty * a - tx * o, tx * a + ty * o); - } else { - float a = psx * l2, b = psy * l2, ta = MathUtils.Atan2(ty, tx); - float aa = a * a, bb = b * b, ll = l1 * l1, dd = tx * tx + ty * ty; - float c0 = bb * ll + aa * dd - aa * bb, c1 = -2 * bb * l1, c2 = bb - aa; - float d = c1 * c1 - 4 * c2 * c0; - if (d >= 0) { - float q = (float)Math.Sqrt(d); - if (c1 < 0) q = -q; - q = -(c1 + q) / 2; - float r0 = q / c2, r1 = c0 / q; - float r = Math.Abs(r0) < Math.Abs(r1) ? r0 : r1; - if (r * r <= dd) { - y = (float)Math.Sqrt(dd - r * r) * bendDir; - a1 = ta - MathUtils.Atan2(y, r); - a2 = MathUtils.Atan2(y / psy, (r - l1) / psx); - goto outer; - } - } - float minAngle = 0, minDist = float.MaxValue, minX = 0, minY = 0; - float maxAngle = 0, maxDist = 0, maxX = 0, maxY = 0; - x = l1 + a; - d = x * x; - if (d > maxDist) { - maxAngle = 0; - maxDist = d; - maxX = x; - } - x = l1 - a; - d = x * x; - if (d < minDist) { - minAngle = MathUtils.PI; - minDist = d; - minX = x; - } - float angle = (float)Math.Acos(-a * l1 / (aa - bb)); - x = a * MathUtils.Cos(angle) + l1; - y = b * MathUtils.Sin(angle); - d = x * x + y * y; - if (d < minDist) { - minAngle = angle; - minDist = d; - minX = x; - minY = y; - } - if (d > maxDist) { - maxAngle = angle; - maxDist = d; - maxX = x; - maxY = y; - } - if (dd <= (minDist + maxDist) / 2) { - a1 = ta - MathUtils.Atan2(minY * bendDir, minX); - a2 = minAngle * bendDir; - } else { - a1 = ta - MathUtils.Atan2(maxY * bendDir, maxX); - a2 = maxAngle * bendDir; - } - } - outer: - float os = MathUtils.Atan2(cy, cx) * s2; - a1 = (a1 - os) * MathUtils.radDeg + os1; - a2 = ((a2 + os) * MathUtils.radDeg - child.shearX) * s2 + os2; - if (a1 > 180) a1 -= 360; - else if (a1 < -180) a1 += 360; - if (a2 > 180) a2 -= 360; - else if (a2 < -180) a2 += 360; - float rotation = parent.rotation; - parent.UpdateWorldTransform(px, py, rotation + (a1 - rotation) * alpha, parent.appliedScaleX, parent.appliedScaleY, 0, 0); - rotation = child.rotation; - child.UpdateWorldTransform(cx, cy, rotation + (a2 - rotation) * alpha, child.appliedScaleX, child.appliedScaleY, child.shearX, child.shearY); - } - } + /// 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. + /// A direct descendant of the parent bone. + static public void Apply(Bone parent, Bone child, float targetX, float targetY, int bendDir, float alpha) + { + if (alpha == 0) return; + float px = parent.x, py = parent.y, psx = parent.appliedScaleX, psy = parent.appliedScaleY; + int os1, os2, s2; + if (psx < 0) + { + psx = -psx; + os1 = 180; + s2 = -1; + } + else + { + os1 = 0; + s2 = 1; + } + if (psy < 0) + { + psy = -psy; + s2 = -s2; + } + float cx = child.x, cy = child.y, csx = child.appliedScaleX; + bool u = Math.Abs(psx - psy) <= 0.0001f; + if (!u && cy != 0) + { + child.worldX = parent.a * cx + parent.worldX; + child.worldY = parent.c * cx + parent.worldY; + cy = 0; + } + if (csx < 0) + { + csx = -csx; + os2 = 180; + } + else + os2 = 0; + Bone pp = parent.parent; + float ppa = pp.a, ppb = pp.b, ppc = pp.c, ppd = pp.d, id = 1 / (ppa * ppd - ppb * ppc); + float x = targetX - pp.worldX, y = targetY - pp.worldY; + float tx = (x * ppd - y * ppb) * id - px, ty = (y * ppa - x * ppc) * id - py; + x = child.worldX - pp.worldX; + y = child.worldY - pp.worldY; + float dx = (x * ppd - y * ppb) * id - px, dy = (y * ppa - x * ppc) * id - py; + float l1 = (float)Math.Sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2; + if (u) + { + l2 *= psx; + float cos = (tx * tx + ty * ty - l1 * l1 - l2 * l2) / (2 * l1 * l2); + if (cos < -1) cos = -1; + else if (cos > 1) cos = 1; + a2 = (float)Math.Acos(cos) * bendDir; + float a = l1 + l2 * cos, o = l2 * MathUtils.Sin(a2); + a1 = MathUtils.Atan2(ty * a - tx * o, tx * a + ty * o); + } + else + { + float a = psx * l2, b = psy * l2, ta = MathUtils.Atan2(ty, tx); + float aa = a * a, bb = b * b, ll = l1 * l1, dd = tx * tx + ty * ty; + float c0 = bb * ll + aa * dd - aa * bb, c1 = -2 * bb * l1, c2 = bb - aa; + float d = c1 * c1 - 4 * c2 * c0; + if (d >= 0) + { + float q = (float)Math.Sqrt(d); + if (c1 < 0) q = -q; + q = -(c1 + q) / 2; + float r0 = q / c2, r1 = c0 / q; + float r = Math.Abs(r0) < Math.Abs(r1) ? r0 : r1; + if (r * r <= dd) + { + y = (float)Math.Sqrt(dd - r * r) * bendDir; + a1 = ta - MathUtils.Atan2(y, r); + a2 = MathUtils.Atan2(y / psy, (r - l1) / psx); + goto outer; + } + } + float minAngle = 0, minDist = float.MaxValue, minX = 0, minY = 0; + float maxAngle = 0, maxDist = 0, maxX = 0, maxY = 0; + x = l1 + a; + d = x * x; + if (d > maxDist) + { + maxAngle = 0; + maxDist = d; + maxX = x; + } + x = l1 - a; + d = x * x; + if (d < minDist) + { + minAngle = MathUtils.PI; + minDist = d; + minX = x; + } + float angle = (float)Math.Acos(-a * l1 / (aa - bb)); + x = a * MathUtils.Cos(angle) + l1; + y = b * MathUtils.Sin(angle); + d = x * x + y * y; + if (d < minDist) + { + minAngle = angle; + minDist = d; + minX = x; + minY = y; + } + if (d > maxDist) + { + maxAngle = angle; + maxDist = d; + maxX = x; + maxY = y; + } + if (dd <= (minDist + maxDist) / 2) + { + a1 = ta - MathUtils.Atan2(minY * bendDir, minX); + a2 = minAngle * bendDir; + } + else + { + a1 = ta - MathUtils.Atan2(maxY * bendDir, maxX); + a2 = maxAngle * bendDir; + } + } + outer: + float os = MathUtils.Atan2(cy, cx) * s2; + a1 = (a1 - os) * MathUtils.radDeg + os1; + a2 = ((a2 + os) * MathUtils.radDeg - child.shearX) * s2 + os2; + if (a1 > 180) a1 -= 360; + else if (a1 < -180) a1 += 360; + if (a2 > 180) a2 -= 360; + else if (a2 < -180) a2 += 360; + float rotation = parent.rotation; + parent.UpdateWorldTransform(px, py, rotation + (a1 - rotation) * alpha, parent.appliedScaleX, parent.appliedScaleY, 0, 0); + rotation = child.rotation; + child.UpdateWorldTransform(cx, cy, rotation + (a2 - rotation) * alpha, child.appliedScaleX, child.appliedScaleY, child.shearX, child.shearY); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/IkConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/IkConstraintData.cs index d4f85bf..898811b 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/IkConstraintData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/IkConstraintData.cs @@ -32,27 +32,31 @@ using System; using System.Collections.Generic; -namespace Spine3_2_xx { - public class IkConstraintData { - internal String name; - internal List bones = new List(); - internal BoneData target; - internal int bendDirection = 1; - internal float mix = 1; +namespace Spine3_2_xx +{ + public class IkConstraintData + { + internal String name; + internal List bones = new List(); + internal BoneData target; + internal int bendDirection = 1; + internal float mix = 1; - public String Name { get { return name; } } - public List Bones { get { return bones; } } - public BoneData Target { get { return target; } set { target = value; } } - public int BendDirection { get { return bendDirection; } set { bendDirection = value; } } - public float Mix { get { return mix; } set { mix = value; } } + public String Name { get { return name; } } + public List Bones { get { return bones; } } + public BoneData Target { get { return target; } set { target = value; } } + public int BendDirection { get { return bendDirection; } set { bendDirection = value; } } + public float Mix { get { return mix; } set { mix = value; } } - public IkConstraintData (String name) { - if (name == null) throw new ArgumentNullException("name cannot be null."); - this.name = name; - } + public IkConstraintData(String name) + { + if (name == null) throw new ArgumentNullException("name cannot be null."); + this.name = name; + } - override public String ToString () { - return name; - } - } + override public String ToString() + { + return name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Json.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Json.cs index edca2b0..8771433 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Json.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Json.cs @@ -30,20 +30,22 @@ *****************************************************************************/ using System; +using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Text; -using System.Collections; -using System.Globalization; -using System.Collections.Generic; -namespace Spine3_2_xx { - public static class Json { - public static object Deserialize (TextReader text) { - var parser = new SharpJson3_2_xx.JsonDecoder(); - parser.parseNumbersAsFloat = true; - return parser.Decode(text.ReadToEnd()); - } - } +namespace Spine3_2_xx +{ + public static class Json + { + public static object Deserialize(TextReader text) + { + var parser = new SharpJson3_2_xx.JsonDecoder(); + parser.parseNumbersAsFloat = true; + return parser.Decode(text.ReadToEnd()); + } + } } /** @@ -77,460 +79,502 @@ public static object Deserialize (TextReader text) { */ namespace SharpJson3_2_xx { - class Lexer - { - public enum Token { - None, - Null, - True, - False, - Colon, - Comma, - String, - Number, - CurlyOpen, - CurlyClose, - SquaredOpen, - SquaredClose, - }; - - public bool hasError { - get { - return !success; - } - } - - public int lineNumber { - get; - private set; - } - - public bool parseNumbersAsFloat { - get; - set; - } - - char[] json; - int index = 0; - bool success = true; - char[] stringBuffer = new char[4096]; - - public Lexer(string text) - { - Reset(); - - json = text.ToCharArray(); - parseNumbersAsFloat = false; - } - - public void Reset() - { - index = 0; - lineNumber = 1; - success = true; - } - - public string ParseString() - { - int idx = 0; - StringBuilder builder = null; - - SkipWhiteSpaces(); - - // " - char c = json[index++]; - - bool failed = false; - bool complete = false; - - while (!complete && !failed) { - if (index == json.Length) - break; - - c = json[index++]; - if (c == '"') { - complete = true; - break; - } else if (c == '\\') { - if (index == json.Length) - break; - - c = json[index++]; - - switch (c) { - case '"': - stringBuffer[idx++] = '"'; - break; - case '\\': - stringBuffer[idx++] = '\\'; - break; - case '/': - stringBuffer[idx++] = '/'; - break; - case 'b': - stringBuffer[idx++] = '\b'; - break; - case'f': - stringBuffer[idx++] = '\f'; - break; - case 'n': - stringBuffer[idx++] = '\n'; - break; - case 'r': - stringBuffer[idx++] = '\r'; - break; - case 't': - stringBuffer[idx++] = '\t'; - break; - case 'u': - int remainingLength = json.Length - index; - if (remainingLength >= 4) { - var hex = new string(json, index, 4); - - // XXX: handle UTF - stringBuffer[idx++] = (char) Convert.ToInt32(hex, 16); - - // skip 4 chars - index += 4; - } else { - failed = true; - } - break; - } - } else { - stringBuffer[idx++] = c; - } - - if (idx >= stringBuffer.Length) { - if (builder == null) - builder = new StringBuilder(); - - builder.Append(stringBuffer, 0, idx); - idx = 0; - } - } - - if (!complete) { - success = false; - return null; - } - - if (builder != null) - return builder.ToString (); - else - return new string (stringBuffer, 0, idx); - } - - string GetNumberString() - { - SkipWhiteSpaces(); - - int lastIndex = GetLastIndexOfNumber(index); - int charLength = (lastIndex - index) + 1; - - var result = new string (json, index, charLength); - - index = lastIndex + 1; - - return result; - } - - public float ParseFloatNumber() - { - float number; - var str = GetNumberString (); - - if (!float.TryParse (str, NumberStyles.Float, CultureInfo.InvariantCulture, out number)) - return 0; - - return number; - } - - public double ParseDoubleNumber() - { - double number; - var str = GetNumberString (); - - if (!double.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out number)) - return 0; - - return number; - } - - int GetLastIndexOfNumber(int index) - { - int lastIndex; - - for (lastIndex = index; lastIndex < json.Length; lastIndex++) { - char ch = json[lastIndex]; - - if ((ch < '0' || ch > '9') && ch != '+' && ch != '-' - && ch != '.' && ch != 'e' && ch != 'E') - break; - } - - return lastIndex - 1; - } - - void SkipWhiteSpaces() - { - for (; index < json.Length; index++) { - char ch = json[index]; - - if (ch == '\n') - lineNumber++; - - if (!char.IsWhiteSpace(json[index])) - break; - } - } - - public Token LookAhead() - { - SkipWhiteSpaces(); - - int savedIndex = index; - return NextToken(json, ref savedIndex); - } - - public Token NextToken() - { - SkipWhiteSpaces(); - return NextToken(json, ref index); - } - - static Token NextToken(char[] json, ref int index) - { - if (index == json.Length) - return Token.None; - - char c = json[index++]; - - switch (c) { - case '{': - return Token.CurlyOpen; - case '}': - return Token.CurlyClose; - case '[': - return Token.SquaredOpen; - case ']': - return Token.SquaredClose; - case ',': - return Token.Comma; - case '"': - return Token.String; - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - case '-': - return Token.Number; - case ':': - return Token.Colon; - } - - index--; - - int remainingLength = json.Length - index; - - // false - if (remainingLength >= 5) { - if (json[index] == 'f' && - json[index + 1] == 'a' && - json[index + 2] == 'l' && - json[index + 3] == 's' && - json[index + 4] == 'e') { - index += 5; - return Token.False; - } - } - - // true - if (remainingLength >= 4) { - if (json[index] == 't' && - json[index + 1] == 'r' && - json[index + 2] == 'u' && - json[index + 3] == 'e') { - index += 4; - return Token.True; - } - } - - // null - if (remainingLength >= 4) { - if (json[index] == 'n' && - json[index + 1] == 'u' && - json[index + 2] == 'l' && - json[index + 3] == 'l') { - index += 4; - return Token.Null; - } - } - - return Token.None; - } - } - - public class JsonDecoder - { - public string errorMessage { - get; - private set; - } - - public bool parseNumbersAsFloat { - get; - set; - } - - Lexer lexer; - - public JsonDecoder() - { - errorMessage = null; - parseNumbersAsFloat = false; - } - - public object Decode(string text) - { - errorMessage = null; - - lexer = new Lexer(text); - lexer.parseNumbersAsFloat = parseNumbersAsFloat; - - return ParseValue(); - } - - public static object DecodeText(string text) - { - var builder = new JsonDecoder(); - return builder.Decode(text); - } - - IDictionary ParseObject() - { - var table = new Dictionary(); - - // { - lexer.NextToken(); - - while (true) { - var token = lexer.LookAhead(); - - switch (token) { - case Lexer.Token.None: - TriggerError("Invalid token"); - return null; - case Lexer.Token.Comma: - lexer.NextToken(); - break; - case Lexer.Token.CurlyClose: - lexer.NextToken(); - return table; - default: - // name - string name = EvalLexer(lexer.ParseString()); - - if (errorMessage != null) - return null; - - // : - token = lexer.NextToken(); - - if (token != Lexer.Token.Colon) { - TriggerError("Invalid token; expected ':'"); - return null; - } - - // value - object value = ParseValue(); - - if (errorMessage != null) - return null; - - table[name] = value; - break; - } - } - - //return null; // Unreachable code - } - - IList ParseArray() - { - var array = new List(); - - // [ - lexer.NextToken(); - - while (true) { - var token = lexer.LookAhead(); - - switch (token) { - case Lexer.Token.None: - TriggerError("Invalid token"); - return null; - case Lexer.Token.Comma: - lexer.NextToken(); - break; - case Lexer.Token.SquaredClose: - lexer.NextToken(); - return array; - default: - object value = ParseValue(); - - if (errorMessage != null) - return null; - - array.Add(value); - break; - } - } - - //return null; // Unreachable code - } - - object ParseValue() - { - switch (lexer.LookAhead()) { - case Lexer.Token.String: - return EvalLexer(lexer.ParseString()); - case Lexer.Token.Number: - if (parseNumbersAsFloat) - return EvalLexer(lexer.ParseFloatNumber()); - else - return EvalLexer(lexer.ParseDoubleNumber()); - case Lexer.Token.CurlyOpen: - return ParseObject(); - case Lexer.Token.SquaredOpen: - return ParseArray(); - case Lexer.Token.True: - lexer.NextToken(); - return true; - case Lexer.Token.False: - lexer.NextToken(); - return false; - case Lexer.Token.Null: - lexer.NextToken(); - return null; - case Lexer.Token.None: - break; - } - - TriggerError("Unable to parse value"); - return null; - } - - void TriggerError(string message) - { - errorMessage = string.Format("Error: '{0}' at line {1}", - message, lexer.lineNumber); - } - - T EvalLexer(T value) - { - if (lexer.hasError) - TriggerError("Lexical error ocurred"); - - return value; - } - } + class Lexer + { + public enum Token + { + None, + Null, + True, + False, + Colon, + Comma, + String, + Number, + CurlyOpen, + CurlyClose, + SquaredOpen, + SquaredClose, + }; + + public bool hasError + { + get + { + return !success; + } + } + + public int lineNumber + { + get; + private set; + } + + public bool parseNumbersAsFloat + { + get; + set; + } + + char[] json; + int index = 0; + bool success = true; + char[] stringBuffer = new char[4096]; + + public Lexer(string text) + { + Reset(); + + json = text.ToCharArray(); + parseNumbersAsFloat = false; + } + + public void Reset() + { + index = 0; + lineNumber = 1; + success = true; + } + + public string ParseString() + { + int idx = 0; + StringBuilder builder = null; + + SkipWhiteSpaces(); + + // " + char c = json[index++]; + + bool failed = false; + bool complete = false; + + while (!complete && !failed) + { + if (index == json.Length) + break; + + c = json[index++]; + if (c == '"') + { + complete = true; + break; + } + else if (c == '\\') + { + if (index == json.Length) + break; + + c = json[index++]; + + switch (c) + { + case '"': + stringBuffer[idx++] = '"'; + break; + case '\\': + stringBuffer[idx++] = '\\'; + break; + case '/': + stringBuffer[idx++] = '/'; + break; + case 'b': + stringBuffer[idx++] = '\b'; + break; + case 'f': + stringBuffer[idx++] = '\f'; + break; + case 'n': + stringBuffer[idx++] = '\n'; + break; + case 'r': + stringBuffer[idx++] = '\r'; + break; + case 't': + stringBuffer[idx++] = '\t'; + break; + case 'u': + int remainingLength = json.Length - index; + if (remainingLength >= 4) + { + var hex = new string(json, index, 4); + + // XXX: handle UTF + stringBuffer[idx++] = (char)Convert.ToInt32(hex, 16); + + // skip 4 chars + index += 4; + } + else + { + failed = true; + } + break; + } + } + else + { + stringBuffer[idx++] = c; + } + + if (idx >= stringBuffer.Length) + { + if (builder == null) + builder = new StringBuilder(); + + builder.Append(stringBuffer, 0, idx); + idx = 0; + } + } + + if (!complete) + { + success = false; + return null; + } + + if (builder != null) + return builder.ToString(); + else + return new string(stringBuffer, 0, idx); + } + + string GetNumberString() + { + SkipWhiteSpaces(); + + int lastIndex = GetLastIndexOfNumber(index); + int charLength = (lastIndex - index) + 1; + + var result = new string(json, index, charLength); + + index = lastIndex + 1; + + return result; + } + + public float ParseFloatNumber() + { + float number; + var str = GetNumberString(); + + if (!float.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out number)) + return 0; + + return number; + } + + public double ParseDoubleNumber() + { + double number; + var str = GetNumberString(); + + if (!double.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out number)) + return 0; + + return number; + } + + int GetLastIndexOfNumber(int index) + { + int lastIndex; + + for (lastIndex = index; lastIndex < json.Length; lastIndex++) + { + char ch = json[lastIndex]; + + if ((ch < '0' || ch > '9') && ch != '+' && ch != '-' + && ch != '.' && ch != 'e' && ch != 'E') + break; + } + + return lastIndex - 1; + } + + void SkipWhiteSpaces() + { + for (; index < json.Length; index++) + { + char ch = json[index]; + + if (ch == '\n') + lineNumber++; + + if (!char.IsWhiteSpace(json[index])) + break; + } + } + + public Token LookAhead() + { + SkipWhiteSpaces(); + + int savedIndex = index; + return NextToken(json, ref savedIndex); + } + + public Token NextToken() + { + SkipWhiteSpaces(); + return NextToken(json, ref index); + } + + static Token NextToken(char[] json, ref int index) + { + if (index == json.Length) + return Token.None; + + char c = json[index++]; + + switch (c) + { + case '{': + return Token.CurlyOpen; + case '}': + return Token.CurlyClose; + case '[': + return Token.SquaredOpen; + case ']': + return Token.SquaredClose; + case ',': + return Token.Comma; + case '"': + return Token.String; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + return Token.Number; + case ':': + return Token.Colon; + } + + index--; + + int remainingLength = json.Length - index; + + // false + if (remainingLength >= 5) + { + if (json[index] == 'f' && + json[index + 1] == 'a' && + json[index + 2] == 'l' && + json[index + 3] == 's' && + json[index + 4] == 'e') + { + index += 5; + return Token.False; + } + } + + // true + if (remainingLength >= 4) + { + if (json[index] == 't' && + json[index + 1] == 'r' && + json[index + 2] == 'u' && + json[index + 3] == 'e') + { + index += 4; + return Token.True; + } + } + + // null + if (remainingLength >= 4) + { + if (json[index] == 'n' && + json[index + 1] == 'u' && + json[index + 2] == 'l' && + json[index + 3] == 'l') + { + index += 4; + return Token.Null; + } + } + + return Token.None; + } + } + + public class JsonDecoder + { + public string errorMessage + { + get; + private set; + } + + public bool parseNumbersAsFloat + { + get; + set; + } + + Lexer lexer; + + public JsonDecoder() + { + errorMessage = null; + parseNumbersAsFloat = false; + } + + public object Decode(string text) + { + errorMessage = null; + + lexer = new Lexer(text); + lexer.parseNumbersAsFloat = parseNumbersAsFloat; + + return ParseValue(); + } + + public static object DecodeText(string text) + { + var builder = new JsonDecoder(); + return builder.Decode(text); + } + + IDictionary ParseObject() + { + var table = new Dictionary(); + + // { + lexer.NextToken(); + + while (true) + { + var token = lexer.LookAhead(); + + switch (token) + { + case Lexer.Token.None: + TriggerError("Invalid token"); + return null; + case Lexer.Token.Comma: + lexer.NextToken(); + break; + case Lexer.Token.CurlyClose: + lexer.NextToken(); + return table; + default: + // name + string name = EvalLexer(lexer.ParseString()); + + if (errorMessage != null) + return null; + + // : + token = lexer.NextToken(); + + if (token != Lexer.Token.Colon) + { + TriggerError("Invalid token; expected ':'"); + return null; + } + + // value + object value = ParseValue(); + + if (errorMessage != null) + return null; + + table[name] = value; + break; + } + } + + //return null; // Unreachable code + } + + IList ParseArray() + { + var array = new List(); + + // [ + lexer.NextToken(); + + while (true) + { + var token = lexer.LookAhead(); + + switch (token) + { + case Lexer.Token.None: + TriggerError("Invalid token"); + return null; + case Lexer.Token.Comma: + lexer.NextToken(); + break; + case Lexer.Token.SquaredClose: + lexer.NextToken(); + return array; + default: + object value = ParseValue(); + + if (errorMessage != null) + return null; + + array.Add(value); + break; + } + } + + //return null; // Unreachable code + } + + object ParseValue() + { + switch (lexer.LookAhead()) + { + case Lexer.Token.String: + return EvalLexer(lexer.ParseString()); + case Lexer.Token.Number: + if (parseNumbersAsFloat) + return EvalLexer(lexer.ParseFloatNumber()); + else + return EvalLexer(lexer.ParseDoubleNumber()); + case Lexer.Token.CurlyOpen: + return ParseObject(); + case Lexer.Token.SquaredOpen: + return ParseArray(); + case Lexer.Token.True: + lexer.NextToken(); + return true; + case Lexer.Token.False: + lexer.NextToken(); + return false; + case Lexer.Token.Null: + lexer.NextToken(); + return null; + case Lexer.Token.None: + break; + } + + TriggerError("Unable to parse value"); + return null; + } + + void TriggerError(string message) + { + errorMessage = string.Format("Error: '{0}' at line {1}", + message, lexer.lineNumber); + } + + T EvalLexer(T value) + { + if (lexer.hasError) + TriggerError("Lexical error ocurred"); + + return value; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/MathUtils.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/MathUtils.cs index ebc623a..8816220 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/MathUtils.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/MathUtils.cs @@ -31,65 +31,75 @@ using System; -namespace Spine3_2_xx { - public static class MathUtils { - public const float PI = 3.1415927f; - public const float PI2 = PI * 2; - public const float radDeg = 180f / PI; - public const float degRad = PI / 180; +namespace Spine3_2_xx +{ + public static class MathUtils + { + public const float PI = 3.1415927f; + public const float PI2 = PI * 2; + public const float radDeg = 180f / PI; + public const float degRad = PI / 180; - const int SIN_BITS = 14; // 16KB. Adjust for accuracy. - const int SIN_MASK = ~(-1 << SIN_BITS); - const int SIN_COUNT = SIN_MASK + 1; - const float radFull = PI * 2; - const float degFull = 360; - const float radToIndex = SIN_COUNT / radFull; - const float degToIndex = SIN_COUNT / degFull; - static float[] sin = new float[SIN_COUNT]; + const int SIN_BITS = 14; // 16KB. Adjust for accuracy. + const int SIN_MASK = ~(-1 << SIN_BITS); + const int SIN_COUNT = SIN_MASK + 1; + const float radFull = PI * 2; + const float degFull = 360; + const float radToIndex = SIN_COUNT / radFull; + const float degToIndex = SIN_COUNT / degFull; + static float[] sin = new float[SIN_COUNT]; - static MathUtils () { - for (int i = 0; i < SIN_COUNT; i++) - sin[i] = (float)Math.Sin((i + 0.5f) / SIN_COUNT * radFull); - for (int i = 0; i < 360; i += 90) - sin[(int)(i * degToIndex) & SIN_MASK] = (float)Math.Sin(i * degRad); - } + static MathUtils() + { + for (int i = 0; i < SIN_COUNT; i++) + sin[i] = (float)Math.Sin((i + 0.5f) / SIN_COUNT * radFull); + for (int i = 0; i < 360; i += 90) + sin[(int)(i * degToIndex) & SIN_MASK] = (float)Math.Sin(i * degRad); + } - /** Returns the sine in radians from a lookup table. */ - static public float Sin (float radians) { - return sin[(int)(radians * radToIndex) & SIN_MASK]; - } + /** Returns the sine in radians from a lookup table. */ + static public float Sin(float radians) + { + return sin[(int)(radians * radToIndex) & SIN_MASK]; + } - /** Returns the cosine in radians from a lookup table. */ - static public float Cos (float radians) { - return sin[(int)((radians + PI / 2) * radToIndex) & SIN_MASK]; - } + /** Returns the cosine in radians from a lookup table. */ + static public float Cos(float radians) + { + return sin[(int)((radians + PI / 2) * radToIndex) & SIN_MASK]; + } - /** Returns the sine in radians from a lookup table. */ - static public float SinDeg (float degrees) { - return sin[(int)(degrees * degToIndex) & SIN_MASK]; - } + /** Returns the sine in radians from a lookup table. */ + static public float SinDeg(float degrees) + { + return sin[(int)(degrees * degToIndex) & SIN_MASK]; + } - /** Returns the cosine in radians from a lookup table. */ - static public float CosDeg (float degrees) { - return sin[(int)((degrees + 90) * degToIndex) & SIN_MASK]; - } + /** Returns the cosine in radians from a lookup table. */ + static public float CosDeg(float degrees) + { + return sin[(int)((degrees + 90) * degToIndex) & SIN_MASK]; + } - /// 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 public float Atan2 (float y, float x) { - if (x == 0f) { - if (y > 0f) return PI / 2; - if (y == 0f) return 0f; - return -PI / 2; - } - float atan, z = y / x; - if (Math.Abs(z) < 1f) { - atan = z / (1f + 0.28f * z * z); - if (x < 0f) return atan + (y < 0f ? -PI : PI); - return atan; - } - atan = PI / 2 - z / (z * z + 0.28f); - return y < 0f ? atan - PI : atan; - } - } + /// 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 public float Atan2(float y, float x) + { + if (x == 0f) + { + if (y > 0f) return PI / 2; + if (y == 0f) return 0f; + return -PI / 2; + } + float atan, z = y / x; + if (Math.Abs(z) < 1f) + { + atan = z / (1f + 0.28f * z * z); + if (x < 0f) return atan + (y < 0f ? -PI : PI); + return atan; + } + atan = PI / 2 - z / (z * z + 0.28f); + return y < 0f ? atan - PI : atan; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Skeleton.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Skeleton.cs index d85a193..a4416be 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Skeleton.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Skeleton.cs @@ -30,287 +30,330 @@ *****************************************************************************/ using System; -using System.Collections.Generic; -namespace Spine3_2_xx { - public class Skeleton { - internal SkeletonData data; - internal ExposedList bones; - internal ExposedList slots; - internal ExposedList drawOrder; - internal ExposedList ikConstraints; - internal ExposedList transformConstraints; - private ExposedList updateCache = new ExposedList(); - internal Skin skin; - internal float r = 1, g = 1, b = 1, a = 1; - internal float time; - internal bool flipX, flipY; - internal float x, y; +namespace Spine3_2_xx +{ + public class Skeleton + { + internal SkeletonData data; + internal ExposedList bones; + internal ExposedList slots; + internal ExposedList drawOrder; + internal ExposedList ikConstraints; + internal ExposedList transformConstraints; + private ExposedList updateCache = new ExposedList(); + internal Skin skin; + internal float r = 1, g = 1, b = 1, a = 1; + internal float time; + internal bool flipX, flipY; + internal float x, y; - public SkeletonData Data { get { return data; } } - public ExposedList Bones { get { return bones; } } - public ExposedList Slots { get { return slots; } } - public ExposedList DrawOrder { get { return drawOrder; } } - public ExposedList IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } } - public Skin Skin { get { return skin; } set { skin = value; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } - public float Time { get { return time; } set { time = value; } } - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public bool FlipX { get { return flipX; } set { flipX = value; } } - public bool FlipY { get { return flipY; } set { flipY = value; } } + public SkeletonData Data { get { return data; } } + public ExposedList Bones { get { return bones; } } + public ExposedList Slots { get { return slots; } } + public ExposedList DrawOrder { get { return drawOrder; } } + public ExposedList IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } } + public Skin Skin { get { return skin; } set { skin = value; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } + public float Time { get { return time; } set { time = value; } } + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public bool FlipX { get { return flipX; } set { flipX = value; } } + public bool FlipY { get { return flipY; } set { flipY = value; } } - public Bone RootBone { - get { - return bones.Count == 0 ? null : bones.Items[0]; - } - } + public Bone RootBone + { + get + { + return bones.Count == 0 ? null : bones.Items[0]; + } + } - public Skeleton (SkeletonData data) { - if (data == null) throw new ArgumentNullException("data cannot be null."); - this.data = data; + public Skeleton(SkeletonData data) + { + if (data == null) throw new ArgumentNullException("data cannot be null."); + this.data = data; - bones = new ExposedList(data.bones.Count); - foreach (BoneData boneData in data.bones) { - Bone parent = boneData.parent == null ? null : bones.Items[data.bones.IndexOf(boneData.parent)]; - Bone bone = new Bone(boneData, this, parent); - if (parent != null) parent.children.Add(bone); - bones.Add(bone); - } + bones = new ExposedList(data.bones.Count); + foreach (BoneData boneData in data.bones) + { + Bone parent = boneData.parent == null ? null : bones.Items[data.bones.IndexOf(boneData.parent)]; + Bone bone = new Bone(boneData, this, parent); + if (parent != null) parent.children.Add(bone); + bones.Add(bone); + } - slots = new ExposedList(data.slots.Count); - drawOrder = new ExposedList(data.slots.Count); - foreach (SlotData slotData in data.slots) { - Bone bone = bones.Items[data.bones.IndexOf(slotData.boneData)]; - Slot slot = new Slot(slotData, bone); - slots.Add(slot); - drawOrder.Add(slot); - } + slots = new ExposedList(data.slots.Count); + drawOrder = new ExposedList(data.slots.Count); + foreach (SlotData slotData in data.slots) + { + Bone bone = bones.Items[data.bones.IndexOf(slotData.boneData)]; + Slot slot = new Slot(slotData, bone); + slots.Add(slot); + drawOrder.Add(slot); + } - ikConstraints = new ExposedList(data.ikConstraints.Count); - foreach (IkConstraintData ikConstraintData in data.ikConstraints) - ikConstraints.Add(new IkConstraint(ikConstraintData, this)); + ikConstraints = new ExposedList(data.ikConstraints.Count); + foreach (IkConstraintData ikConstraintData in data.ikConstraints) + ikConstraints.Add(new IkConstraint(ikConstraintData, this)); - transformConstraints = new ExposedList(data.transformConstraints.Count); - foreach (TransformConstraintData transformConstraintData in data.transformConstraints) - transformConstraints.Add(new TransformConstraint(transformConstraintData, this)); + transformConstraints = new ExposedList(data.transformConstraints.Count); + foreach (TransformConstraintData transformConstraintData in data.transformConstraints) + transformConstraints.Add(new TransformConstraint(transformConstraintData, this)); - UpdateCache(); - UpdateWorldTransform(); - } + UpdateCache(); + UpdateWorldTransform(); + } - /// Caches information about bones and constraints. Must be called if bones or constraints are added - /// or removed. - public void UpdateCache () { - ExposedList bones = this.bones; - ExposedList updateCache = this.updateCache; - ExposedList ikConstraints = this.ikConstraints; - ExposedList transformConstraints = this.transformConstraints; - int ikConstraintsCount = ikConstraints.Count; - int transformConstraintsCount = transformConstraints.Count; - updateCache.Clear(); - for (int i = 0, n = bones.Count; i < n; i++) { - Bone bone = bones.Items[i]; - updateCache.Add(bone); - for (int ii = 0; ii < ikConstraintsCount; ii++) { - IkConstraint ikConstraint = ikConstraints.Items[ii]; - if (bone == ikConstraint.bones.Items[ikConstraint.bones.Count - 1]) { - updateCache.Add(ikConstraint); - break; - } - } - } + /// Caches information about bones and constraints. Must be called if bones or constraints are added + /// or removed. + public void UpdateCache() + { + ExposedList bones = this.bones; + ExposedList updateCache = this.updateCache; + ExposedList ikConstraints = this.ikConstraints; + ExposedList transformConstraints = this.transformConstraints; + int ikConstraintsCount = ikConstraints.Count; + int transformConstraintsCount = transformConstraints.Count; + updateCache.Clear(); + for (int i = 0, n = bones.Count; i < n; i++) + { + Bone bone = bones.Items[i]; + updateCache.Add(bone); + for (int ii = 0; ii < ikConstraintsCount; ii++) + { + IkConstraint ikConstraint = ikConstraints.Items[ii]; + if (bone == ikConstraint.bones.Items[ikConstraint.bones.Count - 1]) + { + updateCache.Add(ikConstraint); + break; + } + } + } - for (int i = 0; i < transformConstraintsCount; i++) { - TransformConstraint transformConstraint = transformConstraints.Items[i]; - for (int ii = updateCache.Count - 1; i >= 0; ii--) { - if (updateCache.Items[ii] == transformConstraint.bone) { - updateCache.Insert(ii + 1, transformConstraint); - break; - } - } - } - } + for (int i = 0; i < transformConstraintsCount; i++) + { + TransformConstraint transformConstraint = transformConstraints.Items[i]; + for (int ii = updateCache.Count - 1; i >= 0; ii--) + { + if (updateCache.Items[ii] == transformConstraint.bone) + { + updateCache.Insert(ii + 1, transformConstraint); + break; + } + } + } + } - /// Updates the world transform for each bone and applies constraints. - public void UpdateWorldTransform () { - ExposedList updateCache = this.updateCache; - for (int i = 0, n = updateCache.Count; i < n; i++) - updateCache.Items[i].Update(); - } + /// Updates the world transform for each bone and applies constraints. + public void UpdateWorldTransform() + { + ExposedList updateCache = this.updateCache; + for (int i = 0, n = updateCache.Count; i < n; i++) + updateCache.Items[i].Update(); + } - /// Sets the bones, constraints, and slots to their setup pose values. - public void SetToSetupPose () { - SetBonesToSetupPose(); - SetSlotsToSetupPose(); - } + /// Sets the bones, constraints, and slots to their setup pose values. + public void SetToSetupPose() + { + SetBonesToSetupPose(); + SetSlotsToSetupPose(); + } - /// Sets the bones and constraints to their setup pose values. - public void SetBonesToSetupPose () { - ExposedList bones = this.bones; - for (int i = 0, n = bones.Count; i < n; i++) - bones.Items[i].SetToSetupPose(); + /// Sets the bones and constraints to their setup pose values. + public void SetBonesToSetupPose() + { + ExposedList bones = this.bones; + for (int i = 0, n = bones.Count; i < n; i++) + bones.Items[i].SetToSetupPose(); - ExposedList ikConstraints = this.ikConstraints; - for (int i = 0, n = ikConstraints.Count; i < n; i++) { - IkConstraint constraint = ikConstraints.Items[i]; - constraint.bendDirection = constraint.data.bendDirection; - constraint.mix = constraint.data.mix; - } + ExposedList ikConstraints = this.ikConstraints; + for (int i = 0, n = ikConstraints.Count; i < n; i++) + { + IkConstraint constraint = ikConstraints.Items[i]; + constraint.bendDirection = constraint.data.bendDirection; + constraint.mix = constraint.data.mix; + } - ExposedList transformConstraints = this.transformConstraints; - for (int i = 0, n = transformConstraints.Count; i < n; i++) { - TransformConstraint constraint = transformConstraints.Items[i]; - TransformConstraintData data = constraint.data; - constraint.rotateMix = data.rotateMix; - constraint.translateMix = data.translateMix; - constraint.scaleMix = data.scaleMix; - constraint.shearMix = data.shearMix; - } - } + ExposedList transformConstraints = this.transformConstraints; + for (int i = 0, n = transformConstraints.Count; i < n; i++) + { + TransformConstraint constraint = transformConstraints.Items[i]; + TransformConstraintData data = constraint.data; + constraint.rotateMix = data.rotateMix; + constraint.translateMix = data.translateMix; + constraint.scaleMix = data.scaleMix; + constraint.shearMix = data.shearMix; + } + } - public void SetSlotsToSetupPose () { - ExposedList slots = this.slots; - drawOrder.Clear(); - for (int i = 0, n = slots.Count; i < n; i++) - drawOrder.Add(slots.Items[i]); + public void SetSlotsToSetupPose() + { + ExposedList slots = this.slots; + drawOrder.Clear(); + for (int i = 0, n = slots.Count; i < n; i++) + drawOrder.Add(slots.Items[i]); - for (int i = 0, n = slots.Count; i < n; i++) - slots.Items[i].SetToSetupPose(i); - } + for (int i = 0, n = slots.Count; i < n; i++) + slots.Items[i].SetToSetupPose(i); + } - /// May be null. - public Bone FindBone (String boneName) { - if (boneName == null) throw new ArgumentNullException("boneName cannot be null."); - ExposedList bones = this.bones; - for (int i = 0, n = bones.Count; i < n; i++) { - Bone bone = bones.Items[i]; - if (bone.data.name == boneName) return bone; - } - return null; - } + /// May be null. + public Bone FindBone(String boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName cannot be null."); + ExposedList bones = this.bones; + for (int i = 0, n = bones.Count; i < n; i++) + { + Bone bone = bones.Items[i]; + if (bone.data.name == boneName) return bone; + } + return null; + } - /// -1 if the bone was not found. - public int FindBoneIndex (String boneName) { - if (boneName == null) throw new ArgumentNullException("boneName cannot be null."); - ExposedList bones = this.bones; - for (int i = 0, n = bones.Count; i < n; i++) - if (bones.Items[i].data.name == boneName) return i; - return -1; - } + /// -1 if the bone was not found. + public int FindBoneIndex(String boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName cannot be null."); + ExposedList bones = this.bones; + for (int i = 0, n = bones.Count; i < n; i++) + if (bones.Items[i].data.name == boneName) return i; + return -1; + } - /// May be null. - public Slot FindSlot (String slotName) { - if (slotName == null) throw new ArgumentNullException("slotName cannot be null."); - ExposedList slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) { - Slot slot = slots.Items[i]; - if (slot.data.name == slotName) return slot; - } - return null; - } + /// May be null. + public Slot FindSlot(String slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName cannot be null."); + ExposedList slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + { + Slot slot = slots.Items[i]; + if (slot.data.name == slotName) return slot; + } + return null; + } - /// -1 if the bone was not found. - public int FindSlotIndex (String slotName) { - if (slotName == null) throw new ArgumentNullException("slotName cannot be null."); - ExposedList slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) - if (slots.Items[i].data.name.Equals(slotName)) return i; - return -1; - } + /// -1 if the bone was not found. + public int FindSlotIndex(String slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName cannot be null."); + ExposedList slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + if (slots.Items[i].data.name.Equals(slotName)) return i; + return -1; + } - /// Sets a skin by name (see SetSkin). - public void SetSkin (String skinName) { - Skin skin = data.FindSkin(skinName); - if (skin == null) throw new ArgumentException("Skin not found: " + skinName); - SetSkin(skin); - } + /// Sets a skin by name (see SetSkin). + public void SetSkin(String skinName) + { + Skin skin = data.FindSkin(skinName); + if (skin == null) throw new ArgumentException("Skin not found: " + skinName); + SetSkin(skin); + } - /// Sets the skin used to look up attachments before looking in the {@link SkeletonData#getDefaultSkin() default - /// skin}. Attachmentsfrom 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. - /// May be null. - public void SetSkin (Skin newSkin) { - if (newSkin != null) { - if (skin != null) - newSkin.AttachAll(this, skin); - else { - ExposedList slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) { - Slot slot = slots.Items[i]; - String name = slot.data.attachmentName; - if (name != null) { - Attachment attachment = newSkin.GetAttachment(i, name); - if (attachment != null) slot.Attachment = attachment; - } - } - } - } - skin = newSkin; - } + /// Sets the skin used to look up attachments before looking in the {@link SkeletonData#getDefaultSkin() default + /// skin}. Attachmentsfrom 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. + /// May be null. + public void SetSkin(Skin newSkin) + { + if (newSkin != null) + { + if (skin != null) + newSkin.AttachAll(this, skin); + else + { + ExposedList slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + { + Slot slot = slots.Items[i]; + String name = slot.data.attachmentName; + if (name != null) + { + Attachment attachment = newSkin.GetAttachment(i, name); + if (attachment != null) slot.Attachment = attachment; + } + } + } + } + skin = newSkin; + } - /// May be null. - public Attachment GetAttachment (String slotName, String attachmentName) { - return GetAttachment(data.FindSlotIndex(slotName), attachmentName); - } + /// May be null. + public Attachment GetAttachment(String slotName, String attachmentName) + { + return GetAttachment(data.FindSlotIndex(slotName), attachmentName); + } - /// May be null. - public Attachment GetAttachment (int slotIndex, String attachmentName) { - if (attachmentName == null) throw new ArgumentNullException("attachmentName cannot be null."); - if (skin != null) { - Attachment attachment = skin.GetAttachment(slotIndex, attachmentName); - if (attachment != null) return attachment; - } - if (data.defaultSkin != null) return data.defaultSkin.GetAttachment(slotIndex, attachmentName); - return null; - } + /// May be null. + public Attachment GetAttachment(int slotIndex, String attachmentName) + { + if (attachmentName == null) throw new ArgumentNullException("attachmentName cannot be null."); + if (skin != null) + { + Attachment attachment = skin.GetAttachment(slotIndex, attachmentName); + if (attachment != null) return attachment; + } + if (data.defaultSkin != null) return data.defaultSkin.GetAttachment(slotIndex, attachmentName); + return null; + } - /// May be null. - public void SetAttachment (String slotName, String attachmentName) { - if (slotName == null) throw new ArgumentNullException("slotName cannot be null."); - ExposedList slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) { - Slot slot = slots.Items[i]; - if (slot.data.name == slotName) { - Attachment attachment = null; - if (attachmentName != null) { - attachment = GetAttachment(i, attachmentName); - if (attachment == null) throw new Exception("Attachment not found: " + attachmentName + ", for slot: " + slotName); - } - slot.Attachment = attachment; - return; - } - } - throw new Exception("Slot not found: " + slotName); - } + /// May be null. + public void SetAttachment(String slotName, String attachmentName) + { + if (slotName == null) throw new ArgumentNullException("slotName cannot be null."); + ExposedList slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + { + Slot slot = slots.Items[i]; + if (slot.data.name == slotName) + { + Attachment attachment = null; + if (attachmentName != null) + { + attachment = GetAttachment(i, attachmentName); + if (attachment == null) throw new Exception("Attachment not found: " + attachmentName + ", for slot: " + slotName); + } + slot.Attachment = attachment; + return; + } + } + throw new Exception("Slot not found: " + slotName); + } - /** @return May be null. */ - public IkConstraint FindIkConstraint (String constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName cannot be null."); - ExposedList ikConstraints = this.ikConstraints; - for (int i = 0, n = ikConstraints.Count; i < n; i++) { - IkConstraint ikConstraint = ikConstraints.Items[i]; - if (ikConstraint.data.name == constraintName) return ikConstraint; - } - return null; - } + /** @return May be null. */ + public IkConstraint FindIkConstraint(String constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName cannot be null."); + ExposedList ikConstraints = this.ikConstraints; + for (int i = 0, n = ikConstraints.Count; i < n; i++) + { + IkConstraint ikConstraint = ikConstraints.Items[i]; + if (ikConstraint.data.name == constraintName) return ikConstraint; + } + return null; + } - /** @return May be null. */ - public TransformConstraint FindTransformConstraint (String constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName cannot be null."); - ExposedList transformConstraints = this.transformConstraints; - for (int i = 0, n = transformConstraints.Count; i < n; i++) { - TransformConstraint transformConstraint = transformConstraints.Items[i]; - if (transformConstraint.data.name == constraintName) return transformConstraint; - } - return null; - } + /** @return May be null. */ + public TransformConstraint FindTransformConstraint(String constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName cannot be null."); + ExposedList transformConstraints = this.transformConstraints; + for (int i = 0, n = transformConstraints.Count; i < n; i++) + { + TransformConstraint transformConstraint = transformConstraints.Items[i]; + if (transformConstraint.data.name == constraintName) return transformConstraint; + } + return null; + } - public void Update (float delta) { - time += delta; - } - } + public void Update(float delta) + { + time += delta; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/SkeletonBinary.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/SkeletonBinary.cs index 975e56c..1cc1fa2 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/SkeletonBinary.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/SkeletonBinary.cs @@ -34,44 +34,48 @@ #endif using System; -using System.IO; using System.Collections.Generic; +using System.IO; #if WINDOWS_STOREAPP using System.Threading.Tasks; using Windows.Storage; #endif -namespace Spine3_2_xx { - public class SkeletonBinary { - public const int TIMELINE_ROTATE = 0; - public const int TIMELINE_TRANSLATE = 1; - public const int TIMELINE_SCALE = 2; - public const int TIMELINE_SHEAR = 3; - public const int TIMELINE_ATTACHMENT = 4; - public const int TIMELINE_COLOR = 5; - - public const int CURVE_LINEAR = 0; - public const int CURVE_STEPPED = 1; - public const int CURVE_BEZIER = 2; - - public float Scale { get; set; } - - private AttachmentLoader attachmentLoader; - private byte[] buffer = new byte[32]; - private List linkedMeshes = new List(); - - public SkeletonBinary (params Atlas[] atlasArray) - : this(new AtlasAttachmentLoader(atlasArray)) { - } - - public SkeletonBinary (AttachmentLoader attachmentLoader) { - if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader"); - this.attachmentLoader = attachmentLoader; - Scale = 1; - } - - #if !ISUNITY && WINDOWS_STOREAPP +namespace Spine3_2_xx +{ + public class SkeletonBinary + { + public const int TIMELINE_ROTATE = 0; + public const int TIMELINE_TRANSLATE = 1; + public const int TIMELINE_SCALE = 2; + public const int TIMELINE_SHEAR = 3; + public const int TIMELINE_ATTACHMENT = 4; + public const int TIMELINE_COLOR = 5; + + public const int CURVE_LINEAR = 0; + public const int CURVE_STEPPED = 1; + public const int CURVE_BEZIER = 2; + + public float Scale { get; set; } + + private AttachmentLoader attachmentLoader; + private byte[] buffer = new byte[32]; + private List linkedMeshes = new List(); + + public SkeletonBinary(params Atlas[] atlasArray) + : this(new AtlasAttachmentLoader(atlasArray)) + { + } + + public SkeletonBinary(AttachmentLoader attachmentLoader) + { + if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader"); + this.attachmentLoader = attachmentLoader; + Scale = 1; + } + +#if !ISUNITY && WINDOWS_STOREAPP private async Task ReadFile(string path) { var folder = Windows.ApplicationModel.Package.Current.InstalledLocation; using (var input = new BufferedStream(await folder.GetFileAsync(path).AsTask().ConfigureAwait(false))) { @@ -84,677 +88,766 @@ private async Task ReadFile(string path) { public SkeletonData ReadSkeletonData (String path) { return this.ReadFile(path).Result; } - #else - public SkeletonData ReadSkeletonData (String path) { - #if WINDOWS_PHONE +#else + public SkeletonData ReadSkeletonData(String path) + { +#if WINDOWS_PHONE using (var input = new BufferedStream(Microsoft.Xna.Framework.TitleContainer.OpenStream(path))) { - #else - using (var input = new BufferedStream(new FileStream(path, FileMode.Open))) { - #endif // WINDOWS_PHONE - SkeletonData skeletonData = ReadSkeletonData(input); - skeletonData.name = Path.GetFileNameWithoutExtension(path); - return skeletonData; - } - } - - #endif // WINDOWS_STOREAPP - - public SkeletonData ReadSkeletonData (Stream input) { - if (input == null) throw new ArgumentNullException("input"); - float scale = Scale; - - var skeletonData = new SkeletonData(); - skeletonData.hash = ReadString(input); - if (skeletonData.hash.Length == 0) skeletonData.hash = null; - skeletonData.version = ReadString(input); - if (skeletonData.version.Length == 0) skeletonData.version = null; - skeletonData.width = ReadFloat(input); - skeletonData.height = ReadFloat(input); - - bool nonessential = ReadBoolean(input); - - if (nonessential) { - skeletonData.imagesPath = ReadString(input); - if (skeletonData.imagesPath.Length == 0) skeletonData.imagesPath = null; - } - - // Bones. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - String name = ReadString(input); - BoneData parent = i == 0 ? null : skeletonData.bones.Items[ReadVarint(input, true)]; - BoneData boneData = new BoneData(name, parent); - boneData.rotation = ReadFloat(input); - boneData.x = ReadFloat(input) * scale; - boneData.y = ReadFloat(input) * scale; - boneData.scaleX = ReadFloat(input); - boneData.scaleY = ReadFloat(input); - boneData.shearX = ReadFloat(input); - boneData.shearY = ReadFloat(input); - boneData.length = ReadFloat(input) * scale; - boneData.inheritRotation = ReadBoolean(input); - boneData.inheritScale = ReadBoolean(input); - if (nonessential) ReadInt(input); // Skip bone color. - skeletonData.bones.Add(boneData); - } - - // IK constraints. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - IkConstraintData ikConstraintData = new IkConstraintData(ReadString(input)); - for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) - ikConstraintData.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]); - ikConstraintData.target = skeletonData.bones.Items[ReadVarint(input, true)]; - ikConstraintData.mix = ReadFloat(input); - ikConstraintData.bendDirection = ReadSByte(input); - skeletonData.ikConstraints.Add(ikConstraintData); - } - - // Transform constraints. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - TransformConstraintData transformConstraintData = new TransformConstraintData(ReadString(input)); - transformConstraintData.bone = skeletonData.bones.Items[ReadVarint(input, true)]; - transformConstraintData.target = skeletonData.bones.Items[ReadVarint(input, true)]; - transformConstraintData.offsetRotation = ReadFloat(input); - transformConstraintData.offsetX = ReadFloat(input) * scale; - transformConstraintData.offsetY = ReadFloat(input) * scale; - transformConstraintData.offsetScaleX = ReadFloat(input); - transformConstraintData.offsetScaleY = ReadFloat(input); - transformConstraintData.offsetShearY = ReadFloat(input); - transformConstraintData.rotateMix = ReadFloat(input); - transformConstraintData.translateMix = ReadFloat(input); - transformConstraintData.scaleMix = ReadFloat(input); - transformConstraintData.shearMix = ReadFloat(input); - skeletonData.transformConstraints.Add(transformConstraintData); - } - - // Slots. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - String slotName = ReadString(input); - BoneData boneData = skeletonData.bones.Items[ReadVarint(input, true)]; - SlotData slotData = new SlotData(slotName, boneData); - int color = ReadInt(input); - slotData.r = ((color & 0xff000000) >> 24) / 255f; - slotData.g = ((color & 0x00ff0000) >> 16) / 255f; - slotData.b = ((color & 0x0000ff00) >> 8) / 255f; - slotData.a = ((color & 0x000000ff)) / 255f; - slotData.attachmentName = ReadString(input); - slotData.blendMode = (BlendMode)ReadVarint(input, true); - skeletonData.slots.Add(slotData); - } - - // Default skin. - Skin defaultSkin = ReadSkin(input, "default", nonessential); - if (defaultSkin != null) { - skeletonData.defaultSkin = defaultSkin; - skeletonData.skins.Add(defaultSkin); - } - - // Skins. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) - skeletonData.skins.Add(ReadSkin(input, ReadString(input), nonessential)); - - // Linked meshes. - for (int i = 0, n = linkedMeshes.Count; i < n; i++) { - SkeletonJson.LinkedMesh linkedMesh = linkedMeshes[i]; - Skin skin = linkedMesh.skin == null ? skeletonData.DefaultSkin : skeletonData.FindSkin(linkedMesh.skin); - if (skin == null) throw new Exception("Skin not found: " + linkedMesh.skin); - Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); - if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); - if (linkedMesh.mesh is MeshAttachment) { - MeshAttachment mesh = (MeshAttachment)linkedMesh.mesh; - mesh.ParentMesh = (MeshAttachment)parent; - mesh.UpdateUVs(); - } else { - WeightedMeshAttachment mesh = (WeightedMeshAttachment)linkedMesh.mesh; - mesh.ParentMesh = (WeightedMeshAttachment)parent; - mesh.UpdateUVs(); - } - } - linkedMeshes.Clear(); - - // Events. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - EventData eventData = new EventData(ReadString(input)); - eventData.Int = ReadVarint(input, false); - eventData.Float = ReadFloat(input); - eventData.String = ReadString(input); - skeletonData.events.Add(eventData); - } - - // Animations. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) - ReadAnimation(ReadString(input), input, skeletonData); - - skeletonData.bones.TrimExcess(); - skeletonData.slots.TrimExcess(); - skeletonData.skins.TrimExcess(); - skeletonData.events.TrimExcess(); - skeletonData.animations.TrimExcess(); - skeletonData.ikConstraints.TrimExcess(); - return skeletonData; - } - - /** @return May be null. */ - private Skin ReadSkin (Stream input, String skinName, bool nonessential) { - int slotCount = ReadVarint(input, true); - if (slotCount == 0) return null; - Skin skin = new Skin(skinName); - 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 = ReadString(input); - skin.AddAttachment(slotIndex, name, ReadAttachment(input, skin, slotIndex, name, nonessential)); - } - } - return skin; - } - - private Attachment ReadAttachment (Stream input, Skin skin, int slotIndex, String attachmentName, bool nonessential) { - float scale = Scale; - - String name = ReadString(input); - if (name == null) name = attachmentName; - - AttachmentType type = (AttachmentType)input.ReadByte(); - switch (type) { - case AttachmentType.region: { - String path = ReadString(input); - float rotation = ReadFloat(input); - float x = ReadFloat(input); - float y = ReadFloat(input); - float scaleX = ReadFloat(input); - float scaleY = ReadFloat(input); - float width = ReadFloat(input); - float height = ReadFloat(input); - int color = ReadInt(input); - - if (path == null) path = name; - RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); - if (region == null) return null; - region.Path = path; - region.x = x * scale; - region.y = y * scale; - region.scaleX = scaleX; - region.scaleY = scaleY; - region.rotation = rotation; - region.width = width * scale; - region.height = height * scale; - region.r = ((color & 0xff000000) >> 24) / 255f; - region.g = ((color & 0x00ff0000) >> 16) / 255f; - region.b = ((color & 0x0000ff00) >> 8) / 255f; - region.a = ((color & 0x000000ff)) / 255f; - region.UpdateOffset(); - return region; - } - case AttachmentType.boundingbox: { - float[] vertices = ReadFloatArray(input, ReadVarint(input, true) * 2, scale); - BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); - if (box == null) return null; - box.vertices = vertices; - return box; - } - case AttachmentType.mesh: { - String path = ReadString(input); - int color = ReadInt(input); - int hullLength = 0; - int verticesLength = ReadVarint(input, true) * 2; - float[] uvs = ReadFloatArray(input, verticesLength, 1); - int[] triangles = ReadShortArray(input); - float[] vertices = ReadFloatArray(input, verticesLength, scale); - hullLength = ReadVarint(input, true); - int[] edges = null; - float width = 0, height = 0; - if (nonessential) { - edges = ReadShortArray(input); - width = ReadFloat(input); - height = ReadFloat(input); - } - - if (path == null) path = name; - MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); - if (mesh == null) return null; - mesh.Path = path; - mesh.r = ((color & 0xff000000) >> 24) / 255f; - mesh.g = ((color & 0x00ff0000) >> 16) / 255f; - mesh.b = ((color & 0x0000ff00) >> 8) / 255f; - mesh.a = ((color & 0x000000ff)) / 255f; - mesh.vertices = vertices; - mesh.triangles = triangles; - mesh.regionUVs = uvs; - mesh.UpdateUVs(); - mesh.HullLength = hullLength; - if (nonessential) { - mesh.Edges = edges; - mesh.Width = width * scale; - mesh.Height = height * scale; - } - return mesh; - } - case AttachmentType.linkedmesh: { - String path = ReadString(input); - int color = ReadInt(input); - String skinName = ReadString(input); - String parent = ReadString(input); - bool inheritFFD = ReadBoolean(input); - float width = 0, height = 0; - if (nonessential) { - width = ReadFloat(input); - height = ReadFloat(input); - } - - if (path == null) path = name; - MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); - if (mesh == null) return null; - mesh.Path = path; - mesh.r = ((color & 0xff000000) >> 24) / 255f; - mesh.g = ((color & 0x00ff0000) >> 16) / 255f; - mesh.b = ((color & 0x0000ff00) >> 8) / 255f; - mesh.a = ((color & 0x000000ff)) / 255f; - mesh.inheritFFD = inheritFFD; - if (nonessential) { - mesh.Width = width * scale; - mesh.Height = height * scale; - } - linkedMeshes.Add(new SkeletonJson.LinkedMesh(mesh, skinName, slotIndex, parent)); - return mesh; - } - case AttachmentType.weightedmesh: { - String path = ReadString(input); - int color = ReadInt(input); - int vertexCount = ReadVarint(input, true); - float[] uvs = ReadFloatArray(input, vertexCount * 2, 1); - int[] triangles = ReadShortArray(input); - var weights = new List(uvs.Length * 3 * 3); - var bones = new List(uvs.Length * 3); - for (int i = 0; i < vertexCount; i++) { - int boneCount = (int)ReadFloat(input); - bones.Add(boneCount); - for (int ii = 0; ii < boneCount; ii++) { - bones.Add((int)ReadFloat(input)); - weights.Add(ReadFloat(input) * scale); - weights.Add(ReadFloat(input) * scale); - weights.Add(ReadFloat(input)); - } - } - int hullLength = ReadVarint(input, true); - int[] edges = null; - float width = 0, height = 0; - if (nonessential) { - edges = ReadShortArray(input); - width = ReadFloat(input); - height = ReadFloat(input); - } - - if (path == null) path = name; - WeightedMeshAttachment mesh = attachmentLoader.NewWeightedMeshAttachment(skin, name, path); - if (mesh == null) return null; - mesh.Path = path; - mesh.r = ((color & 0xff000000) >> 24) / 255f; - mesh.g = ((color & 0x00ff0000) >> 16) / 255f; - mesh.b = ((color & 0x0000ff00) >> 8) / 255f; - mesh.a = ((color & 0x000000ff)) / 255f; - mesh.bones = bones.ToArray(); - mesh.weights = weights.ToArray(); - mesh.triangles = triangles; - mesh.regionUVs = uvs; - mesh.UpdateUVs(); - mesh.HullLength = hullLength * 2; - if (nonessential) { - mesh.Edges = edges; - mesh.Width = width * scale; - mesh.Height = height * scale; - } - // - return mesh; - } - case AttachmentType.weightedlinkedmesh: { - String path = ReadString(input); - int color = ReadInt(input); - String skinName = ReadString(input); - String parent = ReadString(input); - bool inheritFFD = ReadBoolean(input); - float width = 0, height = 0; - if (nonessential) { - width = ReadFloat(input); - height = ReadFloat(input); - } - - if (path == null) path = name; - WeightedMeshAttachment mesh = attachmentLoader.NewWeightedMeshAttachment(skin, name, path); - if (mesh == null) return null; - mesh.Path = path; - mesh.r = ((color & 0xff000000) >> 24) / 255f; - mesh.g = ((color & 0x00ff0000) >> 16) / 255f; - mesh.b = ((color & 0x0000ff00) >> 8) / 255f; - mesh.a = ((color & 0x000000ff)) / 255f; - mesh.inheritFFD = inheritFFD; - if (nonessential) { - mesh.Width = width * scale; - mesh.Height = height * scale; - } - linkedMeshes.Add(new SkeletonJson.LinkedMesh(mesh, skinName, slotIndex, parent)); - return mesh; - } - } - return null; - } - - private float[] ReadFloatArray (Stream input, int n, float scale) { - float[] array = new float[n]; - if (scale == 1) { - for (int i = 0; i < n; i++) - array[i] = ReadFloat(input); - } else { - for (int i = 0; i < n; i++) - array[i] = ReadFloat(input) * scale; - } - return array; - } - - private int[] ReadShortArray (Stream input) { - int n = ReadVarint(input, true); - int[] array = new int[n]; - for (int i = 0; i < n; i++) - array[i] = (input.ReadByte() << 8) | input.ReadByte(); - return array; - } - - private void ReadAnimation (String name, Stream input, SkeletonData skeletonData) { - var timelines = new ExposedList(); - float scale = Scale; - float duration = 0; - - // 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++) { - int timelineType = input.ReadByte(); - int frameCount = ReadVarint(input, true); - switch (timelineType) { - case TIMELINE_COLOR: { - ColorTimeline timeline = new ColorTimeline(frameCount); - timeline.slotIndex = slotIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - float time = ReadFloat(input); - int color = ReadInt(input); - float r = ((color & 0xff000000) >> 24) / 255f; - float g = ((color & 0x00ff0000) >> 16) / 255f; - float b = ((color & 0x0000ff00) >> 8) / 255f; - float a = ((color & 0x000000ff)) / 255f; - timeline.SetFrame(frameIndex, time, r, g, b, a); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[frameCount * 5 - 5]); - break; - } - case TIMELINE_ATTACHMENT: { - AttachmentTimeline timeline = new AttachmentTimeline(frameCount); - timeline.slotIndex = slotIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) - timeline.SetFrame(frameIndex, ReadFloat(input), ReadString(input)); - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[frameCount - 1]); - break; - } - } - } - } - - // 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++) { - int timelineType = input.ReadByte(); - int frameCount = ReadVarint(input, true); - switch (timelineType) { - case TIMELINE_ROTATE: { - RotateTimeline timeline = new RotateTimeline(frameCount); - timeline.boneIndex = boneIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input)); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[frameCount * 2 - 2]); - break; - } - case TIMELINE_TRANSLATE: - case TIMELINE_SCALE: - case TIMELINE_SHEAR: { - TranslateTimeline timeline; - float timelineScale = 1; - if (timelineType == TIMELINE_SCALE) - timeline = new ScaleTimeline(frameCount); - else if (timelineType == TIMELINE_SHEAR) - timeline = new ShearTimeline(frameCount); - else { - timeline = new TranslateTimeline(frameCount); - timelineScale = scale; - } - timeline.boneIndex = boneIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input) * timelineScale, ReadFloat(input) - * timelineScale); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[frameCount * 3 - 3]); - break; - } - } - } - } - - // IK timelines. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - IkConstraintData constraint = skeletonData.ikConstraints.Items[ReadVarint(input, true)]; - int frameCount = ReadVarint(input, true); - IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount); - timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(constraint); - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadSByte(input)); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[frameCount * 3 - 3]); - } - - // Transform constraint timelines. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - TransformConstraintData constraint = skeletonData.transformConstraints.Items[ReadVarint(input, true)]; - int frameCount = ReadVarint(input, true); - TransformConstraintTimeline timeline = new TransformConstraintTimeline(frameCount); - timeline.transformConstraintIndex = skeletonData.transformConstraints.IndexOf(constraint); - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input)); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[frameCount * 5 - 5]); - } - - // FFD timelines. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - Skin skin = skeletonData.skins.Items[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++) { - Attachment attachment = skin.GetAttachment(slotIndex, ReadString(input)); - int frameCount = ReadVarint(input, true); - FfdTimeline timeline = new FfdTimeline(frameCount); - timeline.slotIndex = slotIndex; - timeline.attachment = attachment; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - float time = ReadFloat(input); - - float[] vertices; - int vertexCount; - if (attachment is MeshAttachment) - vertexCount = ((MeshAttachment)attachment).vertices.Length; - else - vertexCount = ((WeightedMeshAttachment)attachment).weights.Length / 3 * 2; - - int end = ReadVarint(input, true); - if (end == 0) { - if (attachment is MeshAttachment) - vertices = ((MeshAttachment)attachment).vertices; - else - vertices = new float[vertexCount]; - } else { - vertices = new float[vertexCount]; - int start = ReadVarint(input, true); - end += start; - if (scale == 1) { - for (int v = start; v < end; v++) - vertices[v] = ReadFloat(input); - } else { - for (int v = start; v < end; v++) - vertices[v] = ReadFloat(input) * scale; - } - if (attachment is MeshAttachment) { - float[] meshVertices = ((MeshAttachment)attachment).vertices; - for (int v = 0, vn = vertices.Length; v < vn; v++) - vertices[v] += meshVertices[v]; - } - } - - timeline.SetFrame(frameIndex, time, vertices); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[frameCount - 1]); - } - } - } - - // Draw order timeline. - int drawOrderCount = ReadVarint(input, true); - if (drawOrderCount > 0) { - DrawOrderTimeline timeline = new DrawOrderTimeline(drawOrderCount); - int slotCount = skeletonData.slots.Count; - for (int i = 0; i < drawOrderCount; i++) { - float time = ReadFloat(input); - int offsetCount = ReadVarint(input, true); - int[] drawOrder = new int[slotCount]; - for (int ii = slotCount - 1; ii >= 0; ii--) - drawOrder[ii] = -1; - int[] unchanged = new int[slotCount - offsetCount]; - int originalIndex = 0, unchangedIndex = 0; - for (int ii = 0; ii < offsetCount; ii++) { - int slotIndex = ReadVarint(input, true); - // Collect unchanged items. - while (originalIndex != slotIndex) - unchanged[unchangedIndex++] = originalIndex++; - // Set changed items. - drawOrder[originalIndex + ReadVarint(input, true)] = originalIndex++; - } - // Collect remaining unchanged items. - while (originalIndex < slotCount) - unchanged[unchangedIndex++] = originalIndex++; - // Fill in unchanged items. - for (int ii = slotCount - 1; ii >= 0; ii--) - if (drawOrder[ii] == -1) drawOrder[ii] = unchanged[--unchangedIndex]; - timeline.SetFrame(i, time, drawOrder); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[drawOrderCount - 1]); - } - - // Event timeline. - int eventCount = ReadVarint(input, true); - if (eventCount > 0) { - EventTimeline timeline = new EventTimeline(eventCount); - for (int i = 0; i < eventCount; i++) { - float time = ReadFloat(input); - EventData eventData = skeletonData.events.Items[ReadVarint(input, true)]; - Event e = new Event(time, eventData); - e.Int = ReadVarint(input, false); - e.Float = ReadFloat(input); - e.String = ReadBoolean(input) ? ReadString(input) : eventData.String; - timeline.SetFrame(i, e); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[eventCount - 1]); - } - - timelines.TrimExcess(); - skeletonData.animations.Add(new Animation(name, timelines, duration)); - } - - private void ReadCurve (Stream input, int frameIndex, CurveTimeline timeline) { - switch (input.ReadByte()) { - case CURVE_STEPPED: - timeline.SetStepped(frameIndex); - break; - case CURVE_BEZIER: - timeline.SetCurve(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input)); - break; - } - } - - private static sbyte ReadSByte (Stream input) { - int value = input.ReadByte(); - if (value == -1) throw new EndOfStreamException(); - return (sbyte)value; - } - - private static bool ReadBoolean (Stream input) { - return input.ReadByte() != 0; - } - - private float ReadFloat (Stream input) { - buffer[3] = (byte)input.ReadByte(); - buffer[2] = (byte)input.ReadByte(); - buffer[1] = (byte)input.ReadByte(); - buffer[0] = (byte)input.ReadByte(); - return BitConverter.ToSingle(buffer, 0); - } - - private static int ReadInt (Stream input) { - return (input.ReadByte() << 24) + (input.ReadByte() << 16) + (input.ReadByte() << 8) + input.ReadByte(); - } - - private static int ReadVarint (Stream input, bool optimizePositive) { - int b = input.ReadByte(); - int result = b & 0x7F; - if ((b & 0x80) != 0) { - b = input.ReadByte(); - result |= (b & 0x7F) << 7; - if ((b & 0x80) != 0) { - b = input.ReadByte(); - result |= (b & 0x7F) << 14; - if ((b & 0x80) != 0) { - b = input.ReadByte(); - result |= (b & 0x7F) << 21; - if ((b & 0x80) != 0) result |= (input.ReadByte() & 0x7F) << 28; - } - } - } - return optimizePositive ? result : ((result >> 1) ^ -(result & 1)); - } - - private string ReadString (Stream input) { - int byteCount = ReadVarint(input, true); - switch (byteCount) { - case 0: - return null; - case 1: - return ""; - } - byteCount--; - byte[] buffer = this.buffer; - if (buffer.Length < byteCount) buffer = new byte[byteCount]; - ReadFully(input, buffer, 0, byteCount); - return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount); - } - - private static void ReadFully (Stream input, byte[] buffer, int offset, int length) { - while (length > 0) { - int count = input.Read(buffer, offset, length); - if (count <= 0) throw new EndOfStreamException(); - offset += count; - length -= count; - } - } - } +#else + using (var input = new BufferedStream(new FileStream(path, FileMode.Open))) + { +#endif // WINDOWS_PHONE + SkeletonData skeletonData = ReadSkeletonData(input); + skeletonData.name = Path.GetFileNameWithoutExtension(path); + return skeletonData; + } + } + +#endif // WINDOWS_STOREAPP + + public SkeletonData ReadSkeletonData(Stream input) + { + if (input == null) throw new ArgumentNullException("input"); + float scale = Scale; + + var skeletonData = new SkeletonData(); + skeletonData.hash = ReadString(input); + if (skeletonData.hash.Length == 0) skeletonData.hash = null; + skeletonData.version = ReadString(input); + if (skeletonData.version.Length == 0) skeletonData.version = null; + skeletonData.width = ReadFloat(input); + skeletonData.height = ReadFloat(input); + + bool nonessential = ReadBoolean(input); + + if (nonessential) + { + skeletonData.imagesPath = ReadString(input); + if (skeletonData.imagesPath.Length == 0) skeletonData.imagesPath = null; + } + + // Bones. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + String name = ReadString(input); + BoneData parent = i == 0 ? null : skeletonData.bones.Items[ReadVarint(input, true)]; + BoneData boneData = new BoneData(name, parent); + boneData.rotation = ReadFloat(input); + boneData.x = ReadFloat(input) * scale; + boneData.y = ReadFloat(input) * scale; + boneData.scaleX = ReadFloat(input); + boneData.scaleY = ReadFloat(input); + boneData.shearX = ReadFloat(input); + boneData.shearY = ReadFloat(input); + boneData.length = ReadFloat(input) * scale; + boneData.inheritRotation = ReadBoolean(input); + boneData.inheritScale = ReadBoolean(input); + if (nonessential) ReadInt(input); // Skip bone color. + skeletonData.bones.Add(boneData); + } + + // IK constraints. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + IkConstraintData ikConstraintData = new IkConstraintData(ReadString(input)); + for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) + ikConstraintData.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]); + ikConstraintData.target = skeletonData.bones.Items[ReadVarint(input, true)]; + ikConstraintData.mix = ReadFloat(input); + ikConstraintData.bendDirection = ReadSByte(input); + skeletonData.ikConstraints.Add(ikConstraintData); + } + + // Transform constraints. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + TransformConstraintData transformConstraintData = new TransformConstraintData(ReadString(input)); + transformConstraintData.bone = skeletonData.bones.Items[ReadVarint(input, true)]; + transformConstraintData.target = skeletonData.bones.Items[ReadVarint(input, true)]; + transformConstraintData.offsetRotation = ReadFloat(input); + transformConstraintData.offsetX = ReadFloat(input) * scale; + transformConstraintData.offsetY = ReadFloat(input) * scale; + transformConstraintData.offsetScaleX = ReadFloat(input); + transformConstraintData.offsetScaleY = ReadFloat(input); + transformConstraintData.offsetShearY = ReadFloat(input); + transformConstraintData.rotateMix = ReadFloat(input); + transformConstraintData.translateMix = ReadFloat(input); + transformConstraintData.scaleMix = ReadFloat(input); + transformConstraintData.shearMix = ReadFloat(input); + skeletonData.transformConstraints.Add(transformConstraintData); + } + + // Slots. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + String slotName = ReadString(input); + BoneData boneData = skeletonData.bones.Items[ReadVarint(input, true)]; + SlotData slotData = new SlotData(slotName, boneData); + int color = ReadInt(input); + slotData.r = ((color & 0xff000000) >> 24) / 255f; + slotData.g = ((color & 0x00ff0000) >> 16) / 255f; + slotData.b = ((color & 0x0000ff00) >> 8) / 255f; + slotData.a = ((color & 0x000000ff)) / 255f; + slotData.attachmentName = ReadString(input); + slotData.blendMode = (BlendMode)ReadVarint(input, true); + skeletonData.slots.Add(slotData); + } + + // Default skin. + Skin defaultSkin = ReadSkin(input, "default", nonessential); + if (defaultSkin != null) + { + skeletonData.defaultSkin = defaultSkin; + skeletonData.skins.Add(defaultSkin); + } + + // Skins. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + skeletonData.skins.Add(ReadSkin(input, ReadString(input), nonessential)); + + // Linked meshes. + for (int i = 0, n = linkedMeshes.Count; i < n; i++) + { + SkeletonJson.LinkedMesh linkedMesh = linkedMeshes[i]; + Skin skin = linkedMesh.skin == null ? skeletonData.DefaultSkin : skeletonData.FindSkin(linkedMesh.skin); + if (skin == null) throw new Exception("Skin not found: " + linkedMesh.skin); + Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); + if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); + if (linkedMesh.mesh is MeshAttachment) + { + MeshAttachment mesh = (MeshAttachment)linkedMesh.mesh; + mesh.ParentMesh = (MeshAttachment)parent; + mesh.UpdateUVs(); + } + else + { + WeightedMeshAttachment mesh = (WeightedMeshAttachment)linkedMesh.mesh; + mesh.ParentMesh = (WeightedMeshAttachment)parent; + mesh.UpdateUVs(); + } + } + linkedMeshes.Clear(); + + // Events. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + EventData eventData = new EventData(ReadString(input)); + eventData.Int = ReadVarint(input, false); + eventData.Float = ReadFloat(input); + eventData.String = ReadString(input); + skeletonData.events.Add(eventData); + } + + // Animations. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + ReadAnimation(ReadString(input), input, skeletonData); + + skeletonData.bones.TrimExcess(); + skeletonData.slots.TrimExcess(); + skeletonData.skins.TrimExcess(); + skeletonData.events.TrimExcess(); + skeletonData.animations.TrimExcess(); + skeletonData.ikConstraints.TrimExcess(); + return skeletonData; + } + + /** @return May be null. */ + private Skin ReadSkin(Stream input, String skinName, bool nonessential) + { + int slotCount = ReadVarint(input, true); + if (slotCount == 0) return null; + Skin skin = new Skin(skinName); + 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 = ReadString(input); + skin.AddAttachment(slotIndex, name, ReadAttachment(input, skin, slotIndex, name, nonessential)); + } + } + return skin; + } + + private Attachment ReadAttachment(Stream input, Skin skin, int slotIndex, String attachmentName, bool nonessential) + { + float scale = Scale; + + String name = ReadString(input); + if (name == null) name = attachmentName; + + AttachmentType type = (AttachmentType)input.ReadByte(); + switch (type) + { + case AttachmentType.region: + { + String path = ReadString(input); + float rotation = ReadFloat(input); + float x = ReadFloat(input); + float y = ReadFloat(input); + float scaleX = ReadFloat(input); + float scaleY = ReadFloat(input); + float width = ReadFloat(input); + float height = ReadFloat(input); + int color = ReadInt(input); + + if (path == null) path = name; + RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); + if (region == null) return null; + region.Path = path; + region.x = x * scale; + region.y = y * scale; + region.scaleX = scaleX; + region.scaleY = scaleY; + region.rotation = rotation; + region.width = width * scale; + region.height = height * scale; + region.r = ((color & 0xff000000) >> 24) / 255f; + region.g = ((color & 0x00ff0000) >> 16) / 255f; + region.b = ((color & 0x0000ff00) >> 8) / 255f; + region.a = ((color & 0x000000ff)) / 255f; + region.UpdateOffset(); + return region; + } + case AttachmentType.boundingbox: + { + float[] vertices = ReadFloatArray(input, ReadVarint(input, true) * 2, scale); + BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); + if (box == null) return null; + box.vertices = vertices; + return box; + } + case AttachmentType.mesh: + { + String path = ReadString(input); + int color = ReadInt(input); + int hullLength = 0; + int verticesLength = ReadVarint(input, true) * 2; + float[] uvs = ReadFloatArray(input, verticesLength, 1); + int[] triangles = ReadShortArray(input); + float[] vertices = ReadFloatArray(input, verticesLength, scale); + hullLength = ReadVarint(input, true); + int[] edges = null; + float width = 0, height = 0; + if (nonessential) + { + edges = ReadShortArray(input); + width = ReadFloat(input); + height = ReadFloat(input); + } + + if (path == null) path = name; + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); + if (mesh == null) return null; + mesh.Path = path; + mesh.r = ((color & 0xff000000) >> 24) / 255f; + mesh.g = ((color & 0x00ff0000) >> 16) / 255f; + mesh.b = ((color & 0x0000ff00) >> 8) / 255f; + mesh.a = ((color & 0x000000ff)) / 255f; + mesh.vertices = vertices; + mesh.triangles = triangles; + mesh.regionUVs = uvs; + mesh.UpdateUVs(); + mesh.HullLength = hullLength; + if (nonessential) + { + mesh.Edges = edges; + mesh.Width = width * scale; + mesh.Height = height * scale; + } + return mesh; + } + case AttachmentType.linkedmesh: + { + String path = ReadString(input); + int color = ReadInt(input); + String skinName = ReadString(input); + String parent = ReadString(input); + bool inheritFFD = ReadBoolean(input); + float width = 0, height = 0; + if (nonessential) + { + width = ReadFloat(input); + height = ReadFloat(input); + } + + if (path == null) path = name; + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); + if (mesh == null) return null; + mesh.Path = path; + mesh.r = ((color & 0xff000000) >> 24) / 255f; + mesh.g = ((color & 0x00ff0000) >> 16) / 255f; + mesh.b = ((color & 0x0000ff00) >> 8) / 255f; + mesh.a = ((color & 0x000000ff)) / 255f; + mesh.inheritFFD = inheritFFD; + if (nonessential) + { + mesh.Width = width * scale; + mesh.Height = height * scale; + } + linkedMeshes.Add(new SkeletonJson.LinkedMesh(mesh, skinName, slotIndex, parent)); + return mesh; + } + case AttachmentType.weightedmesh: + { + String path = ReadString(input); + int color = ReadInt(input); + int vertexCount = ReadVarint(input, true); + float[] uvs = ReadFloatArray(input, vertexCount * 2, 1); + int[] triangles = ReadShortArray(input); + var weights = new List(uvs.Length * 3 * 3); + var bones = new List(uvs.Length * 3); + for (int i = 0; i < vertexCount; i++) + { + int boneCount = (int)ReadFloat(input); + bones.Add(boneCount); + for (int ii = 0; ii < boneCount; ii++) + { + bones.Add((int)ReadFloat(input)); + weights.Add(ReadFloat(input) * scale); + weights.Add(ReadFloat(input) * scale); + weights.Add(ReadFloat(input)); + } + } + int hullLength = ReadVarint(input, true); + int[] edges = null; + float width = 0, height = 0; + if (nonessential) + { + edges = ReadShortArray(input); + width = ReadFloat(input); + height = ReadFloat(input); + } + + if (path == null) path = name; + WeightedMeshAttachment mesh = attachmentLoader.NewWeightedMeshAttachment(skin, name, path); + if (mesh == null) return null; + mesh.Path = path; + mesh.r = ((color & 0xff000000) >> 24) / 255f; + mesh.g = ((color & 0x00ff0000) >> 16) / 255f; + mesh.b = ((color & 0x0000ff00) >> 8) / 255f; + mesh.a = ((color & 0x000000ff)) / 255f; + mesh.bones = bones.ToArray(); + mesh.weights = weights.ToArray(); + mesh.triangles = triangles; + mesh.regionUVs = uvs; + mesh.UpdateUVs(); + mesh.HullLength = hullLength * 2; + if (nonessential) + { + mesh.Edges = edges; + mesh.Width = width * scale; + mesh.Height = height * scale; + } + // + return mesh; + } + case AttachmentType.weightedlinkedmesh: + { + String path = ReadString(input); + int color = ReadInt(input); + String skinName = ReadString(input); + String parent = ReadString(input); + bool inheritFFD = ReadBoolean(input); + float width = 0, height = 0; + if (nonessential) + { + width = ReadFloat(input); + height = ReadFloat(input); + } + + if (path == null) path = name; + WeightedMeshAttachment mesh = attachmentLoader.NewWeightedMeshAttachment(skin, name, path); + if (mesh == null) return null; + mesh.Path = path; + mesh.r = ((color & 0xff000000) >> 24) / 255f; + mesh.g = ((color & 0x00ff0000) >> 16) / 255f; + mesh.b = ((color & 0x0000ff00) >> 8) / 255f; + mesh.a = ((color & 0x000000ff)) / 255f; + mesh.inheritFFD = inheritFFD; + if (nonessential) + { + mesh.Width = width * scale; + mesh.Height = height * scale; + } + linkedMeshes.Add(new SkeletonJson.LinkedMesh(mesh, skinName, slotIndex, parent)); + return mesh; + } + } + return null; + } + + private float[] ReadFloatArray(Stream input, int n, float scale) + { + float[] array = new float[n]; + if (scale == 1) + { + for (int i = 0; i < n; i++) + array[i] = ReadFloat(input); + } + else + { + for (int i = 0; i < n; i++) + array[i] = ReadFloat(input) * scale; + } + return array; + } + + private int[] ReadShortArray(Stream input) + { + int n = ReadVarint(input, true); + int[] array = new int[n]; + for (int i = 0; i < n; i++) + array[i] = (input.ReadByte() << 8) | input.ReadByte(); + return array; + } + + private void ReadAnimation(String name, Stream input, SkeletonData skeletonData) + { + var timelines = new ExposedList(); + float scale = Scale; + float duration = 0; + + // 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++) + { + int timelineType = input.ReadByte(); + int frameCount = ReadVarint(input, true); + switch (timelineType) + { + case TIMELINE_COLOR: + { + ColorTimeline timeline = new ColorTimeline(frameCount); + timeline.slotIndex = slotIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + float time = ReadFloat(input); + int color = ReadInt(input); + float r = ((color & 0xff000000) >> 24) / 255f; + float g = ((color & 0x00ff0000) >> 16) / 255f; + float b = ((color & 0x0000ff00) >> 8) / 255f; + float a = ((color & 0x000000ff)) / 255f; + timeline.SetFrame(frameIndex, time, r, g, b, a); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[frameCount * 5 - 5]); + break; + } + case TIMELINE_ATTACHMENT: + { + AttachmentTimeline timeline = new AttachmentTimeline(frameCount); + timeline.slotIndex = slotIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + timeline.SetFrame(frameIndex, ReadFloat(input), ReadString(input)); + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[frameCount - 1]); + break; + } + } + } + } + + // 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++) + { + int timelineType = input.ReadByte(); + int frameCount = ReadVarint(input, true); + switch (timelineType) + { + case TIMELINE_ROTATE: + { + RotateTimeline timeline = new RotateTimeline(frameCount); + timeline.boneIndex = boneIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input)); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[frameCount * 2 - 2]); + break; + } + case TIMELINE_TRANSLATE: + case TIMELINE_SCALE: + case TIMELINE_SHEAR: + { + TranslateTimeline timeline; + float timelineScale = 1; + if (timelineType == TIMELINE_SCALE) + timeline = new ScaleTimeline(frameCount); + else if (timelineType == TIMELINE_SHEAR) + timeline = new ShearTimeline(frameCount); + else + { + timeline = new TranslateTimeline(frameCount); + timelineScale = scale; + } + timeline.boneIndex = boneIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input) * timelineScale, ReadFloat(input) + * timelineScale); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[frameCount * 3 - 3]); + break; + } + } + } + } + + // IK timelines. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + IkConstraintData constraint = skeletonData.ikConstraints.Items[ReadVarint(input, true)]; + int frameCount = ReadVarint(input, true); + IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount); + timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(constraint); + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadSByte(input)); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[frameCount * 3 - 3]); + } + + // Transform constraint timelines. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + TransformConstraintData constraint = skeletonData.transformConstraints.Items[ReadVarint(input, true)]; + int frameCount = ReadVarint(input, true); + TransformConstraintTimeline timeline = new TransformConstraintTimeline(frameCount); + timeline.transformConstraintIndex = skeletonData.transformConstraints.IndexOf(constraint); + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input)); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[frameCount * 5 - 5]); + } + + // FFD timelines. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + Skin skin = skeletonData.skins.Items[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++) + { + Attachment attachment = skin.GetAttachment(slotIndex, ReadString(input)); + int frameCount = ReadVarint(input, true); + FfdTimeline timeline = new FfdTimeline(frameCount); + timeline.slotIndex = slotIndex; + timeline.attachment = attachment; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + float time = ReadFloat(input); + + float[] vertices; + int vertexCount; + if (attachment is MeshAttachment) + vertexCount = ((MeshAttachment)attachment).vertices.Length; + else + vertexCount = ((WeightedMeshAttachment)attachment).weights.Length / 3 * 2; + + int end = ReadVarint(input, true); + if (end == 0) + { + if (attachment is MeshAttachment) + vertices = ((MeshAttachment)attachment).vertices; + else + vertices = new float[vertexCount]; + } + else + { + vertices = new float[vertexCount]; + int start = ReadVarint(input, true); + end += start; + if (scale == 1) + { + for (int v = start; v < end; v++) + vertices[v] = ReadFloat(input); + } + else + { + for (int v = start; v < end; v++) + vertices[v] = ReadFloat(input) * scale; + } + if (attachment is MeshAttachment) + { + float[] meshVertices = ((MeshAttachment)attachment).vertices; + for (int v = 0, vn = vertices.Length; v < vn; v++) + vertices[v] += meshVertices[v]; + } + } + + timeline.SetFrame(frameIndex, time, vertices); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[frameCount - 1]); + } + } + } + + // Draw order timeline. + int drawOrderCount = ReadVarint(input, true); + if (drawOrderCount > 0) + { + DrawOrderTimeline timeline = new DrawOrderTimeline(drawOrderCount); + int slotCount = skeletonData.slots.Count; + for (int i = 0; i < drawOrderCount; i++) + { + float time = ReadFloat(input); + int offsetCount = ReadVarint(input, true); + int[] drawOrder = new int[slotCount]; + for (int ii = slotCount - 1; ii >= 0; ii--) + drawOrder[ii] = -1; + int[] unchanged = new int[slotCount - offsetCount]; + int originalIndex = 0, unchangedIndex = 0; + for (int ii = 0; ii < offsetCount; ii++) + { + int slotIndex = ReadVarint(input, true); + // Collect unchanged items. + while (originalIndex != slotIndex) + unchanged[unchangedIndex++] = originalIndex++; + // Set changed items. + drawOrder[originalIndex + ReadVarint(input, true)] = originalIndex++; + } + // Collect remaining unchanged items. + while (originalIndex < slotCount) + unchanged[unchangedIndex++] = originalIndex++; + // Fill in unchanged items. + for (int ii = slotCount - 1; ii >= 0; ii--) + if (drawOrder[ii] == -1) drawOrder[ii] = unchanged[--unchangedIndex]; + timeline.SetFrame(i, time, drawOrder); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[drawOrderCount - 1]); + } + + // Event timeline. + int eventCount = ReadVarint(input, true); + if (eventCount > 0) + { + EventTimeline timeline = new EventTimeline(eventCount); + for (int i = 0; i < eventCount; i++) + { + float time = ReadFloat(input); + EventData eventData = skeletonData.events.Items[ReadVarint(input, true)]; + Event e = new Event(time, eventData); + e.Int = ReadVarint(input, false); + e.Float = ReadFloat(input); + e.String = ReadBoolean(input) ? ReadString(input) : eventData.String; + timeline.SetFrame(i, e); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[eventCount - 1]); + } + + timelines.TrimExcess(); + skeletonData.animations.Add(new Animation(name, timelines, duration)); + } + + private void ReadCurve(Stream input, int frameIndex, CurveTimeline timeline) + { + switch (input.ReadByte()) + { + case CURVE_STEPPED: + timeline.SetStepped(frameIndex); + break; + case CURVE_BEZIER: + timeline.SetCurve(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input)); + break; + } + } + + private static sbyte ReadSByte(Stream input) + { + int value = input.ReadByte(); + if (value == -1) throw new EndOfStreamException(); + return (sbyte)value; + } + + private static bool ReadBoolean(Stream input) + { + return input.ReadByte() != 0; + } + + private float ReadFloat(Stream input) + { + buffer[3] = (byte)input.ReadByte(); + buffer[2] = (byte)input.ReadByte(); + buffer[1] = (byte)input.ReadByte(); + buffer[0] = (byte)input.ReadByte(); + return BitConverter.ToSingle(buffer, 0); + } + + private static int ReadInt(Stream input) + { + return (input.ReadByte() << 24) + (input.ReadByte() << 16) + (input.ReadByte() << 8) + input.ReadByte(); + } + + private static int ReadVarint(Stream input, bool optimizePositive) + { + int b = input.ReadByte(); + int result = b & 0x7F; + if ((b & 0x80) != 0) + { + b = input.ReadByte(); + result |= (b & 0x7F) << 7; + if ((b & 0x80) != 0) + { + b = input.ReadByte(); + result |= (b & 0x7F) << 14; + if ((b & 0x80) != 0) + { + b = input.ReadByte(); + result |= (b & 0x7F) << 21; + if ((b & 0x80) != 0) result |= (input.ReadByte() & 0x7F) << 28; + } + } + } + return optimizePositive ? result : ((result >> 1) ^ -(result & 1)); + } + + private string ReadString(Stream input) + { + int byteCount = ReadVarint(input, true); + switch (byteCount) + { + case 0: + return null; + case 1: + return ""; + } + byteCount--; + byte[] buffer = this.buffer; + if (buffer.Length < byteCount) buffer = new byte[byteCount]; + ReadFully(input, buffer, 0, byteCount); + return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount); + } + + private static void ReadFully(Stream input, byte[] buffer, int offset, int length) + { + while (length > 0) + { + int count = input.Read(buffer, offset, length); + if (count <= 0) throw new EndOfStreamException(); + offset += count; + length -= count; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/SkeletonBounds.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/SkeletonBounds.cs index cd5c0f9..5dfd555 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/SkeletonBounds.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/SkeletonBounds.cs @@ -30,187 +30,210 @@ *****************************************************************************/ using System; -using System.Collections.Generic; - -namespace Spine3_2_xx { - public class SkeletonBounds { - private ExposedList polygonPool = new ExposedList(); - private float minX, minY, maxX, maxY; - - public ExposedList BoundingBoxes { get; private set; } - public ExposedList Polygons { get; private set; } - public float MinX { get { return minX; } set { minX = value; } } - public float MinY { get { return minY; } set { minY = value; } } - public float MaxX { get { return maxX; } set { maxX = value; } } - public float MaxY { get { return maxY; } set { maxY = value; } } - public float Width { get { return maxX - minX; } } - public float Height { get { return maxY - minY; } } - - public SkeletonBounds () { - BoundingBoxes = new ExposedList(); - Polygons = new ExposedList(); - } - - public void Update (Skeleton skeleton, bool updateAabb) { - ExposedList boundingBoxes = BoundingBoxes; - ExposedList polygons = Polygons; - ExposedList slots = skeleton.slots; - int slotCount = slots.Count; - - boundingBoxes.Clear(); - for (int i = 0, n = polygons.Count; i < n; i++) - polygonPool.Add(polygons.Items[i]); - polygons.Clear(); - - for (int i = 0; i < slotCount; i++) { - Slot slot = slots.Items[i]; - BoundingBoxAttachment boundingBox = slot.attachment as BoundingBoxAttachment; - if (boundingBox == null) continue; - boundingBoxes.Add(boundingBox); - - Polygon polygon = null; - int poolCount = polygonPool.Count; - if (poolCount > 0) { - polygon = polygonPool.Items[poolCount - 1]; - polygonPool.RemoveAt(poolCount - 1); - } else - polygon = new Polygon(); - polygons.Add(polygon); - - int count = boundingBox.Vertices.Length; - polygon.Count = count; - if (polygon.Vertices.Length < count) polygon.Vertices = new float[count]; - boundingBox.ComputeWorldVertices(slot.bone, polygon.Vertices); - } - - if (updateAabb) aabbCompute(); - } - - private void aabbCompute () { - float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; - ExposedList polygons = Polygons; - for (int i = 0, n = polygons.Count; i < n; i++) { - Polygon polygon = polygons.Items[i]; - float[] vertices = polygon.Vertices; - for (int ii = 0, nn = polygon.Count; ii < nn; ii += 2) { - float x = vertices[ii]; - float y = vertices[ii + 1]; - minX = Math.Min(minX, x); - minY = Math.Min(minY, y); - maxX = Math.Max(maxX, x); - maxY = Math.Max(maxY, y); - } - } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; - } - - - /// Returns true if the axis aligned bounding box contains the point. - public bool AabbContainsPoint (float x, float y) { - return x >= minX && x <= maxX && y >= minY && y <= maxY; - } - - /// Returns true if the axis aligned bounding box intersects the line segment. - public bool AabbIntersectsSegment (float x1, float y1, float x2, float y2) { - float minX = this.minX; - float minY = this.minY; - float maxX = this.maxX; - float maxY = this.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; - } - - /// Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds. - public bool AabbIntersectsSkeleton (SkeletonBounds bounds) { - return minX < bounds.maxX && maxX > bounds.minX && minY < bounds.maxY && maxY > bounds.minY; - } - - /// Returns true if the polygon contains the point. - public bool ContainsPoint (Polygon polygon, float x, float y) { - float[] 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; - } - - /// 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. - public BoundingBoxAttachment ContainsPoint (float x, float y) { - ExposedList polygons = Polygons; - for (int i = 0, n = polygons.Count; i < n; i++) - if (ContainsPoint(polygons.Items[i], x, y)) return BoundingBoxes.Items[i]; - return null; - } - - /// 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. - public BoundingBoxAttachment IntersectsSegment (float x1, float y1, float x2, float y2) { - ExposedList polygons = Polygons; - for (int i = 0, n = polygons.Count; i < n; i++) - if (IntersectsSegment(polygons.Items[i], x1, y1, x2, y2)) return BoundingBoxes.Items[i]; - return null; - } - - /// Returns true if the polygon contains the line segment. - public bool IntersectsSegment (Polygon polygon, float x1, float y1, float x2, float y2) { - float[] vertices = polygon.Vertices; - int 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 (int 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; - } - - public Polygon getPolygon (BoundingBoxAttachment attachment) { - int index = BoundingBoxes.IndexOf(attachment); - return index == -1 ? null : Polygons.Items[index]; - } - } - - public class Polygon { - public float[] Vertices { get; set; } - public int Count { get; set; } - - public Polygon () { - Vertices = new float[16]; - } - } + +namespace Spine3_2_xx +{ + public class SkeletonBounds + { + private ExposedList polygonPool = new ExposedList(); + private float minX, minY, maxX, maxY; + + public ExposedList BoundingBoxes { get; private set; } + public ExposedList Polygons { get; private set; } + public float MinX { get { return minX; } set { minX = value; } } + public float MinY { get { return minY; } set { minY = value; } } + public float MaxX { get { return maxX; } set { maxX = value; } } + public float MaxY { get { return maxY; } set { maxY = value; } } + public float Width { get { return maxX - minX; } } + public float Height { get { return maxY - minY; } } + + public SkeletonBounds() + { + BoundingBoxes = new ExposedList(); + Polygons = new ExposedList(); + } + + public void Update(Skeleton skeleton, bool updateAabb) + { + ExposedList boundingBoxes = BoundingBoxes; + ExposedList polygons = Polygons; + ExposedList slots = skeleton.slots; + int slotCount = slots.Count; + + boundingBoxes.Clear(); + for (int i = 0, n = polygons.Count; i < n; i++) + polygonPool.Add(polygons.Items[i]); + polygons.Clear(); + + for (int i = 0; i < slotCount; i++) + { + Slot slot = slots.Items[i]; + BoundingBoxAttachment boundingBox = slot.attachment as BoundingBoxAttachment; + if (boundingBox == null) continue; + boundingBoxes.Add(boundingBox); + + Polygon polygon = null; + int poolCount = polygonPool.Count; + if (poolCount > 0) + { + polygon = polygonPool.Items[poolCount - 1]; + polygonPool.RemoveAt(poolCount - 1); + } + else + polygon = new Polygon(); + polygons.Add(polygon); + + int count = boundingBox.Vertices.Length; + polygon.Count = count; + if (polygon.Vertices.Length < count) polygon.Vertices = new float[count]; + boundingBox.ComputeWorldVertices(slot.bone, polygon.Vertices); + } + + if (updateAabb) aabbCompute(); + } + + private void aabbCompute() + { + float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; + ExposedList polygons = Polygons; + for (int i = 0, n = polygons.Count; i < n; i++) + { + Polygon polygon = polygons.Items[i]; + float[] vertices = polygon.Vertices; + for (int ii = 0, nn = polygon.Count; ii < nn; ii += 2) + { + float x = vertices[ii]; + float y = vertices[ii + 1]; + minX = Math.Min(minX, x); + minY = Math.Min(minY, y); + maxX = Math.Max(maxX, x); + maxY = Math.Max(maxY, y); + } + } + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + + /// Returns true if the axis aligned bounding box contains the point. + public bool AabbContainsPoint(float x, float y) + { + return x >= minX && x <= maxX && y >= minY && y <= maxY; + } + + /// Returns true if the axis aligned bounding box intersects the line segment. + public bool AabbIntersectsSegment(float x1, float y1, float x2, float y2) + { + float minX = this.minX; + float minY = this.minY; + float maxX = this.maxX; + float maxY = this.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; + } + + /// Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds. + public bool AabbIntersectsSkeleton(SkeletonBounds bounds) + { + return minX < bounds.maxX && maxX > bounds.minX && minY < bounds.maxY && maxY > bounds.minY; + } + + /// Returns true if the polygon contains the point. + public bool ContainsPoint(Polygon polygon, float x, float y) + { + float[] 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; + } + + /// 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. + public BoundingBoxAttachment ContainsPoint(float x, float y) + { + ExposedList polygons = Polygons; + for (int i = 0, n = polygons.Count; i < n; i++) + if (ContainsPoint(polygons.Items[i], x, y)) return BoundingBoxes.Items[i]; + return null; + } + + /// 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. + public BoundingBoxAttachment IntersectsSegment(float x1, float y1, float x2, float y2) + { + ExposedList polygons = Polygons; + for (int i = 0, n = polygons.Count; i < n; i++) + if (IntersectsSegment(polygons.Items[i], x1, y1, x2, y2)) return BoundingBoxes.Items[i]; + return null; + } + + /// Returns true if the polygon contains the line segment. + public bool IntersectsSegment(Polygon polygon, float x1, float y1, float x2, float y2) + { + float[] vertices = polygon.Vertices; + int 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 (int 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; + } + + public Polygon getPolygon(BoundingBoxAttachment attachment) + { + int index = BoundingBoxes.IndexOf(attachment); + return index == -1 ? null : Polygons.Items[index]; + } + } + + public class Polygon + { + public float[] Vertices { get; set; } + public int Count { get; set; } + + public Polygon() + { + Vertices = new float[16]; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/SkeletonData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/SkeletonData.cs index ad3ecbc..5bac51e 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/SkeletonData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/SkeletonData.cs @@ -30,144 +30,160 @@ *****************************************************************************/ using System; -using System.Collections.Generic; - -namespace Spine3_2_xx { - public class SkeletonData { - internal String name; - internal ExposedList bones = new ExposedList(); - internal ExposedList slots = new ExposedList(); - internal ExposedList skins = new ExposedList(); - internal Skin defaultSkin; - internal ExposedList events = new ExposedList(); - internal ExposedList animations = new ExposedList(); - internal ExposedList ikConstraints = new ExposedList(); - internal ExposedList transformConstraints = new ExposedList(); - internal float width, height; - internal String version, hash, imagesPath; - - public String Name { get { return name; } set { name = value; } } - public ExposedList Bones { get { return bones; } } // Ordered parents first. - public ExposedList Slots { get { return slots; } } // Setup pose draw order. - public ExposedList Skins { get { return skins; } set { skins = value; } } - /// May be null. - public Skin DefaultSkin { get { return defaultSkin; } set { defaultSkin = value; } } - public ExposedList Events { get { return events; } set { events = value; } } - public ExposedList Animations { get { return animations; } set { animations = value; } } - public ExposedList IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } } - public float Width { get { return width; } set { width = value; } } - public float Height { get { return height; } set { height = value; } } - /// The Spine version used to export this data. - public String Version { get { return version; } set { version = value; } } - public String Hash { get { return hash; } set { hash = value; } } - - // --- Bones. - - /// May be null. - public BoneData FindBone (String boneName) { - if (boneName == null) throw new ArgumentNullException("boneName cannot be null."); - ExposedList bones = this.bones; - for (int i = 0, n = bones.Count; i < n; i++) { - BoneData bone = bones.Items[i]; - if (bone.name == boneName) return bone; - } - return null; - } - - /// -1 if the bone was not found. - public int FindBoneIndex (String boneName) { - if (boneName == null) throw new ArgumentNullException("boneName cannot be null."); - ExposedList bones = this.bones; - for (int i = 0, n = bones.Count; i < n; i++) - if (bones.Items[i].name == boneName) return i; - return -1; - } - - // --- Slots. - - /// May be null. - public SlotData FindSlot (String slotName) { - if (slotName == null) throw new ArgumentNullException("slotName cannot be null."); - ExposedList slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) { - SlotData slot = slots.Items[i]; - if (slot.name == slotName) return slot; - } - return null; - } - - /// -1 if the bone was not found. - public int FindSlotIndex (String slotName) { - if (slotName == null) throw new ArgumentNullException("slotName cannot be null."); - ExposedList slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) - if (slots.Items[i].name == slotName) return i; - return -1; - } - - // --- Skins. - - /// May be null. - public Skin FindSkin (String skinName) { - if (skinName == null) throw new ArgumentNullException("skinName cannot be null."); - foreach (Skin skin in skins) - if (skin.name == skinName) return skin; - return null; - } - - // --- Events. - - /// May be null. - public EventData FindEvent (String eventDataName) { - if (eventDataName == null) throw new ArgumentNullException("eventDataName cannot be null."); - foreach (EventData eventData in events) - if (eventData.name == eventDataName) return eventData; - return null; - } - - // --- Animations. - - /// May be null. - public Animation FindAnimation (String animationName) { - if (animationName == null) throw new ArgumentNullException("animationName cannot be null."); - ExposedList animations = this.animations; - for (int i = 0, n = animations.Count; i < n; i++) { - Animation animation = animations.Items[i]; - if (animation.name == animationName) return animation; - } - return null; - } - - // --- IK constraints. - - /// May be null. - public IkConstraintData FindIkConstraint (String constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName cannot be null."); - ExposedList ikConstraints = this.ikConstraints; - for (int i = 0, n = ikConstraints.Count; i < n; i++) { - IkConstraintData ikConstraint = ikConstraints.Items[i]; - if (ikConstraint.name == constraintName) return ikConstraint; - } - return null; - } - - // --- Transform constraints. - - /// May be null. - public TransformConstraintData FindTransformConstraint (String constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName cannot be null."); - ExposedList transformConstraints = this.transformConstraints; - for (int i = 0, n = transformConstraints.Count; i < n; i++) { - TransformConstraintData transformConstraint = transformConstraints.Items[i]; - if (transformConstraint.name == constraintName) return transformConstraint; - } - return null; - } - - // --- - - override public String ToString () { - return name ?? base.ToString(); - } - } + +namespace Spine3_2_xx +{ + public class SkeletonData + { + internal String name; + internal ExposedList bones = new ExposedList(); + internal ExposedList slots = new ExposedList(); + internal ExposedList skins = new ExposedList(); + internal Skin defaultSkin; + internal ExposedList events = new ExposedList(); + internal ExposedList animations = new ExposedList(); + internal ExposedList ikConstraints = new ExposedList(); + internal ExposedList transformConstraints = new ExposedList(); + internal float width, height; + internal String version, hash, imagesPath; + + public String Name { get { return name; } set { name = value; } } + public ExposedList Bones { get { return bones; } } // Ordered parents first. + public ExposedList Slots { get { return slots; } } // Setup pose draw order. + public ExposedList Skins { get { return skins; } set { skins = value; } } + /// May be null. + public Skin DefaultSkin { get { return defaultSkin; } set { defaultSkin = value; } } + public ExposedList Events { get { return events; } set { events = value; } } + public ExposedList Animations { get { return animations; } set { animations = value; } } + public ExposedList IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } } + public float Width { get { return width; } set { width = value; } } + public float Height { get { return height; } set { height = value; } } + /// The Spine version used to export this data. + public String Version { get { return version; } set { version = value; } } + public String Hash { get { return hash; } set { hash = value; } } + + // --- Bones. + + /// May be null. + public BoneData FindBone(String boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName cannot be null."); + ExposedList bones = this.bones; + for (int i = 0, n = bones.Count; i < n; i++) + { + BoneData bone = bones.Items[i]; + if (bone.name == boneName) return bone; + } + return null; + } + + /// -1 if the bone was not found. + public int FindBoneIndex(String boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName cannot be null."); + ExposedList bones = this.bones; + for (int i = 0, n = bones.Count; i < n; i++) + if (bones.Items[i].name == boneName) return i; + return -1; + } + + // --- Slots. + + /// May be null. + public SlotData FindSlot(String slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName cannot be null."); + ExposedList slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + { + SlotData slot = slots.Items[i]; + if (slot.name == slotName) return slot; + } + return null; + } + + /// -1 if the bone was not found. + public int FindSlotIndex(String slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName cannot be null."); + ExposedList slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + if (slots.Items[i].name == slotName) return i; + return -1; + } + + // --- Skins. + + /// May be null. + public Skin FindSkin(String skinName) + { + if (skinName == null) throw new ArgumentNullException("skinName cannot be null."); + foreach (Skin skin in skins) + if (skin.name == skinName) return skin; + return null; + } + + // --- Events. + + /// May be null. + public EventData FindEvent(String eventDataName) + { + if (eventDataName == null) throw new ArgumentNullException("eventDataName cannot be null."); + foreach (EventData eventData in events) + if (eventData.name == eventDataName) return eventData; + return null; + } + + // --- Animations. + + /// May be null. + public Animation FindAnimation(String animationName) + { + if (animationName == null) throw new ArgumentNullException("animationName cannot be null."); + ExposedList animations = this.animations; + for (int i = 0, n = animations.Count; i < n; i++) + { + Animation animation = animations.Items[i]; + if (animation.name == animationName) return animation; + } + return null; + } + + // --- IK constraints. + + /// May be null. + public IkConstraintData FindIkConstraint(String constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName cannot be null."); + ExposedList ikConstraints = this.ikConstraints; + for (int i = 0, n = ikConstraints.Count; i < n; i++) + { + IkConstraintData ikConstraint = ikConstraints.Items[i]; + if (ikConstraint.name == constraintName) return ikConstraint; + } + return null; + } + + // --- Transform constraints. + + /// May be null. + public TransformConstraintData FindTransformConstraint(String constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName cannot be null."); + ExposedList transformConstraints = this.transformConstraints; + for (int i = 0, n = transformConstraints.Count; i < n; i++) + { + TransformConstraintData transformConstraint = transformConstraints.Items[i]; + if (transformConstraint.name == constraintName) return transformConstraint; + } + return null; + } + + // --- + + override public String ToString() + { + return name ?? base.ToString(); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/SkeletonJson.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/SkeletonJson.cs index cafbc27..de3552f 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/SkeletonJson.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/SkeletonJson.cs @@ -34,32 +34,36 @@ #endif using System; -using System.IO; using System.Collections.Generic; +using System.IO; #if WINDOWS_STOREAPP using System.Threading.Tasks; using Windows.Storage; #endif -namespace Spine3_2_xx { - public class SkeletonJson { - public float Scale { get; set; } +namespace Spine3_2_xx +{ + public class SkeletonJson + { + public float Scale { get; set; } - private AttachmentLoader attachmentLoader; - private List linkedMeshes = new List(); + private AttachmentLoader attachmentLoader; + private List linkedMeshes = new List(); - public SkeletonJson (params Atlas[] atlasArray) - : this(new AtlasAttachmentLoader(atlasArray)) { - } + public SkeletonJson(params Atlas[] atlasArray) + : this(new AtlasAttachmentLoader(atlasArray)) + { + } - public SkeletonJson (AttachmentLoader attachmentLoader) { - if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader cannot be null."); - this.attachmentLoader = attachmentLoader; - Scale = 1; - } + public SkeletonJson(AttachmentLoader attachmentLoader) + { + if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader cannot be null."); + this.attachmentLoader = attachmentLoader; + Scale = 1; + } - #if !(IS_UNITY) && WINDOWS_STOREAPP +#if !(IS_UNITY) && WINDOWS_STOREAPP private async Task ReadFile(string path) { var folder = Windows.ApplicationModel.Package.Current.InstalledLocation; var file = await folder.GetFileAsync(path).AsTask().ConfigureAwait(false); @@ -73,672 +77,771 @@ private async Task ReadFile(string path) { public SkeletonData ReadSkeletonData (String path) { return this.ReadFile(path).Result; } - #else - public SkeletonData ReadSkeletonData (String path) { - #if WINDOWS_PHONE +#else + public SkeletonData ReadSkeletonData(String path) + { +#if WINDOWS_PHONE Stream stream = Microsoft.Xna.Framework.TitleContainer.OpenStream(path); using (StreamReader reader = new StreamReader(stream)) { - #else - using (StreamReader reader = new StreamReader(path)) { - #endif // WINDOWS_PHONE - SkeletonData skeletonData = ReadSkeletonData(reader); - skeletonData.name = Path.GetFileNameWithoutExtension(path); - return skeletonData; - } - } - #endif // WINDOWS_STOREAPP - - public SkeletonData ReadSkeletonData (TextReader reader) { - if (reader == null) throw new ArgumentNullException("reader cannot be null."); - - var scale = this.Scale; - var skeletonData = new SkeletonData(); - - var root = Json.Deserialize(reader) as Dictionary; - if (root == null) throw new Exception("Invalid JSON."); - - // Skeleton. - if (root.ContainsKey("skeleton")) { - var skeletonMap = (Dictionary)root["skeleton"]; - skeletonData.hash = (String)skeletonMap["hash"]; - skeletonData.version = (String)skeletonMap["spine"]; - skeletonData.width = GetFloat(skeletonMap, "width", 0); - skeletonData.height = GetFloat(skeletonMap, "height", 0); - } - - // Bones. - foreach (Dictionary boneMap in (List)root["bones"]) { - BoneData parent = null; - if (boneMap.ContainsKey("parent")) { - parent = skeletonData.FindBone((String)boneMap["parent"]); - if (parent == null) - throw new Exception("Parent bone not found: " + boneMap["parent"]); - } - var boneData = new BoneData((String)boneMap["name"], parent); - boneData.length = GetFloat(boneMap, "length", 0) * scale; - boneData.x = GetFloat(boneMap, "x", 0) * scale; - boneData.y = GetFloat(boneMap, "y", 0) * scale; - boneData.rotation = GetFloat(boneMap, "rotation", 0); - boneData.scaleX = GetFloat(boneMap, "scaleX", 1); - boneData.scaleY = GetFloat(boneMap, "scaleY", 1); - boneData.shearX = GetFloat(boneMap, "shearX", 0); - boneData.shearY = GetFloat(boneMap, "shearY", 0); - boneData.inheritScale = GetBoolean(boneMap, "inheritScale", true); - boneData.inheritRotation = GetBoolean(boneMap, "inheritRotation", true); - skeletonData.bones.Add(boneData); - } - - // IK constraints. - if (root.ContainsKey("ik")) { - foreach (Dictionary ikMap in (List)root["ik"]) { - IkConstraintData ikConstraintData = new IkConstraintData((String)ikMap["name"]); - - foreach (String boneName in (List)ikMap["bones"]) { - BoneData bone = skeletonData.FindBone(boneName); - if (bone == null) throw new Exception("IK bone not found: " + boneName); - ikConstraintData.bones.Add(bone); - } - - String targetName = (String)ikMap["target"]; - ikConstraintData.target = skeletonData.FindBone(targetName); - if (ikConstraintData.target == null) throw new Exception("Target bone not found: " + targetName); - - ikConstraintData.bendDirection = GetBoolean(ikMap, "bendPositive", true) ? 1 : -1; - ikConstraintData.mix = GetFloat(ikMap, "mix", 1); - - skeletonData.ikConstraints.Add(ikConstraintData); - } - } - - // Transform constraints. - if (root.ContainsKey("transform")) { - foreach (Dictionary transformMap in (List)root["transform"]) { - TransformConstraintData transformConstraintData = new TransformConstraintData((String)transformMap["name"]); - - String boneName = (String)transformMap["bone"]; - transformConstraintData.bone = skeletonData.FindBone(boneName); - if (transformConstraintData.bone == null) throw new Exception("Bone not found: " + boneName); - - String targetName = (String)transformMap["target"]; - transformConstraintData.target = skeletonData.FindBone(targetName); - if (transformConstraintData.target == null) throw new Exception("Target bone not found: " + targetName); - - transformConstraintData.offsetRotation = GetFloat(transformMap, "rotation", 0); - transformConstraintData.offsetX = GetFloat(transformMap, "x", 0) * scale; - transformConstraintData.offsetY = GetFloat(transformMap, "y", 0) * scale; - transformConstraintData.offsetScaleX = GetFloat(transformMap, "scaleX", 0); - transformConstraintData.offsetScaleY = GetFloat(transformMap, "scaleY", 0); - transformConstraintData.offsetShearY = GetFloat(transformMap, "shearY", 0); - - transformConstraintData.rotateMix = GetFloat(transformMap, "rotateMix", 1); - transformConstraintData.translateMix = GetFloat(transformMap, "translateMix", 1); - transformConstraintData.scaleMix = GetFloat(transformMap, "scaleMix", 1); - transformConstraintData.shearMix = GetFloat(transformMap, "shearMix", 1); - - skeletonData.transformConstraints.Add(transformConstraintData); - } - } - - // Slots. - if (root.ContainsKey("slots")) { - foreach (Dictionary slotMap in (List)root["slots"]) { - var slotName = (String)slotMap["name"]; - var boneName = (String)slotMap["bone"]; - BoneData boneData = skeletonData.FindBone(boneName); - if (boneData == null) - throw new Exception("Slot bone not found: " + boneName); - var slotData = new SlotData(slotName, boneData); - - if (slotMap.ContainsKey("color")) { - var color = (String)slotMap["color"]; - slotData.r = ToColor(color, 0); - slotData.g = ToColor(color, 1); - slotData.b = ToColor(color, 2); - slotData.a = ToColor(color, 3); - } - - if (slotMap.ContainsKey("attachment")) - slotData.attachmentName = (String)slotMap["attachment"]; - - if (slotMap.ContainsKey("blend")) - slotData.blendMode = (BlendMode)Enum.Parse(typeof(BlendMode), (String)slotMap["blend"], false); - else - slotData.blendMode = BlendMode.normal; - - skeletonData.slots.Add(slotData); - } - } - - // Skins. - if (root.ContainsKey("skins")) { - foreach (KeyValuePair entry in (Dictionary)root["skins"]) { - var skin = new Skin(entry.Key); - foreach (KeyValuePair slotEntry in (Dictionary)entry.Value) { - int slotIndex = skeletonData.FindSlotIndex(slotEntry.Key); - foreach (KeyValuePair attachmentEntry in ((Dictionary)slotEntry.Value)) { - Attachment attachment = ReadAttachment(skin, slotIndex, attachmentEntry.Key, (Dictionary)attachmentEntry.Value); - if (attachment != null) skin.AddAttachment(slotIndex, attachmentEntry.Key, attachment); - } - } - skeletonData.skins.Add(skin); - if (skin.name == "default") - skeletonData.defaultSkin = skin; - } - } - - // Linked meshes. - for (int i = 0, n = linkedMeshes.Count; i < n; i++) { - LinkedMesh linkedMesh = linkedMeshes[i]; - Skin skin = linkedMesh.skin == null ? skeletonData.defaultSkin : skeletonData.FindSkin(linkedMesh.skin); - if (skin == null) throw new Exception("Slot not found: " + linkedMesh.skin); - Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); - if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); - if (linkedMesh.mesh is MeshAttachment) { - MeshAttachment mesh = (MeshAttachment)linkedMesh.mesh; - mesh.ParentMesh = (MeshAttachment)parent; - mesh.UpdateUVs(); - } else { - WeightedMeshAttachment mesh = (WeightedMeshAttachment)linkedMesh.mesh; - mesh.ParentMesh = (WeightedMeshAttachment)parent; - mesh.UpdateUVs(); - } - } - linkedMeshes.Clear(); - - // Events. - if (root.ContainsKey("events")) { - foreach (KeyValuePair entry in (Dictionary)root["events"]) { - var entryMap = (Dictionary)entry.Value; - var eventData = new EventData(entry.Key); - eventData.Int = GetInt(entryMap, "int", 0); - eventData.Float = GetFloat(entryMap, "float", 0); - eventData.String = GetString(entryMap, "string", null); - skeletonData.events.Add(eventData); - } - } - - // Animations. - if (root.ContainsKey("animations")) { - foreach (KeyValuePair entry in (Dictionary)root["animations"]) - ReadAnimation(entry.Key, (Dictionary)entry.Value, skeletonData); - } - - skeletonData.bones.TrimExcess(); - skeletonData.slots.TrimExcess(); - skeletonData.skins.TrimExcess(); - skeletonData.events.TrimExcess(); - skeletonData.animations.TrimExcess(); - skeletonData.ikConstraints.TrimExcess(); - return skeletonData; - } - - private Attachment ReadAttachment (Skin skin, int slotIndex, String name, Dictionary map) { - if (map.ContainsKey("name")) - name = (String)map["name"]; - - var scale = this.Scale; - - var type = AttachmentType.region; - if (map.ContainsKey("type")) { - var typeName = (String)map["type"]; - if (typeName == "skinnedmesh") typeName = "weightedmesh"; - type = (AttachmentType)Enum.Parse(typeof(AttachmentType), typeName , false); - } - - String path = name; - if (map.ContainsKey("path")) - path = (String)map["path"]; - - switch (type) { - case AttachmentType.region: - RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); - if (region == null) return null; - region.Path = path; - region.x = GetFloat(map, "x", 0) * scale; - region.y = GetFloat(map, "y", 0) * scale; - region.scaleX = GetFloat(map, "scaleX", 1); - region.scaleY = GetFloat(map, "scaleY", 1); - region.rotation = GetFloat(map, "rotation", 0); - region.width = GetFloat(map, "width", 32) * scale; - region.height = GetFloat(map, "height", 32) * scale; - region.UpdateOffset(); - - if (map.ContainsKey("color")) { - var color = (String)map["color"]; - region.r = ToColor(color, 0); - region.g = ToColor(color, 1); - region.b = ToColor(color, 2); - region.a = ToColor(color, 3); - } - - return region; - case AttachmentType.mesh: - case AttachmentType.linkedmesh: { - MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); - if (mesh == null) return null; - mesh.Path = path; - - if (map.ContainsKey("color")) { - var color = (String)map["color"]; - mesh.r = ToColor(color, 0); - mesh.g = ToColor(color, 1); - mesh.b = ToColor(color, 2); - mesh.a = ToColor(color, 3); - } - - mesh.Width = GetInt(map, "width", 0) * scale; - mesh.Height = GetInt(map, "height", 0) * scale; - - String parent = GetString(map, "parent", null); - if (parent == null) { - mesh.vertices = GetFloatArray(map, "vertices", scale); - mesh.triangles = GetIntArray(map, "triangles"); - mesh.regionUVs = GetFloatArray(map, "uvs", 1); - mesh.UpdateUVs(); - - mesh.HullLength = GetInt(map, "hull", 0) * 2; - if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges"); - } else { - mesh.InheritFFD = GetBoolean(map, "ffd", true); - linkedMeshes.Add(new LinkedMesh(mesh, GetString(map, "skin", null), slotIndex, parent)); - } - - return mesh; - } - case AttachmentType.weightedmesh: - case AttachmentType.weightedlinkedmesh: { - WeightedMeshAttachment mesh = attachmentLoader.NewWeightedMeshAttachment(skin, name, path); - if (mesh == null) return null; - - mesh.Path = path; - - if (map.ContainsKey("color")) { - var color = (String)map["color"]; - mesh.r = ToColor(color, 0); - mesh.g = ToColor(color, 1); - mesh.b = ToColor(color, 2); - mesh.a = ToColor(color, 3); - } - - mesh.Width = GetInt(map, "width", 0) * scale; - mesh.Height = GetInt(map, "height", 0) * scale; - - String parent = GetString(map, "parent", null); - if (parent == null) { - float[] uvs = GetFloatArray(map, "uvs", 1); - float[] vertices = GetFloatArray(map, "vertices", 1); - var weights = new List(uvs.Length * 3 * 3); - var bones = new List(uvs.Length * 3); - for (int i = 0, n = vertices.Length; i < n;) { - int boneCount = (int)vertices[i++]; - bones.Add(boneCount); - for (int nn = i + boneCount * 4; i < nn; i += 4) { - bones.Add((int)vertices[i]); - weights.Add(vertices[i + 1] * scale); - weights.Add(vertices[i + 2] * scale); - weights.Add(vertices[i + 3]); - } - } - mesh.bones = bones.ToArray(); - mesh.weights = weights.ToArray(); - mesh.triangles = GetIntArray(map, "triangles"); - mesh.regionUVs = uvs; - mesh.UpdateUVs(); - - mesh.HullLength = GetInt(map, "hull", 0) * 2; - if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges"); - } else { - mesh.InheritFFD = GetBoolean(map, "ffd", true); - linkedMeshes.Add(new LinkedMesh(mesh, GetString(map, "skin", null), slotIndex, parent)); - } - - return mesh; - } - case AttachmentType.boundingbox: - BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); - if (box == null) return null; - box.vertices = GetFloatArray(map, "vertices", scale); - return box; - } - return null; - } - - private float[] GetFloatArray (Dictionary map, String name, float scale) { - var list = (List)map[name]; - var values = new float[list.Count]; - if (scale == 1) { - for (int i = 0, n = list.Count; i < n; i++) - values[i] = (float)list[i]; - } else { - for (int i = 0, n = list.Count; i < n; i++) - values[i] = (float)list[i] * scale; - } - return values; - } - - private int[] GetIntArray (Dictionary map, String name) { - var list = (List)map[name]; - var values = new int[list.Count]; - for (int i = 0, n = list.Count; i < n; i++) - values[i] = (int)(float)list[i]; - return values; - } - - private float GetFloat (Dictionary map, String name, float defaultValue) { - if (!map.ContainsKey(name)) - return defaultValue; - return (float)map[name]; - } - - private int GetInt (Dictionary map, String name, int defaultValue) { - if (!map.ContainsKey(name)) - return defaultValue; - return (int)(float)map[name]; - } - - private bool GetBoolean (Dictionary map, String name, bool defaultValue) { - if (!map.ContainsKey(name)) - return defaultValue; - return (bool)map[name]; - } - - private String GetString (Dictionary map, String name, String defaultValue) { - if (!map.ContainsKey(name)) - return defaultValue; - return (String)map[name]; - } - - private float ToColor (String hexString, int colorIndex) { - if (hexString.Length != 8) - throw new ArgumentException("Color hexidecimal length must be 8, recieved: " + hexString); - return Convert.ToInt32(hexString.Substring(colorIndex * 2, 2), 16) / (float)255; - } - - private void ReadAnimation (String name, Dictionary map, SkeletonData skeletonData) { - var timelines = new ExposedList(); - float duration = 0; - var scale = this.Scale; - - if (map.ContainsKey("slots")) { - foreach (KeyValuePair entry in (Dictionary)map["slots"]) { - String slotName = entry.Key; - int slotIndex = skeletonData.FindSlotIndex(slotName); - var timelineMap = (Dictionary)entry.Value; - - foreach (KeyValuePair timelineEntry in timelineMap) { - var values = (List)timelineEntry.Value; - var timelineName = (String)timelineEntry.Key; - if (timelineName == "color") { - var timeline = new ColorTimeline(values.Count); - timeline.slotIndex = slotIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - String c = (String)valueMap["color"]; - timeline.SetFrame(frameIndex, time, ToColor(c, 0), ToColor(c, 1), ToColor(c, 2), ToColor(c, 3)); - ReadCurve(timeline, frameIndex, valueMap); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 5 - 5]); - - } else if (timelineName == "attachment") { - var timeline = new AttachmentTimeline(values.Count); - timeline.slotIndex = slotIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - timeline.SetFrame(frameIndex++, time, (String)valueMap["name"]); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); - - } else - throw new Exception("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")"); - } - } - } - - if (map.ContainsKey("bones")) { - foreach (KeyValuePair entry in (Dictionary)map["bones"]) { - String boneName = entry.Key; - int boneIndex = skeletonData.FindBoneIndex(boneName); - if (boneIndex == -1) - throw new Exception("Bone not found: " + boneName); - - var timelineMap = (Dictionary)entry.Value; - foreach (KeyValuePair timelineEntry in timelineMap) { - var values = (List)timelineEntry.Value; - var timelineName = (String)timelineEntry.Key; - if (timelineName == "rotate") { - var timeline = new RotateTimeline(values.Count); - timeline.boneIndex = boneIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - timeline.SetFrame(frameIndex, time, (float)valueMap["angle"]); - ReadCurve(timeline, frameIndex, valueMap); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 2 - 2]); - - } else if (timelineName == "translate" || timelineName == "scale" || timelineName == "shear") { - TranslateTimeline timeline; - float timelineScale = 1; - if (timelineName == "scale") - timeline = new ScaleTimeline(values.Count); - else if (timelineName == "shear") - timeline = new ShearTimeline(values.Count); - else { - timeline = new TranslateTimeline(values.Count); - timelineScale = scale; - } - timeline.boneIndex = boneIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - float x = GetFloat(valueMap, "x", 0); - float y = GetFloat(valueMap, "y", 0); - timeline.SetFrame(frameIndex, time, (float)x * timelineScale, (float)y * timelineScale); - ReadCurve(timeline, frameIndex, valueMap); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 3 - 3]); - - } else - throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")"); - } - } - } - - // IK timelines. - if (map.ContainsKey("ik")) { - foreach (KeyValuePair constraintMap in (Dictionary)map["ik"]) { - IkConstraintData constraint = skeletonData.FindIkConstraint(constraintMap.Key); - var values = (List)constraintMap.Value; - var timeline = new IkConstraintTimeline(values.Count); - timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(constraint); - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - float mix = GetFloat(valueMap, "mix", 1); - bool bendPositive = GetBoolean(valueMap, "bendPositive", true); - timeline.SetFrame(frameIndex, time, mix, bendPositive ? 1 : -1); - ReadCurve(timeline, frameIndex, valueMap); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 3 - 3]); - } - } - - // Transform constraint timelines. - if (map.ContainsKey("transform")) { - foreach (KeyValuePair constraintMap in (Dictionary)map["transform"]) { - TransformConstraintData constraint = skeletonData.FindTransformConstraint(constraintMap.Key); - var values = (List)constraintMap.Value; - var timeline = new TransformConstraintTimeline(values.Count); - timeline.transformConstraintIndex = skeletonData.transformConstraints.IndexOf(constraint); - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - float rotateMix = GetFloat(valueMap, "rotateMix", 1); - float translateMix = GetFloat(valueMap, "translateMix", 1); - float scaleMix = GetFloat(valueMap, "scaleMix", 1); - float shearMix = GetFloat(valueMap, "shearMix", 1); - timeline.SetFrame(frameIndex, time, rotateMix, translateMix, scaleMix, shearMix); - ReadCurve(timeline, frameIndex, valueMap); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 5 - 5]); - } - } - - // FFD timelines. - if (map.ContainsKey("ffd")) { - foreach (KeyValuePair ffdMap in (Dictionary)map["ffd"]) { - Skin skin = skeletonData.FindSkin(ffdMap.Key); - foreach (KeyValuePair slotMap in (Dictionary)ffdMap.Value) { - int slotIndex = skeletonData.FindSlotIndex(slotMap.Key); - foreach (KeyValuePair meshMap in (Dictionary)slotMap.Value) { - var values = (List)meshMap.Value; - var timeline = new FfdTimeline(values.Count); - Attachment attachment = skin.GetAttachment(slotIndex, meshMap.Key); - if (attachment == null) throw new Exception("FFD attachment not found: " + meshMap.Key); - timeline.slotIndex = slotIndex; - timeline.attachment = attachment; - - int vertexCount; - if (attachment is MeshAttachment) - vertexCount = ((MeshAttachment)attachment).vertices.Length; - else - vertexCount = ((WeightedMeshAttachment)attachment).Weights.Length / 3 * 2; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float[] vertices; - if (!valueMap.ContainsKey("vertices")) { - if (attachment is MeshAttachment) - vertices = ((MeshAttachment)attachment).vertices; - else - vertices = new float[vertexCount]; - } else { - var verticesValue = (List)valueMap["vertices"]; - vertices = new float[vertexCount]; - int start = GetInt(valueMap, "offset", 0); - if (scale == 1) { - for (int i = 0, n = verticesValue.Count; i < n; i++) - vertices[i + start] = (float)verticesValue[i]; - } else { - for (int i = 0, n = verticesValue.Count; i < n; i++) - vertices[i + start] = (float)verticesValue[i] * scale; - } - if (attachment is MeshAttachment) { - float[] meshVertices = ((MeshAttachment)attachment).vertices; - for (int i = 0; i < vertexCount; i++) - vertices[i] += meshVertices[i]; - } - } - - timeline.SetFrame(frameIndex, (float)valueMap["time"], vertices); - ReadCurve(timeline, frameIndex, valueMap); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); - } - } - } - } - - if (map.ContainsKey("drawOrder") || map.ContainsKey("draworder")) { - var values = (List)map[map.ContainsKey("drawOrder") ? "drawOrder" : "draworder"]; - var timeline = new DrawOrderTimeline(values.Count); - int slotCount = skeletonData.slots.Count; - int frameIndex = 0; - foreach (Dictionary drawOrderMap in values) { - int[] drawOrder = null; - if (drawOrderMap.ContainsKey("offsets")) { - drawOrder = new int[slotCount]; - for (int i = slotCount - 1; i >= 0; i--) - drawOrder[i] = -1; - var offsets = (List)drawOrderMap["offsets"]; - int[] unchanged = new int[slotCount - offsets.Count]; - int originalIndex = 0, unchangedIndex = 0; - foreach (Dictionary offsetMap in offsets) { - int slotIndex = skeletonData.FindSlotIndex((String)offsetMap["slot"]); - if (slotIndex == -1) throw new Exception("Slot not found: " + offsetMap["slot"]); - // Collect unchanged items. - while (originalIndex != slotIndex) - unchanged[unchangedIndex++] = originalIndex++; - // Set changed items. - int index = originalIndex + (int)(float)offsetMap["offset"]; - drawOrder[index] = originalIndex++; - } - // Collect remaining unchanged items. - while (originalIndex < slotCount) - unchanged[unchangedIndex++] = originalIndex++; - // Fill in unchanged items. - for (int i = slotCount - 1; i >= 0; i--) - if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex]; - } - timeline.SetFrame(frameIndex++, (float)drawOrderMap["time"], drawOrder); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); - } - - if (map.ContainsKey("events")) { - var eventsMap = (List)map["events"]; - var timeline = new EventTimeline(eventsMap.Count); - int frameIndex = 0; - foreach (Dictionary eventMap in eventsMap) { - EventData eventData = skeletonData.FindEvent((String)eventMap["name"]); - if (eventData == null) throw new Exception("Event not found: " + eventMap["name"]); - var e = new Event((float)eventMap["time"], eventData); - e.Int = GetInt(eventMap, "int", eventData.Int); - e.Float = GetFloat(eventMap, "float", eventData.Float); - e.String = GetString(eventMap, "string", eventData.String); - timeline.SetFrame(frameIndex++, e); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); - } - - timelines.TrimExcess(); - skeletonData.animations.Add(new Animation(name, timelines, duration)); - } - - private void ReadCurve (CurveTimeline timeline, int frameIndex, Dictionary valueMap) { - if (!valueMap.ContainsKey("curve")) - return; - Object curveObject = valueMap["curve"]; - if (curveObject.Equals("stepped")) - timeline.SetStepped(frameIndex); - else if (curveObject is List) { - var curve = (List)curveObject; - timeline.SetCurve(frameIndex, (float)curve[0], (float)curve[1], (float)curve[2], (float)curve[3]); - } - } - - internal class LinkedMesh { - internal String parent, skin; - internal int slotIndex; - internal Attachment mesh; - - public LinkedMesh (Attachment mesh, String skin, int slotIndex, String parent) { - this.mesh = mesh; - this.skin = skin; - this.slotIndex = slotIndex; - this.parent = parent; - } - } - } +#else + using (StreamReader reader = new StreamReader(path)) + { +#endif // WINDOWS_PHONE + SkeletonData skeletonData = ReadSkeletonData(reader); + skeletonData.name = Path.GetFileNameWithoutExtension(path); + return skeletonData; + } + } +#endif // WINDOWS_STOREAPP + + public SkeletonData ReadSkeletonData(TextReader reader) + { + if (reader == null) throw new ArgumentNullException("reader cannot be null."); + + var scale = this.Scale; + var skeletonData = new SkeletonData(); + + var root = Json.Deserialize(reader) as Dictionary; + if (root == null) throw new Exception("Invalid JSON."); + + // Skeleton. + if (root.ContainsKey("skeleton")) + { + var skeletonMap = (Dictionary)root["skeleton"]; + skeletonData.hash = (String)skeletonMap["hash"]; + skeletonData.version = (String)skeletonMap["spine"]; + skeletonData.width = GetFloat(skeletonMap, "width", 0); + skeletonData.height = GetFloat(skeletonMap, "height", 0); + } + + // Bones. + foreach (Dictionary boneMap in (List)root["bones"]) + { + BoneData parent = null; + if (boneMap.ContainsKey("parent")) + { + parent = skeletonData.FindBone((String)boneMap["parent"]); + if (parent == null) + throw new Exception("Parent bone not found: " + boneMap["parent"]); + } + var boneData = new BoneData((String)boneMap["name"], parent); + boneData.length = GetFloat(boneMap, "length", 0) * scale; + boneData.x = GetFloat(boneMap, "x", 0) * scale; + boneData.y = GetFloat(boneMap, "y", 0) * scale; + boneData.rotation = GetFloat(boneMap, "rotation", 0); + boneData.scaleX = GetFloat(boneMap, "scaleX", 1); + boneData.scaleY = GetFloat(boneMap, "scaleY", 1); + boneData.shearX = GetFloat(boneMap, "shearX", 0); + boneData.shearY = GetFloat(boneMap, "shearY", 0); + boneData.inheritScale = GetBoolean(boneMap, "inheritScale", true); + boneData.inheritRotation = GetBoolean(boneMap, "inheritRotation", true); + skeletonData.bones.Add(boneData); + } + + // IK constraints. + if (root.ContainsKey("ik")) + { + foreach (Dictionary ikMap in (List)root["ik"]) + { + IkConstraintData ikConstraintData = new IkConstraintData((String)ikMap["name"]); + + foreach (String boneName in (List)ikMap["bones"]) + { + BoneData bone = skeletonData.FindBone(boneName); + if (bone == null) throw new Exception("IK bone not found: " + boneName); + ikConstraintData.bones.Add(bone); + } + + String targetName = (String)ikMap["target"]; + ikConstraintData.target = skeletonData.FindBone(targetName); + if (ikConstraintData.target == null) throw new Exception("Target bone not found: " + targetName); + + ikConstraintData.bendDirection = GetBoolean(ikMap, "bendPositive", true) ? 1 : -1; + ikConstraintData.mix = GetFloat(ikMap, "mix", 1); + + skeletonData.ikConstraints.Add(ikConstraintData); + } + } + + // Transform constraints. + if (root.ContainsKey("transform")) + { + foreach (Dictionary transformMap in (List)root["transform"]) + { + TransformConstraintData transformConstraintData = new TransformConstraintData((String)transformMap["name"]); + + String boneName = (String)transformMap["bone"]; + transformConstraintData.bone = skeletonData.FindBone(boneName); + if (transformConstraintData.bone == null) throw new Exception("Bone not found: " + boneName); + + String targetName = (String)transformMap["target"]; + transformConstraintData.target = skeletonData.FindBone(targetName); + if (transformConstraintData.target == null) throw new Exception("Target bone not found: " + targetName); + + transformConstraintData.offsetRotation = GetFloat(transformMap, "rotation", 0); + transformConstraintData.offsetX = GetFloat(transformMap, "x", 0) * scale; + transformConstraintData.offsetY = GetFloat(transformMap, "y", 0) * scale; + transformConstraintData.offsetScaleX = GetFloat(transformMap, "scaleX", 0); + transformConstraintData.offsetScaleY = GetFloat(transformMap, "scaleY", 0); + transformConstraintData.offsetShearY = GetFloat(transformMap, "shearY", 0); + + transformConstraintData.rotateMix = GetFloat(transformMap, "rotateMix", 1); + transformConstraintData.translateMix = GetFloat(transformMap, "translateMix", 1); + transformConstraintData.scaleMix = GetFloat(transformMap, "scaleMix", 1); + transformConstraintData.shearMix = GetFloat(transformMap, "shearMix", 1); + + skeletonData.transformConstraints.Add(transformConstraintData); + } + } + + // Slots. + if (root.ContainsKey("slots")) + { + foreach (Dictionary slotMap in (List)root["slots"]) + { + var slotName = (String)slotMap["name"]; + var boneName = (String)slotMap["bone"]; + BoneData boneData = skeletonData.FindBone(boneName); + if (boneData == null) + throw new Exception("Slot bone not found: " + boneName); + var slotData = new SlotData(slotName, boneData); + + if (slotMap.ContainsKey("color")) + { + var color = (String)slotMap["color"]; + slotData.r = ToColor(color, 0); + slotData.g = ToColor(color, 1); + slotData.b = ToColor(color, 2); + slotData.a = ToColor(color, 3); + } + + if (slotMap.ContainsKey("attachment")) + slotData.attachmentName = (String)slotMap["attachment"]; + + if (slotMap.ContainsKey("blend")) + slotData.blendMode = (BlendMode)Enum.Parse(typeof(BlendMode), (String)slotMap["blend"], false); + else + slotData.blendMode = BlendMode.normal; + + skeletonData.slots.Add(slotData); + } + } + + // Skins. + if (root.ContainsKey("skins")) + { + foreach (KeyValuePair entry in (Dictionary)root["skins"]) + { + var skin = new Skin(entry.Key); + foreach (KeyValuePair slotEntry in (Dictionary)entry.Value) + { + int slotIndex = skeletonData.FindSlotIndex(slotEntry.Key); + foreach (KeyValuePair attachmentEntry in ((Dictionary)slotEntry.Value)) + { + Attachment attachment = ReadAttachment(skin, slotIndex, attachmentEntry.Key, (Dictionary)attachmentEntry.Value); + if (attachment != null) skin.AddAttachment(slotIndex, attachmentEntry.Key, attachment); + } + } + skeletonData.skins.Add(skin); + if (skin.name == "default") + skeletonData.defaultSkin = skin; + } + } + + // Linked meshes. + for (int i = 0, n = linkedMeshes.Count; i < n; i++) + { + LinkedMesh linkedMesh = linkedMeshes[i]; + Skin skin = linkedMesh.skin == null ? skeletonData.defaultSkin : skeletonData.FindSkin(linkedMesh.skin); + if (skin == null) throw new Exception("Slot not found: " + linkedMesh.skin); + Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); + if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); + if (linkedMesh.mesh is MeshAttachment) + { + MeshAttachment mesh = (MeshAttachment)linkedMesh.mesh; + mesh.ParentMesh = (MeshAttachment)parent; + mesh.UpdateUVs(); + } + else + { + WeightedMeshAttachment mesh = (WeightedMeshAttachment)linkedMesh.mesh; + mesh.ParentMesh = (WeightedMeshAttachment)parent; + mesh.UpdateUVs(); + } + } + linkedMeshes.Clear(); + + // Events. + if (root.ContainsKey("events")) + { + foreach (KeyValuePair entry in (Dictionary)root["events"]) + { + var entryMap = (Dictionary)entry.Value; + var eventData = new EventData(entry.Key); + eventData.Int = GetInt(entryMap, "int", 0); + eventData.Float = GetFloat(entryMap, "float", 0); + eventData.String = GetString(entryMap, "string", null); + skeletonData.events.Add(eventData); + } + } + + // Animations. + if (root.ContainsKey("animations")) + { + foreach (KeyValuePair entry in (Dictionary)root["animations"]) + ReadAnimation(entry.Key, (Dictionary)entry.Value, skeletonData); + } + + skeletonData.bones.TrimExcess(); + skeletonData.slots.TrimExcess(); + skeletonData.skins.TrimExcess(); + skeletonData.events.TrimExcess(); + skeletonData.animations.TrimExcess(); + skeletonData.ikConstraints.TrimExcess(); + return skeletonData; + } + + private Attachment ReadAttachment(Skin skin, int slotIndex, String name, Dictionary map) + { + if (map.ContainsKey("name")) + name = (String)map["name"]; + + var scale = this.Scale; + + var type = AttachmentType.region; + if (map.ContainsKey("type")) + { + var typeName = (String)map["type"]; + if (typeName == "skinnedmesh") typeName = "weightedmesh"; + type = (AttachmentType)Enum.Parse(typeof(AttachmentType), typeName, false); + } + + String path = name; + if (map.ContainsKey("path")) + path = (String)map["path"]; + + switch (type) + { + case AttachmentType.region: + RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); + if (region == null) return null; + region.Path = path; + region.x = GetFloat(map, "x", 0) * scale; + region.y = GetFloat(map, "y", 0) * scale; + region.scaleX = GetFloat(map, "scaleX", 1); + region.scaleY = GetFloat(map, "scaleY", 1); + region.rotation = GetFloat(map, "rotation", 0); + region.width = GetFloat(map, "width", 32) * scale; + region.height = GetFloat(map, "height", 32) * scale; + region.UpdateOffset(); + + if (map.ContainsKey("color")) + { + var color = (String)map["color"]; + region.r = ToColor(color, 0); + region.g = ToColor(color, 1); + region.b = ToColor(color, 2); + region.a = ToColor(color, 3); + } + + return region; + case AttachmentType.mesh: + case AttachmentType.linkedmesh: + { + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); + if (mesh == null) return null; + mesh.Path = path; + + if (map.ContainsKey("color")) + { + var color = (String)map["color"]; + mesh.r = ToColor(color, 0); + mesh.g = ToColor(color, 1); + mesh.b = ToColor(color, 2); + mesh.a = ToColor(color, 3); + } + + mesh.Width = GetInt(map, "width", 0) * scale; + mesh.Height = GetInt(map, "height", 0) * scale; + + String parent = GetString(map, "parent", null); + if (parent == null) + { + mesh.vertices = GetFloatArray(map, "vertices", scale); + mesh.triangles = GetIntArray(map, "triangles"); + mesh.regionUVs = GetFloatArray(map, "uvs", 1); + mesh.UpdateUVs(); + + mesh.HullLength = GetInt(map, "hull", 0) * 2; + if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges"); + } + else + { + mesh.InheritFFD = GetBoolean(map, "ffd", true); + linkedMeshes.Add(new LinkedMesh(mesh, GetString(map, "skin", null), slotIndex, parent)); + } + + return mesh; + } + case AttachmentType.weightedmesh: + case AttachmentType.weightedlinkedmesh: + { + WeightedMeshAttachment mesh = attachmentLoader.NewWeightedMeshAttachment(skin, name, path); + if (mesh == null) return null; + + mesh.Path = path; + + if (map.ContainsKey("color")) + { + var color = (String)map["color"]; + mesh.r = ToColor(color, 0); + mesh.g = ToColor(color, 1); + mesh.b = ToColor(color, 2); + mesh.a = ToColor(color, 3); + } + + mesh.Width = GetInt(map, "width", 0) * scale; + mesh.Height = GetInt(map, "height", 0) * scale; + + String parent = GetString(map, "parent", null); + if (parent == null) + { + float[] uvs = GetFloatArray(map, "uvs", 1); + float[] vertices = GetFloatArray(map, "vertices", 1); + var weights = new List(uvs.Length * 3 * 3); + var bones = new List(uvs.Length * 3); + for (int i = 0, n = vertices.Length; i < n;) + { + int boneCount = (int)vertices[i++]; + bones.Add(boneCount); + for (int nn = i + boneCount * 4; i < nn; i += 4) + { + bones.Add((int)vertices[i]); + weights.Add(vertices[i + 1] * scale); + weights.Add(vertices[i + 2] * scale); + weights.Add(vertices[i + 3]); + } + } + mesh.bones = bones.ToArray(); + mesh.weights = weights.ToArray(); + mesh.triangles = GetIntArray(map, "triangles"); + mesh.regionUVs = uvs; + mesh.UpdateUVs(); + + mesh.HullLength = GetInt(map, "hull", 0) * 2; + if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges"); + } + else + { + mesh.InheritFFD = GetBoolean(map, "ffd", true); + linkedMeshes.Add(new LinkedMesh(mesh, GetString(map, "skin", null), slotIndex, parent)); + } + + return mesh; + } + case AttachmentType.boundingbox: + BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); + if (box == null) return null; + box.vertices = GetFloatArray(map, "vertices", scale); + return box; + } + return null; + } + + private float[] GetFloatArray(Dictionary map, String name, float scale) + { + var list = (List)map[name]; + var values = new float[list.Count]; + if (scale == 1) + { + for (int i = 0, n = list.Count; i < n; i++) + values[i] = (float)list[i]; + } + else + { + for (int i = 0, n = list.Count; i < n; i++) + values[i] = (float)list[i] * scale; + } + return values; + } + + private int[] GetIntArray(Dictionary map, String name) + { + var list = (List)map[name]; + var values = new int[list.Count]; + for (int i = 0, n = list.Count; i < n; i++) + values[i] = (int)(float)list[i]; + return values; + } + + private float GetFloat(Dictionary map, String name, float defaultValue) + { + if (!map.ContainsKey(name)) + return defaultValue; + return (float)map[name]; + } + + private int GetInt(Dictionary map, String name, int defaultValue) + { + if (!map.ContainsKey(name)) + return defaultValue; + return (int)(float)map[name]; + } + + private bool GetBoolean(Dictionary map, String name, bool defaultValue) + { + if (!map.ContainsKey(name)) + return defaultValue; + return (bool)map[name]; + } + + private String GetString(Dictionary map, String name, String defaultValue) + { + if (!map.ContainsKey(name)) + return defaultValue; + return (String)map[name]; + } + + private float ToColor(String hexString, int colorIndex) + { + if (hexString.Length != 8) + throw new ArgumentException("Color hexidecimal length must be 8, recieved: " + hexString); + return Convert.ToInt32(hexString.Substring(colorIndex * 2, 2), 16) / (float)255; + } + + private void ReadAnimation(String name, Dictionary map, SkeletonData skeletonData) + { + var timelines = new ExposedList(); + float duration = 0; + var scale = this.Scale; + + if (map.ContainsKey("slots")) + { + foreach (KeyValuePair entry in (Dictionary)map["slots"]) + { + String slotName = entry.Key; + int slotIndex = skeletonData.FindSlotIndex(slotName); + var timelineMap = (Dictionary)entry.Value; + + foreach (KeyValuePair timelineEntry in timelineMap) + { + var values = (List)timelineEntry.Value; + var timelineName = timelineEntry.Key; + if (timelineName == "color") + { + var timeline = new ColorTimeline(values.Count); + timeline.slotIndex = slotIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + String c = (String)valueMap["color"]; + timeline.SetFrame(frameIndex, time, ToColor(c, 0), ToColor(c, 1), ToColor(c, 2), ToColor(c, 3)); + ReadCurve(timeline, frameIndex, valueMap); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 5 - 5]); + + } + else if (timelineName == "attachment") + { + var timeline = new AttachmentTimeline(values.Count); + timeline.slotIndex = slotIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + timeline.SetFrame(frameIndex++, time, (String)valueMap["name"]); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); + + } + else + throw new Exception("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")"); + } + } + } + + if (map.ContainsKey("bones")) + { + foreach (KeyValuePair entry in (Dictionary)map["bones"]) + { + String boneName = entry.Key; + int boneIndex = skeletonData.FindBoneIndex(boneName); + if (boneIndex == -1) + throw new Exception("Bone not found: " + boneName); + + var timelineMap = (Dictionary)entry.Value; + foreach (KeyValuePair timelineEntry in timelineMap) + { + var values = (List)timelineEntry.Value; + var timelineName = timelineEntry.Key; + if (timelineName == "rotate") + { + var timeline = new RotateTimeline(values.Count); + timeline.boneIndex = boneIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + timeline.SetFrame(frameIndex, time, (float)valueMap["angle"]); + ReadCurve(timeline, frameIndex, valueMap); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 2 - 2]); + + } + else if (timelineName == "translate" || timelineName == "scale" || timelineName == "shear") + { + TranslateTimeline timeline; + float timelineScale = 1; + if (timelineName == "scale") + timeline = new ScaleTimeline(values.Count); + else if (timelineName == "shear") + timeline = new ShearTimeline(values.Count); + else + { + timeline = new TranslateTimeline(values.Count); + timelineScale = scale; + } + timeline.boneIndex = boneIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + float x = GetFloat(valueMap, "x", 0); + float y = GetFloat(valueMap, "y", 0); + timeline.SetFrame(frameIndex, time, (float)x * timelineScale, (float)y * timelineScale); + ReadCurve(timeline, frameIndex, valueMap); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 3 - 3]); + + } + else + throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")"); + } + } + } + + // IK timelines. + if (map.ContainsKey("ik")) + { + foreach (KeyValuePair constraintMap in (Dictionary)map["ik"]) + { + IkConstraintData constraint = skeletonData.FindIkConstraint(constraintMap.Key); + var values = (List)constraintMap.Value; + var timeline = new IkConstraintTimeline(values.Count); + timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(constraint); + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + float mix = GetFloat(valueMap, "mix", 1); + bool bendPositive = GetBoolean(valueMap, "bendPositive", true); + timeline.SetFrame(frameIndex, time, mix, bendPositive ? 1 : -1); + ReadCurve(timeline, frameIndex, valueMap); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 3 - 3]); + } + } + + // Transform constraint timelines. + if (map.ContainsKey("transform")) + { + foreach (KeyValuePair constraintMap in (Dictionary)map["transform"]) + { + TransformConstraintData constraint = skeletonData.FindTransformConstraint(constraintMap.Key); + var values = (List)constraintMap.Value; + var timeline = new TransformConstraintTimeline(values.Count); + timeline.transformConstraintIndex = skeletonData.transformConstraints.IndexOf(constraint); + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + float rotateMix = GetFloat(valueMap, "rotateMix", 1); + float translateMix = GetFloat(valueMap, "translateMix", 1); + float scaleMix = GetFloat(valueMap, "scaleMix", 1); + float shearMix = GetFloat(valueMap, "shearMix", 1); + timeline.SetFrame(frameIndex, time, rotateMix, translateMix, scaleMix, shearMix); + ReadCurve(timeline, frameIndex, valueMap); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 5 - 5]); + } + } + + // FFD timelines. + if (map.ContainsKey("ffd")) + { + foreach (KeyValuePair ffdMap in (Dictionary)map["ffd"]) + { + Skin skin = skeletonData.FindSkin(ffdMap.Key); + foreach (KeyValuePair slotMap in (Dictionary)ffdMap.Value) + { + int slotIndex = skeletonData.FindSlotIndex(slotMap.Key); + foreach (KeyValuePair meshMap in (Dictionary)slotMap.Value) + { + var values = (List)meshMap.Value; + var timeline = new FfdTimeline(values.Count); + Attachment attachment = skin.GetAttachment(slotIndex, meshMap.Key); + if (attachment == null) throw new Exception("FFD attachment not found: " + meshMap.Key); + timeline.slotIndex = slotIndex; + timeline.attachment = attachment; + + int vertexCount; + if (attachment is MeshAttachment) + vertexCount = ((MeshAttachment)attachment).vertices.Length; + else + vertexCount = ((WeightedMeshAttachment)attachment).Weights.Length / 3 * 2; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float[] vertices; + if (!valueMap.ContainsKey("vertices")) + { + if (attachment is MeshAttachment) + vertices = ((MeshAttachment)attachment).vertices; + else + vertices = new float[vertexCount]; + } + else + { + var verticesValue = (List)valueMap["vertices"]; + vertices = new float[vertexCount]; + int start = GetInt(valueMap, "offset", 0); + if (scale == 1) + { + for (int i = 0, n = verticesValue.Count; i < n; i++) + vertices[i + start] = (float)verticesValue[i]; + } + else + { + for (int i = 0, n = verticesValue.Count; i < n; i++) + vertices[i + start] = (float)verticesValue[i] * scale; + } + if (attachment is MeshAttachment) + { + float[] meshVertices = ((MeshAttachment)attachment).vertices; + for (int i = 0; i < vertexCount; i++) + vertices[i] += meshVertices[i]; + } + } + + timeline.SetFrame(frameIndex, (float)valueMap["time"], vertices); + ReadCurve(timeline, frameIndex, valueMap); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); + } + } + } + } + + if (map.ContainsKey("drawOrder") || map.ContainsKey("draworder")) + { + var values = (List)map[map.ContainsKey("drawOrder") ? "drawOrder" : "draworder"]; + var timeline = new DrawOrderTimeline(values.Count); + int slotCount = skeletonData.slots.Count; + int frameIndex = 0; + foreach (Dictionary drawOrderMap in values) + { + int[] drawOrder = null; + if (drawOrderMap.ContainsKey("offsets")) + { + drawOrder = new int[slotCount]; + for (int i = slotCount - 1; i >= 0; i--) + drawOrder[i] = -1; + var offsets = (List)drawOrderMap["offsets"]; + int[] unchanged = new int[slotCount - offsets.Count]; + int originalIndex = 0, unchangedIndex = 0; + foreach (Dictionary offsetMap in offsets) + { + int slotIndex = skeletonData.FindSlotIndex((String)offsetMap["slot"]); + if (slotIndex == -1) throw new Exception("Slot not found: " + offsetMap["slot"]); + // Collect unchanged items. + while (originalIndex != slotIndex) + unchanged[unchangedIndex++] = originalIndex++; + // Set changed items. + int index = originalIndex + (int)(float)offsetMap["offset"]; + drawOrder[index] = originalIndex++; + } + // Collect remaining unchanged items. + while (originalIndex < slotCount) + unchanged[unchangedIndex++] = originalIndex++; + // Fill in unchanged items. + for (int i = slotCount - 1; i >= 0; i--) + if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex]; + } + timeline.SetFrame(frameIndex++, (float)drawOrderMap["time"], drawOrder); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); + } + + if (map.ContainsKey("events")) + { + var eventsMap = (List)map["events"]; + var timeline = new EventTimeline(eventsMap.Count); + int frameIndex = 0; + foreach (Dictionary eventMap in eventsMap) + { + EventData eventData = skeletonData.FindEvent((String)eventMap["name"]); + if (eventData == null) throw new Exception("Event not found: " + eventMap["name"]); + var e = new Event((float)eventMap["time"], eventData); + e.Int = GetInt(eventMap, "int", eventData.Int); + e.Float = GetFloat(eventMap, "float", eventData.Float); + e.String = GetString(eventMap, "string", eventData.String); + timeline.SetFrame(frameIndex++, e); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); + } + + timelines.TrimExcess(); + skeletonData.animations.Add(new Animation(name, timelines, duration)); + } + + private void ReadCurve(CurveTimeline timeline, int frameIndex, Dictionary valueMap) + { + if (!valueMap.ContainsKey("curve")) + return; + Object curveObject = valueMap["curve"]; + if (curveObject.Equals("stepped")) + timeline.SetStepped(frameIndex); + else if (curveObject is List) + { + var curve = (List)curveObject; + timeline.SetCurve(frameIndex, (float)curve[0], (float)curve[1], (float)curve[2], (float)curve[3]); + } + } + + internal class LinkedMesh + { + internal String parent, skin; + internal int slotIndex; + internal Attachment mesh; + + public LinkedMesh(Attachment mesh, String skin, int slotIndex, String parent) + { + this.mesh = mesh; + this.skin = skin; + this.slotIndex = slotIndex; + this.parent = parent; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Skin.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Skin.cs index 1ccb9b8..a401f0e 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Skin.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Skin.cs @@ -32,83 +32,99 @@ using System; using System.Collections.Generic; -namespace Spine3_2_xx { - /// Stores attachments by slot index and attachment name. - public class Skin { - internal String name; - private Dictionary attachments = - new Dictionary(AttachmentKeyTupleComparer.Instance); +namespace Spine3_2_xx +{ + /// Stores attachments by slot index and attachment name. + public class Skin + { + internal String name; + private Dictionary attachments = + new Dictionary(AttachmentKeyTupleComparer.Instance); - public String Name { get { return name; } } + public String Name { get { return name; } } - public Skin (String name) { - if (name == null) throw new ArgumentNullException("name cannot be null."); - this.name = name; - } + public Skin(String name) + { + if (name == null) throw new ArgumentNullException("name cannot be null."); + this.name = name; + } - public void AddAttachment (int slotIndex, String name, Attachment attachment) { - if (attachment == null) throw new ArgumentNullException("attachment cannot be null."); - attachments[new AttachmentKeyTuple(slotIndex, name)] = attachment; - } + public void AddAttachment(int slotIndex, String name, Attachment attachment) + { + if (attachment == null) throw new ArgumentNullException("attachment cannot be null."); + attachments[new AttachmentKeyTuple(slotIndex, name)] = attachment; + } - /// May be null. - public Attachment GetAttachment (int slotIndex, String name) { - Attachment attachment; - attachments.TryGetValue(new AttachmentKeyTuple(slotIndex, name), out attachment); - return attachment; - } + /// May be null. + public Attachment GetAttachment(int slotIndex, String name) + { + Attachment attachment; + attachments.TryGetValue(new AttachmentKeyTuple(slotIndex, name), out attachment); + return attachment; + } - public void FindNamesForSlot (int slotIndex, List names) { - if (names == null) throw new ArgumentNullException("names cannot be null."); - foreach (AttachmentKeyTuple key in attachments.Keys) - if (key.slotIndex == slotIndex) names.Add(key.name); - } + public void FindNamesForSlot(int slotIndex, List names) + { + if (names == null) throw new ArgumentNullException("names cannot be null."); + foreach (AttachmentKeyTuple key in attachments.Keys) + if (key.slotIndex == slotIndex) names.Add(key.name); + } - public void FindAttachmentsForSlot (int slotIndex, List attachments) { - if (attachments == null) throw new ArgumentNullException("attachments cannot be null."); - foreach (KeyValuePair entry in this.attachments) - if (entry.Key.slotIndex == slotIndex) attachments.Add(entry.Value); - } + public void FindAttachmentsForSlot(int slotIndex, List attachments) + { + if (attachments == null) throw new ArgumentNullException("attachments cannot be null."); + foreach (KeyValuePair entry in this.attachments) + if (entry.Key.slotIndex == slotIndex) attachments.Add(entry.Value); + } - override public String ToString () { - return name; - } + override public String ToString() + { + return name; + } - /// Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached. - internal void AttachAll (Skeleton skeleton, Skin oldSkin) { - foreach (KeyValuePair entry in oldSkin.attachments) { - int slotIndex = entry.Key.slotIndex; - Slot slot = skeleton.slots.Items[slotIndex]; - if (slot.attachment == entry.Value) { - Attachment attachment = GetAttachment(slotIndex, entry.Key.name); - if (attachment != null) slot.Attachment = attachment; - } - } - } + /// Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached. + internal void AttachAll(Skeleton skeleton, Skin oldSkin) + { + foreach (KeyValuePair entry in oldSkin.attachments) + { + int slotIndex = entry.Key.slotIndex; + Slot slot = skeleton.slots.Items[slotIndex]; + if (slot.attachment == entry.Value) + { + Attachment attachment = GetAttachment(slotIndex, entry.Key.name); + if (attachment != null) slot.Attachment = attachment; + } + } + } - struct AttachmentKeyTuple { - public readonly int slotIndex; - public readonly string name; - internal readonly int nameHashCode; + struct AttachmentKeyTuple + { + public readonly int slotIndex; + public readonly string name; + internal readonly int nameHashCode; - public AttachmentKeyTuple (int slotIndex, string name) { - this.slotIndex = slotIndex; - this.name = name; - nameHashCode = this.name.GetHashCode(); - } - } + public AttachmentKeyTuple(int slotIndex, string name) + { + this.slotIndex = slotIndex; + this.name = name; + nameHashCode = this.name.GetHashCode(); + } + } - // Avoids boxing in the dictionary. - class AttachmentKeyTupleComparer : IEqualityComparer { - internal static readonly AttachmentKeyTupleComparer Instance = new AttachmentKeyTupleComparer(); + // Avoids boxing in the dictionary. + class AttachmentKeyTupleComparer : IEqualityComparer + { + internal static readonly AttachmentKeyTupleComparer Instance = new AttachmentKeyTupleComparer(); - bool IEqualityComparer.Equals (AttachmentKeyTuple o1, AttachmentKeyTuple o2) { - return o1.slotIndex == o2.slotIndex && o1.nameHashCode == o2.nameHashCode && o1.name == o2.name; - } + bool IEqualityComparer.Equals(AttachmentKeyTuple o1, AttachmentKeyTuple o2) + { + return o1.slotIndex == o2.slotIndex && o1.nameHashCode == o2.nameHashCode && o1.name == o2.name; + } - int IEqualityComparer.GetHashCode (AttachmentKeyTuple o) { - return o.slotIndex; - } - } - } + int IEqualityComparer.GetHashCode(AttachmentKeyTuple o) + { + return o.slotIndex; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Slot.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Slot.cs index 8f9ed93..25e92a1 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Slot.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/Slot.cs @@ -31,76 +31,89 @@ using System; -namespace Spine3_2_xx { - public class Slot { - internal SlotData data; - internal Bone bone; - internal float r, g, b, a; - internal Attachment attachment; - internal float attachmentTime; - internal float[] attachmentVertices = new float[0]; - internal int attachmentVerticesCount; +namespace Spine3_2_xx +{ + public class Slot + { + internal SlotData data; + internal Bone bone; + internal float r, g, b, a; + internal Attachment attachment; + internal float attachmentTime; + internal float[] attachmentVertices = new float[0]; + internal int attachmentVerticesCount; - public SlotData Data { get { return data; } } - public Bone Bone { get { return bone; } } - public Skeleton Skeleton { get { return bone.skeleton; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } + public SlotData Data { get { return data; } } + public Bone Bone { get { return bone; } } + public Skeleton Skeleton { get { return bone.skeleton; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } - /// May be null. - public Attachment Attachment { - get { - return attachment; - } - set { - if (attachment == value) return; - attachment = value; - attachmentTime = bone.skeleton.time; - attachmentVerticesCount = 0; - } - } + /// May be null. + public Attachment Attachment + { + get + { + return attachment; + } + set + { + if (attachment == value) return; + attachment = value; + attachmentTime = bone.skeleton.time; + attachmentVerticesCount = 0; + } + } - public float AttachmentTime { - get { - return bone.skeleton.time - attachmentTime; - } - set { - attachmentTime = bone.skeleton.time - value; - } - } + public float AttachmentTime + { + get + { + return bone.skeleton.time - attachmentTime; + } + set + { + attachmentTime = bone.skeleton.time - value; + } + } - public float[] AttachmentVertices { get { return attachmentVertices; } set { attachmentVertices = value; } } - public int AttachmentVerticesCount { get { return attachmentVerticesCount; } set { attachmentVerticesCount = value; } } + public float[] AttachmentVertices { get { return attachmentVertices; } set { attachmentVertices = value; } } + public int AttachmentVerticesCount { get { return attachmentVerticesCount; } set { attachmentVerticesCount = value; } } - public Slot (SlotData data, Bone bone) { - if (data == null) throw new ArgumentNullException("data cannot be null."); - if (bone == null) throw new ArgumentNullException("bone cannot be null."); - this.data = data; - this.bone = bone; - SetToSetupPose(); - } + public Slot(SlotData data, Bone bone) + { + if (data == null) throw new ArgumentNullException("data cannot be null."); + if (bone == null) throw new ArgumentNullException("bone cannot be null."); + this.data = data; + this.bone = bone; + SetToSetupPose(); + } - internal void SetToSetupPose (int slotIndex) { - r = data.r; - g = data.g; - b = data.b; - a = data.a; - if (data.attachmentName == null) - Attachment = null; - else { - attachment = null; - Attachment = bone.skeleton.GetAttachment(slotIndex, data.attachmentName); - } - } + internal void SetToSetupPose(int slotIndex) + { + r = data.r; + g = data.g; + b = data.b; + a = data.a; + if (data.attachmentName == null) + Attachment = null; + else + { + attachment = null; + Attachment = bone.skeleton.GetAttachment(slotIndex, data.attachmentName); + } + } - public void SetToSetupPose () { - SetToSetupPose(bone.skeleton.data.slots.IndexOf(data)); - } + public void SetToSetupPose() + { + SetToSetupPose(bone.skeleton.data.slots.IndexOf(data)); + } - override public String ToString () { - return data.name; - } - } + override public String ToString() + { + return data.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/SlotData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/SlotData.cs index afbf7b5..16b674f 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/SlotData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/SlotData.cs @@ -31,33 +31,37 @@ using System; -namespace Spine3_2_xx { - public class SlotData { - internal String name; - internal BoneData boneData; - internal float r = 1, g = 1, b = 1, a = 1; - internal String attachmentName; - internal BlendMode blendMode; +namespace Spine3_2_xx +{ + public class SlotData + { + internal String name; + internal BoneData boneData; + internal float r = 1, g = 1, b = 1, a = 1; + internal String attachmentName; + internal BlendMode blendMode; - public String Name { get { return name; } } - public BoneData BoneData { get { return boneData; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } - /// May be null. - public String AttachmentName { get { return attachmentName; } set { attachmentName = value; } } - public BlendMode BlendMode { get { return blendMode; } set { blendMode = value; } } + public String Name { get { return name; } } + public BoneData BoneData { get { return boneData; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } + /// May be null. + public String AttachmentName { get { return attachmentName; } set { attachmentName = value; } } + public BlendMode BlendMode { get { return blendMode; } set { blendMode = value; } } - public SlotData (String name, BoneData boneData) { - if (name == null) throw new ArgumentNullException("name cannot be null."); - if (boneData == null) throw new ArgumentNullException("boneData cannot be null."); - this.name = name; - this.boneData = boneData; - } + public SlotData(String name, BoneData boneData) + { + if (name == null) throw new ArgumentNullException("name cannot be null."); + if (boneData == null) throw new ArgumentNullException("boneData cannot be null."); + this.name = name; + this.boneData = boneData; + } - override public String ToString () { - return name; - } - } + override public String ToString() + { + return name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/TransformConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/TransformConstraint.cs index 78a6ab5..6d7df6f 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/TransformConstraint.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/TransformConstraint.cs @@ -30,108 +30,117 @@ *****************************************************************************/ using System; -using System.Collections.Generic; -namespace Spine3_2_xx { - public class TransformConstraint : IUpdatable { - internal TransformConstraintData data; - internal Bone bone, target; - internal float rotateMix, translateMix, scaleMix, shearMix; - internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY; +namespace Spine3_2_xx +{ + public class TransformConstraint : IUpdatable + { + internal TransformConstraintData data; + internal Bone bone, target; + internal float rotateMix, translateMix, scaleMix, shearMix; + internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY; - public TransformConstraintData Data { get { return data; } } - public Bone Bone { get { return bone; } set { bone = value; } } - public Bone Target { get { return target; } set { target = value; } } - public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } - public float TranslateMix { get { return translateMix; } set { translateMix = value; } } - public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } } - public float ShearMix { get { return shearMix; } set { shearMix = value; } } + public TransformConstraintData Data { get { return data; } } + public Bone Bone { get { return bone; } set { bone = value; } } + public Bone Target { get { return target; } set { target = value; } } + public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } + public float TranslateMix { get { return translateMix; } set { translateMix = value; } } + public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } } + public float ShearMix { get { return shearMix; } set { shearMix = value; } } - public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } - public float OffsetX { get { return offsetX; } set { offsetX = value; } } - public float OffsetY { get { return offsetY; } set { offsetY = value; } } - public float OffsetScaleX { get { return offsetScaleX; } set { offsetScaleX = value; } } - public float OffsetScaleY { get { return offsetScaleY; } set { offsetScaleY = value; } } - public float OffsetShearY { get { return offsetShearY; } set { offsetShearY = value; } } + public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } + public float OffsetX { get { return offsetX; } set { offsetX = value; } } + public float OffsetY { get { return offsetY; } set { offsetY = value; } } + public float OffsetScaleX { get { return offsetScaleX; } set { offsetScaleX = value; } } + public float OffsetScaleY { get { return offsetScaleY; } set { offsetScaleY = value; } } + public float OffsetShearY { get { return offsetShearY; } set { offsetShearY = value; } } - public TransformConstraint (TransformConstraintData data, Skeleton skeleton) { - if (data == null) throw new ArgumentNullException("data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); - this.data = data; - translateMix = data.translateMix; - rotateMix = data.rotateMix; - scaleMix = data.scaleMix; - shearMix = data.shearMix; - offsetRotation = data.offsetRotation; - offsetX = data.offsetX; - offsetY = data.offsetY; - offsetScaleX = data.offsetScaleX; - offsetScaleY = data.offsetScaleY; - offsetShearY = data.offsetShearY; + public TransformConstraint(TransformConstraintData data, Skeleton skeleton) + { + if (data == null) throw new ArgumentNullException("data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); + this.data = data; + translateMix = data.translateMix; + rotateMix = data.rotateMix; + scaleMix = data.scaleMix; + shearMix = data.shearMix; + offsetRotation = data.offsetRotation; + offsetX = data.offsetX; + offsetY = data.offsetY; + offsetScaleX = data.offsetScaleX; + offsetScaleY = data.offsetScaleY; + offsetShearY = data.offsetShearY; - bone = skeleton.FindBone(data.bone.name); - target = skeleton.FindBone(data.target.name); - } + bone = skeleton.FindBone(data.bone.name); + target = skeleton.FindBone(data.target.name); + } - public void Apply () { - Update(); - } + public void Apply() + { + Update(); + } - public void Update () { - Bone bone = this.bone; - Bone target = this.target; + public void Update() + { + Bone bone = this.bone; + Bone target = this.target; - if (rotateMix > 0) { - float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - float r = MathUtils.Atan2(target.c, target.a) - MathUtils.Atan2(c, a) + offsetRotation * MathUtils.degRad; - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) r += MathUtils.PI2; - r *= rotateMix; - float cos = MathUtils.Cos(r), sin = MathUtils.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 (rotateMix > 0) + { + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + float r = MathUtils.Atan2(target.c, target.a) - MathUtils.Atan2(c, a) + offsetRotation * MathUtils.degRad; + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) r += MathUtils.PI2; + r *= rotateMix; + float cos = MathUtils.Cos(r), sin = MathUtils.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 (scaleMix > 0) { - float bs = (float)Math.Sqrt(bone.a * bone.a + bone.c * bone.c); - float ts = (float)Math.Sqrt(target.a * target.a + target.c * target.c); - float s = bs > 0.00001f ? (bs + (ts - bs + offsetScaleX) * scaleMix) / bs : 0; - bone.a *= s; - bone.c *= s; - bs = (float)Math.Sqrt(bone.b * bone.b + bone.d * bone.d); - ts = (float)Math.Sqrt(target.b * target.b + target.d * target.d); - s = bs > 0.00001f ? (bs + (ts - bs + offsetScaleY) * scaleMix) / bs : 0; - bone.b *= s; - bone.d *= s; - } + if (scaleMix > 0) + { + float bs = (float)Math.Sqrt(bone.a * bone.a + bone.c * bone.c); + float ts = (float)Math.Sqrt(target.a * target.a + target.c * target.c); + float s = bs > 0.00001f ? (bs + (ts - bs + offsetScaleX) * scaleMix) / bs : 0; + bone.a *= s; + bone.c *= s; + bs = (float)Math.Sqrt(bone.b * bone.b + bone.d * bone.d); + ts = (float)Math.Sqrt(target.b * target.b + target.d * target.d); + s = bs > 0.00001f ? (bs + (ts - bs + offsetScaleY) * scaleMix) / bs : 0; + bone.b *= s; + bone.d *= s; + } - if (shearMix > 0) { - float b = bone.b, d = bone.d; - float by = MathUtils.Atan2(d, b); - float r = MathUtils.Atan2(target.d, target.b) - MathUtils.Atan2(target.c, target.a) - (by - MathUtils.Atan2(bone.c, bone.a)); - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) r += MathUtils.PI2; - r = by + (r + offsetShearY * MathUtils.degRad) * shearMix; - float s = (float)Math.Sqrt(b * b + d * d); - bone.b = MathUtils.Cos(r) * s; - bone.d = MathUtils.Sin(r) * s; - } + if (shearMix > 0) + { + float b = bone.b, d = bone.d; + float by = MathUtils.Atan2(d, b); + float r = MathUtils.Atan2(target.d, target.b) - MathUtils.Atan2(target.c, target.a) - (by - MathUtils.Atan2(bone.c, bone.a)); + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) r += MathUtils.PI2; + r = by + (r + offsetShearY * MathUtils.degRad) * shearMix; + float s = (float)Math.Sqrt(b * b + d * d); + bone.b = MathUtils.Cos(r) * s; + bone.d = MathUtils.Sin(r) * s; + } - float translateMix = this.translateMix; - if (translateMix > 0) { - float tx, ty; - target.LocalToWorld(offsetX, offsetY, out tx, out ty); - bone.worldX += (tx - bone.worldX) * translateMix; - bone.worldY += (ty - bone.worldY) * translateMix; - } - } + float translateMix = this.translateMix; + if (translateMix > 0) + { + float tx, ty; + target.LocalToWorld(offsetX, offsetY, out tx, out ty); + bone.worldX += (tx - bone.worldX) * translateMix; + bone.worldY += (ty - bone.worldY) * translateMix; + } + } - override public String ToString () { - return data.name; - } - } + override public String ToString() + { + return data.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/TransformConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/TransformConstraintData.cs index d6efa4c..1906fac 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/TransformConstraintData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/TransformConstraintData.cs @@ -30,37 +30,40 @@ *****************************************************************************/ using System; -using System.Collections.Generic; -namespace Spine3_2_xx { - public class TransformConstraintData { - internal String name; - internal BoneData bone, target; - internal float rotateMix, translateMix, scaleMix, shearMix; - internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY; +namespace Spine3_2_xx +{ + public class TransformConstraintData + { + internal String name; + internal BoneData bone, target; + internal float rotateMix, translateMix, scaleMix, shearMix; + internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY; - public String Name { get { return name; } } - public BoneData Bone { get { return bone; } set { bone = value; } } - public BoneData Target { get { return target; } set { target = value; } } - public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } - public float TranslateMix { get { return translateMix; } set { translateMix = value; } } - public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } } - public float ShearMix { get { return shearMix; } set { shearMix = value; } } + public String Name { get { return name; } } + public BoneData Bone { get { return bone; } set { bone = value; } } + public BoneData Target { get { return target; } set { target = value; } } + public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } + public float TranslateMix { get { return translateMix; } set { translateMix = value; } } + public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } } + public float ShearMix { get { return shearMix; } set { shearMix = value; } } - public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } - public float OffsetX { get { return offsetX; } set { offsetX = value; } } - public float OffsetY { get { return offsetY; } set { offsetY = value; } } - public float OffsetScaleX { get { return offsetScaleX; } set { offsetScaleX = value; } } - public float OffsetScaleY { get { return offsetScaleY; } set { offsetScaleY = value; } } - public float OffsetShearY { get { return offsetShearY; } set { offsetShearY = value; } } + public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } + public float OffsetX { get { return offsetX; } set { offsetX = value; } } + public float OffsetY { get { return offsetY; } set { offsetY = value; } } + public float OffsetScaleX { get { return offsetScaleX; } set { offsetScaleX = value; } } + public float OffsetScaleY { get { return offsetScaleY; } set { offsetScaleY = value; } } + public float OffsetShearY { get { return offsetShearY; } set { offsetShearY = value; } } - public TransformConstraintData (String name) { - if (name == null) throw new ArgumentNullException("name cannot be null."); - this.name = name; - } + public TransformConstraintData(String name) + { + if (name == null) throw new ArgumentNullException("name cannot be null."); + this.name = name; + } - override public String ToString () { - return name; - } - } + override public String ToString() + { + return name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/XnaLoader/MeshBatcher.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/XnaLoader/MeshBatcher.cs index 2cfe961..f9c2f5e 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/XnaLoader/MeshBatcher.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/XnaLoader/MeshBatcher.cs @@ -32,136 +32,146 @@ using System; using System.Collections.Generic; using Microsoft.Xna.Framework.Graphics; -using Microsoft.Xna.Framework; -namespace Spine3_2_xx { - // #region License - // /* - // Microsoft Public License (Ms-PL) - // MonoGame - Copyright ?2009 The MonoGame Team - // - // All rights reserved. - // - // This license governs use of the accompanying software. If you use the software, you accept this license. If you do not - // accept the license, do not use the software. - // - // 1. Definitions - // The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under - // U.S. copyright law. - // - // A "contribution" is the original software, or any additions or changes to the software. - // A "contributor" is any person that distributes its contribution under this license. - // "Licensed patents" are a contributor's patent claims that read directly on its contribution. - // - // 2. Grant of Rights - // (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, - // each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. - // (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, - // each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. - // - // 3. Conditions and Limitations - // (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. - // (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, - // your patent license from such contributor to the software ends automatically. - // (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution - // notices that are present in the software. - // (D) If you distribute any portion of the software in source code form, you may do so only under this license by including - // a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object - // code form, you may only do so under a license that complies with this license. - // (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees - // or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent - // permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular - // purpose and non-infringement. - // */ - // #endregion License - // +namespace Spine3_2_xx +{ + // #region License + // /* + // Microsoft Public License (Ms-PL) + // MonoGame - Copyright ?2009 The MonoGame Team + // + // All rights reserved. + // + // This license governs use of the accompanying software. If you use the software, you accept this license. If you do not + // accept the license, do not use the software. + // + // 1. Definitions + // The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under + // U.S. copyright law. + // + // A "contribution" is the original software, or any additions or changes to the software. + // A "contributor" is any person that distributes its contribution under this license. + // "Licensed patents" are a contributor's patent claims that read directly on its contribution. + // + // 2. Grant of Rights + // (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, + // each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. + // (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, + // each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. + // + // 3. Conditions and Limitations + // (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. + // (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, + // your patent license from such contributor to the software ends automatically. + // (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution + // notices that are present in the software. + // (D) If you distribute any portion of the software in source code form, you may do so only under this license by including + // a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object + // code form, you may only do so under a license that complies with this license. + // (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees + // or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent + // permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular + // purpose and non-infringement. + // */ + // #endregion License + // - /// Draws batched meshes. - public class MeshBatcher { - private readonly List items; - private readonly Queue freeItems; - private VertexPositionColorTexture[] vertexArray = { }; - private short[] triangles = { }; + /// Draws batched meshes. + public class MeshBatcher + { + private readonly List items; + private readonly Queue freeItems; + private VertexPositionColorTexture[] vertexArray = { }; + private short[] triangles = { }; - public MeshBatcher () { - items = new List(256); - freeItems = new Queue(256); - EnsureCapacity(256, 512); - } + public MeshBatcher() + { + items = new List(256); + freeItems = new Queue(256); + EnsureCapacity(256, 512); + } - /// Returns a pooled MeshItem. - public MeshItem NextItem (int vertexCount, int triangleCount) { - MeshItem item = freeItems.Count > 0 ? freeItems.Dequeue() : new MeshItem(); - if (item.vertices.Length < vertexCount) item.vertices = new VertexPositionColorTexture[vertexCount]; - if (item.triangles.Length < triangleCount) item.triangles = new int[triangleCount]; - item.vertexCount = vertexCount; - item.triangleCount = triangleCount; - items.Add(item); - return item; - } + /// Returns a pooled MeshItem. + public MeshItem NextItem(int vertexCount, int triangleCount) + { + MeshItem item = freeItems.Count > 0 ? freeItems.Dequeue() : new MeshItem(); + if (item.vertices.Length < vertexCount) item.vertices = new VertexPositionColorTexture[vertexCount]; + if (item.triangles.Length < triangleCount) item.triangles = new int[triangleCount]; + item.vertexCount = vertexCount; + item.triangleCount = triangleCount; + items.Add(item); + return item; + } - private void EnsureCapacity (int vertexCount, int triangleCount) { - if (vertexArray.Length < vertexCount) vertexArray = new VertexPositionColorTexture[vertexCount]; - if (triangles.Length < triangleCount) triangles = new short[triangleCount]; - } + private void EnsureCapacity(int vertexCount, int triangleCount) + { + if (vertexArray.Length < vertexCount) vertexArray = new VertexPositionColorTexture[vertexCount]; + if (triangles.Length < triangleCount) triangles = new short[triangleCount]; + } - public void Draw (GraphicsDevice device) { - if (items.Count == 0) return; + public void Draw(GraphicsDevice device) + { + if (items.Count == 0) return; - int itemCount = items.Count; - int vertexCount = 0, triangleCount = 0; - for (int i = 0; i < itemCount; i++) { - MeshItem item = items[i]; - vertexCount += item.vertexCount; - triangleCount += item.triangleCount; - } - EnsureCapacity(vertexCount, triangleCount); + int itemCount = items.Count; + int vertexCount = 0, triangleCount = 0; + for (int i = 0; i < itemCount; i++) + { + MeshItem item = items[i]; + vertexCount += item.vertexCount; + triangleCount += item.triangleCount; + } + EnsureCapacity(vertexCount, triangleCount); - vertexCount = 0; - triangleCount = 0; - Texture2D lastTexture = null; - for (int i = 0; i < itemCount; i++) { - MeshItem item = items[i]; - int itemVertexCount = item.vertexCount; + vertexCount = 0; + triangleCount = 0; + Texture2D lastTexture = null; + for (int i = 0; i < itemCount; i++) + { + MeshItem item = items[i]; + int itemVertexCount = item.vertexCount; - if (item.texture != lastTexture || vertexCount + itemVertexCount > short.MaxValue) { - FlushVertexArray(device, vertexCount, triangleCount); - vertexCount = 0; - triangleCount = 0; - lastTexture = item.texture; - device.Textures[0] = lastTexture; - } + if (item.texture != lastTexture || vertexCount + itemVertexCount > short.MaxValue) + { + FlushVertexArray(device, vertexCount, triangleCount); + vertexCount = 0; + triangleCount = 0; + lastTexture = item.texture; + device.Textures[0] = lastTexture; + } - int[] itemTriangles = item.triangles; - int itemTriangleCount = item.triangleCount; - for (int ii = 0, t = triangleCount; ii < itemTriangleCount; ii++, t++) - triangles[t] = (short)(itemTriangles[ii] + vertexCount); - triangleCount += itemTriangleCount; + int[] itemTriangles = item.triangles; + int itemTriangleCount = item.triangleCount; + for (int ii = 0, t = triangleCount; ii < itemTriangleCount; ii++, t++) + triangles[t] = (short)(itemTriangles[ii] + vertexCount); + triangleCount += itemTriangleCount; - Array.Copy(item.vertices, 0, vertexArray, vertexCount, itemVertexCount); - vertexCount += itemVertexCount; + Array.Copy(item.vertices, 0, vertexArray, vertexCount, itemVertexCount); + vertexCount += itemVertexCount; - item.texture = null; - freeItems.Enqueue(item); - } - FlushVertexArray(device, vertexCount, triangleCount); - items.Clear(); - } + item.texture = null; + freeItems.Enqueue(item); + } + FlushVertexArray(device, vertexCount, triangleCount); + items.Clear(); + } - private void FlushVertexArray (GraphicsDevice device, int vertexCount, int triangleCount) { - if (vertexCount == 0) return; - device.DrawUserIndexedPrimitives( - PrimitiveType.TriangleList, - vertexArray, 0, vertexCount, - triangles, 0, triangleCount / 3, - VertexPositionColorTexture.VertexDeclaration); - } - } + private void FlushVertexArray(GraphicsDevice device, int vertexCount, int triangleCount) + { + if (vertexCount == 0) return; + device.DrawUserIndexedPrimitives( + PrimitiveType.TriangleList, + vertexArray, 0, vertexCount, + triangles, 0, triangleCount / 3, + VertexPositionColorTexture.VertexDeclaration); + } + } - public class MeshItem { - public Texture2D texture; - public int vertexCount, triangleCount; - public VertexPositionColorTexture[] vertices = { }; - public int[] triangles = { }; - } + public class MeshItem + { + public Texture2D texture; + public int vertexCount, triangleCount; + public VertexPositionColorTexture[] vertices = { }; + public int[] triangles = { }; + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/XnaLoader/RegionBatcher.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/XnaLoader/RegionBatcher.cs index 93f0f2e..7955fa0 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/XnaLoader/RegionBatcher.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/XnaLoader/RegionBatcher.cs @@ -32,151 +32,163 @@ using System; using System.Collections.Generic; using Microsoft.Xna.Framework.Graphics; -using Microsoft.Xna.Framework; -namespace Spine3_2_xx { - // #region License - // /* - // Microsoft Public License (Ms-PL) - // MonoGame - Copyright 2009 The MonoGame Team - // - // All rights reserved. - // - // This license governs use of the accompanying software. If you use the software, you accept this license. If you do not - // accept the license, do not use the software. - // - // 1. Definitions - // The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under - // U.S. copyright law. - // - // A "contribution" is the original software, or any additions or changes to the software. - // A "contributor" is any person that distributes its contribution under this license. - // "Licensed patents" are a contributor's patent claims that read directly on its contribution. - // - // 2. Grant of Rights - // (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, - // each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. - // (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, - // each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. - // - // 3. Conditions and Limitations - // (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. - // (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, - // your patent license from such contributor to the software ends automatically. - // (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution - // notices that are present in the software. - // (D) If you distribute any portion of the software in source code form, you may do so only under this license by including - // a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object - // code form, you may only do so under a license that complies with this license. - // (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees - // or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent - // permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular - // purpose and non-infringement. - // */ - // #endregion License - // +namespace Spine3_2_xx +{ + // #region License + // /* + // Microsoft Public License (Ms-PL) + // MonoGame - Copyright 2009 The MonoGame Team + // + // All rights reserved. + // + // This license governs use of the accompanying software. If you use the software, you accept this license. If you do not + // accept the license, do not use the software. + // + // 1. Definitions + // The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under + // U.S. copyright law. + // + // A "contribution" is the original software, or any additions or changes to the software. + // A "contributor" is any person that distributes its contribution under this license. + // "Licensed patents" are a contributor's patent claims that read directly on its contribution. + // + // 2. Grant of Rights + // (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, + // each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. + // (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, + // each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. + // + // 3. Conditions and Limitations + // (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. + // (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, + // your patent license from such contributor to the software ends automatically. + // (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution + // notices that are present in the software. + // (D) If you distribute any portion of the software in source code form, you may do so only under this license by including + // a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object + // code form, you may only do so under a license that complies with this license. + // (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees + // or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent + // permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular + // purpose and non-infringement. + // */ + // #endregion License + // - /// Draws batched quads using indices. - public class RegionBatcher { - private const int maxBatchSize = short.MaxValue / 6; // 6 = 4 vertices unique and 2 shared, per quad - private readonly List items; - private readonly Queue freeItems; - private VertexPositionColorTexture[] vertexArray; - private short[] indices; + /// Draws batched quads using indices. + public class RegionBatcher + { + private const int maxBatchSize = short.MaxValue / 6; // 6 = 4 vertices unique and 2 shared, per quad + private readonly List items; + private readonly Queue freeItems; + private VertexPositionColorTexture[] vertexArray; + private short[] indices; - public RegionBatcher () { - items = new List(256); - freeItems = new Queue(256); - EnsureArrayCapacity(256); - } + public RegionBatcher() + { + items = new List(256); + freeItems = new Queue(256); + EnsureArrayCapacity(256); + } - /// Returns a pooled RegionItem. - public RegionItem NextItem () { - RegionItem item = freeItems.Count > 0 ? freeItems.Dequeue() : new RegionItem(); - items.Add(item); - return item; - } + /// Returns a pooled RegionItem. + public RegionItem NextItem() + { + RegionItem item = freeItems.Count > 0 ? freeItems.Dequeue() : new RegionItem(); + items.Add(item); + return item; + } - /// Resize and recreate the indices and vertex position color buffers. - private void EnsureArrayCapacity (int itemCount) { - if (indices != null && indices.Length >= 6 * itemCount) return; + /// Resize and recreate the indices and vertex position color buffers. + private void EnsureArrayCapacity(int itemCount) + { + if (indices != null && indices.Length >= 6 * itemCount) return; - short[] newIndices = new short[6 * itemCount]; - int start = 0; - if (indices != null) { - indices.CopyTo(newIndices, 0); - start = indices.Length / 6; - } - for (var i = start; i < itemCount; i++) { - /* TL TR + short[] newIndices = new short[6 * itemCount]; + int start = 0; + if (indices != null) + { + indices.CopyTo(newIndices, 0); + start = indices.Length / 6; + } + for (var i = start; i < itemCount; i++) + { + /* TL TR * 0----1 0,1,2,3 = index offsets for vertex indices * | | TL,TR,BL,BR are vertex references in RegionItem. * 2----3 * BL BR */ - newIndices[i * 6 + 0] = (short)(i * 4); - newIndices[i * 6 + 1] = (short)(i * 4 + 1); - newIndices[i * 6 + 2] = (short)(i * 4 + 2); - newIndices[i * 6 + 3] = (short)(i * 4 + 1); - newIndices[i * 6 + 4] = (short)(i * 4 + 3); - newIndices[i * 6 + 5] = (short)(i * 4 + 2); - } - indices = newIndices; + newIndices[i * 6 + 0] = (short)(i * 4); + newIndices[i * 6 + 1] = (short)(i * 4 + 1); + newIndices[i * 6 + 2] = (short)(i * 4 + 2); + newIndices[i * 6 + 3] = (short)(i * 4 + 1); + newIndices[i * 6 + 4] = (short)(i * 4 + 3); + newIndices[i * 6 + 5] = (short)(i * 4 + 2); + } + indices = newIndices; - vertexArray = new VertexPositionColorTexture[4 * itemCount]; - } + vertexArray = new VertexPositionColorTexture[4 * itemCount]; + } - public void Draw (GraphicsDevice device) { - if (items.Count == 0) return; + public void Draw(GraphicsDevice device) + { + if (items.Count == 0) return; - int itemIndex = 0; - int itemCount = items.Count; - while (itemCount > 0) { - int itemsToProcess = Math.Min(itemCount, maxBatchSize); - EnsureArrayCapacity(itemsToProcess); + int itemIndex = 0; + int itemCount = items.Count; + while (itemCount > 0) + { + int itemsToProcess = Math.Min(itemCount, maxBatchSize); + EnsureArrayCapacity(itemsToProcess); - var count = 0; - Texture2D texture = null; - for (int i = 0; i < itemsToProcess; i++, itemIndex++) { - RegionItem item = items[itemIndex]; - if (item.texture != texture) { - FlushVertexArray(device, count); - texture = item.texture; - count = 0; - device.Textures[0] = texture; - } + var count = 0; + Texture2D texture = null; + for (int i = 0; i < itemsToProcess; i++, itemIndex++) + { + RegionItem item = items[itemIndex]; + if (item.texture != texture) + { + FlushVertexArray(device, count); + texture = item.texture; + count = 0; + device.Textures[0] = texture; + } - vertexArray[count++] = item.vertexTL; - vertexArray[count++] = item.vertexTR; - vertexArray[count++] = item.vertexBL; - vertexArray[count++] = item.vertexBR; + vertexArray[count++] = item.vertexTL; + vertexArray[count++] = item.vertexTR; + vertexArray[count++] = item.vertexBL; + vertexArray[count++] = item.vertexBR; - item.texture = null; - freeItems.Enqueue(item); - } - FlushVertexArray(device, count); - itemCount -= itemsToProcess; - } - items.Clear(); - } + item.texture = null; + freeItems.Enqueue(item); + } + FlushVertexArray(device, count); + itemCount -= itemsToProcess; + } + items.Clear(); + } - /// Sends the triangle list to the graphics device. - /// Start index of vertices to draw. Not used except to compute the count of vertices to draw. - /// End index of vertices to draw. Not used except to compute the count of vertices to draw. - private void FlushVertexArray (GraphicsDevice device, int count) { - if (count == 0) return; - device.DrawUserIndexedPrimitives( - PrimitiveType.TriangleList, - vertexArray, 0, count, - indices, 0, (count / 4) * 2, - VertexPositionColorTexture.VertexDeclaration); - } - } + /// Sends the triangle list to the graphics device. + /// Start index of vertices to draw. Not used except to compute the count of vertices to draw. + /// End index of vertices to draw. Not used except to compute the count of vertices to draw. + private void FlushVertexArray(GraphicsDevice device, int count) + { + if (count == 0) return; + device.DrawUserIndexedPrimitives( + PrimitiveType.TriangleList, + vertexArray, 0, count, + indices, 0, (count / 4) * 2, + VertexPositionColorTexture.VertexDeclaration); + } + } - public class RegionItem { - public Texture2D texture; - public VertexPositionColorTexture vertexTL; - public VertexPositionColorTexture vertexTR; - public VertexPositionColorTexture vertexBL; - public VertexPositionColorTexture vertexBR; - } + public class RegionItem + { + public Texture2D texture; + public VertexPositionColorTexture vertexTL; + public VertexPositionColorTexture vertexTR; + public VertexPositionColorTexture vertexBL; + public VertexPositionColorTexture vertexBR; + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/XnaLoader/SkeletonMeshRenderer.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/XnaLoader/SkeletonMeshRenderer.cs index c6bf73c..2a87ee9 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/XnaLoader/SkeletonMeshRenderer.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/XnaLoader/SkeletonMeshRenderer.cs @@ -29,205 +29,228 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; -using System.Collections.Generic; -using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Spine3_2_xx +{ + /// Draws region and mesh attachments. + public class SkeletonMeshRenderer + { + private const int TL = 0; + private const int TR = 1; + private const int BL = 2; + private const int BR = 3; + + GraphicsDevice device; + MeshBatcher batcher; + RasterizerState rasterizerState; + float[] vertices = new float[8]; + int[] quadTriangles = { 0, 1, 2, 1, 3, 2 }; + BlendState defaultBlendState; + + BasicEffect effect; + public BasicEffect Effect { get { return effect; } set { effect = value; } } + + private bool premultipliedAlpha; + public bool PremultipliedAlpha { get { return premultipliedAlpha; } set { premultipliedAlpha = value; } } + + public SkeletonMeshRenderer(GraphicsDevice device) + { + this.device = device; + + batcher = new MeshBatcher(); + + effect = new BasicEffect(device); + effect.World = Matrix.Identity; + effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); + effect.TextureEnabled = true; + effect.VertexColorEnabled = true; + + rasterizerState = new RasterizerState(); + rasterizerState.CullMode = CullMode.None; + + Bone.yDown = true; + } + + public void Begin() + { + defaultBlendState = premultipliedAlpha ? BlendState.AlphaBlend : BlendState.NonPremultiplied; + + device.RasterizerState = rasterizerState; + device.BlendState = defaultBlendState; + + effect.Projection = Matrix.CreateOrthographicOffCenter(0, device.Viewport.Width, device.Viewport.Height, 0, 1, 0); + } + + public void End() + { + foreach (EffectPass pass in effect.CurrentTechnique.Passes) + { + pass.Apply(); + batcher.Draw(device); + } + } + + public void Draw(Skeleton skeleton) + { + float[] vertices = this.vertices; + var drawOrder = skeleton.DrawOrder; + var drawOrderItems = skeleton.DrawOrder.Items; + float skeletonR = skeleton.R, skeletonG = skeleton.G, skeletonB = skeleton.B, skeletonA = skeleton.A; + for (int i = 0, n = drawOrder.Count; i < n; i++) + { + Slot slot = drawOrderItems[i]; + Attachment attachment = slot.Attachment; + if (attachment is RegionAttachment) + { + RegionAttachment regionAttachment = (RegionAttachment)attachment; + BlendState blend = slot.Data.BlendMode == BlendMode.additive ? BlendState.Additive : defaultBlendState; + if (device.BlendState != blend) + { + End(); + device.BlendState = blend; + } + + MeshItem item = batcher.NextItem(4, 6); + item.triangles = quadTriangles; + VertexPositionColorTexture[] itemVertices = item.vertices; + + AtlasRegion region = (AtlasRegion)regionAttachment.RendererObject; + item.texture = (Texture2D)region.page.rendererObject; + + Color color; + float a = skeletonA * slot.A * regionAttachment.A; + if (premultipliedAlpha) + { + color = new Color( + skeletonR * slot.R * regionAttachment.R * a, + skeletonG * slot.G * regionAttachment.G * a, + skeletonB * slot.B * regionAttachment.B * a, a); + } + else + { + color = new Color( + skeletonR * slot.R * regionAttachment.R, + skeletonG * slot.G * regionAttachment.G, + skeletonB * slot.B * regionAttachment.B, a); + } + itemVertices[TL].Color = color; + itemVertices[BL].Color = color; + itemVertices[BR].Color = color; + itemVertices[TR].Color = color; + + regionAttachment.ComputeWorldVertices(slot.Bone, vertices); + itemVertices[TL].Position.X = vertices[RegionAttachment.X1]; + itemVertices[TL].Position.Y = vertices[RegionAttachment.Y1]; + itemVertices[TL].Position.Z = 0; + itemVertices[BL].Position.X = vertices[RegionAttachment.X2]; + itemVertices[BL].Position.Y = vertices[RegionAttachment.Y2]; + itemVertices[BL].Position.Z = 0; + itemVertices[BR].Position.X = vertices[RegionAttachment.X3]; + itemVertices[BR].Position.Y = vertices[RegionAttachment.Y3]; + itemVertices[BR].Position.Z = 0; + itemVertices[TR].Position.X = vertices[RegionAttachment.X4]; + itemVertices[TR].Position.Y = vertices[RegionAttachment.Y4]; + itemVertices[TR].Position.Z = 0; + + float[] uvs = regionAttachment.UVs; + itemVertices[TL].TextureCoordinate.X = uvs[RegionAttachment.X1]; + itemVertices[TL].TextureCoordinate.Y = uvs[RegionAttachment.Y1]; + itemVertices[BL].TextureCoordinate.X = uvs[RegionAttachment.X2]; + itemVertices[BL].TextureCoordinate.Y = uvs[RegionAttachment.Y2]; + itemVertices[BR].TextureCoordinate.X = uvs[RegionAttachment.X3]; + itemVertices[BR].TextureCoordinate.Y = uvs[RegionAttachment.Y3]; + itemVertices[TR].TextureCoordinate.X = uvs[RegionAttachment.X4]; + itemVertices[TR].TextureCoordinate.Y = uvs[RegionAttachment.Y4]; + } + else if (attachment is MeshAttachment) + { + MeshAttachment mesh = (MeshAttachment)attachment; + int vertexCount = mesh.Vertices.Length; + if (vertices.Length < vertexCount) vertices = new float[vertexCount]; + mesh.ComputeWorldVertices(slot, vertices); + + int[] triangles = mesh.Triangles; + MeshItem item = batcher.NextItem(vertexCount, triangles.Length); + item.triangles = triangles; + + AtlasRegion region = (AtlasRegion)mesh.RendererObject; + item.texture = (Texture2D)region.page.rendererObject; + + Color color; + float a = skeletonA * slot.A * mesh.A; + if (premultipliedAlpha) + { + color = new Color( + skeletonR * slot.R * mesh.R * a, + skeletonG * slot.G * mesh.G * a, + skeletonB * slot.B * mesh.B * a, a); + } + else + { + color = new Color( + skeletonR * slot.R * mesh.R, + skeletonG * slot.G * mesh.G, + skeletonB * slot.B * mesh.B, a); + } + + float[] uvs = mesh.UVs; + VertexPositionColorTexture[] itemVertices = item.vertices; + for (int ii = 0, v = 0; v < vertexCount; ii++, v += 2) + { + itemVertices[ii].Color = color; + itemVertices[ii].Position.X = vertices[v]; + itemVertices[ii].Position.Y = vertices[v + 1]; + itemVertices[ii].Position.Z = 0; + itemVertices[ii].TextureCoordinate.X = uvs[v]; + itemVertices[ii].TextureCoordinate.Y = uvs[v + 1]; + } + } + else if (attachment is WeightedMeshAttachment) + { + WeightedMeshAttachment mesh = (WeightedMeshAttachment)attachment; + int vertexCount = mesh.UVs.Length; + if (vertices.Length < vertexCount) vertices = new float[vertexCount]; + mesh.ComputeWorldVertices(slot, vertices); + + int[] triangles = mesh.Triangles; + MeshItem item = batcher.NextItem(vertexCount, triangles.Length); + item.triangles = triangles; + + AtlasRegion region = (AtlasRegion)mesh.RendererObject; + item.texture = (Texture2D)region.page.rendererObject; + + Color color; + float a = skeletonA * slot.A * mesh.A; + if (premultipliedAlpha) + { + color = new Color( + skeletonR * slot.R * mesh.R * a, + skeletonG * slot.G * mesh.G * a, + skeletonB * slot.B * mesh.B * a, a); + } + else + { + color = new Color( + skeletonR * slot.R * mesh.R, + skeletonG * slot.G * mesh.G, + skeletonB * slot.B * mesh.B, a); + } -namespace Spine3_2_xx { - /// Draws region and mesh attachments. - public class SkeletonMeshRenderer { - private const int TL = 0; - private const int TR = 1; - private const int BL = 2; - private const int BR = 3; - - GraphicsDevice device; - MeshBatcher batcher; - RasterizerState rasterizerState; - float[] vertices = new float[8]; - int[] quadTriangles = { 0, 1, 2, 1, 3, 2 }; - BlendState defaultBlendState; - - BasicEffect effect; - public BasicEffect Effect { get { return effect; } set { effect = value; } } - - private bool premultipliedAlpha; - public bool PremultipliedAlpha { get { return premultipliedAlpha; } set { premultipliedAlpha = value; } } - - public SkeletonMeshRenderer (GraphicsDevice device) { - this.device = device; - - batcher = new MeshBatcher(); - - effect = new BasicEffect(device); - effect.World = Matrix.Identity; - effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); - effect.TextureEnabled = true; - effect.VertexColorEnabled = true; - - rasterizerState = new RasterizerState(); - rasterizerState.CullMode = CullMode.None; - - Bone.yDown = true; - } - - public void Begin () { - defaultBlendState = premultipliedAlpha ? BlendState.AlphaBlend : BlendState.NonPremultiplied; - - device.RasterizerState = rasterizerState; - device.BlendState = defaultBlendState; - - effect.Projection = Matrix.CreateOrthographicOffCenter(0, device.Viewport.Width, device.Viewport.Height, 0, 1, 0); - } - - public void End () { - foreach (EffectPass pass in effect.CurrentTechnique.Passes) { - pass.Apply(); - batcher.Draw(device); - } - } - - public void Draw (Skeleton skeleton) { - float[] vertices = this.vertices; - var drawOrder = skeleton.DrawOrder; - var drawOrderItems = skeleton.DrawOrder.Items; - float skeletonR = skeleton.R, skeletonG = skeleton.G, skeletonB = skeleton.B, skeletonA = skeleton.A; - for (int i = 0, n = drawOrder.Count; i < n; i++) { - Slot slot = drawOrderItems[i]; - Attachment attachment = slot.Attachment; - if (attachment is RegionAttachment) { - RegionAttachment regionAttachment = (RegionAttachment)attachment; - BlendState blend = slot.Data.BlendMode == BlendMode.additive ? BlendState.Additive : defaultBlendState; - if (device.BlendState != blend) { - End(); - device.BlendState = blend; - } - - MeshItem item = batcher.NextItem(4, 6); - item.triangles = quadTriangles; - VertexPositionColorTexture[] itemVertices = item.vertices; - - AtlasRegion region = (AtlasRegion)regionAttachment.RendererObject; - item.texture = (Texture2D)region.page.rendererObject; - - Color color; - float a = skeletonA * slot.A * regionAttachment.A; - if (premultipliedAlpha) { - color = new Color( - skeletonR * slot.R * regionAttachment.R * a, - skeletonG * slot.G * regionAttachment.G * a, - skeletonB * slot.B * regionAttachment.B * a, a); - } else { - color = new Color( - skeletonR * slot.R * regionAttachment.R, - skeletonG * slot.G * regionAttachment.G, - skeletonB * slot.B * regionAttachment.B, a); - } - itemVertices[TL].Color = color; - itemVertices[BL].Color = color; - itemVertices[BR].Color = color; - itemVertices[TR].Color = color; - - regionAttachment.ComputeWorldVertices(slot.Bone, vertices); - itemVertices[TL].Position.X = vertices[RegionAttachment.X1]; - itemVertices[TL].Position.Y = vertices[RegionAttachment.Y1]; - itemVertices[TL].Position.Z = 0; - itemVertices[BL].Position.X = vertices[RegionAttachment.X2]; - itemVertices[BL].Position.Y = vertices[RegionAttachment.Y2]; - itemVertices[BL].Position.Z = 0; - itemVertices[BR].Position.X = vertices[RegionAttachment.X3]; - itemVertices[BR].Position.Y = vertices[RegionAttachment.Y3]; - itemVertices[BR].Position.Z = 0; - itemVertices[TR].Position.X = vertices[RegionAttachment.X4]; - itemVertices[TR].Position.Y = vertices[RegionAttachment.Y4]; - itemVertices[TR].Position.Z = 0; - - float[] uvs = regionAttachment.UVs; - itemVertices[TL].TextureCoordinate.X = uvs[RegionAttachment.X1]; - itemVertices[TL].TextureCoordinate.Y = uvs[RegionAttachment.Y1]; - itemVertices[BL].TextureCoordinate.X = uvs[RegionAttachment.X2]; - itemVertices[BL].TextureCoordinate.Y = uvs[RegionAttachment.Y2]; - itemVertices[BR].TextureCoordinate.X = uvs[RegionAttachment.X3]; - itemVertices[BR].TextureCoordinate.Y = uvs[RegionAttachment.Y3]; - itemVertices[TR].TextureCoordinate.X = uvs[RegionAttachment.X4]; - itemVertices[TR].TextureCoordinate.Y = uvs[RegionAttachment.Y4]; - } else if (attachment is MeshAttachment) { - MeshAttachment mesh = (MeshAttachment)attachment; - int vertexCount = mesh.Vertices.Length; - if (vertices.Length < vertexCount) vertices = new float[vertexCount]; - mesh.ComputeWorldVertices(slot, vertices); - - int[] triangles = mesh.Triangles; - MeshItem item = batcher.NextItem(vertexCount, triangles.Length); - item.triangles = triangles; - - AtlasRegion region = (AtlasRegion)mesh.RendererObject; - item.texture = (Texture2D)region.page.rendererObject; - - Color color; - float a = skeletonA * slot.A * mesh.A; - if (premultipliedAlpha) { - color = new Color( - skeletonR * slot.R * mesh.R * a, - skeletonG * slot.G * mesh.G * a, - skeletonB * slot.B * mesh.B * a, a); - } else { - color = new Color( - skeletonR * slot.R * mesh.R, - skeletonG * slot.G * mesh.G, - skeletonB * slot.B * mesh.B, a); - } - - float[] uvs = mesh.UVs; - VertexPositionColorTexture[] itemVertices = item.vertices; - for (int ii = 0, v = 0; v < vertexCount; ii++, v += 2) { - itemVertices[ii].Color = color; - itemVertices[ii].Position.X = vertices[v]; - itemVertices[ii].Position.Y = vertices[v + 1]; - itemVertices[ii].Position.Z = 0; - itemVertices[ii].TextureCoordinate.X = uvs[v]; - itemVertices[ii].TextureCoordinate.Y = uvs[v + 1]; - } - } else if (attachment is WeightedMeshAttachment) { - WeightedMeshAttachment mesh = (WeightedMeshAttachment)attachment; - int vertexCount = mesh.UVs.Length; - if (vertices.Length < vertexCount) vertices = new float[vertexCount]; - mesh.ComputeWorldVertices(slot, vertices); - - int[] triangles = mesh.Triangles; - MeshItem item = batcher.NextItem(vertexCount, triangles.Length); - item.triangles = triangles; - - AtlasRegion region = (AtlasRegion)mesh.RendererObject; - item.texture = (Texture2D)region.page.rendererObject; - - Color color; - float a = skeletonA * slot.A * mesh.A; - if (premultipliedAlpha) { - color = new Color( - skeletonR * slot.R * mesh.R * a, - skeletonG * slot.G * mesh.G * a, - skeletonB * slot.B * mesh.B * a, a); - } else { - color = new Color( - skeletonR * slot.R * mesh.R, - skeletonG * slot.G * mesh.G, - skeletonB * slot.B * mesh.B, a); - } - - float[] uvs = mesh.UVs; - VertexPositionColorTexture[] itemVertices = item.vertices; - for (int ii = 0, v = 0; v < vertexCount; ii++, v += 2) { - itemVertices[ii].Color = color; - itemVertices[ii].Position.X = vertices[v]; - itemVertices[ii].Position.Y = vertices[v + 1]; - itemVertices[ii].Position.Z = 0; - itemVertices[ii].TextureCoordinate.X = uvs[v]; - itemVertices[ii].TextureCoordinate.Y = uvs[v + 1]; - } - } - } - } - } + float[] uvs = mesh.UVs; + VertexPositionColorTexture[] itemVertices = item.vertices; + for (int ii = 0, v = 0; v < vertexCount; ii++, v += 2) + { + itemVertices[ii].Color = color; + itemVertices[ii].Position.X = vertices[v]; + itemVertices[ii].Position.Y = vertices[v + 1]; + itemVertices[ii].Position.Z = 0; + itemVertices[ii].TextureCoordinate.X = uvs[v]; + itemVertices[ii].TextureCoordinate.Y = uvs[v + 1]; + } + } + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/XnaLoader/SkeletonRegionRenderer.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/XnaLoader/SkeletonRegionRenderer.cs index cf31b45..be4a56f 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/XnaLoader/SkeletonRegionRenderer.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/XnaLoader/SkeletonRegionRenderer.cs @@ -29,67 +29,74 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; -using System.Collections.Generic; -using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; -namespace Spine3_2_xx { - /// Draws region attachments. - public class SkeletonRegionRenderer { - GraphicsDevice device; - RegionBatcher batcher; - RasterizerState rasterizerState; - float[] vertices = new float[8]; - BlendState defaultBlendState; - - BasicEffect effect; - public BasicEffect Effect { get { return effect; } set { effect = value; } } - - private bool premultipliedAlpha; - public bool PremultipliedAlpha { get { return premultipliedAlpha; } set { premultipliedAlpha = value; } } - - public SkeletonRegionRenderer (GraphicsDevice device) { - this.device = device; - - batcher = new RegionBatcher(); - - effect = new BasicEffect(device); - effect.World = Matrix.Identity; - effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); - effect.TextureEnabled = true; - effect.VertexColorEnabled = true; - - rasterizerState = new RasterizerState(); - rasterizerState.CullMode = CullMode.None; - - Bone.yDown = true; - } - - public void Begin () { - defaultBlendState = premultipliedAlpha ? BlendState.AlphaBlend : BlendState.NonPremultiplied; - - device.RasterizerState = rasterizerState; - device.BlendState = defaultBlendState; - - effect.Projection = Matrix.CreateOrthographicOffCenter(0, device.Viewport.Width, device.Viewport.Height, 0, 1, 0); - } - - public void End () { - foreach (EffectPass pass in effect.CurrentTechnique.Passes) { - pass.Apply(); - batcher.Draw(device); - } - } - - public void Draw (Skeleton skeleton) { - var drawOrder = skeleton.DrawOrder; - var drawOrderItems = skeleton.DrawOrder.Items; - float skeletonR = skeleton.R, skeletonG = skeleton.G, skeletonB = skeleton.B, skeletonA = skeleton.A; - for (int i = 0, n = drawOrder.Count; i < n; i++) { - Slot slot = drawOrderItems[i]; - RegionAttachment regionAttachment = slot.Attachment as RegionAttachment; - if (regionAttachment != null) { +namespace Spine3_2_xx +{ + /// Draws region attachments. + public class SkeletonRegionRenderer + { + GraphicsDevice device; + RegionBatcher batcher; + RasterizerState rasterizerState; + float[] vertices = new float[8]; + BlendState defaultBlendState; + + BasicEffect effect; + public BasicEffect Effect { get { return effect; } set { effect = value; } } + + private bool premultipliedAlpha; + public bool PremultipliedAlpha { get { return premultipliedAlpha; } set { premultipliedAlpha = value; } } + + public SkeletonRegionRenderer(GraphicsDevice device) + { + this.device = device; + + batcher = new RegionBatcher(); + + effect = new BasicEffect(device); + effect.World = Matrix.Identity; + effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); + effect.TextureEnabled = true; + effect.VertexColorEnabled = true; + + rasterizerState = new RasterizerState(); + rasterizerState.CullMode = CullMode.None; + + Bone.yDown = true; + } + + public void Begin() + { + defaultBlendState = premultipliedAlpha ? BlendState.AlphaBlend : BlendState.NonPremultiplied; + + device.RasterizerState = rasterizerState; + device.BlendState = defaultBlendState; + + effect.Projection = Matrix.CreateOrthographicOffCenter(0, device.Viewport.Width, device.Viewport.Height, 0, 1, 0); + } + + public void End() + { + foreach (EffectPass pass in effect.CurrentTechnique.Passes) + { + pass.Apply(); + batcher.Draw(device); + } + } + + public void Draw(Skeleton skeleton) + { + var drawOrder = skeleton.DrawOrder; + var drawOrderItems = skeleton.DrawOrder.Items; + float skeletonR = skeleton.R, skeletonG = skeleton.G, skeletonB = skeleton.B, skeletonA = skeleton.A; + for (int i = 0, n = drawOrder.Count; i < n; i++) + { + Slot slot = drawOrderItems[i]; + RegionAttachment regionAttachment = slot.Attachment as RegionAttachment; + if (regionAttachment != null) + { BlendState blendState = new BlendState(); Blend blendSrc; Blend blendDst; @@ -148,46 +155,46 @@ public void Draw (Skeleton skeleton) { RegionItem item = batcher.NextItem(); - AtlasRegion region = (AtlasRegion)regionAttachment.RendererObject; - item.texture = (Texture2D)region.page.rendererObject; - - Color color; - float a = skeletonA * slot.A; - if (premultipliedAlpha) - color = new Color(skeletonR * slot.R * a, skeletonG * slot.G * a, skeletonB * slot.B * a, a); - else - color = new Color(skeletonR * slot.R, skeletonG * slot.G, skeletonB * slot.B, a); - item.vertexTL.Color = color; - item.vertexBL.Color = color; - item.vertexBR.Color = color; - item.vertexTR.Color = color; - - float[] vertices = this.vertices; - regionAttachment.ComputeWorldVertices(slot.Bone, vertices); - item.vertexTL.Position.X = vertices[RegionAttachment.X1]; - item.vertexTL.Position.Y = vertices[RegionAttachment.Y1]; - item.vertexTL.Position.Z = 0; - item.vertexBL.Position.X = vertices[RegionAttachment.X2]; - item.vertexBL.Position.Y = vertices[RegionAttachment.Y2]; - item.vertexBL.Position.Z = 0; - item.vertexBR.Position.X = vertices[RegionAttachment.X3]; - item.vertexBR.Position.Y = vertices[RegionAttachment.Y3]; - item.vertexBR.Position.Z = 0; - item.vertexTR.Position.X = vertices[RegionAttachment.X4]; - item.vertexTR.Position.Y = vertices[RegionAttachment.Y4]; - item.vertexTR.Position.Z = 0; - - float[] uvs = regionAttachment.UVs; - item.vertexTL.TextureCoordinate.X = uvs[RegionAttachment.X1]; - item.vertexTL.TextureCoordinate.Y = uvs[RegionAttachment.Y1]; - item.vertexBL.TextureCoordinate.X = uvs[RegionAttachment.X2]; - item.vertexBL.TextureCoordinate.Y = uvs[RegionAttachment.Y2]; - item.vertexBR.TextureCoordinate.X = uvs[RegionAttachment.X3]; - item.vertexBR.TextureCoordinate.Y = uvs[RegionAttachment.Y3]; - item.vertexTR.TextureCoordinate.X = uvs[RegionAttachment.X4]; - item.vertexTR.TextureCoordinate.Y = uvs[RegionAttachment.Y4]; - } - } - } - } + AtlasRegion region = (AtlasRegion)regionAttachment.RendererObject; + item.texture = (Texture2D)region.page.rendererObject; + + Color color; + float a = skeletonA * slot.A; + if (premultipliedAlpha) + color = new Color(skeletonR * slot.R * a, skeletonG * slot.G * a, skeletonB * slot.B * a, a); + else + color = new Color(skeletonR * slot.R, skeletonG * slot.G, skeletonB * slot.B, a); + item.vertexTL.Color = color; + item.vertexBL.Color = color; + item.vertexBR.Color = color; + item.vertexTR.Color = color; + + float[] vertices = this.vertices; + regionAttachment.ComputeWorldVertices(slot.Bone, vertices); + item.vertexTL.Position.X = vertices[RegionAttachment.X1]; + item.vertexTL.Position.Y = vertices[RegionAttachment.Y1]; + item.vertexTL.Position.Z = 0; + item.vertexBL.Position.X = vertices[RegionAttachment.X2]; + item.vertexBL.Position.Y = vertices[RegionAttachment.Y2]; + item.vertexBL.Position.Z = 0; + item.vertexBR.Position.X = vertices[RegionAttachment.X3]; + item.vertexBR.Position.Y = vertices[RegionAttachment.Y3]; + item.vertexBR.Position.Z = 0; + item.vertexTR.Position.X = vertices[RegionAttachment.X4]; + item.vertexTR.Position.Y = vertices[RegionAttachment.Y4]; + item.vertexTR.Position.Z = 0; + + float[] uvs = regionAttachment.UVs; + item.vertexTL.TextureCoordinate.X = uvs[RegionAttachment.X1]; + item.vertexTL.TextureCoordinate.Y = uvs[RegionAttachment.Y1]; + item.vertexBL.TextureCoordinate.X = uvs[RegionAttachment.X2]; + item.vertexBL.TextureCoordinate.Y = uvs[RegionAttachment.Y2]; + item.vertexBR.TextureCoordinate.X = uvs[RegionAttachment.X3]; + item.vertexBR.TextureCoordinate.Y = uvs[RegionAttachment.Y3]; + item.vertexTR.TextureCoordinate.X = uvs[RegionAttachment.X4]; + item.vertexTR.TextureCoordinate.Y = uvs[RegionAttachment.Y4]; + } + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/XnaLoader/XnaTextureLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/XnaLoader/XnaTextureLoader.cs index 00c28f2..7ee62e7 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/XnaLoader/XnaTextureLoader.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.2.xx/XnaLoader/XnaTextureLoader.cs @@ -30,21 +30,23 @@ *****************************************************************************/ using System; -using System.IO; -using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; -namespace Spine3_2_xx { - public class XnaTextureLoader : TextureLoader { - GraphicsDevice device; +namespace Spine3_2_xx +{ + public class XnaTextureLoader : TextureLoader + { + GraphicsDevice device; - public XnaTextureLoader (GraphicsDevice device) { - this.device = device; - } + public XnaTextureLoader(GraphicsDevice device) + { + this.device = device; + } - public void Load (AtlasPage page, String path) { - Texture2D texture = Util.LoadTexture(device, path); - page.rendererObject = texture; + public void Load(AtlasPage page, String path) + { + Texture2D texture = Util.LoadTexture(device, path); + page.rendererObject = texture; if (page.width == 0 || page.height == 0) { page.width = texture.Width; @@ -52,8 +54,9 @@ public void Load (AtlasPage page, String path) { } } - public void Unload (Object texture) { - ((Texture2D)texture).Dispose(); - } - } + public void Unload(Object texture) + { + ((Texture2D)texture).Dispose(); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Animation.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Animation.cs index 63dd5e4..7978b71 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Animation.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Animation.cs @@ -29,846 +29,955 @@ *****************************************************************************/ using System; -using System.Collections.Generic; - -namespace Spine3_4_02 { - public class Animation { - internal ExposedList timelines; - internal float duration; - internal String name; - - public String Name { get { return name; } } - public ExposedList Timelines { get { return timelines; } set { timelines = value; } } - public float Duration { get { return duration; } set { duration = value; } } - - public Animation (String name, ExposedList timelines, float duration) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - if (timelines == null) throw new ArgumentNullException("timelines", "timelines cannot be null."); - this.name = name; - this.timelines = timelines; - this.duration = duration; - } - - /// Poses the skeleton at the specified time for this animation. - /// The last time the animation was applied. - /// Any triggered events are added. May be null. - public void Apply (Skeleton skeleton, float lastTime, float time, bool loop, ExposedList events) { - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - - if (loop && duration != 0) { - time %= duration; - if (lastTime > 0) lastTime %= duration; - } - - ExposedList timelines = this.timelines; - for (int i = 0, n = timelines.Count; i < n; i++) - timelines.Items[i].Apply(skeleton, lastTime, time, events, 1); - } - - /// Poses the skeleton at the specified time for this animation mixed with the current pose. - /// The last time the animation was applied. - /// Any triggered events are added. May be null. - /// The amount of this animation that affects the current pose. - public void Mix (Skeleton skeleton, float lastTime, float time, bool loop, ExposedList events, float alpha) { - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - - if (loop && duration != 0) { - time %= duration; - if (lastTime > 0) lastTime %= duration; - } - - ExposedList timelines = this.timelines; - for (int i = 0, n = timelines.Count; i < n; i++) - timelines.Items[i].Apply(skeleton, lastTime, time, events, alpha); - } - - /// After the first and before the last entry. - internal static int binarySearch (float[] values, float target, int step) { - int low = 0; - int high = values.Length / step - 2; - if (high == 0) return step; - int current = (int)((uint)high >> 1); - while (true) { - if (values[(current + 1) * step] <= target) - low = current + 1; - else - high = current; - if (low == high) return (low + 1) * step; - current = (int)((uint)(low + high) >> 1); - } - } - - /// After the first and before the last entry. - internal static int binarySearch (float[] values, float target) { - int low = 0; - int high = values.Length - 2; - if (high == 0) return 1; - int current = (int)((uint)high >> 1); - while (true) { - if (values[(current + 1)] <= target) - low = current + 1; - else - high = current; - if (low == high) return (low + 1); - current = (int)((uint)(low + high) >> 1); - } - } - - internal static int linearSearch (float[] values, float target, int step) { - for (int i = 0, last = values.Length - step; i <= last; i += step) - if (values[i] > target) return i; - return -1; - } - } - - public interface Timeline { - /// Sets the value(s) for the specified time. - /// May be null to not collect fired events. - void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha); - } - - /// Base class for frames that use an interpolation bezier curve. - abstract public class CurveTimeline : Timeline { - protected const float LINEAR = 0, STEPPED = 1, BEZIER = 2; - protected const int BEZIER_SIZE = 10 * 2 - 1; - - private float[] curves; // type, x, y, ... - public int FrameCount { get { return curves.Length / BEZIER_SIZE + 1; } } - - public CurveTimeline (int frameCount) { - if (frameCount <= 0) throw new ArgumentException("frameCount must be > 0: " + frameCount, "frameCount"); - curves = new float[(frameCount - 1) * BEZIER_SIZE]; - } - - abstract public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha); - - public void SetLinear (int frameIndex) { - curves[frameIndex * BEZIER_SIZE] = LINEAR; - } - - public void SetStepped (int frameIndex) { - curves[frameIndex * BEZIER_SIZE] = STEPPED; - } - - /// Sets the control handle positions for an interpolation bezier curve used to transition from this keyframe to the next. - /// cx1 and cx2 are from 0 to 1, representing the percent of time between the two keyframes. cy1 and cy2 are the percent of - /// the difference between the keyframe's values. - public void SetCurve (int frameIndex, float cx1, float cy1, float cx2, float cy2) { - float tmpx = (-cx1 * 2 + cx2) * 0.03f, tmpy = (-cy1 * 2 + cy2) * 0.03f; - float dddfx = ((cx1 - cx2) * 3 + 1) * 0.006f, dddfy = ((cy1 - cy2) * 3 + 1) * 0.006f; - float ddfx = tmpx * 2 + dddfx, ddfy = tmpy * 2 + dddfy; - float dfx = cx1 * 0.3f + tmpx + dddfx * 0.16666667f, dfy = cy1 * 0.3f + tmpy + dddfy * 0.16666667f; - - int i = frameIndex * BEZIER_SIZE; - float[] curves = this.curves; - curves[i++] = BEZIER; - - float x = dfx, y = dfy; - for (int n = i + BEZIER_SIZE - 1; i < n; i += 2) { - curves[i] = x; - curves[i + 1] = y; - dfx += ddfx; - dfy += ddfy; - ddfx += dddfx; - ddfy += dddfy; - x += dfx; - y += dfy; - } - } - - public float GetCurvePercent (int frameIndex, float percent) { - percent = MathUtils.Clamp (percent, 0, 1); - float[] curves = this.curves; - int i = frameIndex * BEZIER_SIZE; - float type = curves[i]; - if (type == LINEAR) return percent; - if (type == STEPPED) return 0; - i++; - float x = 0; - for (int start = i, n = i + BEZIER_SIZE - 1; i < n; i += 2) { - x = curves[i]; - if (x >= percent) { - float prevX, prevY; - if (i == start) { - prevX = 0; - prevY = 0; - } else { - prevX = curves[i - 2]; - prevY = curves[i - 1]; - } - return prevY + (curves[i + 1] - prevY) * (percent - prevX) / (x - prevX); - } - } - float y = curves[i - 1]; - return y + (1 - y) * (percent - x) / (1 - x); // Last point is 1,1. - } - public float GetCurveType (int frameIndex) { - return curves[frameIndex * BEZIER_SIZE]; - } - } - - public class RotateTimeline : CurveTimeline { - public const int ENTRIES = 2; - internal const int PREV_TIME = -2, PREV_ROTATION = -1; - internal const int ROTATION = 1; - - internal int boneIndex; - internal float[] frames; - - public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, angle, ... - - public RotateTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount << 1]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, float degrees) { - frameIndex <<= 1; - frames[frameIndex] = time; - frames[frameIndex + ROTATION] = degrees; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) { - float[] frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - Bone bone = skeleton.bones.Items[boneIndex]; - - float amount; - - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - amount = bone.data.rotation + frames[frames.Length + PREV_ROTATION] - bone.rotation; - while (amount > 180) - amount -= 360; - while (amount < -180) - amount += 360; - bone.rotation += amount * alpha; - return; - } - - // Interpolate between the previous frame and the current frame. - int frame = Animation.binarySearch(frames, time, ENTRIES); - float prevRotation = frames[frame + PREV_ROTATION]; - float frameTime = frames[frame]; - float percent = GetCurvePercent((frame >> 1) - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - amount = frames[frame + ROTATION] - prevRotation; - while (amount > 180) - amount -= 360; - while (amount < -180) - amount += 360; - amount = bone.data.rotation + (prevRotation + amount * percent) - bone.rotation; - while (amount > 180) - amount -= 360; - while (amount < -180) - amount += 360; - bone.rotation += amount * alpha; - } - } - - public class TranslateTimeline : CurveTimeline { - public const int ENTRIES = 3; - protected const int PREV_TIME = -3, PREV_X = -2, PREV_Y = -1; - protected const int X = 1, Y = 2; - - internal int boneIndex; - internal float[] frames; - - public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, value, value, ... - - public TranslateTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, float x, float y) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + X] = x; - frames[frameIndex + Y] = y; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) { - float[] frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - Bone bone = skeleton.bones.Items[boneIndex]; - - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - bone.x += (bone.data.x + frames[frames.Length + PREV_X] - bone.x) * alpha; - bone.y += (bone.data.y + frames[frames.Length + PREV_Y] - bone.y) * alpha; - return; - } - - // Interpolate between the previous frame and the current frame. - int frame = Animation.binarySearch(frames, time, ENTRIES); - float prevX = frames[frame + PREV_X]; - float prevY = frames[frame + PREV_Y]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - bone.x += (bone.data.x + prevX + (frames[frame + X] - prevX) * percent - bone.x) * alpha; - bone.y += (bone.data.y + prevY + (frames[frame + Y] - prevY) * percent - bone.y) * alpha; - } - } - - public class ScaleTimeline : TranslateTimeline { - public ScaleTimeline (int frameCount) - : base(frameCount) { - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) { - float[] frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - Bone bone = skeleton.bones.Items[boneIndex]; - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - bone.scaleX += (bone.data.scaleX * frames[frames.Length + PREV_X] - bone.scaleX) * alpha; - bone.scaleY += (bone.data.scaleY * frames[frames.Length + PREV_Y] - bone.scaleY) * alpha; - return; - } - - // Interpolate between the previous frame and the current frame. - int frame = Animation.binarySearch(frames, time, ENTRIES); - float prevX = frames[frame + PREV_X]; - float prevY = frames[frame + PREV_Y]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - bone.scaleX += (bone.data.scaleX * (prevX + (frames[frame + X] - prevX) * percent) - bone.scaleX) * alpha; - bone.scaleY += (bone.data.scaleY * (prevY + (frames[frame + Y] - prevY) * percent) - bone.scaleY) * alpha; - } - } - - public class ShearTimeline : TranslateTimeline { - public ShearTimeline (int frameCount) - : base(frameCount) { - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) { - float[] frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - Bone bone = skeleton.bones.Items[boneIndex]; - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - bone.shearX += (bone.data.shearX + frames[frames.Length + PREV_X] - bone.shearX) * alpha; - bone.shearY += (bone.data.shearY + frames[frames.Length + PREV_Y] - bone.shearY) * alpha; - return; - } - - // Interpolate between the previous frame and the current frame. - int frame = Animation.binarySearch(frames, time, ENTRIES); - float prevX = frames[frame + PREV_X]; - float prevY = frames[frame + PREV_Y]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - bone.shearX += (bone.data.shearX + (prevX + (frames[frame + X] - prevX) * percent) - bone.shearX) * alpha; - bone.shearY += (bone.data.shearY + (prevY + (frames[frame + Y] - prevY) * percent) - bone.shearY) * alpha; - } - } - - public class ColorTimeline : CurveTimeline { - public const int ENTRIES = 5; - protected const int PREV_TIME = -5, PREV_R = -4, PREV_G = -3, PREV_B = -2, PREV_A = -1; - protected const int R = 1, G = 2, B = 3, A = 4; - - internal int slotIndex; - internal float[] frames; - - public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, r, g, b, a, ... - - public ColorTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, float r, float g, float b, float a) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + R] = r; - frames[frameIndex + G] = g; - frames[frameIndex + B] = b; - frames[frameIndex + A] = a; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) { - float[] frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - float r, g, b, a; - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - int i = frames.Length; - r = frames[i + PREV_R]; - g = frames[i + PREV_G]; - b = frames[i + PREV_B]; - a = frames[i + PREV_A]; - } else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.binarySearch(frames, time, ENTRIES); - r = frames[frame + PREV_R]; - g = frames[frame + PREV_G]; - b = frames[frame + PREV_B]; - a = frames[frame + PREV_A]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - r += (frames[frame + R] - r) * percent; - g += (frames[frame + G] - g) * percent; - b += (frames[frame + B] - b) * percent; - a += (frames[frame + A] - a) * percent; - } - Slot slot = skeleton.slots.Items[slotIndex]; - if (alpha < 1) { - slot.r += (r - slot.r) * alpha; - slot.g += (g - slot.g) * alpha; - slot.b += (b - slot.b) * alpha; - slot.a += (a - slot.a) * alpha; - } else { - slot.r = r; - slot.g = g; - slot.b = b; - slot.a = a; - } - } - } - - public class AttachmentTimeline : Timeline { - internal int slotIndex; - internal float[] frames; - private String[] attachmentNames; - - public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, ... - public String[] AttachmentNames { get { return attachmentNames; } set { attachmentNames = value; } } - public int FrameCount { get { return frames.Length; } } - - public AttachmentTimeline (int frameCount) { - frames = new float[frameCount]; - attachmentNames = new String[frameCount]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, String attachmentName) { - frames[frameIndex] = time; - attachmentNames[frameIndex] = attachmentName; - } - - public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) { - float[] frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - int frameIndex; - if (time >= frames[frames.Length - 1]) // Time is after last frame. - frameIndex = frames.Length - 1; - else - frameIndex = Animation.binarySearch(frames, time, 1) - 1; - - String attachmentName = attachmentNames[frameIndex]; - skeleton.slots.Items[slotIndex] - .Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); - } - } - - public class EventTimeline : Timeline { - internal float[] frames; - private Event[] events; - - public float[] Frames { get { return frames; } set { frames = value; } } // time, ... - public Event[] Events { get { return events; } set { events = value; } } - public int FrameCount { get { return frames.Length; } } - - public EventTimeline (int frameCount) { - frames = new float[frameCount]; - events = new Event[frameCount]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, Event e) { - frames[frameIndex] = e.Time; - events[frameIndex] = e; - } - - /// Fires events for frames > lastTime and <= time. - public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) { - if (firedEvents == null) return; - float[] frames = this.frames; - int frameCount = frames.Length; - - if (lastTime > time) { // Fire events after last time for looped animations. - Apply(skeleton, lastTime, int.MaxValue, firedEvents, alpha); - lastTime = -1f; - } else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame. - return; - if (time < frames[0]) return; // Time is before first frame. - - int frame; - if (lastTime < frames[0]) - frame = 0; - else { - frame = Animation.binarySearch(frames, lastTime); - float frameTime = frames[frame]; - while (frame > 0) { // Fire multiple events with the same frame. - if (frames[frame - 1] != frameTime) break; - frame--; - } - } - for (; frame < frameCount && time >= frames[frame]; frame++) - firedEvents.Add(events[frame]); - } - } - - public class DrawOrderTimeline : Timeline { - internal float[] frames; - private int[][] drawOrders; - - public float[] Frames { get { return frames; } set { frames = value; } } // time, ... - public int[][] DrawOrders { get { return drawOrders; } set { drawOrders = value; } } - public int FrameCount { get { return frames.Length; } } - - public DrawOrderTimeline (int frameCount) { - frames = new float[frameCount]; - drawOrders = new int[frameCount][]; - } - - /// Sets the time and value of the specified keyframe. - /// May be null to use bind pose draw order. - public void SetFrame (int frameIndex, float time, int[] drawOrder) { - frames[frameIndex] = time; - drawOrders[frameIndex] = drawOrder; - } - - public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) { - float[] frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - int frame; - if (time >= frames[frames.Length - 1]) // Time is after last frame. - frame = frames.Length - 1; - else - frame = Animation.binarySearch(frames, time) - 1; - - ExposedList drawOrder = skeleton.drawOrder; - ExposedList slots = skeleton.slots; - int[] drawOrderToSetupIndex = drawOrders[frame]; - if (drawOrderToSetupIndex == null) { - drawOrder.Clear(); - for (int i = 0, n = slots.Count; i < n; i++) - drawOrder.Add(slots.Items[i]); - } else { - var drawOrderItems = drawOrder.Items; - var slotsItems = slots.Items; - for (int i = 0, n = drawOrderToSetupIndex.Length; i < n; i++) - drawOrderItems[i] = slotsItems[drawOrderToSetupIndex[i]]; - } - } - } - - public class DeformTimeline : CurveTimeline { - internal int slotIndex; - internal float[] frames; - private float[][] frameVertices; - internal VertexAttachment attachment; - - public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, ... - public float[][] Vertices { get { return frameVertices; } set { frameVertices = value; } } - public VertexAttachment Attachment { get { return attachment; } set { attachment = value; } } - - public DeformTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount]; - frameVertices = new float[frameCount][]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, float[] vertices) { - frames[frameIndex] = time; - frameVertices[frameIndex] = vertices; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) { - Slot slot = skeleton.slots.Items[slotIndex]; - VertexAttachment slotAttachment = slot.attachment as VertexAttachment; - if (slotAttachment == null || !slotAttachment.ApplyDeform(attachment)) return; - - float[] frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - float[][] frameVertices = this.frameVertices; - int vertexCount = frameVertices[0].Length; - - var verticesArray = slot.attachmentVertices; - if (verticesArray.Count != vertexCount) alpha = 1; // Don't mix from uninitialized slot vertices. - // verticesArray.SetSize(vertexCount) // Ensure size and preemptively set count. - if (verticesArray.Capacity < vertexCount) verticesArray.Capacity = vertexCount; - verticesArray.Count = vertexCount; - float[] vertices = verticesArray.Items; - - if (time >= frames[frames.Length - 1]) { // Time is after last frame. - float[] lastVertices = frameVertices[frames.Length - 1]; - if (alpha < 1) { - for (int i = 0; i < vertexCount; i++) { - float vertex = vertices[i]; - vertices[i] = vertex + (lastVertices[i] - vertex) * alpha; - } - } else - Array.Copy(lastVertices, 0, vertices, 0, vertexCount); - return; - } - - // Interpolate between the previous frame and the current frame. - int frame = Animation.binarySearch(frames, time); - float[] prevVertices = frameVertices[frame - 1]; - float[] nextVertices = frameVertices[frame]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame - 1, 1 - (time - frameTime) / (frames[frame - 1] - frameTime)); - - if (alpha < 1) { - for (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - float vertex = vertices[i]; - vertices[i] = vertex + (prev + (nextVertices[i] - prev) * percent - vertex) * alpha; - } - } else { - for (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - vertices[i] = prev + (nextVertices[i] - prev) * percent; - } - } - } - } - - public class IkConstraintTimeline : CurveTimeline { - public const int ENTRIES = 3; - private const int PREV_TIME = -3, PREV_MIX = -2, PREV_BEND_DIRECTION = -1; - private const int MIX = 1, BEND_DIRECTION = 2; - - internal int ikConstraintIndex; - internal float[] frames; - - public int IkConstraintIndex { get { return ikConstraintIndex; } set { ikConstraintIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, mix, bendDirection, ... - - public IkConstraintTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - /// Sets the time, mix and bend direction of the specified keyframe. - public void SetFrame (int frameIndex, float time, float mix, int bendDirection) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + MIX] = mix; - frames[frameIndex + BEND_DIRECTION] = bendDirection; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) { - float[] frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - IkConstraint constraint = skeleton.ikConstraints.Items[ikConstraintIndex]; - - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - constraint.mix += (frames[frames.Length + PREV_MIX] - constraint.mix) * alpha; - constraint.bendDirection = (int)frames[frames.Length + PREV_BEND_DIRECTION]; - return; - } - - // Interpolate between the previous frame and the current frame. - int frame = Animation.binarySearch(frames, time, ENTRIES); - float mix = frames[frame + PREV_MIX]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - constraint.mix += (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha; - constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION]; - } - } - - public class TransformConstraintTimeline : CurveTimeline { - public const int ENTRIES = 5; - private const int PREV_TIME = -5, PREV_ROTATE = -4, PREV_TRANSLATE = -3, PREV_SCALE = -2, PREV_SHEAR = -1; - private const int ROTATE = 1, TRANSLATE = 2, SCALE = 3, SHEAR = 4; - - internal int transformConstraintIndex; - internal float[] frames; - - public int TransformConstraintIndex { get { return transformConstraintIndex; } set { transformConstraintIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, scale mix, shear mix, ... - - public TransformConstraintTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - public void SetFrame (int frameIndex, float time, float rotateMix, float translateMix, float scaleMix, float shearMix) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + ROTATE] = rotateMix; - frames[frameIndex + TRANSLATE] = translateMix; - frames[frameIndex + SCALE] = scaleMix; - frames[frameIndex + SHEAR] = shearMix; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) { - float[] frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - TransformConstraint constraint = skeleton.transformConstraints.Items[transformConstraintIndex]; - - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - int i = frames.Length; - constraint.rotateMix += (frames[i + PREV_ROTATE] - constraint.rotateMix) * alpha; - constraint.translateMix += (frames[i + PREV_TRANSLATE] - constraint.translateMix) * alpha; - constraint.scaleMix += (frames[i + PREV_SCALE] - constraint.scaleMix) * alpha; - constraint.shearMix += (frames[i + PREV_SHEAR] - constraint.shearMix) * alpha; - return; - } - - // Interpolate between the previous frame and the current frame. - int frame = Animation.binarySearch(frames, time, ENTRIES); - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - float rotate = frames[frame + PREV_ROTATE]; - float translate = frames[frame + PREV_TRANSLATE]; - float scale = frames[frame + PREV_SCALE]; - float shear = frames[frame + PREV_SHEAR]; - constraint.rotateMix += (rotate + (frames[frame + ROTATE] - rotate) * percent - constraint.rotateMix) * alpha; - constraint.translateMix += (translate + (frames[frame + TRANSLATE] - translate) * percent - constraint.translateMix) - * alpha; - constraint.scaleMix += (scale + (frames[frame + SCALE] - scale) * percent - constraint.scaleMix) * alpha; - constraint.shearMix += (shear + (frames[frame + SHEAR] - shear) * percent - constraint.shearMix) * alpha; - } - } - - public class PathConstraintPositionTimeline : CurveTimeline { - public const int ENTRIES = 2; - protected const int PREV_TIME = -2, PREV_VALUE = -1; - protected const int VALUE = 1; - - internal int pathConstraintIndex; - internal float[] frames; - - public PathConstraintPositionTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - public int PathConstraintIndex { get { return pathConstraintIndex; } set { pathConstraintIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, position, ... - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, float value) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + VALUE] = value; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha) { - float[] frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; - - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - int i = frames.Length; - constraint.position += (frames[i + PREV_VALUE] - constraint.position) * alpha; - return; - } - - // Interpolate between the previous frame and the current frame. - int frame = Animation.binarySearch(frames, time, ENTRIES); - float position = frames[frame + PREV_VALUE]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - constraint.position += (position + (frames[frame + VALUE] - position) * percent - constraint.position) * alpha; - } - } - - public class PathConstraintSpacingTimeline : PathConstraintPositionTimeline { - public PathConstraintSpacingTimeline (int frameCount) - : base(frameCount) { - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha) { - float[] frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; - - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - int i = frames.Length; - constraint.spacing += (frames[i + PREV_VALUE] - constraint.spacing) * alpha; - return; - } - - // Interpolate between the previous frame and the current frame. - int frame = Animation.binarySearch(frames, time, ENTRIES); - float spacing = frames[frame + PREV_VALUE]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - constraint.spacing += (spacing + (frames[frame + VALUE] - spacing) * percent - constraint.spacing) * alpha; - } - } - - public class PathConstraintMixTimeline : CurveTimeline { - public const int ENTRIES = 3; - private const int PREV_TIME = -3, PREV_ROTATE = -2, PREV_TRANSLATE = -1; - private const int ROTATE = 1, TRANSLATE = 2; - - internal int pathConstraintIndex; - internal float[] frames; - - public int PathConstraintIndex { get { return pathConstraintIndex; } set { pathConstraintIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, ... - - public PathConstraintMixTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - /** Sets the time and mixes of the specified keyframe. */ - public void SetFrame (int frameIndex, float time, float rotateMix, float translateMix) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + ROTATE] = rotateMix; - frames[frameIndex + TRANSLATE] = translateMix; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha) { - float[] frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; - - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - int i = frames.Length; - constraint.rotateMix += (frames[i + PREV_ROTATE] - constraint.rotateMix) * alpha; - constraint.translateMix += (frames[i + PREV_TRANSLATE] - constraint.translateMix) * alpha; - return; - } - - // Interpolate between the previous frame and the current frame. - int frame = Animation.binarySearch(frames, time, ENTRIES); - float rotate = frames[frame + PREV_ROTATE]; - float translate = frames[frame + PREV_TRANSLATE]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - constraint.rotateMix += (rotate + (frames[frame + ROTATE] - rotate) * percent - constraint.rotateMix) * alpha; - constraint.translateMix += (translate + (frames[frame + TRANSLATE] - translate) * percent - constraint.translateMix) - * alpha; - } - } + +namespace Spine3_4_02 +{ + public class Animation + { + internal ExposedList timelines; + internal float duration; + internal String name; + + public String Name { get { return name; } } + public ExposedList Timelines { get { return timelines; } set { timelines = value; } } + public float Duration { get { return duration; } set { duration = value; } } + + public Animation(String name, ExposedList timelines, float duration) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + if (timelines == null) throw new ArgumentNullException("timelines", "timelines cannot be null."); + this.name = name; + this.timelines = timelines; + this.duration = duration; + } + + /// Poses the skeleton at the specified time for this animation. + /// The last time the animation was applied. + /// Any triggered events are added. May be null. + public void Apply(Skeleton skeleton, float lastTime, float time, bool loop, ExposedList events) + { + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + + if (loop && duration != 0) + { + time %= duration; + if (lastTime > 0) lastTime %= duration; + } + + ExposedList timelines = this.timelines; + for (int i = 0, n = timelines.Count; i < n; i++) + timelines.Items[i].Apply(skeleton, lastTime, time, events, 1); + } + + /// Poses the skeleton at the specified time for this animation mixed with the current pose. + /// The last time the animation was applied. + /// Any triggered events are added. May be null. + /// The amount of this animation that affects the current pose. + public void Mix(Skeleton skeleton, float lastTime, float time, bool loop, ExposedList events, float alpha) + { + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + + if (loop && duration != 0) + { + time %= duration; + if (lastTime > 0) lastTime %= duration; + } + + ExposedList timelines = this.timelines; + for (int i = 0, n = timelines.Count; i < n; i++) + timelines.Items[i].Apply(skeleton, lastTime, time, events, alpha); + } + + /// After the first and before the last entry. + internal static int binarySearch(float[] values, float target, int step) + { + int low = 0; + int high = values.Length / step - 2; + if (high == 0) return step; + int current = (int)((uint)high >> 1); + while (true) + { + if (values[(current + 1) * step] <= target) + low = current + 1; + else + high = current; + if (low == high) return (low + 1) * step; + current = (int)((uint)(low + high) >> 1); + } + } + + /// After the first and before the last entry. + internal static int binarySearch(float[] values, float target) + { + int low = 0; + int high = values.Length - 2; + if (high == 0) return 1; + int current = (int)((uint)high >> 1); + while (true) + { + if (values[(current + 1)] <= target) + low = current + 1; + else + high = current; + if (low == high) return (low + 1); + current = (int)((uint)(low + high) >> 1); + } + } + + internal static int linearSearch(float[] values, float target, int step) + { + for (int i = 0, last = values.Length - step; i <= last; i += step) + if (values[i] > target) return i; + return -1; + } + } + + public interface Timeline + { + /// Sets the value(s) for the specified time. + /// May be null to not collect fired events. + void Apply(Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha); + } + + /// Base class for frames that use an interpolation bezier curve. + abstract public class CurveTimeline : Timeline + { + protected const float LINEAR = 0, STEPPED = 1, BEZIER = 2; + protected const int BEZIER_SIZE = 10 * 2 - 1; + + private float[] curves; // type, x, y, ... + public int FrameCount { get { return curves.Length / BEZIER_SIZE + 1; } } + + public CurveTimeline(int frameCount) + { + if (frameCount <= 0) throw new ArgumentException("frameCount must be > 0: " + frameCount, "frameCount"); + curves = new float[(frameCount - 1) * BEZIER_SIZE]; + } + + abstract public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha); + + public void SetLinear(int frameIndex) + { + curves[frameIndex * BEZIER_SIZE] = LINEAR; + } + + public void SetStepped(int frameIndex) + { + curves[frameIndex * BEZIER_SIZE] = STEPPED; + } + + /// Sets the control handle positions for an interpolation bezier curve used to transition from this keyframe to the next. + /// cx1 and cx2 are from 0 to 1, representing the percent of time between the two keyframes. cy1 and cy2 are the percent of + /// the difference between the keyframe's values. + public void SetCurve(int frameIndex, float cx1, float cy1, float cx2, float cy2) + { + float tmpx = (-cx1 * 2 + cx2) * 0.03f, tmpy = (-cy1 * 2 + cy2) * 0.03f; + float dddfx = ((cx1 - cx2) * 3 + 1) * 0.006f, dddfy = ((cy1 - cy2) * 3 + 1) * 0.006f; + float ddfx = tmpx * 2 + dddfx, ddfy = tmpy * 2 + dddfy; + float dfx = cx1 * 0.3f + tmpx + dddfx * 0.16666667f, dfy = cy1 * 0.3f + tmpy + dddfy * 0.16666667f; + + int i = frameIndex * BEZIER_SIZE; + float[] curves = this.curves; + curves[i++] = BEZIER; + + float x = dfx, y = dfy; + for (int n = i + BEZIER_SIZE - 1; i < n; i += 2) + { + curves[i] = x; + curves[i + 1] = y; + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + x += dfx; + y += dfy; + } + } + + public float GetCurvePercent(int frameIndex, float percent) + { + percent = MathUtils.Clamp(percent, 0, 1); + float[] curves = this.curves; + int i = frameIndex * BEZIER_SIZE; + float type = curves[i]; + if (type == LINEAR) return percent; + if (type == STEPPED) return 0; + i++; + float x = 0; + for (int start = i, n = i + BEZIER_SIZE - 1; i < n; i += 2) + { + x = curves[i]; + if (x >= percent) + { + float prevX, prevY; + if (i == start) + { + prevX = 0; + prevY = 0; + } + else + { + prevX = curves[i - 2]; + prevY = curves[i - 1]; + } + return prevY + (curves[i + 1] - prevY) * (percent - prevX) / (x - prevX); + } + } + float y = curves[i - 1]; + return y + (1 - y) * (percent - x) / (1 - x); // Last point is 1,1. + } + public float GetCurveType(int frameIndex) + { + return curves[frameIndex * BEZIER_SIZE]; + } + } + + public class RotateTimeline : CurveTimeline + { + public const int ENTRIES = 2; + internal const int PREV_TIME = -2, PREV_ROTATION = -1; + internal const int ROTATION = 1; + + internal int boneIndex; + internal float[] frames; + + public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, angle, ... + + public RotateTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount << 1]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, float degrees) + { + frameIndex <<= 1; + frames[frameIndex] = time; + frames[frameIndex + ROTATION] = degrees; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) + { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + Bone bone = skeleton.bones.Items[boneIndex]; + + float amount; + + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + amount = bone.data.rotation + frames[frames.Length + PREV_ROTATION] - bone.rotation; + while (amount > 180) + amount -= 360; + while (amount < -180) + amount += 360; + bone.rotation += amount * alpha; + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = Animation.binarySearch(frames, time, ENTRIES); + float prevRotation = frames[frame + PREV_ROTATION]; + float frameTime = frames[frame]; + float percent = GetCurvePercent((frame >> 1) - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + amount = frames[frame + ROTATION] - prevRotation; + while (amount > 180) + amount -= 360; + while (amount < -180) + amount += 360; + amount = bone.data.rotation + (prevRotation + amount * percent) - bone.rotation; + while (amount > 180) + amount -= 360; + while (amount < -180) + amount += 360; + bone.rotation += amount * alpha; + } + } + + public class TranslateTimeline : CurveTimeline + { + public const int ENTRIES = 3; + protected const int PREV_TIME = -3, PREV_X = -2, PREV_Y = -1; + protected const int X = 1, Y = 2; + + internal int boneIndex; + internal float[] frames; + + public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, value, value, ... + + public TranslateTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, float x, float y) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + X] = x; + frames[frameIndex + Y] = y; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) + { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + Bone bone = skeleton.bones.Items[boneIndex]; + + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + bone.x += (bone.data.x + frames[frames.Length + PREV_X] - bone.x) * alpha; + bone.y += (bone.data.y + frames[frames.Length + PREV_Y] - bone.y) * alpha; + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = Animation.binarySearch(frames, time, ENTRIES); + float prevX = frames[frame + PREV_X]; + float prevY = frames[frame + PREV_Y]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + bone.x += (bone.data.x + prevX + (frames[frame + X] - prevX) * percent - bone.x) * alpha; + bone.y += (bone.data.y + prevY + (frames[frame + Y] - prevY) * percent - bone.y) * alpha; + } + } + + public class ScaleTimeline : TranslateTimeline + { + public ScaleTimeline(int frameCount) + : base(frameCount) + { + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) + { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + Bone bone = skeleton.bones.Items[boneIndex]; + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + bone.scaleX += (bone.data.scaleX * frames[frames.Length + PREV_X] - bone.scaleX) * alpha; + bone.scaleY += (bone.data.scaleY * frames[frames.Length + PREV_Y] - bone.scaleY) * alpha; + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = Animation.binarySearch(frames, time, ENTRIES); + float prevX = frames[frame + PREV_X]; + float prevY = frames[frame + PREV_Y]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + bone.scaleX += (bone.data.scaleX * (prevX + (frames[frame + X] - prevX) * percent) - bone.scaleX) * alpha; + bone.scaleY += (bone.data.scaleY * (prevY + (frames[frame + Y] - prevY) * percent) - bone.scaleY) * alpha; + } + } + + public class ShearTimeline : TranslateTimeline + { + public ShearTimeline(int frameCount) + : base(frameCount) + { + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) + { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + Bone bone = skeleton.bones.Items[boneIndex]; + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + bone.shearX += (bone.data.shearX + frames[frames.Length + PREV_X] - bone.shearX) * alpha; + bone.shearY += (bone.data.shearY + frames[frames.Length + PREV_Y] - bone.shearY) * alpha; + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = Animation.binarySearch(frames, time, ENTRIES); + float prevX = frames[frame + PREV_X]; + float prevY = frames[frame + PREV_Y]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + bone.shearX += (bone.data.shearX + (prevX + (frames[frame + X] - prevX) * percent) - bone.shearX) * alpha; + bone.shearY += (bone.data.shearY + (prevY + (frames[frame + Y] - prevY) * percent) - bone.shearY) * alpha; + } + } + + public class ColorTimeline : CurveTimeline + { + public const int ENTRIES = 5; + protected const int PREV_TIME = -5, PREV_R = -4, PREV_G = -3, PREV_B = -2, PREV_A = -1; + protected const int R = 1, G = 2, B = 3, A = 4; + + internal int slotIndex; + internal float[] frames; + + public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, r, g, b, a, ... + + public ColorTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, float r, float g, float b, float a) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + R] = r; + frames[frameIndex + G] = g; + frames[frameIndex + B] = b; + frames[frameIndex + A] = a; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) + { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + float r, g, b, a; + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + int i = frames.Length; + r = frames[i + PREV_R]; + g = frames[i + PREV_G]; + b = frames[i + PREV_B]; + a = frames[i + PREV_A]; + } + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.binarySearch(frames, time, ENTRIES); + r = frames[frame + PREV_R]; + g = frames[frame + PREV_G]; + b = frames[frame + PREV_B]; + a = frames[frame + PREV_A]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + r += (frames[frame + R] - r) * percent; + g += (frames[frame + G] - g) * percent; + b += (frames[frame + B] - b) * percent; + a += (frames[frame + A] - a) * percent; + } + Slot slot = skeleton.slots.Items[slotIndex]; + if (alpha < 1) + { + slot.r += (r - slot.r) * alpha; + slot.g += (g - slot.g) * alpha; + slot.b += (b - slot.b) * alpha; + slot.a += (a - slot.a) * alpha; + } + else + { + slot.r = r; + slot.g = g; + slot.b = b; + slot.a = a; + } + } + } + + public class AttachmentTimeline : Timeline + { + internal int slotIndex; + internal float[] frames; + private String[] attachmentNames; + + public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, ... + public String[] AttachmentNames { get { return attachmentNames; } set { attachmentNames = value; } } + public int FrameCount { get { return frames.Length; } } + + public AttachmentTimeline(int frameCount) + { + frames = new float[frameCount]; + attachmentNames = new String[frameCount]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, String attachmentName) + { + frames[frameIndex] = time; + attachmentNames[frameIndex] = attachmentName; + } + + public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) + { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + int frameIndex; + if (time >= frames[frames.Length - 1]) // Time is after last frame. + frameIndex = frames.Length - 1; + else + frameIndex = Animation.binarySearch(frames, time, 1) - 1; + + String attachmentName = attachmentNames[frameIndex]; + skeleton.slots.Items[slotIndex] + .Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); + } + } + + public class EventTimeline : Timeline + { + internal float[] frames; + private Event[] events; + + public float[] Frames { get { return frames; } set { frames = value; } } // time, ... + public Event[] Events { get { return events; } set { events = value; } } + public int FrameCount { get { return frames.Length; } } + + public EventTimeline(int frameCount) + { + frames = new float[frameCount]; + events = new Event[frameCount]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, Event e) + { + frames[frameIndex] = e.Time; + events[frameIndex] = e; + } + + /// Fires events for frames > lastTime and <= time. + public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) + { + if (firedEvents == null) return; + float[] frames = this.frames; + int frameCount = frames.Length; + + if (lastTime > time) + { // Fire events after last time for looped animations. + Apply(skeleton, lastTime, int.MaxValue, firedEvents, alpha); + lastTime = -1f; + } + else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame. + return; + if (time < frames[0]) return; // Time is before first frame. + + int frame; + if (lastTime < frames[0]) + frame = 0; + else + { + frame = Animation.binarySearch(frames, lastTime); + float frameTime = frames[frame]; + while (frame > 0) + { // Fire multiple events with the same frame. + if (frames[frame - 1] != frameTime) break; + frame--; + } + } + for (; frame < frameCount && time >= frames[frame]; frame++) + firedEvents.Add(events[frame]); + } + } + + public class DrawOrderTimeline : Timeline + { + internal float[] frames; + private int[][] drawOrders; + + public float[] Frames { get { return frames; } set { frames = value; } } // time, ... + public int[][] DrawOrders { get { return drawOrders; } set { drawOrders = value; } } + public int FrameCount { get { return frames.Length; } } + + public DrawOrderTimeline(int frameCount) + { + frames = new float[frameCount]; + drawOrders = new int[frameCount][]; + } + + /// Sets the time and value of the specified keyframe. + /// May be null to use bind pose draw order. + public void SetFrame(int frameIndex, float time, int[] drawOrder) + { + frames[frameIndex] = time; + drawOrders[frameIndex] = drawOrder; + } + + public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) + { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + int frame; + if (time >= frames[frames.Length - 1]) // Time is after last frame. + frame = frames.Length - 1; + else + frame = Animation.binarySearch(frames, time) - 1; + + ExposedList drawOrder = skeleton.drawOrder; + ExposedList slots = skeleton.slots; + int[] drawOrderToSetupIndex = drawOrders[frame]; + if (drawOrderToSetupIndex == null) + { + drawOrder.Clear(); + for (int i = 0, n = slots.Count; i < n; i++) + drawOrder.Add(slots.Items[i]); + } + else + { + var drawOrderItems = drawOrder.Items; + var slotsItems = slots.Items; + for (int i = 0, n = drawOrderToSetupIndex.Length; i < n; i++) + drawOrderItems[i] = slotsItems[drawOrderToSetupIndex[i]]; + } + } + } + + public class DeformTimeline : CurveTimeline + { + internal int slotIndex; + internal float[] frames; + private float[][] frameVertices; + internal VertexAttachment attachment; + + public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, ... + public float[][] Vertices { get { return frameVertices; } set { frameVertices = value; } } + public VertexAttachment Attachment { get { return attachment; } set { attachment = value; } } + + public DeformTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount]; + frameVertices = new float[frameCount][]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, float[] vertices) + { + frames[frameIndex] = time; + frameVertices[frameIndex] = vertices; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) + { + Slot slot = skeleton.slots.Items[slotIndex]; + VertexAttachment slotAttachment = slot.attachment as VertexAttachment; + if (slotAttachment == null || !slotAttachment.ApplyDeform(attachment)) return; + + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + float[][] frameVertices = this.frameVertices; + int vertexCount = frameVertices[0].Length; + + var verticesArray = slot.attachmentVertices; + if (verticesArray.Count != vertexCount) alpha = 1; // Don't mix from uninitialized slot vertices. + // verticesArray.SetSize(vertexCount) // Ensure size and preemptively set count. + if (verticesArray.Capacity < vertexCount) verticesArray.Capacity = vertexCount; + verticesArray.Count = vertexCount; + float[] vertices = verticesArray.Items; + + if (time >= frames[frames.Length - 1]) + { // Time is after last frame. + float[] lastVertices = frameVertices[frames.Length - 1]; + if (alpha < 1) + { + for (int i = 0; i < vertexCount; i++) + { + float vertex = vertices[i]; + vertices[i] = vertex + (lastVertices[i] - vertex) * alpha; + } + } + else + Array.Copy(lastVertices, 0, vertices, 0, vertexCount); + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = Animation.binarySearch(frames, time); + float[] prevVertices = frameVertices[frame - 1]; + float[] nextVertices = frameVertices[frame]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame - 1, 1 - (time - frameTime) / (frames[frame - 1] - frameTime)); + + if (alpha < 1) + { + for (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i]; + float vertex = vertices[i]; + vertices[i] = vertex + (prev + (nextVertices[i] - prev) * percent - vertex) * alpha; + } + } + else + { + for (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i]; + vertices[i] = prev + (nextVertices[i] - prev) * percent; + } + } + } + } + + public class IkConstraintTimeline : CurveTimeline + { + public const int ENTRIES = 3; + private const int PREV_TIME = -3, PREV_MIX = -2, PREV_BEND_DIRECTION = -1; + private const int MIX = 1, BEND_DIRECTION = 2; + + internal int ikConstraintIndex; + internal float[] frames; + + public int IkConstraintIndex { get { return ikConstraintIndex; } set { ikConstraintIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, mix, bendDirection, ... + + public IkConstraintTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + /// Sets the time, mix and bend direction of the specified keyframe. + public void SetFrame(int frameIndex, float time, float mix, int bendDirection) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + MIX] = mix; + frames[frameIndex + BEND_DIRECTION] = bendDirection; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) + { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + IkConstraint constraint = skeleton.ikConstraints.Items[ikConstraintIndex]; + + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + constraint.mix += (frames[frames.Length + PREV_MIX] - constraint.mix) * alpha; + constraint.bendDirection = (int)frames[frames.Length + PREV_BEND_DIRECTION]; + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = Animation.binarySearch(frames, time, ENTRIES); + float mix = frames[frame + PREV_MIX]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + constraint.mix += (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha; + constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION]; + } + } + + public class TransformConstraintTimeline : CurveTimeline + { + public const int ENTRIES = 5; + private const int PREV_TIME = -5, PREV_ROTATE = -4, PREV_TRANSLATE = -3, PREV_SCALE = -2, PREV_SHEAR = -1; + private const int ROTATE = 1, TRANSLATE = 2, SCALE = 3, SHEAR = 4; + + internal int transformConstraintIndex; + internal float[] frames; + + public int TransformConstraintIndex { get { return transformConstraintIndex; } set { transformConstraintIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, scale mix, shear mix, ... + + public TransformConstraintTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + public void SetFrame(int frameIndex, float time, float rotateMix, float translateMix, float scaleMix, float shearMix) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + ROTATE] = rotateMix; + frames[frameIndex + TRANSLATE] = translateMix; + frames[frameIndex + SCALE] = scaleMix; + frames[frameIndex + SHEAR] = shearMix; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha) + { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + TransformConstraint constraint = skeleton.transformConstraints.Items[transformConstraintIndex]; + + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + int i = frames.Length; + constraint.rotateMix += (frames[i + PREV_ROTATE] - constraint.rotateMix) * alpha; + constraint.translateMix += (frames[i + PREV_TRANSLATE] - constraint.translateMix) * alpha; + constraint.scaleMix += (frames[i + PREV_SCALE] - constraint.scaleMix) * alpha; + constraint.shearMix += (frames[i + PREV_SHEAR] - constraint.shearMix) * alpha; + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = Animation.binarySearch(frames, time, ENTRIES); + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + float rotate = frames[frame + PREV_ROTATE]; + float translate = frames[frame + PREV_TRANSLATE]; + float scale = frames[frame + PREV_SCALE]; + float shear = frames[frame + PREV_SHEAR]; + constraint.rotateMix += (rotate + (frames[frame + ROTATE] - rotate) * percent - constraint.rotateMix) * alpha; + constraint.translateMix += (translate + (frames[frame + TRANSLATE] - translate) * percent - constraint.translateMix) + * alpha; + constraint.scaleMix += (scale + (frames[frame + SCALE] - scale) * percent - constraint.scaleMix) * alpha; + constraint.shearMix += (shear + (frames[frame + SHEAR] - shear) * percent - constraint.shearMix) * alpha; + } + } + + public class PathConstraintPositionTimeline : CurveTimeline + { + public const int ENTRIES = 2; + protected const int PREV_TIME = -2, PREV_VALUE = -1; + protected const int VALUE = 1; + + internal int pathConstraintIndex; + internal float[] frames; + + public PathConstraintPositionTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + public int PathConstraintIndex { get { return pathConstraintIndex; } set { pathConstraintIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, position, ... + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, float value) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + VALUE] = value; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha) + { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; + + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + int i = frames.Length; + constraint.position += (frames[i + PREV_VALUE] - constraint.position) * alpha; + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = Animation.binarySearch(frames, time, ENTRIES); + float position = frames[frame + PREV_VALUE]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + constraint.position += (position + (frames[frame + VALUE] - position) * percent - constraint.position) * alpha; + } + } + + public class PathConstraintSpacingTimeline : PathConstraintPositionTimeline + { + public PathConstraintSpacingTimeline(int frameCount) + : base(frameCount) + { + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha) + { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; + + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + int i = frames.Length; + constraint.spacing += (frames[i + PREV_VALUE] - constraint.spacing) * alpha; + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = Animation.binarySearch(frames, time, ENTRIES); + float spacing = frames[frame + PREV_VALUE]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + constraint.spacing += (spacing + (frames[frame + VALUE] - spacing) * percent - constraint.spacing) * alpha; + } + } + + public class PathConstraintMixTimeline : CurveTimeline + { + public const int ENTRIES = 3; + private const int PREV_TIME = -3, PREV_ROTATE = -2, PREV_TRANSLATE = -1; + private const int ROTATE = 1, TRANSLATE = 2; + + internal int pathConstraintIndex; + internal float[] frames; + + public int PathConstraintIndex { get { return pathConstraintIndex; } set { pathConstraintIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, ... + + public PathConstraintMixTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + /** Sets the time and mixes of the specified keyframe. */ + public void SetFrame(int frameIndex, float time, float rotateMix, float translateMix) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + ROTATE] = rotateMix; + frames[frameIndex + TRANSLATE] = translateMix; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha) + { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; + + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + int i = frames.Length; + constraint.rotateMix += (frames[i + PREV_ROTATE] - constraint.rotateMix) * alpha; + constraint.translateMix += (frames[i + PREV_TRANSLATE] - constraint.translateMix) * alpha; + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = Animation.binarySearch(frames, time, ENTRIES); + float rotate = frames[frame + PREV_ROTATE]; + float translate = frames[frame + PREV_TRANSLATE]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + constraint.rotateMix += (rotate + (frames[frame + ROTATE] - rotate) * percent - constraint.rotateMix) * alpha; + constraint.translateMix += (translate + (frames[frame + TRANSLATE] - translate) * percent - constraint.translateMix) + * alpha; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/AnimationState.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/AnimationState.cs index 526b0f1..125016e 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/AnimationState.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/AnimationState.cs @@ -29,278 +29,316 @@ *****************************************************************************/ using System; -using System.Collections.Generic; using System.Text; -namespace Spine3_4_02 { - public class AnimationState { - private AnimationStateData data; - private ExposedList tracks = new ExposedList(); - private ExposedList events = new ExposedList(); - private float timeScale = 1; - - public AnimationStateData Data { get { return data; } } - /// A list of tracks that have animations, which may contain nulls. - public ExposedList Tracks { get { return tracks; } } - public float TimeScale { get { return timeScale; } set { timeScale = value; } } - - public delegate void StartEndDelegate (AnimationState state, int trackIndex); - public event StartEndDelegate Start; - public event StartEndDelegate End; - - public delegate void EventDelegate (AnimationState state, int trackIndex, Event e); - public event EventDelegate Event; - - public delegate void CompleteDelegate (AnimationState state, int trackIndex, int loopCount); - public event CompleteDelegate Complete; - - public AnimationState (AnimationStateData data) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - this.data = data; - } - - public void Update (float delta) { - delta *= timeScale; - for (int i = 0; i < tracks.Count; i++) { - TrackEntry current = tracks.Items[i]; - if (current == null) continue; - - float trackDelta = delta * current.timeScale; - float time = current.time + trackDelta; - float endTime = current.endTime; - - current.time = time; - if (current.previous != null) { - current.previous.time += trackDelta; - current.mixTime += trackDelta; - } - - // Check if completed the animation or a loop iteration. - if (current.loop ? (current.lastTime % endTime > time % endTime) : (current.lastTime < endTime && time >= endTime)) { - int count = (int)(time / endTime); - current.OnComplete(this, i, count); - if (Complete != null) Complete(this, i, count); - } - - TrackEntry next = current.next; - if (next != null) { - next.time = current.lastTime - next.delay; - if (next.time >= 0) SetCurrent(i, next); - } else { - // End non-looping animation when it reaches its end time and there is no next entry. - if (!current.loop && current.lastTime >= current.endTime) ClearTrack(i); - } - } - } - - public void Apply (Skeleton skeleton) { - ExposedList events = this.events; - - for (int i = 0; i < tracks.Count; i++) { - TrackEntry current = tracks.Items[i]; - if (current == null) continue; - - events.Clear(); - - float time = current.time; - bool loop = current.loop; - if (!loop && time > current.endTime) time = current.endTime; - - TrackEntry previous = current.previous; - if (previous == null) { - if (current.mix == 1) - current.animation.Apply(skeleton, current.lastTime, time, loop, events); - else - current.animation.Mix(skeleton, current.lastTime, time, loop, events, current.mix); - } else { - float previousTime = previous.time; - if (!previous.loop && previousTime > previous.endTime) previousTime = previous.endTime; - previous.animation.Apply(skeleton, previous.lastTime, previousTime, previous.loop, null); - // Remove the line above, and uncomment the line below, to allow previous animations to fire events during mixing. - //previous.animation.Apply(skeleton, previous.lastTime, previousTime, previous.loop, events); - previous.lastTime = previousTime; - - float alpha = current.mixTime / current.mixDuration * current.mix; - if (alpha >= 1) { - alpha = 1; - current.previous = null; - } - current.animation.Mix(skeleton, current.lastTime, time, loop, events, alpha); - } - - for (int ii = 0, nn = events.Count; ii < nn; ii++) { - Event e = events.Items[ii]; - current.OnEvent(this, i, e); - if (Event != null) Event(this, i, e); - } - - current.lastTime = current.time; - } - } - - public void ClearTracks () { - for (int i = 0, n = tracks.Count; i < n; i++) - ClearTrack(i); - tracks.Clear(); - } - - public void ClearTrack (int trackIndex) { - if (trackIndex >= tracks.Count) return; - TrackEntry current = tracks.Items[trackIndex]; - if (current == null) return; - - current.OnEnd(this, trackIndex); - if (End != null) End(this, trackIndex); - - tracks.Items[trackIndex] = null; - } - - private TrackEntry ExpandToIndex (int index) { - if (index < tracks.Count) return tracks.Items[index]; - while (index >= tracks.Count) - tracks.Add(null); - return null; - } - - private void SetCurrent (int index, TrackEntry entry) { - TrackEntry current = ExpandToIndex(index); - if (current != null) { - TrackEntry previous = current.previous; - current.previous = null; - - current.OnEnd(this, index); - if (End != null) End(this, index); - - entry.mixDuration = data.GetMix(current.animation, entry.animation); - if (entry.mixDuration > 0) { - entry.mixTime = 0; - // If a mix is in progress, mix from the closest animation. - if (previous != null && current.mixTime / current.mixDuration < 0.5f) - entry.previous = previous; - else - entry.previous = current; - } - } - - tracks.Items[index] = entry; - - entry.OnStart(this, index); - if (Start != null) Start(this, index); - } - - /// - public TrackEntry SetAnimation (int trackIndex, String animationName, bool loop) { - Animation animation = data.skeletonData.FindAnimation(animationName); - if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName"); - return SetAnimation(trackIndex, animation, loop); - } - - /// Set the current animation. Any queued animations are cleared. - public TrackEntry SetAnimation (int trackIndex, Animation animation, bool loop) { - if (animation == null) throw new ArgumentNullException("animation", "animation cannot be null."); - TrackEntry entry = new TrackEntry(); - entry.animation = animation; - entry.loop = loop; - entry.time = 0; - entry.endTime = animation.Duration; - SetCurrent(trackIndex, entry); - return entry; - } - - /// - public TrackEntry AddAnimation (int trackIndex, String animationName, bool loop, float delay) { - Animation animation = data.skeletonData.FindAnimation(animationName); - if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName"); - return AddAnimation(trackIndex, animation, loop, delay); - } - - /// Adds an animation to be played delay seconds after the current or last queued animation. - /// May be <= 0 to use duration of previous animation minus any mix duration plus the negative delay. - public TrackEntry AddAnimation (int trackIndex, Animation animation, bool loop, float delay) { - if (animation == null) throw new ArgumentNullException("animation", "animation cannot be null."); - TrackEntry entry = new TrackEntry(); - entry.animation = animation; - entry.loop = loop; - entry.time = 0; - entry.endTime = animation.Duration; - - TrackEntry last = ExpandToIndex(trackIndex); - if (last != null) { - while (last.next != null) - last = last.next; - last.next = entry; - } else - tracks.Items[trackIndex] = entry; - - if (delay <= 0) { - if (last != null) - delay += last.endTime - data.GetMix(last.animation, animation); - else - delay = 0; - } - entry.delay = delay; - - return entry; - } - - /// May be null. - public TrackEntry GetCurrent (int trackIndex) { - if (trackIndex >= tracks.Count) return null; - return tracks.Items[trackIndex]; - } - - override public String ToString () { - StringBuilder buffer = new StringBuilder(); - for (int i = 0, n = tracks.Count; i < n; i++) { - TrackEntry entry = tracks.Items[i]; - if (entry == null) continue; - if (buffer.Length > 0) buffer.Append(", "); - buffer.Append(entry.ToString()); - } - if (buffer.Length == 0) return ""; - return buffer.ToString(); - } - } - - public class TrackEntry { - internal TrackEntry next, previous; - internal Animation animation; - internal bool loop; - internal float delay, time, lastTime = -1, endTime, timeScale = 1; - internal float mixTime, mixDuration, mix = 1; - - public Animation Animation { get { return animation; } } - public float Delay { get { return delay; } set { delay = value; } } - public float Time { get { return time; } set { time = value; } } - public float LastTime { get { return lastTime; } set { lastTime = value; } } - public float EndTime { get { return endTime; } set { endTime = value; } } - public float TimeScale { get { return timeScale; } set { timeScale = value; } } - public float Mix { get { return mix; } set { mix = value; } } - public bool Loop { get { return loop; } set { loop = value; } } - - public event AnimationState.StartEndDelegate Start; - public event AnimationState.StartEndDelegate End; - public event AnimationState.EventDelegate Event; - public event AnimationState.CompleteDelegate Complete; - - internal void OnStart (AnimationState state, int index) { - if (Start != null) Start(state, index); - } - - internal void OnEnd (AnimationState state, int index) { - if (End != null) End(state, index); - } - - internal void OnEvent (AnimationState state, int index, Event e) { - if (Event != null) Event(state, index, e); - } - - internal void OnComplete (AnimationState state, int index, int loopCount) { - if (Complete != null) Complete(state, index, loopCount); - } - - override public String ToString () { - return animation == null ? "" : animation.name; - } - } +namespace Spine3_4_02 +{ + public class AnimationState + { + private AnimationStateData data; + private ExposedList tracks = new ExposedList(); + private ExposedList events = new ExposedList(); + private float timeScale = 1; + + public AnimationStateData Data { get { return data; } } + /// A list of tracks that have animations, which may contain nulls. + public ExposedList Tracks { get { return tracks; } } + public float TimeScale { get { return timeScale; } set { timeScale = value; } } + + public delegate void StartEndDelegate(AnimationState state, int trackIndex); + public event StartEndDelegate Start; + public event StartEndDelegate End; + + public delegate void EventDelegate(AnimationState state, int trackIndex, Event e); + public event EventDelegate Event; + + public delegate void CompleteDelegate(AnimationState state, int trackIndex, int loopCount); + public event CompleteDelegate Complete; + + public AnimationState(AnimationStateData data) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + this.data = data; + } + + public void Update(float delta) + { + delta *= timeScale; + for (int i = 0; i < tracks.Count; i++) + { + TrackEntry current = tracks.Items[i]; + if (current == null) continue; + + float trackDelta = delta * current.timeScale; + float time = current.time + trackDelta; + float endTime = current.endTime; + + current.time = time; + if (current.previous != null) + { + current.previous.time += trackDelta; + current.mixTime += trackDelta; + } + + // Check if completed the animation or a loop iteration. + if (current.loop ? (current.lastTime % endTime > time % endTime) : (current.lastTime < endTime && time >= endTime)) + { + int count = (int)(time / endTime); + current.OnComplete(this, i, count); + if (Complete != null) Complete(this, i, count); + } + + TrackEntry next = current.next; + if (next != null) + { + next.time = current.lastTime - next.delay; + if (next.time >= 0) SetCurrent(i, next); + } + else + { + // End non-looping animation when it reaches its end time and there is no next entry. + if (!current.loop && current.lastTime >= current.endTime) ClearTrack(i); + } + } + } + + public void Apply(Skeleton skeleton) + { + ExposedList events = this.events; + + for (int i = 0; i < tracks.Count; i++) + { + TrackEntry current = tracks.Items[i]; + if (current == null) continue; + + events.Clear(); + + float time = current.time; + bool loop = current.loop; + if (!loop && time > current.endTime) time = current.endTime; + + TrackEntry previous = current.previous; + if (previous == null) + { + if (current.mix == 1) + current.animation.Apply(skeleton, current.lastTime, time, loop, events); + else + current.animation.Mix(skeleton, current.lastTime, time, loop, events, current.mix); + } + else + { + float previousTime = previous.time; + if (!previous.loop && previousTime > previous.endTime) previousTime = previous.endTime; + previous.animation.Apply(skeleton, previous.lastTime, previousTime, previous.loop, null); + // Remove the line above, and uncomment the line below, to allow previous animations to fire events during mixing. + //previous.animation.Apply(skeleton, previous.lastTime, previousTime, previous.loop, events); + previous.lastTime = previousTime; + + float alpha = current.mixTime / current.mixDuration * current.mix; + if (alpha >= 1) + { + alpha = 1; + current.previous = null; + } + current.animation.Mix(skeleton, current.lastTime, time, loop, events, alpha); + } + + for (int ii = 0, nn = events.Count; ii < nn; ii++) + { + Event e = events.Items[ii]; + current.OnEvent(this, i, e); + if (Event != null) Event(this, i, e); + } + + current.lastTime = current.time; + } + } + + public void ClearTracks() + { + for (int i = 0, n = tracks.Count; i < n; i++) + ClearTrack(i); + tracks.Clear(); + } + + public void ClearTrack(int trackIndex) + { + if (trackIndex >= tracks.Count) return; + TrackEntry current = tracks.Items[trackIndex]; + if (current == null) return; + + current.OnEnd(this, trackIndex); + if (End != null) End(this, trackIndex); + + tracks.Items[trackIndex] = null; + } + + private TrackEntry ExpandToIndex(int index) + { + if (index < tracks.Count) return tracks.Items[index]; + while (index >= tracks.Count) + tracks.Add(null); + return null; + } + + private void SetCurrent(int index, TrackEntry entry) + { + TrackEntry current = ExpandToIndex(index); + if (current != null) + { + TrackEntry previous = current.previous; + current.previous = null; + + current.OnEnd(this, index); + if (End != null) End(this, index); + + entry.mixDuration = data.GetMix(current.animation, entry.animation); + if (entry.mixDuration > 0) + { + entry.mixTime = 0; + // If a mix is in progress, mix from the closest animation. + if (previous != null && current.mixTime / current.mixDuration < 0.5f) + entry.previous = previous; + else + entry.previous = current; + } + } + + tracks.Items[index] = entry; + + entry.OnStart(this, index); + if (Start != null) Start(this, index); + } + + /// + public TrackEntry SetAnimation(int trackIndex, String animationName, bool loop) + { + Animation animation = data.skeletonData.FindAnimation(animationName); + if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName"); + return SetAnimation(trackIndex, animation, loop); + } + + /// Set the current animation. Any queued animations are cleared. + public TrackEntry SetAnimation(int trackIndex, Animation animation, bool loop) + { + if (animation == null) throw new ArgumentNullException("animation", "animation cannot be null."); + TrackEntry entry = new TrackEntry(); + entry.animation = animation; + entry.loop = loop; + entry.time = 0; + entry.endTime = animation.Duration; + SetCurrent(trackIndex, entry); + return entry; + } + + /// + public TrackEntry AddAnimation(int trackIndex, String animationName, bool loop, float delay) + { + Animation animation = data.skeletonData.FindAnimation(animationName); + if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName"); + return AddAnimation(trackIndex, animation, loop, delay); + } + + /// Adds an animation to be played delay seconds after the current or last queued animation. + /// May be <= 0 to use duration of previous animation minus any mix duration plus the negative delay. + public TrackEntry AddAnimation(int trackIndex, Animation animation, bool loop, float delay) + { + if (animation == null) throw new ArgumentNullException("animation", "animation cannot be null."); + TrackEntry entry = new TrackEntry(); + entry.animation = animation; + entry.loop = loop; + entry.time = 0; + entry.endTime = animation.Duration; + + TrackEntry last = ExpandToIndex(trackIndex); + if (last != null) + { + while (last.next != null) + last = last.next; + last.next = entry; + } + else + tracks.Items[trackIndex] = entry; + + if (delay <= 0) + { + if (last != null) + delay += last.endTime - data.GetMix(last.animation, animation); + else + delay = 0; + } + entry.delay = delay; + + return entry; + } + + /// May be null. + public TrackEntry GetCurrent(int trackIndex) + { + if (trackIndex >= tracks.Count) return null; + return tracks.Items[trackIndex]; + } + + override public String ToString() + { + StringBuilder buffer = new StringBuilder(); + for (int i = 0, n = tracks.Count; i < n; i++) + { + TrackEntry entry = tracks.Items[i]; + if (entry == null) continue; + if (buffer.Length > 0) buffer.Append(", "); + buffer.Append(entry.ToString()); + } + if (buffer.Length == 0) return ""; + return buffer.ToString(); + } + } + + public class TrackEntry + { + internal TrackEntry next, previous; + internal Animation animation; + internal bool loop; + internal float delay, time, lastTime = -1, endTime, timeScale = 1; + internal float mixTime, mixDuration, mix = 1; + + public Animation Animation { get { return animation; } } + public float Delay { get { return delay; } set { delay = value; } } + public float Time { get { return time; } set { time = value; } } + public float LastTime { get { return lastTime; } set { lastTime = value; } } + public float EndTime { get { return endTime; } set { endTime = value; } } + public float TimeScale { get { return timeScale; } set { timeScale = value; } } + public float Mix { get { return mix; } set { mix = value; } } + public bool Loop { get { return loop; } set { loop = value; } } + + public event AnimationState.StartEndDelegate Start; + public event AnimationState.StartEndDelegate End; + public event AnimationState.EventDelegate Event; + public event AnimationState.CompleteDelegate Complete; + + internal void OnStart(AnimationState state, int index) + { + if (Start != null) Start(state, index); + } + + internal void OnEnd(AnimationState state, int index) + { + if (End != null) End(state, index); + } + + internal void OnEvent(AnimationState state, int index, Event e) + { + if (Event != null) Event(state, index, e); + } + + internal void OnComplete(AnimationState state, int index, int loopCount) + { + if (Complete != null) Complete(state, index, loopCount); + } + + override public String ToString() + { + return animation == null ? "" : animation.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/AnimationStateData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/AnimationStateData.cs index 95d7d19..95f14e7 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/AnimationStateData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/AnimationStateData.cs @@ -31,66 +31,77 @@ using System; using System.Collections.Generic; -namespace Spine3_4_02 { - public class AnimationStateData { - internal SkeletonData skeletonData; - private Dictionary animationToMixTime = new Dictionary(AnimationPairComparer.Instance); - internal float defaultMix; +namespace Spine3_4_02 +{ + public class AnimationStateData + { + internal SkeletonData skeletonData; + private Dictionary animationToMixTime = new Dictionary(AnimationPairComparer.Instance); + internal float defaultMix; - public SkeletonData SkeletonData { get { return skeletonData; } } - public float DefaultMix { get { return defaultMix; } set { defaultMix = value; } } + public SkeletonData SkeletonData { get { return skeletonData; } } + public float DefaultMix { get { return defaultMix; } set { defaultMix = value; } } - public AnimationStateData (SkeletonData skeletonData) { - if (skeletonData == null) throw new ArgumentException ("skeletonData cannot be null."); - this.skeletonData = skeletonData; - } + public AnimationStateData(SkeletonData skeletonData) + { + if (skeletonData == null) throw new ArgumentException("skeletonData cannot be null."); + this.skeletonData = skeletonData; + } - public void SetMix (String fromName, String toName, float duration) { - Animation from = skeletonData.FindAnimation(fromName); - if (from == null) throw new ArgumentException("Animation not found: " + fromName); - Animation to = skeletonData.FindAnimation(toName); - if (to == null) throw new ArgumentException("Animation not found: " + toName); - SetMix(from, to, duration); - } + public void SetMix(String fromName, String toName, float duration) + { + Animation from = skeletonData.FindAnimation(fromName); + if (from == null) throw new ArgumentException("Animation not found: " + fromName); + Animation to = skeletonData.FindAnimation(toName); + if (to == null) throw new ArgumentException("Animation not found: " + toName); + SetMix(from, to, duration); + } - public void SetMix (Animation from, Animation to, float duration) { - if (from == null) throw new ArgumentNullException("from", "from cannot be null."); - if (to == null) throw new ArgumentNullException("to", "to cannot be null."); - AnimationPair key = new AnimationPair(from, to); - animationToMixTime.Remove(key); - animationToMixTime.Add(key, duration); - } + public void SetMix(Animation from, Animation to, float duration) + { + if (from == null) throw new ArgumentNullException("from", "from cannot be null."); + if (to == null) throw new ArgumentNullException("to", "to cannot be null."); + AnimationPair key = new AnimationPair(from, to); + animationToMixTime.Remove(key); + animationToMixTime.Add(key, duration); + } - public float GetMix (Animation from, Animation to) { - AnimationPair key = new AnimationPair(from, to); - float duration; - if (animationToMixTime.TryGetValue(key, out duration)) return duration; - return defaultMix; - } + public float GetMix(Animation from, Animation to) + { + AnimationPair key = new AnimationPair(from, to); + float duration; + if (animationToMixTime.TryGetValue(key, out duration)) return duration; + return defaultMix; + } - struct AnimationPair { - public readonly Animation a1; - public readonly Animation a2; + struct AnimationPair + { + public readonly Animation a1; + public readonly Animation a2; - public AnimationPair (Animation a1, Animation a2) { - this.a1 = a1; - this.a2 = a2; - } - } + public AnimationPair(Animation a1, Animation a2) + { + this.a1 = a1; + this.a2 = a2; + } + } - // Avoids boxing in the dictionary. - class AnimationPairComparer : IEqualityComparer { - internal static readonly AnimationPairComparer Instance = new AnimationPairComparer(); + // Avoids boxing in the dictionary. + class AnimationPairComparer : IEqualityComparer + { + internal static readonly AnimationPairComparer Instance = new AnimationPairComparer(); - bool IEqualityComparer.Equals (AnimationPair x, AnimationPair y) { - return ReferenceEquals(x.a1, y.a1) && ReferenceEquals(x.a2, y.a2); - } + bool IEqualityComparer.Equals(AnimationPair x, AnimationPair y) + { + return ReferenceEquals(x.a1, y.a1) && ReferenceEquals(x.a2, y.a2); + } - int IEqualityComparer.GetHashCode (AnimationPair obj) { - // from Tuple.CombineHashCodes // return (((h1 << 5) + h1) ^ h2); - int h1 = obj.a1.GetHashCode(); - return (((h1 << 5) + h1) ^ obj.a2.GetHashCode()); - } - } - } + int IEqualityComparer.GetHashCode(AnimationPair obj) + { + // from Tuple.CombineHashCodes // return (((h1 << 5) + h1) ^ h2); + int h1 = obj.a1.GetHashCode(); + return (((h1 << 5) + h1) ^ obj.a2.GetHashCode()); + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Atlas.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Atlas.cs index 7f4e87f..26e64ec 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Atlas.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Atlas.cs @@ -31,21 +31,22 @@ using System; using System.Collections.Generic; using System.IO; -using System.Reflection; #if WINDOWS_STOREAPP using System.Threading.Tasks; using Windows.Storage; #endif -namespace Spine3_4_02 { - public class Atlas { - List pages = new List(); - List regions = new List(); - TextureLoader textureLoader; +namespace Spine3_4_02 +{ + public class Atlas + { + List pages = new List(); + List regions = new List(); + TextureLoader textureLoader; - #if !(UNITY_5 || UNITY_4 || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1) // !UNITY - #if WINDOWS_STOREAPP +#if !(UNITY_5 || UNITY_4 || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1) // !UNITY +#if WINDOWS_STOREAPP private async Task ReadFile(string path, TextureLoader textureLoader) { var folder = Windows.ApplicationModel.Package.Current.InstalledLocation; var file = await folder.GetFileAsync(path).AsTask().ConfigureAwait(false); @@ -61,233 +62,264 @@ private async Task ReadFile(string path, TextureLoader textureLoader) { public Atlas(String path, TextureLoader textureLoader) { this.ReadFile(path, textureLoader).Wait(); } - #else +#else - public Atlas (String path, TextureLoader textureLoader) { + public Atlas(String path, TextureLoader textureLoader) + { - #if WINDOWS_PHONE +#if WINDOWS_PHONE Stream stream = Microsoft.Xna.Framework.TitleContainer.OpenStream(path); using (StreamReader reader = new StreamReader(stream)) { - #else - using (StreamReader reader = new StreamReader(path)) { - #endif // WINDOWS_PHONE - - try { - Load(reader, Path.GetDirectoryName(path), textureLoader); - } catch (Exception ex) { - throw new Exception("Error reading atlas file: " + path, ex); - } - - } - } - #endif // WINDOWS_STOREAPP - - #endif // !(UNITY) - - public Atlas (TextReader reader, String dir, TextureLoader textureLoader) { - Load(reader, dir, textureLoader); - } - - public Atlas (List pages, List regions) { - this.pages = pages; - this.regions = regions; - this.textureLoader = null; - } - - private void Load (TextReader reader, String imagesDir, TextureLoader textureLoader) { - if (textureLoader == null) throw new ArgumentNullException("textureLoader cannot be null."); - this.textureLoader = textureLoader; - - String[] tuple = new String[4]; - AtlasPage page = null; - while (true) { - String line = reader.ReadLine(); - if (line == null) break; - if (line.Trim().Length == 0) - page = null; - else if (page == null) { - page = new AtlasPage(); - page.name = line; - - if (ReadTuple(reader, tuple) == 2) { // size is only optional for an atlas packed with an old TexturePacker. - page.width = int.Parse(tuple[0]); - page.height = int.Parse(tuple[1]); - ReadTuple(reader, tuple); - } - page.format = (Format)Enum.Parse(typeof(Format), tuple[0], false); - - ReadTuple(reader, tuple); - page.minFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[0], false); - page.magFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[1], false); - - String direction = ReadValue(reader); - page.uWrap = TextureWrap.ClampToEdge; - page.vWrap = TextureWrap.ClampToEdge; - if (direction == "x") - page.uWrap = TextureWrap.Repeat; - else if (direction == "y") - page.vWrap = TextureWrap.Repeat; - else if (direction == "xy") - page.uWrap = page.vWrap = TextureWrap.Repeat; - - textureLoader.Load(page, Path.Combine(imagesDir, line)); - - pages.Add(page); - - } else { - AtlasRegion region = new AtlasRegion(); - region.name = line; - region.page = page; - - region.rotate = Boolean.Parse(ReadValue(reader)); - - ReadTuple(reader, tuple); - int x = int.Parse(tuple[0]); - int y = int.Parse(tuple[1]); - - ReadTuple(reader, tuple); - int width = int.Parse(tuple[0]); - int height = int.Parse(tuple[1]); - - region.u = x / (float)page.width; - region.v = y / (float)page.height; - if (region.rotate) { - region.u2 = (x + height) / (float)page.width; - region.v2 = (y + width) / (float)page.height; - } else { - region.u2 = (x + width) / (float)page.width; - region.v2 = (y + height) / (float)page.height; - } - region.x = x; - region.y = y; - region.width = Math.Abs(width); - region.height = Math.Abs(height); - - if (ReadTuple(reader, tuple) == 4) { // split is optional - region.splits = new int[] {int.Parse(tuple[0]), int.Parse(tuple[1]), - int.Parse(tuple[2]), int.Parse(tuple[3])}; - - if (ReadTuple(reader, tuple) == 4) { // pad is optional, but only present with splits - region.pads = new int[] {int.Parse(tuple[0]), int.Parse(tuple[1]), - int.Parse(tuple[2]), int.Parse(tuple[3])}; - - ReadTuple(reader, tuple); - } - } - - region.originalWidth = int.Parse(tuple[0]); - region.originalHeight = int.Parse(tuple[1]); - - ReadTuple(reader, tuple); - region.offsetX = int.Parse(tuple[0]); - region.offsetY = int.Parse(tuple[1]); - - region.index = int.Parse(ReadValue(reader)); - - regions.Add(region); - } - } - } - - static String ReadValue (TextReader reader) { - String line = reader.ReadLine(); - int colon = line.IndexOf(':'); - if (colon == -1) throw new Exception("Invalid line: " + line); - return line.Substring(colon + 1).Trim(); - } - - /// Returns the number of tuple values read (1, 2 or 4). - static int ReadTuple (TextReader reader, String[] tuple) { - String line = reader.ReadLine(); - int colon = line.IndexOf(':'); - if (colon == -1) throw new Exception("Invalid line: " + line); - int i = 0, lastMatch = colon + 1; - for (; i < 3; i++) { - int comma = line.IndexOf(',', lastMatch); - if (comma == -1) break; - tuple[i] = line.Substring(lastMatch, comma - lastMatch).Trim(); - lastMatch = comma + 1; - } - tuple[i] = line.Substring(lastMatch).Trim(); - return i + 1; - } - - public void FlipV () { - for (int i = 0, n = regions.Count; i < n; i++) { - AtlasRegion region = regions[i]; - region.v = 1 - region.v; - region.v2 = 1 - region.v2; - } - } - - /// 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. - /// The region, or null. - public AtlasRegion FindRegion (String name) { - for (int i = 0, n = regions.Count; i < n; i++) - if (regions[i].name == name) return regions[i]; - return null; - } - - public void Dispose () { - if (textureLoader == null) return; - for (int i = 0, n = pages.Count; i < n; i++) - textureLoader.Unload(pages[i].rendererObject); - } - } - - public enum Format { - Alpha, - Intensity, - LuminanceAlpha, - RGB565, - RGBA4444, - RGB888, - RGBA8888 - } - - public enum TextureFilter { - Nearest, - Linear, - MipMap, - MipMapNearestNearest, - MipMapLinearNearest, - MipMapNearestLinear, - MipMapLinearLinear - } - - public enum TextureWrap { - MirroredRepeat, - ClampToEdge, - Repeat - } - - public class AtlasPage { - public String name; - public Format format; - public TextureFilter minFilter; - public TextureFilter magFilter; - public TextureWrap uWrap; - public TextureWrap vWrap; - public Object rendererObject; - public int width, height; - } - - public class AtlasRegion { - public AtlasPage page; - public String name; - public int x, y, width, height; - public float u, v, u2, v2; - public float offsetX, offsetY; - public int originalWidth, originalHeight; - public int index; - public bool rotate; - public int[] splits; - public int[] pads; - } - - public interface TextureLoader { - void Load (AtlasPage page, String path); - void Unload (Object texture); - } +#else + using (StreamReader reader = new StreamReader(path)) + { +#endif // WINDOWS_PHONE + + try + { + Load(reader, Path.GetDirectoryName(path), textureLoader); + } + catch (Exception ex) + { + throw new Exception("Error reading atlas file: " + path, ex); + } + + } + } +#endif // WINDOWS_STOREAPP + +#endif // !(UNITY) + + public Atlas(TextReader reader, String dir, TextureLoader textureLoader) + { + Load(reader, dir, textureLoader); + } + + public Atlas(List pages, List regions) + { + this.pages = pages; + this.regions = regions; + this.textureLoader = null; + } + + private void Load(TextReader reader, String imagesDir, TextureLoader textureLoader) + { + if (textureLoader == null) throw new ArgumentNullException("textureLoader cannot be null."); + this.textureLoader = textureLoader; + + String[] tuple = new String[4]; + AtlasPage page = null; + while (true) + { + String line = reader.ReadLine(); + if (line == null) break; + if (line.Trim().Length == 0) + page = null; + else if (page == null) + { + page = new AtlasPage(); + page.name = line; + + if (ReadTuple(reader, tuple) == 2) + { // size is only optional for an atlas packed with an old TexturePacker. + page.width = int.Parse(tuple[0]); + page.height = int.Parse(tuple[1]); + ReadTuple(reader, tuple); + } + page.format = (Format)Enum.Parse(typeof(Format), tuple[0], false); + + ReadTuple(reader, tuple); + page.minFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[0], false); + page.magFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[1], false); + + String direction = ReadValue(reader); + page.uWrap = TextureWrap.ClampToEdge; + page.vWrap = TextureWrap.ClampToEdge; + if (direction == "x") + page.uWrap = TextureWrap.Repeat; + else if (direction == "y") + page.vWrap = TextureWrap.Repeat; + else if (direction == "xy") + page.uWrap = page.vWrap = TextureWrap.Repeat; + + textureLoader.Load(page, Path.Combine(imagesDir, line)); + + pages.Add(page); + + } + else + { + AtlasRegion region = new AtlasRegion(); + region.name = line; + region.page = page; + + region.rotate = Boolean.Parse(ReadValue(reader)); + + ReadTuple(reader, tuple); + int x = int.Parse(tuple[0]); + int y = int.Parse(tuple[1]); + + ReadTuple(reader, tuple); + int width = int.Parse(tuple[0]); + int height = int.Parse(tuple[1]); + + region.u = x / (float)page.width; + region.v = y / (float)page.height; + if (region.rotate) + { + region.u2 = (x + height) / (float)page.width; + region.v2 = (y + width) / (float)page.height; + } + else + { + region.u2 = (x + width) / (float)page.width; + region.v2 = (y + height) / (float)page.height; + } + region.x = x; + region.y = y; + region.width = Math.Abs(width); + region.height = Math.Abs(height); + + if (ReadTuple(reader, tuple) == 4) + { // split is optional + region.splits = new int[] {int.Parse(tuple[0]), int.Parse(tuple[1]), + int.Parse(tuple[2]), int.Parse(tuple[3])}; + + if (ReadTuple(reader, tuple) == 4) + { // pad is optional, but only present with splits + region.pads = new int[] {int.Parse(tuple[0]), int.Parse(tuple[1]), + int.Parse(tuple[2]), int.Parse(tuple[3])}; + + ReadTuple(reader, tuple); + } + } + + region.originalWidth = int.Parse(tuple[0]); + region.originalHeight = int.Parse(tuple[1]); + + ReadTuple(reader, tuple); + region.offsetX = int.Parse(tuple[0]); + region.offsetY = int.Parse(tuple[1]); + + region.index = int.Parse(ReadValue(reader)); + + regions.Add(region); + } + } + } + + static String ReadValue(TextReader reader) + { + String line = reader.ReadLine(); + int colon = line.IndexOf(':'); + if (colon == -1) throw new Exception("Invalid line: " + line); + return line.Substring(colon + 1).Trim(); + } + + /// Returns the number of tuple values read (1, 2 or 4). + static int ReadTuple(TextReader reader, String[] tuple) + { + String line = reader.ReadLine(); + int colon = line.IndexOf(':'); + if (colon == -1) throw new Exception("Invalid line: " + line); + int i = 0, lastMatch = colon + 1; + for (; i < 3; i++) + { + int comma = line.IndexOf(',', lastMatch); + if (comma == -1) break; + tuple[i] = line.Substring(lastMatch, comma - lastMatch).Trim(); + lastMatch = comma + 1; + } + tuple[i] = line.Substring(lastMatch).Trim(); + return i + 1; + } + + public void FlipV() + { + for (int i = 0, n = regions.Count; i < n; i++) + { + AtlasRegion region = regions[i]; + region.v = 1 - region.v; + region.v2 = 1 - region.v2; + } + } + + /// 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. + /// The region, or null. + public AtlasRegion FindRegion(String name) + { + for (int i = 0, n = regions.Count; i < n; i++) + if (regions[i].name == name) return regions[i]; + return null; + } + + public void Dispose() + { + if (textureLoader == null) return; + for (int i = 0, n = pages.Count; i < n; i++) + textureLoader.Unload(pages[i].rendererObject); + } + } + + public enum Format + { + Alpha, + Intensity, + LuminanceAlpha, + RGB565, + RGBA4444, + RGB888, + RGBA8888 + } + + public enum TextureFilter + { + Nearest, + Linear, + MipMap, + MipMapNearestNearest, + MipMapLinearNearest, + MipMapNearestLinear, + MipMapLinearLinear + } + + public enum TextureWrap + { + MirroredRepeat, + ClampToEdge, + Repeat + } + + public class AtlasPage + { + public String name; + public Format format; + public TextureFilter minFilter; + public TextureFilter magFilter; + public TextureWrap uWrap; + public TextureWrap vWrap; + public Object rendererObject; + public int width, height; + } + + public class AtlasRegion + { + public AtlasPage page; + public String name; + public int x, y, width, height; + public float u, v, u2, v2; + public float offsetX, offsetY; + public int originalWidth, originalHeight; + public int index; + public bool rotate; + public int[] splits; + public int[] pads; + } + + public interface TextureLoader + { + void Load(AtlasPage page, String path); + void Unload(Object texture); + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Attachments/AtlasAttachmentLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Attachments/AtlasAttachmentLoader.cs index cb1a25f..62d88a3 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Attachments/AtlasAttachmentLoader.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Attachments/AtlasAttachmentLoader.cs @@ -30,67 +30,76 @@ using System; -namespace Spine3_4_02 { - public class AtlasAttachmentLoader : AttachmentLoader { - private Atlas[] atlasArray; +namespace Spine3_4_02 +{ + public class AtlasAttachmentLoader : AttachmentLoader + { + private Atlas[] atlasArray; - public AtlasAttachmentLoader (params Atlas[] atlasArray) { - if (atlasArray == null) throw new ArgumentNullException("atlas array cannot be null."); - this.atlasArray = atlasArray; - } + public AtlasAttachmentLoader(params Atlas[] atlasArray) + { + if (atlasArray == null) throw new ArgumentNullException("atlas array cannot be null."); + this.atlasArray = atlasArray; + } - public RegionAttachment NewRegionAttachment (Skin skin, String name, String path) { - AtlasRegion region = FindRegion(path); - if (region == null) throw new Exception("Region not found in atlas: " + path + " (region attachment: " + name + ")"); - RegionAttachment attachment = new RegionAttachment(name); - attachment.RendererObject = region; - attachment.SetUVs(region.u, region.v, region.u2, region.v2, region.rotate); - attachment.regionOffsetX = region.offsetX; - attachment.regionOffsetY = region.offsetY; - attachment.regionWidth = region.width; - attachment.regionHeight = region.height; - attachment.regionOriginalWidth = region.originalWidth; - attachment.regionOriginalHeight = region.originalHeight; - return attachment; - } + public RegionAttachment NewRegionAttachment(Skin skin, String name, String path) + { + AtlasRegion region = FindRegion(path); + if (region == null) throw new Exception("Region not found in atlas: " + path + " (region attachment: " + name + ")"); + RegionAttachment attachment = new RegionAttachment(name); + attachment.RendererObject = region; + attachment.SetUVs(region.u, region.v, region.u2, region.v2, region.rotate); + attachment.regionOffsetX = region.offsetX; + attachment.regionOffsetY = region.offsetY; + attachment.regionWidth = region.width; + attachment.regionHeight = region.height; + attachment.regionOriginalWidth = region.originalWidth; + attachment.regionOriginalHeight = region.originalHeight; + return attachment; + } - public MeshAttachment NewMeshAttachment (Skin skin, String name, String path) { - AtlasRegion region = FindRegion(path); - if (region == null) throw new Exception("Region not found in atlas: " + path + " (mesh attachment: " + name + ")"); - MeshAttachment attachment = new MeshAttachment(name); - attachment.RendererObject = region; - attachment.RegionU = region.u; - attachment.RegionV = region.v; - attachment.RegionU2 = region.u2; - attachment.RegionV2 = region.v2; - attachment.RegionRotate = region.rotate; - attachment.regionOffsetX = region.offsetX; - attachment.regionOffsetY = region.offsetY; - attachment.regionWidth = region.width; - attachment.regionHeight = region.height; - attachment.regionOriginalWidth = region.originalWidth; - attachment.regionOriginalHeight = region.originalHeight; - return attachment; - } + public MeshAttachment NewMeshAttachment(Skin skin, String name, String path) + { + AtlasRegion region = FindRegion(path); + if (region == null) throw new Exception("Region not found in atlas: " + path + " (mesh attachment: " + name + ")"); + MeshAttachment attachment = new MeshAttachment(name); + attachment.RendererObject = region; + attachment.RegionU = region.u; + attachment.RegionV = region.v; + attachment.RegionU2 = region.u2; + attachment.RegionV2 = region.v2; + attachment.RegionRotate = region.rotate; + attachment.regionOffsetX = region.offsetX; + attachment.regionOffsetY = region.offsetY; + attachment.regionWidth = region.width; + attachment.regionHeight = region.height; + attachment.regionOriginalWidth = region.originalWidth; + attachment.regionOriginalHeight = region.originalHeight; + return attachment; + } - public BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, String name) { - return new BoundingBoxAttachment(name); - } + public BoundingBoxAttachment NewBoundingBoxAttachment(Skin skin, String name) + { + return new BoundingBoxAttachment(name); + } - public PathAttachment NewPathAttachment (Skin skin, String name) { - return new PathAttachment (name); - } + public PathAttachment NewPathAttachment(Skin skin, String name) + { + return new PathAttachment(name); + } - public AtlasRegion FindRegion (string name) { - AtlasRegion region; + public AtlasRegion FindRegion(string name) + { + AtlasRegion region; - for (int i = 0; i < atlasArray.Length; i++) { - region = atlasArray[i].FindRegion(name); - if (region != null) - return region; - } + for (int i = 0; i < atlasArray.Length; i++) + { + region = atlasArray[i].FindRegion(name); + if (region != null) + return region; + } - return null; - } - } + return null; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Attachments/Attachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Attachments/Attachment.cs index eec28bb..a1628b9 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Attachments/Attachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Attachments/Attachment.cs @@ -30,17 +30,21 @@ using System; -namespace Spine3_4_02 { - abstract public class Attachment { - public String Name { get; private set; } +namespace Spine3_4_02 +{ + abstract public class Attachment + { + public String Name { get; private set; } - public Attachment (String name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null"); - Name = name; - } + public Attachment(String name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null"); + Name = name; + } - override public String ToString () { - return Name; - } - } + override public String ToString() + { + return Name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Attachments/AttachmentLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Attachments/AttachmentLoader.cs index 2c80f69..42913bf 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Attachments/AttachmentLoader.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Attachments/AttachmentLoader.cs @@ -30,18 +30,20 @@ using System; -namespace Spine3_4_02 { - public interface AttachmentLoader { - /// May be null to not load any attachment. - RegionAttachment NewRegionAttachment (Skin skin, String name, String path); +namespace Spine3_4_02 +{ + public interface AttachmentLoader + { + /// May be null to not load any attachment. + RegionAttachment NewRegionAttachment(Skin skin, String name, String path); - /// May be null to not load any attachment. - MeshAttachment NewMeshAttachment (Skin skin, String name, String path); + /// May be null to not load any attachment. + MeshAttachment NewMeshAttachment(Skin skin, String name, String path); - /// May be null to not load any attachment. - BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, String name); + /// May be null to not load any attachment. + BoundingBoxAttachment NewBoundingBoxAttachment(Skin skin, String name); - /// May be null to not load any attachment - PathAttachment NewPathAttachment (Skin skin, String name); - } + /// May be null to not load any attachment + PathAttachment NewPathAttachment(Skin skin, String name); + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Attachments/AttachmentType.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Attachments/AttachmentType.cs index cd796fa..1a89329 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Attachments/AttachmentType.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Attachments/AttachmentType.cs @@ -28,8 +28,10 @@ * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine3_4_02 { - public enum AttachmentType { - Region, Boundingbox, Mesh, Linkedmesh, Path - } +namespace Spine3_4_02 +{ + public enum AttachmentType + { + Region, Boundingbox, Mesh, Linkedmesh, Path + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Attachments/BoundingBoxAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Attachments/BoundingBoxAttachment.cs index ca82566..7234e26 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Attachments/BoundingBoxAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Attachments/BoundingBoxAttachment.cs @@ -28,13 +28,14 @@ * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; - -namespace Spine3_4_02 { - /// Attachment that has a polygon for bounds checking. - public class BoundingBoxAttachment : VertexAttachment { - public BoundingBoxAttachment (string name) - : base(name) { - } - } +namespace Spine3_4_02 +{ + /// Attachment that has a polygon for bounds checking. + public class BoundingBoxAttachment : VertexAttachment + { + public BoundingBoxAttachment(string name) + : base(name) + { + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Attachments/MeshAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Attachments/MeshAttachment.cs index 7580bef..16d4d66 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Attachments/MeshAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Attachments/MeshAttachment.cs @@ -30,90 +30,103 @@ using System; -namespace Spine3_4_02 { - /// Attachment that displays a texture region using a mesh. - public class MeshAttachment : VertexAttachment { - internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; - internal float[] uvs, regionUVs; - internal int[] triangles; - internal float r = 1, g = 1, b = 1, a = 1; - internal int hulllength; - internal MeshAttachment parentMesh; - internal bool inheritDeform; +namespace Spine3_4_02 +{ + /// Attachment that displays a texture region using a mesh. + public class MeshAttachment : VertexAttachment + { + internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; + internal float[] uvs, regionUVs; + internal int[] triangles; + internal float r = 1, g = 1, b = 1, a = 1; + internal int hulllength; + internal MeshAttachment parentMesh; + internal bool inheritDeform; - public int HullLength { get { return hulllength; } set { hulllength = value; } } - public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } } - public float[] UVs { get { return uvs; } set { uvs = value; } } - public int[] Triangles { get { return triangles; } set { triangles = value; } } + public int HullLength { get { return hulllength; } set { hulllength = value; } } + public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } } + public float[] UVs { get { return uvs; } set { uvs = value; } } + public int[] Triangles { get { return triangles; } set { triangles = value; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } - public String Path { get; set; } - public Object RendererObject { get; set; } - public float RegionU { get; set; } - public float RegionV { get; set; } - public float RegionU2 { get; set; } - public float RegionV2 { get; set; } - public bool RegionRotate { get; set; } - public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } - public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. - public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } - public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. - public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } - public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. + public String Path { get; set; } + public Object RendererObject { get; set; } + public float RegionU { get; set; } + public float RegionV { get; set; } + public float RegionU2 { get; set; } + public float RegionV2 { get; set; } + public bool RegionRotate { get; set; } + public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } + public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. + public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } + public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. + public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } + public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. - public bool InheritDeform { get { return inheritDeform; } set { inheritDeform = value; } } + public bool InheritDeform { get { return inheritDeform; } set { inheritDeform = value; } } - public MeshAttachment ParentMesh { - get { return parentMesh; } - set { - parentMesh = value; - if (value != null) { - bones = value.bones; - vertices = value.vertices; - worldVerticesLength = value.worldVerticesLength; - regionUVs = value.regionUVs; - triangles = value.triangles; - HullLength = value.HullLength; - Edges = value.Edges; - Width = value.Width; - Height = value.Height; - } - } - } + public MeshAttachment ParentMesh + { + get { return parentMesh; } + set + { + parentMesh = value; + if (value != null) + { + bones = value.bones; + vertices = value.vertices; + worldVerticesLength = value.worldVerticesLength; + regionUVs = value.regionUVs; + triangles = value.triangles; + HullLength = value.HullLength; + Edges = value.Edges; + Width = value.Width; + Height = value.Height; + } + } + } - // Nonessential. - public int[] Edges { get; set; } - public float Width { get; set; } - public float Height { get; set; } + // Nonessential. + public int[] Edges { get; set; } + public float Width { get; set; } + public float Height { get; set; } - public MeshAttachment (string name) - : base(name) { - } + public MeshAttachment(string name) + : base(name) + { + } - public void UpdateUVs () { - float u = RegionU, v = RegionV, width = RegionU2 - RegionU, height = RegionV2 - RegionV; - float[] regionUVs = this.regionUVs; - if (this.uvs == null || this.uvs.Length != regionUVs.Length) this.uvs = new float[regionUVs.Length]; - float[] uvs = this.uvs; - if (RegionRotate) { - for (int i = 0, n = uvs.Length; i < n; i += 2) { - uvs[i] = u + regionUVs[i + 1] * width; - uvs[i + 1] = v + height - regionUVs[i] * height; - } - } else { - for (int i = 0, n = uvs.Length; i < n; i += 2) { - uvs[i] = u + regionUVs[i] * width; - uvs[i + 1] = v + regionUVs[i + 1] * height; - } - } - } + public void UpdateUVs() + { + float u = RegionU, v = RegionV, width = RegionU2 - RegionU, height = RegionV2 - RegionV; + float[] regionUVs = this.regionUVs; + if (this.uvs == null || this.uvs.Length != regionUVs.Length) this.uvs = new float[regionUVs.Length]; + float[] uvs = this.uvs; + if (RegionRotate) + { + for (int i = 0, n = uvs.Length; i < n; i += 2) + { + uvs[i] = u + regionUVs[i + 1] * width; + uvs[i + 1] = v + height - regionUVs[i] * height; + } + } + else + { + for (int i = 0, n = uvs.Length; i < n; i += 2) + { + uvs[i] = u + regionUVs[i] * width; + uvs[i + 1] = v + regionUVs[i + 1] * height; + } + } + } - override public bool ApplyDeform (VertexAttachment sourceAttachment) { - return this == sourceAttachment || (inheritDeform && parentMesh == sourceAttachment); - } - } + override public bool ApplyDeform(VertexAttachment sourceAttachment) + { + return this == sourceAttachment || (inheritDeform && parentMesh == sourceAttachment); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Attachments/PathAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Attachments/PathAttachment.cs index 2222126..0aca448 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Attachments/PathAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Attachments/PathAttachment.cs @@ -29,20 +29,22 @@ *****************************************************************************/ using System; -using System.Collections.Generic; -namespace Spine3_4_02 { - public class PathAttachment : VertexAttachment { - internal float[] lengths; - internal bool closed, constantSpeed; +namespace Spine3_4_02 +{ + public class PathAttachment : VertexAttachment + { + internal float[] lengths; + internal bool closed, constantSpeed; - /// The length in the setup pose from the start of the path to the end of each curve. - public float[] Lengths { get { return lengths; } set { lengths = value; } } - public bool Closed { get { return closed; } set { closed = value; } } - public bool ConstantSpeed { get { return constantSpeed; } set { constantSpeed = value; } } + /// The length in the setup pose from the start of the path to the end of each curve. + public float[] Lengths { get { return lengths; } set { lengths = value; } } + public bool Closed { get { return closed; } set { closed = value; } } + public bool ConstantSpeed { get { return constantSpeed; } set { constantSpeed = value; } } - public PathAttachment (String name) - : base(name) { - } - } + public PathAttachment(String name) + : base(name) + { + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Attachments/RegionAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Attachments/RegionAttachment.cs index 80f3399..92716e1 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Attachments/RegionAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Attachments/RegionAttachment.cs @@ -30,123 +30,132 @@ using System; -namespace Spine3_4_02 { - /// Attachment that displays a texture region. - public class RegionAttachment : Attachment { - public const int X1 = 0; - public const int Y1 = 1; - public const int X2 = 2; - public const int Y2 = 3; - public const int X3 = 4; - public const int Y3 = 5; - public const int X4 = 6; - public const int Y4 = 7; +namespace Spine3_4_02 +{ + /// Attachment that displays a texture region. + public class RegionAttachment : Attachment + { + public const int X1 = 0; + public const int Y1 = 1; + public const int X2 = 2; + public const int Y2 = 3; + public const int X3 = 4; + public const int Y3 = 5; + public const int X4 = 6; + public const int Y4 = 7; - internal float x, y, rotation, scaleX = 1, scaleY = 1, width, height; - internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; - internal float[] offset = new float[8], uvs = new float[8]; - internal float r = 1, g = 1, b = 1, a = 1; + internal float x, y, rotation, scaleX = 1, scaleY = 1, width, height; + internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; + internal float[] offset = new float[8], uvs = new float[8]; + internal float r = 1, g = 1, b = 1, a = 1; - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public float Rotation { get { return rotation; } set { rotation = value; } } - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - public float ScaleY { get { return scaleY; } set { scaleY = value; } } - public float Width { get { return width; } set { width = value; } } - public float Height { get { return height; } set { height = value; } } + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public float Rotation { get { return rotation; } set { rotation = value; } } + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + public float Width { get { return width; } set { width = value; } } + public float Height { get { return height; } set { height = value; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } - public String Path { get; set; } - public Object RendererObject { get; set; } - public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } - public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. - public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } - public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. - public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } - public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. + public String Path { get; set; } + public Object RendererObject { get; set; } + public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } + public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. + public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } + public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. + public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } + public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. - public float[] Offset { get { return offset; } } - public float[] UVs { get { return uvs; } } + public float[] Offset { get { return offset; } } + public float[] UVs { get { return uvs; } } - public RegionAttachment (string name) - : base(name) { - } + public RegionAttachment(string name) + : base(name) + { + } - public void SetUVs (float u, float v, float u2, float v2, bool rotate) { - float[] uvs = this.uvs; - if (rotate) { - uvs[X2] = u; - uvs[Y2] = v2; - uvs[X3] = u; - uvs[Y3] = v; - uvs[X4] = u2; - uvs[Y4] = v; - uvs[X1] = u2; - uvs[Y1] = v2; - } else { - uvs[X1] = u; - uvs[Y1] = v2; - uvs[X2] = u; - uvs[Y2] = v; - uvs[X3] = u2; - uvs[Y3] = v; - uvs[X4] = u2; - uvs[Y4] = v2; - } - } + public void SetUVs(float u, float v, float u2, float v2, bool rotate) + { + float[] uvs = this.uvs; + if (rotate) + { + uvs[X2] = u; + uvs[Y2] = v2; + uvs[X3] = u; + uvs[Y3] = v; + uvs[X4] = u2; + uvs[Y4] = v; + uvs[X1] = u2; + uvs[Y1] = v2; + } + else + { + uvs[X1] = u; + uvs[Y1] = v2; + uvs[X2] = u; + uvs[Y2] = v; + uvs[X3] = u2; + uvs[Y3] = v; + uvs[X4] = u2; + uvs[Y4] = v2; + } + } - public void UpdateOffset () { - float width = this.width; - float height = this.height; - float scaleX = this.scaleX; - float scaleY = this.scaleY; - float regionScaleX = width / regionOriginalWidth * scaleX; - float regionScaleY = height / regionOriginalHeight * scaleY; - float localX = -width / 2 * scaleX + regionOffsetX * regionScaleX; - float localY = -height / 2 * scaleY + regionOffsetY * regionScaleY; - float localX2 = localX + regionWidth * regionScaleX; - float localY2 = localY + regionHeight * regionScaleY; - float rotation = this.rotation; - float cos = MathUtils.CosDeg(rotation); - float sin = MathUtils.SinDeg(rotation); - float x = this.x; - float y = this.y; - 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; - float[] offset = this.offset; - offset[X1] = localXCos - localYSin; - offset[Y1] = localYCos + localXSin; - offset[X2] = localXCos - localY2Sin; - offset[Y2] = localY2Cos + localXSin; - offset[X3] = localX2Cos - localY2Sin; - offset[Y3] = localY2Cos + localX2Sin; - offset[X4] = localX2Cos - localYSin; - offset[Y4] = localYCos + localX2Sin; - } + public void UpdateOffset() + { + float width = this.width; + float height = this.height; + float scaleX = this.scaleX; + float scaleY = this.scaleY; + float regionScaleX = width / regionOriginalWidth * scaleX; + float regionScaleY = height / regionOriginalHeight * scaleY; + float localX = -width / 2 * scaleX + regionOffsetX * regionScaleX; + float localY = -height / 2 * scaleY + regionOffsetY * regionScaleY; + float localX2 = localX + regionWidth * regionScaleX; + float localY2 = localY + regionHeight * regionScaleY; + float rotation = this.rotation; + float cos = MathUtils.CosDeg(rotation); + float sin = MathUtils.SinDeg(rotation); + float x = this.x; + float y = this.y; + 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; + float[] offset = this.offset; + offset[X1] = localXCos - localYSin; + offset[Y1] = localYCos + localXSin; + offset[X2] = localXCos - localY2Sin; + offset[Y2] = localY2Cos + localXSin; + offset[X3] = localX2Cos - localY2Sin; + offset[Y3] = localY2Cos + localX2Sin; + offset[X4] = localX2Cos - localYSin; + offset[Y4] = localYCos + localX2Sin; + } - public void ComputeWorldVertices (Bone bone, float[] worldVertices) { - Skeleton skeleton = bone.skeleton; - float x = skeleton.x + bone.worldX, y = skeleton.y + bone.worldY; - float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - float[] offset = this.offset; - worldVertices[X1] = offset[X1] * a + offset[Y1] * b + x; - worldVertices[Y1] = offset[X1] * c + offset[Y1] * d + y; - worldVertices[X2] = offset[X2] * a + offset[Y2] * b + x; - worldVertices[Y2] = offset[X2] * c + offset[Y2] * d + y; - worldVertices[X3] = offset[X3] * a + offset[Y3] * b + x; - worldVertices[Y3] = offset[X3] * c + offset[Y3] * d + y; - worldVertices[X4] = offset[X4] * a + offset[Y4] * b + x; - worldVertices[Y4] = offset[X4] * c + offset[Y4] * d + y; - } - } + public void ComputeWorldVertices(Bone bone, float[] worldVertices) + { + Skeleton skeleton = bone.skeleton; + float x = skeleton.x + bone.worldX, y = skeleton.y + bone.worldY; + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + float[] offset = this.offset; + worldVertices[X1] = offset[X1] * a + offset[Y1] * b + x; + worldVertices[Y1] = offset[X1] * c + offset[Y1] * d + y; + worldVertices[X2] = offset[X2] * a + offset[Y2] * b + x; + worldVertices[Y2] = offset[X2] * c + offset[Y2] * d + y; + worldVertices[X3] = offset[X3] * a + offset[Y3] * b + x; + worldVertices[Y3] = offset[X3] * c + offset[Y3] * d + y; + worldVertices[X4] = offset[X4] * a + offset[Y4] * b + x; + worldVertices[Y4] = offset[X4] * c + offset[Y4] * d + y; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Attachments/VertexAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Attachments/VertexAttachment.cs index d3f12bf..d0a0bd1 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Attachments/VertexAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Attachments/VertexAttachment.cs @@ -29,89 +29,104 @@ *****************************************************************************/ using System; -using System.Collections.Generic; -namespace Spine3_4_02 { - /// >An attachment with vertices that are transformed by one or more bones and can be deformed by a slot's vertices. - public class VertexAttachment : Attachment { - internal int[] bones; - internal float[] vertices; - internal int worldVerticesLength; +namespace Spine3_4_02 +{ + /// >An attachment with vertices that are transformed by one or more bones and can be deformed by a slot's vertices. + public class VertexAttachment : Attachment + { + internal int[] bones; + internal float[] vertices; + internal int worldVerticesLength; - public int[] Bones { get { return bones; } set { bones = value; } } - public float[] Vertices { get { return vertices; } set { vertices = value; } } - public int WorldVerticesLength { get { return worldVerticesLength; } set { worldVerticesLength = value; } } + public int[] Bones { get { return bones; } set { bones = value; } } + public float[] Vertices { get { return vertices; } set { vertices = value; } } + public int WorldVerticesLength { get { return worldVerticesLength; } set { worldVerticesLength = value; } } - public VertexAttachment (String name) - : base(name) { - } + public VertexAttachment(String name) + : base(name) + { + } - public void ComputeWorldVertices (Slot slot, float[] worldVertices) { - ComputeWorldVertices(slot, 0, worldVerticesLength, worldVertices, 0); - } + public void ComputeWorldVertices(Slot slot, float[] worldVertices) + { + ComputeWorldVertices(slot, 0, worldVerticesLength, worldVertices, 0); + } - public void ComputeWorldVertices (Slot slot, int start, int count, float[] worldVertices, int offset) { - count += offset; - Skeleton skeleton = slot.Skeleton; - float x = skeleton.x, y = skeleton.y; - var deformArray = slot.attachmentVertices; - float[] vertices = this.vertices; - int[] bones = this.bones; - if (bones == null) { - if (deformArray.Count > 0) vertices = deformArray.Items; - Bone bone = slot.bone; - x += bone.worldX; - y += bone.worldY; - float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - for (int vv = start, w = offset; w < count; vv += 2, w += 2) { - float vx = vertices[vv], 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 (int i = 0; i < start; i += 2) { - int n = bones[v]; - v += n + 1; - skip += n; - } - Bone[] skeletonBones = skeleton.Bones.Items; - if (deformArray.Count == 0) { - for (int w = offset, b = skip * 3; w < count; w += 2) { - float wx = x, wy = y; - int n = bones[v++]; - n += v; - for (; v < n; v++, b += 3) { - Bone bone = skeletonBones[bones[v]]; - float vx = vertices[b], vy = vertices[b + 1], 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 { - float[] deform = deformArray.Items; - for (int w = offset, b = skip * 3, f = skip << 1; w < count; w += 2) { - float wx = x, wy = y; - int n = bones[v++]; - n += v; - for (; v < n; v++, b += 3, f += 2) { - Bone bone = skeletonBones[bones[v]]; - float vx = vertices[b] + deform[f], vy = vertices[b + 1] + deform[f + 1], 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; - } - } - } + public void ComputeWorldVertices(Slot slot, int start, int count, float[] worldVertices, int offset) + { + count += offset; + Skeleton skeleton = slot.Skeleton; + float x = skeleton.x, y = skeleton.y; + var deformArray = slot.attachmentVertices; + float[] vertices = this.vertices; + int[] bones = this.bones; + if (bones == null) + { + if (deformArray.Count > 0) vertices = deformArray.Items; + Bone bone = slot.bone; + x += bone.worldX; + y += bone.worldY; + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + for (int vv = start, w = offset; w < count; vv += 2, w += 2) + { + float vx = vertices[vv], 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 (int i = 0; i < start; i += 2) + { + int n = bones[v]; + v += n + 1; + skip += n; + } + Bone[] skeletonBones = skeleton.Bones.Items; + if (deformArray.Count == 0) + { + for (int w = offset, b = skip * 3; w < count; w += 2) + { + float wx = x, wy = y; + int n = bones[v++]; + n += v; + for (; v < n; v++, b += 3) + { + Bone bone = skeletonBones[bones[v]]; + float vx = vertices[b], vy = vertices[b + 1], 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 + { + float[] deform = deformArray.Items; + for (int w = offset, b = skip * 3, f = skip << 1; w < count; w += 2) + { + float wx = x, wy = y; + int n = bones[v++]; + n += v; + for (; v < n; v++, b += 3, f += 2) + { + Bone bone = skeletonBones[bones[v]]; + float vx = vertices[b] + deform[f], vy = vertices[b + 1] + deform[f + 1], 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; + } + } + } - /// Returns true if a deform originally applied to the specified attachment should be applied to this attachment. - virtual public bool ApplyDeform (VertexAttachment sourceAttachment) { - return this == sourceAttachment; - } - } + /// Returns true if a deform originally applied to the specified attachment should be applied to this attachment. + virtual public bool ApplyDeform(VertexAttachment sourceAttachment) + { + return this == sourceAttachment; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/BlendMode.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/BlendMode.cs index 5215186..0b0eaa1 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/BlendMode.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/BlendMode.cs @@ -28,8 +28,10 @@ * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine3_4_02 { - public enum BlendMode { - normal, additive, multiply, screen - } +namespace Spine3_4_02 +{ + public enum BlendMode + { + normal, additive, multiply, screen + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Bone.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Bone.cs index 5069f1f..75db631 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Bone.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Bone.cs @@ -30,285 +30,320 @@ using System; -namespace Spine3_4_02 { - public class Bone : IUpdatable { - static public bool yDown; +namespace Spine3_4_02 +{ + public class Bone : IUpdatable + { + static public bool yDown; - internal BoneData data; - internal Skeleton skeleton; - internal Bone parent; - internal ExposedList children = new ExposedList(); - internal float x, y, rotation, scaleX, scaleY, shearX, shearY; - internal float appliedRotation; + internal BoneData data; + internal Skeleton skeleton; + internal Bone parent; + internal ExposedList children = new ExposedList(); + internal float x, y, rotation, scaleX, scaleY, shearX, shearY; + internal float appliedRotation; - internal float a, b, worldX; - internal float c, d, worldY; - internal float worldSignX, worldSignY; + internal float a, b, worldX; + internal float c, d, worldY; + internal float worldSignX, worldSignY; - internal bool sorted; + internal bool sorted; - public BoneData Data { get { return data; } } - public Skeleton Skeleton { get { return skeleton; } } - public Bone Parent { get { return parent; } } - public ExposedList Children { get { return children; } } - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public float Rotation { get { return rotation; } set { rotation = value; } } - /// The rotation, as calculated by any constraints. - public float AppliedRotation { get { return appliedRotation; } set { appliedRotation = value; } } - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - public float ScaleY { get { return scaleY; } set { scaleY = value; } } - public float ShearX { get { return shearX; } set { shearX = value; } } - public float ShearY { get { return shearY; } set { shearY = value; } } + public BoneData Data { get { return data; } } + public Skeleton Skeleton { get { return skeleton; } } + public Bone Parent { get { return parent; } } + public ExposedList Children { get { return children; } } + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public float Rotation { get { return rotation; } set { rotation = value; } } + /// The rotation, as calculated by any constraints. + public float AppliedRotation { get { return appliedRotation; } set { appliedRotation = value; } } + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + public float ShearX { get { return shearX; } set { shearX = value; } } + public float ShearY { get { return shearY; } set { shearY = value; } } - public float A { get { return a; } } - public float B { get { return b; } } - public float C { get { return c; } } - public float D { get { return d; } } - public float WorldX { get { return worldX; } } - public float WorldY { get { return worldY; } } - public float WorldSignX { get { return worldSignX; } } - public float WorldSignY { get { return worldSignY; } } - public float WorldRotationX { get { return MathUtils.Atan2(c, a) * MathUtils.radDeg; } } - public float WorldRotationY { get { return MathUtils.Atan2(d, b) * MathUtils.radDeg; } } - public float WorldScaleX { get { return (float)Math.Sqrt(a * a + c * c) * worldSignX; } } - public float WorldScaleY { get { return (float)Math.Sqrt(b * b + d * d) * worldSignY; } } + public float A { get { return a; } } + public float B { get { return b; } } + public float C { get { return c; } } + public float D { get { return d; } } + public float WorldX { get { return worldX; } } + public float WorldY { get { return worldY; } } + public float WorldSignX { get { return worldSignX; } } + public float WorldSignY { get { return worldSignY; } } + public float WorldRotationX { get { return MathUtils.Atan2(c, a) * MathUtils.radDeg; } } + public float WorldRotationY { get { return MathUtils.Atan2(d, b) * MathUtils.radDeg; } } + public float WorldScaleX { get { return (float)Math.Sqrt(a * a + c * c) * worldSignX; } } + public float WorldScaleY { get { return (float)Math.Sqrt(b * b + d * d) * worldSignY; } } - /// May be null. - public Bone (BoneData data, Skeleton skeleton, Bone parent) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.data = data; - this.skeleton = skeleton; - this.parent = parent; - SetToSetupPose(); - } + /// May be null. + public Bone(BoneData data, Skeleton skeleton, Bone parent) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + this.skeleton = skeleton; + this.parent = parent; + SetToSetupPose(); + } - /// Same as . This method exists for Bone to implement . - public void Update () { - UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); - } + /// Same as . This method exists for Bone to implement . + public void Update() + { + UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); + } - /// Computes the world transform using the parent bone and this bone's local transform. - public void UpdateWorldTransform () { - UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); - } + /// Computes the world transform using the parent bone and this bone's local transform. + public void UpdateWorldTransform() + { + UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); + } - /// Computes the world transform using the parent bone and the specified local transform. - public void UpdateWorldTransform (float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY) { - appliedRotation = rotation; + /// Computes the world transform using the parent bone and the specified local transform. + public void UpdateWorldTransform(float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY) + { + appliedRotation = rotation; - float rotationY = rotation + 90 + shearY; - float la = MathUtils.CosDeg(rotation + shearX) * scaleX, lb = MathUtils.CosDeg(rotationY) * scaleY; - float lc = MathUtils.SinDeg(rotation + shearX) * scaleX, ld = MathUtils.SinDeg(rotationY) * scaleY; + float rotationY = rotation + 90 + shearY; + float la = MathUtils.CosDeg(rotation + shearX) * scaleX, lb = MathUtils.CosDeg(rotationY) * scaleY; + float lc = MathUtils.SinDeg(rotation + shearX) * scaleX, ld = MathUtils.SinDeg(rotationY) * scaleY; - Bone parent = this.parent; - if (parent == null) { // Root bone. - Skeleton skeleton = this.skeleton; - if (skeleton.flipX) { - x = -x; - la = -la; - lb = -lb; - } - if (skeleton.flipY != yDown) { - y = -y; - lc = -lc; - ld = -ld; - } - a = la; - b = lb; - c = lc; - d = ld; - worldX = x; - worldY = y; - worldSignX = Math.Sign(scaleX); - worldSignY = Math.Sign(scaleY); - return; - } + Bone parent = this.parent; + if (parent == null) + { // Root bone. + Skeleton skeleton = this.skeleton; + if (skeleton.flipX) + { + x = -x; + la = -la; + lb = -lb; + } + if (skeleton.flipY != yDown) + { + y = -y; + lc = -lc; + ld = -ld; + } + a = la; + b = lb; + c = lc; + d = ld; + worldX = x; + worldY = y; + worldSignX = Math.Sign(scaleX); + worldSignY = Math.Sign(scaleY); + return; + } - float 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; - worldSignX = parent.worldSignX * Math.Sign(scaleX); - worldSignY = parent.worldSignY * Math.Sign(scaleY); + float 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; + worldSignX = parent.worldSignX * Math.Sign(scaleX); + worldSignY = parent.worldSignY * Math.Sign(scaleY); - if (data.inheritRotation && data.inheritScale) { - a = pa * la + pb * lc; - b = pa * lb + pb * ld; - c = pc * la + pd * lc; - d = pc * lb + pd * ld; - } else { - if (data.inheritRotation) { // No scale inheritance. - pa = 1; - pb = 0; - pc = 0; - pd = 1; - do { - float cos = MathUtils.CosDeg(parent.appliedRotation), sin = MathUtils.SinDeg(parent.appliedRotation); - float temp = pa * cos + pb * sin; - pb = pb * cos - pa * sin; - pa = temp; - temp = pc * cos + pd * sin; - pd = pd * cos - pc * sin; - pc = temp; + if (data.inheritRotation && data.inheritScale) + { + a = pa * la + pb * lc; + b = pa * lb + pb * ld; + c = pc * la + pd * lc; + d = pc * lb + pd * ld; + } + else + { + if (data.inheritRotation) + { // No scale inheritance. + pa = 1; + pb = 0; + pc = 0; + pd = 1; + do + { + float cos = MathUtils.CosDeg(parent.appliedRotation), sin = MathUtils.SinDeg(parent.appliedRotation); + float temp = pa * cos + pb * sin; + pb = pb * cos - pa * sin; + pa = temp; + temp = pc * cos + pd * sin; + pd = pd * cos - pc * sin; + pc = temp; - if (!parent.data.inheritRotation) break; - parent = parent.parent; - } while (parent != null); - a = pa * la + pb * lc; - b = pa * lb + pb * ld; - c = pc * la + pd * lc; - d = pc * lb + pd * ld; - } else if (data.inheritScale) { // No rotation inheritance. - pa = 1; - pb = 0; - pc = 0; - pd = 1; - do { - float cos = MathUtils.CosDeg(parent.appliedRotation), sin = MathUtils.SinDeg(parent.appliedRotation); - float psx = parent.scaleX, psy = parent.scaleY; - float za = cos * psx, zb = sin * psy, zc = sin * psx, zd = cos * psy; - float temp = pa * za + pb * zc; - pb = pb * zd - pa * zb; - pa = temp; - temp = pc * za + pd * zc; - pd = pd * zd - pc * zb; - pc = temp; + if (!parent.data.inheritRotation) break; + parent = parent.parent; + } while (parent != null); + a = pa * la + pb * lc; + b = pa * lb + pb * ld; + c = pc * la + pd * lc; + d = pc * lb + pd * ld; + } + else if (data.inheritScale) + { // No rotation inheritance. + pa = 1; + pb = 0; + pc = 0; + pd = 1; + do + { + float cos = MathUtils.CosDeg(parent.appliedRotation), sin = MathUtils.SinDeg(parent.appliedRotation); + float psx = parent.scaleX, psy = parent.scaleY; + float za = cos * psx, zb = sin * psy, zc = sin * psx, zd = cos * psy; + float temp = pa * za + pb * zc; + pb = pb * zd - pa * zb; + pa = temp; + temp = pc * za + pd * zc; + pd = pd * zd - pc * zb; + pc = temp; - if (psx >= 0) sin = -sin; - temp = pa * cos + pb * sin; - pb = pb * cos - pa * sin; - pa = temp; - temp = pc * cos + pd * sin; - pd = pd * cos - pc * sin; - pc = temp; + if (psx >= 0) sin = -sin; + temp = pa * cos + pb * sin; + pb = pb * cos - pa * sin; + pa = temp; + temp = pc * cos + pd * sin; + pd = pd * cos - pc * sin; + pc = temp; - if (!parent.data.inheritScale) break; - parent = parent.parent; - } while (parent != null); - a = pa * la + pb * lc; - b = pa * lb + pb * ld; - c = pc * la + pd * lc; - d = pc * lb + pd * ld; - } else { - a = la; - b = lb; - c = lc; - d = ld; - } - if (skeleton.flipX) { - a = -a; - b = -b; - } - if (skeleton.flipY != yDown) { - c = -c; - d = -d; - } - } - } + if (!parent.data.inheritScale) break; + parent = parent.parent; + } while (parent != null); + a = pa * la + pb * lc; + b = pa * lb + pb * ld; + c = pc * la + pd * lc; + d = pc * lb + pd * ld; + } + else + { + a = la; + b = lb; + c = lc; + d = ld; + } + if (skeleton.flipX) + { + a = -a; + b = -b; + } + if (skeleton.flipY != yDown) + { + c = -c; + d = -d; + } + } + } - public void SetToSetupPose () { - BoneData data = this.data; - x = data.x; - y = data.y; - rotation = data.rotation; - scaleX = data.scaleX; - scaleY = data.scaleY; - shearX = data.shearX; - shearY = data.shearY; - } + public void SetToSetupPose() + { + BoneData data = this.data; + x = data.x; + y = data.y; + rotation = data.rotation; + scaleX = data.scaleX; + scaleY = data.scaleY; + shearX = data.shearX; + shearY = data.shearY; + } - public float WorldToLocalRotationX { - get { - Bone parent = this.parent; - if (parent == null) return rotation; - float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, a = this.a, c = this.c; - return MathUtils.Atan2(pa * c - pc * a, pd * a - pb * c) * MathUtils.radDeg; - } - } + public float WorldToLocalRotationX + { + get + { + Bone parent = this.parent; + if (parent == null) return rotation; + float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, a = this.a, c = this.c; + return MathUtils.Atan2(pa * c - pc * a, pd * a - pb * c) * MathUtils.radDeg; + } + } - public float WorldToLocalRotationY { - get { - Bone parent = this.parent; - if (parent == null) return rotation; - float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, b = this.b, d = this.d; - return MathUtils.Atan2(pa * d - pc * b, pd * b - pb * d) * MathUtils.radDeg; - } - } + public float WorldToLocalRotationY + { + get + { + Bone parent = this.parent; + if (parent == null) return rotation; + float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, b = this.b, d = this.d; + return MathUtils.Atan2(pa * d - pc * b, pd * b - pb * d) * MathUtils.radDeg; + } + } - public void RotateWorld (float degrees) { - float a = this.a, b = this.b, c = this.c, d = this.d; - float cos = MathUtils.CosDeg(degrees), sin = MathUtils.SinDeg(degrees); - this.a = cos * a - sin * c; - this.b = cos * b - sin * d; - this.c = sin * a + cos * c; - this.d = sin * b + cos * d; - } + public void RotateWorld(float degrees) + { + float a = this.a, b = this.b, c = this.c, d = this.d; + float cos = MathUtils.CosDeg(degrees), sin = MathUtils.SinDeg(degrees); + this.a = cos * a - sin * c; + this.b = cos * b - sin * d; + this.c = sin * a + cos * c; + this.d = sin * b + cos * d; + } - /// - /// Computes the local transform from the world transform. This can be useful to perform processing on the local transform - /// after the world transform has been modified directly (eg, by a constraint). - /// - /// Some redundant information is lost by the world transform, such as -1,-1 scale versus 180 rotation. The computed local - /// transform values may differ from the original values but are functionally the same. - /// - public void UpdateLocalTransform () { - Bone parent = this.parent; - if (parent == null) { - x = worldX; - y = worldY; - rotation = MathUtils.Atan2(c, a) * MathUtils.radDeg; - scaleX = (float)Math.Sqrt(a * a + c * c); - scaleY = (float)Math.Sqrt(b * b + d * d); - float det = a * d - b * c; - shearX = 0; - shearY = MathUtils.Atan2(a * b + c * d, det) * MathUtils.radDeg; - return; - } - float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d; - float pid = 1 / (pa * pd - pb * pc); - float dx = worldX - parent.worldX, dy = worldY - parent.worldY; - x = (dx * pd * pid - dy * pb * pid); - y = (dy * pa * pid - dx * pc * pid); - float ia = pid * pd; - float id = pid * pa; - float ib = pid * pb; - float ic = pid * pc; - float ra = ia * a - ib * c; - float rb = ia * b - ib * d; - float rc = id * c - ic * a; - float rd = id * d - ic * b; - shearX = 0; - scaleX = (float)Math.Sqrt(ra * ra + rc * rc); - if (scaleX > 0.0001f) { - float det = ra * rd - rb * rc; - scaleY = det / scaleX; - shearY = MathUtils.Atan2(ra * rb + rc * rd, det) * MathUtils.radDeg; - rotation = MathUtils.Atan2(rc, ra) * MathUtils.radDeg; - } else { - scaleX = 0; - scaleY = (float)Math.Sqrt(rb * rb + rd * rd); - shearY = 0; - rotation = 90 - MathUtils.Atan2(rd, rb) * MathUtils.radDeg; - } - appliedRotation = rotation; - } + /// + /// Computes the local transform from the world transform. This can be useful to perform processing on the local transform + /// after the world transform has been modified directly (eg, by a constraint). + /// + /// Some redundant information is lost by the world transform, such as -1,-1 scale versus 180 rotation. The computed local + /// transform values may differ from the original values but are functionally the same. + /// + public void UpdateLocalTransform() + { + Bone parent = this.parent; + if (parent == null) + { + x = worldX; + y = worldY; + rotation = MathUtils.Atan2(c, a) * MathUtils.radDeg; + scaleX = (float)Math.Sqrt(a * a + c * c); + scaleY = (float)Math.Sqrt(b * b + d * d); + float det = a * d - b * c; + shearX = 0; + shearY = MathUtils.Atan2(a * b + c * d, det) * MathUtils.radDeg; + return; + } + float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d; + float pid = 1 / (pa * pd - pb * pc); + float dx = worldX - parent.worldX, dy = worldY - parent.worldY; + x = (dx * pd * pid - dy * pb * pid); + y = (dy * pa * pid - dx * pc * pid); + float ia = pid * pd; + float id = pid * pa; + float ib = pid * pb; + float ic = pid * pc; + float ra = ia * a - ib * c; + float rb = ia * b - ib * d; + float rc = id * c - ic * a; + float rd = id * d - ic * b; + shearX = 0; + scaleX = (float)Math.Sqrt(ra * ra + rc * rc); + if (scaleX > 0.0001f) + { + float det = ra * rd - rb * rc; + scaleY = det / scaleX; + shearY = MathUtils.Atan2(ra * rb + rc * rd, det) * MathUtils.radDeg; + rotation = MathUtils.Atan2(rc, ra) * MathUtils.radDeg; + } + else + { + scaleX = 0; + scaleY = (float)Math.Sqrt(rb * rb + rd * rd); + shearY = 0; + rotation = 90 - MathUtils.Atan2(rd, rb) * MathUtils.radDeg; + } + appliedRotation = rotation; + } - public void WorldToLocal (float worldX, float worldY, out float localX, out float localY) { - float a = this.a, b = this.b, c = this.c, d = this.d; - float invDet = 1 / (a * d - b * c); - float x = worldX - this.worldX, y = worldY - this.worldY; - localX = (x * d * invDet - y * b * invDet); - localY = (y * a * invDet - x * c * invDet); - } + public void WorldToLocal(float worldX, float worldY, out float localX, out float localY) + { + float a = this.a, b = this.b, c = this.c, d = this.d; + float invDet = 1 / (a * d - b * c); + float x = worldX - this.worldX, y = worldY - this.worldY; + localX = (x * d * invDet - y * b * invDet); + localY = (y * a * invDet - x * c * invDet); + } - public void LocalToWorld (float localX, float localY, out float worldX, out float worldY) { - worldX = localX * a + localY * b + this.worldX; - worldY = localX * c + localY * d + this.worldY; - } + public void LocalToWorld(float localX, float localY, out float worldX, out float worldY) + { + worldX = localX * a + localY * b + this.worldX; + worldY = localX * c + localY * d + this.worldY; + } - override public String ToString () { - return data.name; - } - } + override public String ToString() + { + return data.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/BoneData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/BoneData.cs index 127370a..06aac76 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/BoneData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/BoneData.cs @@ -30,41 +30,45 @@ using System; -namespace Spine3_4_02 { - public class BoneData { - internal int index; - internal String name; - internal BoneData parent; - internal float length; - internal float x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY; - internal bool inheritRotation = true, inheritScale = true; +namespace Spine3_4_02 +{ + public class BoneData + { + internal int index; + internal String name; + internal BoneData parent; + internal float length; + internal float x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY; + internal bool inheritRotation = true, inheritScale = true; - /// May be null. - public int Index { get { return index; } } - public String Name { get { return name; } } - public BoneData Parent { get { return parent; } } - public float Length { get { return length; } set { length = value; } } - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public float Rotation { get { return rotation; } set { rotation = value; } } - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - public float ScaleY { get { return scaleY; } set { scaleY = value; } } - public float ShearX { get { return shearX; } set { shearX = value; } } - public float ShearY { get { return shearY; } set { shearY = value; } } - public bool InheritRotation { get { return inheritRotation; } set { inheritRotation = value; } } - public bool InheritScale { get { return inheritScale; } set { inheritScale = value; } } + /// May be null. + public int Index { get { return index; } } + public String Name { get { return name; } } + public BoneData Parent { get { return parent; } } + public float Length { get { return length; } set { length = value; } } + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public float Rotation { get { return rotation; } set { rotation = value; } } + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + public float ShearX { get { return shearX; } set { shearX = value; } } + public float ShearY { get { return shearY; } set { shearY = value; } } + public bool InheritRotation { get { return inheritRotation; } set { inheritRotation = value; } } + public bool InheritScale { get { return inheritScale; } set { inheritScale = value; } } - /// May be null. - public BoneData (int index, String name, BoneData parent) { - if (index < 0) throw new ArgumentException("index must be >= 0", "index"); - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.index = index; - this.name = name; - this.parent = parent; - } + /// May be null. + public BoneData(int index, String name, BoneData parent) + { + if (index < 0) throw new ArgumentException("index must be >= 0", "index"); + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.index = index; + this.name = name; + this.parent = parent; + } - override public String ToString () { - return name; - } - } + override public String ToString() + { + return name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Event.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Event.cs index e98262a..7994604 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Event.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Event.cs @@ -30,22 +30,26 @@ using System; -namespace Spine3_4_02 { - public class Event { - public EventData Data { get; private set; } - public int Int { get; set; } - public float Float { get; set; } - public String String { get; set; } - public float Time { get; private set; } +namespace Spine3_4_02 +{ + public class Event + { + public EventData Data { get; private set; } + public int Int { get; set; } + public float Float { get; set; } + public String String { get; set; } + public float Time { get; private set; } - public Event (float time, EventData data) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - Time = time; - Data = data; - } + public Event(float time, EventData data) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + Time = time; + Data = data; + } - override public String ToString () { - return Data.Name; - } - } + override public String ToString() + { + return Data.Name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/EventData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/EventData.cs index d99a4ae..2b77a80 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/EventData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/EventData.cs @@ -30,22 +30,26 @@ using System; -namespace Spine3_4_02 { - public class EventData { - internal String name; +namespace Spine3_4_02 +{ + public class EventData + { + internal String name; - public String Name { get { return name; } } - public int Int { get; set; } - public float Float { get; set; } - public String String { get; set; } + public String Name { get { return name; } } + public int Int { get; set; } + public float Float { get; set; } + public String String { get; set; } - public EventData (String name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; - } + public EventData(String name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + } - override public String ToString () { - return Name; - } - } + override public String ToString() + { + return Name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/ExposedList.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/ExposedList.cs index ba74201..109c33c 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/ExposedList.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/ExposedList.cs @@ -35,555 +35,645 @@ using System.Collections.Generic; using System.Diagnostics; -namespace Spine3_4_02 { - [Serializable] - [DebuggerDisplay("Count={Count}")] - public class ExposedList : IEnumerable { - public T[] Items; - public int Count; - private const int DefaultCapacity = 4; - private static readonly T[] EmptyArray = new T[0]; - private int version; - - public ExposedList () { - Items = EmptyArray; - } - - public ExposedList (IEnumerable collection) { - CheckCollection(collection); - - // initialize to needed size (if determinable) - ICollection c = collection as ICollection; - if (c == null) { - Items = EmptyArray; - AddEnumerable(collection); - } else { - Items = new T[c.Count]; - AddCollection(c); - } - } - - public ExposedList (int capacity) { - if (capacity < 0) - throw new ArgumentOutOfRangeException("capacity"); - Items = new T[capacity]; - } - - internal ExposedList (T[] data, int size) { - Items = data; - Count = size; - } - - public void Add (T item) { - // If we check to see if we need to grow before trying to grow - // we can speed things up by 25% - if (Count == Items.Length) - GrowIfNeeded(1); - Items[Count++] = item; - version++; - } - - public void GrowIfNeeded (int newCount) { - int minimumSize = Count + newCount; - if (minimumSize > Items.Length) - Capacity = Math.Max(Math.Max(Capacity * 2, DefaultCapacity), minimumSize); - } - - public ExposedList Resize (int newSize) { - if (newSize > Items.Length) Array.Resize(ref Items, newSize); - Count = newSize; - return this; - } - - private void CheckRange (int idx, int count) { - if (idx < 0) - throw new ArgumentOutOfRangeException("index"); - - if (count < 0) - throw new ArgumentOutOfRangeException("count"); - - if ((uint)idx + (uint)count > (uint)Count) - throw new ArgumentException("index and count exceed length of list"); - } - - private void AddCollection (ICollection collection) { - int collectionCount = collection.Count; - if (collectionCount == 0) - return; - - GrowIfNeeded(collectionCount); - collection.CopyTo(Items, Count); - Count += collectionCount; - } - - private void AddEnumerable (IEnumerable enumerable) { - foreach (T t in enumerable) { - Add(t); - } - } - - public void AddRange (IEnumerable collection) { - CheckCollection(collection); - - ICollection c = collection as ICollection; - if (c != null) - AddCollection(c); - else - AddEnumerable(collection); - version++; - } - - public int BinarySearch (T item) { - return Array.BinarySearch(Items, 0, Count, item); - } - - public int BinarySearch (T item, IComparer comparer) { - return Array.BinarySearch(Items, 0, Count, item, comparer); - } - - public int BinarySearch (int index, int count, T item, IComparer comparer) { - CheckRange(index, count); - return Array.BinarySearch(Items, index, count, item, comparer); - } - - public void Clear (bool clearArray = true) { - if (clearArray) - Array.Clear(Items, 0, Items.Length); - - Count = 0; - version++; - } - - public bool Contains (T item) { - return Array.IndexOf(Items, item, 0, Count) != -1; - } - - public ExposedList ConvertAll (Converter converter) { - if (converter == null) - throw new ArgumentNullException("converter"); - ExposedList u = new ExposedList(Count); - for (int i = 0; i < Count; i++) - u.Items[i] = converter(Items[i]); - - u.Count = Count; - return u; - } - - public void CopyTo (T[] array) { - Array.Copy(Items, 0, array, 0, Count); - } - - public void CopyTo (T[] array, int arrayIndex) { - Array.Copy(Items, 0, array, arrayIndex, Count); - } - - public void CopyTo (int index, T[] array, int arrayIndex, int count) { - CheckRange(index, count); - Array.Copy(Items, index, array, arrayIndex, count); - } - - public bool Exists (Predicate match) { - CheckMatch(match); - return GetIndex(0, Count, match) != -1; - } - - public T Find (Predicate match) { - CheckMatch(match); - int i = GetIndex(0, Count, match); - return (i != -1) ? Items[i] : default(T); - } - - private static void CheckMatch (Predicate match) { - if (match == null) - throw new ArgumentNullException("match"); - } - - public ExposedList FindAll (Predicate match) { - CheckMatch(match); - return FindAllList(match); - } - - private ExposedList FindAllList (Predicate match) { - ExposedList results = new ExposedList(); - for (int i = 0; i < Count; i++) - if (match(Items[i])) - results.Add(Items[i]); - - return results; - } - - public int FindIndex (Predicate match) { - CheckMatch(match); - return GetIndex(0, Count, match); - } - - public int FindIndex (int startIndex, Predicate match) { - CheckMatch(match); - CheckIndex(startIndex); - return GetIndex(startIndex, Count - startIndex, match); - } - - public int FindIndex (int startIndex, int count, Predicate match) { - CheckMatch(match); - CheckRange(startIndex, count); - return GetIndex(startIndex, count, match); - } - - private int GetIndex (int startIndex, int count, Predicate match) { - int end = startIndex + count; - for (int i = startIndex; i < end; i++) - if (match(Items[i])) - return i; - - return -1; - } - - public T FindLast (Predicate match) { - CheckMatch(match); - int i = GetLastIndex(0, Count, match); - return i == -1 ? default(T) : Items[i]; - } - - public int FindLastIndex (Predicate match) { - CheckMatch(match); - return GetLastIndex(0, Count, match); - } - - public int FindLastIndex (int startIndex, Predicate match) { - CheckMatch(match); - CheckIndex(startIndex); - return GetLastIndex(0, startIndex + 1, match); - } - - public int FindLastIndex (int startIndex, int count, Predicate match) { - CheckMatch(match); - int start = startIndex - count + 1; - CheckRange(start, count); - return GetLastIndex(start, count, match); - } - - private int GetLastIndex (int startIndex, int count, Predicate match) { - // unlike FindLastIndex, takes regular params for search range - for (int i = startIndex + count; i != startIndex; ) - if (match(Items[--i])) - return i; - return -1; - } - - public void ForEach (Action action) { - if (action == null) - throw new ArgumentNullException("action"); - for (int i = 0; i < Count; i++) - action(Items[i]); - } - - public Enumerator GetEnumerator () { - return new Enumerator(this); - } - - public ExposedList GetRange (int index, int count) { - CheckRange(index, count); - T[] tmpArray = new T[count]; - Array.Copy(Items, index, tmpArray, 0, count); - return new ExposedList(tmpArray, count); - } - - public int IndexOf (T item) { - return Array.IndexOf(Items, item, 0, Count); - } - - public int IndexOf (T item, int index) { - CheckIndex(index); - return Array.IndexOf(Items, item, index, Count - index); - } - - public int IndexOf (T item, int index, int count) { - if (index < 0) - throw new ArgumentOutOfRangeException("index"); - - if (count < 0) - throw new ArgumentOutOfRangeException("count"); - - if ((uint)index + (uint)count > (uint)Count) - throw new ArgumentOutOfRangeException("index and count exceed length of list"); - - return Array.IndexOf(Items, item, index, count); - } - - private void Shift (int start, int delta) { - if (delta < 0) - start -= delta; - - if (start < Count) - Array.Copy(Items, start, Items, start + delta, Count - start); - - Count += delta; - - if (delta < 0) - Array.Clear(Items, Count, -delta); - } - - private void CheckIndex (int index) { - if (index < 0 || (uint)index > (uint)Count) - throw new ArgumentOutOfRangeException("index"); - } - - public void Insert (int index, T item) { - CheckIndex(index); - if (Count == Items.Length) - GrowIfNeeded(1); - Shift(index, 1); - Items[index] = item; - version++; - } - - private void CheckCollection (IEnumerable collection) { - if (collection == null) - throw new ArgumentNullException("collection"); - } - - public void InsertRange (int index, IEnumerable collection) { - CheckCollection(collection); - CheckIndex(index); - if (collection == this) { - T[] buffer = new T[Count]; - CopyTo(buffer, 0); - GrowIfNeeded(Count); - Shift(index, buffer.Length); - Array.Copy(buffer, 0, Items, index, buffer.Length); - } else { - ICollection c = collection as ICollection; - if (c != null) - InsertCollection(index, c); - else - InsertEnumeration(index, collection); - } - version++; - } - - private void InsertCollection (int index, ICollection collection) { - int collectionCount = collection.Count; - GrowIfNeeded(collectionCount); - - Shift(index, collectionCount); - collection.CopyTo(Items, index); - } - - private void InsertEnumeration (int index, IEnumerable enumerable) { - foreach (T t in enumerable) - Insert(index++, t); - } - - public int LastIndexOf (T item) { - return Array.LastIndexOf(Items, item, Count - 1, Count); - } - - public int LastIndexOf (T item, int index) { - CheckIndex(index); - return Array.LastIndexOf(Items, item, index, index + 1); - } - - public int LastIndexOf (T item, int index, int count) { - if (index < 0) - throw new ArgumentOutOfRangeException("index", index, "index is negative"); - - if (count < 0) - throw new ArgumentOutOfRangeException("count", count, "count is negative"); - - if (index - count + 1 < 0) - throw new ArgumentOutOfRangeException("count", count, "count is too large"); - - return Array.LastIndexOf(Items, item, index, count); - } - - public bool Remove (T item) { - int loc = IndexOf(item); - if (loc != -1) - RemoveAt(loc); - - return loc != -1; - } - - public int RemoveAll (Predicate match) { - CheckMatch(match); - int i = 0; - int j = 0; - - // Find the first item to remove - for (i = 0; i < Count; i++) - if (match(Items[i])) - break; - - if (i == Count) - return 0; - - version++; - - // Remove any additional items - for (j = i + 1; j < Count; j++) { - if (!match(Items[j])) - Items[i++] = Items[j]; - } - if (j - i > 0) - Array.Clear(Items, i, j - i); - - Count = i; - return (j - i); - } - - public void RemoveAt (int index) { - if (index < 0 || (uint)index >= (uint)Count) - throw new ArgumentOutOfRangeException("index"); - Shift(index, -1); - Array.Clear(Items, Count, 1); - version++; - } - - public void RemoveRange (int index, int count) { - CheckRange(index, count); - if (count > 0) { - Shift(index, -count); - Array.Clear(Items, Count, count); - version++; - } - } - - public void Reverse () { - Array.Reverse(Items, 0, Count); - version++; - } - - public void Reverse (int index, int count) { - CheckRange(index, count); - Array.Reverse(Items, index, count); - version++; - } - - public void Sort () { - Array.Sort(Items, 0, Count, Comparer.Default); - version++; - } - - public void Sort (IComparer comparer) { - Array.Sort(Items, 0, Count, comparer); - version++; - } - - public void Sort (Comparison comparison) { - Array.Sort(Items, comparison); - version++; - } - - public void Sort (int index, int count, IComparer comparer) { - CheckRange(index, count); - Array.Sort(Items, index, count, comparer); - version++; - } - - public T[] ToArray () { - T[] t = new T[Count]; - Array.Copy(Items, t, Count); - - return t; - } - - public void TrimExcess () { - Capacity = Count; - } - - public bool TrueForAll (Predicate match) { - CheckMatch(match); - - for (int i = 0; i < Count; i++) - if (!match(Items[i])) - return false; - - return true; - } - - public int Capacity { - get { - return Items.Length; - } - set { - if ((uint)value < (uint)Count) - throw new ArgumentOutOfRangeException(); - - Array.Resize(ref Items, value); - } - } - - #region Interface implementations. - - IEnumerator IEnumerable.GetEnumerator () { - return GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator () { - return GetEnumerator(); - } - - #endregion - - [Serializable] - public struct Enumerator : IEnumerator, IDisposable { - private ExposedList l; - private int next; - private int ver; - private T current; - - internal Enumerator (ExposedList l) - : this() { - this.l = l; - ver = l.version; - } - - public void Dispose () { - l = null; - } - - private void VerifyState () { - if (l == null) - throw new ObjectDisposedException(GetType().FullName); - if (ver != l.version) - throw new InvalidOperationException( - "Collection was modified; enumeration operation may not execute."); - } - - public bool MoveNext () { - VerifyState(); - - if (next < 0) - return false; - - if (next < l.Count) { - current = l.Items[next++]; - return true; - } - - next = -1; - return false; - } - - public T Current { - get { - return current; - } - } - - void IEnumerator.Reset () { - VerifyState(); - next = 0; - } - - object IEnumerator.Current { - get { - VerifyState(); - if (next <= 0) - throw new InvalidOperationException(); - return current; - } - } - } - } +namespace Spine3_4_02 +{ + [Serializable] + [DebuggerDisplay("Count={Count}")] + public class ExposedList : IEnumerable + { + public T[] Items; + public int Count; + private const int DefaultCapacity = 4; + private static readonly T[] EmptyArray = new T[0]; + private int version; + + public ExposedList() + { + Items = EmptyArray; + } + + public ExposedList(IEnumerable collection) + { + CheckCollection(collection); + + // initialize to needed size (if determinable) + ICollection c = collection as ICollection; + if (c == null) + { + Items = EmptyArray; + AddEnumerable(collection); + } + else + { + Items = new T[c.Count]; + AddCollection(c); + } + } + + public ExposedList(int capacity) + { + if (capacity < 0) + throw new ArgumentOutOfRangeException("capacity"); + Items = new T[capacity]; + } + + internal ExposedList(T[] data, int size) + { + Items = data; + Count = size; + } + + public void Add(T item) + { + // If we check to see if we need to grow before trying to grow + // we can speed things up by 25% + if (Count == Items.Length) + GrowIfNeeded(1); + Items[Count++] = item; + version++; + } + + public void GrowIfNeeded(int newCount) + { + int minimumSize = Count + newCount; + if (minimumSize > Items.Length) + Capacity = Math.Max(Math.Max(Capacity * 2, DefaultCapacity), minimumSize); + } + + public ExposedList Resize(int newSize) + { + if (newSize > Items.Length) Array.Resize(ref Items, newSize); + Count = newSize; + return this; + } + + private void CheckRange(int idx, int count) + { + if (idx < 0) + throw new ArgumentOutOfRangeException("index"); + + if (count < 0) + throw new ArgumentOutOfRangeException("count"); + + if ((uint)idx + (uint)count > (uint)Count) + throw new ArgumentException("index and count exceed length of list"); + } + + private void AddCollection(ICollection collection) + { + int collectionCount = collection.Count; + if (collectionCount == 0) + return; + + GrowIfNeeded(collectionCount); + collection.CopyTo(Items, Count); + Count += collectionCount; + } + + private void AddEnumerable(IEnumerable enumerable) + { + foreach (T t in enumerable) + { + Add(t); + } + } + + public void AddRange(IEnumerable collection) + { + CheckCollection(collection); + + ICollection c = collection as ICollection; + if (c != null) + AddCollection(c); + else + AddEnumerable(collection); + version++; + } + + public int BinarySearch(T item) + { + return Array.BinarySearch(Items, 0, Count, item); + } + + public int BinarySearch(T item, IComparer comparer) + { + return Array.BinarySearch(Items, 0, Count, item, comparer); + } + + public int BinarySearch(int index, int count, T item, IComparer comparer) + { + CheckRange(index, count); + return Array.BinarySearch(Items, index, count, item, comparer); + } + + public void Clear(bool clearArray = true) + { + if (clearArray) + Array.Clear(Items, 0, Items.Length); + + Count = 0; + version++; + } + + public bool Contains(T item) + { + return Array.IndexOf(Items, item, 0, Count) != -1; + } + + public ExposedList ConvertAll(Converter converter) + { + if (converter == null) + throw new ArgumentNullException("converter"); + ExposedList u = new ExposedList(Count); + for (int i = 0; i < Count; i++) + u.Items[i] = converter(Items[i]); + + u.Count = Count; + return u; + } + + public void CopyTo(T[] array) + { + Array.Copy(Items, 0, array, 0, Count); + } + + public void CopyTo(T[] array, int arrayIndex) + { + Array.Copy(Items, 0, array, arrayIndex, Count); + } + + public void CopyTo(int index, T[] array, int arrayIndex, int count) + { + CheckRange(index, count); + Array.Copy(Items, index, array, arrayIndex, count); + } + + public bool Exists(Predicate match) + { + CheckMatch(match); + return GetIndex(0, Count, match) != -1; + } + + public T Find(Predicate match) + { + CheckMatch(match); + int i = GetIndex(0, Count, match); + return (i != -1) ? Items[i] : default(T); + } + + private static void CheckMatch(Predicate match) + { + if (match == null) + throw new ArgumentNullException("match"); + } + + public ExposedList FindAll(Predicate match) + { + CheckMatch(match); + return FindAllList(match); + } + + private ExposedList FindAllList(Predicate match) + { + ExposedList results = new ExposedList(); + for (int i = 0; i < Count; i++) + if (match(Items[i])) + results.Add(Items[i]); + + return results; + } + + public int FindIndex(Predicate match) + { + CheckMatch(match); + return GetIndex(0, Count, match); + } + + public int FindIndex(int startIndex, Predicate match) + { + CheckMatch(match); + CheckIndex(startIndex); + return GetIndex(startIndex, Count - startIndex, match); + } + + public int FindIndex(int startIndex, int count, Predicate match) + { + CheckMatch(match); + CheckRange(startIndex, count); + return GetIndex(startIndex, count, match); + } + + private int GetIndex(int startIndex, int count, Predicate match) + { + int end = startIndex + count; + for (int i = startIndex; i < end; i++) + if (match(Items[i])) + return i; + + return -1; + } + + public T FindLast(Predicate match) + { + CheckMatch(match); + int i = GetLastIndex(0, Count, match); + return i == -1 ? default(T) : Items[i]; + } + + public int FindLastIndex(Predicate match) + { + CheckMatch(match); + return GetLastIndex(0, Count, match); + } + + public int FindLastIndex(int startIndex, Predicate match) + { + CheckMatch(match); + CheckIndex(startIndex); + return GetLastIndex(0, startIndex + 1, match); + } + + public int FindLastIndex(int startIndex, int count, Predicate match) + { + CheckMatch(match); + int start = startIndex - count + 1; + CheckRange(start, count); + return GetLastIndex(start, count, match); + } + + private int GetLastIndex(int startIndex, int count, Predicate match) + { + // unlike FindLastIndex, takes regular params for search range + for (int i = startIndex + count; i != startIndex;) + if (match(Items[--i])) + return i; + return -1; + } + + public void ForEach(Action action) + { + if (action == null) + throw new ArgumentNullException("action"); + for (int i = 0; i < Count; i++) + action(Items[i]); + } + + public Enumerator GetEnumerator() + { + return new Enumerator(this); + } + + public ExposedList GetRange(int index, int count) + { + CheckRange(index, count); + T[] tmpArray = new T[count]; + Array.Copy(Items, index, tmpArray, 0, count); + return new ExposedList(tmpArray, count); + } + + public int IndexOf(T item) + { + return Array.IndexOf(Items, item, 0, Count); + } + + public int IndexOf(T item, int index) + { + CheckIndex(index); + return Array.IndexOf(Items, item, index, Count - index); + } + + public int IndexOf(T item, int index, int count) + { + if (index < 0) + throw new ArgumentOutOfRangeException("index"); + + if (count < 0) + throw new ArgumentOutOfRangeException("count"); + + if ((uint)index + (uint)count > (uint)Count) + throw new ArgumentOutOfRangeException("index and count exceed length of list"); + + return Array.IndexOf(Items, item, index, count); + } + + private void Shift(int start, int delta) + { + if (delta < 0) + start -= delta; + + if (start < Count) + Array.Copy(Items, start, Items, start + delta, Count - start); + + Count += delta; + + if (delta < 0) + Array.Clear(Items, Count, -delta); + } + + private void CheckIndex(int index) + { + if (index < 0 || (uint)index > (uint)Count) + throw new ArgumentOutOfRangeException("index"); + } + + public void Insert(int index, T item) + { + CheckIndex(index); + if (Count == Items.Length) + GrowIfNeeded(1); + Shift(index, 1); + Items[index] = item; + version++; + } + + private void CheckCollection(IEnumerable collection) + { + if (collection == null) + throw new ArgumentNullException("collection"); + } + + public void InsertRange(int index, IEnumerable collection) + { + CheckCollection(collection); + CheckIndex(index); + if (collection == this) + { + T[] buffer = new T[Count]; + CopyTo(buffer, 0); + GrowIfNeeded(Count); + Shift(index, buffer.Length); + Array.Copy(buffer, 0, Items, index, buffer.Length); + } + else + { + ICollection c = collection as ICollection; + if (c != null) + InsertCollection(index, c); + else + InsertEnumeration(index, collection); + } + version++; + } + + private void InsertCollection(int index, ICollection collection) + { + int collectionCount = collection.Count; + GrowIfNeeded(collectionCount); + + Shift(index, collectionCount); + collection.CopyTo(Items, index); + } + + private void InsertEnumeration(int index, IEnumerable enumerable) + { + foreach (T t in enumerable) + Insert(index++, t); + } + + public int LastIndexOf(T item) + { + return Array.LastIndexOf(Items, item, Count - 1, Count); + } + + public int LastIndexOf(T item, int index) + { + CheckIndex(index); + return Array.LastIndexOf(Items, item, index, index + 1); + } + + public int LastIndexOf(T item, int index, int count) + { + if (index < 0) + throw new ArgumentOutOfRangeException("index", index, "index is negative"); + + if (count < 0) + throw new ArgumentOutOfRangeException("count", count, "count is negative"); + + if (index - count + 1 < 0) + throw new ArgumentOutOfRangeException("count", count, "count is too large"); + + return Array.LastIndexOf(Items, item, index, count); + } + + public bool Remove(T item) + { + int loc = IndexOf(item); + if (loc != -1) + RemoveAt(loc); + + return loc != -1; + } + + public int RemoveAll(Predicate match) + { + CheckMatch(match); + int i = 0; + int j = 0; + + // Find the first item to remove + for (i = 0; i < Count; i++) + if (match(Items[i])) + break; + + if (i == Count) + return 0; + + version++; + + // Remove any additional items + for (j = i + 1; j < Count; j++) + { + if (!match(Items[j])) + Items[i++] = Items[j]; + } + if (j - i > 0) + Array.Clear(Items, i, j - i); + + Count = i; + return (j - i); + } + + public void RemoveAt(int index) + { + if (index < 0 || (uint)index >= (uint)Count) + throw new ArgumentOutOfRangeException("index"); + Shift(index, -1); + Array.Clear(Items, Count, 1); + version++; + } + + public void RemoveRange(int index, int count) + { + CheckRange(index, count); + if (count > 0) + { + Shift(index, -count); + Array.Clear(Items, Count, count); + version++; + } + } + + public void Reverse() + { + Array.Reverse(Items, 0, Count); + version++; + } + + public void Reverse(int index, int count) + { + CheckRange(index, count); + Array.Reverse(Items, index, count); + version++; + } + + public void Sort() + { + Array.Sort(Items, 0, Count, Comparer.Default); + version++; + } + + public void Sort(IComparer comparer) + { + Array.Sort(Items, 0, Count, comparer); + version++; + } + + public void Sort(Comparison comparison) + { + Array.Sort(Items, comparison); + version++; + } + + public void Sort(int index, int count, IComparer comparer) + { + CheckRange(index, count); + Array.Sort(Items, index, count, comparer); + version++; + } + + public T[] ToArray() + { + T[] t = new T[Count]; + Array.Copy(Items, t, Count); + + return t; + } + + public void TrimExcess() + { + Capacity = Count; + } + + public bool TrueForAll(Predicate match) + { + CheckMatch(match); + + for (int i = 0; i < Count; i++) + if (!match(Items[i])) + return false; + + return true; + } + + public int Capacity + { + get + { + return Items.Length; + } + set + { + if ((uint)value < (uint)Count) + throw new ArgumentOutOfRangeException(); + + Array.Resize(ref Items, value); + } + } + + #region Interface implementations. + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion + + [Serializable] + public struct Enumerator : IEnumerator, IDisposable + { + private ExposedList l; + private int next; + private int ver; + private T current; + + internal Enumerator(ExposedList l) + : this() + { + this.l = l; + ver = l.version; + } + + public void Dispose() + { + l = null; + } + + private void VerifyState() + { + if (l == null) + throw new ObjectDisposedException(GetType().FullName); + if (ver != l.version) + throw new InvalidOperationException( + "Collection was modified; enumeration operation may not execute."); + } + + public bool MoveNext() + { + VerifyState(); + + if (next < 0) + return false; + + if (next < l.Count) + { + current = l.Items[next++]; + return true; + } + + next = -1; + return false; + } + + public T Current + { + get + { + return current; + } + } + + void IEnumerator.Reset() + { + VerifyState(); + next = 0; + } + + object IEnumerator.Current + { + get + { + VerifyState(); + if (next <= 0) + throw new InvalidOperationException(); + return current; + } + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/IUpdatable.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/IUpdatable.cs index abaf0ad..15dab3f 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/IUpdatable.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/IUpdatable.cs @@ -28,10 +28,10 @@ * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; - -namespace Spine3_4_02 { - public interface IUpdatable { - void Update (); - } +namespace Spine3_4_02 +{ + public interface IUpdatable + { + void Update(); + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/IkConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/IkConstraint.cs index d51278c..5701215 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/IkConstraint.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/IkConstraint.cs @@ -30,205 +30,236 @@ using System; -namespace Spine3_4_02 { - public class IkConstraint : IUpdatable { - internal IkConstraintData data; - internal ExposedList bones = new ExposedList(); - internal Bone target; - internal float mix; - internal int bendDirection; +namespace Spine3_4_02 +{ + public class IkConstraint : IUpdatable + { + internal IkConstraintData data; + internal ExposedList bones = new ExposedList(); + internal Bone target; + internal float mix; + internal int bendDirection; - internal int level; + internal int level; - public IkConstraintData Data { get { return data; } } - public ExposedList Bones { get { return bones; } } - public Bone Target { get { return target; } set { target = value; } } - public int BendDirection { get { return bendDirection; } set { bendDirection = value; } } - public float Mix { get { return mix; } set { mix = value; } } + public IkConstraintData Data { get { return data; } } + public ExposedList Bones { get { return bones; } } + public Bone Target { get { return target; } set { target = value; } } + public int BendDirection { get { return bendDirection; } set { bendDirection = value; } } + public float Mix { get { return mix; } set { mix = value; } } - public IkConstraint (IkConstraintData data, Skeleton skeleton) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.data = data; - mix = data.mix; - bendDirection = data.bendDirection; + public IkConstraint(IkConstraintData data, Skeleton skeleton) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + mix = data.mix; + bendDirection = data.bendDirection; - bones = new ExposedList(data.bones.Count); - foreach (BoneData boneData in data.bones) - bones.Add(skeleton.FindBone(boneData.name)); - target = skeleton.FindBone(data.target.name); - } + bones = new ExposedList(data.bones.Count); + foreach (BoneData boneData in data.bones) + bones.Add(skeleton.FindBone(boneData.name)); + target = skeleton.FindBone(data.target.name); + } - public void Update () { - Apply(); - } + public void Update() + { + Apply(); + } - public void Apply () { - Bone target = this.target; - ExposedList bones = this.bones; - switch (bones.Count) { - case 1: - Apply(bones.Items[0], target.worldX, target.worldY, mix); - break; - case 2: - Apply(bones.Items[0], bones.Items[1], target.worldX, target.worldY, bendDirection, mix); - break; - } - } + public void Apply() + { + Bone target = this.target; + ExposedList bones = this.bones; + switch (bones.Count) + { + case 1: + Apply(bones.Items[0], target.worldX, target.worldY, mix); + break; + case 2: + Apply(bones.Items[0], bones.Items[1], target.worldX, target.worldY, bendDirection, mix); + break; + } + } - override public String ToString () { - return data.name; - } + override public String ToString() + { + return data.name; + } - /// 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 public void Apply (Bone bone, float targetX, float targetY, float alpha) { - Bone pp = bone.parent; - float id = 1 / (pp.a * pp.d - pp.b * pp.c); - float x = targetX - pp.worldX, y = targetY - pp.worldY; - float tx = (x * pp.d - y * pp.b) * id - bone.x, ty = (y * pp.a - x * pp.c) * id - bone.y; - float rotationIK = MathUtils.Atan2(ty, tx) * MathUtils.radDeg - bone.shearX - bone.rotation; - if (bone.scaleX < 0) rotationIK += 180; - if (rotationIK > 180) - rotationIK -= 360; - else if (rotationIK < -180) rotationIK += 360; - bone.UpdateWorldTransform(bone.x, bone.y, bone.rotation + rotationIK * alpha, bone.scaleX, bone.scaleY, - bone.shearX, bone.shearY); - } + /// 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 public void Apply(Bone bone, float targetX, float targetY, float alpha) + { + Bone pp = bone.parent; + float id = 1 / (pp.a * pp.d - pp.b * pp.c); + float x = targetX - pp.worldX, y = targetY - pp.worldY; + float tx = (x * pp.d - y * pp.b) * id - bone.x, ty = (y * pp.a - x * pp.c) * id - bone.y; + float rotationIK = MathUtils.Atan2(ty, tx) * MathUtils.radDeg - bone.shearX - bone.rotation; + if (bone.scaleX < 0) rotationIK += 180; + if (rotationIK > 180) + rotationIK -= 360; + else if (rotationIK < -180) rotationIK += 360; + bone.UpdateWorldTransform(bone.x, bone.y, bone.rotation + rotationIK * alpha, bone.scaleX, bone.scaleY, + bone.shearX, bone.shearY); + } - /// 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. - /// A direct descendant of the parent bone. - static public void Apply (Bone parent, Bone child, float targetX, float targetY, int bendDir, float alpha) { - if (alpha == 0) { - child.UpdateWorldTransform (); - return; - } - float px = parent.x, py = parent.y, psx = parent.scaleX, psy = parent.scaleY, csx = child.scaleX; - int os1, os2, s2; - if (psx < 0) { - psx = -psx; - os1 = 180; - s2 = -1; - } else { - os1 = 0; - s2 = 1; - } - if (psy < 0) { - psy = -psy; - s2 = -s2; - } - if (csx < 0) { - csx = -csx; - os2 = 180; - } else - os2 = 0; - float cx = child.x, cy, cwx, cwy, a = parent.a, b = parent.b, c = parent.c, d = parent.d; - bool u = Math.Abs(psx - psy) <= 0.0001f; - if (!u) { - cy = 0; - cwx = a * cx + parent.worldX; - cwy = c * cx + parent.worldY; - } else { - cy = child.y; - cwx = a * cx + b * cy + parent.worldX; - cwy = c * cx + d * cy + parent.worldY; - } - Bone pp = parent.parent; - a = pp.a; - b = pp.b; - c = pp.c; - d = pp.d; - float id = 1 / (a * d - b * c), x = targetX - pp.worldX, y = targetY - pp.worldY; - float tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py; - x = cwx - pp.worldX; - y = cwy - pp.worldY; - float dx = (x * d - y * b) * id - px, dy = (y * a - x * c) * id - py; - float l1 = (float)Math.Sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2; - if (u) { - l2 *= psx; - float cos = (tx * tx + ty * ty - l1 * l1 - l2 * l2) / (2 * l1 * l2); - if (cos < -1) - cos = -1; - else if (cos > 1) cos = 1; - a2 = (float)Math.Acos(cos) * bendDir; - a = l1 + l2 * cos; - b = l2 * MathUtils.Sin(a2); - a1 = MathUtils.Atan2(ty * a - tx * b, tx * a + ty * b); - } else { - a = psx * l2; - b = psy * l2; - float aa = a * a, bb = b * b, dd = tx * tx + ty * ty, ta = MathUtils.Atan2(ty, tx); - c = bb * l1 * l1 + aa * dd - aa * bb; - float c1 = -2 * bb * l1, c2 = bb - aa; - d = c1 * c1 - 4 * c2 * c; - if (d >= 0) { - float q = (float)Math.Sqrt(d); - if (c1 < 0) q = -q; - q = -(c1 + q) / 2; - float r0 = q / c2, r1 = c / q; - float r = Math.Abs(r0) < Math.Abs(r1) ? r0 : r1; - if (r * r <= dd) { - y = (float)Math.Sqrt(dd - r * r) * bendDir; - a1 = ta - MathUtils.Atan2(y, r); - a2 = MathUtils.Atan2(y / psy, (r - l1) / psx); - goto outer; - } - } - float minAngle = 0, minDist = float.MaxValue, minX = 0, minY = 0; - float maxAngle = 0, maxDist = 0, maxX = 0, maxY = 0; - x = l1 + a; - d = x * x; - if (d > maxDist) { - maxAngle = 0; - maxDist = d; - maxX = x; - } - x = l1 - a; - d = x * x; - if (d < minDist) { - minAngle = MathUtils.PI; - minDist = d; - minX = x; - } - float angle = (float)Math.Acos(-a * l1 / (aa - bb)); - x = a * MathUtils.Cos(angle) + l1; - y = b * MathUtils.Sin(angle); - d = x * x + y * y; - if (d < minDist) { - minAngle = angle; - minDist = d; - minX = x; - minY = y; - } - if (d > maxDist) { - maxAngle = angle; - maxDist = d; - maxX = x; - maxY = y; - } - if (dd <= (minDist + maxDist) / 2) { - a1 = ta - MathUtils.Atan2(minY * bendDir, minX); - a2 = minAngle * bendDir; - } else { - a1 = ta - MathUtils.Atan2(maxY * bendDir, maxX); - a2 = maxAngle * bendDir; - } - } - outer: - float os = MathUtils.Atan2(cy, cx) * s2; - float rotation = parent.rotation; - a1 = (a1 - os) * MathUtils.radDeg + os1 - rotation; - if (a1 > 180) - a1 -= 360; - else if (a1 < -180) a1 += 360; - parent.UpdateWorldTransform(px, py, rotation + a1 * alpha, parent.scaleX, parent.scaleY, 0, 0); - rotation = child.rotation; - a2 = ((a2 + os) * MathUtils.radDeg - child.shearX) * s2 + os2 - rotation; - if (a2 > 180) - a2 -= 360; - else if (a2 < -180) a2 += 360; - child.UpdateWorldTransform(cx, cy, rotation + a2 * alpha, child.scaleX, child.scaleY, child.shearX, child.shearY); - } - } + /// 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. + /// A direct descendant of the parent bone. + static public void Apply(Bone parent, Bone child, float targetX, float targetY, int bendDir, float alpha) + { + if (alpha == 0) + { + child.UpdateWorldTransform(); + return; + } + float px = parent.x, py = parent.y, psx = parent.scaleX, psy = parent.scaleY, csx = child.scaleX; + int os1, os2, s2; + if (psx < 0) + { + psx = -psx; + os1 = 180; + s2 = -1; + } + else + { + os1 = 0; + s2 = 1; + } + if (psy < 0) + { + psy = -psy; + s2 = -s2; + } + if (csx < 0) + { + csx = -csx; + os2 = 180; + } + else + os2 = 0; + float cx = child.x, cy, cwx, cwy, a = parent.a, b = parent.b, c = parent.c, d = parent.d; + bool u = Math.Abs(psx - psy) <= 0.0001f; + if (!u) + { + cy = 0; + cwx = a * cx + parent.worldX; + cwy = c * cx + parent.worldY; + } + else + { + cy = child.y; + cwx = a * cx + b * cy + parent.worldX; + cwy = c * cx + d * cy + parent.worldY; + } + Bone pp = parent.parent; + a = pp.a; + b = pp.b; + c = pp.c; + d = pp.d; + float id = 1 / (a * d - b * c), x = targetX - pp.worldX, y = targetY - pp.worldY; + float tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py; + x = cwx - pp.worldX; + y = cwy - pp.worldY; + float dx = (x * d - y * b) * id - px, dy = (y * a - x * c) * id - py; + float l1 = (float)Math.Sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2; + if (u) + { + l2 *= psx; + float cos = (tx * tx + ty * ty - l1 * l1 - l2 * l2) / (2 * l1 * l2); + if (cos < -1) + cos = -1; + else if (cos > 1) cos = 1; + a2 = (float)Math.Acos(cos) * bendDir; + a = l1 + l2 * cos; + b = l2 * MathUtils.Sin(a2); + a1 = MathUtils.Atan2(ty * a - tx * b, tx * a + ty * b); + } + else + { + a = psx * l2; + b = psy * l2; + float aa = a * a, bb = b * b, dd = tx * tx + ty * ty, ta = MathUtils.Atan2(ty, tx); + c = bb * l1 * l1 + aa * dd - aa * bb; + float c1 = -2 * bb * l1, c2 = bb - aa; + d = c1 * c1 - 4 * c2 * c; + if (d >= 0) + { + float q = (float)Math.Sqrt(d); + if (c1 < 0) q = -q; + q = -(c1 + q) / 2; + float r0 = q / c2, r1 = c / q; + float r = Math.Abs(r0) < Math.Abs(r1) ? r0 : r1; + if (r * r <= dd) + { + y = (float)Math.Sqrt(dd - r * r) * bendDir; + a1 = ta - MathUtils.Atan2(y, r); + a2 = MathUtils.Atan2(y / psy, (r - l1) / psx); + goto outer; + } + } + float minAngle = 0, minDist = float.MaxValue, minX = 0, minY = 0; + float maxAngle = 0, maxDist = 0, maxX = 0, maxY = 0; + x = l1 + a; + d = x * x; + if (d > maxDist) + { + maxAngle = 0; + maxDist = d; + maxX = x; + } + x = l1 - a; + d = x * x; + if (d < minDist) + { + minAngle = MathUtils.PI; + minDist = d; + minX = x; + } + float angle = (float)Math.Acos(-a * l1 / (aa - bb)); + x = a * MathUtils.Cos(angle) + l1; + y = b * MathUtils.Sin(angle); + d = x * x + y * y; + if (d < minDist) + { + minAngle = angle; + minDist = d; + minX = x; + minY = y; + } + if (d > maxDist) + { + maxAngle = angle; + maxDist = d; + maxX = x; + maxY = y; + } + if (dd <= (minDist + maxDist) / 2) + { + a1 = ta - MathUtils.Atan2(minY * bendDir, minX); + a2 = minAngle * bendDir; + } + else + { + a1 = ta - MathUtils.Atan2(maxY * bendDir, maxX); + a2 = maxAngle * bendDir; + } + } + outer: + float os = MathUtils.Atan2(cy, cx) * s2; + float rotation = parent.rotation; + a1 = (a1 - os) * MathUtils.radDeg + os1 - rotation; + if (a1 > 180) + a1 -= 360; + else if (a1 < -180) a1 += 360; + parent.UpdateWorldTransform(px, py, rotation + a1 * alpha, parent.scaleX, parent.scaleY, 0, 0); + rotation = child.rotation; + a2 = ((a2 + os) * MathUtils.radDeg - child.shearX) * s2 + os2 - rotation; + if (a2 > 180) + a2 -= 360; + else if (a2 < -180) a2 += 360; + child.UpdateWorldTransform(cx, cy, rotation + a2 * alpha, child.scaleX, child.scaleY, child.shearX, child.shearY); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/IkConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/IkConstraintData.cs index e300ad4..07b059a 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/IkConstraintData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/IkConstraintData.cs @@ -31,27 +31,31 @@ using System; using System.Collections.Generic; -namespace Spine3_4_02 { - public class IkConstraintData { - internal String name; - internal List bones = new List(); - internal BoneData target; - internal int bendDirection = 1; - internal float mix = 1; +namespace Spine3_4_02 +{ + public class IkConstraintData + { + internal String name; + internal List bones = new List(); + internal BoneData target; + internal int bendDirection = 1; + internal float mix = 1; - public String Name { get { return name; } } - public List Bones { get { return bones; } } - public BoneData Target { get { return target; } set { target = value; } } - public int BendDirection { get { return bendDirection; } set { bendDirection = value; } } - public float Mix { get { return mix; } set { mix = value; } } + public String Name { get { return name; } } + public List Bones { get { return bones; } } + public BoneData Target { get { return target; } set { target = value; } } + public int BendDirection { get { return bendDirection; } set { bendDirection = value; } } + public float Mix { get { return mix; } set { mix = value; } } - public IkConstraintData (String name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; - } + public IkConstraintData(String name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + } - override public String ToString () { - return name; - } - } + override public String ToString() + { + return name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Json.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Json.cs index 5938635..e1c8df6 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Json.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Json.cs @@ -29,20 +29,22 @@ *****************************************************************************/ using System; +using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Text; -using System.Collections; -using System.Globalization; -using System.Collections.Generic; -namespace Spine3_4_02 { - public static class Json { - public static object Deserialize (TextReader text) { - var parser = new SharpJson3_4_02.JsonDecoder(); - parser.parseNumbersAsFloat = true; - return parser.Decode(text.ReadToEnd()); - } - } +namespace Spine3_4_02 +{ + public static class Json + { + public static object Deserialize(TextReader text) + { + var parser = new SharpJson3_4_02.JsonDecoder(); + parser.parseNumbersAsFloat = true; + return parser.Decode(text.ReadToEnd()); + } + } } /** @@ -76,460 +78,502 @@ public static object Deserialize (TextReader text) { */ namespace SharpJson3_4_02 { - class Lexer - { - public enum Token { - None, - Null, - True, - False, - Colon, - Comma, - String, - Number, - CurlyOpen, - CurlyClose, - SquaredOpen, - SquaredClose, - }; - - public bool hasError { - get { - return !success; - } - } - - public int lineNumber { - get; - private set; - } - - public bool parseNumbersAsFloat { - get; - set; - } - - char[] json; - int index = 0; - bool success = true; - char[] stringBuffer = new char[4096]; - - public Lexer(string text) - { - Reset(); - - json = text.ToCharArray(); - parseNumbersAsFloat = false; - } - - public void Reset() - { - index = 0; - lineNumber = 1; - success = true; - } - - public string ParseString() - { - int idx = 0; - StringBuilder builder = null; - - SkipWhiteSpaces(); - - // " - char c = json[index++]; - - bool failed = false; - bool complete = false; - - while (!complete && !failed) { - if (index == json.Length) - break; - - c = json[index++]; - if (c == '"') { - complete = true; - break; - } else if (c == '\\') { - if (index == json.Length) - break; - - c = json[index++]; - - switch (c) { - case '"': - stringBuffer[idx++] = '"'; - break; - case '\\': - stringBuffer[idx++] = '\\'; - break; - case '/': - stringBuffer[idx++] = '/'; - break; - case 'b': - stringBuffer[idx++] = '\b'; - break; - case'f': - stringBuffer[idx++] = '\f'; - break; - case 'n': - stringBuffer[idx++] = '\n'; - break; - case 'r': - stringBuffer[idx++] = '\r'; - break; - case 't': - stringBuffer[idx++] = '\t'; - break; - case 'u': - int remainingLength = json.Length - index; - if (remainingLength >= 4) { - var hex = new string(json, index, 4); - - // XXX: handle UTF - stringBuffer[idx++] = (char) Convert.ToInt32(hex, 16); - - // skip 4 chars - index += 4; - } else { - failed = true; - } - break; - } - } else { - stringBuffer[idx++] = c; - } - - if (idx >= stringBuffer.Length) { - if (builder == null) - builder = new StringBuilder(); - - builder.Append(stringBuffer, 0, idx); - idx = 0; - } - } - - if (!complete) { - success = false; - return null; - } - - if (builder != null) - return builder.ToString (); - else - return new string (stringBuffer, 0, idx); - } - - string GetNumberString() - { - SkipWhiteSpaces(); - - int lastIndex = GetLastIndexOfNumber(index); - int charLength = (lastIndex - index) + 1; - - var result = new string (json, index, charLength); - - index = lastIndex + 1; - - return result; - } - - public float ParseFloatNumber() - { - float number; - var str = GetNumberString (); - - if (!float.TryParse (str, NumberStyles.Float, CultureInfo.InvariantCulture, out number)) - return 0; - - return number; - } - - public double ParseDoubleNumber() - { - double number; - var str = GetNumberString (); - - if (!double.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out number)) - return 0; - - return number; - } - - int GetLastIndexOfNumber(int index) - { - int lastIndex; - - for (lastIndex = index; lastIndex < json.Length; lastIndex++) { - char ch = json[lastIndex]; - - if ((ch < '0' || ch > '9') && ch != '+' && ch != '-' - && ch != '.' && ch != 'e' && ch != 'E') - break; - } - - return lastIndex - 1; - } - - void SkipWhiteSpaces() - { - for (; index < json.Length; index++) { - char ch = json[index]; - - if (ch == '\n') - lineNumber++; - - if (!char.IsWhiteSpace(json[index])) - break; - } - } - - public Token LookAhead() - { - SkipWhiteSpaces(); - - int savedIndex = index; - return NextToken(json, ref savedIndex); - } - - public Token NextToken() - { - SkipWhiteSpaces(); - return NextToken(json, ref index); - } - - static Token NextToken(char[] json, ref int index) - { - if (index == json.Length) - return Token.None; - - char c = json[index++]; - - switch (c) { - case '{': - return Token.CurlyOpen; - case '}': - return Token.CurlyClose; - case '[': - return Token.SquaredOpen; - case ']': - return Token.SquaredClose; - case ',': - return Token.Comma; - case '"': - return Token.String; - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - case '-': - return Token.Number; - case ':': - return Token.Colon; - } - - index--; - - int remainingLength = json.Length - index; - - // false - if (remainingLength >= 5) { - if (json[index] == 'f' && - json[index + 1] == 'a' && - json[index + 2] == 'l' && - json[index + 3] == 's' && - json[index + 4] == 'e') { - index += 5; - return Token.False; - } - } - - // true - if (remainingLength >= 4) { - if (json[index] == 't' && - json[index + 1] == 'r' && - json[index + 2] == 'u' && - json[index + 3] == 'e') { - index += 4; - return Token.True; - } - } - - // null - if (remainingLength >= 4) { - if (json[index] == 'n' && - json[index + 1] == 'u' && - json[index + 2] == 'l' && - json[index + 3] == 'l') { - index += 4; - return Token.Null; - } - } - - return Token.None; - } - } - - public class JsonDecoder - { - public string errorMessage { - get; - private set; - } - - public bool parseNumbersAsFloat { - get; - set; - } - - Lexer lexer; - - public JsonDecoder() - { - errorMessage = null; - parseNumbersAsFloat = false; - } - - public object Decode(string text) - { - errorMessage = null; - - lexer = new Lexer(text); - lexer.parseNumbersAsFloat = parseNumbersAsFloat; - - return ParseValue(); - } - - public static object DecodeText(string text) - { - var builder = new JsonDecoder(); - return builder.Decode(text); - } - - IDictionary ParseObject() - { - var table = new Dictionary(); - - // { - lexer.NextToken(); - - while (true) { - var token = lexer.LookAhead(); - - switch (token) { - case Lexer.Token.None: - TriggerError("Invalid token"); - return null; - case Lexer.Token.Comma: - lexer.NextToken(); - break; - case Lexer.Token.CurlyClose: - lexer.NextToken(); - return table; - default: - // name - string name = EvalLexer(lexer.ParseString()); - - if (errorMessage != null) - return null; - - // : - token = lexer.NextToken(); - - if (token != Lexer.Token.Colon) { - TriggerError("Invalid token; expected ':'"); - return null; - } - - // value - object value = ParseValue(); - - if (errorMessage != null) - return null; - - table[name] = value; - break; - } - } - - //return null; // Unreachable code - } - - IList ParseArray() - { - var array = new List(); - - // [ - lexer.NextToken(); - - while (true) { - var token = lexer.LookAhead(); - - switch (token) { - case Lexer.Token.None: - TriggerError("Invalid token"); - return null; - case Lexer.Token.Comma: - lexer.NextToken(); - break; - case Lexer.Token.SquaredClose: - lexer.NextToken(); - return array; - default: - object value = ParseValue(); - - if (errorMessage != null) - return null; - - array.Add(value); - break; - } - } - - //return null; // Unreachable code - } - - object ParseValue() - { - switch (lexer.LookAhead()) { - case Lexer.Token.String: - return EvalLexer(lexer.ParseString()); - case Lexer.Token.Number: - if (parseNumbersAsFloat) - return EvalLexer(lexer.ParseFloatNumber()); - else - return EvalLexer(lexer.ParseDoubleNumber()); - case Lexer.Token.CurlyOpen: - return ParseObject(); - case Lexer.Token.SquaredOpen: - return ParseArray(); - case Lexer.Token.True: - lexer.NextToken(); - return true; - case Lexer.Token.False: - lexer.NextToken(); - return false; - case Lexer.Token.Null: - lexer.NextToken(); - return null; - case Lexer.Token.None: - break; - } - - TriggerError("Unable to parse value"); - return null; - } - - void TriggerError(string message) - { - errorMessage = string.Format("Error: '{0}' at line {1}", - message, lexer.lineNumber); - } - - T EvalLexer(T value) - { - if (lexer.hasError) - TriggerError("Lexical error ocurred"); - - return value; - } - } + class Lexer + { + public enum Token + { + None, + Null, + True, + False, + Colon, + Comma, + String, + Number, + CurlyOpen, + CurlyClose, + SquaredOpen, + SquaredClose, + }; + + public bool hasError + { + get + { + return !success; + } + } + + public int lineNumber + { + get; + private set; + } + + public bool parseNumbersAsFloat + { + get; + set; + } + + char[] json; + int index = 0; + bool success = true; + char[] stringBuffer = new char[4096]; + + public Lexer(string text) + { + Reset(); + + json = text.ToCharArray(); + parseNumbersAsFloat = false; + } + + public void Reset() + { + index = 0; + lineNumber = 1; + success = true; + } + + public string ParseString() + { + int idx = 0; + StringBuilder builder = null; + + SkipWhiteSpaces(); + + // " + char c = json[index++]; + + bool failed = false; + bool complete = false; + + while (!complete && !failed) + { + if (index == json.Length) + break; + + c = json[index++]; + if (c == '"') + { + complete = true; + break; + } + else if (c == '\\') + { + if (index == json.Length) + break; + + c = json[index++]; + + switch (c) + { + case '"': + stringBuffer[idx++] = '"'; + break; + case '\\': + stringBuffer[idx++] = '\\'; + break; + case '/': + stringBuffer[idx++] = '/'; + break; + case 'b': + stringBuffer[idx++] = '\b'; + break; + case 'f': + stringBuffer[idx++] = '\f'; + break; + case 'n': + stringBuffer[idx++] = '\n'; + break; + case 'r': + stringBuffer[idx++] = '\r'; + break; + case 't': + stringBuffer[idx++] = '\t'; + break; + case 'u': + int remainingLength = json.Length - index; + if (remainingLength >= 4) + { + var hex = new string(json, index, 4); + + // XXX: handle UTF + stringBuffer[idx++] = (char)Convert.ToInt32(hex, 16); + + // skip 4 chars + index += 4; + } + else + { + failed = true; + } + break; + } + } + else + { + stringBuffer[idx++] = c; + } + + if (idx >= stringBuffer.Length) + { + if (builder == null) + builder = new StringBuilder(); + + builder.Append(stringBuffer, 0, idx); + idx = 0; + } + } + + if (!complete) + { + success = false; + return null; + } + + if (builder != null) + return builder.ToString(); + else + return new string(stringBuffer, 0, idx); + } + + string GetNumberString() + { + SkipWhiteSpaces(); + + int lastIndex = GetLastIndexOfNumber(index); + int charLength = (lastIndex - index) + 1; + + var result = new string(json, index, charLength); + + index = lastIndex + 1; + + return result; + } + + public float ParseFloatNumber() + { + float number; + var str = GetNumberString(); + + if (!float.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out number)) + return 0; + + return number; + } + + public double ParseDoubleNumber() + { + double number; + var str = GetNumberString(); + + if (!double.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out number)) + return 0; + + return number; + } + + int GetLastIndexOfNumber(int index) + { + int lastIndex; + + for (lastIndex = index; lastIndex < json.Length; lastIndex++) + { + char ch = json[lastIndex]; + + if ((ch < '0' || ch > '9') && ch != '+' && ch != '-' + && ch != '.' && ch != 'e' && ch != 'E') + break; + } + + return lastIndex - 1; + } + + void SkipWhiteSpaces() + { + for (; index < json.Length; index++) + { + char ch = json[index]; + + if (ch == '\n') + lineNumber++; + + if (!char.IsWhiteSpace(json[index])) + break; + } + } + + public Token LookAhead() + { + SkipWhiteSpaces(); + + int savedIndex = index; + return NextToken(json, ref savedIndex); + } + + public Token NextToken() + { + SkipWhiteSpaces(); + return NextToken(json, ref index); + } + + static Token NextToken(char[] json, ref int index) + { + if (index == json.Length) + return Token.None; + + char c = json[index++]; + + switch (c) + { + case '{': + return Token.CurlyOpen; + case '}': + return Token.CurlyClose; + case '[': + return Token.SquaredOpen; + case ']': + return Token.SquaredClose; + case ',': + return Token.Comma; + case '"': + return Token.String; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + return Token.Number; + case ':': + return Token.Colon; + } + + index--; + + int remainingLength = json.Length - index; + + // false + if (remainingLength >= 5) + { + if (json[index] == 'f' && + json[index + 1] == 'a' && + json[index + 2] == 'l' && + json[index + 3] == 's' && + json[index + 4] == 'e') + { + index += 5; + return Token.False; + } + } + + // true + if (remainingLength >= 4) + { + if (json[index] == 't' && + json[index + 1] == 'r' && + json[index + 2] == 'u' && + json[index + 3] == 'e') + { + index += 4; + return Token.True; + } + } + + // null + if (remainingLength >= 4) + { + if (json[index] == 'n' && + json[index + 1] == 'u' && + json[index + 2] == 'l' && + json[index + 3] == 'l') + { + index += 4; + return Token.Null; + } + } + + return Token.None; + } + } + + public class JsonDecoder + { + public string errorMessage + { + get; + private set; + } + + public bool parseNumbersAsFloat + { + get; + set; + } + + Lexer lexer; + + public JsonDecoder() + { + errorMessage = null; + parseNumbersAsFloat = false; + } + + public object Decode(string text) + { + errorMessage = null; + + lexer = new Lexer(text); + lexer.parseNumbersAsFloat = parseNumbersAsFloat; + + return ParseValue(); + } + + public static object DecodeText(string text) + { + var builder = new JsonDecoder(); + return builder.Decode(text); + } + + IDictionary ParseObject() + { + var table = new Dictionary(); + + // { + lexer.NextToken(); + + while (true) + { + var token = lexer.LookAhead(); + + switch (token) + { + case Lexer.Token.None: + TriggerError("Invalid token"); + return null; + case Lexer.Token.Comma: + lexer.NextToken(); + break; + case Lexer.Token.CurlyClose: + lexer.NextToken(); + return table; + default: + // name + string name = EvalLexer(lexer.ParseString()); + + if (errorMessage != null) + return null; + + // : + token = lexer.NextToken(); + + if (token != Lexer.Token.Colon) + { + TriggerError("Invalid token; expected ':'"); + return null; + } + + // value + object value = ParseValue(); + + if (errorMessage != null) + return null; + + table[name] = value; + break; + } + } + + //return null; // Unreachable code + } + + IList ParseArray() + { + var array = new List(); + + // [ + lexer.NextToken(); + + while (true) + { + var token = lexer.LookAhead(); + + switch (token) + { + case Lexer.Token.None: + TriggerError("Invalid token"); + return null; + case Lexer.Token.Comma: + lexer.NextToken(); + break; + case Lexer.Token.SquaredClose: + lexer.NextToken(); + return array; + default: + object value = ParseValue(); + + if (errorMessage != null) + return null; + + array.Add(value); + break; + } + } + + //return null; // Unreachable code + } + + object ParseValue() + { + switch (lexer.LookAhead()) + { + case Lexer.Token.String: + return EvalLexer(lexer.ParseString()); + case Lexer.Token.Number: + if (parseNumbersAsFloat) + return EvalLexer(lexer.ParseFloatNumber()); + else + return EvalLexer(lexer.ParseDoubleNumber()); + case Lexer.Token.CurlyOpen: + return ParseObject(); + case Lexer.Token.SquaredOpen: + return ParseArray(); + case Lexer.Token.True: + lexer.NextToken(); + return true; + case Lexer.Token.False: + lexer.NextToken(); + return false; + case Lexer.Token.Null: + lexer.NextToken(); + return null; + case Lexer.Token.None: + break; + } + + TriggerError("Unable to parse value"); + return null; + } + + void TriggerError(string message) + { + errorMessage = string.Format("Error: '{0}' at line {1}", + message, lexer.lineNumber); + } + + T EvalLexer(T value) + { + if (lexer.hasError) + TriggerError("Lexical error ocurred"); + + return value; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/MathUtils.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/MathUtils.cs index d226980..faa2c61 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/MathUtils.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/MathUtils.cs @@ -30,71 +30,82 @@ using System; -namespace Spine3_4_02 { - public static class MathUtils { - public const float PI = 3.1415927f; - public const float PI2 = PI * 2; - public const float radDeg = 180f / PI; - public const float degRad = PI / 180; +namespace Spine3_4_02 +{ + public static class MathUtils + { + public const float PI = 3.1415927f; + public const float PI2 = PI * 2; + public const float radDeg = 180f / PI; + public const float degRad = PI / 180; - const int SIN_BITS = 14; // 16KB. Adjust for accuracy. - const int SIN_MASK = ~(-1 << SIN_BITS); - const int SIN_COUNT = SIN_MASK + 1; - const float radFull = PI * 2; - const float degFull = 360; - const float radToIndex = SIN_COUNT / radFull; - const float degToIndex = SIN_COUNT / degFull; - static float[] sin = new float[SIN_COUNT]; + const int SIN_BITS = 14; // 16KB. Adjust for accuracy. + const int SIN_MASK = ~(-1 << SIN_BITS); + const int SIN_COUNT = SIN_MASK + 1; + const float radFull = PI * 2; + const float degFull = 360; + const float radToIndex = SIN_COUNT / radFull; + const float degToIndex = SIN_COUNT / degFull; + static float[] sin = new float[SIN_COUNT]; - static MathUtils () { - for (int i = 0; i < SIN_COUNT; i++) - sin[i] = (float)Math.Sin((i + 0.5f) / SIN_COUNT * radFull); - for (int i = 0; i < 360; i += 90) - sin[(int)(i * degToIndex) & SIN_MASK] = (float)Math.Sin(i * degRad); - } + static MathUtils() + { + for (int i = 0; i < SIN_COUNT; i++) + sin[i] = (float)Math.Sin((i + 0.5f) / SIN_COUNT * radFull); + for (int i = 0; i < 360; i += 90) + sin[(int)(i * degToIndex) & SIN_MASK] = (float)Math.Sin(i * degRad); + } - /// Returns the sine in radians from a lookup table. - static public float Sin (float radians) { - return sin[(int)(radians * radToIndex) & SIN_MASK]; - } + /// Returns the sine in radians from a lookup table. + static public float Sin(float radians) + { + return sin[(int)(radians * radToIndex) & SIN_MASK]; + } - /// Returns the cosine in radians from a lookup table. - static public float Cos (float radians) { - return sin[(int)((radians + PI / 2) * radToIndex) & SIN_MASK]; - } - - /// Returns the sine in radians from a lookup table. - static public float SinDeg (float degrees) { - return sin[(int)(degrees * degToIndex) & SIN_MASK]; - } - - /// Returns the cosine in radians from a lookup table. - static public float CosDeg (float degrees) { - return sin[(int)((degrees + 90) * degToIndex) & SIN_MASK]; - } + /// Returns the cosine in radians from a lookup table. + static public float Cos(float radians) + { + return sin[(int)((radians + PI / 2) * radToIndex) & SIN_MASK]; + } - /// 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 public float Atan2 (float y, float x) { - if (x == 0f) { - if (y > 0f) return PI / 2; - if (y == 0f) return 0f; - return -PI / 2; - } - float atan, z = y / x; - if (Math.Abs(z) < 1f) { - atan = z / (1f + 0.28f * z * z); - if (x < 0f) return atan + (y < 0f ? -PI : PI); - return atan; - } - atan = PI / 2 - z / (z * z + 0.28f); - return y < 0f ? atan - PI : atan; - } + /// Returns the sine in radians from a lookup table. + static public float SinDeg(float degrees) + { + return sin[(int)(degrees * degToIndex) & SIN_MASK]; + } - static public float Clamp (float value, float min, float max) { - if (value < min) return min; - if (value > max) return max; - return value; - } - } + /// Returns the cosine in radians from a lookup table. + static public float CosDeg(float degrees) + { + return sin[(int)((degrees + 90) * degToIndex) & SIN_MASK]; + } + + /// 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 public float Atan2(float y, float x) + { + if (x == 0f) + { + if (y > 0f) return PI / 2; + if (y == 0f) return 0f; + return -PI / 2; + } + float atan, z = y / x; + if (Math.Abs(z) < 1f) + { + atan = z / (1f + 0.28f * z * z); + if (x < 0f) return atan + (y < 0f ? -PI : PI); + return atan; + } + atan = PI / 2 - z / (z * z + 0.28f); + return y < 0f ? atan - PI : atan; + } + + static public float Clamp(float value, float min, float max) + { + if (value < min) return min; + if (value > max) return max; + return value; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/PathConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/PathConstraint.cs index 2bbfdf3..9cfcc94 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/PathConstraint.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/PathConstraint.cs @@ -30,370 +30,420 @@ using System; -namespace Spine3_4_02 { - public class PathConstraint : IUpdatable { - private const int NONE = -1, BEFORE = -2, AFTER = -3; +namespace Spine3_4_02 +{ + public class PathConstraint : IUpdatable + { + private const int NONE = -1, BEFORE = -2, AFTER = -3; - internal PathConstraintData data; - internal ExposedList bones; - internal Slot target; - internal float position, spacing, rotateMix, translateMix; + internal PathConstraintData data; + internal ExposedList bones; + internal Slot target; + internal float position, spacing, rotateMix, translateMix; - internal ExposedList spaces = new ExposedList(), positions = new ExposedList(); - internal ExposedList world = new ExposedList(), curves = new ExposedList(), lengths = new ExposedList(); - internal float[] segments = new float[10]; + internal ExposedList spaces = new ExposedList(), positions = new ExposedList(); + internal ExposedList world = new ExposedList(), curves = new ExposedList(), lengths = new ExposedList(); + internal float[] segments = new float[10]; - public float Position { get { return position; } set { position = value; } } - public float Spacing { get { return spacing; } set { spacing = value; } } - public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } - public float TranslateMix { get { return translateMix; } set { translateMix = value; } } - public ExposedList Bones { get { return bones; } } - public Slot Target { get { return target; } set { target = value; } } - public PathConstraintData Data { get { return data; } } + public float Position { get { return position; } set { position = value; } } + public float Spacing { get { return spacing; } set { spacing = value; } } + public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } + public float TranslateMix { get { return translateMix; } set { translateMix = value; } } + public ExposedList Bones { get { return bones; } } + public Slot Target { get { return target; } set { target = value; } } + public PathConstraintData Data { get { return data; } } - public PathConstraint (PathConstraintData data, Skeleton skeleton) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.data = data; - bones = new ExposedList(data.Bones.Count); - foreach (BoneData boneData in data.bones) - bones.Add(skeleton.FindBone(boneData.name)); - target = skeleton.FindSlot(data.target.name); - position = data.position; - spacing = data.spacing; - rotateMix = data.rotateMix; - translateMix = data.translateMix; - } + public PathConstraint(PathConstraintData data, Skeleton skeleton) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + bones = new ExposedList(data.Bones.Count); + foreach (BoneData boneData in data.bones) + bones.Add(skeleton.FindBone(boneData.name)); + target = skeleton.FindSlot(data.target.name); + position = data.position; + spacing = data.spacing; + rotateMix = data.rotateMix; + translateMix = data.translateMix; + } - public void Apply () { - Update(); - } - - public void Update () { - PathAttachment attachment = target.Attachment as PathAttachment; - if (attachment == null) return; + public void Apply() + { + Update(); + } - float rotateMix = this.rotateMix, translateMix = this.translateMix; - bool translate = translateMix > 0, rotate = rotateMix > 0; - if (!translate && !rotate) return; + public void Update() + { + PathAttachment attachment = target.Attachment as PathAttachment; + if (attachment == null) return; - PathConstraintData data = this.data; - SpacingMode spacingMode = data.spacingMode; - bool lengthSpacing = spacingMode == SpacingMode.Length; - RotateMode rotateMode = data.rotateMode; - bool tangents = rotateMode == RotateMode.Tangent, scale = rotateMode == RotateMode.ChainScale; - int boneCount = this.bones.Count, spacesCount = tangents ? boneCount : boneCount + 1; - Bone[] bones = this.bones.Items; - ExposedList spaces = this.spaces.Resize(spacesCount), lengths = null; - float spacing = this.spacing; - if (scale || lengthSpacing) { - if (scale) lengths = this.lengths.Resize(boneCount); - for (int i = 0, n = spacesCount - 1; i < n;) { - Bone bone = bones[i]; - float length = bone.data.length, x = length * bone.a, y = length * bone.c; - length = (float)Math.Sqrt(x * x + y * y); - if (scale) lengths.Items[i] = length; - spaces.Items[++i] = lengthSpacing ? Math.Max(0, length + spacing) : spacing; - } - } else { - for (int i = 1; i < spacesCount; i++) - spaces.Items[i] = spacing; - } + float rotateMix = this.rotateMix, translateMix = this.translateMix; + bool translate = translateMix > 0, rotate = rotateMix > 0; + if (!translate && !rotate) return; - float[] positions = ComputeWorldPositions(attachment, spacesCount, tangents, - data.positionMode == PositionMode.Percent, spacingMode == SpacingMode.Percent); - Skeleton skeleton = target.Skeleton; - float skeletonX = skeleton.x, skeletonY = skeleton.y; - float boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation; - bool tip = rotateMode == RotateMode.Chain && offsetRotation == 0; - for (int i = 0, p = 3; i < boneCount; i++, p += 3) { - Bone bone = (Bone)bones[i]; - bone.worldX += (boneX - skeletonX - bone.worldX) * translateMix; - bone.worldY += (boneY - skeletonY - bone.worldY) * translateMix; - float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY; - if (scale) { - float length = lengths.Items[i]; - if (length != 0) { - float s = ((float)Math.Sqrt(dx * dx + dy * dy) / length - 1) * rotateMix + 1; - bone.a *= s; - bone.c *= s; - } - } - boneX = x; - boneY = y; - if (rotate) { - 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.Items[i + 1] == 0) - r = positions[p + 2]; - else - r = MathUtils.Atan2(dy, dx); - r -= MathUtils.Atan2(c, a) - offsetRotation * MathUtils.degRad; - if (tip) { - cos = MathUtils.Cos(r); - sin = MathUtils.Sin(r); - float length = bone.data.length; - boneX += (length * (cos * a - sin * c) - dx) * rotateMix; - boneY += (length * (sin * a + cos * c) - dy) * rotateMix; - } - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) // - r += MathUtils.PI2; - r *= rotateMix; - cos = MathUtils.Cos(r); - sin = MathUtils.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; - } - } - } + PathConstraintData data = this.data; + SpacingMode spacingMode = data.spacingMode; + bool lengthSpacing = spacingMode == SpacingMode.Length; + RotateMode rotateMode = data.rotateMode; + bool tangents = rotateMode == RotateMode.Tangent, scale = rotateMode == RotateMode.ChainScale; + int boneCount = this.bones.Count, spacesCount = tangents ? boneCount : boneCount + 1; + Bone[] bones = this.bones.Items; + ExposedList spaces = this.spaces.Resize(spacesCount), lengths = null; + float spacing = this.spacing; + if (scale || lengthSpacing) + { + if (scale) lengths = this.lengths.Resize(boneCount); + for (int i = 0, n = spacesCount - 1; i < n;) + { + Bone bone = bones[i]; + float length = bone.data.length, x = length * bone.a, y = length * bone.c; + length = (float)Math.Sqrt(x * x + y * y); + if (scale) lengths.Items[i] = length; + spaces.Items[++i] = lengthSpacing ? Math.Max(0, length + spacing) : spacing; + } + } + else + { + for (int i = 1; i < spacesCount; i++) + spaces.Items[i] = spacing; + } - float[] ComputeWorldPositions (PathAttachment path, int spacesCount, bool tangents, bool percentPosition, - bool percentSpacing) { + float[] positions = ComputeWorldPositions(attachment, spacesCount, tangents, + data.positionMode == PositionMode.Percent, spacingMode == SpacingMode.Percent); + Skeleton skeleton = target.Skeleton; + float skeletonX = skeleton.x, skeletonY = skeleton.y; + float boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation; + bool tip = rotateMode == RotateMode.Chain && offsetRotation == 0; + for (int i = 0, p = 3; i < boneCount; i++, p += 3) + { + Bone bone = bones[i]; + bone.worldX += (boneX - skeletonX - bone.worldX) * translateMix; + bone.worldY += (boneY - skeletonY - bone.worldY) * translateMix; + float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY; + if (scale) + { + float length = lengths.Items[i]; + if (length != 0) + { + float s = ((float)Math.Sqrt(dx * dx + dy * dy) / length - 1) * rotateMix + 1; + bone.a *= s; + bone.c *= s; + } + } + boneX = x; + boneY = y; + if (rotate) + { + 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.Items[i + 1] == 0) + r = positions[p + 2]; + else + r = MathUtils.Atan2(dy, dx); + r -= MathUtils.Atan2(c, a) - offsetRotation * MathUtils.degRad; + if (tip) + { + cos = MathUtils.Cos(r); + sin = MathUtils.Sin(r); + float length = bone.data.length; + boneX += (length * (cos * a - sin * c) - dx) * rotateMix; + boneY += (length * (sin * a + cos * c) - dy) * rotateMix; + } + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) // + r += MathUtils.PI2; + r *= rotateMix; + cos = MathUtils.Cos(r); + sin = MathUtils.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; + } + } + } - Slot target = this.target; - float position = this.position; - float[] spaces = this.spaces.Items, output = this.positions.Resize(spacesCount * 3 + 2).Items, world; - bool closed = path.Closed; - int verticesLength = path.WorldVerticesLength, curveCount = verticesLength / 6, prevCurve = NONE; + float[] ComputeWorldPositions(PathAttachment path, int spacesCount, bool tangents, bool percentPosition, + bool percentSpacing) + { - float pathLength; - if (!path.ConstantSpeed) { - float[] lengths = path.Lengths; - curveCount -= closed ? 1 : 2; - pathLength = lengths[curveCount]; - if (percentPosition) position *= pathLength; - if (percentSpacing) { - for (int i = 0; i < spacesCount; i++) - spaces[i] *= pathLength; - } - world = this.world.Resize(8).Items; - for (int i = 0, o = 0, curve = 0; i < spacesCount; i++, o += 3) { - float space = spaces[i]; - position += space; - float p = position; + Slot target = this.target; + float position = this.position; + float[] spaces = this.spaces.Items, output = this.positions.Resize(spacesCount * 3 + 2).Items, world; + bool closed = path.Closed; + int verticesLength = path.WorldVerticesLength, curveCount = verticesLength / 6, prevCurve = NONE; - if (closed) { - 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, output, o); - continue; - } else if (p > pathLength) { - if (prevCurve != AFTER) { - prevCurve = AFTER; - path.ComputeWorldVertices(target, verticesLength - 6, 4, world, 0); - } - AddAfterPosition(p - pathLength, world, 0, output, o); - continue; - } + float pathLength; + if (!path.ConstantSpeed) + { + float[] lengths = path.Lengths; + curveCount -= closed ? 1 : 2; + pathLength = lengths[curveCount]; + if (percentPosition) position *= pathLength; + if (percentSpacing) + { + for (int i = 0; i < spacesCount; i++) + spaces[i] *= pathLength; + } + world = this.world.Resize(8).Items; + for (int i = 0, o = 0, curve = 0; i < spacesCount; i++, o += 3) + { + float space = spaces[i]; + position += space; + float p = position; - // 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], output, o, - tangents || (i > 0 && space == 0)); - } - return output; - } + if (closed) + { + 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, output, o); + continue; + } + else if (p > pathLength) + { + if (prevCurve != AFTER) + { + prevCurve = AFTER; + path.ComputeWorldVertices(target, verticesLength - 6, 4, world, 0); + } + AddAfterPosition(p - pathLength, world, 0, output, o); + continue; + } - // World vertices. - if (closed) { - verticesLength += 2; - world = this.world.Resize(verticesLength).Items; - 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 = this.world.Resize(verticesLength).Items; - path.ComputeWorldVertices(target, 2, verticesLength, world, 0); - } + // 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], output, o, + tangents || (i > 0 && space == 0)); + } + return output; + } - // Curve lengths. - float[] curves = this.curves.Resize(curveCount).Items; - 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 += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - dfx += ddfx; - dfy += ddfy; - ddfx += dddfx; - ddfy += dddfy; - pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - dfx += ddfx; - dfy += ddfy; - pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - dfx += ddfx + dddfx; - dfy += ddfy + dddfy; - pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - curves[i] = pathLength; - x1 = x2; - y1 = y2; - } - if (percentPosition) position *= pathLength; - if (percentSpacing) { - for (int i = 0; i < spacesCount; i++) - spaces[i] *= pathLength; - } + // World vertices. + if (closed) + { + verticesLength += 2; + world = this.world.Resize(verticesLength).Items; + 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 = this.world.Resize(verticesLength).Items; + path.ComputeWorldVertices(target, 2, verticesLength, world, 0); + } - float[] segments = this.segments; - float curveLength = 0; - for (int i = 0, o = 0, curve = 0, segment = 0; i < spacesCount; i++, o += 3) { - float space = spaces[i]; - position += space; - float p = position; + // Curve lengths. + float[] curves = this.curves.Resize(curveCount).Items; + 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 += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx; + dfy += ddfy; + pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx + dddfx; + dfy += ddfy + dddfy; + pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + curves[i] = pathLength; + x1 = x2; + y1 = y2; + } + if (percentPosition) position *= pathLength; + if (percentSpacing) + { + for (int i = 0; i < spacesCount; i++) + spaces[i] *= pathLength; + } - if (closed) { - p %= pathLength; - if (p < 0) p += pathLength; - curve = 0; - } else if (p < 0) { - AddBeforePosition(p, world, 0, output, o); - continue; - } else if (p > pathLength) { - AddAfterPosition(p - pathLength, world, verticesLength - 4, output, o); - continue; - } + float[] segments = this.segments; + float curveLength = 0; + for (int i = 0, o = 0, curve = 0, segment = 0; i < spacesCount; i++, o += 3) + { + float space = spaces[i]; + position += space; + float p = position; - // 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; - } + if (closed) + { + p %= pathLength; + if (p < 0) p += pathLength; + curve = 0; + } + else if (p < 0) + { + AddBeforePosition(p, world, 0, output, o); + continue; + } + else if (p > pathLength) + { + AddAfterPosition(p - pathLength, world, verticesLength - 4, output, o); + continue; + } - // 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 = (float)Math.Sqrt(dfx * dfx + dfy * dfy); - segments[0] = curveLength; - for (ii = 1; ii < 8; ii++) { - dfx += ddfx; - dfy += ddfy; - ddfx += dddfx; - ddfy += dddfy; - curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - segments[ii] = curveLength; - } - dfx += ddfx; - dfy += ddfy; - curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - segments[8] = curveLength; - dfx += ddfx + dddfx; - dfy += ddfy + dddfy; - curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - segments[9] = curveLength; - segment = 0; - } + // 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; + } - // 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, output, o, tangents || (i > 0 && space == 0)); - } - return output; - } + // 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 = (float)Math.Sqrt(dfx * dfx + dfy * dfy); + segments[0] = curveLength; + for (ii = 1; ii < 8; ii++) + { + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + segments[ii] = curveLength; + } + dfx += ddfx; + dfy += ddfy; + curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + segments[8] = curveLength; + dfx += ddfx + dddfx; + dfy += ddfy + dddfy; + curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + segments[9] = curveLength; + segment = 0; + } - private void AddBeforePosition (float p, float[] temp, int i, float[] output, int o) { - float x1 = temp[i], y1 = temp[i + 1], dx = temp[i + 2] - x1, dy = temp[i + 3] - y1, r = MathUtils.Atan2(dy, dx); - output[o] = x1 + p * MathUtils.Cos(r); - output[o + 1] = y1 + p * MathUtils.Sin(r); - output[o + 2] = r; - } + // 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, output, o, tangents || (i > 0 && space == 0)); + } + return output; + } - private void AddAfterPosition (float p, float[] temp, int i, float[] output, int o) { - float x1 = temp[i + 2], y1 = temp[i + 3], dx = x1 - temp[i], dy = y1 - temp[i + 1], r = MathUtils.Atan2(dy, dx); - output[o] = x1 + p * MathUtils.Cos(r); - output[o + 1] = y1 + p * MathUtils.Sin(r); - output[o + 2] = r; - } + private void AddBeforePosition(float p, float[] temp, int i, float[] output, int o) + { + float x1 = temp[i], y1 = temp[i + 1], dx = temp[i + 2] - x1, dy = temp[i + 3] - y1, r = MathUtils.Atan2(dy, dx); + output[o] = x1 + p * MathUtils.Cos(r); + output[o + 1] = y1 + p * MathUtils.Sin(r); + output[o + 2] = r; + } - private void AddCurvePosition (float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2, - float[] output, int o, bool tangents) { - if (p == 0) p = 0.0001f; - 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) output[o + 2] = (float)Math.Atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt)); - } - } + private void AddAfterPosition(float p, float[] temp, int i, float[] output, int o) + { + float x1 = temp[i + 2], y1 = temp[i + 3], dx = x1 - temp[i], dy = y1 - temp[i + 1], r = MathUtils.Atan2(dy, dx); + output[o] = x1 + p * MathUtils.Cos(r); + output[o + 1] = y1 + p * MathUtils.Sin(r); + output[o + 2] = r; + } + + private void AddCurvePosition(float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2, + float[] output, int o, bool tangents) + { + if (p == 0) p = 0.0001f; + 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) output[o + 2] = (float)Math.Atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt)); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/PathConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/PathConstraintData.cs index 1baec8d..158add6 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/PathConstraintData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/PathConstraintData.cs @@ -30,44 +30,50 @@ using System; -namespace Spine3_4_02 { - public class PathConstraintData { - internal String name; - internal ExposedList bones = new ExposedList(); - internal SlotData target; - internal PositionMode positionMode; - internal SpacingMode spacingMode; - internal RotateMode rotateMode; - internal float offsetRotation; - internal float position, spacing, rotateMix, translateMix; +namespace Spine3_4_02 +{ + public class PathConstraintData + { + internal String name; + internal ExposedList bones = new ExposedList(); + internal SlotData target; + internal PositionMode positionMode; + internal SpacingMode spacingMode; + internal RotateMode rotateMode; + internal float offsetRotation; + internal float position, spacing, rotateMix, translateMix; - public ExposedList Bones { get { return bones; } } - public SlotData Target { get { return target; } set { target = value; } } - public PositionMode PositionMode { get { return positionMode; } set { positionMode = value; } } - public SpacingMode SpacingMode { get { return spacingMode; } set { spacingMode = value; } } - public RotateMode RotateMode { get { return rotateMode; } set { rotateMode = value; } } - public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } - public float Position { get { return position; } set { position = value; } } - public float Spacing { get { return spacing; } set { spacing = value; } } - public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } - public float TranslateMix { get { return translateMix; } set { translateMix = value; } } - public String Name { get { return name; } } + public ExposedList Bones { get { return bones; } } + public SlotData Target { get { return target; } set { target = value; } } + public PositionMode PositionMode { get { return positionMode; } set { positionMode = value; } } + public SpacingMode SpacingMode { get { return spacingMode; } set { spacingMode = value; } } + public RotateMode RotateMode { get { return rotateMode; } set { rotateMode = value; } } + public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } + public float Position { get { return position; } set { position = value; } } + public float Spacing { get { return spacing; } set { spacing = value; } } + public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } + public float TranslateMix { get { return translateMix; } set { translateMix = value; } } + public String Name { get { return name; } } - public PathConstraintData (String name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; - } - } - - public enum PositionMode { - Fixed, Percent - } + public PathConstraintData(String name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + } + } - public enum SpacingMode { - Length, Fixed, Percent - } + public enum PositionMode + { + Fixed, Percent + } - public enum RotateMode { - Tangent, Chain, ChainScale - } + public enum SpacingMode + { + Length, Fixed, Percent + } + + public enum RotateMode + { + Tangent, Chain, ChainScale + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Skeleton.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Skeleton.cs index b326b1b..ce4ed22 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Skeleton.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Skeleton.cs @@ -29,429 +29,484 @@ *****************************************************************************/ using System; -using System.Collections.Generic; - -namespace Spine3_4_02 { - public class Skeleton { - internal SkeletonData data; - internal ExposedList bones; - internal ExposedList slots; - internal ExposedList drawOrder; - internal ExposedList ikConstraints, ikConstraintsSorted; - internal ExposedList transformConstraints; - internal ExposedList pathConstraints; - internal ExposedList updateCache = new ExposedList(); - internal Skin skin; - internal float r = 1, g = 1, b = 1, a = 1; - internal float time; - internal bool flipX, flipY; - internal float x, y; - - public SkeletonData Data { get { return data; } } - public ExposedList Bones { get { return bones; } } - public ExposedList UpdateCacheList { get { return updateCache; } } - public ExposedList Slots { get { return slots; } } - public ExposedList DrawOrder { get { return drawOrder; } } - public ExposedList IkConstraints { get { return ikConstraints; } } - public ExposedList PathConstraints { get { return pathConstraints; } } - public ExposedList TransformConstraints { get { return transformConstraints; } } - public Skin Skin { get { return skin; } set { skin = value; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } - public float Time { get { return time; } set { time = value; } } - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public bool FlipX { get { return flipX; } set { flipX = value; } } - public bool FlipY { get { return flipY; } set { flipY = value; } } - - public Bone RootBone { - get { return bones.Count == 0 ? null : bones.Items[0]; } - } - - public Skeleton (SkeletonData data) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - this.data = data; - - bones = new ExposedList(data.bones.Count); - foreach (BoneData boneData in data.bones) { - Bone bone; - if (boneData.parent == null) { - bone = new Bone(boneData, this, null); - } else { - Bone parent = bones.Items[boneData.parent.index]; - bone = new Bone(boneData, this, parent); - parent.children.Add(bone); - } - bones.Add(bone); - } - - slots = new ExposedList(data.slots.Count); - drawOrder = new ExposedList(data.slots.Count); - foreach (SlotData slotData in data.slots) { - Bone bone = bones.Items[slotData.boneData.index]; - Slot slot = new Slot(slotData, bone); - slots.Add(slot); - drawOrder.Add(slot); - } - - ikConstraints = new ExposedList(data.ikConstraints.Count); - ikConstraintsSorted = new ExposedList(data.ikConstraints.Count); - foreach (IkConstraintData ikConstraintData in data.ikConstraints) - ikConstraints.Add(new IkConstraint(ikConstraintData, this)); - - transformConstraints = new ExposedList(data.transformConstraints.Count); - foreach (TransformConstraintData transformConstraintData in data.transformConstraints) - transformConstraints.Add(new TransformConstraint(transformConstraintData, this)); - - pathConstraints = new ExposedList (data.pathConstraints.Count); - foreach (PathConstraintData pathConstraintData in data.pathConstraints) - pathConstraints.Add(new PathConstraint(pathConstraintData, this)); - - UpdateCache(); - UpdateWorldTransform(); - } - - /// Caches information about bones and constraints. Must be called if bones, constraints or weighted path attachments are added - /// or removed. - public void UpdateCache () { - ExposedList updateCache = this.updateCache; - updateCache.Clear(); - - ExposedList bones = this.bones; - for (int i = 0, n = bones.Count; i < n; i++) - bones.Items[i].sorted = false; - - ExposedList ikConstraints = this.ikConstraintsSorted; - ikConstraints.Clear(); - ikConstraints.AddRange(this.ikConstraints); - int ikCount = ikConstraints.Count; - for (int i = 0, level, n = ikCount; i < n; i++) { - IkConstraint ik = ikConstraints.Items[i]; - Bone bone = ik.bones.Items[0].parent; - for (level = 0; bone != null; level++) - bone = bone.parent; - ik.level = level; - } - for (int i = 1, ii; i < ikCount; i++) { - IkConstraint ik = ikConstraints.Items[i]; - int level = ik.level; - for (ii = i - 1; ii >= 0; ii--) { - IkConstraint other = ikConstraints.Items[ii]; - if (other.level < level) break; - ikConstraints.Items[ii + 1] = other; - } - ikConstraints.Items[ii + 1] = ik; - } - for (int i = 0, n = ikConstraints.Count; i < n; i++) { - IkConstraint constraint = ikConstraints.Items[i]; - Bone target = constraint.target; - SortBone(target); - - ExposedList constrained = constraint.bones; - Bone parent = constrained.Items[0]; - SortBone(parent); - - updateCache.Add(constraint); - - SortReset(parent.children); - constrained.Items[constrained.Count - 1].sorted = true; - } - - ExposedList pathConstraints = this.pathConstraints; - for (int i = 0, n = pathConstraints.Count; i < n; i++) { - PathConstraint constraint = pathConstraints.Items[i]; - - Slot slot = constraint.target; - int slotIndex = slot.data.index; - Bone slotBone = slot.bone; - if (skin != null) SortPathConstraintAttachment(skin, slotIndex, slotBone); - if (data.defaultSkin != null && data.defaultSkin != skin) - SortPathConstraintAttachment(data.defaultSkin, slotIndex, slotBone); - for (int ii = 0, nn = data.skins.Count; ii < nn; ii++) - SortPathConstraintAttachment(data.skins.Items[ii], slotIndex, slotBone); - - PathAttachment attachment = slot.Attachment as PathAttachment; - if (attachment != null) SortPathConstraintAttachment(attachment, slotBone); - - ExposedList constrained = constraint.bones; - int boneCount = constrained.Count; - for (int ii = 0; ii < boneCount; ii++) - SortBone(constrained.Items[ii]); - - updateCache.Add(constraint); - - for (int ii = 0; ii < boneCount; ii++) - SortReset(constrained.Items[ii].children); - for (int ii = 0; ii < boneCount; ii++) - constrained.Items[ii].sorted = true; - } - - ExposedList transformConstraints = this.transformConstraints; - for (int i = 0, n = transformConstraints.Count; i < n; i++) { - TransformConstraint constraint = transformConstraints.Items[i]; - - SortBone(constraint.target); - - ExposedList constrained = constraint.bones; - int boneCount = constrained.Count; - for (int ii = 0; ii < boneCount; ii++) - SortBone(constrained.Items[ii]); - - updateCache.Add(constraint); - - for (int ii = 0; ii < boneCount; ii++) - SortReset(constrained.Items[ii].children); - for (int ii = 0; ii < boneCount; ii++) - constrained.Items[ii].sorted = true; - } - - for (int i = 0, n = bones.Count; i < n; i++) - SortBone(bones.Items[i]); - } - - private void SortPathConstraintAttachment (Skin skin, int slotIndex, Bone slotBone) { - foreach (var entry in skin.Attachments) - if (entry.Key.slotIndex == slotIndex) SortPathConstraintAttachment(entry.Value, slotBone); - } - - private void SortPathConstraintAttachment (Attachment attachment, Bone slotBone) { - var pathAttachment = attachment as PathAttachment; - if (pathAttachment == null) return; - int[] pathBones = pathAttachment.bones; - if (pathBones == null) - SortBone(slotBone); - else { - var bonesItems = this.bones.Items; - for (int i = 0, n = pathBones.Length; i < n;) { - int nn = pathBones[i++]; - nn += i; - while (i < nn) - SortBone(bonesItems[pathBones[i++]]); - } - } - } - - private void SortBone (Bone bone) { - if (bone.sorted) return; - Bone parent = bone.parent; - if (parent != null) SortBone(parent); - bone.sorted = true; - updateCache.Add(bone); - } - - private void SortReset (ExposedList bones) { - var bonesItems = bones.Items; - for (int i = 0, n = bones.Count; i < n; i++) { - Bone bone = bonesItems[i]; - if (bone.sorted) SortReset(bone.children); - bone.sorted = false; - } - } - - /// Updates the world transform for each bone and applies constraints. - public void UpdateWorldTransform () { - var updateItems = this.updateCache.Items; - for (int i = 0, n = updateCache.Count; i < n; i++) - updateItems[i].Update(); - } - - /// Sets the bones, constraints, and slots to their setup pose values. - public void SetToSetupPose () { - SetBonesToSetupPose(); - SetSlotsToSetupPose(); - } - - /// Sets the bones and constraints to their setup pose values. - public void SetBonesToSetupPose () { - var bonesItems = this.bones.Items; - for (int i = 0, n = bones.Count; i < n; i++) - bonesItems[i].SetToSetupPose(); - - var ikConstraintsItems = this.ikConstraints.Items; - for (int i = 0, n = ikConstraints.Count; i < n; i++) { - IkConstraint constraint = ikConstraintsItems[i]; - constraint.bendDirection = constraint.data.bendDirection; - constraint.mix = constraint.data.mix; - } - - var transformConstraintsItems = this.transformConstraints.Items; - for (int i = 0, n = transformConstraints.Count; i < n; i++) { - TransformConstraint constraint = transformConstraintsItems[i]; - TransformConstraintData data = constraint.data; - constraint.rotateMix = data.rotateMix; - constraint.translateMix = data.translateMix; - constraint.scaleMix = data.scaleMix; - constraint.shearMix = data.shearMix; - } - - var pathConstraintItems = this.pathConstraints.Items; - for (int i = 0, n = pathConstraints.Count; i < n; i++) { - PathConstraint constraint = pathConstraintItems[i]; - PathConstraintData data = constraint.data; - constraint.position = data.position; - constraint.spacing = data.spacing; - constraint.rotateMix = data.rotateMix; - constraint.translateMix = data.translateMix; - } - } - - public void SetSlotsToSetupPose () { - var slots = this.slots; - var slotsItems = slots.Items; - drawOrder.Clear(); - for (int i = 0, n = slots.Count; i < n; i++) - drawOrder.Add(slotsItems[i]); - - for (int i = 0, n = slots.Count; i < n; i++) - slotsItems[i].SetToSetupPose(); - } - - /// May be null. - public Bone FindBone (String boneName) { - if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); - var bones = this.bones; - var bonesItems = bones.Items; - for (int i = 0, n = bones.Count; i < n; i++) { - Bone bone = bonesItems[i]; - if (bone.data.name == boneName) return bone; - } - return null; - } - - /// -1 if the bone was not found. - public int FindBoneIndex (String boneName) { - if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); - var bones = this.bones; - var bonesItems = bones.Items; - for (int i = 0, n = bones.Count; i < n; i++) - if (bonesItems[i].data.name == boneName) return i; - return -1; - } - - /// May be null. - public Slot FindSlot (String slotName) { - if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); - var slots = this.slots; - var slotsItems = slots.Items; - for (int i = 0, n = slots.Count; i < n; i++) { - Slot slot = slotsItems[i]; - if (slot.data.name == slotName) return slot; - } - return null; - } - - /// -1 if the bone was not found. - public int FindSlotIndex (String slotName) { - if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); - var slots = this.slots; - var slotsItems = slots.Items; - for (int i = 0, n = slots.Count; i < n; i++) - if (slotsItems[i].data.name.Equals(slotName)) return i; - return -1; - } - - /// Sets a skin by name (see SetSkin). - public void SetSkin (String skinName) { - Skin skin = data.FindSkin(skinName); - if (skin == null) throw new ArgumentException("Skin not found: " + skinName, "skinName"); - SetSkin(skin); - } - - /// Sets the skin used to look up attachments before looking in the {@link SkeletonData#getDefaultSkin() default - /// skin}. Attachmentsfrom 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. - /// May be null. - public void SetSkin (Skin newSkin) { - if (newSkin != null) { - if (skin != null) - newSkin.AttachAll(this, skin); - else { - ExposedList slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) { - Slot slot = slots.Items[i]; - String name = slot.data.attachmentName; - if (name != null) { - Attachment attachment = newSkin.GetAttachment(i, name); - if (attachment != null) slot.Attachment = attachment; - } - } - } - } - skin = newSkin; - } - - /// May be null. - public Attachment GetAttachment (String slotName, String attachmentName) { - return GetAttachment(data.FindSlotIndex(slotName), attachmentName); - } - - /// May be null. - public Attachment GetAttachment (int slotIndex, String attachmentName) { - if (attachmentName == null) throw new ArgumentNullException("attachmentName", "attachmentName cannot be null."); - if (skin != null) { - Attachment attachment = skin.GetAttachment(slotIndex, attachmentName); - if (attachment != null) return attachment; - } - if (data.defaultSkin != null) return data.defaultSkin.GetAttachment(slotIndex, attachmentName); - return null; - } - - /// May be null. - public void SetAttachment (String slotName, String attachmentName) { - if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); - ExposedList slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) { - Slot slot = slots.Items[i]; - if (slot.data.name == slotName) { - Attachment attachment = null; - if (attachmentName != null) { - attachment = GetAttachment(i, attachmentName); - if (attachment == null) throw new Exception("Attachment not found: " + attachmentName + ", for slot: " + slotName); - } - slot.Attachment = attachment; - return; - } - } - throw new Exception("Slot not found: " + slotName); - } - - /// May be null. - public IkConstraint FindIkConstraint (String constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - ExposedList ikConstraints = this.ikConstraints; - for (int i = 0, n = ikConstraints.Count; i < n; i++) { - IkConstraint ikConstraint = ikConstraints.Items[i]; - if (ikConstraint.data.name == constraintName) return ikConstraint; - } - return null; - } - - /// May be null. - public TransformConstraint FindTransformConstraint (String constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - ExposedList transformConstraints = this.transformConstraints; - for (int i = 0, n = transformConstraints.Count; i < n; i++) { - TransformConstraint transformConstraint = transformConstraints.Items[i]; - if (transformConstraint.data.name == constraintName) return transformConstraint; - } - return null; - } - - /// May be null. - public PathConstraint FindPathConstraint (String constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - ExposedList pathConstraints = this.pathConstraints; - for (int i = 0, n = pathConstraints.Count; i < n; i++) { - PathConstraint constraint = pathConstraints.Items[i]; - if (constraint.data.name.Equals(constraintName)) return constraint; - } - return null; - } - - public void Update (float delta) { - time += delta; - } - } + +namespace Spine3_4_02 +{ + public class Skeleton + { + internal SkeletonData data; + internal ExposedList bones; + internal ExposedList slots; + internal ExposedList drawOrder; + internal ExposedList ikConstraints, ikConstraintsSorted; + internal ExposedList transformConstraints; + internal ExposedList pathConstraints; + internal ExposedList updateCache = new ExposedList(); + internal Skin skin; + internal float r = 1, g = 1, b = 1, a = 1; + internal float time; + internal bool flipX, flipY; + internal float x, y; + + public SkeletonData Data { get { return data; } } + public ExposedList Bones { get { return bones; } } + public ExposedList UpdateCacheList { get { return updateCache; } } + public ExposedList Slots { get { return slots; } } + public ExposedList DrawOrder { get { return drawOrder; } } + public ExposedList IkConstraints { get { return ikConstraints; } } + public ExposedList PathConstraints { get { return pathConstraints; } } + public ExposedList TransformConstraints { get { return transformConstraints; } } + public Skin Skin { get { return skin; } set { skin = value; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } + public float Time { get { return time; } set { time = value; } } + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public bool FlipX { get { return flipX; } set { flipX = value; } } + public bool FlipY { get { return flipY; } set { flipY = value; } } + + public Bone RootBone + { + get { return bones.Count == 0 ? null : bones.Items[0]; } + } + + public Skeleton(SkeletonData data) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + this.data = data; + + bones = new ExposedList(data.bones.Count); + foreach (BoneData boneData in data.bones) + { + Bone bone; + if (boneData.parent == null) + { + bone = new Bone(boneData, this, null); + } + else + { + Bone parent = bones.Items[boneData.parent.index]; + bone = new Bone(boneData, this, parent); + parent.children.Add(bone); + } + bones.Add(bone); + } + + slots = new ExposedList(data.slots.Count); + drawOrder = new ExposedList(data.slots.Count); + foreach (SlotData slotData in data.slots) + { + Bone bone = bones.Items[slotData.boneData.index]; + Slot slot = new Slot(slotData, bone); + slots.Add(slot); + drawOrder.Add(slot); + } + + ikConstraints = new ExposedList(data.ikConstraints.Count); + ikConstraintsSorted = new ExposedList(data.ikConstraints.Count); + foreach (IkConstraintData ikConstraintData in data.ikConstraints) + ikConstraints.Add(new IkConstraint(ikConstraintData, this)); + + transformConstraints = new ExposedList(data.transformConstraints.Count); + foreach (TransformConstraintData transformConstraintData in data.transformConstraints) + transformConstraints.Add(new TransformConstraint(transformConstraintData, this)); + + pathConstraints = new ExposedList(data.pathConstraints.Count); + foreach (PathConstraintData pathConstraintData in data.pathConstraints) + pathConstraints.Add(new PathConstraint(pathConstraintData, this)); + + UpdateCache(); + UpdateWorldTransform(); + } + + /// Caches information about bones and constraints. Must be called if bones, constraints or weighted path attachments are added + /// or removed. + public void UpdateCache() + { + ExposedList updateCache = this.updateCache; + updateCache.Clear(); + + ExposedList bones = this.bones; + for (int i = 0, n = bones.Count; i < n; i++) + bones.Items[i].sorted = false; + + ExposedList ikConstraints = this.ikConstraintsSorted; + ikConstraints.Clear(); + ikConstraints.AddRange(this.ikConstraints); + int ikCount = ikConstraints.Count; + for (int i = 0, level, n = ikCount; i < n; i++) + { + IkConstraint ik = ikConstraints.Items[i]; + Bone bone = ik.bones.Items[0].parent; + for (level = 0; bone != null; level++) + bone = bone.parent; + ik.level = level; + } + for (int i = 1, ii; i < ikCount; i++) + { + IkConstraint ik = ikConstraints.Items[i]; + int level = ik.level; + for (ii = i - 1; ii >= 0; ii--) + { + IkConstraint other = ikConstraints.Items[ii]; + if (other.level < level) break; + ikConstraints.Items[ii + 1] = other; + } + ikConstraints.Items[ii + 1] = ik; + } + for (int i = 0, n = ikConstraints.Count; i < n; i++) + { + IkConstraint constraint = ikConstraints.Items[i]; + Bone target = constraint.target; + SortBone(target); + + ExposedList constrained = constraint.bones; + Bone parent = constrained.Items[0]; + SortBone(parent); + + updateCache.Add(constraint); + + SortReset(parent.children); + constrained.Items[constrained.Count - 1].sorted = true; + } + + ExposedList pathConstraints = this.pathConstraints; + for (int i = 0, n = pathConstraints.Count; i < n; i++) + { + PathConstraint constraint = pathConstraints.Items[i]; + + Slot slot = constraint.target; + int slotIndex = slot.data.index; + Bone slotBone = slot.bone; + if (skin != null) SortPathConstraintAttachment(skin, slotIndex, slotBone); + if (data.defaultSkin != null && data.defaultSkin != skin) + SortPathConstraintAttachment(data.defaultSkin, slotIndex, slotBone); + for (int ii = 0, nn = data.skins.Count; ii < nn; ii++) + SortPathConstraintAttachment(data.skins.Items[ii], slotIndex, slotBone); + + PathAttachment attachment = slot.Attachment as PathAttachment; + if (attachment != null) SortPathConstraintAttachment(attachment, slotBone); + + ExposedList constrained = constraint.bones; + int boneCount = constrained.Count; + for (int ii = 0; ii < boneCount; ii++) + SortBone(constrained.Items[ii]); + + updateCache.Add(constraint); + + for (int ii = 0; ii < boneCount; ii++) + SortReset(constrained.Items[ii].children); + for (int ii = 0; ii < boneCount; ii++) + constrained.Items[ii].sorted = true; + } + + ExposedList transformConstraints = this.transformConstraints; + for (int i = 0, n = transformConstraints.Count; i < n; i++) + { + TransformConstraint constraint = transformConstraints.Items[i]; + + SortBone(constraint.target); + + ExposedList constrained = constraint.bones; + int boneCount = constrained.Count; + for (int ii = 0; ii < boneCount; ii++) + SortBone(constrained.Items[ii]); + + updateCache.Add(constraint); + + for (int ii = 0; ii < boneCount; ii++) + SortReset(constrained.Items[ii].children); + for (int ii = 0; ii < boneCount; ii++) + constrained.Items[ii].sorted = true; + } + + for (int i = 0, n = bones.Count; i < n; i++) + SortBone(bones.Items[i]); + } + + private void SortPathConstraintAttachment(Skin skin, int slotIndex, Bone slotBone) + { + foreach (var entry in skin.Attachments) + if (entry.Key.slotIndex == slotIndex) SortPathConstraintAttachment(entry.Value, slotBone); + } + + private void SortPathConstraintAttachment(Attachment attachment, Bone slotBone) + { + var pathAttachment = attachment as PathAttachment; + if (pathAttachment == null) return; + int[] pathBones = pathAttachment.bones; + if (pathBones == null) + SortBone(slotBone); + else + { + var bonesItems = this.bones.Items; + for (int i = 0, n = pathBones.Length; i < n;) + { + int nn = pathBones[i++]; + nn += i; + while (i < nn) + SortBone(bonesItems[pathBones[i++]]); + } + } + } + + private void SortBone(Bone bone) + { + if (bone.sorted) return; + Bone parent = bone.parent; + if (parent != null) SortBone(parent); + bone.sorted = true; + updateCache.Add(bone); + } + + private void SortReset(ExposedList bones) + { + var bonesItems = bones.Items; + for (int i = 0, n = bones.Count; i < n; i++) + { + Bone bone = bonesItems[i]; + if (bone.sorted) SortReset(bone.children); + bone.sorted = false; + } + } + + /// Updates the world transform for each bone and applies constraints. + public void UpdateWorldTransform() + { + var updateItems = this.updateCache.Items; + for (int i = 0, n = updateCache.Count; i < n; i++) + updateItems[i].Update(); + } + + /// Sets the bones, constraints, and slots to their setup pose values. + public void SetToSetupPose() + { + SetBonesToSetupPose(); + SetSlotsToSetupPose(); + } + + /// Sets the bones and constraints to their setup pose values. + public void SetBonesToSetupPose() + { + var bonesItems = this.bones.Items; + for (int i = 0, n = bones.Count; i < n; i++) + bonesItems[i].SetToSetupPose(); + + var ikConstraintsItems = this.ikConstraints.Items; + for (int i = 0, n = ikConstraints.Count; i < n; i++) + { + IkConstraint constraint = ikConstraintsItems[i]; + constraint.bendDirection = constraint.data.bendDirection; + constraint.mix = constraint.data.mix; + } + + var transformConstraintsItems = this.transformConstraints.Items; + for (int i = 0, n = transformConstraints.Count; i < n; i++) + { + TransformConstraint constraint = transformConstraintsItems[i]; + TransformConstraintData data = constraint.data; + constraint.rotateMix = data.rotateMix; + constraint.translateMix = data.translateMix; + constraint.scaleMix = data.scaleMix; + constraint.shearMix = data.shearMix; + } + + var pathConstraintItems = this.pathConstraints.Items; + for (int i = 0, n = pathConstraints.Count; i < n; i++) + { + PathConstraint constraint = pathConstraintItems[i]; + PathConstraintData data = constraint.data; + constraint.position = data.position; + constraint.spacing = data.spacing; + constraint.rotateMix = data.rotateMix; + constraint.translateMix = data.translateMix; + } + } + + public void SetSlotsToSetupPose() + { + var slots = this.slots; + var slotsItems = slots.Items; + drawOrder.Clear(); + for (int i = 0, n = slots.Count; i < n; i++) + drawOrder.Add(slotsItems[i]); + + for (int i = 0, n = slots.Count; i < n; i++) + slotsItems[i].SetToSetupPose(); + } + + /// May be null. + public Bone FindBone(String boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); + var bones = this.bones; + var bonesItems = bones.Items; + for (int i = 0, n = bones.Count; i < n; i++) + { + Bone bone = bonesItems[i]; + if (bone.data.name == boneName) return bone; + } + return null; + } + + /// -1 if the bone was not found. + public int FindBoneIndex(String boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); + var bones = this.bones; + var bonesItems = bones.Items; + for (int i = 0, n = bones.Count; i < n; i++) + if (bonesItems[i].data.name == boneName) return i; + return -1; + } + + /// May be null. + public Slot FindSlot(String slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + var slots = this.slots; + var slotsItems = slots.Items; + for (int i = 0, n = slots.Count; i < n; i++) + { + Slot slot = slotsItems[i]; + if (slot.data.name == slotName) return slot; + } + return null; + } + + /// -1 if the bone was not found. + public int FindSlotIndex(String slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + var slots = this.slots; + var slotsItems = slots.Items; + for (int i = 0, n = slots.Count; i < n; i++) + if (slotsItems[i].data.name.Equals(slotName)) return i; + return -1; + } + + /// Sets a skin by name (see SetSkin). + public void SetSkin(String skinName) + { + Skin skin = data.FindSkin(skinName); + if (skin == null) throw new ArgumentException("Skin not found: " + skinName, "skinName"); + SetSkin(skin); + } + + /// Sets the skin used to look up attachments before looking in the {@link SkeletonData#getDefaultSkin() default + /// skin}. Attachmentsfrom 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. + /// May be null. + public void SetSkin(Skin newSkin) + { + if (newSkin != null) + { + if (skin != null) + newSkin.AttachAll(this, skin); + else + { + ExposedList slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + { + Slot slot = slots.Items[i]; + String name = slot.data.attachmentName; + if (name != null) + { + Attachment attachment = newSkin.GetAttachment(i, name); + if (attachment != null) slot.Attachment = attachment; + } + } + } + } + skin = newSkin; + } + + /// May be null. + public Attachment GetAttachment(String slotName, String attachmentName) + { + return GetAttachment(data.FindSlotIndex(slotName), attachmentName); + } + + /// May be null. + public Attachment GetAttachment(int slotIndex, String attachmentName) + { + if (attachmentName == null) throw new ArgumentNullException("attachmentName", "attachmentName cannot be null."); + if (skin != null) + { + Attachment attachment = skin.GetAttachment(slotIndex, attachmentName); + if (attachment != null) return attachment; + } + if (data.defaultSkin != null) return data.defaultSkin.GetAttachment(slotIndex, attachmentName); + return null; + } + + /// May be null. + public void SetAttachment(String slotName, String attachmentName) + { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + ExposedList slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + { + Slot slot = slots.Items[i]; + if (slot.data.name == slotName) + { + Attachment attachment = null; + if (attachmentName != null) + { + attachment = GetAttachment(i, attachmentName); + if (attachment == null) throw new Exception("Attachment not found: " + attachmentName + ", for slot: " + slotName); + } + slot.Attachment = attachment; + return; + } + } + throw new Exception("Slot not found: " + slotName); + } + + /// May be null. + public IkConstraint FindIkConstraint(String constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + ExposedList ikConstraints = this.ikConstraints; + for (int i = 0, n = ikConstraints.Count; i < n; i++) + { + IkConstraint ikConstraint = ikConstraints.Items[i]; + if (ikConstraint.data.name == constraintName) return ikConstraint; + } + return null; + } + + /// May be null. + public TransformConstraint FindTransformConstraint(String constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + ExposedList transformConstraints = this.transformConstraints; + for (int i = 0, n = transformConstraints.Count; i < n; i++) + { + TransformConstraint transformConstraint = transformConstraints.Items[i]; + if (transformConstraint.data.name == constraintName) return transformConstraint; + } + return null; + } + + /// May be null. + public PathConstraint FindPathConstraint(String constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + ExposedList pathConstraints = this.pathConstraints; + for (int i = 0, n = pathConstraints.Count; i < n; i++) + { + PathConstraint constraint = pathConstraints.Items[i]; + if (constraint.data.name.Equals(constraintName)) return constraint; + } + return null; + } + + public void Update(float delta) + { + time += delta; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/SkeletonBinary.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/SkeletonBinary.cs index 9cc40fe..474ac83 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/SkeletonBinary.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/SkeletonBinary.cs @@ -33,49 +33,53 @@ #endif using System; -using System.IO; using System.Collections.Generic; +using System.IO; #if WINDOWS_STOREAPP using System.Threading.Tasks; using Windows.Storage; #endif -namespace Spine3_4_02 { - public class SkeletonBinary { - public const int BONE_ROTATE = 0; - public const int BONE_TRANSLATE = 1; - public const int BONE_SCALE = 2; - public const int BONE_SHEAR = 3; - - public const int SLOT_ATTACHMENT = 0; - public const int SLOT_COLOR = 1; - - public const int PATH_POSITION = 0; - public const int PATH_SPACING = 1; - public const int PATH_MIX = 2; - - public const int CURVE_LINEAR = 0; - public const int CURVE_STEPPED = 1; - public const int CURVE_BEZIER = 2; - - public float Scale { get; set; } - - private AttachmentLoader attachmentLoader; - private byte[] buffer = new byte[32]; - private List linkedMeshes = new List(); - - public SkeletonBinary (params Atlas[] atlasArray) - : this(new AtlasAttachmentLoader(atlasArray)) { - } - - public SkeletonBinary (AttachmentLoader attachmentLoader) { - if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader"); - this.attachmentLoader = attachmentLoader; - Scale = 1; - } - - #if !ISUNITY && WINDOWS_STOREAPP +namespace Spine3_4_02 +{ + public class SkeletonBinary + { + public const int BONE_ROTATE = 0; + public const int BONE_TRANSLATE = 1; + public const int BONE_SCALE = 2; + public const int BONE_SHEAR = 3; + + public const int SLOT_ATTACHMENT = 0; + public const int SLOT_COLOR = 1; + + public const int PATH_POSITION = 0; + public const int PATH_SPACING = 1; + public const int PATH_MIX = 2; + + public const int CURVE_LINEAR = 0; + public const int CURVE_STEPPED = 1; + public const int CURVE_BEZIER = 2; + + public float Scale { get; set; } + + private AttachmentLoader attachmentLoader; + private byte[] buffer = new byte[32]; + private List linkedMeshes = new List(); + + public SkeletonBinary(params Atlas[] atlasArray) + : this(new AtlasAttachmentLoader(atlasArray)) + { + } + + public SkeletonBinary(AttachmentLoader attachmentLoader) + { + if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader"); + this.attachmentLoader = attachmentLoader; + Scale = 1; + } + +#if !ISUNITY && WINDOWS_STOREAPP private async Task ReadFile(string path) { var folder = Windows.ApplicationModel.Package.Current.InstalledLocation; using (var input = new BufferedStream(await folder.GetFileAsync(path).AsTask().ConfigureAwait(false))) { @@ -88,709 +92,802 @@ private async Task ReadFile(string path) { public SkeletonData ReadSkeletonData (String path) { return this.ReadFile(path).Result; } - #else - public SkeletonData ReadSkeletonData (String path) { - #if WINDOWS_PHONE +#else + public SkeletonData ReadSkeletonData(String path) + { +#if WINDOWS_PHONE using (var input = new BufferedStream(Microsoft.Xna.Framework.TitleContainer.OpenStream(path))) { - #else - using (var input = new BufferedStream(new FileStream(path, FileMode.Open))) { - #endif // WINDOWS_PHONE - SkeletonData skeletonData = ReadSkeletonData(input); - skeletonData.name = Path.GetFileNameWithoutExtension(path); - return skeletonData; - } - } - - #endif // WINDOWS_STOREAPP - - public SkeletonData ReadSkeletonData (Stream input) { - if (input == null) throw new ArgumentNullException("input"); - float scale = Scale; - - var skeletonData = new SkeletonData(); - skeletonData.hash = ReadString(input); - if (skeletonData.hash.Length == 0) skeletonData.hash = null; - skeletonData.version = ReadString(input); - if (skeletonData.version.Length == 0) skeletonData.version = null; - skeletonData.width = ReadFloat(input); - skeletonData.height = ReadFloat(input); - - bool nonessential = ReadBoolean(input); - - if (nonessential) { - skeletonData.imagesPath = ReadString(input); - if (skeletonData.imagesPath.Length == 0) skeletonData.imagesPath = null; - } - - // Bones. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - String name = ReadString(input); - BoneData parent = i == 0 ? null : skeletonData.bones.Items[ReadVarint(input, true)]; - BoneData data = new BoneData(i, name, 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.inheritRotation = ReadBoolean(input); - data.inheritScale = ReadBoolean(input); - if (nonessential) ReadInt(input); // Skip bone color. - skeletonData.bones.Add(data); - } - - // Slots. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - String slotName = ReadString(input); - BoneData boneData = skeletonData.bones.Items[ReadVarint(input, true)]; - SlotData slotData = new SlotData(i, slotName, boneData); - int color = ReadInt(input); - slotData.r = ((color & 0xff000000) >> 24) / 255f; - slotData.g = ((color & 0x00ff0000) >> 16) / 255f; - slotData.b = ((color & 0x0000ff00) >> 8) / 255f; - slotData.a = ((color & 0x000000ff)) / 255f; - slotData.attachmentName = ReadString(input); - slotData.blendMode = (BlendMode)ReadVarint(input, true); - skeletonData.slots.Add(slotData); - } - - // IK constraints. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - IkConstraintData data = new IkConstraintData(ReadString(input)); - for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) - data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]); - data.target = skeletonData.bones.Items[ReadVarint(input, true)]; - data.mix = ReadFloat(input); - data.bendDirection = ReadSByte(input); - skeletonData.ikConstraints.Add(data); - } - - // Transform constraints. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - TransformConstraintData data = new TransformConstraintData(ReadString(input)); - for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) - data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]); - data.target = skeletonData.bones.Items[ReadVarint(input, true)]; - data.offsetRotation = ReadFloat(input); - data.offsetX = ReadFloat(input) * scale; - data.offsetY = ReadFloat(input) * scale; - data.offsetScaleX = ReadFloat(input); - data.offsetScaleY = ReadFloat(input); - data.offsetShearY = ReadFloat(input); - data.rotateMix = ReadFloat(input); - data.translateMix = ReadFloat(input); - data.scaleMix = ReadFloat(input); - data.shearMix = ReadFloat(input); - skeletonData.transformConstraints.Add(data); - } - - // Path constraints - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - PathConstraintData data = new PathConstraintData(ReadString(input)); - for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) - data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]); - data.target = skeletonData.slots.Items[ReadVarint(input, true)]; - data.positionMode = (PositionMode)Enum.GetValues(typeof(PositionMode)).GetValue(ReadVarint(input, true)); - data.spacingMode = (SpacingMode)Enum.GetValues(typeof(SpacingMode)).GetValue(ReadVarint(input, true)); - data.rotateMode = (RotateMode)Enum.GetValues(typeof(RotateMode)).GetValue(ReadVarint(input, true)); - 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.rotateMix = ReadFloat(input); - data.translateMix = ReadFloat(input); - skeletonData.pathConstraints.Add(data); - } - - // Default skin. - Skin defaultSkin = ReadSkin(input, "default", nonessential); - if (defaultSkin != null) { - skeletonData.defaultSkin = defaultSkin; - skeletonData.skins.Add(defaultSkin); - } - - // Skins. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) - skeletonData.skins.Add(ReadSkin(input, ReadString(input), nonessential)); - - // Linked meshes. - for (int i = 0, n = linkedMeshes.Count; i < n; i++) { - SkeletonJson.LinkedMesh linkedMesh = linkedMeshes[i]; - Skin skin = linkedMesh.skin == null ? skeletonData.DefaultSkin : skeletonData.FindSkin(linkedMesh.skin); - if (skin == null) throw new Exception("Skin not found: " + linkedMesh.skin); - Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); - if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); - linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; - linkedMesh.mesh.UpdateUVs(); - } - linkedMeshes.Clear(); - - // Events. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - EventData data = new EventData(ReadString(input)); - data.Int = ReadVarint(input, false); - data.Float = ReadFloat(input); - data.String = ReadString(input); - skeletonData.events.Add(data); - } - - // Animations. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) - ReadAnimation(ReadString(input), input, skeletonData); - - skeletonData.bones.TrimExcess(); - skeletonData.slots.TrimExcess(); - skeletonData.skins.TrimExcess(); - skeletonData.events.TrimExcess(); - skeletonData.animations.TrimExcess(); - skeletonData.ikConstraints.TrimExcess(); - skeletonData.pathConstraints.TrimExcess(); - return skeletonData; - } - - - /// May be null. - private Skin ReadSkin (Stream input, String skinName, bool nonessential) { - int slotCount = ReadVarint(input, true); - if (slotCount == 0) return null; - Skin skin = new Skin(skinName); - 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 = ReadString(input); - skin.AddAttachment(slotIndex, name, ReadAttachment(input, skin, slotIndex, name, nonessential)); - } - } - return skin; - } - - private Attachment ReadAttachment (Stream input, Skin skin, int slotIndex, String attachmentName, bool nonessential) { - float scale = Scale; - - String name = ReadString(input); - if (name == null) name = attachmentName; - - AttachmentType type = (AttachmentType)input.ReadByte(); - switch (type) { - case AttachmentType.Region: { - String path = ReadString(input); - float rotation = ReadFloat(input); - float x = ReadFloat(input); - float y = ReadFloat(input); - float scaleX = ReadFloat(input); - float scaleY = ReadFloat(input); - float width = ReadFloat(input); - float height = ReadFloat(input); - int color = ReadInt(input); - - if (path == null) path = name; - RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); - if (region == null) return null; - region.Path = path; - region.x = x * scale; - region.y = y * scale; - region.scaleX = scaleX; - region.scaleY = scaleY; - region.rotation = rotation; - region.width = width * scale; - region.height = height * scale; - region.r = ((color & 0xff000000) >> 24) / 255f; - region.g = ((color & 0x00ff0000) >> 16) / 255f; - region.b = ((color & 0x0000ff00) >> 8) / 255f; - region.a = ((color & 0x000000ff)) / 255f; - region.UpdateOffset(); - return region; - } - case AttachmentType.Boundingbox: { - int vertexCount = ReadVarint(input, true); - Vertices vertices = ReadVertices(input, vertexCount); - if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0; // Avoid unused local warning. - - BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); - if (box == null) return null; - box.worldVerticesLength = vertexCount << 1; - box.vertices = vertices.vertices; - box.bones = vertices.bones; - return box; - } - case AttachmentType.Mesh: { - String path = ReadString(input); - int color = ReadInt(input); - int vertexCount = ReadVarint(input, true); - float[] uvs = ReadFloatArray(input, vertexCount << 1, 1); - int[] triangles = ReadShortArray(input); - Vertices vertices = ReadVertices(input, vertexCount); - int hullLength = ReadVarint(input, true); - int[] edges = null; - float width = 0, height = 0; - if (nonessential) { - edges = ReadShortArray(input); - width = ReadFloat(input); - height = ReadFloat(input); - } - - if (path == null) path = name; - MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); - if (mesh == null) return null; - mesh.Path = path; - mesh.r = ((color & 0xff000000) >> 24) / 255f; - mesh.g = ((color & 0x00ff0000) >> 16) / 255f; - mesh.b = ((color & 0x0000ff00) >> 8) / 255f; - mesh.a = ((color & 0x000000ff)) / 255f; - mesh.bones = vertices.bones; - mesh.vertices = vertices.vertices; - mesh.WorldVerticesLength = vertexCount << 1; - mesh.triangles = triangles; - mesh.regionUVs = uvs; - mesh.UpdateUVs(); - mesh.HullLength = hullLength << 1; - if (nonessential) { - mesh.Edges = edges; - mesh.Width = width * scale; - mesh.Height = height * scale; - } - return mesh; - } - case AttachmentType.Linkedmesh: { - String path = ReadString(input); - int color = ReadInt(input); - String skinName = ReadString(input); - String parent = ReadString(input); - bool inheritDeform = ReadBoolean(input); - float width = 0, height = 0; - if (nonessential) { - width = ReadFloat(input); - height = ReadFloat(input); - } - - if (path == null) path = name; - MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); - if (mesh == null) return null; - mesh.Path = path; - mesh.r = ((color & 0xff000000) >> 24) / 255f; - mesh.g = ((color & 0x00ff0000) >> 16) / 255f; - mesh.b = ((color & 0x0000ff00) >> 8) / 255f; - mesh.a = ((color & 0x000000ff)) / 255f; - mesh.inheritDeform = inheritDeform; - if (nonessential) { - mesh.Width = width * scale; - mesh.Height = height * scale; - } - linkedMeshes.Add(new SkeletonJson.LinkedMesh(mesh, skinName, slotIndex, parent)); - return mesh; - } - case AttachmentType.Path: { - bool closed = ReadBoolean(input); - bool constantSpeed = ReadBoolean(input); - int vertexCount = ReadVarint(input, true); - Vertices vertices = ReadVertices(input, vertexCount); - float[] lengths = new float[vertexCount / 3]; - for (int i = 0, n = lengths.Length; i < n; i++) - lengths[i] = ReadFloat(input) * scale; - if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0; // Avoid unused local warning. - - PathAttachment path = attachmentLoader.NewPathAttachment(skin, name); - if (path == null) return null; - path.closed = closed; - path.constantSpeed = constantSpeed; - path.worldVerticesLength = vertexCount << 1; - path.vertices = vertices.vertices; - path.bones = vertices.bones; - path.lengths = lengths; - return path; - } - } - return null; - } - - private Vertices ReadVertices (Stream input, int vertexCount) { - float scale = Scale; - int verticesLength = vertexCount << 1; - Vertices vertices = new Vertices(); - if(!ReadBoolean(input)) { - vertices.vertices = ReadFloatArray(input, verticesLength, scale); - return vertices; - } - var weights = new ExposedList(verticesLength * 3 * 3); - var bonesArray = new ExposedList(verticesLength * 3); - for (int i = 0; i < vertexCount; i++) { - int boneCount = ReadVarint(input, true); - bonesArray.Add(boneCount); - for (int ii = 0; ii < boneCount; ii++) { - bonesArray.Add(ReadVarint(input, true)); - weights.Add(ReadFloat(input) * scale); - weights.Add(ReadFloat(input) * scale); - weights.Add(ReadFloat(input)); - } - } - - vertices.vertices = weights.ToArray(); - vertices.bones = bonesArray.ToArray(); - return vertices; - } - - private float[] ReadFloatArray (Stream input, int n, float scale) { - float[] array = new float[n]; - if (scale == 1) { - for (int i = 0; i < n; i++) - array[i] = ReadFloat(input); - } else { - for (int i = 0; i < n; i++) - array[i] = ReadFloat(input) * scale; - } - return array; - } - - private int[] ReadShortArray (Stream input) { - int n = ReadVarint(input, true); - int[] array = new int[n]; - for (int i = 0; i < n; i++) - array[i] = (input.ReadByte() << 8) | input.ReadByte(); - return array; - } - - private void ReadAnimation (String name, Stream input, SkeletonData skeletonData) { - var timelines = new ExposedList(); - float scale = Scale; - float duration = 0; - - // 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++) { - int timelineType = input.ReadByte(); - int frameCount = ReadVarint(input, true); - switch (timelineType) { - case SLOT_COLOR: { - ColorTimeline timeline = new ColorTimeline(frameCount); - timeline.slotIndex = slotIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - float time = ReadFloat(input); - int color = ReadInt(input); - float r = ((color & 0xff000000) >> 24) / 255f; - float g = ((color & 0x00ff0000) >> 16) / 255f; - float b = ((color & 0x0000ff00) >> 8) / 255f; - float a = ((color & 0x000000ff)) / 255f; - timeline.SetFrame(frameIndex, time, r, g, b, a); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * ColorTimeline.ENTRIES]); - break; - } - case SLOT_ATTACHMENT: { - AttachmentTimeline timeline = new AttachmentTimeline(frameCount); - timeline.slotIndex = slotIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) - timeline.SetFrame(frameIndex, ReadFloat(input), ReadString(input)); - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[frameCount - 1]); - break; - } - } - } - } - - // 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++) { - int timelineType = input.ReadByte(); - int frameCount = ReadVarint(input, true); - switch (timelineType) { - case BONE_ROTATE: { - RotateTimeline timeline = new RotateTimeline(frameCount); - timeline.boneIndex = boneIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input)); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(frameCount - 1) * RotateTimeline.ENTRIES]); - break; - } - case BONE_TRANSLATE: - case BONE_SCALE: - case BONE_SHEAR: { - TranslateTimeline timeline; - float timelineScale = 1; - if (timelineType == BONE_SCALE) - timeline = new ScaleTimeline(frameCount); - else if (timelineType == BONE_SHEAR) - timeline = new ShearTimeline(frameCount); - else { - timeline = new TranslateTimeline(frameCount); - timelineScale = scale; - } - timeline.boneIndex = boneIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input) * timelineScale, ReadFloat(input) - * timelineScale); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TranslateTimeline.ENTRIES]); - break; - } - } - } - } - - // IK timelines. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - int index = ReadVarint(input, true); - int frameCount = ReadVarint(input, true); - IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount); - timeline.ikConstraintIndex = index; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadSByte(input)); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(frameCount - 1) * IkConstraintTimeline.ENTRIES]); - } - - // Transform constraint timelines. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - int index = ReadVarint(input, true); - int frameCount = ReadVarint(input, true); - TransformConstraintTimeline timeline = new TransformConstraintTimeline(frameCount); - timeline.transformConstraintIndex = index; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input)); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TransformConstraintTimeline.ENTRIES]); - } - - // Path constraint timelines. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - int index = ReadVarint(input, true); - PathConstraintData data = skeletonData.pathConstraints.Items[index]; - for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) { - int timelineType = ReadSByte(input); - int frameCount = ReadVarint(input, true); - switch(timelineType) { - case PATH_POSITION: - case PATH_SPACING: { - PathConstraintPositionTimeline timeline; - float timelineScale = 1; - if (timelineType == PATH_SPACING) { - timeline = new PathConstraintSpacingTimeline(frameCount); - if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale; - } else { - timeline = new PathConstraintPositionTimeline(frameCount); - if (data.positionMode == PositionMode.Fixed) timelineScale = scale; - } - timeline.pathConstraintIndex = index; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input) * timelineScale); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintPositionTimeline.ENTRIES]); - break; - } - case PATH_MIX: { - PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(frameCount); - timeline.pathConstraintIndex = index; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input)); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintMixTimeline.ENTRIES]); - break; - } - } - } - } - - // Deform timelines. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - Skin skin = skeletonData.skins.Items[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++) { - VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, ReadString(input)); - bool weighted = attachment.bones != null; - float[] vertices = attachment.vertices; - int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length; - - int frameCount = ReadVarint(input, true); - DeformTimeline timeline = new DeformTimeline(frameCount); - timeline.slotIndex = slotIndex; - timeline.attachment = attachment; - - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - float time = ReadFloat(input); - float[] deform; - int end = ReadVarint(input, true); - if (end == 0) - deform = weighted ? new float[deformLength] : vertices; - else { - deform = new float[deformLength]; - int start = ReadVarint(input, true); - end += start; - if (scale == 1) { - for (int v = start; v < end; v++) - deform[v] = ReadFloat(input); - } else { - for (int v = start; v < end; v++) - deform[v] = ReadFloat(input) * scale; - } - if (!weighted) { - for (int v = 0, vn = deform.Length; v < vn; v++) - deform[v] += vertices[v]; - } - } - - timeline.SetFrame(frameIndex, time, deform); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[frameCount - 1]); - } - } - } - - // Draw order timeline. - int drawOrderCount = ReadVarint(input, true); - if (drawOrderCount > 0) { - DrawOrderTimeline timeline = new DrawOrderTimeline(drawOrderCount); - int slotCount = skeletonData.slots.Count; - for (int i = 0; i < drawOrderCount; i++) { - float time = ReadFloat(input); - int offsetCount = ReadVarint(input, true); - int[] drawOrder = new int[slotCount]; - for (int ii = slotCount - 1; ii >= 0; ii--) - drawOrder[ii] = -1; - int[] unchanged = new int[slotCount - offsetCount]; - int originalIndex = 0, unchangedIndex = 0; - for (int ii = 0; ii < offsetCount; ii++) { - int slotIndex = ReadVarint(input, true); - // Collect unchanged items. - while (originalIndex != slotIndex) - unchanged[unchangedIndex++] = originalIndex++; - // Set changed items. - drawOrder[originalIndex + ReadVarint(input, true)] = originalIndex++; - } - // Collect remaining unchanged items. - while (originalIndex < slotCount) - unchanged[unchangedIndex++] = originalIndex++; - // Fill in unchanged items. - for (int ii = slotCount - 1; ii >= 0; ii--) - if (drawOrder[ii] == -1) drawOrder[ii] = unchanged[--unchangedIndex]; - timeline.SetFrame(i, time, drawOrder); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[drawOrderCount - 1]); - } - - // Event timeline. - int eventCount = ReadVarint(input, true); - if (eventCount > 0) { - EventTimeline timeline = new EventTimeline(eventCount); - for (int i = 0; i < eventCount; i++) { - float time = ReadFloat(input); - EventData eventData = skeletonData.events.Items[ReadVarint(input, true)]; - Event e = new Event(time, eventData); - e.Int = ReadVarint(input, false); - e.Float = ReadFloat(input); - e.String = ReadBoolean(input) ? ReadString(input) : eventData.String; - timeline.SetFrame(i, e); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[eventCount - 1]); - } - - timelines.TrimExcess(); - skeletonData.animations.Add(new Animation(name, timelines, duration)); - } - - private void ReadCurve (Stream input, int frameIndex, CurveTimeline timeline) { - switch (input.ReadByte()) { - case CURVE_STEPPED: - timeline.SetStepped(frameIndex); - break; - case CURVE_BEZIER: - timeline.SetCurve(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input)); - break; - } - } - - private static sbyte ReadSByte (Stream input) { - int value = input.ReadByte(); - if (value == -1) throw new EndOfStreamException(); - return (sbyte)value; - } - - private static bool ReadBoolean (Stream input) { - return input.ReadByte() != 0; - } - - private float ReadFloat (Stream input) { - buffer[3] = (byte)input.ReadByte(); - buffer[2] = (byte)input.ReadByte(); - buffer[1] = (byte)input.ReadByte(); - buffer[0] = (byte)input.ReadByte(); - return BitConverter.ToSingle(buffer, 0); - } - - private static int ReadInt (Stream input) { - return (input.ReadByte() << 24) + (input.ReadByte() << 16) + (input.ReadByte() << 8) + input.ReadByte(); - } - - private static int ReadVarint (Stream input, bool optimizePositive) { - int b = input.ReadByte(); - int result = b & 0x7F; - if ((b & 0x80) != 0) { - b = input.ReadByte(); - result |= (b & 0x7F) << 7; - if ((b & 0x80) != 0) { - b = input.ReadByte(); - result |= (b & 0x7F) << 14; - if ((b & 0x80) != 0) { - b = input.ReadByte(); - result |= (b & 0x7F) << 21; - if ((b & 0x80) != 0) result |= (input.ReadByte() & 0x7F) << 28; - } - } - } - return optimizePositive ? result : ((result >> 1) ^ -(result & 1)); - } - - private string ReadString (Stream input) { - int byteCount = ReadVarint(input, true); - switch (byteCount) { - case 0: - return null; - case 1: - return ""; - } - byteCount--; - byte[] buffer = this.buffer; - if (buffer.Length < byteCount) buffer = new byte[byteCount]; - ReadFully(input, buffer, 0, byteCount); - return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount); - } - - private static void ReadFully (Stream input, byte[] buffer, int offset, int length) { - while (length > 0) { - int count = input.Read(buffer, offset, length); - if (count <= 0) throw new EndOfStreamException(); - offset += count; - length -= count; - } - } - - internal class Vertices { - public int[] bones; - public float[] vertices; - } - } +#else + using (var input = new BufferedStream(new FileStream(path, FileMode.Open))) + { +#endif // WINDOWS_PHONE + SkeletonData skeletonData = ReadSkeletonData(input); + skeletonData.name = Path.GetFileNameWithoutExtension(path); + return skeletonData; + } + } + +#endif // WINDOWS_STOREAPP + + public SkeletonData ReadSkeletonData(Stream input) + { + if (input == null) throw new ArgumentNullException("input"); + float scale = Scale; + + var skeletonData = new SkeletonData(); + skeletonData.hash = ReadString(input); + if (skeletonData.hash.Length == 0) skeletonData.hash = null; + skeletonData.version = ReadString(input); + if (skeletonData.version.Length == 0) skeletonData.version = null; + skeletonData.width = ReadFloat(input); + skeletonData.height = ReadFloat(input); + + bool nonessential = ReadBoolean(input); + + if (nonessential) + { + skeletonData.imagesPath = ReadString(input); + if (skeletonData.imagesPath.Length == 0) skeletonData.imagesPath = null; + } + + // Bones. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + String name = ReadString(input); + BoneData parent = i == 0 ? null : skeletonData.bones.Items[ReadVarint(input, true)]; + BoneData data = new BoneData(i, name, 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.inheritRotation = ReadBoolean(input); + data.inheritScale = ReadBoolean(input); + if (nonessential) ReadInt(input); // Skip bone color. + skeletonData.bones.Add(data); + } + + // Slots. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + String slotName = ReadString(input); + BoneData boneData = skeletonData.bones.Items[ReadVarint(input, true)]; + SlotData slotData = new SlotData(i, slotName, boneData); + int color = ReadInt(input); + slotData.r = ((color & 0xff000000) >> 24) / 255f; + slotData.g = ((color & 0x00ff0000) >> 16) / 255f; + slotData.b = ((color & 0x0000ff00) >> 8) / 255f; + slotData.a = ((color & 0x000000ff)) / 255f; + slotData.attachmentName = ReadString(input); + slotData.blendMode = (BlendMode)ReadVarint(input, true); + skeletonData.slots.Add(slotData); + } + + // IK constraints. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + IkConstraintData data = new IkConstraintData(ReadString(input)); + for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) + data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]); + data.target = skeletonData.bones.Items[ReadVarint(input, true)]; + data.mix = ReadFloat(input); + data.bendDirection = ReadSByte(input); + skeletonData.ikConstraints.Add(data); + } + + // Transform constraints. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + TransformConstraintData data = new TransformConstraintData(ReadString(input)); + for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) + data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]); + data.target = skeletonData.bones.Items[ReadVarint(input, true)]; + data.offsetRotation = ReadFloat(input); + data.offsetX = ReadFloat(input) * scale; + data.offsetY = ReadFloat(input) * scale; + data.offsetScaleX = ReadFloat(input); + data.offsetScaleY = ReadFloat(input); + data.offsetShearY = ReadFloat(input); + data.rotateMix = ReadFloat(input); + data.translateMix = ReadFloat(input); + data.scaleMix = ReadFloat(input); + data.shearMix = ReadFloat(input); + skeletonData.transformConstraints.Add(data); + } + + // Path constraints + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + PathConstraintData data = new PathConstraintData(ReadString(input)); + for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) + data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]); + data.target = skeletonData.slots.Items[ReadVarint(input, true)]; + data.positionMode = (PositionMode)Enum.GetValues(typeof(PositionMode)).GetValue(ReadVarint(input, true)); + data.spacingMode = (SpacingMode)Enum.GetValues(typeof(SpacingMode)).GetValue(ReadVarint(input, true)); + data.rotateMode = (RotateMode)Enum.GetValues(typeof(RotateMode)).GetValue(ReadVarint(input, true)); + 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.rotateMix = ReadFloat(input); + data.translateMix = ReadFloat(input); + skeletonData.pathConstraints.Add(data); + } + + // Default skin. + Skin defaultSkin = ReadSkin(input, "default", nonessential); + if (defaultSkin != null) + { + skeletonData.defaultSkin = defaultSkin; + skeletonData.skins.Add(defaultSkin); + } + + // Skins. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + skeletonData.skins.Add(ReadSkin(input, ReadString(input), nonessential)); + + // Linked meshes. + for (int i = 0, n = linkedMeshes.Count; i < n; i++) + { + SkeletonJson.LinkedMesh linkedMesh = linkedMeshes[i]; + Skin skin = linkedMesh.skin == null ? skeletonData.DefaultSkin : skeletonData.FindSkin(linkedMesh.skin); + if (skin == null) throw new Exception("Skin not found: " + linkedMesh.skin); + Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); + if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); + linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; + linkedMesh.mesh.UpdateUVs(); + } + linkedMeshes.Clear(); + + // Events. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + EventData data = new EventData(ReadString(input)); + data.Int = ReadVarint(input, false); + data.Float = ReadFloat(input); + data.String = ReadString(input); + skeletonData.events.Add(data); + } + + // Animations. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + ReadAnimation(ReadString(input), input, skeletonData); + + skeletonData.bones.TrimExcess(); + skeletonData.slots.TrimExcess(); + skeletonData.skins.TrimExcess(); + skeletonData.events.TrimExcess(); + skeletonData.animations.TrimExcess(); + skeletonData.ikConstraints.TrimExcess(); + skeletonData.pathConstraints.TrimExcess(); + return skeletonData; + } + + + /// May be null. + private Skin ReadSkin(Stream input, String skinName, bool nonessential) + { + int slotCount = ReadVarint(input, true); + if (slotCount == 0) return null; + Skin skin = new Skin(skinName); + 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 = ReadString(input); + skin.AddAttachment(slotIndex, name, ReadAttachment(input, skin, slotIndex, name, nonessential)); + } + } + return skin; + } + + private Attachment ReadAttachment(Stream input, Skin skin, int slotIndex, String attachmentName, bool nonessential) + { + float scale = Scale; + + String name = ReadString(input); + if (name == null) name = attachmentName; + + AttachmentType type = (AttachmentType)input.ReadByte(); + switch (type) + { + case AttachmentType.Region: + { + String path = ReadString(input); + float rotation = ReadFloat(input); + float x = ReadFloat(input); + float y = ReadFloat(input); + float scaleX = ReadFloat(input); + float scaleY = ReadFloat(input); + float width = ReadFloat(input); + float height = ReadFloat(input); + int color = ReadInt(input); + + if (path == null) path = name; + RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); + if (region == null) return null; + region.Path = path; + region.x = x * scale; + region.y = y * scale; + region.scaleX = scaleX; + region.scaleY = scaleY; + region.rotation = rotation; + region.width = width * scale; + region.height = height * scale; + region.r = ((color & 0xff000000) >> 24) / 255f; + region.g = ((color & 0x00ff0000) >> 16) / 255f; + region.b = ((color & 0x0000ff00) >> 8) / 255f; + region.a = ((color & 0x000000ff)) / 255f; + region.UpdateOffset(); + return region; + } + case AttachmentType.Boundingbox: + { + int vertexCount = ReadVarint(input, true); + Vertices vertices = ReadVertices(input, vertexCount); + if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0; // Avoid unused local warning. + + BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); + if (box == null) return null; + box.worldVerticesLength = vertexCount << 1; + box.vertices = vertices.vertices; + box.bones = vertices.bones; + return box; + } + case AttachmentType.Mesh: + { + String path = ReadString(input); + int color = ReadInt(input); + int vertexCount = ReadVarint(input, true); + float[] uvs = ReadFloatArray(input, vertexCount << 1, 1); + int[] triangles = ReadShortArray(input); + Vertices vertices = ReadVertices(input, vertexCount); + int hullLength = ReadVarint(input, true); + int[] edges = null; + float width = 0, height = 0; + if (nonessential) + { + edges = ReadShortArray(input); + width = ReadFloat(input); + height = ReadFloat(input); + } + + if (path == null) path = name; + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); + if (mesh == null) return null; + mesh.Path = path; + mesh.r = ((color & 0xff000000) >> 24) / 255f; + mesh.g = ((color & 0x00ff0000) >> 16) / 255f; + mesh.b = ((color & 0x0000ff00) >> 8) / 255f; + mesh.a = ((color & 0x000000ff)) / 255f; + mesh.bones = vertices.bones; + mesh.vertices = vertices.vertices; + mesh.WorldVerticesLength = vertexCount << 1; + mesh.triangles = triangles; + mesh.regionUVs = uvs; + mesh.UpdateUVs(); + mesh.HullLength = hullLength << 1; + if (nonessential) + { + mesh.Edges = edges; + mesh.Width = width * scale; + mesh.Height = height * scale; + } + return mesh; + } + case AttachmentType.Linkedmesh: + { + String path = ReadString(input); + int color = ReadInt(input); + String skinName = ReadString(input); + String parent = ReadString(input); + bool inheritDeform = ReadBoolean(input); + float width = 0, height = 0; + if (nonessential) + { + width = ReadFloat(input); + height = ReadFloat(input); + } + + if (path == null) path = name; + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); + if (mesh == null) return null; + mesh.Path = path; + mesh.r = ((color & 0xff000000) >> 24) / 255f; + mesh.g = ((color & 0x00ff0000) >> 16) / 255f; + mesh.b = ((color & 0x0000ff00) >> 8) / 255f; + mesh.a = ((color & 0x000000ff)) / 255f; + mesh.inheritDeform = inheritDeform; + if (nonessential) + { + mesh.Width = width * scale; + mesh.Height = height * scale; + } + linkedMeshes.Add(new SkeletonJson.LinkedMesh(mesh, skinName, slotIndex, parent)); + return mesh; + } + case AttachmentType.Path: + { + bool closed = ReadBoolean(input); + bool constantSpeed = ReadBoolean(input); + int vertexCount = ReadVarint(input, true); + Vertices vertices = ReadVertices(input, vertexCount); + float[] lengths = new float[vertexCount / 3]; + for (int i = 0, n = lengths.Length; i < n; i++) + lengths[i] = ReadFloat(input) * scale; + if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0; // Avoid unused local warning. + + PathAttachment path = attachmentLoader.NewPathAttachment(skin, name); + if (path == null) return null; + path.closed = closed; + path.constantSpeed = constantSpeed; + path.worldVerticesLength = vertexCount << 1; + path.vertices = vertices.vertices; + path.bones = vertices.bones; + path.lengths = lengths; + return path; + } + } + return null; + } + + private Vertices ReadVertices(Stream input, int vertexCount) + { + float scale = Scale; + int verticesLength = vertexCount << 1; + Vertices vertices = new Vertices(); + if (!ReadBoolean(input)) + { + vertices.vertices = ReadFloatArray(input, verticesLength, scale); + return vertices; + } + var weights = new ExposedList(verticesLength * 3 * 3); + var bonesArray = new ExposedList(verticesLength * 3); + for (int i = 0; i < vertexCount; i++) + { + int boneCount = ReadVarint(input, true); + bonesArray.Add(boneCount); + for (int ii = 0; ii < boneCount; ii++) + { + bonesArray.Add(ReadVarint(input, true)); + weights.Add(ReadFloat(input) * scale); + weights.Add(ReadFloat(input) * scale); + weights.Add(ReadFloat(input)); + } + } + + vertices.vertices = weights.ToArray(); + vertices.bones = bonesArray.ToArray(); + return vertices; + } + + private float[] ReadFloatArray(Stream input, int n, float scale) + { + float[] array = new float[n]; + if (scale == 1) + { + for (int i = 0; i < n; i++) + array[i] = ReadFloat(input); + } + else + { + for (int i = 0; i < n; i++) + array[i] = ReadFloat(input) * scale; + } + return array; + } + + private int[] ReadShortArray(Stream input) + { + int n = ReadVarint(input, true); + int[] array = new int[n]; + for (int i = 0; i < n; i++) + array[i] = (input.ReadByte() << 8) | input.ReadByte(); + return array; + } + + private void ReadAnimation(String name, Stream input, SkeletonData skeletonData) + { + var timelines = new ExposedList(); + float scale = Scale; + float duration = 0; + + // 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++) + { + int timelineType = input.ReadByte(); + int frameCount = ReadVarint(input, true); + switch (timelineType) + { + case SLOT_COLOR: + { + ColorTimeline timeline = new ColorTimeline(frameCount); + timeline.slotIndex = slotIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + float time = ReadFloat(input); + int color = ReadInt(input); + float r = ((color & 0xff000000) >> 24) / 255f; + float g = ((color & 0x00ff0000) >> 16) / 255f; + float b = ((color & 0x0000ff00) >> 8) / 255f; + float a = ((color & 0x000000ff)) / 255f; + timeline.SetFrame(frameIndex, time, r, g, b, a); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * ColorTimeline.ENTRIES]); + break; + } + case SLOT_ATTACHMENT: + { + AttachmentTimeline timeline = new AttachmentTimeline(frameCount); + timeline.slotIndex = slotIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + timeline.SetFrame(frameIndex, ReadFloat(input), ReadString(input)); + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[frameCount - 1]); + break; + } + } + } + } + + // 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++) + { + int timelineType = input.ReadByte(); + int frameCount = ReadVarint(input, true); + switch (timelineType) + { + case BONE_ROTATE: + { + RotateTimeline timeline = new RotateTimeline(frameCount); + timeline.boneIndex = boneIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input)); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(frameCount - 1) * RotateTimeline.ENTRIES]); + break; + } + case BONE_TRANSLATE: + case BONE_SCALE: + case BONE_SHEAR: + { + TranslateTimeline timeline; + float timelineScale = 1; + if (timelineType == BONE_SCALE) + timeline = new ScaleTimeline(frameCount); + else if (timelineType == BONE_SHEAR) + timeline = new ShearTimeline(frameCount); + else + { + timeline = new TranslateTimeline(frameCount); + timelineScale = scale; + } + timeline.boneIndex = boneIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input) * timelineScale, ReadFloat(input) + * timelineScale); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TranslateTimeline.ENTRIES]); + break; + } + } + } + } + + // IK timelines. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + int index = ReadVarint(input, true); + int frameCount = ReadVarint(input, true); + IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount); + timeline.ikConstraintIndex = index; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadSByte(input)); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(frameCount - 1) * IkConstraintTimeline.ENTRIES]); + } + + // Transform constraint timelines. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + int index = ReadVarint(input, true); + int frameCount = ReadVarint(input, true); + TransformConstraintTimeline timeline = new TransformConstraintTimeline(frameCount); + timeline.transformConstraintIndex = index; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input)); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TransformConstraintTimeline.ENTRIES]); + } + + // Path constraint timelines. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + int index = ReadVarint(input, true); + PathConstraintData data = skeletonData.pathConstraints.Items[index]; + for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) + { + int timelineType = ReadSByte(input); + int frameCount = ReadVarint(input, true); + switch (timelineType) + { + case PATH_POSITION: + case PATH_SPACING: + { + PathConstraintPositionTimeline timeline; + float timelineScale = 1; + if (timelineType == PATH_SPACING) + { + timeline = new PathConstraintSpacingTimeline(frameCount); + if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale; + } + else + { + timeline = new PathConstraintPositionTimeline(frameCount); + if (data.positionMode == PositionMode.Fixed) timelineScale = scale; + } + timeline.pathConstraintIndex = index; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input) * timelineScale); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintPositionTimeline.ENTRIES]); + break; + } + case PATH_MIX: + { + PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(frameCount); + timeline.pathConstraintIndex = index; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input)); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintMixTimeline.ENTRIES]); + break; + } + } + } + } + + // Deform timelines. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + Skin skin = skeletonData.skins.Items[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++) + { + VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, ReadString(input)); + bool weighted = attachment.bones != null; + float[] vertices = attachment.vertices; + int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length; + + int frameCount = ReadVarint(input, true); + DeformTimeline timeline = new DeformTimeline(frameCount); + timeline.slotIndex = slotIndex; + timeline.attachment = attachment; + + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + float time = ReadFloat(input); + float[] deform; + int end = ReadVarint(input, true); + if (end == 0) + deform = weighted ? new float[deformLength] : vertices; + else + { + deform = new float[deformLength]; + int start = ReadVarint(input, true); + end += start; + if (scale == 1) + { + for (int v = start; v < end; v++) + deform[v] = ReadFloat(input); + } + else + { + for (int v = start; v < end; v++) + deform[v] = ReadFloat(input) * scale; + } + if (!weighted) + { + for (int v = 0, vn = deform.Length; v < vn; v++) + deform[v] += vertices[v]; + } + } + + timeline.SetFrame(frameIndex, time, deform); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[frameCount - 1]); + } + } + } + + // Draw order timeline. + int drawOrderCount = ReadVarint(input, true); + if (drawOrderCount > 0) + { + DrawOrderTimeline timeline = new DrawOrderTimeline(drawOrderCount); + int slotCount = skeletonData.slots.Count; + for (int i = 0; i < drawOrderCount; i++) + { + float time = ReadFloat(input); + int offsetCount = ReadVarint(input, true); + int[] drawOrder = new int[slotCount]; + for (int ii = slotCount - 1; ii >= 0; ii--) + drawOrder[ii] = -1; + int[] unchanged = new int[slotCount - offsetCount]; + int originalIndex = 0, unchangedIndex = 0; + for (int ii = 0; ii < offsetCount; ii++) + { + int slotIndex = ReadVarint(input, true); + // Collect unchanged items. + while (originalIndex != slotIndex) + unchanged[unchangedIndex++] = originalIndex++; + // Set changed items. + drawOrder[originalIndex + ReadVarint(input, true)] = originalIndex++; + } + // Collect remaining unchanged items. + while (originalIndex < slotCount) + unchanged[unchangedIndex++] = originalIndex++; + // Fill in unchanged items. + for (int ii = slotCount - 1; ii >= 0; ii--) + if (drawOrder[ii] == -1) drawOrder[ii] = unchanged[--unchangedIndex]; + timeline.SetFrame(i, time, drawOrder); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[drawOrderCount - 1]); + } + + // Event timeline. + int eventCount = ReadVarint(input, true); + if (eventCount > 0) + { + EventTimeline timeline = new EventTimeline(eventCount); + for (int i = 0; i < eventCount; i++) + { + float time = ReadFloat(input); + EventData eventData = skeletonData.events.Items[ReadVarint(input, true)]; + Event e = new Event(time, eventData); + e.Int = ReadVarint(input, false); + e.Float = ReadFloat(input); + e.String = ReadBoolean(input) ? ReadString(input) : eventData.String; + timeline.SetFrame(i, e); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[eventCount - 1]); + } + + timelines.TrimExcess(); + skeletonData.animations.Add(new Animation(name, timelines, duration)); + } + + private void ReadCurve(Stream input, int frameIndex, CurveTimeline timeline) + { + switch (input.ReadByte()) + { + case CURVE_STEPPED: + timeline.SetStepped(frameIndex); + break; + case CURVE_BEZIER: + timeline.SetCurve(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input)); + break; + } + } + + private static sbyte ReadSByte(Stream input) + { + int value = input.ReadByte(); + if (value == -1) throw new EndOfStreamException(); + return (sbyte)value; + } + + private static bool ReadBoolean(Stream input) + { + return input.ReadByte() != 0; + } + + private float ReadFloat(Stream input) + { + buffer[3] = (byte)input.ReadByte(); + buffer[2] = (byte)input.ReadByte(); + buffer[1] = (byte)input.ReadByte(); + buffer[0] = (byte)input.ReadByte(); + return BitConverter.ToSingle(buffer, 0); + } + + private static int ReadInt(Stream input) + { + return (input.ReadByte() << 24) + (input.ReadByte() << 16) + (input.ReadByte() << 8) + input.ReadByte(); + } + + private static int ReadVarint(Stream input, bool optimizePositive) + { + int b = input.ReadByte(); + int result = b & 0x7F; + if ((b & 0x80) != 0) + { + b = input.ReadByte(); + result |= (b & 0x7F) << 7; + if ((b & 0x80) != 0) + { + b = input.ReadByte(); + result |= (b & 0x7F) << 14; + if ((b & 0x80) != 0) + { + b = input.ReadByte(); + result |= (b & 0x7F) << 21; + if ((b & 0x80) != 0) result |= (input.ReadByte() & 0x7F) << 28; + } + } + } + return optimizePositive ? result : ((result >> 1) ^ -(result & 1)); + } + + private string ReadString(Stream input) + { + int byteCount = ReadVarint(input, true); + switch (byteCount) + { + case 0: + return null; + case 1: + return ""; + } + byteCount--; + byte[] buffer = this.buffer; + if (buffer.Length < byteCount) buffer = new byte[byteCount]; + ReadFully(input, buffer, 0, byteCount); + return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount); + } + + private static void ReadFully(Stream input, byte[] buffer, int offset, int length) + { + while (length > 0) + { + int count = input.Read(buffer, offset, length); + if (count <= 0) throw new EndOfStreamException(); + offset += count; + length -= count; + } + } + + internal class Vertices + { + public int[] bones; + public float[] vertices; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/SkeletonBounds.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/SkeletonBounds.cs index a9ad663..6e49f4b 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/SkeletonBounds.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/SkeletonBounds.cs @@ -30,185 +30,209 @@ using System; -namespace Spine3_4_02 { - public class SkeletonBounds { - private ExposedList polygonPool = new ExposedList(); - private float minX, minY, maxX, maxY; - - public ExposedList BoundingBoxes { get; private set; } - public ExposedList Polygons { get; private set; } - public float MinX { get { return minX; } set { minX = value; } } - public float MinY { get { return minY; } set { minY = value; } } - public float MaxX { get { return maxX; } set { maxX = value; } } - public float MaxY { get { return maxY; } set { maxY = value; } } - public float Width { get { return maxX - minX; } } - public float Height { get { return maxY - minY; } } - - public SkeletonBounds () { - BoundingBoxes = new ExposedList(); - Polygons = new ExposedList(); - } - - public void Update (Skeleton skeleton, bool updateAabb) { - ExposedList boundingBoxes = BoundingBoxes; - ExposedList polygons = Polygons; - ExposedList slots = skeleton.slots; - int slotCount = slots.Count; - - boundingBoxes.Clear(); - for (int i = 0, n = polygons.Count; i < n; i++) - polygonPool.Add(polygons.Items[i]); - polygons.Clear(); - - for (int i = 0; i < slotCount; i++) { - Slot slot = slots.Items[i]; - BoundingBoxAttachment boundingBox = slot.attachment as BoundingBoxAttachment; - if (boundingBox == null) continue; - boundingBoxes.Add(boundingBox); - - Polygon polygon = null; - int poolCount = polygonPool.Count; - if (poolCount > 0) { - polygon = polygonPool.Items[poolCount - 1]; - polygonPool.RemoveAt(poolCount - 1); - } else - polygon = new Polygon(); - polygons.Add(polygon); - - int count = boundingBox.Vertices.Length; - polygon.Count = count; - if (polygon.Vertices.Length < count) polygon.Vertices = new float[count]; - boundingBox.ComputeWorldVertices(slot, polygon.Vertices); - } - - if (updateAabb) aabbCompute(); - } - - private void aabbCompute () { - float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; - ExposedList polygons = Polygons; - for (int i = 0, n = polygons.Count; i < n; i++) { - Polygon polygon = polygons.Items[i]; - float[] vertices = polygon.Vertices; - for (int ii = 0, nn = polygon.Count; ii < nn; ii += 2) { - float x = vertices[ii]; - float y = vertices[ii + 1]; - minX = Math.Min(minX, x); - minY = Math.Min(minY, y); - maxX = Math.Max(maxX, x); - maxY = Math.Max(maxY, y); - } - } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; - } - - - /// Returns true if the axis aligned bounding box contains the point. - public bool AabbContainsPoint (float x, float y) { - return x >= minX && x <= maxX && y >= minY && y <= maxY; - } - - /// Returns true if the axis aligned bounding box intersects the line segment. - public bool AabbIntersectsSegment (float x1, float y1, float x2, float y2) { - float minX = this.minX; - float minY = this.minY; - float maxX = this.maxX; - float maxY = this.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; - } - - /// Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds. - public bool AabbIntersectsSkeleton (SkeletonBounds bounds) { - return minX < bounds.maxX && maxX > bounds.minX && minY < bounds.maxY && maxY > bounds.minY; - } - - /// Returns true if the polygon contains the point. - public bool ContainsPoint (Polygon polygon, float x, float y) { - float[] 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; - } - - /// 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. - public BoundingBoxAttachment ContainsPoint (float x, float y) { - ExposedList polygons = Polygons; - for (int i = 0, n = polygons.Count; i < n; i++) - if (ContainsPoint(polygons.Items[i], x, y)) return BoundingBoxes.Items[i]; - return null; - } - - /// 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. - public BoundingBoxAttachment IntersectsSegment (float x1, float y1, float x2, float y2) { - ExposedList polygons = Polygons; - for (int i = 0, n = polygons.Count; i < n; i++) - if (IntersectsSegment(polygons.Items[i], x1, y1, x2, y2)) return BoundingBoxes.Items[i]; - return null; - } - - /// Returns true if the polygon contains the line segment. - public bool IntersectsSegment (Polygon polygon, float x1, float y1, float x2, float y2) { - float[] vertices = polygon.Vertices; - int 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 (int 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; - } - - public Polygon getPolygon (BoundingBoxAttachment attachment) { - int index = BoundingBoxes.IndexOf(attachment); - return index == -1 ? null : Polygons.Items[index]; - } - } - - public class Polygon { - public float[] Vertices { get; set; } - public int Count { get; set; } - - public Polygon () { - Vertices = new float[16]; - } - } +namespace Spine3_4_02 +{ + public class SkeletonBounds + { + private ExposedList polygonPool = new ExposedList(); + private float minX, minY, maxX, maxY; + + public ExposedList BoundingBoxes { get; private set; } + public ExposedList Polygons { get; private set; } + public float MinX { get { return minX; } set { minX = value; } } + public float MinY { get { return minY; } set { minY = value; } } + public float MaxX { get { return maxX; } set { maxX = value; } } + public float MaxY { get { return maxY; } set { maxY = value; } } + public float Width { get { return maxX - minX; } } + public float Height { get { return maxY - minY; } } + + public SkeletonBounds() + { + BoundingBoxes = new ExposedList(); + Polygons = new ExposedList(); + } + + public void Update(Skeleton skeleton, bool updateAabb) + { + ExposedList boundingBoxes = BoundingBoxes; + ExposedList polygons = Polygons; + ExposedList slots = skeleton.slots; + int slotCount = slots.Count; + + boundingBoxes.Clear(); + for (int i = 0, n = polygons.Count; i < n; i++) + polygonPool.Add(polygons.Items[i]); + polygons.Clear(); + + for (int i = 0; i < slotCount; i++) + { + Slot slot = slots.Items[i]; + BoundingBoxAttachment boundingBox = slot.attachment as BoundingBoxAttachment; + if (boundingBox == null) continue; + boundingBoxes.Add(boundingBox); + + Polygon polygon = null; + int poolCount = polygonPool.Count; + if (poolCount > 0) + { + polygon = polygonPool.Items[poolCount - 1]; + polygonPool.RemoveAt(poolCount - 1); + } + else + polygon = new Polygon(); + polygons.Add(polygon); + + int count = boundingBox.Vertices.Length; + polygon.Count = count; + if (polygon.Vertices.Length < count) polygon.Vertices = new float[count]; + boundingBox.ComputeWorldVertices(slot, polygon.Vertices); + } + + if (updateAabb) aabbCompute(); + } + + private void aabbCompute() + { + float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; + ExposedList polygons = Polygons; + for (int i = 0, n = polygons.Count; i < n; i++) + { + Polygon polygon = polygons.Items[i]; + float[] vertices = polygon.Vertices; + for (int ii = 0, nn = polygon.Count; ii < nn; ii += 2) + { + float x = vertices[ii]; + float y = vertices[ii + 1]; + minX = Math.Min(minX, x); + minY = Math.Min(minY, y); + maxX = Math.Max(maxX, x); + maxY = Math.Max(maxY, y); + } + } + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + + /// Returns true if the axis aligned bounding box contains the point. + public bool AabbContainsPoint(float x, float y) + { + return x >= minX && x <= maxX && y >= minY && y <= maxY; + } + + /// Returns true if the axis aligned bounding box intersects the line segment. + public bool AabbIntersectsSegment(float x1, float y1, float x2, float y2) + { + float minX = this.minX; + float minY = this.minY; + float maxX = this.maxX; + float maxY = this.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; + } + + /// Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds. + public bool AabbIntersectsSkeleton(SkeletonBounds bounds) + { + return minX < bounds.maxX && maxX > bounds.minX && minY < bounds.maxY && maxY > bounds.minY; + } + + /// Returns true if the polygon contains the point. + public bool ContainsPoint(Polygon polygon, float x, float y) + { + float[] 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; + } + + /// 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. + public BoundingBoxAttachment ContainsPoint(float x, float y) + { + ExposedList polygons = Polygons; + for (int i = 0, n = polygons.Count; i < n; i++) + if (ContainsPoint(polygons.Items[i], x, y)) return BoundingBoxes.Items[i]; + return null; + } + + /// 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. + public BoundingBoxAttachment IntersectsSegment(float x1, float y1, float x2, float y2) + { + ExposedList polygons = Polygons; + for (int i = 0, n = polygons.Count; i < n; i++) + if (IntersectsSegment(polygons.Items[i], x1, y1, x2, y2)) return BoundingBoxes.Items[i]; + return null; + } + + /// Returns true if the polygon contains the line segment. + public bool IntersectsSegment(Polygon polygon, float x1, float y1, float x2, float y2) + { + float[] vertices = polygon.Vertices; + int 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 (int 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; + } + + public Polygon getPolygon(BoundingBoxAttachment attachment) + { + int index = BoundingBoxes.IndexOf(attachment); + return index == -1 ? null : Polygons.Items[index]; + } + } + + public class Polygon + { + public float[] Vertices { get; set; } + public int Count { get; set; } + + public Polygon() + { + Vertices = new float[16]; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/SkeletonData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/SkeletonData.cs index 55da734..80dc410 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/SkeletonData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/SkeletonData.cs @@ -30,167 +30,187 @@ using System; -namespace Spine3_4_02 { - public class SkeletonData { - internal String name; - internal ExposedList bones = new ExposedList(); - internal ExposedList slots = new ExposedList(); - internal ExposedList skins = new ExposedList(); - internal Skin defaultSkin; - internal ExposedList events = new ExposedList(); - internal ExposedList animations = new ExposedList(); - internal ExposedList ikConstraints = new ExposedList(); - internal ExposedList transformConstraints = new ExposedList(); - internal ExposedList pathConstraints = new ExposedList(); - internal float width, height; - internal String version, hash, imagesPath; - - public String Name { get { return name; } set { name = value; } } - public ExposedList Bones { get { return bones; } } // Ordered parents first. - public ExposedList Slots { get { return slots; } } // Setup pose draw order. - public ExposedList Skins { get { return skins; } set { skins = value; } } - /// May be null. - public Skin DefaultSkin { get { return defaultSkin; } set { defaultSkin = value; } } - public ExposedList Events { get { return events; } set { events = value; } } - public ExposedList Animations { get { return animations; } set { animations = value; } } - public ExposedList IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } } - public ExposedList TransformConstraints { get { return transformConstraints; } set { transformConstraints = value; } } - public ExposedList PathConstraints { get { return pathConstraints; } set { pathConstraints = value; } } - public float Width { get { return width; } set { width = value; } } - public float Height { get { return height; } set { height = value; } } - /// The Spine version used to export this data. - public String Version { get { return version; } set { version = value; } } - public String Hash { get { return hash; } set { hash = value; } } - - // --- Bones. - - /// May be null. - public BoneData FindBone (String boneName) { - if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); - ExposedList bones = this.bones; - for (int i = 0, n = bones.Count; i < n; i++) { - BoneData bone = bones.Items[i]; - if (bone.name == boneName) return bone; - } - return null; - } - - /// -1 if the bone was not found. - public int FindBoneIndex (String boneName) { - if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); - ExposedList bones = this.bones; - for (int i = 0, n = bones.Count; i < n; i++) - if (bones.Items[i].name == boneName) return i; - return -1; - } - - // --- Slots. - - /// May be null. - public SlotData FindSlot (String slotName) { - if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); - ExposedList slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) { - SlotData slot = slots.Items[i]; - if (slot.name == slotName) return slot; - } - return null; - } - - /// -1 if the slot was not found. - public int FindSlotIndex (String slotName) { - if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); - ExposedList slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) - if (slots.Items[i].name == slotName) return i; - return -1; - } - - // --- Skins. - - /// May be null. - public Skin FindSkin (String skinName) { - if (skinName == null) throw new ArgumentNullException("skinName", "skinName cannot be null."); - foreach (Skin skin in skins) - if (skin.name == skinName) return skin; - return null; - } - - // --- Events. - - /// May be null. - public EventData FindEvent (String eventDataName) { - if (eventDataName == null) throw new ArgumentNullException("eventDataName", "eventDataName cannot be null."); - foreach (EventData eventData in events) - if (eventData.name == eventDataName) return eventData; - return null; - } - - // --- Animations. - - /// May be null. - public Animation FindAnimation (String animationName) { - if (animationName == null) throw new ArgumentNullException("animationName", "animationName cannot be null."); - ExposedList animations = this.animations; - for (int i = 0, n = animations.Count; i < n; i++) { - Animation animation = animations.Items[i]; - if (animation.name == animationName) return animation; - } - return null; - } - - // --- IK constraints. - - /// May be null. - public IkConstraintData FindIkConstraint (String constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - ExposedList ikConstraints = this.ikConstraints; - for (int i = 0, n = ikConstraints.Count; i < n; i++) { - IkConstraintData ikConstraint = ikConstraints.Items[i]; - if (ikConstraint.name == constraintName) return ikConstraint; - } - return null; - } - - // --- Transform constraints. - - /// May be null. - public TransformConstraintData FindTransformConstraint (String constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - ExposedList transformConstraints = this.transformConstraints; - for (int i = 0, n = transformConstraints.Count; i < n; i++) { - TransformConstraintData transformConstraint = transformConstraints.Items[i]; - if (transformConstraint.name == constraintName) return transformConstraint; - } - return null; - } - - // --- Path constraints. - - /// May be null. - public PathConstraintData FindPathConstraint (String constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - ExposedList pathConstraints = this.pathConstraints; - for (int i = 0, n = pathConstraints.Count; i < n; i++) { - PathConstraintData constraint = pathConstraints.Items[i]; - if (constraint.name.Equals(constraintName)) return constraint; - } - return null; - } - - /// -1 if the path constraint was not found. - public int FindPathConstraintIndex (String pathConstraintName) { - if (pathConstraintName == null) throw new ArgumentNullException("pathConstraintName", "pathConstraintName cannot be null."); - ExposedList pathConstraints = this.pathConstraints; - for (int i = 0, n = pathConstraints.Count; i < n; i++) - if (pathConstraints.Items[i].name.Equals(pathConstraintName)) return i; - return -1; - } - - // --- - - override public String ToString () { - return name ?? base.ToString(); - } - } +namespace Spine3_4_02 +{ + public class SkeletonData + { + internal String name; + internal ExposedList bones = new ExposedList(); + internal ExposedList slots = new ExposedList(); + internal ExposedList skins = new ExposedList(); + internal Skin defaultSkin; + internal ExposedList events = new ExposedList(); + internal ExposedList animations = new ExposedList(); + internal ExposedList ikConstraints = new ExposedList(); + internal ExposedList transformConstraints = new ExposedList(); + internal ExposedList pathConstraints = new ExposedList(); + internal float width, height; + internal String version, hash, imagesPath; + + public String Name { get { return name; } set { name = value; } } + public ExposedList Bones { get { return bones; } } // Ordered parents first. + public ExposedList Slots { get { return slots; } } // Setup pose draw order. + public ExposedList Skins { get { return skins; } set { skins = value; } } + /// May be null. + public Skin DefaultSkin { get { return defaultSkin; } set { defaultSkin = value; } } + public ExposedList Events { get { return events; } set { events = value; } } + public ExposedList Animations { get { return animations; } set { animations = value; } } + public ExposedList IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } } + public ExposedList TransformConstraints { get { return transformConstraints; } set { transformConstraints = value; } } + public ExposedList PathConstraints { get { return pathConstraints; } set { pathConstraints = value; } } + public float Width { get { return width; } set { width = value; } } + public float Height { get { return height; } set { height = value; } } + /// The Spine version used to export this data. + public String Version { get { return version; } set { version = value; } } + public String Hash { get { return hash; } set { hash = value; } } + + // --- Bones. + + /// May be null. + public BoneData FindBone(String boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); + ExposedList bones = this.bones; + for (int i = 0, n = bones.Count; i < n; i++) + { + BoneData bone = bones.Items[i]; + if (bone.name == boneName) return bone; + } + return null; + } + + /// -1 if the bone was not found. + public int FindBoneIndex(String boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); + ExposedList bones = this.bones; + for (int i = 0, n = bones.Count; i < n; i++) + if (bones.Items[i].name == boneName) return i; + return -1; + } + + // --- Slots. + + /// May be null. + public SlotData FindSlot(String slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + ExposedList slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + { + SlotData slot = slots.Items[i]; + if (slot.name == slotName) return slot; + } + return null; + } + + /// -1 if the slot was not found. + public int FindSlotIndex(String slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + ExposedList slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + if (slots.Items[i].name == slotName) return i; + return -1; + } + + // --- Skins. + + /// May be null. + public Skin FindSkin(String skinName) + { + if (skinName == null) throw new ArgumentNullException("skinName", "skinName cannot be null."); + foreach (Skin skin in skins) + if (skin.name == skinName) return skin; + return null; + } + + // --- Events. + + /// May be null. + public EventData FindEvent(String eventDataName) + { + if (eventDataName == null) throw new ArgumentNullException("eventDataName", "eventDataName cannot be null."); + foreach (EventData eventData in events) + if (eventData.name == eventDataName) return eventData; + return null; + } + + // --- Animations. + + /// May be null. + public Animation FindAnimation(String animationName) + { + if (animationName == null) throw new ArgumentNullException("animationName", "animationName cannot be null."); + ExposedList animations = this.animations; + for (int i = 0, n = animations.Count; i < n; i++) + { + Animation animation = animations.Items[i]; + if (animation.name == animationName) return animation; + } + return null; + } + + // --- IK constraints. + + /// May be null. + public IkConstraintData FindIkConstraint(String constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + ExposedList ikConstraints = this.ikConstraints; + for (int i = 0, n = ikConstraints.Count; i < n; i++) + { + IkConstraintData ikConstraint = ikConstraints.Items[i]; + if (ikConstraint.name == constraintName) return ikConstraint; + } + return null; + } + + // --- Transform constraints. + + /// May be null. + public TransformConstraintData FindTransformConstraint(String constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + ExposedList transformConstraints = this.transformConstraints; + for (int i = 0, n = transformConstraints.Count; i < n; i++) + { + TransformConstraintData transformConstraint = transformConstraints.Items[i]; + if (transformConstraint.name == constraintName) return transformConstraint; + } + return null; + } + + // --- Path constraints. + + /// May be null. + public PathConstraintData FindPathConstraint(String constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + ExposedList pathConstraints = this.pathConstraints; + for (int i = 0, n = pathConstraints.Count; i < n; i++) + { + PathConstraintData constraint = pathConstraints.Items[i]; + if (constraint.name.Equals(constraintName)) return constraint; + } + return null; + } + + /// -1 if the path constraint was not found. + public int FindPathConstraintIndex(String pathConstraintName) + { + if (pathConstraintName == null) throw new ArgumentNullException("pathConstraintName", "pathConstraintName cannot be null."); + ExposedList pathConstraints = this.pathConstraints; + for (int i = 0, n = pathConstraints.Count; i < n; i++) + if (pathConstraints.Items[i].name.Equals(pathConstraintName)) return i; + return -1; + } + + // --- + + override public String ToString() + { + return name ?? base.ToString(); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/SkeletonJson.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/SkeletonJson.cs index bc51904..7b74eee 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/SkeletonJson.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/SkeletonJson.cs @@ -33,32 +33,36 @@ #endif using System; -using System.IO; using System.Collections.Generic; +using System.IO; #if WINDOWS_STOREAPP using System.Threading.Tasks; using Windows.Storage; #endif -namespace Spine3_4_02 { - public class SkeletonJson { - public float Scale { get; set; } +namespace Spine3_4_02 +{ + public class SkeletonJson + { + public float Scale { get; set; } - private AttachmentLoader attachmentLoader; - private List linkedMeshes = new List(); + private AttachmentLoader attachmentLoader; + private List linkedMeshes = new List(); - public SkeletonJson (params Atlas[] atlasArray) - : this(new AtlasAttachmentLoader(atlasArray)) { - } + public SkeletonJson(params Atlas[] atlasArray) + : this(new AtlasAttachmentLoader(atlasArray)) + { + } - public SkeletonJson (AttachmentLoader attachmentLoader) { - if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader", "attachmentLoader cannot be null."); - this.attachmentLoader = attachmentLoader; - Scale = 1; - } + public SkeletonJson(AttachmentLoader attachmentLoader) + { + if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader", "attachmentLoader cannot be null."); + this.attachmentLoader = attachmentLoader; + Scale = 1; + } - #if !(IS_UNITY) && WINDOWS_STOREAPP +#if !(IS_UNITY) && WINDOWS_STOREAPP private async Task ReadFile(string path) { var folder = Windows.ApplicationModel.Package.Current.InstalledLocation; var file = await folder.GetFileAsync(path).AsTask().ConfigureAwait(false); @@ -72,734 +76,845 @@ private async Task ReadFile(string path) { public SkeletonData ReadSkeletonData (String path) { return this.ReadFile(path).Result; } - #else - public SkeletonData ReadSkeletonData (String path) { - #if WINDOWS_PHONE +#else + public SkeletonData ReadSkeletonData(String path) + { +#if WINDOWS_PHONE Stream stream = Microsoft.Xna.Framework.TitleContainer.OpenStream(path); using (StreamReader reader = new StreamReader(stream)) { - #else - using (var reader = new StreamReader(path)) { - #endif // WINDOWS_PHONE - SkeletonData skeletonData = ReadSkeletonData(reader); - skeletonData.name = Path.GetFileNameWithoutExtension(path); - return skeletonData; - } - } - #endif // WINDOWS_STOREAPP - - public SkeletonData ReadSkeletonData (TextReader reader) { - if (reader == null) throw new ArgumentNullException("reader", "reader cannot be null."); - - var scale = this.Scale; - var skeletonData = new SkeletonData(); - - var root = Json.Deserialize(reader) as Dictionary; - if (root == null) throw new Exception("Invalid JSON."); - - // Skeleton. - if (root.ContainsKey("skeleton")) { - var skeletonMap = (Dictionary)root["skeleton"]; - skeletonData.hash = (String)skeletonMap["hash"]; - skeletonData.version = (String)skeletonMap["spine"]; - skeletonData.width = GetFloat(skeletonMap, "width", 0); - skeletonData.height = GetFloat(skeletonMap, "height", 0); - } - - // Bones. - foreach (Dictionary boneMap in (List)root["bones"]) { - BoneData parent = null; - if (boneMap.ContainsKey("parent")) { - parent = skeletonData.FindBone((String)boneMap["parent"]); - if (parent == null) - throw new Exception("Parent bone not found: " + boneMap["parent"]); - } - var data = new BoneData(skeletonData.Bones.Count, (String)boneMap["name"], parent); - data.length = GetFloat(boneMap, "length", 0) * scale; - data.x = GetFloat(boneMap, "x", 0) * scale; - data.y = GetFloat(boneMap, "y", 0) * scale; - data.rotation = GetFloat(boneMap, "rotation", 0); - data.scaleX = GetFloat(boneMap, "scaleX", 1); - data.scaleY = GetFloat(boneMap, "scaleY", 1); - data.shearX = GetFloat(boneMap, "shearX", 0); - data.shearY = GetFloat(boneMap, "shearY", 0); - data.inheritRotation = GetBoolean(boneMap, "inheritRotation", true); - data.inheritScale = GetBoolean(boneMap, "inheritScale", true); - - skeletonData.bones.Add(data); - } - - // Slots. - if (root.ContainsKey("slots")) { - foreach (Dictionary slotMap in (List)root["slots"]) { - var slotName = (String)slotMap["name"]; - var boneName = (String)slotMap["bone"]; - BoneData boneData = skeletonData.FindBone(boneName); - if (boneData == null) throw new Exception("Slot bone not found: " + boneName); - var data = new SlotData(skeletonData.Slots.Count, slotName, boneData); - - if (slotMap.ContainsKey("color")) { - var color = (String)slotMap["color"]; - data.r = ToColor(color, 0); - data.g = ToColor(color, 1); - data.b = ToColor(color, 2); - data.a = ToColor(color, 3); - } - - data.attachmentName = GetString(slotMap, "attachment", null); - if (slotMap.ContainsKey("blend")) - data.blendMode = (BlendMode)Enum.Parse(typeof(BlendMode), (String)slotMap["blend"], false); - else - data.blendMode = BlendMode.normal; - skeletonData.slots.Add(data); - } - } - - // IK constraints. - if (root.ContainsKey("ik")) { - foreach (Dictionary constraintMap in (List)root["ik"]) { - IkConstraintData data = new IkConstraintData((String)constraintMap["name"]); - - foreach (String boneName in (List)constraintMap["bones"]) { - BoneData bone = skeletonData.FindBone(boneName); - if (bone == null) throw new Exception("IK constraint bone not found: " + boneName); - data.bones.Add(bone); - } - - String targetName = (String)constraintMap["target"]; - data.target = skeletonData.FindBone(targetName); - if (data.target == null) throw new Exception("Target bone not found: " + targetName); - - data.bendDirection = GetBoolean(constraintMap, "bendPositive", true) ? 1 : -1; - data.mix = GetFloat(constraintMap, "mix", 1); - - skeletonData.ikConstraints.Add(data); - } - } - - // Transform constraints. - if (root.ContainsKey("transform")) { - foreach (Dictionary constraintMap in (List)root["transform"]) { - TransformConstraintData data = new TransformConstraintData((String)constraintMap["name"]); - - foreach (String boneName in (List)constraintMap["bones"]) { - BoneData bone = skeletonData.FindBone(boneName); - if (bone == null) throw new Exception("Transform constraint bone not found: " + boneName); - data.bones.Add(bone); - } - - String targetName = (String)constraintMap["target"]; - data.target = skeletonData.FindBone(targetName); - if (data.target == null) throw new Exception("Target bone not found: " + targetName); - - data.offsetRotation = GetFloat(constraintMap, "rotation", 0); - data.offsetX = GetFloat(constraintMap, "x", 0) * scale; - data.offsetY = GetFloat(constraintMap, "y", 0) * scale; - data.offsetScaleX = GetFloat(constraintMap, "scaleX", 0); - data.offsetScaleY = GetFloat(constraintMap, "scaleY", 0); - data.offsetShearY = GetFloat(constraintMap, "shearY", 0); - - data.rotateMix = GetFloat(constraintMap, "rotateMix", 1); - data.translateMix = GetFloat(constraintMap, "translateMix", 1); - data.scaleMix = GetFloat(constraintMap, "scaleMix", 1); - data.shearMix = GetFloat(constraintMap, "shearMix", 1); - - skeletonData.transformConstraints.Add(data); - } - } - - // Path constraints. - if(root.ContainsKey("path")) { - foreach (Dictionary constraintMap in (List)root["path"]) { - PathConstraintData data = new PathConstraintData((String)constraintMap["name"]); - - foreach (String boneName in (List)constraintMap["bones"]) { - BoneData bone = skeletonData.FindBone(boneName); - if (bone == null) throw new Exception("Path bone not found: " + boneName); - data.bones.Add(bone); - } - - String targetName = (String)constraintMap["target"]; - data.target = skeletonData.FindSlot(targetName); - if (data.target == null) throw new Exception("Target slot not found: " + targetName); - - data.positionMode = (PositionMode)Enum.Parse(typeof(PositionMode), GetString(constraintMap, "positionMode", "percent"), true); - data.spacingMode = (SpacingMode)Enum.Parse(typeof(SpacingMode), GetString(constraintMap, "spacingMode", "length"), true); - data.rotateMode = (RotateMode)Enum.Parse(typeof(RotateMode), GetString(constraintMap, "rotateMode", "tangent"), true); - data.offsetRotation = GetFloat(constraintMap, "rotation", 0); - data.position = GetFloat(constraintMap, "position", 0); - if (data.positionMode == PositionMode.Fixed) data.position *= scale; - data.spacing = GetFloat(constraintMap, "spacing", 0); - if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale; - data.rotateMix = GetFloat(constraintMap, "rotateMix", 1); - data.translateMix = GetFloat(constraintMap, "translateMix", 1); - - skeletonData.pathConstraints.Add(data); - } - } - - // Skins. - if (root.ContainsKey("skins")) { - foreach (KeyValuePair skinMap in (Dictionary)root["skins"]) { - var skin = new Skin(skinMap.Key); - foreach (KeyValuePair slotEntry in (Dictionary)skinMap.Value) { - int slotIndex = skeletonData.FindSlotIndex(slotEntry.Key); - foreach (KeyValuePair entry in ((Dictionary)slotEntry.Value)) { - try { - Attachment attachment = ReadAttachment((Dictionary)entry.Value, skin, slotIndex, entry.Key); - if (attachment != null) skin.AddAttachment(slotIndex, entry.Key, attachment); - } catch (Exception e) { - throw new Exception("Error reading attachment: " + entry.Key + ", skin: " + skin, e); - } - } - } - skeletonData.skins.Add(skin); - if (skin.name == "default") skeletonData.defaultSkin = skin; - } - } - - // Linked meshes. - for (int i = 0, n = linkedMeshes.Count; i < n; i++) { - LinkedMesh linkedMesh = linkedMeshes[i]; - Skin skin = linkedMesh.skin == null ? skeletonData.defaultSkin : skeletonData.FindSkin(linkedMesh.skin); - if (skin == null) throw new Exception("Slot not found: " + linkedMesh.skin); - Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); - if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); - linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; - linkedMesh.mesh.UpdateUVs(); - } - linkedMeshes.Clear(); - - // Events. - if (root.ContainsKey("events")) { - foreach (KeyValuePair entry in (Dictionary)root["events"]) { - var entryMap = (Dictionary)entry.Value; - var data = new EventData(entry.Key); - data.Int = GetInt(entryMap, "int", 0); - data.Float = GetFloat(entryMap, "float", 0); - data.String = GetString(entryMap, "string", null); - skeletonData.events.Add(data); - } - } - - // Animations. - if (root.ContainsKey("animations")) { - foreach (KeyValuePair entry in (Dictionary)root["animations"]) { - try { - ReadAnimation((Dictionary)entry.Value, entry.Key, skeletonData); - } catch (Exception e) { - throw new Exception("Error reading animation: " + entry.Key, e); - } - } - } - - skeletonData.bones.TrimExcess(); - skeletonData.slots.TrimExcess(); - skeletonData.skins.TrimExcess(); - skeletonData.events.TrimExcess(); - skeletonData.animations.TrimExcess(); - skeletonData.ikConstraints.TrimExcess(); - return skeletonData; - } - - private Attachment ReadAttachment (Dictionary map, Skin skin, int slotIndex, String name) { - var scale = this.Scale; - name = GetString(map, "name", name); - - var typeName = GetString(map, "type", "region"); - if (typeName == "skinnedmesh") typeName = "weightedmesh"; - if (typeName == "weightedmesh") typeName = "mesh"; - if (typeName == "weightedlinkedmesh") typeName = "linkedmesh"; - var type = (AttachmentType)Enum.Parse(typeof(AttachmentType), typeName, true); - - String path = GetString(map, "path", name); - - switch (type) { - case AttachmentType.Region: - RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); - if (region == null) return null; - region.Path = path; - region.x = GetFloat(map, "x", 0) * scale; - region.y = GetFloat(map, "y", 0) * scale; - region.scaleX = GetFloat(map, "scaleX", 1); - region.scaleY = GetFloat(map, "scaleY", 1); - region.rotation = GetFloat(map, "rotation", 0); - region.width = GetFloat(map, "width", 32) * scale; - region.height = GetFloat(map, "height", 32) * scale; - region.UpdateOffset(); - - if (map.ContainsKey("color")) { - var color = (String)map["color"]; - region.r = ToColor(color, 0); - region.g = ToColor(color, 1); - region.b = ToColor(color, 2); - region.a = ToColor(color, 3); - } - - region.UpdateOffset(); - return region; - case AttachmentType.Boundingbox: - BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); - if (box == null) return null; - ReadVertices(map, box, GetInt(map, "vertexCount", 0) << 1); - return box; - case AttachmentType.Mesh: - case AttachmentType.Linkedmesh: { - MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); - if (mesh == null) return null; - mesh.Path = path; - - if (map.ContainsKey("color")) { - var color = (String)map["color"]; - mesh.r = ToColor(color, 0); - mesh.g = ToColor(color, 1); - mesh.b = ToColor(color, 2); - mesh.a = ToColor(color, 3); - } - - mesh.Width = GetFloat(map, "width", 0) * scale; - mesh.Height = GetFloat(map, "height", 0) * scale; - - String parent = GetString(map, "parent", null); - if (parent != null) { - mesh.InheritDeform = GetBoolean(map, "deform", true); - linkedMeshes.Add(new LinkedMesh(mesh, GetString(map, "skin", null), slotIndex, parent)); - return mesh; - } - - float[] uvs = GetFloatArray(map, "uvs", 1); - ReadVertices(map, mesh, uvs.Length); - mesh.triangles = GetIntArray(map, "triangles"); - mesh.regionUVs = uvs; - mesh.UpdateUVs(); - - if (map.ContainsKey("hull")) mesh.HullLength = GetInt(map, "hull", 0) * 2; - if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges"); - return mesh; - } - case AttachmentType.Path: { - PathAttachment pathAttachment = attachmentLoader.NewPathAttachment(skin, name); - if (pathAttachment == null) return null; - pathAttachment.closed = GetBoolean(map, "closed", false); - pathAttachment.constantSpeed = GetBoolean(map, "constantSpeed", true); - - int vertexCount = GetInt(map, "vertexCount", 0); - ReadVertices(map, pathAttachment, vertexCount << 1); - - // potential BOZO see Java impl - pathAttachment.lengths = GetFloatArray(map, "lengths", scale); - return pathAttachment; - } - } - return null; - } - - private void ReadVertices (Dictionary map, VertexAttachment attachment, int verticesLength) { - attachment.WorldVerticesLength = verticesLength; - float[] vertices = GetFloatArray(map, "vertices", 1); - float scale = Scale; - if (verticesLength == vertices.Length) { - if (scale != 1) { - for (int i = 0; i < vertices.Length; i++) { - vertices[i] *= scale; - } - } - attachment.vertices = vertices; - return; - } - ExposedList weights = new ExposedList(verticesLength * 3 * 3); - ExposedList bones = new ExposedList(verticesLength * 3); - for (int i = 0, n = vertices.Length; i < n;) { - int boneCount = (int)vertices[i++]; - bones.Add(boneCount); - for (int nn = i + boneCount * 4; i < nn; i += 4) { - bones.Add((int)vertices[i]); - weights.Add(vertices[i + 1] * this.Scale); - weights.Add(vertices[i + 2] * this.Scale); - weights.Add(vertices[i + 3]); - } - } - attachment.bones = bones.ToArray(); - attachment.vertices = weights.ToArray(); - } - - private void ReadAnimation (Dictionary map, String name, SkeletonData skeletonData) { - var scale = this.Scale; - var timelines = new ExposedList(); - float duration = 0; - - // Slot timelines. - if (map.ContainsKey("slots")) { - foreach (KeyValuePair entry in (Dictionary)map["slots"]) { - String slotName = entry.Key; - int slotIndex = skeletonData.FindSlotIndex(slotName); - var timelineMap = (Dictionary)entry.Value; - foreach (KeyValuePair timelineEntry in timelineMap) { - var values = (List)timelineEntry.Value; - var timelineName = (String)timelineEntry.Key; - if (timelineName == "color") { - var timeline = new ColorTimeline(values.Count); - timeline.slotIndex = slotIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - String c = (String)valueMap["color"]; - timeline.SetFrame(frameIndex, time, ToColor(c, 0), ToColor(c, 1), ToColor(c, 2), ToColor(c, 3)); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * ColorTimeline.ENTRIES]); - - } else if (timelineName == "attachment") { - var timeline = new AttachmentTimeline(values.Count); - timeline.slotIndex = slotIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - timeline.SetFrame(frameIndex++, time, (String)valueMap["name"]); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); - - } else - throw new Exception("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")"); - } - } - } - - // Bone timelines. - if (map.ContainsKey("bones")) { - foreach (KeyValuePair entry in (Dictionary)map["bones"]) { - String boneName = entry.Key; - int boneIndex = skeletonData.FindBoneIndex(boneName); - if (boneIndex == -1) throw new Exception("Bone not found: " + boneName); - var timelineMap = (Dictionary)entry.Value; - foreach (KeyValuePair timelineEntry in timelineMap) { - var values = (List)timelineEntry.Value; - var timelineName = (String)timelineEntry.Key; - if (timelineName == "rotate") { - var timeline = new RotateTimeline(values.Count); - timeline.boneIndex = boneIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - timeline.SetFrame(frameIndex, (float)valueMap["time"], (float)valueMap["angle"]); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * RotateTimeline.ENTRIES]); - - } else if (timelineName == "translate" || timelineName == "scale" || timelineName == "shear") { - TranslateTimeline timeline; - float timelineScale = 1; - if (timelineName == "scale") - timeline = new ScaleTimeline(values.Count); - else if (timelineName == "shear") - timeline = new ShearTimeline(values.Count); - else { - timeline = new TranslateTimeline(values.Count); - timelineScale = scale; - } - timeline.boneIndex = boneIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - float x = GetFloat(valueMap, "x", 0); - float y = GetFloat(valueMap, "y", 0); - timeline.SetFrame(frameIndex, time, x * timelineScale, y * timelineScale); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TranslateTimeline.ENTRIES]); - - } else - throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")"); - } - } - } - - // IK constraint timelines. - if (map.ContainsKey("ik")) { - foreach (KeyValuePair constraintMap in (Dictionary)map["ik"]) { - IkConstraintData constraint = skeletonData.FindIkConstraint(constraintMap.Key); - var values = (List)constraintMap.Value; - var timeline = new IkConstraintTimeline(values.Count); - timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(constraint); - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - float mix = GetFloat(valueMap, "mix", 1); - bool bendPositive = GetBoolean(valueMap, "bendPositive", true); - timeline.SetFrame(frameIndex, time, mix, bendPositive ? 1 : -1); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * IkConstraintTimeline.ENTRIES]); - } - } - - // Transform constraint timelines. - if (map.ContainsKey("transform")) { - foreach (KeyValuePair constraintMap in (Dictionary)map["transform"]) { - TransformConstraintData constraint = skeletonData.FindTransformConstraint(constraintMap.Key); - var values = (List)constraintMap.Value; - var timeline = new TransformConstraintTimeline(values.Count); - timeline.transformConstraintIndex = skeletonData.transformConstraints.IndexOf(constraint); - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - float rotateMix = GetFloat(valueMap, "rotateMix", 1); - float translateMix = GetFloat(valueMap, "translateMix", 1); - float scaleMix = GetFloat(valueMap, "scaleMix", 1); - float shearMix = GetFloat(valueMap, "shearMix", 1); - timeline.SetFrame(frameIndex, time, rotateMix, translateMix, scaleMix, shearMix); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TransformConstraintTimeline.ENTRIES]); - } - } - - // Path constraint timelines. - if (map.ContainsKey("paths")) { - foreach (KeyValuePair constraintMap in (Dictionary)map["paths"]) { - int index = skeletonData.FindPathConstraintIndex(constraintMap.Key); - if (index == -1) throw new Exception("Path constraint not found: " + constraintMap.Key); - PathConstraintData data = skeletonData.pathConstraints.Items[index]; - var timelineMap = (Dictionary)constraintMap.Value; - foreach (KeyValuePair timelineEntry in timelineMap) { - var values = (List)timelineEntry.Value; - var timelineName = (String)timelineEntry.Key; - if (timelineName == "position" || timelineName == "spacing") { - PathConstraintPositionTimeline timeline; - float timelineScale = 1; - if (timelineName == "spacing") { - timeline = new PathConstraintSpacingTimeline(values.Count); - if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale; - } - else { - timeline = new PathConstraintPositionTimeline(values.Count); - if (data.positionMode == PositionMode.Fixed) timelineScale = scale; - } - timeline.pathConstraintIndex = index; - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - timeline.SetFrame(frameIndex, (float)valueMap["time"], GetFloat(valueMap, timelineName, 0) * timelineScale); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintPositionTimeline.ENTRIES]); - } - else if (timelineName == "mix") { - PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(values.Count); - timeline.pathConstraintIndex = index; - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - timeline.SetFrame(frameIndex, (float)valueMap["time"], GetFloat(valueMap, "rotateMix", 1), GetFloat(valueMap, "translateMix", 1)); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintMixTimeline.ENTRIES]); - } - } - } - } - - // Deform timelines. - if (map.ContainsKey("deform")) { - foreach (KeyValuePair deformMap in (Dictionary)map["deform"]) { - Skin skin = skeletonData.FindSkin(deformMap.Key); - foreach (KeyValuePair slotMap in (Dictionary)deformMap.Value) { - int slotIndex = skeletonData.FindSlotIndex(slotMap.Key); - if (slotIndex == -1) throw new Exception("Slot not found: " + slotMap.Key); - foreach (KeyValuePair timelineMap in (Dictionary)slotMap.Value) { - var values = (List)timelineMap.Value; - VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, timelineMap.Key); - if (attachment == null) throw new Exception("Deform attachment not found: " + timelineMap.Key); - bool weighted = attachment.bones != null; - float[] vertices = attachment.vertices; - int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length; - - var timeline = new DeformTimeline(values.Count); - timeline.slotIndex = slotIndex; - timeline.attachment = attachment; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float[] deform; - if (!valueMap.ContainsKey("vertices")) { - deform = weighted ? new float[deformLength] : vertices; - } else { - deform = new float[deformLength]; - int start = GetInt(valueMap, "offset", 0); - float[] verticesValue = GetFloatArray(valueMap, "vertices", 1); - Array.Copy(verticesValue, 0, deform, start, verticesValue.Length); - if (scale != 1) { - for (int i = start, n = i + verticesValue.Length; i < n; i++) - deform[i] *= scale; - } - - if (!weighted) { - for (int i = 0; i < deformLength; i++) - deform[i] += vertices[i]; - } - } - - timeline.SetFrame(frameIndex, (float)valueMap["time"], deform); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); - } - } - } - } - - // Draw order timeline. - if (map.ContainsKey("drawOrder") || map.ContainsKey("draworder")) { - var values = (List)map[map.ContainsKey("drawOrder") ? "drawOrder" : "draworder"]; - var timeline = new DrawOrderTimeline(values.Count); - int slotCount = skeletonData.slots.Count; - int frameIndex = 0; - foreach (Dictionary drawOrderMap in values) { - int[] drawOrder = null; - if (drawOrderMap.ContainsKey("offsets")) { - drawOrder = new int[slotCount]; - for (int i = slotCount - 1; i >= 0; i--) - drawOrder[i] = -1; - var offsets = (List)drawOrderMap["offsets"]; - int[] unchanged = new int[slotCount - offsets.Count]; - int originalIndex = 0, unchangedIndex = 0; - foreach (Dictionary offsetMap in offsets) { - int slotIndex = skeletonData.FindSlotIndex((String)offsetMap["slot"]); - if (slotIndex == -1) throw new Exception("Slot not found: " + offsetMap["slot"]); - // Collect unchanged items. - while (originalIndex != slotIndex) - unchanged[unchangedIndex++] = originalIndex++; - // Set changed items. - int index = originalIndex + (int)(float)offsetMap["offset"]; - drawOrder[index] = originalIndex++; - } - // Collect remaining unchanged items. - while (originalIndex < slotCount) - unchanged[unchangedIndex++] = originalIndex++; - // Fill in unchanged items. - for (int i = slotCount - 1; i >= 0; i--) - if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex]; - } - timeline.SetFrame(frameIndex++, (float)drawOrderMap["time"], drawOrder); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); - } - - // Event timeline. - if (map.ContainsKey("events")) { - var eventsMap = (List)map["events"]; - var timeline = new EventTimeline(eventsMap.Count); - int frameIndex = 0; - foreach (Dictionary eventMap in eventsMap) { - EventData eventData = skeletonData.FindEvent((String)eventMap["name"]); - if (eventData == null) throw new Exception("Event not found: " + eventMap["name"]); - var e = new Event((float)eventMap["time"], eventData); - e.Int = GetInt(eventMap, "int", eventData.Int); - e.Float = GetFloat(eventMap, "float", eventData.Float); - e.String = GetString(eventMap, "string", eventData.String); - timeline.SetFrame(frameIndex++, e); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); - } - - timelines.TrimExcess(); - skeletonData.animations.Add(new Animation(name, timelines, duration)); - } - - static void ReadCurve (Dictionary valueMap, CurveTimeline timeline, int frameIndex) { - if (!valueMap.ContainsKey("curve")) - return; - Object curveObject = valueMap["curve"]; - if (curveObject.Equals("stepped")) - timeline.SetStepped(frameIndex); - else { - var curve = curveObject as List; - if (curve != null) - timeline.SetCurve(frameIndex, (float)curve[0], (float)curve[1], (float)curve[2], (float)curve[3]); - } - } - - internal class LinkedMesh { - internal String parent, skin; - internal int slotIndex; - internal MeshAttachment mesh; - - public LinkedMesh (MeshAttachment mesh, String skin, int slotIndex, String parent) { - this.mesh = mesh; - this.skin = skin; - this.slotIndex = slotIndex; - this.parent = parent; - } - } - - static float[] GetFloatArray(Dictionary map, String name, float scale) { - var list = (List)map[name]; - var values = new float[list.Count]; - if (scale == 1) { - for (int i = 0, n = list.Count; i < n; i++) - values[i] = (float)list[i]; - } else { - for (int i = 0, n = list.Count; i < n; i++) - values[i] = (float)list[i] * scale; - } - return values; - } - - static int[] GetIntArray(Dictionary map, String name) { - var list = (List)map[name]; - var values = new int[list.Count]; - for (int i = 0, n = list.Count; i < n; i++) - values[i] = (int)(float)list[i]; - return values; - } - - static float GetFloat(Dictionary map, String name, float defaultValue) { - if (!map.ContainsKey(name)) - return defaultValue; - return (float)map[name]; - } - - static int GetInt(Dictionary map, String name, int defaultValue) { - if (!map.ContainsKey(name)) - return defaultValue; - return (int)(float)map[name]; - } - - static bool GetBoolean(Dictionary map, String name, bool defaultValue) { - if (!map.ContainsKey(name)) - return defaultValue; - return (bool)map[name]; - } - - static String GetString(Dictionary map, String name, String defaultValue) { - if (!map.ContainsKey(name)) - return defaultValue; - return (String)map[name]; - } - - static float ToColor(String hexString, int colorIndex) { - if (hexString.Length != 8) - throw new ArgumentException("Color hexidecimal length must be 8, recieved: " + hexString, "hexString"); - return Convert.ToInt32(hexString.Substring(colorIndex * 2, 2), 16) / (float)255; - } - } +#else + using (var reader = new StreamReader(path)) + { +#endif // WINDOWS_PHONE + SkeletonData skeletonData = ReadSkeletonData(reader); + skeletonData.name = Path.GetFileNameWithoutExtension(path); + return skeletonData; + } + } +#endif // WINDOWS_STOREAPP + + public SkeletonData ReadSkeletonData(TextReader reader) + { + if (reader == null) throw new ArgumentNullException("reader", "reader cannot be null."); + + var scale = this.Scale; + var skeletonData = new SkeletonData(); + + var root = Json.Deserialize(reader) as Dictionary; + if (root == null) throw new Exception("Invalid JSON."); + + // Skeleton. + if (root.ContainsKey("skeleton")) + { + var skeletonMap = (Dictionary)root["skeleton"]; + skeletonData.hash = (String)skeletonMap["hash"]; + skeletonData.version = (String)skeletonMap["spine"]; + skeletonData.width = GetFloat(skeletonMap, "width", 0); + skeletonData.height = GetFloat(skeletonMap, "height", 0); + } + + // Bones. + foreach (Dictionary boneMap in (List)root["bones"]) + { + BoneData parent = null; + if (boneMap.ContainsKey("parent")) + { + parent = skeletonData.FindBone((String)boneMap["parent"]); + if (parent == null) + throw new Exception("Parent bone not found: " + boneMap["parent"]); + } + var data = new BoneData(skeletonData.Bones.Count, (String)boneMap["name"], parent); + data.length = GetFloat(boneMap, "length", 0) * scale; + data.x = GetFloat(boneMap, "x", 0) * scale; + data.y = GetFloat(boneMap, "y", 0) * scale; + data.rotation = GetFloat(boneMap, "rotation", 0); + data.scaleX = GetFloat(boneMap, "scaleX", 1); + data.scaleY = GetFloat(boneMap, "scaleY", 1); + data.shearX = GetFloat(boneMap, "shearX", 0); + data.shearY = GetFloat(boneMap, "shearY", 0); + data.inheritRotation = GetBoolean(boneMap, "inheritRotation", true); + data.inheritScale = GetBoolean(boneMap, "inheritScale", true); + + skeletonData.bones.Add(data); + } + + // Slots. + if (root.ContainsKey("slots")) + { + foreach (Dictionary slotMap in (List)root["slots"]) + { + var slotName = (String)slotMap["name"]; + var boneName = (String)slotMap["bone"]; + BoneData boneData = skeletonData.FindBone(boneName); + if (boneData == null) throw new Exception("Slot bone not found: " + boneName); + var data = new SlotData(skeletonData.Slots.Count, slotName, boneData); + + if (slotMap.ContainsKey("color")) + { + var color = (String)slotMap["color"]; + data.r = ToColor(color, 0); + data.g = ToColor(color, 1); + data.b = ToColor(color, 2); + data.a = ToColor(color, 3); + } + + data.attachmentName = GetString(slotMap, "attachment", null); + if (slotMap.ContainsKey("blend")) + data.blendMode = (BlendMode)Enum.Parse(typeof(BlendMode), (String)slotMap["blend"], false); + else + data.blendMode = BlendMode.normal; + skeletonData.slots.Add(data); + } + } + + // IK constraints. + if (root.ContainsKey("ik")) + { + foreach (Dictionary constraintMap in (List)root["ik"]) + { + IkConstraintData data = new IkConstraintData((String)constraintMap["name"]); + + foreach (String boneName in (List)constraintMap["bones"]) + { + BoneData bone = skeletonData.FindBone(boneName); + if (bone == null) throw new Exception("IK constraint bone not found: " + boneName); + data.bones.Add(bone); + } + + String targetName = (String)constraintMap["target"]; + data.target = skeletonData.FindBone(targetName); + if (data.target == null) throw new Exception("Target bone not found: " + targetName); + + data.bendDirection = GetBoolean(constraintMap, "bendPositive", true) ? 1 : -1; + data.mix = GetFloat(constraintMap, "mix", 1); + + skeletonData.ikConstraints.Add(data); + } + } + + // Transform constraints. + if (root.ContainsKey("transform")) + { + foreach (Dictionary constraintMap in (List)root["transform"]) + { + TransformConstraintData data = new TransformConstraintData((String)constraintMap["name"]); + + foreach (String boneName in (List)constraintMap["bones"]) + { + BoneData bone = skeletonData.FindBone(boneName); + if (bone == null) throw new Exception("Transform constraint bone not found: " + boneName); + data.bones.Add(bone); + } + + String targetName = (String)constraintMap["target"]; + data.target = skeletonData.FindBone(targetName); + if (data.target == null) throw new Exception("Target bone not found: " + targetName); + + data.offsetRotation = GetFloat(constraintMap, "rotation", 0); + data.offsetX = GetFloat(constraintMap, "x", 0) * scale; + data.offsetY = GetFloat(constraintMap, "y", 0) * scale; + data.offsetScaleX = GetFloat(constraintMap, "scaleX", 0); + data.offsetScaleY = GetFloat(constraintMap, "scaleY", 0); + data.offsetShearY = GetFloat(constraintMap, "shearY", 0); + + data.rotateMix = GetFloat(constraintMap, "rotateMix", 1); + data.translateMix = GetFloat(constraintMap, "translateMix", 1); + data.scaleMix = GetFloat(constraintMap, "scaleMix", 1); + data.shearMix = GetFloat(constraintMap, "shearMix", 1); + + skeletonData.transformConstraints.Add(data); + } + } + + // Path constraints. + if (root.ContainsKey("path")) + { + foreach (Dictionary constraintMap in (List)root["path"]) + { + PathConstraintData data = new PathConstraintData((String)constraintMap["name"]); + + foreach (String boneName in (List)constraintMap["bones"]) + { + BoneData bone = skeletonData.FindBone(boneName); + if (bone == null) throw new Exception("Path bone not found: " + boneName); + data.bones.Add(bone); + } + + String targetName = (String)constraintMap["target"]; + data.target = skeletonData.FindSlot(targetName); + if (data.target == null) throw new Exception("Target slot not found: " + targetName); + + data.positionMode = (PositionMode)Enum.Parse(typeof(PositionMode), GetString(constraintMap, "positionMode", "percent"), true); + data.spacingMode = (SpacingMode)Enum.Parse(typeof(SpacingMode), GetString(constraintMap, "spacingMode", "length"), true); + data.rotateMode = (RotateMode)Enum.Parse(typeof(RotateMode), GetString(constraintMap, "rotateMode", "tangent"), true); + data.offsetRotation = GetFloat(constraintMap, "rotation", 0); + data.position = GetFloat(constraintMap, "position", 0); + if (data.positionMode == PositionMode.Fixed) data.position *= scale; + data.spacing = GetFloat(constraintMap, "spacing", 0); + if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale; + data.rotateMix = GetFloat(constraintMap, "rotateMix", 1); + data.translateMix = GetFloat(constraintMap, "translateMix", 1); + + skeletonData.pathConstraints.Add(data); + } + } + + // Skins. + if (root.ContainsKey("skins")) + { + foreach (KeyValuePair skinMap in (Dictionary)root["skins"]) + { + var skin = new Skin(skinMap.Key); + foreach (KeyValuePair slotEntry in (Dictionary)skinMap.Value) + { + int slotIndex = skeletonData.FindSlotIndex(slotEntry.Key); + foreach (KeyValuePair entry in ((Dictionary)slotEntry.Value)) + { + try + { + Attachment attachment = ReadAttachment((Dictionary)entry.Value, skin, slotIndex, entry.Key); + if (attachment != null) skin.AddAttachment(slotIndex, entry.Key, attachment); + } + catch (Exception e) + { + throw new Exception("Error reading attachment: " + entry.Key + ", skin: " + skin, e); + } + } + } + skeletonData.skins.Add(skin); + if (skin.name == "default") skeletonData.defaultSkin = skin; + } + } + + // Linked meshes. + for (int i = 0, n = linkedMeshes.Count; i < n; i++) + { + LinkedMesh linkedMesh = linkedMeshes[i]; + Skin skin = linkedMesh.skin == null ? skeletonData.defaultSkin : skeletonData.FindSkin(linkedMesh.skin); + if (skin == null) throw new Exception("Slot not found: " + linkedMesh.skin); + Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); + if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); + linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; + linkedMesh.mesh.UpdateUVs(); + } + linkedMeshes.Clear(); + + // Events. + if (root.ContainsKey("events")) + { + foreach (KeyValuePair entry in (Dictionary)root["events"]) + { + var entryMap = (Dictionary)entry.Value; + var data = new EventData(entry.Key); + data.Int = GetInt(entryMap, "int", 0); + data.Float = GetFloat(entryMap, "float", 0); + data.String = GetString(entryMap, "string", null); + skeletonData.events.Add(data); + } + } + + // Animations. + if (root.ContainsKey("animations")) + { + foreach (KeyValuePair entry in (Dictionary)root["animations"]) + { + try + { + ReadAnimation((Dictionary)entry.Value, entry.Key, skeletonData); + } + catch (Exception e) + { + throw new Exception("Error reading animation: " + entry.Key, e); + } + } + } + + skeletonData.bones.TrimExcess(); + skeletonData.slots.TrimExcess(); + skeletonData.skins.TrimExcess(); + skeletonData.events.TrimExcess(); + skeletonData.animations.TrimExcess(); + skeletonData.ikConstraints.TrimExcess(); + return skeletonData; + } + + private Attachment ReadAttachment(Dictionary map, Skin skin, int slotIndex, String name) + { + var scale = this.Scale; + name = GetString(map, "name", name); + + var typeName = GetString(map, "type", "region"); + if (typeName == "skinnedmesh") typeName = "weightedmesh"; + if (typeName == "weightedmesh") typeName = "mesh"; + if (typeName == "weightedlinkedmesh") typeName = "linkedmesh"; + var type = (AttachmentType)Enum.Parse(typeof(AttachmentType), typeName, true); + + String path = GetString(map, "path", name); + + switch (type) + { + case AttachmentType.Region: + RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); + if (region == null) return null; + region.Path = path; + region.x = GetFloat(map, "x", 0) * scale; + region.y = GetFloat(map, "y", 0) * scale; + region.scaleX = GetFloat(map, "scaleX", 1); + region.scaleY = GetFloat(map, "scaleY", 1); + region.rotation = GetFloat(map, "rotation", 0); + region.width = GetFloat(map, "width", 32) * scale; + region.height = GetFloat(map, "height", 32) * scale; + region.UpdateOffset(); + + if (map.ContainsKey("color")) + { + var color = (String)map["color"]; + region.r = ToColor(color, 0); + region.g = ToColor(color, 1); + region.b = ToColor(color, 2); + region.a = ToColor(color, 3); + } + + region.UpdateOffset(); + return region; + case AttachmentType.Boundingbox: + BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); + if (box == null) return null; + ReadVertices(map, box, GetInt(map, "vertexCount", 0) << 1); + return box; + case AttachmentType.Mesh: + case AttachmentType.Linkedmesh: + { + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); + if (mesh == null) return null; + mesh.Path = path; + + if (map.ContainsKey("color")) + { + var color = (String)map["color"]; + mesh.r = ToColor(color, 0); + mesh.g = ToColor(color, 1); + mesh.b = ToColor(color, 2); + mesh.a = ToColor(color, 3); + } + + mesh.Width = GetFloat(map, "width", 0) * scale; + mesh.Height = GetFloat(map, "height", 0) * scale; + + String parent = GetString(map, "parent", null); + if (parent != null) + { + mesh.InheritDeform = GetBoolean(map, "deform", true); + linkedMeshes.Add(new LinkedMesh(mesh, GetString(map, "skin", null), slotIndex, parent)); + return mesh; + } + + float[] uvs = GetFloatArray(map, "uvs", 1); + ReadVertices(map, mesh, uvs.Length); + mesh.triangles = GetIntArray(map, "triangles"); + mesh.regionUVs = uvs; + mesh.UpdateUVs(); + + if (map.ContainsKey("hull")) mesh.HullLength = GetInt(map, "hull", 0) * 2; + if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges"); + return mesh; + } + case AttachmentType.Path: + { + PathAttachment pathAttachment = attachmentLoader.NewPathAttachment(skin, name); + if (pathAttachment == null) return null; + pathAttachment.closed = GetBoolean(map, "closed", false); + pathAttachment.constantSpeed = GetBoolean(map, "constantSpeed", true); + + int vertexCount = GetInt(map, "vertexCount", 0); + ReadVertices(map, pathAttachment, vertexCount << 1); + + // potential BOZO see Java impl + pathAttachment.lengths = GetFloatArray(map, "lengths", scale); + return pathAttachment; + } + } + return null; + } + + private void ReadVertices(Dictionary map, VertexAttachment attachment, int verticesLength) + { + attachment.WorldVerticesLength = verticesLength; + float[] vertices = GetFloatArray(map, "vertices", 1); + float scale = Scale; + if (verticesLength == vertices.Length) + { + if (scale != 1) + { + for (int i = 0; i < vertices.Length; i++) + { + vertices[i] *= scale; + } + } + attachment.vertices = vertices; + return; + } + ExposedList weights = new ExposedList(verticesLength * 3 * 3); + ExposedList bones = new ExposedList(verticesLength * 3); + for (int i = 0, n = vertices.Length; i < n;) + { + int boneCount = (int)vertices[i++]; + bones.Add(boneCount); + for (int nn = i + boneCount * 4; i < nn; i += 4) + { + bones.Add((int)vertices[i]); + weights.Add(vertices[i + 1] * this.Scale); + weights.Add(vertices[i + 2] * this.Scale); + weights.Add(vertices[i + 3]); + } + } + attachment.bones = bones.ToArray(); + attachment.vertices = weights.ToArray(); + } + + private void ReadAnimation(Dictionary map, String name, SkeletonData skeletonData) + { + var scale = this.Scale; + var timelines = new ExposedList(); + float duration = 0; + + // Slot timelines. + if (map.ContainsKey("slots")) + { + foreach (KeyValuePair entry in (Dictionary)map["slots"]) + { + String slotName = entry.Key; + int slotIndex = skeletonData.FindSlotIndex(slotName); + var timelineMap = (Dictionary)entry.Value; + foreach (KeyValuePair timelineEntry in timelineMap) + { + var values = (List)timelineEntry.Value; + var timelineName = timelineEntry.Key; + if (timelineName == "color") + { + var timeline = new ColorTimeline(values.Count); + timeline.slotIndex = slotIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + String c = (String)valueMap["color"]; + timeline.SetFrame(frameIndex, time, ToColor(c, 0), ToColor(c, 1), ToColor(c, 2), ToColor(c, 3)); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * ColorTimeline.ENTRIES]); + + } + else if (timelineName == "attachment") + { + var timeline = new AttachmentTimeline(values.Count); + timeline.slotIndex = slotIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + timeline.SetFrame(frameIndex++, time, (String)valueMap["name"]); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); + + } + else + throw new Exception("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")"); + } + } + } + + // Bone timelines. + if (map.ContainsKey("bones")) + { + foreach (KeyValuePair entry in (Dictionary)map["bones"]) + { + String boneName = entry.Key; + int boneIndex = skeletonData.FindBoneIndex(boneName); + if (boneIndex == -1) throw new Exception("Bone not found: " + boneName); + var timelineMap = (Dictionary)entry.Value; + foreach (KeyValuePair timelineEntry in timelineMap) + { + var values = (List)timelineEntry.Value; + var timelineName = timelineEntry.Key; + if (timelineName == "rotate") + { + var timeline = new RotateTimeline(values.Count); + timeline.boneIndex = boneIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + timeline.SetFrame(frameIndex, (float)valueMap["time"], (float)valueMap["angle"]); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * RotateTimeline.ENTRIES]); + + } + else if (timelineName == "translate" || timelineName == "scale" || timelineName == "shear") + { + TranslateTimeline timeline; + float timelineScale = 1; + if (timelineName == "scale") + timeline = new ScaleTimeline(values.Count); + else if (timelineName == "shear") + timeline = new ShearTimeline(values.Count); + else + { + timeline = new TranslateTimeline(values.Count); + timelineScale = scale; + } + timeline.boneIndex = boneIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + float x = GetFloat(valueMap, "x", 0); + float y = GetFloat(valueMap, "y", 0); + timeline.SetFrame(frameIndex, time, x * timelineScale, y * timelineScale); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TranslateTimeline.ENTRIES]); + + } + else + throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")"); + } + } + } + + // IK constraint timelines. + if (map.ContainsKey("ik")) + { + foreach (KeyValuePair constraintMap in (Dictionary)map["ik"]) + { + IkConstraintData constraint = skeletonData.FindIkConstraint(constraintMap.Key); + var values = (List)constraintMap.Value; + var timeline = new IkConstraintTimeline(values.Count); + timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(constraint); + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + float mix = GetFloat(valueMap, "mix", 1); + bool bendPositive = GetBoolean(valueMap, "bendPositive", true); + timeline.SetFrame(frameIndex, time, mix, bendPositive ? 1 : -1); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * IkConstraintTimeline.ENTRIES]); + } + } + + // Transform constraint timelines. + if (map.ContainsKey("transform")) + { + foreach (KeyValuePair constraintMap in (Dictionary)map["transform"]) + { + TransformConstraintData constraint = skeletonData.FindTransformConstraint(constraintMap.Key); + var values = (List)constraintMap.Value; + var timeline = new TransformConstraintTimeline(values.Count); + timeline.transformConstraintIndex = skeletonData.transformConstraints.IndexOf(constraint); + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + float rotateMix = GetFloat(valueMap, "rotateMix", 1); + float translateMix = GetFloat(valueMap, "translateMix", 1); + float scaleMix = GetFloat(valueMap, "scaleMix", 1); + float shearMix = GetFloat(valueMap, "shearMix", 1); + timeline.SetFrame(frameIndex, time, rotateMix, translateMix, scaleMix, shearMix); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TransformConstraintTimeline.ENTRIES]); + } + } + + // Path constraint timelines. + if (map.ContainsKey("paths")) + { + foreach (KeyValuePair constraintMap in (Dictionary)map["paths"]) + { + int index = skeletonData.FindPathConstraintIndex(constraintMap.Key); + if (index == -1) throw new Exception("Path constraint not found: " + constraintMap.Key); + PathConstraintData data = skeletonData.pathConstraints.Items[index]; + var timelineMap = (Dictionary)constraintMap.Value; + foreach (KeyValuePair timelineEntry in timelineMap) + { + var values = (List)timelineEntry.Value; + var timelineName = timelineEntry.Key; + if (timelineName == "position" || timelineName == "spacing") + { + PathConstraintPositionTimeline timeline; + float timelineScale = 1; + if (timelineName == "spacing") + { + timeline = new PathConstraintSpacingTimeline(values.Count); + if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale; + } + else + { + timeline = new PathConstraintPositionTimeline(values.Count); + if (data.positionMode == PositionMode.Fixed) timelineScale = scale; + } + timeline.pathConstraintIndex = index; + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + timeline.SetFrame(frameIndex, (float)valueMap["time"], GetFloat(valueMap, timelineName, 0) * timelineScale); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintPositionTimeline.ENTRIES]); + } + else if (timelineName == "mix") + { + PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(values.Count); + timeline.pathConstraintIndex = index; + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + timeline.SetFrame(frameIndex, (float)valueMap["time"], GetFloat(valueMap, "rotateMix", 1), GetFloat(valueMap, "translateMix", 1)); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintMixTimeline.ENTRIES]); + } + } + } + } + + // Deform timelines. + if (map.ContainsKey("deform")) + { + foreach (KeyValuePair deformMap in (Dictionary)map["deform"]) + { + Skin skin = skeletonData.FindSkin(deformMap.Key); + foreach (KeyValuePair slotMap in (Dictionary)deformMap.Value) + { + int slotIndex = skeletonData.FindSlotIndex(slotMap.Key); + if (slotIndex == -1) throw new Exception("Slot not found: " + slotMap.Key); + foreach (KeyValuePair timelineMap in (Dictionary)slotMap.Value) + { + var values = (List)timelineMap.Value; + VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, timelineMap.Key); + if (attachment == null) throw new Exception("Deform attachment not found: " + timelineMap.Key); + bool weighted = attachment.bones != null; + float[] vertices = attachment.vertices; + int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length; + + var timeline = new DeformTimeline(values.Count); + timeline.slotIndex = slotIndex; + timeline.attachment = attachment; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float[] deform; + if (!valueMap.ContainsKey("vertices")) + { + deform = weighted ? new float[deformLength] : vertices; + } + else + { + deform = new float[deformLength]; + int start = GetInt(valueMap, "offset", 0); + float[] verticesValue = GetFloatArray(valueMap, "vertices", 1); + Array.Copy(verticesValue, 0, deform, start, verticesValue.Length); + if (scale != 1) + { + for (int i = start, n = i + verticesValue.Length; i < n; i++) + deform[i] *= scale; + } + + if (!weighted) + { + for (int i = 0; i < deformLength; i++) + deform[i] += vertices[i]; + } + } + + timeline.SetFrame(frameIndex, (float)valueMap["time"], deform); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); + } + } + } + } + + // Draw order timeline. + if (map.ContainsKey("drawOrder") || map.ContainsKey("draworder")) + { + var values = (List)map[map.ContainsKey("drawOrder") ? "drawOrder" : "draworder"]; + var timeline = new DrawOrderTimeline(values.Count); + int slotCount = skeletonData.slots.Count; + int frameIndex = 0; + foreach (Dictionary drawOrderMap in values) + { + int[] drawOrder = null; + if (drawOrderMap.ContainsKey("offsets")) + { + drawOrder = new int[slotCount]; + for (int i = slotCount - 1; i >= 0; i--) + drawOrder[i] = -1; + var offsets = (List)drawOrderMap["offsets"]; + int[] unchanged = new int[slotCount - offsets.Count]; + int originalIndex = 0, unchangedIndex = 0; + foreach (Dictionary offsetMap in offsets) + { + int slotIndex = skeletonData.FindSlotIndex((String)offsetMap["slot"]); + if (slotIndex == -1) throw new Exception("Slot not found: " + offsetMap["slot"]); + // Collect unchanged items. + while (originalIndex != slotIndex) + unchanged[unchangedIndex++] = originalIndex++; + // Set changed items. + int index = originalIndex + (int)(float)offsetMap["offset"]; + drawOrder[index] = originalIndex++; + } + // Collect remaining unchanged items. + while (originalIndex < slotCount) + unchanged[unchangedIndex++] = originalIndex++; + // Fill in unchanged items. + for (int i = slotCount - 1; i >= 0; i--) + if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex]; + } + timeline.SetFrame(frameIndex++, (float)drawOrderMap["time"], drawOrder); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); + } + + // Event timeline. + if (map.ContainsKey("events")) + { + var eventsMap = (List)map["events"]; + var timeline = new EventTimeline(eventsMap.Count); + int frameIndex = 0; + foreach (Dictionary eventMap in eventsMap) + { + EventData eventData = skeletonData.FindEvent((String)eventMap["name"]); + if (eventData == null) throw new Exception("Event not found: " + eventMap["name"]); + var e = new Event((float)eventMap["time"], eventData); + e.Int = GetInt(eventMap, "int", eventData.Int); + e.Float = GetFloat(eventMap, "float", eventData.Float); + e.String = GetString(eventMap, "string", eventData.String); + timeline.SetFrame(frameIndex++, e); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); + } + + timelines.TrimExcess(); + skeletonData.animations.Add(new Animation(name, timelines, duration)); + } + + static void ReadCurve(Dictionary valueMap, CurveTimeline timeline, int frameIndex) + { + if (!valueMap.ContainsKey("curve")) + return; + Object curveObject = valueMap["curve"]; + if (curveObject.Equals("stepped")) + timeline.SetStepped(frameIndex); + else + { + var curve = curveObject as List; + if (curve != null) + timeline.SetCurve(frameIndex, (float)curve[0], (float)curve[1], (float)curve[2], (float)curve[3]); + } + } + + internal class LinkedMesh + { + internal String parent, skin; + internal int slotIndex; + internal MeshAttachment mesh; + + public LinkedMesh(MeshAttachment mesh, String skin, int slotIndex, String parent) + { + this.mesh = mesh; + this.skin = skin; + this.slotIndex = slotIndex; + this.parent = parent; + } + } + + static float[] GetFloatArray(Dictionary map, String name, float scale) + { + var list = (List)map[name]; + var values = new float[list.Count]; + if (scale == 1) + { + for (int i = 0, n = list.Count; i < n; i++) + values[i] = (float)list[i]; + } + else + { + for (int i = 0, n = list.Count; i < n; i++) + values[i] = (float)list[i] * scale; + } + return values; + } + + static int[] GetIntArray(Dictionary map, String name) + { + var list = (List)map[name]; + var values = new int[list.Count]; + for (int i = 0, n = list.Count; i < n; i++) + values[i] = (int)(float)list[i]; + return values; + } + + static float GetFloat(Dictionary map, String name, float defaultValue) + { + if (!map.ContainsKey(name)) + return defaultValue; + return (float)map[name]; + } + + static int GetInt(Dictionary map, String name, int defaultValue) + { + if (!map.ContainsKey(name)) + return defaultValue; + return (int)(float)map[name]; + } + + static bool GetBoolean(Dictionary map, String name, bool defaultValue) + { + if (!map.ContainsKey(name)) + return defaultValue; + return (bool)map[name]; + } + + static String GetString(Dictionary map, String name, String defaultValue) + { + if (!map.ContainsKey(name)) + return defaultValue; + return (String)map[name]; + } + + static float ToColor(String hexString, int colorIndex) + { + if (hexString.Length != 8) + throw new ArgumentException("Color hexidecimal length must be 8, recieved: " + hexString, "hexString"); + return Convert.ToInt32(hexString.Substring(colorIndex * 2, 2), 16) / (float)255; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Skin.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Skin.cs index 2dd8b1a..36ac5f7 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Skin.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Skin.cs @@ -31,84 +31,100 @@ using System; using System.Collections.Generic; -namespace Spine3_4_02 { - /// Stores attachments by slot index and attachment name. - public class Skin { - internal String name; - private Dictionary attachments = - new Dictionary(AttachmentKeyTupleComparer.Instance); +namespace Spine3_4_02 +{ + /// Stores attachments by slot index and attachment name. + public class Skin + { + internal String name; + private Dictionary attachments = + new Dictionary(AttachmentKeyTupleComparer.Instance); - public String Name { get { return name; } } - public Dictionary Attachments { get { return attachments; } } + public String Name { get { return name; } } + public Dictionary Attachments { get { return attachments; } } - public Skin (String name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; - } + public Skin(String name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + } - public void AddAttachment (int slotIndex, String name, Attachment attachment) { - if (attachment == null) throw new ArgumentNullException("attachment", "attachment cannot be null."); - attachments[new AttachmentKeyTuple(slotIndex, name)] = attachment; - } + public void AddAttachment(int slotIndex, String name, Attachment attachment) + { + if (attachment == null) throw new ArgumentNullException("attachment", "attachment cannot be null."); + attachments[new AttachmentKeyTuple(slotIndex, name)] = attachment; + } - /// May be null. - public Attachment GetAttachment (int slotIndex, String name) { - Attachment attachment; - attachments.TryGetValue(new AttachmentKeyTuple(slotIndex, name), out attachment); - return attachment; - } + /// May be null. + public Attachment GetAttachment(int slotIndex, String name) + { + Attachment attachment; + attachments.TryGetValue(new AttachmentKeyTuple(slotIndex, name), out attachment); + return attachment; + } - public void FindNamesForSlot (int slotIndex, List names) { - if (names == null) throw new ArgumentNullException("names", "names cannot be null."); - foreach (AttachmentKeyTuple key in attachments.Keys) - if (key.slotIndex == slotIndex) names.Add(key.name); - } + public void FindNamesForSlot(int slotIndex, List names) + { + if (names == null) throw new ArgumentNullException("names", "names cannot be null."); + foreach (AttachmentKeyTuple key in attachments.Keys) + if (key.slotIndex == slotIndex) names.Add(key.name); + } - public void FindAttachmentsForSlot (int slotIndex, List attachments) { - if (attachments == null) throw new ArgumentNullException("attachments", "attachments cannot be null."); - foreach (KeyValuePair entry in this.attachments) - if (entry.Key.slotIndex == slotIndex) attachments.Add(entry.Value); - } + public void FindAttachmentsForSlot(int slotIndex, List attachments) + { + if (attachments == null) throw new ArgumentNullException("attachments", "attachments cannot be null."); + foreach (KeyValuePair entry in this.attachments) + if (entry.Key.slotIndex == slotIndex) attachments.Add(entry.Value); + } - override public String ToString () { - return name; - } + override public String ToString() + { + return name; + } - /// Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached. - internal void AttachAll (Skeleton skeleton, Skin oldSkin) { - foreach (KeyValuePair entry in oldSkin.attachments) { - int slotIndex = entry.Key.slotIndex; - Slot slot = skeleton.slots.Items[slotIndex]; - if (slot.attachment == entry.Value) { - Attachment attachment = GetAttachment(slotIndex, entry.Key.name); - if (attachment != null) slot.Attachment = attachment; - } - } - } + /// Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached. + internal void AttachAll(Skeleton skeleton, Skin oldSkin) + { + foreach (KeyValuePair entry in oldSkin.attachments) + { + int slotIndex = entry.Key.slotIndex; + Slot slot = skeleton.slots.Items[slotIndex]; + if (slot.attachment == entry.Value) + { + Attachment attachment = GetAttachment(slotIndex, entry.Key.name); + if (attachment != null) slot.Attachment = attachment; + } + } + } - public struct AttachmentKeyTuple { - public readonly int slotIndex; - public readonly string name; - internal readonly int nameHashCode; + public struct AttachmentKeyTuple + { + public readonly int slotIndex; + public readonly string name; + internal readonly int nameHashCode; - public AttachmentKeyTuple (int slotIndex, string name) { - this.slotIndex = slotIndex; - this.name = name; - nameHashCode = this.name.GetHashCode(); - } - } + public AttachmentKeyTuple(int slotIndex, string name) + { + this.slotIndex = slotIndex; + this.name = name; + nameHashCode = this.name.GetHashCode(); + } + } - // Avoids boxing in the dictionary. - class AttachmentKeyTupleComparer : IEqualityComparer { - internal static readonly AttachmentKeyTupleComparer Instance = new AttachmentKeyTupleComparer(); + // Avoids boxing in the dictionary. + class AttachmentKeyTupleComparer : IEqualityComparer + { + internal static readonly AttachmentKeyTupleComparer Instance = new AttachmentKeyTupleComparer(); - bool IEqualityComparer.Equals (AttachmentKeyTuple o1, AttachmentKeyTuple o2) { - return o1.slotIndex == o2.slotIndex && o1.nameHashCode == o2.nameHashCode && o1.name == o2.name; - } + bool IEqualityComparer.Equals(AttachmentKeyTuple o1, AttachmentKeyTuple o2) + { + return o1.slotIndex == o2.slotIndex && o1.nameHashCode == o2.nameHashCode && o1.name == o2.name; + } - int IEqualityComparer.GetHashCode (AttachmentKeyTuple o) { - return o.slotIndex; - } - } - } + int IEqualityComparer.GetHashCode(AttachmentKeyTuple o) + { + return o.slotIndex; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Slot.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Slot.cs index 7e2d789..d08008b 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Slot.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/Slot.cs @@ -30,64 +30,73 @@ using System; -namespace Spine3_4_02 { - public class Slot { - internal SlotData data; - internal Bone bone; - internal float r, g, b, a; - internal Attachment attachment; - internal float attachmentTime; - internal ExposedList attachmentVertices = new ExposedList(); +namespace Spine3_4_02 +{ + public class Slot + { + internal SlotData data; + internal Bone bone; + internal float r, g, b, a; + internal Attachment attachment; + internal float attachmentTime; + internal ExposedList attachmentVertices = new ExposedList(); - public SlotData Data { get { return data; } } - public Bone Bone { get { return bone; } } - public Skeleton Skeleton { get { return bone.skeleton; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } + public SlotData Data { get { return data; } } + public Bone Bone { get { return bone; } } + public Skeleton Skeleton { get { return bone.skeleton; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } - /// May be null. - public Attachment Attachment { - get { return attachment; } - set { - if (attachment == value) return; - attachment = value; - attachmentTime = bone.skeleton.time; - attachmentVertices.Clear(false); - } - } + /// May be null. + public Attachment Attachment + { + get { return attachment; } + set + { + if (attachment == value) return; + attachment = value; + attachmentTime = bone.skeleton.time; + attachmentVertices.Clear(false); + } + } - public float AttachmentTime { - get { return bone.skeleton.time - attachmentTime; } - set { attachmentTime = bone.skeleton.time - value; } - } + public float AttachmentTime + { + get { return bone.skeleton.time - attachmentTime; } + set { attachmentTime = bone.skeleton.time - value; } + } - public ExposedList AttachmentVertices { get { return attachmentVertices; } set { attachmentVertices = value; } } + public ExposedList AttachmentVertices { get { return attachmentVertices; } set { attachmentVertices = value; } } - public Slot (SlotData data, Bone bone) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); - this.data = data; - this.bone = bone; - SetToSetupPose(); - } + public Slot(SlotData data, Bone bone) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); + this.data = data; + this.bone = bone; + SetToSetupPose(); + } - public void SetToSetupPose () { - r = data.r; - g = data.g; - b = data.b; - a = data.a; - if (data.attachmentName == null) - Attachment = null; - else { - attachment = null; - Attachment = bone.skeleton.GetAttachment(data.index, data.attachmentName); - } - } + public void SetToSetupPose() + { + r = data.r; + g = data.g; + b = data.b; + a = data.a; + if (data.attachmentName == null) + Attachment = null; + else + { + attachment = null; + Attachment = bone.skeleton.GetAttachment(data.index, data.attachmentName); + } + } - override public String ToString () { - return data.name; - } - } + override public String ToString() + { + return data.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/SlotData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/SlotData.cs index b765209..91c1296 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/SlotData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/SlotData.cs @@ -30,37 +30,41 @@ using System; -namespace Spine3_4_02 { - public class SlotData { - internal int index; - internal String name; - internal BoneData boneData; - internal float r = 1, g = 1, b = 1, a = 1; - internal String attachmentName; - internal BlendMode blendMode; +namespace Spine3_4_02 +{ + public class SlotData + { + internal int index; + internal String name; + internal BoneData boneData; + internal float r = 1, g = 1, b = 1, a = 1; + internal String attachmentName; + internal BlendMode blendMode; - public int Index { get { return index; } } - public String Name { get { return name; } } - public BoneData BoneData { get { return boneData; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } - /// May be null. - public String AttachmentName { get { return attachmentName; } set { attachmentName = value; } } - public BlendMode BlendMode { get { return blendMode; } set { blendMode = value; } } + public int Index { get { return index; } } + public String Name { get { return name; } } + public BoneData BoneData { get { return boneData; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } + /// May be null. + public String AttachmentName { get { return attachmentName; } set { attachmentName = value; } } + public BlendMode BlendMode { get { return blendMode; } set { blendMode = value; } } - public SlotData (int index, String name, BoneData boneData) { - if (index < 0) throw new ArgumentException ("index must be >= 0.", "index"); - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - if (boneData == null) throw new ArgumentNullException("boneData", "boneData cannot be null."); - this.index = index; - this.name = name; - this.boneData = boneData; - } + public SlotData(int index, String name, BoneData boneData) + { + if (index < 0) throw new ArgumentException("index must be >= 0.", "index"); + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + if (boneData == null) throw new ArgumentNullException("boneData", "boneData cannot be null."); + this.index = index; + this.name = name; + this.boneData = boneData; + } - override public String ToString () { - return name; - } - } + override public String ToString() + { + return name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/TransformConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/TransformConstraint.cs index 403b6d8..a833075 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/TransformConstraint.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/TransformConstraint.cs @@ -30,100 +30,111 @@ using System; -namespace Spine3_4_02 { - public class TransformConstraint : IUpdatable { - internal TransformConstraintData data; - internal ExposedList bones; - internal Bone target; - internal float rotateMix, translateMix, scaleMix, shearMix; +namespace Spine3_4_02 +{ + public class TransformConstraint : IUpdatable + { + internal TransformConstraintData data; + internal ExposedList bones; + internal Bone target; + internal float rotateMix, translateMix, scaleMix, shearMix; - public TransformConstraintData Data { get { return data; } } - public ExposedList Bones { get { return bones; } } - public Bone Target { get { return target; } set { target = value; } } - public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } - public float TranslateMix { get { return translateMix; } set { translateMix = value; } } - public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } } - public float ShearMix { get { return shearMix; } set { shearMix = value; } } + public TransformConstraintData Data { get { return data; } } + public ExposedList Bones { get { return bones; } } + public Bone Target { get { return target; } set { target = value; } } + public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } + public float TranslateMix { get { return translateMix; } set { translateMix = value; } } + public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } } + public float ShearMix { get { return shearMix; } set { shearMix = value; } } - public TransformConstraint (TransformConstraintData data, Skeleton skeleton) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.data = data; - rotateMix = data.rotateMix; - translateMix = data.translateMix; - scaleMix = data.scaleMix; - shearMix = data.shearMix; + public TransformConstraint(TransformConstraintData data, Skeleton skeleton) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + rotateMix = data.rotateMix; + translateMix = data.translateMix; + scaleMix = data.scaleMix; + shearMix = data.shearMix; - bones = new ExposedList(); - foreach (BoneData boneData in data.bones) - bones.Add (skeleton.FindBone (boneData.name)); - - target = skeleton.FindBone(data.target.name); - } + bones = new ExposedList(); + foreach (BoneData boneData in data.bones) + bones.Add(skeleton.FindBone(boneData.name)); - public void Apply () { - Update(); - } + target = skeleton.FindBone(data.target.name); + } - public void Update () { - float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; - Bone target = this.target; - float ta = target.a, tb = target.b, tc = target.c, td = target.d; - ExposedList bones = this.bones; - for (int i = 0, n = bones.Count; i < n; i++) { - Bone bone = bones.Items[i]; + public void Apply() + { + Update(); + } - if (rotateMix > 0) { - float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - float r = (float)Math.Atan2(tc, ta) - (float)Math.Atan2(c, a) + data.offsetRotation * MathUtils.degRad; - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) r += MathUtils.PI2; - r *= rotateMix; - float cos = MathUtils.Cos(r), sin = MathUtils.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; - } + public void Update() + { + float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; + Bone target = this.target; + float ta = target.a, tb = target.b, tc = target.c, td = target.d; + ExposedList bones = this.bones; + for (int i = 0, n = bones.Count; i < n; i++) + { + Bone bone = bones.Items[i]; - if (translateMix > 0) { - float tempx, tempy; - target.LocalToWorld(data.offsetX, data.offsetY, out tempx, out tempy); - bone.worldX += (tempx - bone.worldX) * translateMix; - bone.worldY += (tempy - bone.worldY) * translateMix; - } + if (rotateMix > 0) + { + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + float r = (float)Math.Atan2(tc, ta) - (float)Math.Atan2(c, a) + data.offsetRotation * MathUtils.degRad; + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) r += MathUtils.PI2; + r *= rotateMix; + float cos = MathUtils.Cos(r), sin = MathUtils.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 (scaleMix > 0) { - float bs = (float)Math.Sqrt(bone.a * bone.a + bone.c * bone.c); - float ts = (float)Math.Sqrt(ta * ta + tc * tc); - float s = bs > 0.00001f ? (bs + (ts - bs + data.offsetScaleX) * scaleMix) / bs : 0; - bone.a *= s; - bone.c *= s; - bs = (float)Math.Sqrt(bone.b * bone.b + bone.d * bone.d); - ts = (float)Math.Sqrt(tb * tb + td * td); - s = bs > 0.00001f ? (bs + (ts - bs + data.offsetScaleY) * scaleMix) / bs : 0; - bone.b *= s; - bone.d *= s; - } + if (translateMix > 0) + { + float tempx, tempy; + target.LocalToWorld(data.offsetX, data.offsetY, out tempx, out tempy); + bone.worldX += (tempx - bone.worldX) * translateMix; + bone.worldY += (tempy - bone.worldY) * translateMix; + } - if (shearMix > 0) { - float b = bone.b, d = bone.d; - float by = MathUtils.Atan2(d, b); - float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta) - (by - MathUtils.Atan2(bone.c, bone.a)); - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) r += MathUtils.PI2; - r = by + (r + data.offsetShearY * MathUtils.degRad) * shearMix; - float s = (float)Math.Sqrt(b * b + d * d); - bone.b = MathUtils.Cos(r) * s; - bone.d = MathUtils.Sin(r) * s; - } - } - } + if (scaleMix > 0) + { + float bs = (float)Math.Sqrt(bone.a * bone.a + bone.c * bone.c); + float ts = (float)Math.Sqrt(ta * ta + tc * tc); + float s = bs > 0.00001f ? (bs + (ts - bs + data.offsetScaleX) * scaleMix) / bs : 0; + bone.a *= s; + bone.c *= s; + bs = (float)Math.Sqrt(bone.b * bone.b + bone.d * bone.d); + ts = (float)Math.Sqrt(tb * tb + td * td); + s = bs > 0.00001f ? (bs + (ts - bs + data.offsetScaleY) * scaleMix) / bs : 0; + bone.b *= s; + bone.d *= s; + } - override public String ToString () { - return data.name; - } - } + if (shearMix > 0) + { + float b = bone.b, d = bone.d; + float by = MathUtils.Atan2(d, b); + float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta) - (by - MathUtils.Atan2(bone.c, bone.a)); + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) r += MathUtils.PI2; + r = by + (r + data.offsetShearY * MathUtils.degRad) * shearMix; + float s = (float)Math.Sqrt(b * b + d * d); + bone.b = MathUtils.Cos(r) * s; + bone.d = MathUtils.Sin(r) * s; + } + } + } + + override public String ToString() + { + return data.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/TransformConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/TransformConstraintData.cs index 14be06c..587e896 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/TransformConstraintData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/TransformConstraintData.cs @@ -30,36 +30,40 @@ using System; -namespace Spine3_4_02 { - public class TransformConstraintData { - internal String name; - internal ExposedList bones = new ExposedList(); - internal BoneData target; - internal float rotateMix, translateMix, scaleMix, shearMix; - internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY; +namespace Spine3_4_02 +{ + public class TransformConstraintData + { + internal String name; + internal ExposedList bones = new ExposedList(); + internal BoneData target; + internal float rotateMix, translateMix, scaleMix, shearMix; + internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY; - public String Name { get { return name; } } - public ExposedList Bones { get { return bones; } } - public BoneData Target { get { return target; } set { target = value; } } - public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } - public float TranslateMix { get { return translateMix; } set { translateMix = value; } } - public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } } - public float ShearMix { get { return shearMix; } set { shearMix = value; } } + public String Name { get { return name; } } + public ExposedList Bones { get { return bones; } } + public BoneData Target { get { return target; } set { target = value; } } + public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } + public float TranslateMix { get { return translateMix; } set { translateMix = value; } } + public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } } + public float ShearMix { get { return shearMix; } set { shearMix = value; } } - public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } - public float OffsetX { get { return offsetX; } set { offsetX = value; } } - public float OffsetY { get { return offsetY; } set { offsetY = value; } } - public float OffsetScaleX { get { return offsetScaleX; } set { offsetScaleX = value; } } - public float OffsetScaleY { get { return offsetScaleY; } set { offsetScaleY = value; } } - public float OffsetShearY { get { return offsetShearY; } set { offsetShearY = value; } } + public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } + public float OffsetX { get { return offsetX; } set { offsetX = value; } } + public float OffsetY { get { return offsetY; } set { offsetY = value; } } + public float OffsetScaleX { get { return offsetScaleX; } set { offsetScaleX = value; } } + public float OffsetScaleY { get { return offsetScaleY; } set { offsetScaleY = value; } } + public float OffsetShearY { get { return offsetShearY; } set { offsetShearY = value; } } - public TransformConstraintData (String name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; - } + public TransformConstraintData(String name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + } - override public String ToString () { - return name; - } - } + override public String ToString() + { + return name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/XnaLoader/MeshBatcher.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/XnaLoader/MeshBatcher.cs index 529724f..548cd44 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/XnaLoader/MeshBatcher.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/XnaLoader/MeshBatcher.cs @@ -31,136 +31,146 @@ using System; using System.Collections.Generic; using Microsoft.Xna.Framework.Graphics; -using Microsoft.Xna.Framework; -namespace Spine3_4_02 { - // #region License - // /* - // Microsoft Public License (Ms-PL) - // MonoGame - Copyright � 2009 The MonoGame Team - // - // All rights reserved. - // - // This license governs use of the accompanying software. If you use the software, you accept this license. If you do not - // accept the license, do not use the software. - // - // 1. Definitions - // The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under - // U.S. copyright law. - // - // A "contribution" is the original software, or any additions or changes to the software. - // A "contributor" is any person that distributes its contribution under this license. - // "Licensed patents" are a contributor's patent claims that read directly on its contribution. - // - // 2. Grant of Rights - // (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, - // each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. - // (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, - // each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. - // - // 3. Conditions and Limitations - // (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. - // (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, - // your patent license from such contributor to the software ends automatically. - // (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution - // notices that are present in the software. - // (D) If you distribute any portion of the software in source code form, you may do so only under this license by including - // a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object - // code form, you may only do so under a license that complies with this license. - // (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees - // or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent - // permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular - // purpose and non-infringement. - // */ - // #endregion License - // +namespace Spine3_4_02 +{ + // #region License + // /* + // Microsoft Public License (Ms-PL) + // MonoGame - Copyright � 2009 The MonoGame Team + // + // All rights reserved. + // + // This license governs use of the accompanying software. If you use the software, you accept this license. If you do not + // accept the license, do not use the software. + // + // 1. Definitions + // The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under + // U.S. copyright law. + // + // A "contribution" is the original software, or any additions or changes to the software. + // A "contributor" is any person that distributes its contribution under this license. + // "Licensed patents" are a contributor's patent claims that read directly on its contribution. + // + // 2. Grant of Rights + // (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, + // each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. + // (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, + // each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. + // + // 3. Conditions and Limitations + // (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. + // (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, + // your patent license from such contributor to the software ends automatically. + // (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution + // notices that are present in the software. + // (D) If you distribute any portion of the software in source code form, you may do so only under this license by including + // a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object + // code form, you may only do so under a license that complies with this license. + // (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees + // or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent + // permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular + // purpose and non-infringement. + // */ + // #endregion License + // - /// Draws batched meshes. - public class MeshBatcher { - private readonly List items; - private readonly Queue freeItems; - private VertexPositionColorTexture[] vertexArray = { }; - private short[] triangles = { }; + /// Draws batched meshes. + public class MeshBatcher + { + private readonly List items; + private readonly Queue freeItems; + private VertexPositionColorTexture[] vertexArray = { }; + private short[] triangles = { }; - public MeshBatcher () { - items = new List(256); - freeItems = new Queue(256); - EnsureCapacity(256, 512); - } + public MeshBatcher() + { + items = new List(256); + freeItems = new Queue(256); + EnsureCapacity(256, 512); + } - /// Returns a pooled MeshItem. - public MeshItem NextItem (int vertexCount, int triangleCount) { - MeshItem item = freeItems.Count > 0 ? freeItems.Dequeue() : new MeshItem(); - if (item.vertices.Length < vertexCount) item.vertices = new VertexPositionColorTexture[vertexCount]; - if (item.triangles.Length < triangleCount) item.triangles = new int[triangleCount]; - item.vertexCount = vertexCount; - item.triangleCount = triangleCount; - items.Add(item); - return item; - } + /// Returns a pooled MeshItem. + public MeshItem NextItem(int vertexCount, int triangleCount) + { + MeshItem item = freeItems.Count > 0 ? freeItems.Dequeue() : new MeshItem(); + if (item.vertices.Length < vertexCount) item.vertices = new VertexPositionColorTexture[vertexCount]; + if (item.triangles.Length < triangleCount) item.triangles = new int[triangleCount]; + item.vertexCount = vertexCount; + item.triangleCount = triangleCount; + items.Add(item); + return item; + } - private void EnsureCapacity (int vertexCount, int triangleCount) { - if (vertexArray.Length < vertexCount) vertexArray = new VertexPositionColorTexture[vertexCount]; - if (triangles.Length < triangleCount) triangles = new short[triangleCount]; - } + private void EnsureCapacity(int vertexCount, int triangleCount) + { + if (vertexArray.Length < vertexCount) vertexArray = new VertexPositionColorTexture[vertexCount]; + if (triangles.Length < triangleCount) triangles = new short[triangleCount]; + } - public void Draw (GraphicsDevice device) { - if (items.Count == 0) return; + public void Draw(GraphicsDevice device) + { + if (items.Count == 0) return; - int itemCount = items.Count; - int vertexCount = 0, triangleCount = 0; - for (int i = 0; i < itemCount; i++) { - MeshItem item = items[i]; - vertexCount += item.vertexCount; - triangleCount += item.triangleCount; - } - EnsureCapacity(vertexCount, triangleCount); + int itemCount = items.Count; + int vertexCount = 0, triangleCount = 0; + for (int i = 0; i < itemCount; i++) + { + MeshItem item = items[i]; + vertexCount += item.vertexCount; + triangleCount += item.triangleCount; + } + EnsureCapacity(vertexCount, triangleCount); - vertexCount = 0; - triangleCount = 0; - Texture2D lastTexture = null; - for (int i = 0; i < itemCount; i++) { - MeshItem item = items[i]; - int itemVertexCount = item.vertexCount; + vertexCount = 0; + triangleCount = 0; + Texture2D lastTexture = null; + for (int i = 0; i < itemCount; i++) + { + MeshItem item = items[i]; + int itemVertexCount = item.vertexCount; - if (item.texture != lastTexture || vertexCount + itemVertexCount > short.MaxValue) { - FlushVertexArray(device, vertexCount, triangleCount); - vertexCount = 0; - triangleCount = 0; - lastTexture = item.texture; - device.Textures[0] = lastTexture; - } + if (item.texture != lastTexture || vertexCount + itemVertexCount > short.MaxValue) + { + FlushVertexArray(device, vertexCount, triangleCount); + vertexCount = 0; + triangleCount = 0; + lastTexture = item.texture; + device.Textures[0] = lastTexture; + } - int[] itemTriangles = item.triangles; - int itemTriangleCount = item.triangleCount; - for (int ii = 0, t = triangleCount; ii < itemTriangleCount; ii++, t++) - triangles[t] = (short)(itemTriangles[ii] + vertexCount); - triangleCount += itemTriangleCount; + int[] itemTriangles = item.triangles; + int itemTriangleCount = item.triangleCount; + for (int ii = 0, t = triangleCount; ii < itemTriangleCount; ii++, t++) + triangles[t] = (short)(itemTriangles[ii] + vertexCount); + triangleCount += itemTriangleCount; - Array.Copy(item.vertices, 0, vertexArray, vertexCount, itemVertexCount); - vertexCount += itemVertexCount; + Array.Copy(item.vertices, 0, vertexArray, vertexCount, itemVertexCount); + vertexCount += itemVertexCount; - item.texture = null; - freeItems.Enqueue(item); - } - FlushVertexArray(device, vertexCount, triangleCount); - items.Clear(); - } + item.texture = null; + freeItems.Enqueue(item); + } + FlushVertexArray(device, vertexCount, triangleCount); + items.Clear(); + } - private void FlushVertexArray (GraphicsDevice device, int vertexCount, int triangleCount) { - if (vertexCount == 0) return; - device.DrawUserIndexedPrimitives( - PrimitiveType.TriangleList, - vertexArray, 0, vertexCount, - triangles, 0, triangleCount / 3, - VertexPositionColorTexture.VertexDeclaration); - } - } + private void FlushVertexArray(GraphicsDevice device, int vertexCount, int triangleCount) + { + if (vertexCount == 0) return; + device.DrawUserIndexedPrimitives( + PrimitiveType.TriangleList, + vertexArray, 0, vertexCount, + triangles, 0, triangleCount / 3, + VertexPositionColorTexture.VertexDeclaration); + } + } - public class MeshItem { - public Texture2D texture; - public int vertexCount, triangleCount; - public VertexPositionColorTexture[] vertices = { }; - public int[] triangles = { }; - } + public class MeshItem + { + public Texture2D texture; + public int vertexCount, triangleCount; + public VertexPositionColorTexture[] vertices = { }; + public int[] triangles = { }; + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/XnaLoader/RegionBatcher.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/XnaLoader/RegionBatcher.cs index 4b68ffc..a63354a 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/XnaLoader/RegionBatcher.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/XnaLoader/RegionBatcher.cs @@ -31,151 +31,163 @@ using System; using System.Collections.Generic; using Microsoft.Xna.Framework.Graphics; -using Microsoft.Xna.Framework; -namespace Spine3_4_02 { - // #region License - // /* - // Microsoft Public License (Ms-PL) - // MonoGame - Copyright � 2009 The MonoGame Team - // - // All rights reserved. - // - // This license governs use of the accompanying software. If you use the software, you accept this license. If you do not - // accept the license, do not use the software. - // - // 1. Definitions - // The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under - // U.S. copyright law. - // - // A "contribution" is the original software, or any additions or changes to the software. - // A "contributor" is any person that distributes its contribution under this license. - // "Licensed patents" are a contributor's patent claims that read directly on its contribution. - // - // 2. Grant of Rights - // (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, - // each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. - // (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, - // each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. - // - // 3. Conditions and Limitations - // (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. - // (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, - // your patent license from such contributor to the software ends automatically. - // (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution - // notices that are present in the software. - // (D) If you distribute any portion of the software in source code form, you may do so only under this license by including - // a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object - // code form, you may only do so under a license that complies with this license. - // (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees - // or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent - // permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular - // purpose and non-infringement. - // */ - // #endregion License - // +namespace Spine3_4_02 +{ + // #region License + // /* + // Microsoft Public License (Ms-PL) + // MonoGame - Copyright � 2009 The MonoGame Team + // + // All rights reserved. + // + // This license governs use of the accompanying software. If you use the software, you accept this license. If you do not + // accept the license, do not use the software. + // + // 1. Definitions + // The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under + // U.S. copyright law. + // + // A "contribution" is the original software, or any additions or changes to the software. + // A "contributor" is any person that distributes its contribution under this license. + // "Licensed patents" are a contributor's patent claims that read directly on its contribution. + // + // 2. Grant of Rights + // (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, + // each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. + // (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, + // each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. + // + // 3. Conditions and Limitations + // (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. + // (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, + // your patent license from such contributor to the software ends automatically. + // (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution + // notices that are present in the software. + // (D) If you distribute any portion of the software in source code form, you may do so only under this license by including + // a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object + // code form, you may only do so under a license that complies with this license. + // (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees + // or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent + // permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular + // purpose and non-infringement. + // */ + // #endregion License + // - /// Draws batched quads using indices. - public class RegionBatcher { - private const int maxBatchSize = short.MaxValue / 6; // 6 = 4 vertices unique and 2 shared, per quad - private readonly List items; - private readonly Queue freeItems; - private VertexPositionColorTexture[] vertexArray; - private short[] indices; + /// Draws batched quads using indices. + public class RegionBatcher + { + private const int maxBatchSize = short.MaxValue / 6; // 6 = 4 vertices unique and 2 shared, per quad + private readonly List items; + private readonly Queue freeItems; + private VertexPositionColorTexture[] vertexArray; + private short[] indices; - public RegionBatcher () { - items = new List(256); - freeItems = new Queue(256); - EnsureArrayCapacity(256); - } + public RegionBatcher() + { + items = new List(256); + freeItems = new Queue(256); + EnsureArrayCapacity(256); + } - /// Returns a pooled RegionItem. - public RegionItem NextItem () { - RegionItem item = freeItems.Count > 0 ? freeItems.Dequeue() : new RegionItem(); - items.Add(item); - return item; - } + /// Returns a pooled RegionItem. + public RegionItem NextItem() + { + RegionItem item = freeItems.Count > 0 ? freeItems.Dequeue() : new RegionItem(); + items.Add(item); + return item; + } - /// Resize and recreate the indices and vertex position color buffers. - private void EnsureArrayCapacity (int itemCount) { - if (indices != null && indices.Length >= 6 * itemCount) return; + /// Resize and recreate the indices and vertex position color buffers. + private void EnsureArrayCapacity(int itemCount) + { + if (indices != null && indices.Length >= 6 * itemCount) return; - short[] newIndices = new short[6 * itemCount]; - int start = 0; - if (indices != null) { - indices.CopyTo(newIndices, 0); - start = indices.Length / 6; - } - for (var i = start; i < itemCount; i++) { - /* TL TR + short[] newIndices = new short[6 * itemCount]; + int start = 0; + if (indices != null) + { + indices.CopyTo(newIndices, 0); + start = indices.Length / 6; + } + for (var i = start; i < itemCount; i++) + { + /* TL TR * 0----1 0,1,2,3 = index offsets for vertex indices * | | TL,TR,BL,BR are vertex references in RegionItem. * 2----3 * BL BR */ - newIndices[i * 6 + 0] = (short)(i * 4); - newIndices[i * 6 + 1] = (short)(i * 4 + 1); - newIndices[i * 6 + 2] = (short)(i * 4 + 2); - newIndices[i * 6 + 3] = (short)(i * 4 + 1); - newIndices[i * 6 + 4] = (short)(i * 4 + 3); - newIndices[i * 6 + 5] = (short)(i * 4 + 2); - } - indices = newIndices; + newIndices[i * 6 + 0] = (short)(i * 4); + newIndices[i * 6 + 1] = (short)(i * 4 + 1); + newIndices[i * 6 + 2] = (short)(i * 4 + 2); + newIndices[i * 6 + 3] = (short)(i * 4 + 1); + newIndices[i * 6 + 4] = (short)(i * 4 + 3); + newIndices[i * 6 + 5] = (short)(i * 4 + 2); + } + indices = newIndices; - vertexArray = new VertexPositionColorTexture[4 * itemCount]; - } + vertexArray = new VertexPositionColorTexture[4 * itemCount]; + } - public void Draw (GraphicsDevice device) { - if (items.Count == 0) return; + public void Draw(GraphicsDevice device) + { + if (items.Count == 0) return; - int itemIndex = 0; - int itemCount = items.Count; - while (itemCount > 0) { - int itemsToProcess = Math.Min(itemCount, maxBatchSize); - EnsureArrayCapacity(itemsToProcess); + int itemIndex = 0; + int itemCount = items.Count; + while (itemCount > 0) + { + int itemsToProcess = Math.Min(itemCount, maxBatchSize); + EnsureArrayCapacity(itemsToProcess); - var count = 0; - Texture2D texture = null; - for (int i = 0; i < itemsToProcess; i++, itemIndex++) { - RegionItem item = items[itemIndex]; - if (item.texture != texture) { - FlushVertexArray(device, count); - texture = item.texture; - count = 0; - device.Textures[0] = texture; - } + var count = 0; + Texture2D texture = null; + for (int i = 0; i < itemsToProcess; i++, itemIndex++) + { + RegionItem item = items[itemIndex]; + if (item.texture != texture) + { + FlushVertexArray(device, count); + texture = item.texture; + count = 0; + device.Textures[0] = texture; + } - vertexArray[count++] = item.vertexTL; - vertexArray[count++] = item.vertexTR; - vertexArray[count++] = item.vertexBL; - vertexArray[count++] = item.vertexBR; + vertexArray[count++] = item.vertexTL; + vertexArray[count++] = item.vertexTR; + vertexArray[count++] = item.vertexBL; + vertexArray[count++] = item.vertexBR; - item.texture = null; - freeItems.Enqueue(item); - } - FlushVertexArray(device, count); - itemCount -= itemsToProcess; - } - items.Clear(); - } + item.texture = null; + freeItems.Enqueue(item); + } + FlushVertexArray(device, count); + itemCount -= itemsToProcess; + } + items.Clear(); + } - /// Sends the triangle list to the graphics device. - /// Start index of vertices to draw. Not used except to compute the count of vertices to draw. - /// End index of vertices to draw. Not used except to compute the count of vertices to draw. - private void FlushVertexArray (GraphicsDevice device, int count) { - if (count == 0) return; - device.DrawUserIndexedPrimitives( - PrimitiveType.TriangleList, - vertexArray, 0, count, - indices, 0, (count / 4) * 2, - VertexPositionColorTexture.VertexDeclaration); - } - } + /// Sends the triangle list to the graphics device. + /// Start index of vertices to draw. Not used except to compute the count of vertices to draw. + /// End index of vertices to draw. Not used except to compute the count of vertices to draw. + private void FlushVertexArray(GraphicsDevice device, int count) + { + if (count == 0) return; + device.DrawUserIndexedPrimitives( + PrimitiveType.TriangleList, + vertexArray, 0, count, + indices, 0, (count / 4) * 2, + VertexPositionColorTexture.VertexDeclaration); + } + } - public class RegionItem { - public Texture2D texture; - public VertexPositionColorTexture vertexTL; - public VertexPositionColorTexture vertexTR; - public VertexPositionColorTexture vertexBL; - public VertexPositionColorTexture vertexBR; - } + public class RegionItem + { + public Texture2D texture; + public VertexPositionColorTexture vertexTL; + public VertexPositionColorTexture vertexTR; + public VertexPositionColorTexture vertexBL; + public VertexPositionColorTexture vertexBR; + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/XnaLoader/SkeletonMeshRenderer.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/XnaLoader/SkeletonMeshRenderer.cs index 20a2cbc..09c495e 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/XnaLoader/SkeletonMeshRenderer.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/XnaLoader/SkeletonMeshRenderer.cs @@ -28,168 +28,185 @@ * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; -using System.Collections.Generic; -using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; -namespace Spine3_4_02 { - /// Draws region and mesh attachments. - public class SkeletonMeshRenderer { - private const int TL = 0; - private const int TR = 1; - private const int BL = 2; - private const int BR = 3; - - GraphicsDevice device; - MeshBatcher batcher; - RasterizerState rasterizerState; - float[] vertices = new float[8]; - int[] quadTriangles = { 0, 1, 2, 1, 3, 2 }; - BlendState defaultBlendState; - - BasicEffect effect; - public BasicEffect Effect { get { return effect; } set { effect = value; } } - - private bool premultipliedAlpha; - public bool PremultipliedAlpha { get { return premultipliedAlpha; } set { premultipliedAlpha = value; } } - - public SkeletonMeshRenderer (GraphicsDevice device) { - this.device = device; - - batcher = new MeshBatcher(); - - effect = new BasicEffect(device); - effect.World = Matrix.Identity; - effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); - effect.TextureEnabled = true; - effect.VertexColorEnabled = true; - - rasterizerState = new RasterizerState(); - rasterizerState.CullMode = CullMode.None; - - Bone.yDown = true; - } - - public void Begin () { - defaultBlendState = premultipliedAlpha ? BlendState.AlphaBlend : BlendState.NonPremultiplied; - - device.RasterizerState = rasterizerState; - device.BlendState = defaultBlendState; - - effect.Projection = Matrix.CreateOrthographicOffCenter(0, device.Viewport.Width, device.Viewport.Height, 0, 1, 0); - } - - public void End () { - foreach (EffectPass pass in effect.CurrentTechnique.Passes) { - pass.Apply(); - batcher.Draw(device); - } - } - - public void Draw (Skeleton skeleton) { - float[] vertices = this.vertices; - var drawOrder = skeleton.DrawOrder; - var drawOrderItems = skeleton.DrawOrder.Items; - float skeletonR = skeleton.R, skeletonG = skeleton.G, skeletonB = skeleton.B, skeletonA = skeleton.A; - for (int i = 0, n = drawOrder.Count; i < n; i++) { - Slot slot = drawOrderItems[i]; - Attachment attachment = slot.Attachment; - if (attachment is RegionAttachment) { - RegionAttachment regionAttachment = (RegionAttachment)attachment; - BlendState blend = slot.Data.BlendMode == BlendMode.additive ? BlendState.Additive : defaultBlendState; - if (device.BlendState != blend) { - End(); - device.BlendState = blend; - } - - MeshItem item = batcher.NextItem(4, 6); - item.triangles = quadTriangles; - VertexPositionColorTexture[] itemVertices = item.vertices; - - AtlasRegion region = (AtlasRegion)regionAttachment.RendererObject; - item.texture = (Texture2D)region.page.rendererObject; - - Color color; - float a = skeletonA * slot.A * regionAttachment.A; - if (premultipliedAlpha) { - color = new Color( - skeletonR * slot.R * regionAttachment.R * a, - skeletonG * slot.G * regionAttachment.G * a, - skeletonB * slot.B * regionAttachment.B * a, a); - } else { - color = new Color( - skeletonR * slot.R * regionAttachment.R, - skeletonG * slot.G * regionAttachment.G, - skeletonB * slot.B * regionAttachment.B, a); - } - itemVertices[TL].Color = color; - itemVertices[BL].Color = color; - itemVertices[BR].Color = color; - itemVertices[TR].Color = color; - - regionAttachment.ComputeWorldVertices(slot.Bone, vertices); - itemVertices[TL].Position.X = vertices[RegionAttachment.X1]; - itemVertices[TL].Position.Y = vertices[RegionAttachment.Y1]; - itemVertices[TL].Position.Z = 0; - itemVertices[BL].Position.X = vertices[RegionAttachment.X2]; - itemVertices[BL].Position.Y = vertices[RegionAttachment.Y2]; - itemVertices[BL].Position.Z = 0; - itemVertices[BR].Position.X = vertices[RegionAttachment.X3]; - itemVertices[BR].Position.Y = vertices[RegionAttachment.Y3]; - itemVertices[BR].Position.Z = 0; - itemVertices[TR].Position.X = vertices[RegionAttachment.X4]; - itemVertices[TR].Position.Y = vertices[RegionAttachment.Y4]; - itemVertices[TR].Position.Z = 0; - - float[] uvs = regionAttachment.UVs; - itemVertices[TL].TextureCoordinate.X = uvs[RegionAttachment.X1]; - itemVertices[TL].TextureCoordinate.Y = uvs[RegionAttachment.Y1]; - itemVertices[BL].TextureCoordinate.X = uvs[RegionAttachment.X2]; - itemVertices[BL].TextureCoordinate.Y = uvs[RegionAttachment.Y2]; - itemVertices[BR].TextureCoordinate.X = uvs[RegionAttachment.X3]; - itemVertices[BR].TextureCoordinate.Y = uvs[RegionAttachment.Y3]; - itemVertices[TR].TextureCoordinate.X = uvs[RegionAttachment.X4]; - itemVertices[TR].TextureCoordinate.Y = uvs[RegionAttachment.Y4]; - } else if (attachment is MeshAttachment) { - MeshAttachment mesh = (MeshAttachment)attachment; - int vertexCount = mesh.WorldVerticesLength; - if (vertices.Length < vertexCount) vertices = new float[vertexCount]; - mesh.ComputeWorldVertices(slot, vertices); - - int[] triangles = mesh.Triangles; - MeshItem item = batcher.NextItem(vertexCount, triangles.Length); - item.triangles = triangles; - - AtlasRegion region = (AtlasRegion)mesh.RendererObject; - item.texture = (Texture2D)region.page.rendererObject; - - Color color; - float a = skeletonA * slot.A * mesh.A; - if (premultipliedAlpha) { - color = new Color( - skeletonR * slot.R * mesh.R * a, - skeletonG * slot.G * mesh.G * a, - skeletonB * slot.B * mesh.B * a, a); - } else { - color = new Color( - skeletonR * slot.R * mesh.R, - skeletonG * slot.G * mesh.G, - skeletonB * slot.B * mesh.B, a); - } - - float[] uvs = mesh.UVs; - VertexPositionColorTexture[] itemVertices = item.vertices; - for (int ii = 0, v = 0; v < vertexCount; ii++, v += 2) { - itemVertices[ii].Color = color; - itemVertices[ii].Position.X = vertices[v]; - itemVertices[ii].Position.Y = vertices[v + 1]; - itemVertices[ii].Position.Z = 0; - itemVertices[ii].TextureCoordinate.X = uvs[v]; - itemVertices[ii].TextureCoordinate.Y = uvs[v + 1]; - } - } - } - } - } +namespace Spine3_4_02 +{ + /// Draws region and mesh attachments. + public class SkeletonMeshRenderer + { + private const int TL = 0; + private const int TR = 1; + private const int BL = 2; + private const int BR = 3; + + GraphicsDevice device; + MeshBatcher batcher; + RasterizerState rasterizerState; + float[] vertices = new float[8]; + int[] quadTriangles = { 0, 1, 2, 1, 3, 2 }; + BlendState defaultBlendState; + + BasicEffect effect; + public BasicEffect Effect { get { return effect; } set { effect = value; } } + + private bool premultipliedAlpha; + public bool PremultipliedAlpha { get { return premultipliedAlpha; } set { premultipliedAlpha = value; } } + + public SkeletonMeshRenderer(GraphicsDevice device) + { + this.device = device; + + batcher = new MeshBatcher(); + + effect = new BasicEffect(device); + effect.World = Matrix.Identity; + effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); + effect.TextureEnabled = true; + effect.VertexColorEnabled = true; + + rasterizerState = new RasterizerState(); + rasterizerState.CullMode = CullMode.None; + + Bone.yDown = true; + } + + public void Begin() + { + defaultBlendState = premultipliedAlpha ? BlendState.AlphaBlend : BlendState.NonPremultiplied; + + device.RasterizerState = rasterizerState; + device.BlendState = defaultBlendState; + + effect.Projection = Matrix.CreateOrthographicOffCenter(0, device.Viewport.Width, device.Viewport.Height, 0, 1, 0); + } + + public void End() + { + foreach (EffectPass pass in effect.CurrentTechnique.Passes) + { + pass.Apply(); + batcher.Draw(device); + } + } + + public void Draw(Skeleton skeleton) + { + float[] vertices = this.vertices; + var drawOrder = skeleton.DrawOrder; + var drawOrderItems = skeleton.DrawOrder.Items; + float skeletonR = skeleton.R, skeletonG = skeleton.G, skeletonB = skeleton.B, skeletonA = skeleton.A; + for (int i = 0, n = drawOrder.Count; i < n; i++) + { + Slot slot = drawOrderItems[i]; + Attachment attachment = slot.Attachment; + if (attachment is RegionAttachment) + { + RegionAttachment regionAttachment = (RegionAttachment)attachment; + BlendState blend = slot.Data.BlendMode == BlendMode.additive ? BlendState.Additive : defaultBlendState; + if (device.BlendState != blend) + { + End(); + device.BlendState = blend; + } + + MeshItem item = batcher.NextItem(4, 6); + item.triangles = quadTriangles; + VertexPositionColorTexture[] itemVertices = item.vertices; + + AtlasRegion region = (AtlasRegion)regionAttachment.RendererObject; + item.texture = (Texture2D)region.page.rendererObject; + + Color color; + float a = skeletonA * slot.A * regionAttachment.A; + if (premultipliedAlpha) + { + color = new Color( + skeletonR * slot.R * regionAttachment.R * a, + skeletonG * slot.G * regionAttachment.G * a, + skeletonB * slot.B * regionAttachment.B * a, a); + } + else + { + color = new Color( + skeletonR * slot.R * regionAttachment.R, + skeletonG * slot.G * regionAttachment.G, + skeletonB * slot.B * regionAttachment.B, a); + } + itemVertices[TL].Color = color; + itemVertices[BL].Color = color; + itemVertices[BR].Color = color; + itemVertices[TR].Color = color; + + regionAttachment.ComputeWorldVertices(slot.Bone, vertices); + itemVertices[TL].Position.X = vertices[RegionAttachment.X1]; + itemVertices[TL].Position.Y = vertices[RegionAttachment.Y1]; + itemVertices[TL].Position.Z = 0; + itemVertices[BL].Position.X = vertices[RegionAttachment.X2]; + itemVertices[BL].Position.Y = vertices[RegionAttachment.Y2]; + itemVertices[BL].Position.Z = 0; + itemVertices[BR].Position.X = vertices[RegionAttachment.X3]; + itemVertices[BR].Position.Y = vertices[RegionAttachment.Y3]; + itemVertices[BR].Position.Z = 0; + itemVertices[TR].Position.X = vertices[RegionAttachment.X4]; + itemVertices[TR].Position.Y = vertices[RegionAttachment.Y4]; + itemVertices[TR].Position.Z = 0; + + float[] uvs = regionAttachment.UVs; + itemVertices[TL].TextureCoordinate.X = uvs[RegionAttachment.X1]; + itemVertices[TL].TextureCoordinate.Y = uvs[RegionAttachment.Y1]; + itemVertices[BL].TextureCoordinate.X = uvs[RegionAttachment.X2]; + itemVertices[BL].TextureCoordinate.Y = uvs[RegionAttachment.Y2]; + itemVertices[BR].TextureCoordinate.X = uvs[RegionAttachment.X3]; + itemVertices[BR].TextureCoordinate.Y = uvs[RegionAttachment.Y3]; + itemVertices[TR].TextureCoordinate.X = uvs[RegionAttachment.X4]; + itemVertices[TR].TextureCoordinate.Y = uvs[RegionAttachment.Y4]; + } + else if (attachment is MeshAttachment) + { + MeshAttachment mesh = (MeshAttachment)attachment; + int vertexCount = mesh.WorldVerticesLength; + if (vertices.Length < vertexCount) vertices = new float[vertexCount]; + mesh.ComputeWorldVertices(slot, vertices); + + int[] triangles = mesh.Triangles; + MeshItem item = batcher.NextItem(vertexCount, triangles.Length); + item.triangles = triangles; + + AtlasRegion region = (AtlasRegion)mesh.RendererObject; + item.texture = (Texture2D)region.page.rendererObject; + + Color color; + float a = skeletonA * slot.A * mesh.A; + if (premultipliedAlpha) + { + color = new Color( + skeletonR * slot.R * mesh.R * a, + skeletonG * slot.G * mesh.G * a, + skeletonB * slot.B * mesh.B * a, a); + } + else + { + color = new Color( + skeletonR * slot.R * mesh.R, + skeletonG * slot.G * mesh.G, + skeletonB * slot.B * mesh.B, a); + } + + float[] uvs = mesh.UVs; + VertexPositionColorTexture[] itemVertices = item.vertices; + for (int ii = 0, v = 0; v < vertexCount; ii++, v += 2) + { + itemVertices[ii].Color = color; + itemVertices[ii].Position.X = vertices[v]; + itemVertices[ii].Position.Y = vertices[v + 1]; + itemVertices[ii].Position.Z = 0; + itemVertices[ii].TextureCoordinate.X = uvs[v]; + itemVertices[ii].TextureCoordinate.Y = uvs[v + 1]; + } + } + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/XnaLoader/SkeletonRegionRenderer.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/XnaLoader/SkeletonRegionRenderer.cs index fde132e..54c1fbd 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/XnaLoader/SkeletonRegionRenderer.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/XnaLoader/SkeletonRegionRenderer.cs @@ -28,67 +28,74 @@ * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; -using System.Collections.Generic; -using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; -namespace Spine3_4_02 { - /// Draws region attachments. - public class SkeletonRegionRenderer { - GraphicsDevice device; - RegionBatcher batcher; - RasterizerState rasterizerState; - float[] vertices = new float[8]; - BlendState defaultBlendState; - - BasicEffect effect; - public BasicEffect Effect { get { return effect; } set { effect = value; } } - - private bool premultipliedAlpha; - public bool PremultipliedAlpha { get { return premultipliedAlpha; } set { premultipliedAlpha = value; } } - - public SkeletonRegionRenderer (GraphicsDevice device) { - this.device = device; - - batcher = new RegionBatcher(); - - effect = new BasicEffect(device); - effect.World = Matrix.Identity; - effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); - effect.TextureEnabled = true; - effect.VertexColorEnabled = true; - - rasterizerState = new RasterizerState(); - rasterizerState.CullMode = CullMode.None; - - Bone.yDown = true; - } - - public void Begin () { - defaultBlendState = premultipliedAlpha ? BlendState.AlphaBlend : BlendState.NonPremultiplied; - - device.RasterizerState = rasterizerState; - device.BlendState = defaultBlendState; - - effect.Projection = Matrix.CreateOrthographicOffCenter(0, device.Viewport.Width, device.Viewport.Height, 0, 1, 0); - } - - public void End () { - foreach (EffectPass pass in effect.CurrentTechnique.Passes) { - pass.Apply(); - batcher.Draw(device); - } - } - - public void Draw (Skeleton skeleton) { - var drawOrder = skeleton.DrawOrder; - var drawOrderItems = skeleton.DrawOrder.Items; - float skeletonR = skeleton.R, skeletonG = skeleton.G, skeletonB = skeleton.B, skeletonA = skeleton.A; - for (int i = 0, n = drawOrder.Count; i < n; i++) { - Slot slot = drawOrderItems[i]; - RegionAttachment regionAttachment = slot.Attachment as RegionAttachment; - if (regionAttachment != null) { +namespace Spine3_4_02 +{ + /// Draws region attachments. + public class SkeletonRegionRenderer + { + GraphicsDevice device; + RegionBatcher batcher; + RasterizerState rasterizerState; + float[] vertices = new float[8]; + BlendState defaultBlendState; + + BasicEffect effect; + public BasicEffect Effect { get { return effect; } set { effect = value; } } + + private bool premultipliedAlpha; + public bool PremultipliedAlpha { get { return premultipliedAlpha; } set { premultipliedAlpha = value; } } + + public SkeletonRegionRenderer(GraphicsDevice device) + { + this.device = device; + + batcher = new RegionBatcher(); + + effect = new BasicEffect(device); + effect.World = Matrix.Identity; + effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); + effect.TextureEnabled = true; + effect.VertexColorEnabled = true; + + rasterizerState = new RasterizerState(); + rasterizerState.CullMode = CullMode.None; + + Bone.yDown = true; + } + + public void Begin() + { + defaultBlendState = premultipliedAlpha ? BlendState.AlphaBlend : BlendState.NonPremultiplied; + + device.RasterizerState = rasterizerState; + device.BlendState = defaultBlendState; + + effect.Projection = Matrix.CreateOrthographicOffCenter(0, device.Viewport.Width, device.Viewport.Height, 0, 1, 0); + } + + public void End() + { + foreach (EffectPass pass in effect.CurrentTechnique.Passes) + { + pass.Apply(); + batcher.Draw(device); + } + } + + public void Draw(Skeleton skeleton) + { + var drawOrder = skeleton.DrawOrder; + var drawOrderItems = skeleton.DrawOrder.Items; + float skeletonR = skeleton.R, skeletonG = skeleton.G, skeletonB = skeleton.B, skeletonA = skeleton.A; + for (int i = 0, n = drawOrder.Count; i < n; i++) + { + Slot slot = drawOrderItems[i]; + RegionAttachment regionAttachment = slot.Attachment as RegionAttachment; + if (regionAttachment != null) + { BlendState blendState = new BlendState(); Blend blendSrc; Blend blendDst; @@ -147,46 +154,46 @@ public void Draw (Skeleton skeleton) { RegionItem item = batcher.NextItem(); - AtlasRegion region = (AtlasRegion)regionAttachment.RendererObject; - item.texture = (Texture2D)region.page.rendererObject; - - Color color; - float a = skeletonA * slot.A; - if (premultipliedAlpha) - color = new Color(skeletonR * slot.R * a, skeletonG * slot.G * a, skeletonB * slot.B * a, a); - else - color = new Color(skeletonR * slot.R, skeletonG * slot.G, skeletonB * slot.B, a); - item.vertexTL.Color = color; - item.vertexBL.Color = color; - item.vertexBR.Color = color; - item.vertexTR.Color = color; - - float[] vertices = this.vertices; - regionAttachment.ComputeWorldVertices(slot.Bone, vertices); - item.vertexTL.Position.X = vertices[RegionAttachment.X1]; - item.vertexTL.Position.Y = vertices[RegionAttachment.Y1]; - item.vertexTL.Position.Z = 0; - item.vertexBL.Position.X = vertices[RegionAttachment.X2]; - item.vertexBL.Position.Y = vertices[RegionAttachment.Y2]; - item.vertexBL.Position.Z = 0; - item.vertexBR.Position.X = vertices[RegionAttachment.X3]; - item.vertexBR.Position.Y = vertices[RegionAttachment.Y3]; - item.vertexBR.Position.Z = 0; - item.vertexTR.Position.X = vertices[RegionAttachment.X4]; - item.vertexTR.Position.Y = vertices[RegionAttachment.Y4]; - item.vertexTR.Position.Z = 0; - - float[] uvs = regionAttachment.UVs; - item.vertexTL.TextureCoordinate.X = uvs[RegionAttachment.X1]; - item.vertexTL.TextureCoordinate.Y = uvs[RegionAttachment.Y1]; - item.vertexBL.TextureCoordinate.X = uvs[RegionAttachment.X2]; - item.vertexBL.TextureCoordinate.Y = uvs[RegionAttachment.Y2]; - item.vertexBR.TextureCoordinate.X = uvs[RegionAttachment.X3]; - item.vertexBR.TextureCoordinate.Y = uvs[RegionAttachment.Y3]; - item.vertexTR.TextureCoordinate.X = uvs[RegionAttachment.X4]; - item.vertexTR.TextureCoordinate.Y = uvs[RegionAttachment.Y4]; - } - } - } - } + AtlasRegion region = (AtlasRegion)regionAttachment.RendererObject; + item.texture = (Texture2D)region.page.rendererObject; + + Color color; + float a = skeletonA * slot.A; + if (premultipliedAlpha) + color = new Color(skeletonR * slot.R * a, skeletonG * slot.G * a, skeletonB * slot.B * a, a); + else + color = new Color(skeletonR * slot.R, skeletonG * slot.G, skeletonB * slot.B, a); + item.vertexTL.Color = color; + item.vertexBL.Color = color; + item.vertexBR.Color = color; + item.vertexTR.Color = color; + + float[] vertices = this.vertices; + regionAttachment.ComputeWorldVertices(slot.Bone, vertices); + item.vertexTL.Position.X = vertices[RegionAttachment.X1]; + item.vertexTL.Position.Y = vertices[RegionAttachment.Y1]; + item.vertexTL.Position.Z = 0; + item.vertexBL.Position.X = vertices[RegionAttachment.X2]; + item.vertexBL.Position.Y = vertices[RegionAttachment.Y2]; + item.vertexBL.Position.Z = 0; + item.vertexBR.Position.X = vertices[RegionAttachment.X3]; + item.vertexBR.Position.Y = vertices[RegionAttachment.Y3]; + item.vertexBR.Position.Z = 0; + item.vertexTR.Position.X = vertices[RegionAttachment.X4]; + item.vertexTR.Position.Y = vertices[RegionAttachment.Y4]; + item.vertexTR.Position.Z = 0; + + float[] uvs = regionAttachment.UVs; + item.vertexTL.TextureCoordinate.X = uvs[RegionAttachment.X1]; + item.vertexTL.TextureCoordinate.Y = uvs[RegionAttachment.Y1]; + item.vertexBL.TextureCoordinate.X = uvs[RegionAttachment.X2]; + item.vertexBL.TextureCoordinate.Y = uvs[RegionAttachment.Y2]; + item.vertexBR.TextureCoordinate.X = uvs[RegionAttachment.X3]; + item.vertexBR.TextureCoordinate.Y = uvs[RegionAttachment.Y3]; + item.vertexTR.TextureCoordinate.X = uvs[RegionAttachment.X4]; + item.vertexTR.TextureCoordinate.Y = uvs[RegionAttachment.Y4]; + } + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/XnaLoader/XnaTextureLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/XnaLoader/XnaTextureLoader.cs index b3d76b2..1342496 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/XnaLoader/XnaTextureLoader.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.4.02/XnaLoader/XnaTextureLoader.cs @@ -29,21 +29,23 @@ *****************************************************************************/ using System; -using System.IO; -using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; -namespace Spine3_4_02 { - public class XnaTextureLoader : TextureLoader { - GraphicsDevice device; +namespace Spine3_4_02 +{ + public class XnaTextureLoader : TextureLoader + { + GraphicsDevice device; - public XnaTextureLoader (GraphicsDevice device) { - this.device = device; - } + public XnaTextureLoader(GraphicsDevice device) + { + this.device = device; + } - public void Load (AtlasPage page, String path) { - Texture2D texture = Util.LoadTexture(device, path); - page.rendererObject = texture; + public void Load(AtlasPage page, String path) + { + Texture2D texture = Util.LoadTexture(device, path); + page.rendererObject = texture; if (page.width == 0 || page.height == 0) { page.width = texture.Width; @@ -51,8 +53,9 @@ public void Load (AtlasPage page, String path) { } } - public void Unload (Object texture) { - ((Texture2D)texture).Dispose(); - } - } + public void Unload(Object texture) + { + ((Texture2D)texture).Dispose(); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Animation.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Animation.cs index 04d9381..0fdeb05 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Animation.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Animation.cs @@ -29,1105 +29,1309 @@ *****************************************************************************/ using System; -using System.Collections.Generic; - -namespace Spine3_5_51 { - public class Animation { - internal ExposedList timelines; - internal float duration; - internal String name; - - public String Name { get { return name; } } - public ExposedList Timelines { get { return timelines; } set { timelines = value; } } - public float Duration { get { return duration; } set { duration = value; } } - - public Animation (String name, ExposedList timelines, float duration) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - if (timelines == null) throw new ArgumentNullException("timelines", "timelines cannot be null."); - this.name = name; - this.timelines = timelines; - this.duration = duration; - } - - /// Applies all the animation's timelines to the specified skeleton. - /// The skeleton to be posed. - /// The last time the animation was applied. - /// The point in time in the animation to apply to the skeleton. - /// If true, time wraps within the animation duration. - /// Any triggered events are added. May be null. - /// The percentage between this animation's pose and the current pose. - /// If true, the animation is mixed with the setup pose, else it is mixed with the current pose. Passing true when alpha is 1 is slightly more efficient. - /// True when mixing over time toward the setup or current pose, false when mixing toward the keyed pose. Irrelevant when alpha is 1. - /// - public void Apply (Skeleton skeleton, float lastTime, float time, bool loop, ExposedList events, float alpha, bool setupPose, bool mixingOut) { - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - - if (loop && duration != 0) { - time %= duration; - if (lastTime > 0) lastTime %= duration; - } - - ExposedList timelines = this.timelines; - for (int i = 0, n = timelines.Count; i < n; i++) - timelines.Items[i].Apply(skeleton, lastTime, time, events, alpha, setupPose, mixingOut); - } - - /// After the first and before the last entry. - internal static int BinarySearch (float[] values, float target, int step) { - int low = 0; - int high = values.Length / step - 2; - if (high == 0) return step; - int current = (int)((uint)high >> 1); - while (true) { - if (values[(current + 1) * step] <= target) - low = current + 1; - else - high = current; - if (low == high) return (low + 1) * step; - current = (int)((uint)(low + high) >> 1); - } - } - - /// After the first and before the last entry. - internal static int BinarySearch (float[] values, float target) { - int low = 0; - int high = values.Length - 2; - if (high == 0) return 1; - int current = (int)((uint)high >> 1); - while (true) { - if (values[(current + 1)] <= target) - low = current + 1; - else - high = current; - if (low == high) return (low + 1); - current = (int)((uint)(low + high) >> 1); - } - } - - internal static int LinearSearch (float[] values, float target, int step) { - for (int i = 0, last = values.Length - step; i <= last; i += step) - if (values[i] > target) return i; - return -1; - } - } - - public interface Timeline { - /// Sets the value(s) for the specified time. - /// Any triggered events are added. May be null. - /// True when the timeline is mixed with the setup pose, false when it is mixed with the current pose. Passing true when alpha is 1 is slightly more efficient. - /// True when mixing over time toward the setup or current pose, false when mixing toward the keyed pose. - /// Used for timelines with instant transitions, eg draw order, attachment visibility, scale sign. - void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, bool setupPose, bool mixingOut); - int PropertyId { get; } - } - - internal enum TimelineType { - Rotate = 0, Translate, Scale, Shear, // - Attachment, Color, Deform, // - Event, DrawOrder, // - IkConstraint, TransformConstraint, // - PathConstraintPosition, PathConstraintSpacing, PathConstraintMix - } - - /// Base class for frames that use an interpolation bezier curve. - abstract public class CurveTimeline : Timeline { - protected const float LINEAR = 0, STEPPED = 1, BEZIER = 2; - protected const int BEZIER_SIZE = 10 * 2 - 1; - - private float[] curves; // type, x, y, ... - public int FrameCount { get { return curves.Length / BEZIER_SIZE + 1; } } - - public CurveTimeline (int frameCount) { - if (frameCount <= 0) throw new ArgumentException("frameCount must be > 0: " + frameCount, "frameCount"); - curves = new float[(frameCount - 1) * BEZIER_SIZE]; - } - - abstract public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut); - - abstract public int PropertyId { get; } - - public void SetLinear (int frameIndex) { - curves[frameIndex * BEZIER_SIZE] = LINEAR; - } - - public void SetStepped (int frameIndex) { - curves[frameIndex * BEZIER_SIZE] = STEPPED; - } - - /// Sets the control handle positions for an interpolation bezier curve used to transition from this keyframe to the next. - /// cx1 and cx2 are from 0 to 1, representing the percent of time between the two keyframes. cy1 and cy2 are the percent of - /// the difference between the keyframe's values. - public void SetCurve (int frameIndex, float cx1, float cy1, float cx2, float cy2) { - float tmpx = (-cx1 * 2 + cx2) * 0.03f, tmpy = (-cy1 * 2 + cy2) * 0.03f; - float dddfx = ((cx1 - cx2) * 3 + 1) * 0.006f, dddfy = ((cy1 - cy2) * 3 + 1) * 0.006f; - float ddfx = tmpx * 2 + dddfx, ddfy = tmpy * 2 + dddfy; - float dfx = cx1 * 0.3f + tmpx + dddfx * 0.16666667f, dfy = cy1 * 0.3f + tmpy + dddfy * 0.16666667f; - - int i = frameIndex * BEZIER_SIZE; - float[] curves = this.curves; - curves[i++] = BEZIER; - - float x = dfx, y = dfy; - for (int n = i + BEZIER_SIZE - 1; i < n; i += 2) { - curves[i] = x; - curves[i + 1] = y; - dfx += ddfx; - dfy += ddfy; - ddfx += dddfx; - ddfy += dddfy; - x += dfx; - y += dfy; - } - } - - public float GetCurvePercent (int frameIndex, float percent) { - percent = MathUtils.Clamp (percent, 0, 1); - float[] curves = this.curves; - int i = frameIndex * BEZIER_SIZE; - float type = curves[i]; - if (type == LINEAR) return percent; - if (type == STEPPED) return 0; - i++; - float x = 0; - for (int start = i, n = i + BEZIER_SIZE - 1; i < n; i += 2) { - x = curves[i]; - if (x >= percent) { - float prevX, prevY; - if (i == start) { - prevX = 0; - prevY = 0; - } else { - prevX = curves[i - 2]; - prevY = curves[i - 1]; - } - return prevY + (curves[i + 1] - prevY) * (percent - prevX) / (x - prevX); - } - } - float y = curves[i - 1]; - return y + (1 - y) * (percent - x) / (1 - x); // Last point is 1,1. - } - public float GetCurveType (int frameIndex) { - return curves[frameIndex * BEZIER_SIZE]; - } - } - - public class RotateTimeline : CurveTimeline { - public const int ENTRIES = 2; - internal const int PREV_TIME = -2, PREV_ROTATION = -1; - internal const int ROTATION = 1; - - internal int boneIndex; - internal float[] frames; - - public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, angle, ... - - override public int PropertyId { - get { return ((int)TimelineType.Rotate << 24) + boneIndex; } - } - - public RotateTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount << 1]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, float degrees) { - frameIndex <<= 1; - frames[frameIndex] = time; - frames[frameIndex + ROTATION] = degrees; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut) { - Bone bone = skeleton.bones.Items[boneIndex]; - - float[] frames = this.frames; - if (time < frames[0]) { - if (setupPose) bone.rotation = bone.data.rotation; - return; - } - - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - if (setupPose) { - bone.rotation = bone.data.rotation + frames[frames.Length + PREV_ROTATION] * alpha; - } else { - float rr = bone.data.rotation + frames[frames.Length + PREV_ROTATION] - bone.rotation; - rr -= (16384 - (int)(16384.499999999996 - rr / 360)) * 360; // Wrap within -180 and 180. - bone.rotation += rr * alpha; - } - return; - } - - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - float prevRotation = frames[frame + PREV_ROTATION]; - float frameTime = frames[frame]; - float percent = GetCurvePercent((frame >> 1) - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - float r = frames[frame + ROTATION] - prevRotation; - r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; - r = prevRotation + r * percent; - if (setupPose) { - r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; - bone.rotation = bone.data.rotation + r * alpha; - } else { - r = bone.data.rotation + r - bone.rotation; - r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; - bone.rotation += r * alpha; - } - } - } - - public class TranslateTimeline : CurveTimeline { - public const int ENTRIES = 3; - protected const int PREV_TIME = -3, PREV_X = -2, PREV_Y = -1; - protected const int X = 1, Y = 2; - - internal int boneIndex; - internal float[] frames; - - public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, value, value, ... - - override public int PropertyId { - get { return ((int)TimelineType.Translate << 24) + boneIndex; } - } - - public TranslateTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, float x, float y) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + X] = x; - frames[frameIndex + Y] = y; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut) { - Bone bone = skeleton.bones.Items[boneIndex]; - - float[] frames = this.frames; - if (time < frames[0]) { - if (setupPose) { - bone.x = bone.data.x; - bone.y = bone.data.y; - } - return; - } - - float x, y; - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - x = frames[frames.Length + PREV_X]; - y = frames[frames.Length + PREV_Y]; - } else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - x = frames[frame + PREV_X]; - y = frames[frame + PREV_Y]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - x += (frames[frame + X] - x) * percent; - y += (frames[frame + Y] - y) * percent; - } - if (setupPose) { - bone.x = bone.data.x + x * alpha; - bone.y = bone.data.y + y * alpha; - } else { - bone.x += (bone.data.x + x - bone.x) * alpha; - bone.y += (bone.data.y + y - bone.y) * alpha; - } - } - } - - public class ScaleTimeline : TranslateTimeline { - override public int PropertyId { - get { return ((int)TimelineType.Scale << 24) + boneIndex; } - } - - public ScaleTimeline (int frameCount) - : base(frameCount) { - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut) { - Bone bone = skeleton.bones.Items[boneIndex]; - - float[] frames = this.frames; - if (time < frames[0]) { - if (setupPose) { - bone.scaleX = bone.data.scaleX; - bone.scaleY = bone.data.scaleY; - } - return; - } - - float x, y; - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - x = frames[frames.Length + PREV_X] * bone.data.scaleX; - y = frames[frames.Length + PREV_Y] * bone.data.scaleY; - } else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - x = frames[frame + PREV_X]; - y = frames[frame + PREV_Y]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - x = (x + (frames[frame + X] - x) * percent) * bone.data.scaleX; - y = (y + (frames[frame + Y] - y) * percent) * bone.data.scaleY; - } - if (alpha == 1) { - bone.scaleX = x; - bone.scaleY = y; - } else { - float bx, by; - if (setupPose) { - bx = bone.data.scaleX; - by = bone.data.scaleY; - } else { - bx = bone.scaleX; - by = bone.scaleY; - } - // Mixing out uses sign of setup or current pose, else use sign of key. - if (mixingOut) { - x = Math.Abs(x) * Math.Sign(bx); - y = Math.Abs(y) * Math.Sign(by); - } else { - bx = Math.Abs(bx) * Math.Sign(x); - by = Math.Abs(by) * Math.Sign(y); - } - bone.scaleX = bx + (x - bx) * alpha; - bone.scaleY = by + (y - by) * alpha; - } - } - } - - public class ShearTimeline : TranslateTimeline { - override public int PropertyId { - get { return ((int)TimelineType.Shear << 24) + boneIndex; } - } - - public ShearTimeline (int frameCount) - : base(frameCount) { - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut) { - Bone bone = skeleton.bones.Items[boneIndex]; - float[] frames = this.frames; - if (time < frames[0]) { - if (setupPose) { - bone.shearX = bone.data.shearX; - bone.shearY = bone.data.shearY; - } - return; - } - - float x, y; - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - x = frames[frames.Length + PREV_X]; - y = frames[frames.Length + PREV_Y]; - } else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - x = frames[frame + PREV_X]; - y = frames[frame + PREV_Y]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - x = x + (frames[frame + X] - x) * percent; - y = y + (frames[frame + Y] - y) * percent; - } - if (setupPose) { - bone.shearX = bone.data.shearX + x * alpha; - bone.shearY = bone.data.shearY + y * alpha; - } else { - bone.shearX += (bone.data.shearX + x - bone.shearX) * alpha; - bone.shearY += (bone.data.shearY + y - bone.shearY) * alpha; - } - } - } - - public class ColorTimeline : CurveTimeline { - public const int ENTRIES = 5; - protected const int PREV_TIME = -5, PREV_R = -4, PREV_G = -3, PREV_B = -2, PREV_A = -1; - protected const int R = 1, G = 2, B = 3, A = 4; - - internal int slotIndex; - internal float[] frames; - - public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, r, g, b, a, ... - - override public int PropertyId { - get { return ((int)TimelineType.Color << 24) + slotIndex; } - } - - public ColorTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, float r, float g, float b, float a) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + R] = r; - frames[frameIndex + G] = g; - frames[frameIndex + B] = b; - frames[frameIndex + A] = a; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut) { - Slot slot = skeleton.slots.Items[slotIndex]; - float[] frames = this.frames; - if (time < frames[0]) { - if (setupPose) { - var slotData = slot.data; - slot.r = slotData.r; - slot.g = slotData.g; - slot.b = slotData.b; - slot.a = slotData.a; - } - return; - } - - float r, g, b, a; - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - int i = frames.Length; - r = frames[i + PREV_R]; - g = frames[i + PREV_G]; - b = frames[i + PREV_B]; - a = frames[i + PREV_A]; - } else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - r = frames[frame + PREV_R]; - g = frames[frame + PREV_G]; - b = frames[frame + PREV_B]; - a = frames[frame + PREV_A]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - r += (frames[frame + R] - r) * percent; - g += (frames[frame + G] - g) * percent; - b += (frames[frame + B] - b) * percent; - a += (frames[frame + A] - a) * percent; - } - if (alpha == 1) { - slot.r = r; - slot.g = g; - slot.b = b; - slot.a = a; - } else { - float br, bg, bb, ba; - if (setupPose) { - br = slot.data.r; - bg = slot.data.g; - bb = slot.data.b; - ba = slot.data.a; - } else { - br = slot.r; - bg = slot.g; - bb = slot.b; - ba = slot.a; - } - slot.r = br + ((r - br) * alpha); - slot.g = bg + ((g - bg) * alpha); - slot.b = bb + ((b - bb) * alpha); - slot.a = ba + ((a - ba) * alpha); - } - } - } - - public class AttachmentTimeline : Timeline { - internal int slotIndex; - internal float[] frames; - private String[] attachmentNames; - - public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, ... - public String[] AttachmentNames { get { return attachmentNames; } set { attachmentNames = value; } } - public int FrameCount { get { return frames.Length; } } - - public int PropertyId { - get { return ((int)TimelineType.Attachment << 24) + slotIndex; } - } - - public AttachmentTimeline (int frameCount) { - frames = new float[frameCount]; - attachmentNames = new String[frameCount]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, String attachmentName) { - frames[frameIndex] = time; - attachmentNames[frameIndex] = attachmentName; - } - - public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut) { - string attachmentName; - Slot slot = skeleton.slots.Items[slotIndex]; - if (mixingOut && setupPose) { - attachmentName = slot.data.attachmentName; - slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); - return; - } - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - if (setupPose) { - attachmentName = slot.data.attachmentName; - slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); - } - return; - } - - int frameIndex; - if (time >= frames[frames.Length - 1]) // Time is after last frame. - frameIndex = frames.Length - 1; - else - frameIndex = Animation.BinarySearch(frames, time, 1) - 1; - - attachmentName = attachmentNames[frameIndex]; - slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); - } - } - - public class DeformTimeline : CurveTimeline { - internal int slotIndex; - internal float[] frames; - internal float[][] frameVertices; - internal VertexAttachment attachment; - - public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, ... - public float[][] Vertices { get { return frameVertices; } set { frameVertices = value; } } - public VertexAttachment Attachment { get { return attachment; } set { attachment = value; } } - - override public int PropertyId { - get { return ((int)TimelineType.Deform << 24) + slotIndex; } - } - - public DeformTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount]; - frameVertices = new float[frameCount][]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, float[] vertices) { - frames[frameIndex] = time; - frameVertices[frameIndex] = vertices; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut) { - Slot slot = skeleton.slots.Items[slotIndex]; - VertexAttachment slotAttachment = slot.attachment as VertexAttachment; - if (slotAttachment == null || !slotAttachment.ApplyDeform(attachment)) return; - - var verticesArray = slot.attachmentVertices; - float[] frames = this.frames; - if (time < frames[0]) { - if (setupPose) verticesArray.Clear(); - return; - } - - float[][] frameVertices = this.frameVertices; - int vertexCount = frameVertices[0].Length; - - if (verticesArray.Count != vertexCount) alpha = 1; // Don't mix from uninitialized slot vertices. - // verticesArray.SetSize(vertexCount) // Ensure size and preemptively set count. - if (verticesArray.Capacity < vertexCount) verticesArray.Capacity = vertexCount; - verticesArray.Count = vertexCount; - float[] vertices = verticesArray.Items; - - if (time >= frames[frames.Length - 1]) { // Time is after last frame. - float[] lastVertices = frameVertices[frames.Length - 1]; - if (alpha == 1) { - // Vertex positions or deform offsets, no alpha. - Array.Copy(lastVertices, 0, vertices, 0, vertexCount); - } else if (setupPose) { - VertexAttachment vertexAttachment = slotAttachment; - if (vertexAttachment.bones == null) { - // Unweighted vertex positions, with alpha. - float[] setupVertices = vertexAttachment.vertices; - for (int i = 0; i < vertexCount; i++) { - float setup = setupVertices[i]; - vertices[i] = setup + (lastVertices[i] - setup) * alpha; - } - } else { - // Weighted deform offsets, with alpha. - for (int i = 0; i < vertexCount; i++) - vertices[i] = lastVertices[i] * alpha; - } - } else { - // Vertex positions or deform offsets, with alpha. - for (int i = 0; i < vertexCount; i++) - vertices[i] += (lastVertices[i] - vertices[i]) * alpha; - } - return; - } - - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time); - float[] prevVertices = frameVertices[frame - 1]; - float[] nextVertices = frameVertices[frame]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame - 1, 1 - (time - frameTime) / (frames[frame - 1] - frameTime)); - - if (alpha == 1) { - // Vertex positions or deform offsets, no alpha. - for (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - vertices[i] = prev + (nextVertices[i] - prev) * percent; - } - } else if (setupPose) { - VertexAttachment vertexAttachment = (VertexAttachment)slotAttachment; - if (vertexAttachment.bones == null) { - // Unweighted vertex positions, with alpha. - var setupVertices = vertexAttachment.vertices; - for (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i], setup = setupVertices[i]; - vertices[i] = setup + (prev + (nextVertices[i] - prev) * percent - setup) * alpha; - } - } else { - // Weighted deform offsets, with alpha. - for (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - vertices[i] = (prev + (nextVertices[i] - prev) * percent) * alpha; - } - } - } else { - // Vertex positions or deform offsets, with alpha. - for (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - vertices[i] += (prev + (nextVertices[i] - prev) * percent - vertices[i]) * alpha; - } - } - } - } - - public class EventTimeline : Timeline { - internal float[] frames; - private Event[] events; - - public float[] Frames { get { return frames; } set { frames = value; } } // time, ... - public Event[] Events { get { return events; } set { events = value; } } - public int FrameCount { get { return frames.Length; } } - - public int PropertyId { - get { return ((int)TimelineType.Event << 24); } - } - - public EventTimeline (int frameCount) { - frames = new float[frameCount]; - events = new Event[frameCount]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, Event e) { - frames[frameIndex] = e.Time; - events[frameIndex] = e; - } - - /// Fires events for frames > lastTime and <= time. - public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut) { - if (firedEvents == null) return; - float[] frames = this.frames; - int frameCount = frames.Length; - - if (lastTime > time) { // Fire events after last time for looped animations. - Apply(skeleton, lastTime, int.MaxValue, firedEvents, alpha, setupPose, mixingOut); - lastTime = -1f; - } else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame. - return; - if (time < frames[0]) return; // Time is before first frame. - - int frame; - if (lastTime < frames[0]) - frame = 0; - else { - frame = Animation.BinarySearch(frames, lastTime); - float frameTime = frames[frame]; - while (frame > 0) { // Fire multiple events with the same frame. - if (frames[frame - 1] != frameTime) break; - frame--; - } - } - for (; frame < frameCount && time >= frames[frame]; frame++) - firedEvents.Add(events[frame]); - } - } - - public class DrawOrderTimeline : Timeline { - internal float[] frames; - private int[][] drawOrders; - - public float[] Frames { get { return frames; } set { frames = value; } } // time, ... - public int[][] DrawOrders { get { return drawOrders; } set { drawOrders = value; } } - public int FrameCount { get { return frames.Length; } } - - public int PropertyId { - get { return ((int)TimelineType.DrawOrder << 24); } - } - - public DrawOrderTimeline (int frameCount) { - frames = new float[frameCount]; - drawOrders = new int[frameCount][]; - } - - /// Sets the time and value of the specified keyframe. - /// May be null to use bind pose draw order. - public void SetFrame (int frameIndex, float time, int[] drawOrder) { - frames[frameIndex] = time; - drawOrders[frameIndex] = drawOrder; - } - - public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut) { - ExposedList drawOrder = skeleton.drawOrder; - ExposedList slots = skeleton.slots; - if (mixingOut && setupPose) { - Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count); - return; - } - - float[] frames = this.frames; - if (time < frames[0]) { - if (setupPose) Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count); - return; - } - - int frame; - if (time >= frames[frames.Length - 1]) // Time is after last frame. - frame = frames.Length - 1; - else - frame = Animation.BinarySearch(frames, time) - 1; - - int[] drawOrderToSetupIndex = drawOrders[frame]; - if (drawOrderToSetupIndex == null) { - drawOrder.Clear(); - for (int i = 0, n = slots.Count; i < n; i++) - drawOrder.Add(slots.Items[i]); - } else { - var drawOrderItems = drawOrder.Items; - var slotsItems = slots.Items; - for (int i = 0, n = drawOrderToSetupIndex.Length; i < n; i++) - drawOrderItems[i] = slotsItems[drawOrderToSetupIndex[i]]; - } - } - } - - public class IkConstraintTimeline : CurveTimeline { - public const int ENTRIES = 3; - private const int PREV_TIME = -3, PREV_MIX = -2, PREV_BEND_DIRECTION = -1; - private const int MIX = 1, BEND_DIRECTION = 2; - - internal int ikConstraintIndex; - internal float[] frames; - - public int IkConstraintIndex { get { return ikConstraintIndex; } set { ikConstraintIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, mix, bendDirection, ... - - override public int PropertyId { - get { return ((int)TimelineType.IkConstraint << 24) + ikConstraintIndex; } - } - - public IkConstraintTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - /// Sets the time, mix and bend direction of the specified keyframe. - public void SetFrame (int frameIndex, float time, float mix, int bendDirection) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + MIX] = mix; - frames[frameIndex + BEND_DIRECTION] = bendDirection; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut) { - IkConstraint constraint = skeleton.ikConstraints.Items[ikConstraintIndex]; - float[] frames = this.frames; - if (time < frames[0]) { - if (setupPose) { - constraint.mix = constraint.data.mix; - constraint.bendDirection = constraint.data.bendDirection; - } - return; - } - - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - if (setupPose) { - constraint.mix = constraint.data.mix + (frames[frames.Length + PREV_MIX] - constraint.data.mix) * alpha; - constraint.bendDirection = mixingOut ? constraint.data.bendDirection - : (int)frames[frames.Length + PREV_BEND_DIRECTION]; - } else { - constraint.mix += (frames[frames.Length + PREV_MIX] - constraint.mix) * alpha; - if (!mixingOut) constraint.bendDirection = (int)frames[frames.Length + PREV_BEND_DIRECTION]; - } - return; - } - - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - float mix = frames[frame + PREV_MIX]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - if (setupPose) { - constraint.mix = constraint.data.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.data.mix) * alpha; - constraint.bendDirection = mixingOut ? constraint.data.bendDirection : (int)frames[frame + PREV_BEND_DIRECTION]; - } else { - constraint.mix += (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha; - if (!mixingOut) constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION]; - } - } - } - - public class TransformConstraintTimeline : CurveTimeline { - public const int ENTRIES = 5; - private const int PREV_TIME = -5, PREV_ROTATE = -4, PREV_TRANSLATE = -3, PREV_SCALE = -2, PREV_SHEAR = -1; - private const int ROTATE = 1, TRANSLATE = 2, SCALE = 3, SHEAR = 4; - - internal int transformConstraintIndex; - internal float[] frames; - - public int TransformConstraintIndex { get { return transformConstraintIndex; } set { transformConstraintIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, scale mix, shear mix, ... - - override public int PropertyId { - get { return ((int)TimelineType.TransformConstraint << 24) + transformConstraintIndex; } - } - - public TransformConstraintTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - public void SetFrame (int frameIndex, float time, float rotateMix, float translateMix, float scaleMix, float shearMix) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + ROTATE] = rotateMix; - frames[frameIndex + TRANSLATE] = translateMix; - frames[frameIndex + SCALE] = scaleMix; - frames[frameIndex + SHEAR] = shearMix; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut) { - TransformConstraint constraint = skeleton.transformConstraints.Items[transformConstraintIndex]; - float[] frames = this.frames; - if (time < frames[0]) { - if (setupPose) { - var data = constraint.data; - constraint.rotateMix = data.rotateMix; - constraint.translateMix = data.translateMix; - constraint.scaleMix = data.scaleMix; - constraint.shearMix = data.shearMix; - } - return; - } - - float rotate, translate, scale, shear; - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - int i = frames.Length; - rotate = frames[i + PREV_ROTATE]; - translate = frames[i + PREV_TRANSLATE]; - scale = frames[i + PREV_SCALE]; - shear = frames[i + PREV_SHEAR]; - } else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - rotate = frames[frame + PREV_ROTATE]; - translate = frames[frame + PREV_TRANSLATE]; - scale = frames[frame + PREV_SCALE]; - shear = frames[frame + PREV_SHEAR]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - rotate += (frames[frame + ROTATE] - rotate) * percent; - translate += (frames[frame + TRANSLATE] - translate) * percent; - scale += (frames[frame + SCALE] - scale) * percent; - shear += (frames[frame + SHEAR] - shear) * percent; - } - if (setupPose) { - TransformConstraintData data = constraint.data; - constraint.rotateMix = data.rotateMix + (rotate - data.rotateMix) * alpha; - constraint.translateMix = data.translateMix + (translate - data.translateMix) * alpha; - constraint.scaleMix = data.scaleMix + (scale - data.scaleMix) * alpha; - constraint.shearMix = data.shearMix + (shear - data.shearMix) * alpha; - } else { - constraint.rotateMix += (rotate - constraint.rotateMix) * alpha; - constraint.translateMix += (translate - constraint.translateMix) * alpha; - constraint.scaleMix += (scale - constraint.scaleMix) * alpha; - constraint.shearMix += (shear - constraint.shearMix) * alpha; - } - } - } - - public class PathConstraintPositionTimeline : CurveTimeline { - public const int ENTRIES = 2; - protected const int PREV_TIME = -2, PREV_VALUE = -1; - protected const int VALUE = 1; - - internal int pathConstraintIndex; - internal float[] frames; - - override public int PropertyId { - get { return ((int)TimelineType.PathConstraintPosition << 24) + pathConstraintIndex; } - } - - public PathConstraintPositionTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - public int PathConstraintIndex { get { return pathConstraintIndex; } set { pathConstraintIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, position, ... - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, float value) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + VALUE] = value; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut) { - PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; - float[] frames = this.frames; - if (time < frames[0]) { - if (setupPose) constraint.position = constraint.data.position; - return; - } - - float position; - if (time >= frames[frames.Length - ENTRIES]) // Time is after last frame. - position = frames[frames.Length + PREV_VALUE]; - else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - position = frames[frame + PREV_VALUE]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - position += (frames[frame + VALUE] - position) * percent; - } - if (setupPose) - constraint.position = constraint.data.position + (position - constraint.data.position) * alpha; - else - constraint.position += (position - constraint.position) * alpha; - } - } - - public class PathConstraintSpacingTimeline : PathConstraintPositionTimeline { - override public int PropertyId { - get { return ((int)TimelineType.PathConstraintSpacing << 24) + pathConstraintIndex; } - } - - public PathConstraintSpacingTimeline (int frameCount) - : base(frameCount) { - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut) { - PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; - float[] frames = this.frames; - if (time < frames[0]) { - if (setupPose) constraint.spacing = constraint.data.spacing; - return; - } - - float spacing; - if (time >= frames[frames.Length - ENTRIES]) // Time is after last frame. - spacing = frames[frames.Length + PREV_VALUE]; - else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - spacing = frames[frame + PREV_VALUE]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - spacing += (frames[frame + VALUE] - spacing) * percent; - } - - if (setupPose) - constraint.spacing = constraint.data.spacing + (spacing - constraint.data.spacing) * alpha; - else - constraint.spacing += (spacing - constraint.spacing) * alpha; - } - } - - public class PathConstraintMixTimeline : CurveTimeline { - public const int ENTRIES = 3; - private const int PREV_TIME = -3, PREV_ROTATE = -2, PREV_TRANSLATE = -1; - private const int ROTATE = 1, TRANSLATE = 2; - - internal int pathConstraintIndex; - internal float[] frames; - - public int PathConstraintIndex { get { return pathConstraintIndex; } set { pathConstraintIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, ... - - override public int PropertyId { - get { return ((int)TimelineType.PathConstraintMix << 24) + pathConstraintIndex; } - } - - public PathConstraintMixTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - /// Sets the time and mixes of the specified keyframe. - public void SetFrame (int frameIndex, float time, float rotateMix, float translateMix) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + ROTATE] = rotateMix; - frames[frameIndex + TRANSLATE] = translateMix; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut) { - PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; - float[] frames = this.frames; - if (time < frames[0]) { - if (setupPose) { - constraint.rotateMix = constraint.data.rotateMix; - constraint.translateMix = constraint.data.translateMix; - } - return; - } - - float rotate, translate; - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - rotate = frames[frames.Length + PREV_ROTATE]; - translate = frames[frames.Length + PREV_TRANSLATE]; - } else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - rotate = frames[frame + PREV_ROTATE]; - translate = frames[frame + PREV_TRANSLATE]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - rotate += (frames[frame + ROTATE] - rotate) * percent; - translate += (frames[frame + TRANSLATE] - translate) * percent; - } - - if (setupPose) { - constraint.rotateMix = constraint.data.rotateMix + (rotate - constraint.data.rotateMix) * alpha; - constraint.translateMix = constraint.data.translateMix + (translate - constraint.data.translateMix) * alpha; - } else { - constraint.rotateMix += (rotate - constraint.rotateMix) * alpha; - constraint.translateMix += (translate - constraint.translateMix) * alpha; - } - } - } + +namespace Spine3_5_51 +{ + public class Animation + { + internal ExposedList timelines; + internal float duration; + internal String name; + + public String Name { get { return name; } } + public ExposedList Timelines { get { return timelines; } set { timelines = value; } } + public float Duration { get { return duration; } set { duration = value; } } + + public Animation(String name, ExposedList timelines, float duration) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + if (timelines == null) throw new ArgumentNullException("timelines", "timelines cannot be null."); + this.name = name; + this.timelines = timelines; + this.duration = duration; + } + + /// Applies all the animation's timelines to the specified skeleton. + /// The skeleton to be posed. + /// The last time the animation was applied. + /// The point in time in the animation to apply to the skeleton. + /// If true, time wraps within the animation duration. + /// Any triggered events are added. May be null. + /// The percentage between this animation's pose and the current pose. + /// If true, the animation is mixed with the setup pose, else it is mixed with the current pose. Passing true when alpha is 1 is slightly more efficient. + /// True when mixing over time toward the setup or current pose, false when mixing toward the keyed pose. Irrelevant when alpha is 1. + /// + public void Apply(Skeleton skeleton, float lastTime, float time, bool loop, ExposedList events, float alpha, bool setupPose, bool mixingOut) + { + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + + if (loop && duration != 0) + { + time %= duration; + if (lastTime > 0) lastTime %= duration; + } + + ExposedList timelines = this.timelines; + for (int i = 0, n = timelines.Count; i < n; i++) + timelines.Items[i].Apply(skeleton, lastTime, time, events, alpha, setupPose, mixingOut); + } + + /// After the first and before the last entry. + internal static int BinarySearch(float[] values, float target, int step) + { + int low = 0; + int high = values.Length / step - 2; + if (high == 0) return step; + int current = (int)((uint)high >> 1); + while (true) + { + if (values[(current + 1) * step] <= target) + low = current + 1; + else + high = current; + if (low == high) return (low + 1) * step; + current = (int)((uint)(low + high) >> 1); + } + } + + /// After the first and before the last entry. + internal static int BinarySearch(float[] values, float target) + { + int low = 0; + int high = values.Length - 2; + if (high == 0) return 1; + int current = (int)((uint)high >> 1); + while (true) + { + if (values[(current + 1)] <= target) + low = current + 1; + else + high = current; + if (low == high) return (low + 1); + current = (int)((uint)(low + high) >> 1); + } + } + + internal static int LinearSearch(float[] values, float target, int step) + { + for (int i = 0, last = values.Length - step; i <= last; i += step) + if (values[i] > target) return i; + return -1; + } + } + + public interface Timeline + { + /// Sets the value(s) for the specified time. + /// Any triggered events are added. May be null. + /// True when the timeline is mixed with the setup pose, false when it is mixed with the current pose. Passing true when alpha is 1 is slightly more efficient. + /// True when mixing over time toward the setup or current pose, false when mixing toward the keyed pose. + /// Used for timelines with instant transitions, eg draw order, attachment visibility, scale sign. + void Apply(Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, bool setupPose, bool mixingOut); + int PropertyId { get; } + } + + internal enum TimelineType + { + Rotate = 0, Translate, Scale, Shear, // + Attachment, Color, Deform, // + Event, DrawOrder, // + IkConstraint, TransformConstraint, // + PathConstraintPosition, PathConstraintSpacing, PathConstraintMix + } + + /// Base class for frames that use an interpolation bezier curve. + abstract public class CurveTimeline : Timeline + { + protected const float LINEAR = 0, STEPPED = 1, BEZIER = 2; + protected const int BEZIER_SIZE = 10 * 2 - 1; + + private float[] curves; // type, x, y, ... + public int FrameCount { get { return curves.Length / BEZIER_SIZE + 1; } } + + public CurveTimeline(int frameCount) + { + if (frameCount <= 0) throw new ArgumentException("frameCount must be > 0: " + frameCount, "frameCount"); + curves = new float[(frameCount - 1) * BEZIER_SIZE]; + } + + abstract public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut); + + abstract public int PropertyId { get; } + + public void SetLinear(int frameIndex) + { + curves[frameIndex * BEZIER_SIZE] = LINEAR; + } + + public void SetStepped(int frameIndex) + { + curves[frameIndex * BEZIER_SIZE] = STEPPED; + } + + /// Sets the control handle positions for an interpolation bezier curve used to transition from this keyframe to the next. + /// cx1 and cx2 are from 0 to 1, representing the percent of time between the two keyframes. cy1 and cy2 are the percent of + /// the difference between the keyframe's values. + public void SetCurve(int frameIndex, float cx1, float cy1, float cx2, float cy2) + { + float tmpx = (-cx1 * 2 + cx2) * 0.03f, tmpy = (-cy1 * 2 + cy2) * 0.03f; + float dddfx = ((cx1 - cx2) * 3 + 1) * 0.006f, dddfy = ((cy1 - cy2) * 3 + 1) * 0.006f; + float ddfx = tmpx * 2 + dddfx, ddfy = tmpy * 2 + dddfy; + float dfx = cx1 * 0.3f + tmpx + dddfx * 0.16666667f, dfy = cy1 * 0.3f + tmpy + dddfy * 0.16666667f; + + int i = frameIndex * BEZIER_SIZE; + float[] curves = this.curves; + curves[i++] = BEZIER; + + float x = dfx, y = dfy; + for (int n = i + BEZIER_SIZE - 1; i < n; i += 2) + { + curves[i] = x; + curves[i + 1] = y; + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + x += dfx; + y += dfy; + } + } + + public float GetCurvePercent(int frameIndex, float percent) + { + percent = MathUtils.Clamp(percent, 0, 1); + float[] curves = this.curves; + int i = frameIndex * BEZIER_SIZE; + float type = curves[i]; + if (type == LINEAR) return percent; + if (type == STEPPED) return 0; + i++; + float x = 0; + for (int start = i, n = i + BEZIER_SIZE - 1; i < n; i += 2) + { + x = curves[i]; + if (x >= percent) + { + float prevX, prevY; + if (i == start) + { + prevX = 0; + prevY = 0; + } + else + { + prevX = curves[i - 2]; + prevY = curves[i - 1]; + } + return prevY + (curves[i + 1] - prevY) * (percent - prevX) / (x - prevX); + } + } + float y = curves[i - 1]; + return y + (1 - y) * (percent - x) / (1 - x); // Last point is 1,1. + } + public float GetCurveType(int frameIndex) + { + return curves[frameIndex * BEZIER_SIZE]; + } + } + + public class RotateTimeline : CurveTimeline + { + public const int ENTRIES = 2; + internal const int PREV_TIME = -2, PREV_ROTATION = -1; + internal const int ROTATION = 1; + + internal int boneIndex; + internal float[] frames; + + public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, angle, ... + + override public int PropertyId + { + get { return ((int)TimelineType.Rotate << 24) + boneIndex; } + } + + public RotateTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount << 1]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, float degrees) + { + frameIndex <<= 1; + frames[frameIndex] = time; + frames[frameIndex + ROTATION] = degrees; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut) + { + Bone bone = skeleton.bones.Items[boneIndex]; + + float[] frames = this.frames; + if (time < frames[0]) + { + if (setupPose) bone.rotation = bone.data.rotation; + return; + } + + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + if (setupPose) + { + bone.rotation = bone.data.rotation + frames[frames.Length + PREV_ROTATION] * alpha; + } + else + { + float rr = bone.data.rotation + frames[frames.Length + PREV_ROTATION] - bone.rotation; + rr -= (16384 - (int)(16384.499999999996 - rr / 360)) * 360; // Wrap within -180 and 180. + bone.rotation += rr * alpha; + } + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + float prevRotation = frames[frame + PREV_ROTATION]; + float frameTime = frames[frame]; + float percent = GetCurvePercent((frame >> 1) - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + float r = frames[frame + ROTATION] - prevRotation; + r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; + r = prevRotation + r * percent; + if (setupPose) + { + r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; + bone.rotation = bone.data.rotation + r * alpha; + } + else + { + r = bone.data.rotation + r - bone.rotation; + r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; + bone.rotation += r * alpha; + } + } + } + + public class TranslateTimeline : CurveTimeline + { + public const int ENTRIES = 3; + protected const int PREV_TIME = -3, PREV_X = -2, PREV_Y = -1; + protected const int X = 1, Y = 2; + + internal int boneIndex; + internal float[] frames; + + public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, value, value, ... + + override public int PropertyId + { + get { return ((int)TimelineType.Translate << 24) + boneIndex; } + } + + public TranslateTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, float x, float y) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + X] = x; + frames[frameIndex + Y] = y; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut) + { + Bone bone = skeleton.bones.Items[boneIndex]; + + float[] frames = this.frames; + if (time < frames[0]) + { + if (setupPose) + { + bone.x = bone.data.x; + bone.y = bone.data.y; + } + return; + } + + float x, y; + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + x = frames[frames.Length + PREV_X]; + y = frames[frames.Length + PREV_Y]; + } + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + x = frames[frame + PREV_X]; + y = frames[frame + PREV_Y]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + x += (frames[frame + X] - x) * percent; + y += (frames[frame + Y] - y) * percent; + } + if (setupPose) + { + bone.x = bone.data.x + x * alpha; + bone.y = bone.data.y + y * alpha; + } + else + { + bone.x += (bone.data.x + x - bone.x) * alpha; + bone.y += (bone.data.y + y - bone.y) * alpha; + } + } + } + + public class ScaleTimeline : TranslateTimeline + { + override public int PropertyId + { + get { return ((int)TimelineType.Scale << 24) + boneIndex; } + } + + public ScaleTimeline(int frameCount) + : base(frameCount) + { + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut) + { + Bone bone = skeleton.bones.Items[boneIndex]; + + float[] frames = this.frames; + if (time < frames[0]) + { + if (setupPose) + { + bone.scaleX = bone.data.scaleX; + bone.scaleY = bone.data.scaleY; + } + return; + } + + float x, y; + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + x = frames[frames.Length + PREV_X] * bone.data.scaleX; + y = frames[frames.Length + PREV_Y] * bone.data.scaleY; + } + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + x = frames[frame + PREV_X]; + y = frames[frame + PREV_Y]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + x = (x + (frames[frame + X] - x) * percent) * bone.data.scaleX; + y = (y + (frames[frame + Y] - y) * percent) * bone.data.scaleY; + } + if (alpha == 1) + { + bone.scaleX = x; + bone.scaleY = y; + } + else + { + float bx, by; + if (setupPose) + { + bx = bone.data.scaleX; + by = bone.data.scaleY; + } + else + { + bx = bone.scaleX; + by = bone.scaleY; + } + // Mixing out uses sign of setup or current pose, else use sign of key. + if (mixingOut) + { + x = Math.Abs(x) * Math.Sign(bx); + y = Math.Abs(y) * Math.Sign(by); + } + else + { + bx = Math.Abs(bx) * Math.Sign(x); + by = Math.Abs(by) * Math.Sign(y); + } + bone.scaleX = bx + (x - bx) * alpha; + bone.scaleY = by + (y - by) * alpha; + } + } + } + + public class ShearTimeline : TranslateTimeline + { + override public int PropertyId + { + get { return ((int)TimelineType.Shear << 24) + boneIndex; } + } + + public ShearTimeline(int frameCount) + : base(frameCount) + { + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut) + { + Bone bone = skeleton.bones.Items[boneIndex]; + float[] frames = this.frames; + if (time < frames[0]) + { + if (setupPose) + { + bone.shearX = bone.data.shearX; + bone.shearY = bone.data.shearY; + } + return; + } + + float x, y; + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + x = frames[frames.Length + PREV_X]; + y = frames[frames.Length + PREV_Y]; + } + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + x = frames[frame + PREV_X]; + y = frames[frame + PREV_Y]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + x = x + (frames[frame + X] - x) * percent; + y = y + (frames[frame + Y] - y) * percent; + } + if (setupPose) + { + bone.shearX = bone.data.shearX + x * alpha; + bone.shearY = bone.data.shearY + y * alpha; + } + else + { + bone.shearX += (bone.data.shearX + x - bone.shearX) * alpha; + bone.shearY += (bone.data.shearY + y - bone.shearY) * alpha; + } + } + } + + public class ColorTimeline : CurveTimeline + { + public const int ENTRIES = 5; + protected const int PREV_TIME = -5, PREV_R = -4, PREV_G = -3, PREV_B = -2, PREV_A = -1; + protected const int R = 1, G = 2, B = 3, A = 4; + + internal int slotIndex; + internal float[] frames; + + public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, r, g, b, a, ... + + override public int PropertyId + { + get { return ((int)TimelineType.Color << 24) + slotIndex; } + } + + public ColorTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, float r, float g, float b, float a) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + R] = r; + frames[frameIndex + G] = g; + frames[frameIndex + B] = b; + frames[frameIndex + A] = a; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut) + { + Slot slot = skeleton.slots.Items[slotIndex]; + float[] frames = this.frames; + if (time < frames[0]) + { + if (setupPose) + { + var slotData = slot.data; + slot.r = slotData.r; + slot.g = slotData.g; + slot.b = slotData.b; + slot.a = slotData.a; + } + return; + } + + float r, g, b, a; + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + int i = frames.Length; + r = frames[i + PREV_R]; + g = frames[i + PREV_G]; + b = frames[i + PREV_B]; + a = frames[i + PREV_A]; + } + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + r = frames[frame + PREV_R]; + g = frames[frame + PREV_G]; + b = frames[frame + PREV_B]; + a = frames[frame + PREV_A]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + r += (frames[frame + R] - r) * percent; + g += (frames[frame + G] - g) * percent; + b += (frames[frame + B] - b) * percent; + a += (frames[frame + A] - a) * percent; + } + if (alpha == 1) + { + slot.r = r; + slot.g = g; + slot.b = b; + slot.a = a; + } + else + { + float br, bg, bb, ba; + if (setupPose) + { + br = slot.data.r; + bg = slot.data.g; + bb = slot.data.b; + ba = slot.data.a; + } + else + { + br = slot.r; + bg = slot.g; + bb = slot.b; + ba = slot.a; + } + slot.r = br + ((r - br) * alpha); + slot.g = bg + ((g - bg) * alpha); + slot.b = bb + ((b - bb) * alpha); + slot.a = ba + ((a - ba) * alpha); + } + } + } + + public class AttachmentTimeline : Timeline + { + internal int slotIndex; + internal float[] frames; + private String[] attachmentNames; + + public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, ... + public String[] AttachmentNames { get { return attachmentNames; } set { attachmentNames = value; } } + public int FrameCount { get { return frames.Length; } } + + public int PropertyId + { + get { return ((int)TimelineType.Attachment << 24) + slotIndex; } + } + + public AttachmentTimeline(int frameCount) + { + frames = new float[frameCount]; + attachmentNames = new String[frameCount]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, String attachmentName) + { + frames[frameIndex] = time; + attachmentNames[frameIndex] = attachmentName; + } + + public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut) + { + string attachmentName; + Slot slot = skeleton.slots.Items[slotIndex]; + if (mixingOut && setupPose) + { + attachmentName = slot.data.attachmentName; + slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); + return; + } + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + if (setupPose) + { + attachmentName = slot.data.attachmentName; + slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); + } + return; + } + + int frameIndex; + if (time >= frames[frames.Length - 1]) // Time is after last frame. + frameIndex = frames.Length - 1; + else + frameIndex = Animation.BinarySearch(frames, time, 1) - 1; + + attachmentName = attachmentNames[frameIndex]; + slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); + } + } + + public class DeformTimeline : CurveTimeline + { + internal int slotIndex; + internal float[] frames; + internal float[][] frameVertices; + internal VertexAttachment attachment; + + public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, ... + public float[][] Vertices { get { return frameVertices; } set { frameVertices = value; } } + public VertexAttachment Attachment { get { return attachment; } set { attachment = value; } } + + override public int PropertyId + { + get { return ((int)TimelineType.Deform << 24) + slotIndex; } + } + + public DeformTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount]; + frameVertices = new float[frameCount][]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, float[] vertices) + { + frames[frameIndex] = time; + frameVertices[frameIndex] = vertices; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut) + { + Slot slot = skeleton.slots.Items[slotIndex]; + VertexAttachment slotAttachment = slot.attachment as VertexAttachment; + if (slotAttachment == null || !slotAttachment.ApplyDeform(attachment)) return; + + var verticesArray = slot.attachmentVertices; + float[] frames = this.frames; + if (time < frames[0]) + { + if (setupPose) verticesArray.Clear(); + return; + } + + float[][] frameVertices = this.frameVertices; + int vertexCount = frameVertices[0].Length; + + if (verticesArray.Count != vertexCount) alpha = 1; // Don't mix from uninitialized slot vertices. + // verticesArray.SetSize(vertexCount) // Ensure size and preemptively set count. + if (verticesArray.Capacity < vertexCount) verticesArray.Capacity = vertexCount; + verticesArray.Count = vertexCount; + float[] vertices = verticesArray.Items; + + if (time >= frames[frames.Length - 1]) + { // Time is after last frame. + float[] lastVertices = frameVertices[frames.Length - 1]; + if (alpha == 1) + { + // Vertex positions or deform offsets, no alpha. + Array.Copy(lastVertices, 0, vertices, 0, vertexCount); + } + else if (setupPose) + { + VertexAttachment vertexAttachment = slotAttachment; + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions, with alpha. + float[] setupVertices = vertexAttachment.vertices; + for (int i = 0; i < vertexCount; i++) + { + float setup = setupVertices[i]; + vertices[i] = setup + (lastVertices[i] - setup) * alpha; + } + } + else + { + // Weighted deform offsets, with alpha. + for (int i = 0; i < vertexCount; i++) + vertices[i] = lastVertices[i] * alpha; + } + } + else + { + // Vertex positions or deform offsets, with alpha. + for (int i = 0; i < vertexCount; i++) + vertices[i] += (lastVertices[i] - vertices[i]) * alpha; + } + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time); + float[] prevVertices = frameVertices[frame - 1]; + float[] nextVertices = frameVertices[frame]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame - 1, 1 - (time - frameTime) / (frames[frame - 1] - frameTime)); + + if (alpha == 1) + { + // Vertex positions or deform offsets, no alpha. + for (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i]; + vertices[i] = prev + (nextVertices[i] - prev) * percent; + } + } + else if (setupPose) + { + VertexAttachment vertexAttachment = slotAttachment; + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions, with alpha. + var setupVertices = vertexAttachment.vertices; + for (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i], setup = setupVertices[i]; + vertices[i] = setup + (prev + (nextVertices[i] - prev) * percent - setup) * alpha; + } + } + else + { + // Weighted deform offsets, with alpha. + for (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i]; + vertices[i] = (prev + (nextVertices[i] - prev) * percent) * alpha; + } + } + } + else + { + // Vertex positions or deform offsets, with alpha. + for (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i]; + vertices[i] += (prev + (nextVertices[i] - prev) * percent - vertices[i]) * alpha; + } + } + } + } + + public class EventTimeline : Timeline + { + internal float[] frames; + private Event[] events; + + public float[] Frames { get { return frames; } set { frames = value; } } // time, ... + public Event[] Events { get { return events; } set { events = value; } } + public int FrameCount { get { return frames.Length; } } + + public int PropertyId + { + get { return ((int)TimelineType.Event << 24); } + } + + public EventTimeline(int frameCount) + { + frames = new float[frameCount]; + events = new Event[frameCount]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, Event e) + { + frames[frameIndex] = e.Time; + events[frameIndex] = e; + } + + /// Fires events for frames > lastTime and <= time. + public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut) + { + if (firedEvents == null) return; + float[] frames = this.frames; + int frameCount = frames.Length; + + if (lastTime > time) + { // Fire events after last time for looped animations. + Apply(skeleton, lastTime, int.MaxValue, firedEvents, alpha, setupPose, mixingOut); + lastTime = -1f; + } + else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame. + return; + if (time < frames[0]) return; // Time is before first frame. + + int frame; + if (lastTime < frames[0]) + frame = 0; + else + { + frame = Animation.BinarySearch(frames, lastTime); + float frameTime = frames[frame]; + while (frame > 0) + { // Fire multiple events with the same frame. + if (frames[frame - 1] != frameTime) break; + frame--; + } + } + for (; frame < frameCount && time >= frames[frame]; frame++) + firedEvents.Add(events[frame]); + } + } + + public class DrawOrderTimeline : Timeline + { + internal float[] frames; + private int[][] drawOrders; + + public float[] Frames { get { return frames; } set { frames = value; } } // time, ... + public int[][] DrawOrders { get { return drawOrders; } set { drawOrders = value; } } + public int FrameCount { get { return frames.Length; } } + + public int PropertyId + { + get { return ((int)TimelineType.DrawOrder << 24); } + } + + public DrawOrderTimeline(int frameCount) + { + frames = new float[frameCount]; + drawOrders = new int[frameCount][]; + } + + /// Sets the time and value of the specified keyframe. + /// May be null to use bind pose draw order. + public void SetFrame(int frameIndex, float time, int[] drawOrder) + { + frames[frameIndex] = time; + drawOrders[frameIndex] = drawOrder; + } + + public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut) + { + ExposedList drawOrder = skeleton.drawOrder; + ExposedList slots = skeleton.slots; + if (mixingOut && setupPose) + { + Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count); + return; + } + + float[] frames = this.frames; + if (time < frames[0]) + { + if (setupPose) Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count); + return; + } + + int frame; + if (time >= frames[frames.Length - 1]) // Time is after last frame. + frame = frames.Length - 1; + else + frame = Animation.BinarySearch(frames, time) - 1; + + int[] drawOrderToSetupIndex = drawOrders[frame]; + if (drawOrderToSetupIndex == null) + { + drawOrder.Clear(); + for (int i = 0, n = slots.Count; i < n; i++) + drawOrder.Add(slots.Items[i]); + } + else + { + var drawOrderItems = drawOrder.Items; + var slotsItems = slots.Items; + for (int i = 0, n = drawOrderToSetupIndex.Length; i < n; i++) + drawOrderItems[i] = slotsItems[drawOrderToSetupIndex[i]]; + } + } + } + + public class IkConstraintTimeline : CurveTimeline + { + public const int ENTRIES = 3; + private const int PREV_TIME = -3, PREV_MIX = -2, PREV_BEND_DIRECTION = -1; + private const int MIX = 1, BEND_DIRECTION = 2; + + internal int ikConstraintIndex; + internal float[] frames; + + public int IkConstraintIndex { get { return ikConstraintIndex; } set { ikConstraintIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, mix, bendDirection, ... + + override public int PropertyId + { + get { return ((int)TimelineType.IkConstraint << 24) + ikConstraintIndex; } + } + + public IkConstraintTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + /// Sets the time, mix and bend direction of the specified keyframe. + public void SetFrame(int frameIndex, float time, float mix, int bendDirection) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + MIX] = mix; + frames[frameIndex + BEND_DIRECTION] = bendDirection; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut) + { + IkConstraint constraint = skeleton.ikConstraints.Items[ikConstraintIndex]; + float[] frames = this.frames; + if (time < frames[0]) + { + if (setupPose) + { + constraint.mix = constraint.data.mix; + constraint.bendDirection = constraint.data.bendDirection; + } + return; + } + + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + if (setupPose) + { + constraint.mix = constraint.data.mix + (frames[frames.Length + PREV_MIX] - constraint.data.mix) * alpha; + constraint.bendDirection = mixingOut ? constraint.data.bendDirection + : (int)frames[frames.Length + PREV_BEND_DIRECTION]; + } + else + { + constraint.mix += (frames[frames.Length + PREV_MIX] - constraint.mix) * alpha; + if (!mixingOut) constraint.bendDirection = (int)frames[frames.Length + PREV_BEND_DIRECTION]; + } + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + float mix = frames[frame + PREV_MIX]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + if (setupPose) + { + constraint.mix = constraint.data.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.data.mix) * alpha; + constraint.bendDirection = mixingOut ? constraint.data.bendDirection : (int)frames[frame + PREV_BEND_DIRECTION]; + } + else + { + constraint.mix += (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha; + if (!mixingOut) constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION]; + } + } + } + + public class TransformConstraintTimeline : CurveTimeline + { + public const int ENTRIES = 5; + private const int PREV_TIME = -5, PREV_ROTATE = -4, PREV_TRANSLATE = -3, PREV_SCALE = -2, PREV_SHEAR = -1; + private const int ROTATE = 1, TRANSLATE = 2, SCALE = 3, SHEAR = 4; + + internal int transformConstraintIndex; + internal float[] frames; + + public int TransformConstraintIndex { get { return transformConstraintIndex; } set { transformConstraintIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, scale mix, shear mix, ... + + override public int PropertyId + { + get { return ((int)TimelineType.TransformConstraint << 24) + transformConstraintIndex; } + } + + public TransformConstraintTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + public void SetFrame(int frameIndex, float time, float rotateMix, float translateMix, float scaleMix, float shearMix) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + ROTATE] = rotateMix; + frames[frameIndex + TRANSLATE] = translateMix; + frames[frameIndex + SCALE] = scaleMix; + frames[frameIndex + SHEAR] = shearMix; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut) + { + TransformConstraint constraint = skeleton.transformConstraints.Items[transformConstraintIndex]; + float[] frames = this.frames; + if (time < frames[0]) + { + if (setupPose) + { + var data = constraint.data; + constraint.rotateMix = data.rotateMix; + constraint.translateMix = data.translateMix; + constraint.scaleMix = data.scaleMix; + constraint.shearMix = data.shearMix; + } + return; + } + + float rotate, translate, scale, shear; + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + int i = frames.Length; + rotate = frames[i + PREV_ROTATE]; + translate = frames[i + PREV_TRANSLATE]; + scale = frames[i + PREV_SCALE]; + shear = frames[i + PREV_SHEAR]; + } + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + rotate = frames[frame + PREV_ROTATE]; + translate = frames[frame + PREV_TRANSLATE]; + scale = frames[frame + PREV_SCALE]; + shear = frames[frame + PREV_SHEAR]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + rotate += (frames[frame + ROTATE] - rotate) * percent; + translate += (frames[frame + TRANSLATE] - translate) * percent; + scale += (frames[frame + SCALE] - scale) * percent; + shear += (frames[frame + SHEAR] - shear) * percent; + } + if (setupPose) + { + TransformConstraintData data = constraint.data; + constraint.rotateMix = data.rotateMix + (rotate - data.rotateMix) * alpha; + constraint.translateMix = data.translateMix + (translate - data.translateMix) * alpha; + constraint.scaleMix = data.scaleMix + (scale - data.scaleMix) * alpha; + constraint.shearMix = data.shearMix + (shear - data.shearMix) * alpha; + } + else + { + constraint.rotateMix += (rotate - constraint.rotateMix) * alpha; + constraint.translateMix += (translate - constraint.translateMix) * alpha; + constraint.scaleMix += (scale - constraint.scaleMix) * alpha; + constraint.shearMix += (shear - constraint.shearMix) * alpha; + } + } + } + + public class PathConstraintPositionTimeline : CurveTimeline + { + public const int ENTRIES = 2; + protected const int PREV_TIME = -2, PREV_VALUE = -1; + protected const int VALUE = 1; + + internal int pathConstraintIndex; + internal float[] frames; + + override public int PropertyId + { + get { return ((int)TimelineType.PathConstraintPosition << 24) + pathConstraintIndex; } + } + + public PathConstraintPositionTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + public int PathConstraintIndex { get { return pathConstraintIndex; } set { pathConstraintIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, position, ... + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, float value) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + VALUE] = value; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut) + { + PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; + float[] frames = this.frames; + if (time < frames[0]) + { + if (setupPose) constraint.position = constraint.data.position; + return; + } + + float position; + if (time >= frames[frames.Length - ENTRIES]) // Time is after last frame. + position = frames[frames.Length + PREV_VALUE]; + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + position = frames[frame + PREV_VALUE]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + position += (frames[frame + VALUE] - position) * percent; + } + if (setupPose) + constraint.position = constraint.data.position + (position - constraint.data.position) * alpha; + else + constraint.position += (position - constraint.position) * alpha; + } + } + + public class PathConstraintSpacingTimeline : PathConstraintPositionTimeline + { + override public int PropertyId + { + get { return ((int)TimelineType.PathConstraintSpacing << 24) + pathConstraintIndex; } + } + + public PathConstraintSpacingTimeline(int frameCount) + : base(frameCount) + { + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut) + { + PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; + float[] frames = this.frames; + if (time < frames[0]) + { + if (setupPose) constraint.spacing = constraint.data.spacing; + return; + } + + float spacing; + if (time >= frames[frames.Length - ENTRIES]) // Time is after last frame. + spacing = frames[frames.Length + PREV_VALUE]; + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + spacing = frames[frame + PREV_VALUE]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + spacing += (frames[frame + VALUE] - spacing) * percent; + } + + if (setupPose) + constraint.spacing = constraint.data.spacing + (spacing - constraint.data.spacing) * alpha; + else + constraint.spacing += (spacing - constraint.spacing) * alpha; + } + } + + public class PathConstraintMixTimeline : CurveTimeline + { + public const int ENTRIES = 3; + private const int PREV_TIME = -3, PREV_ROTATE = -2, PREV_TRANSLATE = -1; + private const int ROTATE = 1, TRANSLATE = 2; + + internal int pathConstraintIndex; + internal float[] frames; + + public int PathConstraintIndex { get { return pathConstraintIndex; } set { pathConstraintIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, ... + + override public int PropertyId + { + get { return ((int)TimelineType.PathConstraintMix << 24) + pathConstraintIndex; } + } + + public PathConstraintMixTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + /// Sets the time and mixes of the specified keyframe. + public void SetFrame(int frameIndex, float time, float rotateMix, float translateMix) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + ROTATE] = rotateMix; + frames[frameIndex + TRANSLATE] = translateMix; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, bool setupPose, bool mixingOut) + { + PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; + float[] frames = this.frames; + if (time < frames[0]) + { + if (setupPose) + { + constraint.rotateMix = constraint.data.rotateMix; + constraint.translateMix = constraint.data.translateMix; + } + return; + } + + float rotate, translate; + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + rotate = frames[frames.Length + PREV_ROTATE]; + translate = frames[frames.Length + PREV_TRANSLATE]; + } + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + rotate = frames[frame + PREV_ROTATE]; + translate = frames[frame + PREV_TRANSLATE]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + rotate += (frames[frame + ROTATE] - rotate) * percent; + translate += (frames[frame + TRANSLATE] - translate) * percent; + } + + if (setupPose) + { + constraint.rotateMix = constraint.data.rotateMix + (rotate - constraint.data.rotateMix) * alpha; + constraint.translateMix = constraint.data.translateMix + (translate - constraint.data.translateMix) * alpha; + } + else + { + constraint.rotateMix += (rotate - constraint.rotateMix) * alpha; + constraint.translateMix += (translate - constraint.translateMix) * alpha; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/AnimationState.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/AnimationState.cs index 3dc1fd3..ff235ba 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/AnimationState.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/AnimationState.cs @@ -31,970 +31,1087 @@ using System; using System.Collections.Generic; -namespace Spine3_5_51 { - public class AnimationState { - static readonly Animation EmptyAnimation = new Animation("", new ExposedList(), 0); - - private AnimationStateData data; - private readonly ExposedList tracks = new ExposedList(); - private readonly HashSet propertyIDs = new HashSet(); - private readonly ExposedList events = new ExposedList(); - private readonly EventQueue queue; - private bool animationsChanged; - private float timeScale = 1; - - Pool trackEntryPool = new Pool(); - - public AnimationStateData Data { get { return data; } } - /// A list of tracks that have animations, which may contain nulls. - public ExposedList Tracks { get { return tracks; } } - public float TimeScale { get { return timeScale; } set { timeScale = value; } } - - public delegate void TrackEntryDelegate (TrackEntry trackEntry); - public event TrackEntryDelegate Start, Interrupt, End, Dispose, Complete; - - public delegate void TrackEntryEventDelegate (TrackEntry trackEntry, Event e); - public event TrackEntryEventDelegate Event; - - public AnimationState (AnimationStateData data) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - this.data = data; - this.queue = new EventQueue(this, HandleAnimationsChanged, trackEntryPool); - } - - void HandleAnimationsChanged () { - this.animationsChanged = true; - } - - /// - /// Increments the track entry times, setting queued animations as current if needed - /// delta time - public void Update (float delta) { - delta *= timeScale; - var tracksItems = tracks.Items; - for (int i = 0, n = tracks.Count; i < n; i++) { - TrackEntry current = tracksItems[i]; - if (current == null) continue; - - 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 = nextTime + (delta * next.timeScale); - current.trackTime += currentDelta; - SetCurrent(i, next, true); - while (next.mixingFrom != null) { - next.mixTime += currentDelta; - next = next.mixingFrom; - } - continue; - } - } else { - // Clear the track when there is no next entry, the track end time is reached, and there is no mixingFrom. - if (current.trackLast >= current.trackEnd && current.mixingFrom == null) { - tracksItems[i] = null; - queue.End(current); - DisposeNext(current); - continue; - } - } - UpdateMixingFrom(current, delta); - - current.trackTime += currentDelta; - } - - queue.Drain(); - } - - private void UpdateMixingFrom (TrackEntry entry, float delta) { - TrackEntry from = entry.mixingFrom; - if (from == null) return; - - UpdateMixingFrom(from, delta); - - if (entry.mixTime >= entry.mixDuration && from.mixingFrom == null && entry.mixTime > 0) { - entry.mixingFrom = null; - queue.End(from); - return; - } - - from.animationLast = from.nextAnimationLast; - from.trackLast = from.nextTrackLast; - from.trackTime += delta * from.timeScale; - entry.mixTime += delta * entry.timeScale; - } - - - /// - /// 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. - public void Apply (Skeleton skeleton) { - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - if (animationsChanged) AnimationsChanged(); - - var events = this.events; - - var tracksItems = tracks.Items; - for (int i = 0, m = tracks.Count; i < m; i++) { - TrackEntry current = tracksItems[i]; - if (current == null || current.delay > 0) continue; - - // Apply mixing from entries first. - float mix = current.alpha; - if (current.mixingFrom != null) - mix *= ApplyMixingFrom(current, skeleton); - else if (current.trackTime >= current.trackEnd) // - mix = 0; // Set to setup pose the last time the entry will be applied. - - // Apply current entry. - float animationLast = current.animationLast, animationTime = current.AnimationTime; - int timelineCount = current.animation.timelines.Count; - var timelines = current.animation.timelines; - var timelinesItems = timelines.Items; - if (mix == 1) { - for (int ii = 0; ii < timelineCount; ii++) - timelinesItems[ii].Apply(skeleton, animationLast, animationTime, events, 1, true, false); - } else { - bool firstFrame = current.timelinesRotation.Count == 0; - if (firstFrame) current.timelinesRotation.EnsureCapacity(timelines.Count << 1); - var timelinesRotation = current.timelinesRotation.Items; - - var timelinesFirstItems = current.timelinesFirst.Items; - for (int ii = 0; ii < timelineCount; ii++) { - Timeline timeline = timelinesItems[ii]; - var rotateTimeline = timeline as RotateTimeline; - if (rotateTimeline != null) { - ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, mix, timelinesFirstItems[ii], timelinesRotation, ii << 1, - firstFrame); - } else { - timeline.Apply(skeleton, animationLast, animationTime, events, mix, timelinesFirstItems[ii], false); - } - } - } - QueueEvents(current, animationTime); - events.Clear(false); - current.nextAnimationLast = animationTime; - current.nextTrackLast = current.trackTime; - } - - queue.Drain(); - } - - private float ApplyMixingFrom (TrackEntry entry, Skeleton skeleton) { - TrackEntry from = entry.mixingFrom; - if (from.mixingFrom != null) ApplyMixingFrom(from, skeleton); - - float mix; - if (entry.mixDuration == 0) // Single frame mix to undo mixingFrom changes. - mix = 1; - else { - mix = entry.mixTime / entry.mixDuration; - if (mix > 1) mix = 1; - } - - var eventBuffer = mix < from.eventThreshold ? this.events : null; - bool attachments = mix < from.attachmentThreshold, drawOrder = mix < from.drawOrderThreshold; - float animationLast = from.animationLast, animationTime = from.AnimationTime; - var timelines = from.animation.timelines; - var timelinesItems = timelines.Items; - int timelineCount = timelines.Count; - var timelinesFirst = from.timelinesFirst; - var timelinesFirstItems = timelinesFirst.Items; - float alpha = from.alpha * entry.mixAlpha * (1 - mix); - - bool firstFrame = entry.timelinesRotation.Count == 0; - if (firstFrame) entry.timelinesRotation.EnsureCapacity(timelines.Count << 1); - var timelinesRotation = entry.timelinesRotation.Items; - - for (int i = 0; i < timelineCount; i++) { - Timeline timeline = timelinesItems[i]; - bool setupPose = timelinesFirstItems[i]; - var rotateTimeline = timeline as RotateTimeline; - if (rotateTimeline != null) { - ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, alpha, setupPose, timelinesRotation, i << 1, firstFrame); - } else { - if (!setupPose) { - if (!attachments && timeline is AttachmentTimeline) continue; - if (!drawOrder && timeline is DrawOrderTimeline) continue; - } - timeline.Apply(skeleton, animationLast, animationTime, eventBuffer, alpha, setupPose, true); - } - } - - if (entry.mixDuration > 0 ) QueueEvents(from, animationTime); - events.Clear(false); - from.nextAnimationLast = animationTime; - from.nextTrackLast = from.trackTime; - - return mix; - } - - static private void ApplyRotateTimeline (RotateTimeline rotateTimeline, Skeleton skeleton, float time, float alpha, bool setupPose, - float[] timelinesRotation, int i, bool firstFrame) { - - if (firstFrame) timelinesRotation[i] = 0; - - if (alpha == 1) { - rotateTimeline.Apply(skeleton, 0, time, null, 1, setupPose, false); - return; - } - - Bone bone = skeleton.bones.Items[rotateTimeline.boneIndex]; - float[] frames = rotateTimeline.frames; - if (time < frames[0]) { - if (setupPose) bone.rotation = bone.data.rotation; - return; - } - - float r2; - if (time >= frames[frames.Length - RotateTimeline.ENTRIES]) // Time is after last frame. - r2 = bone.data.rotation + frames[frames.Length + RotateTimeline.PREV_ROTATION]; - else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, RotateTimeline.ENTRIES); - float prevRotation = frames[frame + RotateTimeline.PREV_ROTATION]; - float frameTime = frames[frame]; - float percent = rotateTimeline.GetCurvePercent((frame >> 1) - 1, - 1 - (time - frameTime) / (frames[frame + RotateTimeline.PREV_TIME] - frameTime)); - - r2 = frames[frame + RotateTimeline.ROTATION] - prevRotation; - r2 -= (16384 - (int)(16384.499999999996 - r2 / 360)) * 360; - r2 = prevRotation + r2 * percent + bone.data.rotation; - r2 -= (16384 - (int)(16384.499999999996 - r2 / 360)) * 360; - } - - // Mix between rotations using the direction of the shortest route on the first frame while detecting crosses. - float r1 = setupPose ? bone.data.rotation : bone.rotation; - float total, diff = r2 - r1; - if (diff == 0) { - total = timelinesRotation[i]; - } else { - diff -= (16384 - (int)(16384.499999999996 - diff / 360)) * 360; - float lastTotal, lastDiff; - if (firstFrame) { - lastTotal = 0; - lastDiff = diff; - } else { - lastTotal = timelinesRotation[i]; // Angle and direction of mix, including loops. - lastDiff = timelinesRotation[i + 1]; // Difference between bones. - } - bool current = diff > 0, dir = lastTotal >= 0; - // Detect cross at 0 (not 180). - if (Math.Sign(lastDiff) != Math.Sign(diff) && Math.Abs(lastDiff) <= 90) { - // A cross after a 360 rotation is a loop. - if (Math.Abs(lastTotal) > 180) lastTotal += 360 * Math.Sign(lastTotal); - dir = current; - } - total = diff + lastTotal - lastTotal % 360; // Store loops as part of lastTotal. - if (dir != current) total += 360 * Math.Sign(lastTotal); - timelinesRotation[i] = total; - } - timelinesRotation[i + 1] = diff; - r1 += total * alpha; - bone.rotation = r1 - (16384 - (int)(16384.499999999996 - r1 / 360)) * 360; - } - - private void QueueEvents (TrackEntry entry, float animationTime) { - float animationStart = entry.animationStart, animationEnd = entry.animationEnd; - float duration = animationEnd - animationStart; - float trackLastWrapped = entry.trackLast % duration; - - // Queue events before complete. - var events = this.events; - var eventsItems = events.Items; - int i = 0, n = events.Count; - for (; i < n; i++) { - var e = eventsItems[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. - if (entry.loop ? (trackLastWrapped > entry.trackTime % duration) - : (animationTime >= animationEnd && entry.animationLast < animationEnd)) { - queue.Complete(entry); - } - - // Queue events after complete. - for (; i < n; i++) { - Event e = eventsItems[i]; - if (e.time < animationStart) continue; // Discard events outside animation start/end. - queue.Event(entry, eventsItems[i]); - } - } - - /// - /// Removes all animations from all tracks, leaving skeletons in their previous pose. - /// It may be desired to use to mix the skeletons back to the setup pose, - /// rather than leaving them in their previous pose. - public void ClearTracks () { - bool oldDrainDisabled = queue.drainDisabled; - queue.drainDisabled = true; - for (int i = 0, n = tracks.Count; i < n; i++) { - ClearTrack(i); - } - tracks.Clear(); - queue.drainDisabled = oldDrainDisabled; - queue.Drain(); - } - - /// - /// Removes all animations from the tracks, leaving skeletons in their previous pose. - /// It may be desired to use to mix the skeletons back to the setup pose, - /// rather than leaving them in their previous pose. - public void ClearTrack (int trackIndex) { - if (trackIndex >= tracks.Count) return; - TrackEntry current = tracks.Items[trackIndex]; - if (current == null) return; - - queue.End(current); - - DisposeNext(current); - - TrackEntry entry = current; - while (true) { - TrackEntry from = entry.mixingFrom; - if (from == null) break; - queue.End(from); - entry.mixingFrom = null; - entry = from; - } - - tracks.Items[current.trackIndex] = null; - - queue.Drain(); - } - - private void SetCurrent (int index, TrackEntry current, bool interrupt) { - TrackEntry from = ExpandToIndex(index); - tracks.Items[index] = current; - - if (from != null) { - if (interrupt) queue.Interrupt(from); - current.mixingFrom = from; - current.mixTime = 0; - - from.timelinesRotation.Clear(); - - // If not completely mixed in, set mixAlpha so mixing out happens from current mix to zero. - if (from.mixingFrom != null && from.mixDuration > 0) current.mixAlpha *= Math.Min(from.mixTime / from.mixDuration, 1); - } - - queue.Start(current); - } - - - /// Sets an animation by name. - public TrackEntry SetAnimation (int trackIndex, String animationName, bool loop) { - Animation animation = data.skeletonData.FindAnimation(animationName); - if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName"); - return SetAnimation(trackIndex, animation, loop); - } - - /// Sets the current animation for a track, discarding any queued animations. - /// 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 determines when the track is cleared. - /// - /// A track entry to allow further customization of animation playback. References to the track entry must not be kept - /// after . - public TrackEntry SetAnimation (int trackIndex, Animation animation, bool loop) { - if (animation == null) throw new ArgumentNullException("animation", "animation cannot be 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.Items[trackIndex] = current.mixingFrom; - queue.Interrupt(current); - queue.End(current); - DisposeNext(current); - current = current.mixingFrom; - interrupt = false; - } else { - DisposeNext(current); - } - } - TrackEntry entry = NewTrackEntry(trackIndex, animation, loop, current); - SetCurrent(trackIndex, entry, interrupt); - queue.Drain(); - return entry; - } - - /// Queues an animation by name. - /// - public TrackEntry AddAnimation (int trackIndex, String animationName, bool loop, float delay) { - Animation animation = data.skeletonData.FindAnimation(animationName); - if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName"); - return AddAnimation(trackIndex, animation, loop, 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 . - /// - /// 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. - /// - /// A track entry to allow further customization of animation playback. References to the track entry must not be kept - /// after - public TrackEntry AddAnimation (int trackIndex, Animation animation, bool loop, float delay) { - if (animation == null) throw new ArgumentNullException("animation", "animation cannot be 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(); - } else { - last.next = entry; - if (delay <= 0) { - float duration = last.animationEnd - last.animationStart; - if (duration != 0) - delay += duration * (1 + (int)(last.trackTime / duration)) - data.GetMix(last.animation, animation); - else - delay = 0; - } - } - - entry.delay = delay; - return entry; - } - - /// - /// Sets an empty animation for a track, discarding any queued animations, and mixes to it over the specified mix duration. - public TrackEntry SetEmptyAnimation (int trackIndex, float mixDuration) { - TrackEntry entry = SetAnimation(trackIndex, AnimationState.EmptyAnimation, false); - entry.mixDuration = mixDuration; - entry.trackEnd = mixDuration; - return entry; - } - - /// - /// 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. - /// - /// A track entry to allow further customization of animation playback. References to the track entry must not be kept after . - /// - /// Track number. - /// Mix duration. - /// 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. - public TrackEntry AddEmptyAnimation (int trackIndex, float mixDuration, float delay) { - if (delay <= 0) delay -= mixDuration; - TrackEntry entry = AddAnimation(trackIndex, AnimationState.EmptyAnimation, false, delay); - entry.mixDuration = mixDuration; - entry.trackEnd = mixDuration; - return entry; - } - - /// - /// Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix duration. - public void SetEmptyAnimations (float mixDuration) { - bool oldDrainDisabled = queue.drainDisabled; - queue.drainDisabled = true; - for (int i = 0, n = tracks.Count; i < n; i++) { - TrackEntry current = tracks.Items[i]; - if (current != null) SetEmptyAnimation(i, mixDuration); - } - queue.drainDisabled = oldDrainDisabled; - queue.Drain(); - } - - private TrackEntry ExpandToIndex (int index) { - if (index < tracks.Count) return tracks.Items[index]; - while (index >= tracks.Count) - tracks.Add(null); - return null; - } - - /// May be null. - private TrackEntry NewTrackEntry (int trackIndex, Animation animation, bool loop, TrackEntry last) { - TrackEntry entry = trackEntryPool.Obtain(); // Pooling - entry.trackIndex = trackIndex; - entry.animation = animation; - entry.loop = loop; - - entry.eventThreshold = 0; - entry.attachmentThreshold = 0; - entry.drawOrderThreshold = 0; - - entry.animationStart = 0; - entry.animationEnd = animation.Duration; - entry.animationLast = -1; - entry.nextAnimationLast = -1; - - entry.delay = 0; - entry.trackTime = 0; - entry.trackLast = -1; - entry.nextTrackLast = -1; - entry.trackEnd = float.MaxValue; // loop ? float.MaxValue : animation.Duration; - entry.timeScale = 1; - - entry.alpha = 1; - entry.mixAlpha = 1; - entry.mixTime = 0; - entry.mixDuration = (last == null) ? 0 : data.GetMix(last.animation, animation); - return entry; - } - - private void DisposeNext (TrackEntry entry) { - TrackEntry next = entry.next; - while (next != null) { - queue.Dispose(next); - next = next.next; - } - entry.next = null; - } - - private void AnimationsChanged () { - animationsChanged = false; - - var propertyIDs = this.propertyIDs; - - // Set timelinesFirst for all entries, from lowest track to highest. - int i = 0, n = tracks.Count; - propertyIDs.Clear(); - for (; i < n; i++) { // Find first non-null entry. - TrackEntry entry = tracks.Items[i]; - if (entry == null) continue; - SetTimelinesFirst(entry); - i++; - break; - } - for (; i < n; i++) { // Rest of entries. - TrackEntry entry = tracks.Items[i]; - if (entry != null) CheckTimelinesFirst(entry); - } - } - - /// From last to first mixingFrom entries, sets timelinesFirst to true on last, calls checkTimelineUsage on rest. - private void SetTimelinesFirst (TrackEntry entry) { - if (entry.mixingFrom != null) { - SetTimelinesFirst(entry.mixingFrom); - CheckTimelinesUsage(entry); - return; - } - var propertyIDs = this.propertyIDs; - var timelines = entry.animation.timelines; - int n = timelines.Count; - entry.timelinesFirst.EnsureCapacity(n); // entry.timelinesFirst.setSize(n); - var usage = entry.timelinesFirst.Items; - var timelinesItems = timelines.Items; - for (int i = 0; i < n; i++) { - propertyIDs.Add(timelinesItems[i].PropertyId); - usage[i] = true; - } - } - - /// From last to first mixingFrom entries, calls checkTimelineUsage. - private void CheckTimelinesFirst (TrackEntry entry) { - if (entry.mixingFrom != null) CheckTimelinesFirst(entry.mixingFrom); - CheckTimelinesUsage(entry); - } - - private void CheckTimelinesUsage (TrackEntry entry) { - var propertyIDs = this.propertyIDs; - var timelines = entry.animation.timelines; - int n = timelines.Count; - var usageArray = entry.timelinesFirst; - usageArray.EnsureCapacity(n); - var usage = usageArray.Items; - var timelinesItems = timelines.Items; - for (int i = 0; i < n; i++) - usage[i] = propertyIDs.Add(timelinesItems[i].PropertyId); - } - - /// The track entry for the animation currently playing on the track, or null if no animation is currently playing. - public TrackEntry GetCurrent (int trackIndex) { - return (trackIndex >= tracks.Count) ? null : tracks.Items[trackIndex]; - } - - override public String ToString () { - var buffer = new System.Text.StringBuilder(); - for (int i = 0, n = tracks.Count; i < n; i++) { - TrackEntry entry = tracks.Items[i]; - if (entry == null) continue; - if (buffer.Length > 0) buffer.Append(", "); - buffer.Append(entry.ToString()); - } - return buffer.Length == 0 ? "" : buffer.ToString(); - } - - internal void OnStart (TrackEntry entry) { if (Start != null) Start(entry); } - internal void OnInterrupt (TrackEntry entry) { if (Interrupt != null) Interrupt(entry); } - internal void OnEnd (TrackEntry entry) { if (End != null) End(entry); } - internal void OnDispose (TrackEntry entry) { if (Dispose != null) Dispose(entry); } - internal void OnComplete (TrackEntry entry) { if (Complete != null) Complete(entry); } - internal void OnEvent (TrackEntry entry, Event e) { if (Event != null) Event(entry, e); } - } - - /// State for the playback of an animation. - public class TrackEntry : Pool.IPoolable { - internal Animation animation; - - internal TrackEntry next, mixingFrom; - internal int trackIndex; - - internal bool loop; - internal float eventThreshold, attachmentThreshold, drawOrderThreshold; - internal float animationStart, animationEnd, animationLast, nextAnimationLast; - internal float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale = 1f; - internal float alpha, mixTime, mixDuration, mixAlpha; - internal readonly ExposedList timelinesFirst = new ExposedList(); - internal readonly ExposedList timelinesRotation = new ExposedList(); - - // IPoolable.Reset() - public void Reset () { - next = null; - mixingFrom = null; - animation = null; - timelinesFirst.Clear(); - timelinesRotation.Clear(); - - Start = null; - Interrupt = null; - End = null; - Dispose = null; - Complete = null; - Event = null; - } - - /// The index of the track where this entry is either current or queued. - public int TrackIndex { get { return trackIndex; } } - - /// The animation to apply for this track entry. - public Animation Animation { get { return animation; } } - - /// - /// If true, the animation will repeat. If false, it will not, instead its last frame is applied if played beyond its duration. - public bool Loop { get { return loop; } set { loop = value; } } - - /// - /// 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. - public float Delay { get { return delay; } set { delay = value; } } - - /// - /// Current time in seconds this track entry has been the current track entry. The track time determines - /// . The track time can be set to start the animation at a time other than 0, without affecting looping. - public float TrackTime { get { return trackTime; } set { trackTime = value; } } - - /// - /// 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 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 to mix the properties back to the - /// setup pose over time, rather than have it happen instantly. - /// - public float TrackEnd { get { return trackEnd; } set { trackEnd = value; } } - - /// - /// 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 to the same value to - /// prevent timeline keys before the start time from triggering. - /// - public float AnimationStart { get { return animationStart; } set { animationStart = value; } } - - /// - /// Seconds for the last frame of this animation. Non-looping animations won't play past this time. Looping animations will - /// loop back to at this time. Defaults to the animation duration. - public float AnimationEnd { get { return animationEnd; } } - - /// - /// 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. - public float AnimationLast { - get { return animationLast; } - set { - animationLast = value; - nextAnimationLast = value; - } - } - - /// - /// Uses to compute the animation time between . and - /// . When the track time is 0, the animation time is equal to the animation start time. - /// - public float AnimationTime { - get { - if (loop) { - float duration = animationEnd - animationStart; - if (duration == 0) return animationStart; - return (trackTime % duration) + animationStart; - } - return Math.Min(trackTime + animationStart, animationEnd); - } - } - - /// - /// Multiplier for the delta time when the animation state is updated, causing time for this animation to play slower or - /// faster. Defaults to 1. - /// - public float TimeScale { get { return timeScale; } set { timeScale = value; } } - - /// - /// 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. - /// - public float Alpha { get { return alpha; } set { alpha = value; } } - - /// - /// 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. - public float EventThreshold { get { return eventThreshold; } set { eventThreshold = value; } } - - /// - /// 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. - public float AttachmentThreshold { get { return attachmentThreshold; } set { attachmentThreshold = value; } } - - /// - /// 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. - /// - public float DrawOrderThreshold { get { return drawOrderThreshold; } set { drawOrderThreshold = value; } } - - /// - /// The animation queued to start after this animation, or null. - public TrackEntry Next { get { return next; } } - - /// - /// Returns true if at least one loop has been completed. - public bool IsComplete { - get { return trackTime >= animationEnd - animationStart; } - } - - /// - /// Seconds from 0 to the mix duration when mixing from the previous animation to this animation. May be slightly more than - /// . - public float MixTime { get { return mixTime; } set { mixTime = value; } } - - /// - /// Seconds for mixing from the previous animation to this animation. Defaults to the value provided by - /// based on the animation before this animation (if any). - /// - /// The mix duration must be set before is next called. - /// - public float MixDuration { get { return mixDuration; } set { mixDuration = value; } } - - /// - /// The track entry for the previous animation when mixing from the previous animation to this animation, or null if no - /// mixing is currently occuring. - public TrackEntry MixingFrom { get { return mixingFrom; } } - - public event AnimationState.TrackEntryDelegate Start, Interrupt, End, Dispose, Complete; - public event AnimationState.TrackEntryEventDelegate Event; - internal void OnStart () { if (Start != null) Start(this); } - internal void OnInterrupt () { if (Interrupt != null) Interrupt(this); } - internal void OnEnd () { if (End != null) End(this); } - internal void OnDispose () { if (Dispose != null) Dispose(this); } - internal void OnComplete () { if (Complete != null) Complete(this); } - internal void OnEvent (Event e) { if (Event != null) Event(this, e); } - - /// - /// 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 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. - public void ResetRotationDirections () { - timelinesRotation.Clear(); - } - - override public String ToString () { - return animation == null ? "" : animation.name; - } - } - - class EventQueue { - private readonly List eventQueueEntries = new List(); - public bool drainDisabled; - - private readonly AnimationState state; - private readonly Pool trackEntryPool; - public event Action AnimationsChanged; - - public EventQueue (AnimationState state, Action HandleAnimationsChanged, Pool trackEntryPool) { - this.state = state; - this.AnimationsChanged += HandleAnimationsChanged; - this.trackEntryPool = trackEntryPool; - } - - struct EventQueueEntry { - public EventType type; - public TrackEntry entry; - public Event e; - - public EventQueueEntry (EventType eventType, TrackEntry trackEntry, Event e = null) { - this.type = eventType; - this.entry = trackEntry; - this.e = e; - } - } - - enum EventType { - Start, Interrupt, End, Dispose, Complete, Event - } - - public void Start (TrackEntry entry) { - eventQueueEntries.Add(new EventQueueEntry(EventType.Start, entry)); - if (AnimationsChanged != null) AnimationsChanged(); - } - - public void Interrupt (TrackEntry entry) { - eventQueueEntries.Add(new EventQueueEntry(EventType.Interrupt, entry)); - } - - public void End (TrackEntry entry) { - eventQueueEntries.Add(new EventQueueEntry(EventType.End, entry)); - if (AnimationsChanged != null) AnimationsChanged(); - } - - public void Dispose (TrackEntry entry) { - eventQueueEntries.Add(new EventQueueEntry(EventType.Dispose, entry)); - } - - public void Complete (TrackEntry entry) { - eventQueueEntries.Add(new EventQueueEntry(EventType.Complete, entry)); - } - - public void Event (TrackEntry entry, Event e) { - eventQueueEntries.Add(new EventQueueEntry(EventType.Event, entry, e)); - } - - public void Drain () { - if (drainDisabled) return; - drainDisabled = true; - - var entries = this.eventQueueEntries; - AnimationState state = this.state; - - // Don't cache entries.Count so callbacks can queue their own events (eg, call SetAnimation in AnimationState_Complete). - for (int i = 0; i < entries.Count; i++) { - var queueEntry = entries[i]; - TrackEntry trackEntry = queueEntry.entry; - - switch (queueEntry.type) { - case EventType.Start: - trackEntry.OnStart(); - state.OnStart(trackEntry); - break; - case EventType.Interrupt: - trackEntry.OnInterrupt(); - state.OnInterrupt(trackEntry); - break; - case EventType.End: - trackEntry.OnEnd(); - state.OnEnd(trackEntry); - goto case EventType.Dispose; // Fall through. (C#) - case EventType.Dispose: - trackEntry.OnDispose(); - state.OnDispose(trackEntry); - trackEntryPool.Free(trackEntry); // Pooling - break; - case EventType.Complete: - trackEntry.OnComplete(); - state.OnComplete(trackEntry); - break; - case EventType.Event: - trackEntry.OnEvent(queueEntry.e); - state.OnEvent(trackEntry, queueEntry.e); - break; - } - } - eventQueueEntries.Clear(); - - drainDisabled = false; - } - - public void Clear () { - eventQueueEntries.Clear(); - } - } - - public class Pool where T : class, new() { - public readonly int max; - readonly Stack freeObjects; - - public int Count { get { return freeObjects.Count; } } - public int Peak { get; private set; } - - public Pool (int initialCapacity = 16, int max = int.MaxValue) { - freeObjects = new Stack(initialCapacity); - this.max = max; - } - - public T Obtain () { - return freeObjects.Count == 0 ? new T() : freeObjects.Pop(); - } - - public void Free (T obj) { - if (obj == null) throw new ArgumentNullException("obj", "obj cannot be null"); - if (freeObjects.Count < max) { - freeObjects.Push(obj); - Peak = Math.Max(Peak, freeObjects.Count); - } - Reset(obj); - } - -// protected void FreeAll (List objects) { -// if (objects == null) throw new ArgumentNullException("objects", "objects cannot be null."); -// var freeObjects = this.freeObjects; -// int max = this.max; -// for (int i = 0; i < objects.Count; i++) { -// T obj = objects[i]; -// if (obj == null) continue; -// if (freeObjects.Count < max) freeObjects.Push(obj); -// Reset(obj); -// } -// Peak = Math.Max(Peak, freeObjects.Count); -// } - - public void Clear () { - freeObjects.Clear(); - } - - protected void Reset (T obj) { - var poolable = obj as IPoolable; - if (poolable != null) poolable.Reset(); - } - - public interface IPoolable { - void Reset (); - } - } +namespace Spine3_5_51 +{ + public class AnimationState + { + static readonly Animation EmptyAnimation = new Animation("", new ExposedList(), 0); + + private AnimationStateData data; + private readonly ExposedList tracks = new ExposedList(); + private readonly HashSet propertyIDs = new HashSet(); + private readonly ExposedList events = new ExposedList(); + private readonly EventQueue queue; + private bool animationsChanged; + private float timeScale = 1; + + Pool trackEntryPool = new Pool(); + + public AnimationStateData Data { get { return data; } } + /// A list of tracks that have animations, which may contain nulls. + public ExposedList Tracks { get { return tracks; } } + public float TimeScale { get { return timeScale; } set { timeScale = value; } } + + public delegate void TrackEntryDelegate(TrackEntry trackEntry); + public event TrackEntryDelegate Start, Interrupt, End, Dispose, Complete; + + public delegate void TrackEntryEventDelegate(TrackEntry trackEntry, Event e); + public event TrackEntryEventDelegate Event; + + public AnimationState(AnimationStateData data) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + this.data = data; + this.queue = new EventQueue(this, HandleAnimationsChanged, trackEntryPool); + } + + void HandleAnimationsChanged() + { + this.animationsChanged = true; + } + + /// + /// Increments the track entry times, setting queued animations as current if needed + /// delta time + public void Update(float delta) + { + delta *= timeScale; + var tracksItems = tracks.Items; + for (int i = 0, n = tracks.Count; i < n; i++) + { + TrackEntry current = tracksItems[i]; + if (current == null) continue; + + 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 = nextTime + (delta * next.timeScale); + current.trackTime += currentDelta; + SetCurrent(i, next, true); + while (next.mixingFrom != null) + { + next.mixTime += currentDelta; + next = next.mixingFrom; + } + continue; + } + } + else + { + // Clear the track when there is no next entry, the track end time is reached, and there is no mixingFrom. + if (current.trackLast >= current.trackEnd && current.mixingFrom == null) + { + tracksItems[i] = null; + queue.End(current); + DisposeNext(current); + continue; + } + } + UpdateMixingFrom(current, delta); + + current.trackTime += currentDelta; + } + + queue.Drain(); + } + + private void UpdateMixingFrom(TrackEntry entry, float delta) + { + TrackEntry from = entry.mixingFrom; + if (from == null) return; + + UpdateMixingFrom(from, delta); + + if (entry.mixTime >= entry.mixDuration && from.mixingFrom == null && entry.mixTime > 0) + { + entry.mixingFrom = null; + queue.End(from); + return; + } + + from.animationLast = from.nextAnimationLast; + from.trackLast = from.nextTrackLast; + from.trackTime += delta * from.timeScale; + entry.mixTime += delta * entry.timeScale; + } + + + /// + /// 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. + public void Apply(Skeleton skeleton) + { + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + if (animationsChanged) AnimationsChanged(); + + var events = this.events; + + var tracksItems = tracks.Items; + for (int i = 0, m = tracks.Count; i < m; i++) + { + TrackEntry current = tracksItems[i]; + if (current == null || current.delay > 0) continue; + + // Apply mixing from entries first. + float mix = current.alpha; + if (current.mixingFrom != null) + mix *= ApplyMixingFrom(current, skeleton); + else if (current.trackTime >= current.trackEnd) // + mix = 0; // Set to setup pose the last time the entry will be applied. + + // Apply current entry. + float animationLast = current.animationLast, animationTime = current.AnimationTime; + int timelineCount = current.animation.timelines.Count; + var timelines = current.animation.timelines; + var timelinesItems = timelines.Items; + if (mix == 1) + { + for (int ii = 0; ii < timelineCount; ii++) + timelinesItems[ii].Apply(skeleton, animationLast, animationTime, events, 1, true, false); + } + else + { + bool firstFrame = current.timelinesRotation.Count == 0; + if (firstFrame) current.timelinesRotation.EnsureCapacity(timelines.Count << 1); + var timelinesRotation = current.timelinesRotation.Items; + + var timelinesFirstItems = current.timelinesFirst.Items; + for (int ii = 0; ii < timelineCount; ii++) + { + Timeline timeline = timelinesItems[ii]; + var rotateTimeline = timeline as RotateTimeline; + if (rotateTimeline != null) + { + ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, mix, timelinesFirstItems[ii], timelinesRotation, ii << 1, + firstFrame); + } + else + { + timeline.Apply(skeleton, animationLast, animationTime, events, mix, timelinesFirstItems[ii], false); + } + } + } + QueueEvents(current, animationTime); + events.Clear(false); + current.nextAnimationLast = animationTime; + current.nextTrackLast = current.trackTime; + } + + queue.Drain(); + } + + private float ApplyMixingFrom(TrackEntry entry, Skeleton skeleton) + { + TrackEntry from = entry.mixingFrom; + if (from.mixingFrom != null) ApplyMixingFrom(from, skeleton); + + float mix; + if (entry.mixDuration == 0) // Single frame mix to undo mixingFrom changes. + mix = 1; + else + { + mix = entry.mixTime / entry.mixDuration; + if (mix > 1) mix = 1; + } + + var eventBuffer = mix < from.eventThreshold ? this.events : null; + bool attachments = mix < from.attachmentThreshold, drawOrder = mix < from.drawOrderThreshold; + float animationLast = from.animationLast, animationTime = from.AnimationTime; + var timelines = from.animation.timelines; + var timelinesItems = timelines.Items; + int timelineCount = timelines.Count; + var timelinesFirst = from.timelinesFirst; + var timelinesFirstItems = timelinesFirst.Items; + float alpha = from.alpha * entry.mixAlpha * (1 - mix); + + bool firstFrame = entry.timelinesRotation.Count == 0; + if (firstFrame) entry.timelinesRotation.EnsureCapacity(timelines.Count << 1); + var timelinesRotation = entry.timelinesRotation.Items; + + for (int i = 0; i < timelineCount; i++) + { + Timeline timeline = timelinesItems[i]; + bool setupPose = timelinesFirstItems[i]; + var rotateTimeline = timeline as RotateTimeline; + if (rotateTimeline != null) + { + ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, alpha, setupPose, timelinesRotation, i << 1, firstFrame); + } + else + { + if (!setupPose) + { + if (!attachments && timeline is AttachmentTimeline) continue; + if (!drawOrder && timeline is DrawOrderTimeline) continue; + } + timeline.Apply(skeleton, animationLast, animationTime, eventBuffer, alpha, setupPose, true); + } + } + + if (entry.mixDuration > 0) QueueEvents(from, animationTime); + events.Clear(false); + from.nextAnimationLast = animationTime; + from.nextTrackLast = from.trackTime; + + return mix; + } + + static private void ApplyRotateTimeline(RotateTimeline rotateTimeline, Skeleton skeleton, float time, float alpha, bool setupPose, + float[] timelinesRotation, int i, bool firstFrame) + { + + if (firstFrame) timelinesRotation[i] = 0; + + if (alpha == 1) + { + rotateTimeline.Apply(skeleton, 0, time, null, 1, setupPose, false); + return; + } + + Bone bone = skeleton.bones.Items[rotateTimeline.boneIndex]; + float[] frames = rotateTimeline.frames; + if (time < frames[0]) + { + if (setupPose) bone.rotation = bone.data.rotation; + return; + } + + float r2; + if (time >= frames[frames.Length - RotateTimeline.ENTRIES]) // Time is after last frame. + r2 = bone.data.rotation + frames[frames.Length + RotateTimeline.PREV_ROTATION]; + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, RotateTimeline.ENTRIES); + float prevRotation = frames[frame + RotateTimeline.PREV_ROTATION]; + float frameTime = frames[frame]; + float percent = rotateTimeline.GetCurvePercent((frame >> 1) - 1, + 1 - (time - frameTime) / (frames[frame + RotateTimeline.PREV_TIME] - frameTime)); + + r2 = frames[frame + RotateTimeline.ROTATION] - prevRotation; + r2 -= (16384 - (int)(16384.499999999996 - r2 / 360)) * 360; + r2 = prevRotation + r2 * percent + bone.data.rotation; + r2 -= (16384 - (int)(16384.499999999996 - r2 / 360)) * 360; + } + + // Mix between rotations using the direction of the shortest route on the first frame while detecting crosses. + float r1 = setupPose ? bone.data.rotation : bone.rotation; + float total, diff = r2 - r1; + if (diff == 0) + { + total = timelinesRotation[i]; + } + else + { + diff -= (16384 - (int)(16384.499999999996 - diff / 360)) * 360; + float lastTotal, lastDiff; + if (firstFrame) + { + lastTotal = 0; + lastDiff = diff; + } + else + { + lastTotal = timelinesRotation[i]; // Angle and direction of mix, including loops. + lastDiff = timelinesRotation[i + 1]; // Difference between bones. + } + bool current = diff > 0, dir = lastTotal >= 0; + // Detect cross at 0 (not 180). + if (Math.Sign(lastDiff) != Math.Sign(diff) && Math.Abs(lastDiff) <= 90) + { + // A cross after a 360 rotation is a loop. + if (Math.Abs(lastTotal) > 180) lastTotal += 360 * Math.Sign(lastTotal); + dir = current; + } + total = diff + lastTotal - lastTotal % 360; // Store loops as part of lastTotal. + if (dir != current) total += 360 * Math.Sign(lastTotal); + timelinesRotation[i] = total; + } + timelinesRotation[i + 1] = diff; + r1 += total * alpha; + bone.rotation = r1 - (16384 - (int)(16384.499999999996 - r1 / 360)) * 360; + } + + private void QueueEvents(TrackEntry entry, float animationTime) + { + float animationStart = entry.animationStart, animationEnd = entry.animationEnd; + float duration = animationEnd - animationStart; + float trackLastWrapped = entry.trackLast % duration; + + // Queue events before complete. + var events = this.events; + var eventsItems = events.Items; + int i = 0, n = events.Count; + for (; i < n; i++) + { + var e = eventsItems[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. + if (entry.loop ? (trackLastWrapped > entry.trackTime % duration) + : (animationTime >= animationEnd && entry.animationLast < animationEnd)) + { + queue.Complete(entry); + } + + // Queue events after complete. + for (; i < n; i++) + { + Event e = eventsItems[i]; + if (e.time < animationStart) continue; // Discard events outside animation start/end. + queue.Event(entry, eventsItems[i]); + } + } + + /// + /// Removes all animations from all tracks, leaving skeletons in their previous pose. + /// It may be desired to use to mix the skeletons back to the setup pose, + /// rather than leaving them in their previous pose. + public void ClearTracks() + { + bool oldDrainDisabled = queue.drainDisabled; + queue.drainDisabled = true; + for (int i = 0, n = tracks.Count; i < n; i++) + { + ClearTrack(i); + } + tracks.Clear(); + queue.drainDisabled = oldDrainDisabled; + queue.Drain(); + } + + /// + /// Removes all animations from the tracks, leaving skeletons in their previous pose. + /// It may be desired to use to mix the skeletons back to the setup pose, + /// rather than leaving them in their previous pose. + public void ClearTrack(int trackIndex) + { + if (trackIndex >= tracks.Count) return; + TrackEntry current = tracks.Items[trackIndex]; + if (current == null) return; + + queue.End(current); + + DisposeNext(current); + + TrackEntry entry = current; + while (true) + { + TrackEntry from = entry.mixingFrom; + if (from == null) break; + queue.End(from); + entry.mixingFrom = null; + entry = from; + } + + tracks.Items[current.trackIndex] = null; + + queue.Drain(); + } + + private void SetCurrent(int index, TrackEntry current, bool interrupt) + { + TrackEntry from = ExpandToIndex(index); + tracks.Items[index] = current; + + if (from != null) + { + if (interrupt) queue.Interrupt(from); + current.mixingFrom = from; + current.mixTime = 0; + + from.timelinesRotation.Clear(); + + // If not completely mixed in, set mixAlpha so mixing out happens from current mix to zero. + if (from.mixingFrom != null && from.mixDuration > 0) current.mixAlpha *= Math.Min(from.mixTime / from.mixDuration, 1); + } + + queue.Start(current); + } + + + /// Sets an animation by name. + public TrackEntry SetAnimation(int trackIndex, String animationName, bool loop) + { + Animation animation = data.skeletonData.FindAnimation(animationName); + if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName"); + return SetAnimation(trackIndex, animation, loop); + } + + /// Sets the current animation for a track, discarding any queued animations. + /// 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 determines when the track is cleared. + /// + /// A track entry to allow further customization of animation playback. References to the track entry must not be kept + /// after . + public TrackEntry SetAnimation(int trackIndex, Animation animation, bool loop) + { + if (animation == null) throw new ArgumentNullException("animation", "animation cannot be 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.Items[trackIndex] = current.mixingFrom; + queue.Interrupt(current); + queue.End(current); + DisposeNext(current); + current = current.mixingFrom; + interrupt = false; + } + else + { + DisposeNext(current); + } + } + TrackEntry entry = NewTrackEntry(trackIndex, animation, loop, current); + SetCurrent(trackIndex, entry, interrupt); + queue.Drain(); + return entry; + } + + /// Queues an animation by name. + /// + public TrackEntry AddAnimation(int trackIndex, String animationName, bool loop, float delay) + { + Animation animation = data.skeletonData.FindAnimation(animationName); + if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName"); + return AddAnimation(trackIndex, animation, loop, 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 . + /// + /// 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. + /// + /// A track entry to allow further customization of animation playback. References to the track entry must not be kept + /// after + public TrackEntry AddAnimation(int trackIndex, Animation animation, bool loop, float delay) + { + if (animation == null) throw new ArgumentNullException("animation", "animation cannot be 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(); + } + else + { + last.next = entry; + if (delay <= 0) + { + float duration = last.animationEnd - last.animationStart; + if (duration != 0) + delay += duration * (1 + (int)(last.trackTime / duration)) - data.GetMix(last.animation, animation); + else + delay = 0; + } + } + + entry.delay = delay; + return entry; + } + + /// + /// Sets an empty animation for a track, discarding any queued animations, and mixes to it over the specified mix duration. + public TrackEntry SetEmptyAnimation(int trackIndex, float mixDuration) + { + TrackEntry entry = SetAnimation(trackIndex, AnimationState.EmptyAnimation, false); + entry.mixDuration = mixDuration; + entry.trackEnd = mixDuration; + return entry; + } + + /// + /// 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. + /// + /// A track entry to allow further customization of animation playback. References to the track entry must not be kept after . + /// + /// Track number. + /// Mix duration. + /// 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. + public TrackEntry AddEmptyAnimation(int trackIndex, float mixDuration, float delay) + { + if (delay <= 0) delay -= mixDuration; + TrackEntry entry = AddAnimation(trackIndex, AnimationState.EmptyAnimation, false, delay); + entry.mixDuration = mixDuration; + entry.trackEnd = mixDuration; + return entry; + } + + /// + /// Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix duration. + public void SetEmptyAnimations(float mixDuration) + { + bool oldDrainDisabled = queue.drainDisabled; + queue.drainDisabled = true; + for (int i = 0, n = tracks.Count; i < n; i++) + { + TrackEntry current = tracks.Items[i]; + if (current != null) SetEmptyAnimation(i, mixDuration); + } + queue.drainDisabled = oldDrainDisabled; + queue.Drain(); + } + + private TrackEntry ExpandToIndex(int index) + { + if (index < tracks.Count) return tracks.Items[index]; + while (index >= tracks.Count) + tracks.Add(null); + return null; + } + + /// May be null. + private TrackEntry NewTrackEntry(int trackIndex, Animation animation, bool loop, TrackEntry last) + { + TrackEntry entry = trackEntryPool.Obtain(); // Pooling + entry.trackIndex = trackIndex; + entry.animation = animation; + entry.loop = loop; + + entry.eventThreshold = 0; + entry.attachmentThreshold = 0; + entry.drawOrderThreshold = 0; + + entry.animationStart = 0; + entry.animationEnd = animation.Duration; + entry.animationLast = -1; + entry.nextAnimationLast = -1; + + entry.delay = 0; + entry.trackTime = 0; + entry.trackLast = -1; + entry.nextTrackLast = -1; + entry.trackEnd = float.MaxValue; // loop ? float.MaxValue : animation.Duration; + entry.timeScale = 1; + + entry.alpha = 1; + entry.mixAlpha = 1; + entry.mixTime = 0; + entry.mixDuration = (last == null) ? 0 : data.GetMix(last.animation, animation); + return entry; + } + + private void DisposeNext(TrackEntry entry) + { + TrackEntry next = entry.next; + while (next != null) + { + queue.Dispose(next); + next = next.next; + } + entry.next = null; + } + + private void AnimationsChanged() + { + animationsChanged = false; + + var propertyIDs = this.propertyIDs; + + // Set timelinesFirst for all entries, from lowest track to highest. + int i = 0, n = tracks.Count; + propertyIDs.Clear(); + for (; i < n; i++) + { // Find first non-null entry. + TrackEntry entry = tracks.Items[i]; + if (entry == null) continue; + SetTimelinesFirst(entry); + i++; + break; + } + for (; i < n; i++) + { // Rest of entries. + TrackEntry entry = tracks.Items[i]; + if (entry != null) CheckTimelinesFirst(entry); + } + } + + /// From last to first mixingFrom entries, sets timelinesFirst to true on last, calls checkTimelineUsage on rest. + private void SetTimelinesFirst(TrackEntry entry) + { + if (entry.mixingFrom != null) + { + SetTimelinesFirst(entry.mixingFrom); + CheckTimelinesUsage(entry); + return; + } + var propertyIDs = this.propertyIDs; + var timelines = entry.animation.timelines; + int n = timelines.Count; + entry.timelinesFirst.EnsureCapacity(n); // entry.timelinesFirst.setSize(n); + var usage = entry.timelinesFirst.Items; + var timelinesItems = timelines.Items; + for (int i = 0; i < n; i++) + { + propertyIDs.Add(timelinesItems[i].PropertyId); + usage[i] = true; + } + } + + /// From last to first mixingFrom entries, calls checkTimelineUsage. + private void CheckTimelinesFirst(TrackEntry entry) + { + if (entry.mixingFrom != null) CheckTimelinesFirst(entry.mixingFrom); + CheckTimelinesUsage(entry); + } + + private void CheckTimelinesUsage(TrackEntry entry) + { + var propertyIDs = this.propertyIDs; + var timelines = entry.animation.timelines; + int n = timelines.Count; + var usageArray = entry.timelinesFirst; + usageArray.EnsureCapacity(n); + var usage = usageArray.Items; + var timelinesItems = timelines.Items; + for (int i = 0; i < n; i++) + usage[i] = propertyIDs.Add(timelinesItems[i].PropertyId); + } + + /// The track entry for the animation currently playing on the track, or null if no animation is currently playing. + public TrackEntry GetCurrent(int trackIndex) + { + return (trackIndex >= tracks.Count) ? null : tracks.Items[trackIndex]; + } + + override public String ToString() + { + var buffer = new System.Text.StringBuilder(); + for (int i = 0, n = tracks.Count; i < n; i++) + { + TrackEntry entry = tracks.Items[i]; + if (entry == null) continue; + if (buffer.Length > 0) buffer.Append(", "); + buffer.Append(entry.ToString()); + } + return buffer.Length == 0 ? "" : buffer.ToString(); + } + + internal void OnStart(TrackEntry entry) { if (Start != null) Start(entry); } + internal void OnInterrupt(TrackEntry entry) { if (Interrupt != null) Interrupt(entry); } + internal void OnEnd(TrackEntry entry) { if (End != null) End(entry); } + internal void OnDispose(TrackEntry entry) { if (Dispose != null) Dispose(entry); } + internal void OnComplete(TrackEntry entry) { if (Complete != null) Complete(entry); } + internal void OnEvent(TrackEntry entry, Event e) { if (Event != null) Event(entry, e); } + } + + /// State for the playback of an animation. + public class TrackEntry : Pool.IPoolable + { + internal Animation animation; + + internal TrackEntry next, mixingFrom; + internal int trackIndex; + + internal bool loop; + internal float eventThreshold, attachmentThreshold, drawOrderThreshold; + internal float animationStart, animationEnd, animationLast, nextAnimationLast; + internal float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale = 1f; + internal float alpha, mixTime, mixDuration, mixAlpha; + internal readonly ExposedList timelinesFirst = new ExposedList(); + internal readonly ExposedList timelinesRotation = new ExposedList(); + + // IPoolable.Reset() + public void Reset() + { + next = null; + mixingFrom = null; + animation = null; + timelinesFirst.Clear(); + timelinesRotation.Clear(); + + Start = null; + Interrupt = null; + End = null; + Dispose = null; + Complete = null; + Event = null; + } + + /// The index of the track where this entry is either current or queued. + public int TrackIndex { get { return trackIndex; } } + + /// The animation to apply for this track entry. + public Animation Animation { get { return animation; } } + + /// + /// If true, the animation will repeat. If false, it will not, instead its last frame is applied if played beyond its duration. + public bool Loop { get { return loop; } set { loop = value; } } + + /// + /// 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. + public float Delay { get { return delay; } set { delay = value; } } + + /// + /// Current time in seconds this track entry has been the current track entry. The track time determines + /// . The track time can be set to start the animation at a time other than 0, without affecting looping. + public float TrackTime { get { return trackTime; } set { trackTime = value; } } + + /// + /// 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 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 to mix the properties back to the + /// setup pose over time, rather than have it happen instantly. + /// + public float TrackEnd { get { return trackEnd; } set { trackEnd = value; } } + + /// + /// 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 to the same value to + /// prevent timeline keys before the start time from triggering. + /// + public float AnimationStart { get { return animationStart; } set { animationStart = value; } } + + /// + /// Seconds for the last frame of this animation. Non-looping animations won't play past this time. Looping animations will + /// loop back to at this time. Defaults to the animation duration. + public float AnimationEnd { get { return animationEnd; } } + + /// + /// 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. + public float AnimationLast + { + get { return animationLast; } + set + { + animationLast = value; + nextAnimationLast = value; + } + } + + /// + /// Uses to compute the animation time between . and + /// . When the track time is 0, the animation time is equal to the animation start time. + /// + public float AnimationTime + { + get + { + if (loop) + { + float duration = animationEnd - animationStart; + if (duration == 0) return animationStart; + return (trackTime % duration) + animationStart; + } + return Math.Min(trackTime + animationStart, animationEnd); + } + } + + /// + /// Multiplier for the delta time when the animation state is updated, causing time for this animation to play slower or + /// faster. Defaults to 1. + /// + public float TimeScale { get { return timeScale; } set { timeScale = value; } } + + /// + /// 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. + /// + public float Alpha { get { return alpha; } set { alpha = value; } } + + /// + /// 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. + public float EventThreshold { get { return eventThreshold; } set { eventThreshold = value; } } + + /// + /// 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. + public float AttachmentThreshold { get { return attachmentThreshold; } set { attachmentThreshold = value; } } + + /// + /// 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. + /// + public float DrawOrderThreshold { get { return drawOrderThreshold; } set { drawOrderThreshold = value; } } + + /// + /// The animation queued to start after this animation, or null. + public TrackEntry Next { get { return next; } } + + /// + /// Returns true if at least one loop has been completed. + public bool IsComplete + { + get { return trackTime >= animationEnd - animationStart; } + } + + /// + /// Seconds from 0 to the mix duration when mixing from the previous animation to this animation. May be slightly more than + /// . + public float MixTime { get { return mixTime; } set { mixTime = value; } } + + /// + /// Seconds for mixing from the previous animation to this animation. Defaults to the value provided by + /// based on the animation before this animation (if any). + /// + /// The mix duration must be set before is next called. + /// + public float MixDuration { get { return mixDuration; } set { mixDuration = value; } } + + /// + /// The track entry for the previous animation when mixing from the previous animation to this animation, or null if no + /// mixing is currently occuring. + public TrackEntry MixingFrom { get { return mixingFrom; } } + + public event AnimationState.TrackEntryDelegate Start, Interrupt, End, Dispose, Complete; + public event AnimationState.TrackEntryEventDelegate Event; + internal void OnStart() { if (Start != null) Start(this); } + internal void OnInterrupt() { if (Interrupt != null) Interrupt(this); } + internal void OnEnd() { if (End != null) End(this); } + internal void OnDispose() { if (Dispose != null) Dispose(this); } + internal void OnComplete() { if (Complete != null) Complete(this); } + internal void OnEvent(Event e) { if (Event != null) Event(this, e); } + + /// + /// 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 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. + public void ResetRotationDirections() + { + timelinesRotation.Clear(); + } + + override public String ToString() + { + return animation == null ? "" : animation.name; + } + } + + class EventQueue + { + private readonly List eventQueueEntries = new List(); + public bool drainDisabled; + + private readonly AnimationState state; + private readonly Pool trackEntryPool; + public event Action AnimationsChanged; + + public EventQueue(AnimationState state, Action HandleAnimationsChanged, Pool trackEntryPool) + { + this.state = state; + this.AnimationsChanged += HandleAnimationsChanged; + this.trackEntryPool = trackEntryPool; + } + + struct EventQueueEntry + { + public EventType type; + public TrackEntry entry; + public Event e; + + public EventQueueEntry(EventType eventType, TrackEntry trackEntry, Event e = null) + { + this.type = eventType; + this.entry = trackEntry; + this.e = e; + } + } + + enum EventType + { + Start, Interrupt, End, Dispose, Complete, Event + } + + public void Start(TrackEntry entry) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.Start, entry)); + if (AnimationsChanged != null) AnimationsChanged(); + } + + public void Interrupt(TrackEntry entry) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.Interrupt, entry)); + } + + public void End(TrackEntry entry) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.End, entry)); + if (AnimationsChanged != null) AnimationsChanged(); + } + + public void Dispose(TrackEntry entry) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.Dispose, entry)); + } + + public void Complete(TrackEntry entry) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.Complete, entry)); + } + + public void Event(TrackEntry entry, Event e) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.Event, entry, e)); + } + + public void Drain() + { + if (drainDisabled) return; + drainDisabled = true; + + var entries = this.eventQueueEntries; + AnimationState state = this.state; + + // Don't cache entries.Count so callbacks can queue their own events (eg, call SetAnimation in AnimationState_Complete). + for (int i = 0; i < entries.Count; i++) + { + var queueEntry = entries[i]; + TrackEntry trackEntry = queueEntry.entry; + + switch (queueEntry.type) + { + case EventType.Start: + trackEntry.OnStart(); + state.OnStart(trackEntry); + break; + case EventType.Interrupt: + trackEntry.OnInterrupt(); + state.OnInterrupt(trackEntry); + break; + case EventType.End: + trackEntry.OnEnd(); + state.OnEnd(trackEntry); + goto case EventType.Dispose; // Fall through. (C#) + case EventType.Dispose: + trackEntry.OnDispose(); + state.OnDispose(trackEntry); + trackEntryPool.Free(trackEntry); // Pooling + break; + case EventType.Complete: + trackEntry.OnComplete(); + state.OnComplete(trackEntry); + break; + case EventType.Event: + trackEntry.OnEvent(queueEntry.e); + state.OnEvent(trackEntry, queueEntry.e); + break; + } + } + eventQueueEntries.Clear(); + + drainDisabled = false; + } + + public void Clear() + { + eventQueueEntries.Clear(); + } + } + + public class Pool where T : class, new() + { + public readonly int max; + readonly Stack freeObjects; + + public int Count { get { return freeObjects.Count; } } + public int Peak { get; private set; } + + public Pool(int initialCapacity = 16, int max = int.MaxValue) + { + freeObjects = new Stack(initialCapacity); + this.max = max; + } + + public T Obtain() + { + return freeObjects.Count == 0 ? new T() : freeObjects.Pop(); + } + + public void Free(T obj) + { + if (obj == null) throw new ArgumentNullException("obj", "obj cannot be null"); + if (freeObjects.Count < max) + { + freeObjects.Push(obj); + Peak = Math.Max(Peak, freeObjects.Count); + } + Reset(obj); + } + + // protected void FreeAll (List objects) { + // if (objects == null) throw new ArgumentNullException("objects", "objects cannot be null."); + // var freeObjects = this.freeObjects; + // int max = this.max; + // for (int i = 0; i < objects.Count; i++) { + // T obj = objects[i]; + // if (obj == null) continue; + // if (freeObjects.Count < max) freeObjects.Push(obj); + // Reset(obj); + // } + // Peak = Math.Max(Peak, freeObjects.Count); + // } + + public void Clear() + { + freeObjects.Clear(); + } + + protected void Reset(T obj) + { + var poolable = obj as IPoolable; + if (poolable != null) poolable.Reset(); + } + + public interface IPoolable + { + void Reset(); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/AnimationStateData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/AnimationStateData.cs index 6d8c879..a44cefb 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/AnimationStateData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/AnimationStateData.cs @@ -31,73 +31,85 @@ using System; using System.Collections.Generic; -namespace Spine3_5_51 { - public class AnimationStateData { - internal SkeletonData skeletonData; +namespace Spine3_5_51 +{ + public class AnimationStateData + { + internal SkeletonData skeletonData; - readonly Dictionary animationToMixTime = new Dictionary(AnimationPairComparer.Instance); - internal float defaultMix; + readonly Dictionary animationToMixTime = new Dictionary(AnimationPairComparer.Instance); + internal float defaultMix; - public SkeletonData SkeletonData { get { return skeletonData; } } - public float DefaultMix { get { return defaultMix; } set { defaultMix = value; } } + public SkeletonData SkeletonData { get { return skeletonData; } } + public float DefaultMix { get { return defaultMix; } set { defaultMix = value; } } - public AnimationStateData (SkeletonData skeletonData) { - if (skeletonData == null) throw new ArgumentException ("skeletonData cannot be null."); - this.skeletonData = skeletonData; - } + public AnimationStateData(SkeletonData skeletonData) + { + if (skeletonData == null) throw new ArgumentException("skeletonData cannot be null."); + this.skeletonData = skeletonData; + } - public void SetMix (String fromName, String toName, float duration) { - Animation from = skeletonData.FindAnimation(fromName); - if (from == null) throw new ArgumentException("Animation not found: " + fromName); - Animation to = skeletonData.FindAnimation(toName); - if (to == null) throw new ArgumentException("Animation not found: " + toName); - SetMix(from, to, duration); - } + public void SetMix(String fromName, String toName, float duration) + { + Animation from = skeletonData.FindAnimation(fromName); + if (from == null) throw new ArgumentException("Animation not found: " + fromName); + Animation to = skeletonData.FindAnimation(toName); + if (to == null) throw new ArgumentException("Animation not found: " + toName); + SetMix(from, to, duration); + } - public void SetMix (Animation from, Animation to, float duration) { - if (from == null) throw new ArgumentNullException("from", "from cannot be null."); - if (to == null) throw new ArgumentNullException("to", "to cannot be null."); - AnimationPair key = new AnimationPair(from, to); - animationToMixTime.Remove(key); - animationToMixTime.Add(key, duration); - } + public void SetMix(Animation from, Animation to, float duration) + { + if (from == null) throw new ArgumentNullException("from", "from cannot be null."); + if (to == null) throw new ArgumentNullException("to", "to cannot be null."); + AnimationPair key = new AnimationPair(from, to); + animationToMixTime.Remove(key); + animationToMixTime.Add(key, duration); + } - public float GetMix (Animation from, Animation to) { - if (from == null) throw new ArgumentNullException("from", "from cannot be null."); - if (to == null) throw new ArgumentNullException("to", "to cannot be null."); - AnimationPair key = new AnimationPair(from, to); - float duration; - if (animationToMixTime.TryGetValue(key, out duration)) return duration; - return defaultMix; - } + public float GetMix(Animation from, Animation to) + { + if (from == null) throw new ArgumentNullException("from", "from cannot be null."); + if (to == null) throw new ArgumentNullException("to", "to cannot be null."); + AnimationPair key = new AnimationPair(from, to); + float duration; + if (animationToMixTime.TryGetValue(key, out duration)) return duration; + return defaultMix; + } - struct AnimationPair { - public readonly Animation a1; - public readonly Animation a2; + struct AnimationPair + { + public readonly Animation a1; + public readonly Animation a2; - public AnimationPair (Animation a1, Animation a2) { - this.a1 = a1; - this.a2 = a2; - } + public AnimationPair(Animation a1, Animation a2) + { + this.a1 = a1; + this.a2 = a2; + } - public override string ToString () { - return a1.name + "->" + a2.name; - } - } + public override string ToString() + { + return a1.name + "->" + a2.name; + } + } - // Avoids boxing in the dictionary. - class AnimationPairComparer : IEqualityComparer { - internal static readonly AnimationPairComparer Instance = new AnimationPairComparer(); + // Avoids boxing in the dictionary. + class AnimationPairComparer : IEqualityComparer + { + internal static readonly AnimationPairComparer Instance = new AnimationPairComparer(); - bool IEqualityComparer.Equals (AnimationPair x, AnimationPair y) { - return ReferenceEquals(x.a1, y.a1) && ReferenceEquals(x.a2, y.a2); - } + bool IEqualityComparer.Equals(AnimationPair x, AnimationPair y) + { + return ReferenceEquals(x.a1, y.a1) && ReferenceEquals(x.a2, y.a2); + } - int IEqualityComparer.GetHashCode (AnimationPair obj) { - // from Tuple.CombineHashCodes // return (((h1 << 5) + h1) ^ h2); - int h1 = obj.a1.GetHashCode(); - return (((h1 << 5) + h1) ^ obj.a2.GetHashCode()); - } - } - } + int IEqualityComparer.GetHashCode(AnimationPair obj) + { + // from Tuple.CombineHashCodes // return (((h1 << 5) + h1) ^ h2); + int h1 = obj.a1.GetHashCode(); + return (((h1 << 5) + h1) ^ obj.a2.GetHashCode()); + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Atlas.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Atlas.cs index 3d2075a..0da84cb 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Atlas.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Atlas.cs @@ -31,21 +31,22 @@ using System; using System.Collections.Generic; using System.IO; -using System.Reflection; #if WINDOWS_STOREAPP using System.Threading.Tasks; using Windows.Storage; #endif -namespace Spine3_5_51 { - public class Atlas { - List pages = new List(); - List regions = new List(); - TextureLoader textureLoader; +namespace Spine3_5_51 +{ + public class Atlas + { + List pages = new List(); + List regions = new List(); + TextureLoader textureLoader; - #if !(UNITY_5 || UNITY_4 || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1) // !UNITY - #if WINDOWS_STOREAPP +#if !(UNITY_5 || UNITY_4 || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1) // !UNITY +#if WINDOWS_STOREAPP private async Task ReadFile(string path, TextureLoader textureLoader) { var folder = Windows.ApplicationModel.Package.Current.InstalledLocation; var file = await folder.GetFileAsync(path).AsTask().ConfigureAwait(false); @@ -61,233 +62,264 @@ private async Task ReadFile(string path, TextureLoader textureLoader) { public Atlas(String path, TextureLoader textureLoader) { this.ReadFile(path, textureLoader).Wait(); } - #else +#else - public Atlas (String path, TextureLoader textureLoader) { + public Atlas(String path, TextureLoader textureLoader) + { - #if WINDOWS_PHONE +#if WINDOWS_PHONE Stream stream = Microsoft.Xna.Framework.TitleContainer.OpenStream(path); using (StreamReader reader = new StreamReader(stream)) { - #else - using (StreamReader reader = new StreamReader(path)) { - #endif // WINDOWS_PHONE - - try { - Load(reader, Path.GetDirectoryName(path), textureLoader); - } catch (Exception ex) { - throw new Exception("Error reading atlas file: " + path, ex); - } - - } - } - #endif // WINDOWS_STOREAPP - - #endif // !(UNITY) - - public Atlas (TextReader reader, String dir, TextureLoader textureLoader) { - Load(reader, dir, textureLoader); - } - - public Atlas (List pages, List regions) { - this.pages = pages; - this.regions = regions; - this.textureLoader = null; - } - - private void Load (TextReader reader, String imagesDir, TextureLoader textureLoader) { - if (textureLoader == null) throw new ArgumentNullException("textureLoader cannot be null."); - this.textureLoader = textureLoader; - - String[] tuple = new String[4]; - AtlasPage page = null; - while (true) { - String line = reader.ReadLine(); - if (line == null) break; - if (line.Trim().Length == 0) - page = null; - else if (page == null) { - page = new AtlasPage(); - page.name = line; - - if (ReadTuple(reader, tuple) == 2) { // size is only optional for an atlas packed with an old TexturePacker. - page.width = int.Parse(tuple[0]); - page.height = int.Parse(tuple[1]); - ReadTuple(reader, tuple); - } - page.format = (Format)Enum.Parse(typeof(Format), tuple[0], false); - - ReadTuple(reader, tuple); - page.minFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[0], false); - page.magFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[1], false); - - String direction = ReadValue(reader); - page.uWrap = TextureWrap.ClampToEdge; - page.vWrap = TextureWrap.ClampToEdge; - if (direction == "x") - page.uWrap = TextureWrap.Repeat; - else if (direction == "y") - page.vWrap = TextureWrap.Repeat; - else if (direction == "xy") - page.uWrap = page.vWrap = TextureWrap.Repeat; - - textureLoader.Load(page, Path.Combine(imagesDir, line)); - - pages.Add(page); - - } else { - AtlasRegion region = new AtlasRegion(); - region.name = line; - region.page = page; - - region.rotate = Boolean.Parse(ReadValue(reader)); - - ReadTuple(reader, tuple); - int x = int.Parse(tuple[0]); - int y = int.Parse(tuple[1]); - - ReadTuple(reader, tuple); - int width = int.Parse(tuple[0]); - int height = int.Parse(tuple[1]); - - region.u = x / (float)page.width; - region.v = y / (float)page.height; - if (region.rotate) { - region.u2 = (x + height) / (float)page.width; - region.v2 = (y + width) / (float)page.height; - } else { - region.u2 = (x + width) / (float)page.width; - region.v2 = (y + height) / (float)page.height; - } - region.x = x; - region.y = y; - region.width = Math.Abs(width); - region.height = Math.Abs(height); - - if (ReadTuple(reader, tuple) == 4) { // split is optional - region.splits = new int[] {int.Parse(tuple[0]), int.Parse(tuple[1]), - int.Parse(tuple[2]), int.Parse(tuple[3])}; - - if (ReadTuple(reader, tuple) == 4) { // pad is optional, but only present with splits - region.pads = new int[] {int.Parse(tuple[0]), int.Parse(tuple[1]), - int.Parse(tuple[2]), int.Parse(tuple[3])}; - - ReadTuple(reader, tuple); - } - } - - region.originalWidth = int.Parse(tuple[0]); - region.originalHeight = int.Parse(tuple[1]); - - ReadTuple(reader, tuple); - region.offsetX = int.Parse(tuple[0]); - region.offsetY = int.Parse(tuple[1]); - - region.index = int.Parse(ReadValue(reader)); - - regions.Add(region); - } - } - } - - static String ReadValue (TextReader reader) { - String line = reader.ReadLine(); - int colon = line.IndexOf(':'); - if (colon == -1) throw new Exception("Invalid line: " + line); - return line.Substring(colon + 1).Trim(); - } - - /// Returns the number of tuple values read (1, 2 or 4). - static int ReadTuple (TextReader reader, String[] tuple) { - String line = reader.ReadLine(); - int colon = line.IndexOf(':'); - if (colon == -1) throw new Exception("Invalid line: " + line); - int i = 0, lastMatch = colon + 1; - for (; i < 3; i++) { - int comma = line.IndexOf(',', lastMatch); - if (comma == -1) break; - tuple[i] = line.Substring(lastMatch, comma - lastMatch).Trim(); - lastMatch = comma + 1; - } - tuple[i] = line.Substring(lastMatch).Trim(); - return i + 1; - } - - public void FlipV () { - for (int i = 0, n = regions.Count; i < n; i++) { - AtlasRegion region = regions[i]; - region.v = 1 - region.v; - region.v2 = 1 - region.v2; - } - } - - /// 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. - /// The region, or null. - public AtlasRegion FindRegion (String name) { - for (int i = 0, n = regions.Count; i < n; i++) - if (regions[i].name == name) return regions[i]; - return null; - } - - public void Dispose () { - if (textureLoader == null) return; - for (int i = 0, n = pages.Count; i < n; i++) - textureLoader.Unload(pages[i].rendererObject); - } - } - - public enum Format { - Alpha, - Intensity, - LuminanceAlpha, - RGB565, - RGBA4444, - RGB888, - RGBA8888 - } - - public enum TextureFilter { - Nearest, - Linear, - MipMap, - MipMapNearestNearest, - MipMapLinearNearest, - MipMapNearestLinear, - MipMapLinearLinear - } - - public enum TextureWrap { - MirroredRepeat, - ClampToEdge, - Repeat - } - - public class AtlasPage { - public String name; - public Format format; - public TextureFilter minFilter; - public TextureFilter magFilter; - public TextureWrap uWrap; - public TextureWrap vWrap; - public Object rendererObject; - public int width, height; - } - - public class AtlasRegion { - public AtlasPage page; - public String name; - public int x, y, width, height; - public float u, v, u2, v2; - public float offsetX, offsetY; - public int originalWidth, originalHeight; - public int index; - public bool rotate; - public int[] splits; - public int[] pads; - } - - public interface TextureLoader { - void Load (AtlasPage page, String path); - void Unload (Object texture); - } +#else + using (StreamReader reader = new StreamReader(path)) + { +#endif // WINDOWS_PHONE + + try + { + Load(reader, Path.GetDirectoryName(path), textureLoader); + } + catch (Exception ex) + { + throw new Exception("Error reading atlas file: " + path, ex); + } + + } + } +#endif // WINDOWS_STOREAPP + +#endif // !(UNITY) + + public Atlas(TextReader reader, String dir, TextureLoader textureLoader) + { + Load(reader, dir, textureLoader); + } + + public Atlas(List pages, List regions) + { + this.pages = pages; + this.regions = regions; + this.textureLoader = null; + } + + private void Load(TextReader reader, String imagesDir, TextureLoader textureLoader) + { + if (textureLoader == null) throw new ArgumentNullException("textureLoader cannot be null."); + this.textureLoader = textureLoader; + + String[] tuple = new String[4]; + AtlasPage page = null; + while (true) + { + String line = reader.ReadLine(); + if (line == null) break; + if (line.Trim().Length == 0) + page = null; + else if (page == null) + { + page = new AtlasPage(); + page.name = line; + + if (ReadTuple(reader, tuple) == 2) + { // size is only optional for an atlas packed with an old TexturePacker. + page.width = int.Parse(tuple[0]); + page.height = int.Parse(tuple[1]); + ReadTuple(reader, tuple); + } + page.format = (Format)Enum.Parse(typeof(Format), tuple[0], false); + + ReadTuple(reader, tuple); + page.minFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[0], false); + page.magFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[1], false); + + String direction = ReadValue(reader); + page.uWrap = TextureWrap.ClampToEdge; + page.vWrap = TextureWrap.ClampToEdge; + if (direction == "x") + page.uWrap = TextureWrap.Repeat; + else if (direction == "y") + page.vWrap = TextureWrap.Repeat; + else if (direction == "xy") + page.uWrap = page.vWrap = TextureWrap.Repeat; + + textureLoader.Load(page, Path.Combine(imagesDir, line)); + + pages.Add(page); + + } + else + { + AtlasRegion region = new AtlasRegion(); + region.name = line; + region.page = page; + + region.rotate = Boolean.Parse(ReadValue(reader)); + + ReadTuple(reader, tuple); + int x = int.Parse(tuple[0]); + int y = int.Parse(tuple[1]); + + ReadTuple(reader, tuple); + int width = int.Parse(tuple[0]); + int height = int.Parse(tuple[1]); + + region.u = x / (float)page.width; + region.v = y / (float)page.height; + if (region.rotate) + { + region.u2 = (x + height) / (float)page.width; + region.v2 = (y + width) / (float)page.height; + } + else + { + region.u2 = (x + width) / (float)page.width; + region.v2 = (y + height) / (float)page.height; + } + region.x = x; + region.y = y; + region.width = Math.Abs(width); + region.height = Math.Abs(height); + + if (ReadTuple(reader, tuple) == 4) + { // split is optional + region.splits = new int[] {int.Parse(tuple[0]), int.Parse(tuple[1]), + int.Parse(tuple[2]), int.Parse(tuple[3])}; + + if (ReadTuple(reader, tuple) == 4) + { // pad is optional, but only present with splits + region.pads = new int[] {int.Parse(tuple[0]), int.Parse(tuple[1]), + int.Parse(tuple[2]), int.Parse(tuple[3])}; + + ReadTuple(reader, tuple); + } + } + + region.originalWidth = int.Parse(tuple[0]); + region.originalHeight = int.Parse(tuple[1]); + + ReadTuple(reader, tuple); + region.offsetX = int.Parse(tuple[0]); + region.offsetY = int.Parse(tuple[1]); + + region.index = int.Parse(ReadValue(reader)); + + regions.Add(region); + } + } + } + + static String ReadValue(TextReader reader) + { + String line = reader.ReadLine(); + int colon = line.IndexOf(':'); + if (colon == -1) throw new Exception("Invalid line: " + line); + return line.Substring(colon + 1).Trim(); + } + + /// Returns the number of tuple values read (1, 2 or 4). + static int ReadTuple(TextReader reader, String[] tuple) + { + String line = reader.ReadLine(); + int colon = line.IndexOf(':'); + if (colon == -1) throw new Exception("Invalid line: " + line); + int i = 0, lastMatch = colon + 1; + for (; i < 3; i++) + { + int comma = line.IndexOf(',', lastMatch); + if (comma == -1) break; + tuple[i] = line.Substring(lastMatch, comma - lastMatch).Trim(); + lastMatch = comma + 1; + } + tuple[i] = line.Substring(lastMatch).Trim(); + return i + 1; + } + + public void FlipV() + { + for (int i = 0, n = regions.Count; i < n; i++) + { + AtlasRegion region = regions[i]; + region.v = 1 - region.v; + region.v2 = 1 - region.v2; + } + } + + /// 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. + /// The region, or null. + public AtlasRegion FindRegion(String name) + { + for (int i = 0, n = regions.Count; i < n; i++) + if (regions[i].name == name) return regions[i]; + return null; + } + + public void Dispose() + { + if (textureLoader == null) return; + for (int i = 0, n = pages.Count; i < n; i++) + textureLoader.Unload(pages[i].rendererObject); + } + } + + public enum Format + { + Alpha, + Intensity, + LuminanceAlpha, + RGB565, + RGBA4444, + RGB888, + RGBA8888 + } + + public enum TextureFilter + { + Nearest, + Linear, + MipMap, + MipMapNearestNearest, + MipMapLinearNearest, + MipMapNearestLinear, + MipMapLinearLinear + } + + public enum TextureWrap + { + MirroredRepeat, + ClampToEdge, + Repeat + } + + public class AtlasPage + { + public String name; + public Format format; + public TextureFilter minFilter; + public TextureFilter magFilter; + public TextureWrap uWrap; + public TextureWrap vWrap; + public Object rendererObject; + public int width, height; + } + + public class AtlasRegion + { + public AtlasPage page; + public String name; + public int x, y, width, height; + public float u, v, u2, v2; + public float offsetX, offsetY; + public int originalWidth, originalHeight; + public int index; + public bool rotate; + public int[] splits; + public int[] pads; + } + + public interface TextureLoader + { + void Load(AtlasPage page, String path); + void Unload(Object texture); + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Attachments/AtlasAttachmentLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Attachments/AtlasAttachmentLoader.cs index 3c1eaa1..951fd6b 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Attachments/AtlasAttachmentLoader.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Attachments/AtlasAttachmentLoader.cs @@ -30,67 +30,76 @@ using System; -namespace Spine3_5_51 { - public class AtlasAttachmentLoader : AttachmentLoader { - private Atlas[] atlasArray; +namespace Spine3_5_51 +{ + public class AtlasAttachmentLoader : AttachmentLoader + { + private Atlas[] atlasArray; - public AtlasAttachmentLoader (params Atlas[] atlasArray) { - if (atlasArray == null) throw new ArgumentNullException("atlas array cannot be null."); - this.atlasArray = atlasArray; - } + public AtlasAttachmentLoader(params Atlas[] atlasArray) + { + if (atlasArray == null) throw new ArgumentNullException("atlas array cannot be null."); + this.atlasArray = atlasArray; + } - public RegionAttachment NewRegionAttachment (Skin skin, String name, String path) { - AtlasRegion region = FindRegion(path); - if (region == null) throw new Exception("Region not found in atlas: " + path + " (region attachment: " + name + ")"); - RegionAttachment attachment = new RegionAttachment(name); - attachment.RendererObject = region; - attachment.SetUVs(region.u, region.v, region.u2, region.v2, region.rotate); - attachment.regionOffsetX = region.offsetX; - attachment.regionOffsetY = region.offsetY; - attachment.regionWidth = region.width; - attachment.regionHeight = region.height; - attachment.regionOriginalWidth = region.originalWidth; - attachment.regionOriginalHeight = region.originalHeight; - return attachment; - } + public RegionAttachment NewRegionAttachment(Skin skin, String name, String path) + { + AtlasRegion region = FindRegion(path); + if (region == null) throw new Exception("Region not found in atlas: " + path + " (region attachment: " + name + ")"); + RegionAttachment attachment = new RegionAttachment(name); + attachment.RendererObject = region; + attachment.SetUVs(region.u, region.v, region.u2, region.v2, region.rotate); + attachment.regionOffsetX = region.offsetX; + attachment.regionOffsetY = region.offsetY; + attachment.regionWidth = region.width; + attachment.regionHeight = region.height; + attachment.regionOriginalWidth = region.originalWidth; + attachment.regionOriginalHeight = region.originalHeight; + return attachment; + } - public MeshAttachment NewMeshAttachment (Skin skin, String name, String path) { - AtlasRegion region = FindRegion(path); - if (region == null) throw new Exception("Region not found in atlas: " + path + " (mesh attachment: " + name + ")"); - MeshAttachment attachment = new MeshAttachment(name); - attachment.RendererObject = region; - attachment.RegionU = region.u; - attachment.RegionV = region.v; - attachment.RegionU2 = region.u2; - attachment.RegionV2 = region.v2; - attachment.RegionRotate = region.rotate; - attachment.regionOffsetX = region.offsetX; - attachment.regionOffsetY = region.offsetY; - attachment.regionWidth = region.width; - attachment.regionHeight = region.height; - attachment.regionOriginalWidth = region.originalWidth; - attachment.regionOriginalHeight = region.originalHeight; - return attachment; - } + public MeshAttachment NewMeshAttachment(Skin skin, String name, String path) + { + AtlasRegion region = FindRegion(path); + if (region == null) throw new Exception("Region not found in atlas: " + path + " (mesh attachment: " + name + ")"); + MeshAttachment attachment = new MeshAttachment(name); + attachment.RendererObject = region; + attachment.RegionU = region.u; + attachment.RegionV = region.v; + attachment.RegionU2 = region.u2; + attachment.RegionV2 = region.v2; + attachment.RegionRotate = region.rotate; + attachment.regionOffsetX = region.offsetX; + attachment.regionOffsetY = region.offsetY; + attachment.regionWidth = region.width; + attachment.regionHeight = region.height; + attachment.regionOriginalWidth = region.originalWidth; + attachment.regionOriginalHeight = region.originalHeight; + return attachment; + } - public BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, String name) { - return new BoundingBoxAttachment(name); - } + public BoundingBoxAttachment NewBoundingBoxAttachment(Skin skin, String name) + { + return new BoundingBoxAttachment(name); + } - public PathAttachment NewPathAttachment (Skin skin, String name) { - return new PathAttachment (name); - } + public PathAttachment NewPathAttachment(Skin skin, String name) + { + return new PathAttachment(name); + } - public AtlasRegion FindRegion (string name) { - AtlasRegion region; + public AtlasRegion FindRegion(string name) + { + AtlasRegion region; - for (int i = 0; i < atlasArray.Length; i++) { - region = atlasArray[i].FindRegion(name); - if (region != null) - return region; - } + for (int i = 0; i < atlasArray.Length; i++) + { + region = atlasArray[i].FindRegion(name); + if (region != null) + return region; + } - return null; - } - } + return null; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Attachments/Attachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Attachments/Attachment.cs index d9890ed..0acc5bc 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Attachments/Attachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Attachments/Attachment.cs @@ -30,17 +30,21 @@ using System; -namespace Spine3_5_51 { - abstract public class Attachment { - public String Name { get; private set; } +namespace Spine3_5_51 +{ + abstract public class Attachment + { + public String Name { get; private set; } - public Attachment (String name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null"); - Name = name; - } + public Attachment(String name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null"); + Name = name; + } - override public String ToString () { - return Name; - } - } + override public String ToString() + { + return Name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Attachments/AttachmentLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Attachments/AttachmentLoader.cs index 3b8566a..08a860c 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Attachments/AttachmentLoader.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Attachments/AttachmentLoader.cs @@ -30,18 +30,20 @@ using System; -namespace Spine3_5_51 { - public interface AttachmentLoader { - /// May be null to not load any attachment. - RegionAttachment NewRegionAttachment (Skin skin, String name, String path); +namespace Spine3_5_51 +{ + public interface AttachmentLoader + { + /// May be null to not load any attachment. + RegionAttachment NewRegionAttachment(Skin skin, String name, String path); - /// May be null to not load any attachment. - MeshAttachment NewMeshAttachment (Skin skin, String name, String path); + /// May be null to not load any attachment. + MeshAttachment NewMeshAttachment(Skin skin, String name, String path); - /// May be null to not load any attachment. - BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, String name); + /// May be null to not load any attachment. + BoundingBoxAttachment NewBoundingBoxAttachment(Skin skin, String name); - /// May be null to not load any attachment - PathAttachment NewPathAttachment (Skin skin, String name); - } + /// May be null to not load any attachment + PathAttachment NewPathAttachment(Skin skin, String name); + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Attachments/AttachmentType.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Attachments/AttachmentType.cs index bb57034..7590c51 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Attachments/AttachmentType.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Attachments/AttachmentType.cs @@ -28,8 +28,10 @@ * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine3_5_51 { - public enum AttachmentType { - Region, Boundingbox, Mesh, Linkedmesh, Path - } +namespace Spine3_5_51 +{ + public enum AttachmentType + { + Region, Boundingbox, Mesh, Linkedmesh, Path + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Attachments/BoundingBoxAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Attachments/BoundingBoxAttachment.cs index 9f5f082..1d5f75e 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Attachments/BoundingBoxAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Attachments/BoundingBoxAttachment.cs @@ -28,13 +28,14 @@ * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; - -namespace Spine3_5_51 { - /// Attachment that has a polygon for bounds checking. - public class BoundingBoxAttachment : VertexAttachment { - public BoundingBoxAttachment (string name) - : base(name) { - } - } +namespace Spine3_5_51 +{ + /// Attachment that has a polygon for bounds checking. + public class BoundingBoxAttachment : VertexAttachment + { + public BoundingBoxAttachment(string name) + : base(name) + { + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Attachments/MeshAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Attachments/MeshAttachment.cs index 51bba3c..2fd9b99 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Attachments/MeshAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Attachments/MeshAttachment.cs @@ -30,90 +30,103 @@ using System; -namespace Spine3_5_51 { - /// Attachment that displays a texture region using a mesh. - public class MeshAttachment : VertexAttachment { - internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; - internal float[] uvs, regionUVs; - internal int[] triangles; - internal float r = 1, g = 1, b = 1, a = 1; - internal int hulllength; - internal MeshAttachment parentMesh; - internal bool inheritDeform; +namespace Spine3_5_51 +{ + /// Attachment that displays a texture region using a mesh. + public class MeshAttachment : VertexAttachment + { + internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; + internal float[] uvs, regionUVs; + internal int[] triangles; + internal float r = 1, g = 1, b = 1, a = 1; + internal int hulllength; + internal MeshAttachment parentMesh; + internal bool inheritDeform; - public int HullLength { get { return hulllength; } set { hulllength = value; } } - public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } } - public float[] UVs { get { return uvs; } set { uvs = value; } } - public int[] Triangles { get { return triangles; } set { triangles = value; } } + public int HullLength { get { return hulllength; } set { hulllength = value; } } + public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } } + public float[] UVs { get { return uvs; } set { uvs = value; } } + public int[] Triangles { get { return triangles; } set { triangles = value; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } - public String Path { get; set; } - public Object RendererObject { get; set; } - public float RegionU { get; set; } - public float RegionV { get; set; } - public float RegionU2 { get; set; } - public float RegionV2 { get; set; } - public bool RegionRotate { get; set; } - public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } - public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. - public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } - public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. - public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } - public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. + public String Path { get; set; } + public Object RendererObject { get; set; } + public float RegionU { get; set; } + public float RegionV { get; set; } + public float RegionU2 { get; set; } + public float RegionV2 { get; set; } + public bool RegionRotate { get; set; } + public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } + public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. + public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } + public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. + public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } + public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. - public bool InheritDeform { get { return inheritDeform; } set { inheritDeform = value; } } + public bool InheritDeform { get { return inheritDeform; } set { inheritDeform = value; } } - public MeshAttachment ParentMesh { - get { return parentMesh; } - set { - parentMesh = value; - if (value != null) { - bones = value.bones; - vertices = value.vertices; - worldVerticesLength = value.worldVerticesLength; - regionUVs = value.regionUVs; - triangles = value.triangles; - HullLength = value.HullLength; - Edges = value.Edges; - Width = value.Width; - Height = value.Height; - } - } - } + public MeshAttachment ParentMesh + { + get { return parentMesh; } + set + { + parentMesh = value; + if (value != null) + { + bones = value.bones; + vertices = value.vertices; + worldVerticesLength = value.worldVerticesLength; + regionUVs = value.regionUVs; + triangles = value.triangles; + HullLength = value.HullLength; + Edges = value.Edges; + Width = value.Width; + Height = value.Height; + } + } + } - // Nonessential. - public int[] Edges { get; set; } - public float Width { get; set; } - public float Height { get; set; } + // Nonessential. + public int[] Edges { get; set; } + public float Width { get; set; } + public float Height { get; set; } - public MeshAttachment (string name) - : base(name) { - } + public MeshAttachment(string name) + : base(name) + { + } - public void UpdateUVs () { - float u = RegionU, v = RegionV, width = RegionU2 - RegionU, height = RegionV2 - RegionV; - float[] regionUVs = this.regionUVs; - if (this.uvs == null || this.uvs.Length != regionUVs.Length) this.uvs = new float[regionUVs.Length]; - float[] uvs = this.uvs; - if (RegionRotate) { - for (int i = 0, n = uvs.Length; i < n; i += 2) { - uvs[i] = u + regionUVs[i + 1] * width; - uvs[i + 1] = v + height - regionUVs[i] * height; - } - } else { - for (int i = 0, n = uvs.Length; i < n; i += 2) { - uvs[i] = u + regionUVs[i] * width; - uvs[i + 1] = v + regionUVs[i + 1] * height; - } - } - } + public void UpdateUVs() + { + float u = RegionU, v = RegionV, width = RegionU2 - RegionU, height = RegionV2 - RegionV; + float[] regionUVs = this.regionUVs; + if (this.uvs == null || this.uvs.Length != regionUVs.Length) this.uvs = new float[regionUVs.Length]; + float[] uvs = this.uvs; + if (RegionRotate) + { + for (int i = 0, n = uvs.Length; i < n; i += 2) + { + uvs[i] = u + regionUVs[i + 1] * width; + uvs[i + 1] = v + height - regionUVs[i] * height; + } + } + else + { + for (int i = 0, n = uvs.Length; i < n; i += 2) + { + uvs[i] = u + regionUVs[i] * width; + uvs[i + 1] = v + regionUVs[i + 1] * height; + } + } + } - override public bool ApplyDeform (VertexAttachment sourceAttachment) { - return this == sourceAttachment || (inheritDeform && parentMesh == sourceAttachment); - } - } + override public bool ApplyDeform(VertexAttachment sourceAttachment) + { + return this == sourceAttachment || (inheritDeform && parentMesh == sourceAttachment); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Attachments/PathAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Attachments/PathAttachment.cs index 7d25fe5..08ad4cd 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Attachments/PathAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Attachments/PathAttachment.cs @@ -29,20 +29,22 @@ *****************************************************************************/ using System; -using System.Collections.Generic; -namespace Spine3_5_51 { - public class PathAttachment : VertexAttachment { - internal float[] lengths; - internal bool closed, constantSpeed; +namespace Spine3_5_51 +{ + public class PathAttachment : VertexAttachment + { + internal float[] lengths; + internal bool closed, constantSpeed; - /// The length in the setup pose from the start of the path to the end of each curve. - public float[] Lengths { get { return lengths; } set { lengths = value; } } - public bool Closed { get { return closed; } set { closed = value; } } - public bool ConstantSpeed { get { return constantSpeed; } set { constantSpeed = value; } } + /// The length in the setup pose from the start of the path to the end of each curve. + public float[] Lengths { get { return lengths; } set { lengths = value; } } + public bool Closed { get { return closed; } set { closed = value; } } + public bool ConstantSpeed { get { return constantSpeed; } set { constantSpeed = value; } } - public PathAttachment (String name) - : base(name) { - } - } + public PathAttachment(String name) + : base(name) + { + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Attachments/RegionAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Attachments/RegionAttachment.cs index d56ff2a..d4f0bfb 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Attachments/RegionAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Attachments/RegionAttachment.cs @@ -30,122 +30,131 @@ using System; -namespace Spine3_5_51 { - /// Attachment that displays a texture region. - public class RegionAttachment : Attachment { - public const int X1 = 0; - public const int Y1 = 1; - public const int X2 = 2; - public const int Y2 = 3; - public const int X3 = 4; - public const int Y3 = 5; - public const int X4 = 6; - public const int Y4 = 7; +namespace Spine3_5_51 +{ + /// Attachment that displays a texture region. + public class RegionAttachment : Attachment + { + public const int X1 = 0; + public const int Y1 = 1; + public const int X2 = 2; + public const int Y2 = 3; + public const int X3 = 4; + public const int Y3 = 5; + public const int X4 = 6; + public const int Y4 = 7; - internal float x, y, rotation, scaleX = 1, scaleY = 1, width, height; - internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; - internal float[] offset = new float[8], uvs = new float[8]; - internal float r = 1, g = 1, b = 1, a = 1; + internal float x, y, rotation, scaleX = 1, scaleY = 1, width, height; + internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; + internal float[] offset = new float[8], uvs = new float[8]; + internal float r = 1, g = 1, b = 1, a = 1; - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public float Rotation { get { return rotation; } set { rotation = value; } } - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - public float ScaleY { get { return scaleY; } set { scaleY = value; } } - public float Width { get { return width; } set { width = value; } } - public float Height { get { return height; } set { height = value; } } + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public float Rotation { get { return rotation; } set { rotation = value; } } + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + public float Width { get { return width; } set { width = value; } } + public float Height { get { return height; } set { height = value; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } - public String Path { get; set; } - public Object RendererObject { get; set; } - public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } - public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. - public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } - public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. - public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } - public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. + public String Path { get; set; } + public Object RendererObject { get; set; } + public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } + public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. + public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } + public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. + public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } + public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. - public float[] Offset { get { return offset; } } - public float[] UVs { get { return uvs; } } + public float[] Offset { get { return offset; } } + public float[] UVs { get { return uvs; } } - public RegionAttachment (string name) - : base(name) { - } + public RegionAttachment(string name) + : base(name) + { + } - public void SetUVs (float u, float v, float u2, float v2, bool rotate) { - float[] uvs = this.uvs; - if (rotate) { - uvs[X2] = u; - uvs[Y2] = v2; - uvs[X3] = u; - uvs[Y3] = v; - uvs[X4] = u2; - uvs[Y4] = v; - uvs[X1] = u2; - uvs[Y1] = v2; - } else { - uvs[X1] = u; - uvs[Y1] = v2; - uvs[X2] = u; - uvs[Y2] = v; - uvs[X3] = u2; - uvs[Y3] = v; - uvs[X4] = u2; - uvs[Y4] = v2; - } - } + public void SetUVs(float u, float v, float u2, float v2, bool rotate) + { + float[] uvs = this.uvs; + if (rotate) + { + uvs[X2] = u; + uvs[Y2] = v2; + uvs[X3] = u; + uvs[Y3] = v; + uvs[X4] = u2; + uvs[Y4] = v; + uvs[X1] = u2; + uvs[Y1] = v2; + } + else + { + uvs[X1] = u; + uvs[Y1] = v2; + uvs[X2] = u; + uvs[Y2] = v; + uvs[X3] = u2; + uvs[Y3] = v; + uvs[X4] = u2; + uvs[Y4] = v2; + } + } - public void UpdateOffset () { - float width = this.width; - float height = this.height; - float scaleX = this.scaleX; - float scaleY = this.scaleY; - float regionScaleX = width / regionOriginalWidth * scaleX; - float regionScaleY = height / regionOriginalHeight * scaleY; - float localX = -width / 2 * scaleX + regionOffsetX * regionScaleX; - float localY = -height / 2 * scaleY + regionOffsetY * regionScaleY; - float localX2 = localX + regionWidth * regionScaleX; - float localY2 = localY + regionHeight * regionScaleY; - float rotation = this.rotation; - float cos = MathUtils.CosDeg(rotation); - float sin = MathUtils.SinDeg(rotation); - float x = this.x; - float y = this.y; - 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; - float[] offset = this.offset; - offset[X1] = localXCos - localYSin; - offset[Y1] = localYCos + localXSin; - offset[X2] = localXCos - localY2Sin; - offset[Y2] = localY2Cos + localXSin; - offset[X3] = localX2Cos - localY2Sin; - offset[Y3] = localY2Cos + localX2Sin; - offset[X4] = localX2Cos - localYSin; - offset[Y4] = localYCos + localX2Sin; - } + public void UpdateOffset() + { + float width = this.width; + float height = this.height; + float scaleX = this.scaleX; + float scaleY = this.scaleY; + float regionScaleX = width / regionOriginalWidth * scaleX; + float regionScaleY = height / regionOriginalHeight * scaleY; + float localX = -width / 2 * scaleX + regionOffsetX * regionScaleX; + float localY = -height / 2 * scaleY + regionOffsetY * regionScaleY; + float localX2 = localX + regionWidth * regionScaleX; + float localY2 = localY + regionHeight * regionScaleY; + float rotation = this.rotation; + float cos = MathUtils.CosDeg(rotation); + float sin = MathUtils.SinDeg(rotation); + float x = this.x; + float y = this.y; + 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; + float[] offset = this.offset; + offset[X1] = localXCos - localYSin; + offset[Y1] = localYCos + localXSin; + offset[X2] = localXCos - localY2Sin; + offset[Y2] = localY2Cos + localXSin; + offset[X3] = localX2Cos - localY2Sin; + offset[Y3] = localY2Cos + localX2Sin; + offset[X4] = localX2Cos - localYSin; + offset[Y4] = localYCos + localX2Sin; + } - public void ComputeWorldVertices (Bone bone, float[] worldVertices) { - float x = bone.worldX, y = bone.worldY; - float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - float[] offset = this.offset; - worldVertices[X1] = offset[X1] * a + offset[Y1] * b + x; - worldVertices[Y1] = offset[X1] * c + offset[Y1] * d + y; - worldVertices[X2] = offset[X2] * a + offset[Y2] * b + x; - worldVertices[Y2] = offset[X2] * c + offset[Y2] * d + y; - worldVertices[X3] = offset[X3] * a + offset[Y3] * b + x; - worldVertices[Y3] = offset[X3] * c + offset[Y3] * d + y; - worldVertices[X4] = offset[X4] * a + offset[Y4] * b + x; - worldVertices[Y4] = offset[X4] * c + offset[Y4] * d + y; - } - } + public void ComputeWorldVertices(Bone bone, float[] worldVertices) + { + float x = bone.worldX, y = bone.worldY; + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + float[] offset = this.offset; + worldVertices[X1] = offset[X1] * a + offset[Y1] * b + x; + worldVertices[Y1] = offset[X1] * c + offset[Y1] * d + y; + worldVertices[X2] = offset[X2] * a + offset[Y2] * b + x; + worldVertices[Y2] = offset[X2] * c + offset[Y2] * d + y; + worldVertices[X3] = offset[X3] * a + offset[Y3] * b + x; + worldVertices[Y3] = offset[X3] * c + offset[Y3] * d + y; + worldVertices[X4] = offset[X4] * a + offset[Y4] * b + x; + worldVertices[Y4] = offset[X4] * c + offset[Y4] * d + y; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Attachments/VertexAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Attachments/VertexAttachment.cs index ce8cb76..78f866c 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Attachments/VertexAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Attachments/VertexAttachment.cs @@ -29,92 +29,107 @@ *****************************************************************************/ using System; -using System.Collections.Generic; -namespace Spine3_5_51 { - /// >An attachment with vertices that are transformed by one or more bones and can be deformed by a slot's vertices. - public class VertexAttachment : Attachment { - internal int[] bones; - internal float[] vertices; - internal int worldVerticesLength; +namespace Spine3_5_51 +{ + /// >An attachment with vertices that are transformed by one or more bones and can be deformed by a slot's vertices. + public class VertexAttachment : Attachment + { + internal int[] bones; + internal float[] vertices; + internal int worldVerticesLength; - public int[] Bones { get { return bones; } set { bones = value; } } - public float[] Vertices { get { return vertices; } set { vertices = value; } } - public int WorldVerticesLength { get { return worldVerticesLength; } set { worldVerticesLength = value; } } + public int[] Bones { get { return bones; } set { bones = value; } } + public float[] Vertices { get { return vertices; } set { vertices = value; } } + public int WorldVerticesLength { get { return worldVerticesLength; } set { worldVerticesLength = value; } } - public VertexAttachment (String name) - : base(name) { - } + public VertexAttachment(String name) + : base(name) + { + } - public void ComputeWorldVertices (Slot slot, float[] worldVertices) { - ComputeWorldVertices(slot, 0, worldVerticesLength, worldVertices, 0); - } + public void ComputeWorldVertices(Slot slot, float[] worldVertices) + { + ComputeWorldVertices(slot, 0, worldVerticesLength, worldVertices, 0); + } - /// Transforms local vertices to world coordinates. - /// The index of the first value to transform. Each vertex has 2 values, x and y. - /// The number of world vertex values to output. Must be less than or equal to - start. - /// The output world vertices. Must have a length greater than or equal to + . - /// The index to begin writing values. - public void ComputeWorldVertices (Slot slot, int start, int count, float[] worldVertices, int offset) { - count += offset; - Skeleton skeleton = slot.Skeleton; - var deformArray = slot.attachmentVertices; - float[] vertices = this.vertices; - int[] bones = this.bones; - if (bones == null) { - if (deformArray.Count > 0) vertices = deformArray.Items; - Bone bone = slot.bone; - float x = bone.worldX, y = bone.worldY; - float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - for (int vv = start, w = offset; w < count; vv += 2, w += 2) { - float vx = vertices[vv], 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 (int i = 0; i < start; i += 2) { - int n = bones[v]; - v += n + 1; - skip += n; - } - Bone[] skeletonBones = skeleton.Bones.Items; - if (deformArray.Count == 0) { - for (int w = offset, b = skip * 3; w < count; w += 2) { - float wx = 0, wy = 0; - int n = bones[v++]; - n += v; - for (; v < n; v++, b += 3) { - Bone bone = skeletonBones[bones[v]]; - float vx = vertices[b], vy = vertices[b + 1], 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 { - float[] deform = deformArray.Items; - for (int w = offset, b = skip * 3, f = skip << 1; w < count; w += 2) { - float wx = 0, wy = 0; - int n = bones[v++]; - n += v; - for (; v < n; v++, b += 3, f += 2) { - Bone bone = skeletonBones[bones[v]]; - float vx = vertices[b] + deform[f], vy = vertices[b + 1] + deform[f + 1], 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; - } - } - } + /// Transforms local vertices to world coordinates. + /// The index of the first value to transform. Each vertex has 2 values, x and y. + /// The number of world vertex values to output. Must be less than or equal to - start. + /// The output world vertices. Must have a length greater than or equal to + . + /// The index to begin writing values. + public void ComputeWorldVertices(Slot slot, int start, int count, float[] worldVertices, int offset) + { + count += offset; + Skeleton skeleton = slot.Skeleton; + var deformArray = slot.attachmentVertices; + float[] vertices = this.vertices; + int[] bones = this.bones; + if (bones == null) + { + if (deformArray.Count > 0) vertices = deformArray.Items; + Bone bone = slot.bone; + float x = bone.worldX, y = bone.worldY; + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + for (int vv = start, w = offset; w < count; vv += 2, w += 2) + { + float vx = vertices[vv], 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 (int i = 0; i < start; i += 2) + { + int n = bones[v]; + v += n + 1; + skip += n; + } + Bone[] skeletonBones = skeleton.Bones.Items; + if (deformArray.Count == 0) + { + for (int w = offset, b = skip * 3; w < count; w += 2) + { + float wx = 0, wy = 0; + int n = bones[v++]; + n += v; + for (; v < n; v++, b += 3) + { + Bone bone = skeletonBones[bones[v]]; + float vx = vertices[b], vy = vertices[b + 1], 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 + { + float[] deform = deformArray.Items; + for (int w = offset, b = skip * 3, f = skip << 1; w < count; w += 2) + { + float wx = 0, wy = 0; + int n = bones[v++]; + n += v; + for (; v < n; v++, b += 3, f += 2) + { + Bone bone = skeletonBones[bones[v]]; + float vx = vertices[b] + deform[f], vy = vertices[b + 1] + deform[f + 1], 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; + } + } + } - /// Returns true if a deform originally applied to the specified attachment should be applied to this attachment. - virtual public bool ApplyDeform (VertexAttachment sourceAttachment) { - return this == sourceAttachment; - } - } + /// Returns true if a deform originally applied to the specified attachment should be applied to this attachment. + virtual public bool ApplyDeform(VertexAttachment sourceAttachment) + { + return this == sourceAttachment; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/BlendMode.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/BlendMode.cs index 9d4fc8f..0e73578 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/BlendMode.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/BlendMode.cs @@ -28,8 +28,10 @@ * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine3_5_51 { - public enum BlendMode { - normal, additive, multiply, screen - } +namespace Spine3_5_51 +{ + public enum BlendMode + { + normal, additive, multiply, screen + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Bone.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Bone.cs index 5707a4f..6719ae3 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Bone.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Bone.cs @@ -30,306 +30,340 @@ using System; -namespace Spine3_5_51 { - public class Bone : IUpdatable { - static public bool yDown; +namespace Spine3_5_51 +{ + public class Bone : IUpdatable + { + static public bool yDown; - internal BoneData data; - internal Skeleton skeleton; - internal Bone parent; - internal ExposedList children = new ExposedList(); - internal float x, y, rotation, scaleX, scaleY, shearX, shearY; - internal float ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY; - internal bool appliedValid; + internal BoneData data; + internal Skeleton skeleton; + internal Bone parent; + internal ExposedList children = new ExposedList(); + internal float x, y, rotation, scaleX, scaleY, shearX, shearY; + internal float ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY; + internal bool appliedValid; - internal float a, b, worldX; - internal float c, d, worldY; + internal float a, b, worldX; + internal float c, d, worldY; -// internal float worldSignX, worldSignY; -// public float WorldSignX { get { return worldSignX; } } -// public float WorldSignY { get { return worldSignY; } } + // internal float worldSignX, worldSignY; + // public float WorldSignX { get { return worldSignX; } } + // public float WorldSignY { get { return worldSignY; } } - internal bool sorted; + internal bool sorted; - public BoneData Data { get { return data; } } - public Skeleton Skeleton { get { return skeleton; } } - public Bone Parent { get { return parent; } } - public ExposedList Children { get { return children; } } - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public float Rotation { get { return rotation; } set { rotation = value; } } - /// The rotation, as calculated by any constraints. - public float AppliedRotation { get { return arotation; } set { arotation = value; } } - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - public float ScaleY { get { return scaleY; } set { scaleY = value; } } - public float ShearX { get { return shearX; } set { shearX = value; } } - public float ShearY { get { return shearY; } set { shearY = value; } } + public BoneData Data { get { return data; } } + public Skeleton Skeleton { get { return skeleton; } } + public Bone Parent { get { return parent; } } + public ExposedList Children { get { return children; } } + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public float Rotation { get { return rotation; } set { rotation = value; } } + /// The rotation, as calculated by any constraints. + public float AppliedRotation { get { return arotation; } set { arotation = value; } } + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + public float ShearX { get { return shearX; } set { shearX = value; } } + public float ShearY { get { return shearY; } set { shearY = value; } } - public float A { get { return a; } } - public float B { get { return b; } } - public float C { get { return c; } } - public float D { get { return d; } } - public float WorldX { get { return worldX; } } - public float WorldY { get { return worldY; } } - public float WorldRotationX { get { return MathUtils.Atan2(c, a) * MathUtils.RadDeg; } } - public float WorldRotationY { get { return MathUtils.Atan2(d, b) * MathUtils.RadDeg; } } + public float A { get { return a; } } + public float B { get { return b; } } + public float C { get { return c; } } + public float D { get { return d; } } + public float WorldX { get { return worldX; } } + public float WorldY { get { return worldY; } } + public float WorldRotationX { get { return MathUtils.Atan2(c, a) * MathUtils.RadDeg; } } + public float WorldRotationY { get { return MathUtils.Atan2(d, b) * MathUtils.RadDeg; } } - /// Returns the magnitide (always positive) of the world scale X. - public float WorldScaleX { get { return (float)Math.Sqrt(a * a + c * c); } } - /// Returns the magnitide (always positive) of the world scale Y. - public float WorldScaleY { get { return (float)Math.Sqrt(b * b + d * d); } } + /// Returns the magnitide (always positive) of the world scale X. + public float WorldScaleX { get { return (float)Math.Sqrt(a * a + c * c); } } + /// Returns the magnitide (always positive) of the world scale Y. + public float WorldScaleY { get { return (float)Math.Sqrt(b * b + d * d); } } - /// May be null. - public Bone (BoneData data, Skeleton skeleton, Bone parent) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.data = data; - this.skeleton = skeleton; - this.parent = parent; - SetToSetupPose(); - } + /// May be null. + public Bone(BoneData data, Skeleton skeleton, Bone parent) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + this.skeleton = skeleton; + this.parent = parent; + SetToSetupPose(); + } - /// Same as . This method exists for Bone to implement . - public void Update () { - UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); - } + /// Same as . This method exists for Bone to implement . + public void Update() + { + UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); + } - /// Computes the world transform using the parent bone and this bone's local transform. - public void UpdateWorldTransform () { - UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); - } + /// Computes the world transform using the parent bone and this bone's local transform. + public void UpdateWorldTransform() + { + UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); + } - /// Computes the world transform using the parent bone and the specified local transform. - public void UpdateWorldTransform (float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY) { - ax = x; - ay = y; - arotation = rotation; - ascaleX = scaleX; - ascaleY = scaleY; - ashearX = shearX; - ashearY = shearY; - appliedValid = true; - Skeleton skeleton = this.skeleton; + /// Computes the world transform using the parent bone and the specified local transform. + public void UpdateWorldTransform(float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY) + { + ax = x; + ay = y; + arotation = rotation; + ascaleX = scaleX; + ascaleY = scaleY; + ashearX = shearX; + ashearY = shearY; + appliedValid = true; + Skeleton skeleton = this.skeleton; - Bone parent = this.parent; - if (parent == null) { // Root bone. - float rotationY = rotation + 90 + shearY; - float la = MathUtils.CosDeg(rotation + shearX) * scaleX; - float lb = MathUtils.CosDeg(rotationY) * scaleY; - float lc = MathUtils.SinDeg(rotation + shearX) * scaleX; - float ld = MathUtils.SinDeg(rotationY) * scaleY; - if (skeleton.flipX) { - x = -x; - la = -la; - lb = -lb; - } - if (skeleton.flipY != yDown) { - y = -y; - lc = -lc; - ld = -ld; - } - a = la; - b = lb; - c = lc; - d = ld; - worldX = x + skeleton.x; - worldY = y + skeleton.y; -// worldSignX = Math.Sign(scaleX); -// worldSignY = Math.Sign(scaleY); - return; - } + Bone parent = this.parent; + if (parent == null) + { // Root bone. + float rotationY = rotation + 90 + shearY; + float la = MathUtils.CosDeg(rotation + shearX) * scaleX; + float lb = MathUtils.CosDeg(rotationY) * scaleY; + float lc = MathUtils.SinDeg(rotation + shearX) * scaleX; + float ld = MathUtils.SinDeg(rotationY) * scaleY; + if (skeleton.flipX) + { + x = -x; + la = -la; + lb = -lb; + } + if (skeleton.flipY != yDown) + { + y = -y; + lc = -lc; + ld = -ld; + } + a = la; + b = lb; + c = lc; + d = ld; + worldX = x + skeleton.x; + worldY = y + skeleton.y; + // worldSignX = Math.Sign(scaleX); + // worldSignY = Math.Sign(scaleY); + return; + } - float 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; -// worldSignX = parent.worldSignX * Math.Sign(scaleX); -// worldSignY = parent.worldSignY * Math.Sign(scaleY); + float 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; + // worldSignX = parent.worldSignX * Math.Sign(scaleX); + // worldSignY = parent.worldSignY * Math.Sign(scaleY); - switch (data.transformMode) { - case TransformMode.Normal: { - float rotationY = rotation + 90 + shearY; - float la = MathUtils.CosDeg(rotation + shearX) * scaleX; - float lb = MathUtils.CosDeg(rotationY) * scaleY; - float lc = MathUtils.SinDeg(rotation + shearX) * scaleX; - float ld = MathUtils.SinDeg(rotationY) * scaleY; - a = pa * la + pb * lc; - b = pa * lb + pb * ld; - c = pc * la + pd * lc; - d = pc * lb + pd * ld; - return; - } - case TransformMode.OnlyTranslation: { - float rotationY = rotation + 90 + shearY; - a = MathUtils.CosDeg(rotation + shearX) * scaleX; - b = MathUtils.CosDeg(rotationY) * scaleY; - c = MathUtils.SinDeg(rotation + shearX) * scaleX; - d = MathUtils.SinDeg(rotationY) * scaleY; - break; - } - case TransformMode.NoRotationOrReflection: { - float s = pa * pa + pc * pc, prx; - if (s > 0.0001f) { - s = Math.Abs(pa * pd - pb * pc) / s; - pb = pc * s; - pd = pa * s; - prx = MathUtils.Atan2(pc, pa) * MathUtils.RadDeg; - } else { - pa = 0; - pc = 0; - prx = 90 - MathUtils.Atan2(pd, pb) * MathUtils.RadDeg; - } - float rx = rotation + shearX - prx; - float ry = rotation + shearY - prx + 90; - float la = MathUtils.CosDeg(rx) * scaleX; - float lb = MathUtils.CosDeg(ry) * scaleY; - float lc = MathUtils.SinDeg(rx) * scaleX; - float ld = MathUtils.SinDeg(ry) * scaleY; - a = pa * la - pb * lc; - b = pa * lb - pb * ld; - c = pc * la + pd * lc; - d = pc * lb + pd * ld; - break; - } - case TransformMode.NoScale: - case TransformMode.NoScaleOrReflection: { - float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation); - float za = pa * cos + pb * sin; - float zc = pc * cos + pd * sin; - float s = (float)Math.Sqrt(za * za + zc * zc); - if (s > 0.00001f) s = 1 / s; - za *= s; - zc *= s; - s = (float)Math.Sqrt(za * za + zc * zc); - float r = MathUtils.PI / 2 + MathUtils.Atan2(zc, za); - float zb = MathUtils.Cos(r) * s; - float zd = MathUtils.Sin(r) * s; - float la = MathUtils.CosDeg(shearX) * scaleX; - float lb = MathUtils.CosDeg(90 + shearY) * scaleY; - float lc = MathUtils.SinDeg(shearX) * scaleX; - float ld = MathUtils.SinDeg(90 + shearY) * scaleY; - a = za * la + zb * lc; - b = za * lb + zb * ld; - c = zc * la + zd * lc; - d = zc * lb + zd * ld; - if (data.transformMode != TransformMode.NoScaleOrReflection ? pa * pd - pb * pc < 0 : skeleton.flipX != skeleton.flipY) { - b = -b; - d = -d; - } - return; - } - } + switch (data.transformMode) + { + case TransformMode.Normal: + { + float rotationY = rotation + 90 + shearY; + float la = MathUtils.CosDeg(rotation + shearX) * scaleX; + float lb = MathUtils.CosDeg(rotationY) * scaleY; + float lc = MathUtils.SinDeg(rotation + shearX) * scaleX; + float ld = MathUtils.SinDeg(rotationY) * scaleY; + a = pa * la + pb * lc; + b = pa * lb + pb * ld; + c = pc * la + pd * lc; + d = pc * lb + pd * ld; + return; + } + case TransformMode.OnlyTranslation: + { + float rotationY = rotation + 90 + shearY; + a = MathUtils.CosDeg(rotation + shearX) * scaleX; + b = MathUtils.CosDeg(rotationY) * scaleY; + c = MathUtils.SinDeg(rotation + shearX) * scaleX; + d = MathUtils.SinDeg(rotationY) * scaleY; + break; + } + case TransformMode.NoRotationOrReflection: + { + float s = pa * pa + pc * pc, prx; + if (s > 0.0001f) + { + s = Math.Abs(pa * pd - pb * pc) / s; + pb = pc * s; + pd = pa * s; + prx = MathUtils.Atan2(pc, pa) * MathUtils.RadDeg; + } + else + { + pa = 0; + pc = 0; + prx = 90 - MathUtils.Atan2(pd, pb) * MathUtils.RadDeg; + } + float rx = rotation + shearX - prx; + float ry = rotation + shearY - prx + 90; + float la = MathUtils.CosDeg(rx) * scaleX; + float lb = MathUtils.CosDeg(ry) * scaleY; + float lc = MathUtils.SinDeg(rx) * scaleX; + float ld = MathUtils.SinDeg(ry) * scaleY; + a = pa * la - pb * lc; + b = pa * lb - pb * ld; + c = pc * la + pd * lc; + d = pc * lb + pd * ld; + break; + } + case TransformMode.NoScale: + case TransformMode.NoScaleOrReflection: + { + float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation); + float za = pa * cos + pb * sin; + float zc = pc * cos + pd * sin; + float s = (float)Math.Sqrt(za * za + zc * zc); + if (s > 0.00001f) s = 1 / s; + za *= s; + zc *= s; + s = (float)Math.Sqrt(za * za + zc * zc); + float r = MathUtils.PI / 2 + MathUtils.Atan2(zc, za); + float zb = MathUtils.Cos(r) * s; + float zd = MathUtils.Sin(r) * s; + float la = MathUtils.CosDeg(shearX) * scaleX; + float lb = MathUtils.CosDeg(90 + shearY) * scaleY; + float lc = MathUtils.SinDeg(shearX) * scaleX; + float ld = MathUtils.SinDeg(90 + shearY) * scaleY; + a = za * la + zb * lc; + b = za * lb + zb * ld; + c = zc * la + zd * lc; + d = zc * lb + zd * ld; + if (data.transformMode != TransformMode.NoScaleOrReflection ? pa * pd - pb * pc < 0 : skeleton.flipX != skeleton.flipY) + { + b = -b; + d = -d; + } + return; + } + } - if (skeleton.flipX) { - a = -a; - b = -b; - } - if (skeleton.flipY != Bone.yDown) { - c = -c; - d = -d; - } - } + if (skeleton.flipX) + { + a = -a; + b = -b; + } + if (skeleton.flipY != Bone.yDown) + { + c = -c; + d = -d; + } + } - public void SetToSetupPose () { - BoneData data = this.data; - x = data.x; - y = data.y; - rotation = data.rotation; - scaleX = data.scaleX; - scaleY = data.scaleY; - shearX = data.shearX; - shearY = data.shearY; - } + public void SetToSetupPose() + { + BoneData data = this.data; + x = data.x; + y = data.y; + rotation = data.rotation; + scaleX = data.scaleX; + scaleY = data.scaleY; + shearX = data.shearX; + shearY = data.shearY; + } - public float WorldToLocalRotationX { - get { - Bone parent = this.parent; - if (parent == null) return arotation; - float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, a = this.a, c = this.c; - return MathUtils.Atan2(pa * c - pc * a, pd * a - pb * c) * MathUtils.RadDeg; - } - } + public float WorldToLocalRotationX + { + get + { + Bone parent = this.parent; + if (parent == null) return arotation; + float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, a = this.a, c = this.c; + return MathUtils.Atan2(pa * c - pc * a, pd * a - pb * c) * MathUtils.RadDeg; + } + } - public float WorldToLocalRotationY { - get { - Bone parent = this.parent; - if (parent == null) return arotation; - float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, b = this.b, d = this.d; - return MathUtils.Atan2(pa * d - pc * b, pd * b - pb * d) * MathUtils.RadDeg; - } - } + public float WorldToLocalRotationY + { + get + { + Bone parent = this.parent; + if (parent == null) return arotation; + float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, b = this.b, d = this.d; + return MathUtils.Atan2(pa * d - pc * b, pd * b - pb * d) * MathUtils.RadDeg; + } + } - public void RotateWorld (float degrees) { - float a = this.a, b = this.b, c = this.c, d = this.d; - float cos = MathUtils.CosDeg(degrees), sin = MathUtils.SinDeg(degrees); - this.a = cos * a - sin * c; - this.b = cos * b - sin * d; - this.c = sin * a + cos * c; - this.d = sin * b + cos * d; - appliedValid = false; - } + public void RotateWorld(float degrees) + { + float a = this.a, b = this.b, c = this.c, d = this.d; + float cos = MathUtils.CosDeg(degrees), sin = MathUtils.SinDeg(degrees); + this.a = cos * a - sin * c; + this.b = cos * b - sin * d; + this.c = sin * a + cos * c; + this.d = sin * b + cos * d; + appliedValid = false; + } - /// - /// 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. - /// - internal void UpdateAppliedTransform () { - appliedValid = true; - Bone parent = this.parent; - if (parent == null) { - ax = worldX; - ay = worldY; - arotation = MathUtils.Atan2(c, a) * MathUtils.RadDeg; - ascaleX = (float)Math.Sqrt(a * a + c * c); - ascaleY = (float)Math.Sqrt(b * b + d * d); - ashearX = 0; - ashearY = MathUtils.Atan2(a * b + c * d, a * d - b * c) * MathUtils.RadDeg; - return; - } - float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d; - float pid = 1 / (pa * pd - pb * pc); - float dx = worldX - parent.worldX, dy = worldY - parent.worldY; - ax = (dx * pd * pid - dy * pb * pid); - ay = (dy * pa * pid - dx * pc * pid); - float ia = pid * pd; - float id = pid * pa; - float ib = pid * pb; - float ic = pid * pc; - float ra = ia * a - ib * c; - float rb = ia * b - ib * d; - float rc = id * c - ic * a; - float rd = id * d - ic * b; - ashearX = 0; - ascaleX = (float)Math.Sqrt(ra * ra + rc * rc); - if (ascaleX > 0.0001f) { - float det = ra * rd - rb * rc; - ascaleY = det / ascaleX; - ashearY = MathUtils.Atan2(ra * rb + rc * rd, det) * MathUtils.RadDeg; - arotation = MathUtils.Atan2(rc, ra) * MathUtils.RadDeg; - } else { - ascaleX = 0; - ascaleY = (float)Math.Sqrt(rb * rb + rd * rd); - ashearY = 0; - arotation = 90 - MathUtils.Atan2(rd, rb) * MathUtils.RadDeg; - } - } + /// + /// 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. + /// + internal void UpdateAppliedTransform() + { + appliedValid = true; + Bone parent = this.parent; + if (parent == null) + { + ax = worldX; + ay = worldY; + arotation = MathUtils.Atan2(c, a) * MathUtils.RadDeg; + ascaleX = (float)Math.Sqrt(a * a + c * c); + ascaleY = (float)Math.Sqrt(b * b + d * d); + ashearX = 0; + ashearY = MathUtils.Atan2(a * b + c * d, a * d - b * c) * MathUtils.RadDeg; + return; + } + float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d; + float pid = 1 / (pa * pd - pb * pc); + float dx = worldX - parent.worldX, dy = worldY - parent.worldY; + ax = (dx * pd * pid - dy * pb * pid); + ay = (dy * pa * pid - dx * pc * pid); + float ia = pid * pd; + float id = pid * pa; + float ib = pid * pb; + float ic = pid * pc; + float ra = ia * a - ib * c; + float rb = ia * b - ib * d; + float rc = id * c - ic * a; + float rd = id * d - ic * b; + ashearX = 0; + ascaleX = (float)Math.Sqrt(ra * ra + rc * rc); + if (ascaleX > 0.0001f) + { + float det = ra * rd - rb * rc; + ascaleY = det / ascaleX; + ashearY = MathUtils.Atan2(ra * rb + rc * rd, det) * MathUtils.RadDeg; + arotation = MathUtils.Atan2(rc, ra) * MathUtils.RadDeg; + } + else + { + ascaleX = 0; + ascaleY = (float)Math.Sqrt(rb * rb + rd * rd); + ashearY = 0; + arotation = 90 - MathUtils.Atan2(rd, rb) * MathUtils.RadDeg; + } + } - public void WorldToLocal (float worldX, float worldY, out float localX, out float localY) { - float a = this.a, b = this.b, c = this.c, d = this.d; - float invDet = 1 / (a * d - b * c); - float x = worldX - this.worldX, y = worldY - this.worldY; - localX = (x * d * invDet - y * b * invDet); - localY = (y * a * invDet - x * c * invDet); - } + public void WorldToLocal(float worldX, float worldY, out float localX, out float localY) + { + float a = this.a, b = this.b, c = this.c, d = this.d; + float invDet = 1 / (a * d - b * c); + float x = worldX - this.worldX, y = worldY - this.worldY; + localX = (x * d * invDet - y * b * invDet); + localY = (y * a * invDet - x * c * invDet); + } - public void LocalToWorld (float localX, float localY, out float worldX, out float worldY) { - worldX = localX * a + localY * b + this.worldX; - worldY = localX * c + localY * d + this.worldY; - } + public void LocalToWorld(float localX, float localY, out float worldX, out float worldY) + { + worldX = localX * a + localY * b + this.worldX; + worldY = localX * c + localY * d + this.worldY; + } - override public String ToString () { - return data.name; - } - } + override public String ToString() + { + return data.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/BoneData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/BoneData.cs index 86709ac..a53b191 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/BoneData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/BoneData.cs @@ -30,54 +30,59 @@ using System; -namespace Spine3_5_51 { - public class BoneData { - internal int index; - internal String name; - internal BoneData parent; - internal float length; - internal float x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY; - internal TransformMode transformMode = TransformMode.Normal; - //internal bool inheritRotation = true, inheritScale = true; +namespace Spine3_5_51 +{ + public class BoneData + { + internal int index; + internal String name; + internal BoneData parent; + internal float length; + internal float x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY; + internal TransformMode transformMode = TransformMode.Normal; + //internal bool inheritRotation = true, inheritScale = true; - /// May be null. - public int Index { get { return index; } } - public String Name { get { return name; } } - public BoneData Parent { get { return parent; } } - public float Length { get { return length; } set { length = value; } } - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public float Rotation { get { return rotation; } set { rotation = value; } } - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - public float ScaleY { get { return scaleY; } set { scaleY = value; } } - public float ShearX { get { return shearX; } set { shearX = value; } } - public float ShearY { get { return shearY; } set { shearY = value; } } - public TransformMode TransformMode { get { return transformMode; } set { transformMode = value; } } -// public bool InheritRotation { get { return inheritRotation; } set { inheritRotation = value; } } -// public bool InheritScale { get { return inheritScale; } set { inheritScale = value; } } + /// May be null. + public int Index { get { return index; } } + public String Name { get { return name; } } + public BoneData Parent { get { return parent; } } + public float Length { get { return length; } set { length = value; } } + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public float Rotation { get { return rotation; } set { rotation = value; } } + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + public float ShearX { get { return shearX; } set { shearX = value; } } + public float ShearY { get { return shearY; } set { shearY = value; } } + public TransformMode TransformMode { get { return transformMode; } set { transformMode = value; } } + // public bool InheritRotation { get { return inheritRotation; } set { inheritRotation = value; } } + // public bool InheritScale { get { return inheritScale; } set { inheritScale = value; } } - /// May be null. - public BoneData (int index, String name, BoneData parent) { - if (index < 0) throw new ArgumentException("index must be >= 0", "index"); - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.index = index; - this.name = name; - this.parent = parent; - } + /// May be null. + public BoneData(int index, String name, BoneData parent) + { + if (index < 0) throw new ArgumentException("index must be >= 0", "index"); + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.index = index; + this.name = name; + this.parent = parent; + } - override public String ToString () { - return name; - } - } + override public String ToString() + { + return name; + } + } - [Flags] - public enum TransformMode { - //0000 0FSR - Normal = 0, // 0000 - OnlyTranslation = 7, // 0111 - NoRotationOrReflection = 1, // 0001 - NoScale = 2, // 0010 - NoScaleOrReflection = 6, // 0110 - } + [Flags] + public enum TransformMode + { + //0000 0FSR + Normal = 0, // 0000 + OnlyTranslation = 7, // 0111 + NoRotationOrReflection = 1, // 0001 + NoScale = 2, // 0010 + NoScaleOrReflection = 6, // 0110 + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Event.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Event.cs index a82dce2..475e6b1 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Event.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Event.cs @@ -30,28 +30,32 @@ using System; -namespace Spine3_5_51 { - public class Event { - internal readonly EventData data; - internal readonly float time; - internal int intValue; - internal float floatValue; - internal string stringValue; +namespace Spine3_5_51 +{ + public class Event + { + internal readonly EventData data; + internal readonly float time; + internal int intValue; + internal float floatValue; + internal string stringValue; - public EventData Data { get { return data; } } - public float Time { get { return time; } } - public int Int { get { return intValue; } set { intValue = value; } } - public float Float { get { return floatValue; } set { floatValue = value; } } - public String String { get { return stringValue; } set { stringValue = value; } } + public EventData Data { get { return data; } } + public float Time { get { return time; } } + public int Int { get { return intValue; } set { intValue = value; } } + public float Float { get { return floatValue; } set { floatValue = value; } } + public String String { get { return stringValue; } set { stringValue = value; } } - public Event (float time, EventData data) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - this.time = time; - this.data = data; - } + public Event(float time, EventData data) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + this.time = time; + this.data = data; + } - override public string ToString () { - return this.data.Name; - } - } + override public string ToString() + { + return this.data.Name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/EventData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/EventData.cs index 80e78d2..c80e494 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/EventData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/EventData.cs @@ -30,22 +30,26 @@ using System; -namespace Spine3_5_51 { - public class EventData { - internal String name; +namespace Spine3_5_51 +{ + public class EventData + { + internal String name; - public String Name { get { return name; } } - public int Int { get; set; } - public float Float { get; set; } - public String String { get; set; } + public String Name { get { return name; } } + public int Int { get; set; } + public float Float { get; set; } + public String String { get; set; } - public EventData (String name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; - } + public EventData(String name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + } - override public String ToString () { - return Name; - } - } + override public String ToString() + { + return Name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/ExposedList.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/ExposedList.cs index 6bfb944..54b2658 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/ExposedList.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/ExposedList.cs @@ -35,566 +35,658 @@ using System.Collections.Generic; using System.Diagnostics; -namespace Spine3_5_51 { - [Serializable] - [DebuggerDisplay("Count={Count}")] - public class ExposedList : IEnumerable { - public T[] Items; - public int Count; - private const int DefaultCapacity = 4; - private static readonly T[] EmptyArray = new T[0]; - private int version; - - public ExposedList () { - Items = EmptyArray; - } - - public ExposedList (IEnumerable collection) { - CheckCollection(collection); - - // initialize to needed size (if determinable) - ICollection c = collection as ICollection; - if (c == null) { - Items = EmptyArray; - AddEnumerable(collection); - } else { - Items = new T[c.Count]; - AddCollection(c); - } - } - - public ExposedList (int capacity) { - if (capacity < 0) - throw new ArgumentOutOfRangeException("capacity"); - Items = new T[capacity]; - } - - internal ExposedList (T[] data, int size) { - Items = data; - Count = size; - } - - public void Add (T item) { - // If we check to see if we need to grow before trying to grow - // we can speed things up by 25% - if (Count == Items.Length) - GrowIfNeeded(1); - Items[Count++] = item; - version++; - } - - public void GrowIfNeeded (int newCount) { - int minimumSize = Count + newCount; - if (minimumSize > Items.Length) - Capacity = Math.Max(Math.Max(Capacity * 2, DefaultCapacity), minimumSize); - } - - public ExposedList Resize (int newSize) { - if (newSize > Items.Length) Array.Resize(ref Items, newSize); - Count = newSize; - return this; - } - - public void EnsureCapacity (int min) { - if (Items.Length < min) { - int newCapacity = Items.Length == 0 ? DefaultCapacity : Items.Length * 2; - //if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength; - if (newCapacity < min) newCapacity = min; - Capacity = newCapacity; - } - } - - private void CheckRange (int idx, int count) { - if (idx < 0) - throw new ArgumentOutOfRangeException("index"); - - if (count < 0) - throw new ArgumentOutOfRangeException("count"); - - if ((uint)idx + (uint)count > (uint)Count) - throw new ArgumentException("index and count exceed length of list"); - } - - private void AddCollection (ICollection collection) { - int collectionCount = collection.Count; - if (collectionCount == 0) - return; - - GrowIfNeeded(collectionCount); - collection.CopyTo(Items, Count); - Count += collectionCount; - } - - private void AddEnumerable (IEnumerable enumerable) { - foreach (T t in enumerable) { - Add(t); - } - } - - public void AddRange (IEnumerable collection) { - CheckCollection(collection); - - ICollection c = collection as ICollection; - if (c != null) - AddCollection(c); - else - AddEnumerable(collection); - version++; - } - - public int BinarySearch (T item) { - return Array.BinarySearch(Items, 0, Count, item); - } - - public int BinarySearch (T item, IComparer comparer) { - return Array.BinarySearch(Items, 0, Count, item, comparer); - } - - public int BinarySearch (int index, int count, T item, IComparer comparer) { - CheckRange(index, count); - return Array.BinarySearch(Items, index, count, item, comparer); - } - - public void Clear (bool clearArray = true) { - if (clearArray) - Array.Clear(Items, 0, Items.Length); - - Count = 0; - version++; - } - - public bool Contains (T item) { - return Array.IndexOf(Items, item, 0, Count) != -1; - } - - public ExposedList ConvertAll (Converter converter) { - if (converter == null) - throw new ArgumentNullException("converter"); - ExposedList u = new ExposedList(Count); - for (int i = 0; i < Count; i++) - u.Items[i] = converter(Items[i]); - - u.Count = Count; - return u; - } - - public void CopyTo (T[] array) { - Array.Copy(Items, 0, array, 0, Count); - } - - public void CopyTo (T[] array, int arrayIndex) { - Array.Copy(Items, 0, array, arrayIndex, Count); - } - - public void CopyTo (int index, T[] array, int arrayIndex, int count) { - CheckRange(index, count); - Array.Copy(Items, index, array, arrayIndex, count); - } - - - - public bool Exists (Predicate match) { - CheckMatch(match); - return GetIndex(0, Count, match) != -1; - } - - public T Find (Predicate match) { - CheckMatch(match); - int i = GetIndex(0, Count, match); - return (i != -1) ? Items[i] : default(T); - } - - private static void CheckMatch (Predicate match) { - if (match == null) - throw new ArgumentNullException("match"); - } - - public ExposedList FindAll (Predicate match) { - CheckMatch(match); - return FindAllList(match); - } - - private ExposedList FindAllList (Predicate match) { - ExposedList results = new ExposedList(); - for (int i = 0; i < Count; i++) - if (match(Items[i])) - results.Add(Items[i]); - - return results; - } - - public int FindIndex (Predicate match) { - CheckMatch(match); - return GetIndex(0, Count, match); - } - - public int FindIndex (int startIndex, Predicate match) { - CheckMatch(match); - CheckIndex(startIndex); - return GetIndex(startIndex, Count - startIndex, match); - } - - public int FindIndex (int startIndex, int count, Predicate match) { - CheckMatch(match); - CheckRange(startIndex, count); - return GetIndex(startIndex, count, match); - } - - private int GetIndex (int startIndex, int count, Predicate match) { - int end = startIndex + count; - for (int i = startIndex; i < end; i++) - if (match(Items[i])) - return i; - - return -1; - } - - public T FindLast (Predicate match) { - CheckMatch(match); - int i = GetLastIndex(0, Count, match); - return i == -1 ? default(T) : Items[i]; - } - - public int FindLastIndex (Predicate match) { - CheckMatch(match); - return GetLastIndex(0, Count, match); - } - - public int FindLastIndex (int startIndex, Predicate match) { - CheckMatch(match); - CheckIndex(startIndex); - return GetLastIndex(0, startIndex + 1, match); - } - - public int FindLastIndex (int startIndex, int count, Predicate match) { - CheckMatch(match); - int start = startIndex - count + 1; - CheckRange(start, count); - return GetLastIndex(start, count, match); - } - - private int GetLastIndex (int startIndex, int count, Predicate match) { - // unlike FindLastIndex, takes regular params for search range - for (int i = startIndex + count; i != startIndex; ) - if (match(Items[--i])) - return i; - return -1; - } - - public void ForEach (Action action) { - if (action == null) - throw new ArgumentNullException("action"); - for (int i = 0; i < Count; i++) - action(Items[i]); - } - - public Enumerator GetEnumerator () { - return new Enumerator(this); - } - - public ExposedList GetRange (int index, int count) { - CheckRange(index, count); - T[] tmpArray = new T[count]; - Array.Copy(Items, index, tmpArray, 0, count); - return new ExposedList(tmpArray, count); - } - - public int IndexOf (T item) { - return Array.IndexOf(Items, item, 0, Count); - } - - public int IndexOf (T item, int index) { - CheckIndex(index); - return Array.IndexOf(Items, item, index, Count - index); - } - - public int IndexOf (T item, int index, int count) { - if (index < 0) - throw new ArgumentOutOfRangeException("index"); - - if (count < 0) - throw new ArgumentOutOfRangeException("count"); - - if ((uint)index + (uint)count > (uint)Count) - throw new ArgumentOutOfRangeException("index and count exceed length of list"); - - return Array.IndexOf(Items, item, index, count); - } - - private void Shift (int start, int delta) { - if (delta < 0) - start -= delta; - - if (start < Count) - Array.Copy(Items, start, Items, start + delta, Count - start); - - Count += delta; - - if (delta < 0) - Array.Clear(Items, Count, -delta); - } - - private void CheckIndex (int index) { - if (index < 0 || (uint)index > (uint)Count) - throw new ArgumentOutOfRangeException("index"); - } - - public void Insert (int index, T item) { - CheckIndex(index); - if (Count == Items.Length) - GrowIfNeeded(1); - Shift(index, 1); - Items[index] = item; - version++; - } - - private void CheckCollection (IEnumerable collection) { - if (collection == null) - throw new ArgumentNullException("collection"); - } - - public void InsertRange (int index, IEnumerable collection) { - CheckCollection(collection); - CheckIndex(index); - if (collection == this) { - T[] buffer = new T[Count]; - CopyTo(buffer, 0); - GrowIfNeeded(Count); - Shift(index, buffer.Length); - Array.Copy(buffer, 0, Items, index, buffer.Length); - } else { - ICollection c = collection as ICollection; - if (c != null) - InsertCollection(index, c); - else - InsertEnumeration(index, collection); - } - version++; - } - - private void InsertCollection (int index, ICollection collection) { - int collectionCount = collection.Count; - GrowIfNeeded(collectionCount); - - Shift(index, collectionCount); - collection.CopyTo(Items, index); - } - - private void InsertEnumeration (int index, IEnumerable enumerable) { - foreach (T t in enumerable) - Insert(index++, t); - } - - public int LastIndexOf (T item) { - return Array.LastIndexOf(Items, item, Count - 1, Count); - } - - public int LastIndexOf (T item, int index) { - CheckIndex(index); - return Array.LastIndexOf(Items, item, index, index + 1); - } - - public int LastIndexOf (T item, int index, int count) { - if (index < 0) - throw new ArgumentOutOfRangeException("index", index, "index is negative"); - - if (count < 0) - throw new ArgumentOutOfRangeException("count", count, "count is negative"); - - if (index - count + 1 < 0) - throw new ArgumentOutOfRangeException("count", count, "count is too large"); - - return Array.LastIndexOf(Items, item, index, count); - } - - public bool Remove (T item) { - int loc = IndexOf(item); - if (loc != -1) - RemoveAt(loc); - - return loc != -1; - } - - public int RemoveAll (Predicate match) { - CheckMatch(match); - int i = 0; - int j = 0; - - // Find the first item to remove - for (i = 0; i < Count; i++) - if (match(Items[i])) - break; - - if (i == Count) - return 0; - - version++; - - // Remove any additional items - for (j = i + 1; j < Count; j++) { - if (!match(Items[j])) - Items[i++] = Items[j]; - } - if (j - i > 0) - Array.Clear(Items, i, j - i); - - Count = i; - return (j - i); - } - - public void RemoveAt (int index) { - if (index < 0 || (uint)index >= (uint)Count) - throw new ArgumentOutOfRangeException("index"); - Shift(index, -1); - Array.Clear(Items, Count, 1); - version++; - } - - public void RemoveRange (int index, int count) { - CheckRange(index, count); - if (count > 0) { - Shift(index, -count); - Array.Clear(Items, Count, count); - version++; - } - } - - public void Reverse () { - Array.Reverse(Items, 0, Count); - version++; - } - - public void Reverse (int index, int count) { - CheckRange(index, count); - Array.Reverse(Items, index, count); - version++; - } - - public void Sort () { - Array.Sort(Items, 0, Count, Comparer.Default); - version++; - } - - public void Sort (IComparer comparer) { - Array.Sort(Items, 0, Count, comparer); - version++; - } - - public void Sort (Comparison comparison) { - Array.Sort(Items, comparison); - version++; - } - - public void Sort (int index, int count, IComparer comparer) { - CheckRange(index, count); - Array.Sort(Items, index, count, comparer); - version++; - } - - public T[] ToArray () { - T[] t = new T[Count]; - Array.Copy(Items, t, Count); - - return t; - } - - public void TrimExcess () { - Capacity = Count; - } - - public bool TrueForAll (Predicate match) { - CheckMatch(match); - - for (int i = 0; i < Count; i++) - if (!match(Items[i])) - return false; - - return true; - } - - public int Capacity { - get { - return Items.Length; - } - set { - if ((uint)value < (uint)Count) - throw new ArgumentOutOfRangeException(); - - Array.Resize(ref Items, value); - } - } - - #region Interface implementations. - - IEnumerator IEnumerable.GetEnumerator () { - return GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator () { - return GetEnumerator(); - } - - #endregion - - [Serializable] - public struct Enumerator : IEnumerator, IDisposable { - private ExposedList l; - private int next; - private int ver; - private T current; - - internal Enumerator (ExposedList l) - : this() { - this.l = l; - ver = l.version; - } - - public void Dispose () { - l = null; - } - - private void VerifyState () { - if (l == null) - throw new ObjectDisposedException(GetType().FullName); - if (ver != l.version) - throw new InvalidOperationException( - "Collection was modified; enumeration operation may not execute."); - } - - public bool MoveNext () { - VerifyState(); - - if (next < 0) - return false; - - if (next < l.Count) { - current = l.Items[next++]; - return true; - } - - next = -1; - return false; - } - - public T Current { - get { - return current; - } - } - - void IEnumerator.Reset () { - VerifyState(); - next = 0; - } - - object IEnumerator.Current { - get { - VerifyState(); - if (next <= 0) - throw new InvalidOperationException(); - return current; - } - } - } - } +namespace Spine3_5_51 +{ + [Serializable] + [DebuggerDisplay("Count={Count}")] + public class ExposedList : IEnumerable + { + public T[] Items; + public int Count; + private const int DefaultCapacity = 4; + private static readonly T[] EmptyArray = new T[0]; + private int version; + + public ExposedList() + { + Items = EmptyArray; + } + + public ExposedList(IEnumerable collection) + { + CheckCollection(collection); + + // initialize to needed size (if determinable) + ICollection c = collection as ICollection; + if (c == null) + { + Items = EmptyArray; + AddEnumerable(collection); + } + else + { + Items = new T[c.Count]; + AddCollection(c); + } + } + + public ExposedList(int capacity) + { + if (capacity < 0) + throw new ArgumentOutOfRangeException("capacity"); + Items = new T[capacity]; + } + + internal ExposedList(T[] data, int size) + { + Items = data; + Count = size; + } + + public void Add(T item) + { + // If we check to see if we need to grow before trying to grow + // we can speed things up by 25% + if (Count == Items.Length) + GrowIfNeeded(1); + Items[Count++] = item; + version++; + } + + public void GrowIfNeeded(int newCount) + { + int minimumSize = Count + newCount; + if (minimumSize > Items.Length) + Capacity = Math.Max(Math.Max(Capacity * 2, DefaultCapacity), minimumSize); + } + + public ExposedList Resize(int newSize) + { + if (newSize > Items.Length) Array.Resize(ref Items, newSize); + Count = newSize; + return this; + } + + public void EnsureCapacity(int min) + { + if (Items.Length < min) + { + int newCapacity = Items.Length == 0 ? DefaultCapacity : Items.Length * 2; + //if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength; + if (newCapacity < min) newCapacity = min; + Capacity = newCapacity; + } + } + + private void CheckRange(int idx, int count) + { + if (idx < 0) + throw new ArgumentOutOfRangeException("index"); + + if (count < 0) + throw new ArgumentOutOfRangeException("count"); + + if ((uint)idx + (uint)count > (uint)Count) + throw new ArgumentException("index and count exceed length of list"); + } + + private void AddCollection(ICollection collection) + { + int collectionCount = collection.Count; + if (collectionCount == 0) + return; + + GrowIfNeeded(collectionCount); + collection.CopyTo(Items, Count); + Count += collectionCount; + } + + private void AddEnumerable(IEnumerable enumerable) + { + foreach (T t in enumerable) + { + Add(t); + } + } + + public void AddRange(IEnumerable collection) + { + CheckCollection(collection); + + ICollection c = collection as ICollection; + if (c != null) + AddCollection(c); + else + AddEnumerable(collection); + version++; + } + + public int BinarySearch(T item) + { + return Array.BinarySearch(Items, 0, Count, item); + } + + public int BinarySearch(T item, IComparer comparer) + { + return Array.BinarySearch(Items, 0, Count, item, comparer); + } + + public int BinarySearch(int index, int count, T item, IComparer comparer) + { + CheckRange(index, count); + return Array.BinarySearch(Items, index, count, item, comparer); + } + + public void Clear(bool clearArray = true) + { + if (clearArray) + Array.Clear(Items, 0, Items.Length); + + Count = 0; + version++; + } + + public bool Contains(T item) + { + return Array.IndexOf(Items, item, 0, Count) != -1; + } + + public ExposedList ConvertAll(Converter converter) + { + if (converter == null) + throw new ArgumentNullException("converter"); + ExposedList u = new ExposedList(Count); + for (int i = 0; i < Count; i++) + u.Items[i] = converter(Items[i]); + + u.Count = Count; + return u; + } + + public void CopyTo(T[] array) + { + Array.Copy(Items, 0, array, 0, Count); + } + + public void CopyTo(T[] array, int arrayIndex) + { + Array.Copy(Items, 0, array, arrayIndex, Count); + } + + public void CopyTo(int index, T[] array, int arrayIndex, int count) + { + CheckRange(index, count); + Array.Copy(Items, index, array, arrayIndex, count); + } + + + + public bool Exists(Predicate match) + { + CheckMatch(match); + return GetIndex(0, Count, match) != -1; + } + + public T Find(Predicate match) + { + CheckMatch(match); + int i = GetIndex(0, Count, match); + return (i != -1) ? Items[i] : default(T); + } + + private static void CheckMatch(Predicate match) + { + if (match == null) + throw new ArgumentNullException("match"); + } + + public ExposedList FindAll(Predicate match) + { + CheckMatch(match); + return FindAllList(match); + } + + private ExposedList FindAllList(Predicate match) + { + ExposedList results = new ExposedList(); + for (int i = 0; i < Count; i++) + if (match(Items[i])) + results.Add(Items[i]); + + return results; + } + + public int FindIndex(Predicate match) + { + CheckMatch(match); + return GetIndex(0, Count, match); + } + + public int FindIndex(int startIndex, Predicate match) + { + CheckMatch(match); + CheckIndex(startIndex); + return GetIndex(startIndex, Count - startIndex, match); + } + + public int FindIndex(int startIndex, int count, Predicate match) + { + CheckMatch(match); + CheckRange(startIndex, count); + return GetIndex(startIndex, count, match); + } + + private int GetIndex(int startIndex, int count, Predicate match) + { + int end = startIndex + count; + for (int i = startIndex; i < end; i++) + if (match(Items[i])) + return i; + + return -1; + } + + public T FindLast(Predicate match) + { + CheckMatch(match); + int i = GetLastIndex(0, Count, match); + return i == -1 ? default(T) : Items[i]; + } + + public int FindLastIndex(Predicate match) + { + CheckMatch(match); + return GetLastIndex(0, Count, match); + } + + public int FindLastIndex(int startIndex, Predicate match) + { + CheckMatch(match); + CheckIndex(startIndex); + return GetLastIndex(0, startIndex + 1, match); + } + + public int FindLastIndex(int startIndex, int count, Predicate match) + { + CheckMatch(match); + int start = startIndex - count + 1; + CheckRange(start, count); + return GetLastIndex(start, count, match); + } + + private int GetLastIndex(int startIndex, int count, Predicate match) + { + // unlike FindLastIndex, takes regular params for search range + for (int i = startIndex + count; i != startIndex;) + if (match(Items[--i])) + return i; + return -1; + } + + public void ForEach(Action action) + { + if (action == null) + throw new ArgumentNullException("action"); + for (int i = 0; i < Count; i++) + action(Items[i]); + } + + public Enumerator GetEnumerator() + { + return new Enumerator(this); + } + + public ExposedList GetRange(int index, int count) + { + CheckRange(index, count); + T[] tmpArray = new T[count]; + Array.Copy(Items, index, tmpArray, 0, count); + return new ExposedList(tmpArray, count); + } + + public int IndexOf(T item) + { + return Array.IndexOf(Items, item, 0, Count); + } + + public int IndexOf(T item, int index) + { + CheckIndex(index); + return Array.IndexOf(Items, item, index, Count - index); + } + + public int IndexOf(T item, int index, int count) + { + if (index < 0) + throw new ArgumentOutOfRangeException("index"); + + if (count < 0) + throw new ArgumentOutOfRangeException("count"); + + if ((uint)index + (uint)count > (uint)Count) + throw new ArgumentOutOfRangeException("index and count exceed length of list"); + + return Array.IndexOf(Items, item, index, count); + } + + private void Shift(int start, int delta) + { + if (delta < 0) + start -= delta; + + if (start < Count) + Array.Copy(Items, start, Items, start + delta, Count - start); + + Count += delta; + + if (delta < 0) + Array.Clear(Items, Count, -delta); + } + + private void CheckIndex(int index) + { + if (index < 0 || (uint)index > (uint)Count) + throw new ArgumentOutOfRangeException("index"); + } + + public void Insert(int index, T item) + { + CheckIndex(index); + if (Count == Items.Length) + GrowIfNeeded(1); + Shift(index, 1); + Items[index] = item; + version++; + } + + private void CheckCollection(IEnumerable collection) + { + if (collection == null) + throw new ArgumentNullException("collection"); + } + + public void InsertRange(int index, IEnumerable collection) + { + CheckCollection(collection); + CheckIndex(index); + if (collection == this) + { + T[] buffer = new T[Count]; + CopyTo(buffer, 0); + GrowIfNeeded(Count); + Shift(index, buffer.Length); + Array.Copy(buffer, 0, Items, index, buffer.Length); + } + else + { + ICollection c = collection as ICollection; + if (c != null) + InsertCollection(index, c); + else + InsertEnumeration(index, collection); + } + version++; + } + + private void InsertCollection(int index, ICollection collection) + { + int collectionCount = collection.Count; + GrowIfNeeded(collectionCount); + + Shift(index, collectionCount); + collection.CopyTo(Items, index); + } + + private void InsertEnumeration(int index, IEnumerable enumerable) + { + foreach (T t in enumerable) + Insert(index++, t); + } + + public int LastIndexOf(T item) + { + return Array.LastIndexOf(Items, item, Count - 1, Count); + } + + public int LastIndexOf(T item, int index) + { + CheckIndex(index); + return Array.LastIndexOf(Items, item, index, index + 1); + } + + public int LastIndexOf(T item, int index, int count) + { + if (index < 0) + throw new ArgumentOutOfRangeException("index", index, "index is negative"); + + if (count < 0) + throw new ArgumentOutOfRangeException("count", count, "count is negative"); + + if (index - count + 1 < 0) + throw new ArgumentOutOfRangeException("count", count, "count is too large"); + + return Array.LastIndexOf(Items, item, index, count); + } + + public bool Remove(T item) + { + int loc = IndexOf(item); + if (loc != -1) + RemoveAt(loc); + + return loc != -1; + } + + public int RemoveAll(Predicate match) + { + CheckMatch(match); + int i = 0; + int j = 0; + + // Find the first item to remove + for (i = 0; i < Count; i++) + if (match(Items[i])) + break; + + if (i == Count) + return 0; + + version++; + + // Remove any additional items + for (j = i + 1; j < Count; j++) + { + if (!match(Items[j])) + Items[i++] = Items[j]; + } + if (j - i > 0) + Array.Clear(Items, i, j - i); + + Count = i; + return (j - i); + } + + public void RemoveAt(int index) + { + if (index < 0 || (uint)index >= (uint)Count) + throw new ArgumentOutOfRangeException("index"); + Shift(index, -1); + Array.Clear(Items, Count, 1); + version++; + } + + public void RemoveRange(int index, int count) + { + CheckRange(index, count); + if (count > 0) + { + Shift(index, -count); + Array.Clear(Items, Count, count); + version++; + } + } + + public void Reverse() + { + Array.Reverse(Items, 0, Count); + version++; + } + + public void Reverse(int index, int count) + { + CheckRange(index, count); + Array.Reverse(Items, index, count); + version++; + } + + public void Sort() + { + Array.Sort(Items, 0, Count, Comparer.Default); + version++; + } + + public void Sort(IComparer comparer) + { + Array.Sort(Items, 0, Count, comparer); + version++; + } + + public void Sort(Comparison comparison) + { + Array.Sort(Items, comparison); + version++; + } + + public void Sort(int index, int count, IComparer comparer) + { + CheckRange(index, count); + Array.Sort(Items, index, count, comparer); + version++; + } + + public T[] ToArray() + { + T[] t = new T[Count]; + Array.Copy(Items, t, Count); + + return t; + } + + public void TrimExcess() + { + Capacity = Count; + } + + public bool TrueForAll(Predicate match) + { + CheckMatch(match); + + for (int i = 0; i < Count; i++) + if (!match(Items[i])) + return false; + + return true; + } + + public int Capacity + { + get + { + return Items.Length; + } + set + { + if ((uint)value < (uint)Count) + throw new ArgumentOutOfRangeException(); + + Array.Resize(ref Items, value); + } + } + + #region Interface implementations. + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion + + [Serializable] + public struct Enumerator : IEnumerator, IDisposable + { + private ExposedList l; + private int next; + private int ver; + private T current; + + internal Enumerator(ExposedList l) + : this() + { + this.l = l; + ver = l.version; + } + + public void Dispose() + { + l = null; + } + + private void VerifyState() + { + if (l == null) + throw new ObjectDisposedException(GetType().FullName); + if (ver != l.version) + throw new InvalidOperationException( + "Collection was modified; enumeration operation may not execute."); + } + + public bool MoveNext() + { + VerifyState(); + + if (next < 0) + return false; + + if (next < l.Count) + { + current = l.Items[next++]; + return true; + } + + next = -1; + return false; + } + + public T Current + { + get + { + return current; + } + } + + void IEnumerator.Reset() + { + VerifyState(); + next = 0; + } + + object IEnumerator.Current + { + get + { + VerifyState(); + if (next <= 0) + throw new InvalidOperationException(); + return current; + } + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/IConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/IConstraint.cs index d01e344..634a0a1 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/IConstraint.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/IConstraint.cs @@ -1,5 +1,7 @@ -namespace Spine3_5_51 { - public interface IConstraint : IUpdatable { - int Order { get; } - } +namespace Spine3_5_51 +{ + public interface IConstraint : IUpdatable + { + int Order { get; } + } } \ No newline at end of file diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/IUpdatable.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/IUpdatable.cs index 620e6ae..fb614fc 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/IUpdatable.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/IUpdatable.cs @@ -28,10 +28,10 @@ * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; - -namespace Spine3_5_51 { - public interface IUpdatable { - void Update (); - } +namespace Spine3_5_51 +{ + public interface IUpdatable + { + void Update(); + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/IkConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/IkConstraint.cs index df9383b..7b516bf 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/IkConstraint.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/IkConstraint.cs @@ -30,208 +30,239 @@ using System; -namespace Spine3_5_51 { - public class IkConstraint : IConstraint { - internal IkConstraintData data; - internal ExposedList bones = new ExposedList(); - internal Bone target; - internal float mix; - internal int bendDirection; +namespace Spine3_5_51 +{ + public class IkConstraint : IConstraint + { + internal IkConstraintData data; + internal ExposedList bones = new ExposedList(); + internal Bone target; + internal float mix; + internal int bendDirection; - public IkConstraintData Data { get { return data; } } - public int Order { get { return data.order; } } - public ExposedList Bones { get { return bones; } } - public Bone Target { get { return target; } set { target = value; } } - public int BendDirection { get { return bendDirection; } set { bendDirection = value; } } - public float Mix { get { return mix; } set { mix = value; } } + public IkConstraintData Data { get { return data; } } + public int Order { get { return data.order; } } + public ExposedList Bones { get { return bones; } } + public Bone Target { get { return target; } set { target = value; } } + public int BendDirection { get { return bendDirection; } set { bendDirection = value; } } + public float Mix { get { return mix; } set { mix = value; } } - public IkConstraint (IkConstraintData data, Skeleton skeleton) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.data = data; - mix = data.mix; - bendDirection = data.bendDirection; + public IkConstraint(IkConstraintData data, Skeleton skeleton) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + mix = data.mix; + bendDirection = data.bendDirection; - bones = new ExposedList(data.bones.Count); - foreach (BoneData boneData in data.bones) - bones.Add(skeleton.FindBone(boneData.name)); - target = skeleton.FindBone(data.target.name); - } + bones = new ExposedList(data.bones.Count); + foreach (BoneData boneData in data.bones) + bones.Add(skeleton.FindBone(boneData.name)); + target = skeleton.FindBone(data.target.name); + } - public void Update () { - Apply(); - } + public void Update() + { + Apply(); + } - public void Apply () { - Bone target = this.target; - ExposedList bones = this.bones; - switch (bones.Count) { - case 1: - Apply(bones.Items[0], target.worldX, target.worldY, mix); - break; - case 2: - Apply(bones.Items[0], bones.Items[1], target.worldX, target.worldY, bendDirection, mix); - break; - } - } + public void Apply() + { + Bone target = this.target; + ExposedList bones = this.bones; + switch (bones.Count) + { + case 1: + Apply(bones.Items[0], target.worldX, target.worldY, mix); + break; + case 2: + Apply(bones.Items[0], bones.Items[1], target.worldX, target.worldY, bendDirection, mix); + break; + } + } - override public String ToString () { - return data.name; - } + override public String ToString() + { + return data.name; + } - /// 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 public void Apply (Bone bone, float targetX, float targetY, float alpha) { - if (!bone.appliedValid) bone.UpdateAppliedTransform(); - Bone p = bone.parent; - float id = 1 / (p.a * p.d - p.b * p.c); - float x = targetX - p.worldX, y = targetY - p.worldY; - float tx = (x * p.d - y * p.b) * id - bone.ax, ty = (y * p.a - x * p.c) * id - bone.ay; - float rotationIK = (float)Math.Atan2(ty, tx) * MathUtils.RadDeg - bone.ashearX - bone.arotation; - if (bone.ascaleX < 0) rotationIK += 180; - if (rotationIK > 180) - rotationIK -= 360; - else if (rotationIK < -180) rotationIK += 360; - bone.UpdateWorldTransform(bone.ax, bone.ay, bone.arotation + rotationIK * alpha, bone.ascaleX, bone.ascaleY, bone.ashearX, - bone.ashearY); - } + /// 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 public void Apply(Bone bone, float targetX, float targetY, float alpha) + { + if (!bone.appliedValid) bone.UpdateAppliedTransform(); + Bone p = bone.parent; + float id = 1 / (p.a * p.d - p.b * p.c); + float x = targetX - p.worldX, y = targetY - p.worldY; + float tx = (x * p.d - y * p.b) * id - bone.ax, ty = (y * p.a - x * p.c) * id - bone.ay; + float rotationIK = (float)Math.Atan2(ty, tx) * MathUtils.RadDeg - bone.ashearX - bone.arotation; + if (bone.ascaleX < 0) rotationIK += 180; + if (rotationIK > 180) + rotationIK -= 360; + else if (rotationIK < -180) rotationIK += 360; + bone.UpdateWorldTransform(bone.ax, bone.ay, bone.arotation + rotationIK * alpha, bone.ascaleX, bone.ascaleY, bone.ashearX, + bone.ashearY); + } - /// 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. - /// A direct descendant of the parent bone. - static public void Apply (Bone parent, Bone child, float targetX, float targetY, int bendDir, float alpha) { - if (alpha == 0) { - child.UpdateWorldTransform (); - return; - } - //float px = parent.x, py = parent.y, psx = parent.scaleX, psy = parent.scaleY, csx = child.scaleX; - if (!parent.appliedValid) parent.UpdateAppliedTransform(); - if (!child.appliedValid) child.UpdateAppliedTransform(); - float px = parent.ax, py = parent.ay, psx = parent.ascaleX, psy = parent.ascaleY, csx = child.ascaleX; - int os1, os2, s2; - if (psx < 0) { - psx = -psx; - os1 = 180; - s2 = -1; - } else { - os1 = 0; - s2 = 1; - } - if (psy < 0) { - psy = -psy; - s2 = -s2; - } - if (csx < 0) { - csx = -csx; - os2 = 180; - } else - os2 = 0; - float cx = child.ax, cy, cwx, cwy, a = parent.a, b = parent.b, c = parent.c, d = parent.d; - bool u = Math.Abs(psx - psy) <= 0.0001f; - if (!u) { - cy = 0; - cwx = a * cx + parent.worldX; - cwy = c * cx + parent.worldY; - } else { - cy = child.ay; - cwx = a * cx + b * cy + parent.worldX; - cwy = c * cx + d * cy + parent.worldY; - } - Bone pp = parent.parent; - a = pp.a; - b = pp.b; - c = pp.c; - d = pp.d; - float id = 1 / (a * d - b * c), x = targetX - pp.worldX, y = targetY - pp.worldY; - float tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py; - x = cwx - pp.worldX; - y = cwy - pp.worldY; - float dx = (x * d - y * b) * id - px, dy = (y * a - x * c) * id - py; - float l1 = (float)Math.Sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2; - if (u) { - l2 *= psx; - float cos = (tx * tx + ty * ty - l1 * l1 - l2 * l2) / (2 * l1 * l2); - if (cos < -1) - cos = -1; - else if (cos > 1) cos = 1; - a2 = (float)Math.Acos(cos) * bendDir; - a = l1 + l2 * cos; - b = l2 * (float)Math.Sin(a2); - a1 = (float)Math.Atan2(ty * a - tx * b, tx * a + ty * b); - } else { - a = psx * l2; - b = psy * l2; - float aa = a * a, bb = b * b, dd = tx * tx + ty * ty, ta = (float)Math.Atan2(ty, tx); - c = bb * l1 * l1 + aa * dd - aa * bb; - float c1 = -2 * bb * l1, c2 = bb - aa; - d = c1 * c1 - 4 * c2 * c; - if (d >= 0) { - float q = (float)Math.Sqrt(d); - if (c1 < 0) q = -q; - q = -(c1 + q) / 2; - float r0 = q / c2, r1 = c / q; - float r = Math.Abs(r0) < Math.Abs(r1) ? r0 : r1; - if (r * r <= dd) { - y = (float)Math.Sqrt(dd - r * r) * bendDir; - a1 = ta - (float)Math.Atan2(y, r); - a2 = (float)Math.Atan2(y / psy, (r - l1) / psx); - goto outer; - } - } - float minAngle = 0, minDist = float.MaxValue, minX = 0, minY = 0; - float maxAngle = 0, maxDist = 0, maxX = 0, maxY = 0; - x = l1 + a; - d = x * x; - if (d > maxDist) { - maxAngle = 0; - maxDist = d; - maxX = x; - } - x = l1 - a; - d = x * x; - if (d < minDist) { - minAngle = (float)Math.PI; - minDist = d; - minX = x; - } - float angle = (float)Math.Acos(-a * l1 / (aa - bb)); - x = a * (float)Math.Cos(angle) + l1; - y = b * (float)Math.Sin(angle); - d = x * x + y * y; - if (d < minDist) { - minAngle = angle; - minDist = d; - minX = x; - minY = y; - } - if (d > maxDist) { - maxAngle = angle; - maxDist = d; - maxX = x; - maxY = y; - } - if (dd <= (minDist + maxDist) / 2) { - a1 = ta - (float)Math.Atan2(minY * bendDir, minX); - a2 = minAngle * bendDir; - } else { - a1 = ta - (float)Math.Atan2(maxY * bendDir, maxX); - a2 = maxAngle * bendDir; - } - } - outer: - float os = (float)Math.Atan2(cy, cx) * s2; - float rotation = parent.arotation; - a1 = (a1 - os) * MathUtils.RadDeg + os1 - rotation; - if (a1 > 180) - a1 -= 360; - else if (a1 < -180) a1 += 360; - parent.UpdateWorldTransform(px, py, rotation + a1 * alpha, parent.scaleX, parent.ascaleY, 0, 0); - rotation = child.arotation; - a2 = ((a2 + os) * MathUtils.RadDeg - child.ashearX) * s2 + os2 - rotation; - if (a2 > 180) - a2 -= 360; - else if (a2 < -180) a2 += 360; - child.UpdateWorldTransform(cx, cy, rotation + a2 * alpha, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY); - } - } + /// 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. + /// A direct descendant of the parent bone. + static public void Apply(Bone parent, Bone child, float targetX, float targetY, int bendDir, float alpha) + { + if (alpha == 0) + { + child.UpdateWorldTransform(); + return; + } + //float px = parent.x, py = parent.y, psx = parent.scaleX, psy = parent.scaleY, csx = child.scaleX; + if (!parent.appliedValid) parent.UpdateAppliedTransform(); + if (!child.appliedValid) child.UpdateAppliedTransform(); + float px = parent.ax, py = parent.ay, psx = parent.ascaleX, psy = parent.ascaleY, csx = child.ascaleX; + int os1, os2, s2; + if (psx < 0) + { + psx = -psx; + os1 = 180; + s2 = -1; + } + else + { + os1 = 0; + s2 = 1; + } + if (psy < 0) + { + psy = -psy; + s2 = -s2; + } + if (csx < 0) + { + csx = -csx; + os2 = 180; + } + else + os2 = 0; + float cx = child.ax, cy, cwx, cwy, a = parent.a, b = parent.b, c = parent.c, d = parent.d; + bool u = Math.Abs(psx - psy) <= 0.0001f; + if (!u) + { + cy = 0; + cwx = a * cx + parent.worldX; + cwy = c * cx + parent.worldY; + } + else + { + cy = child.ay; + cwx = a * cx + b * cy + parent.worldX; + cwy = c * cx + d * cy + parent.worldY; + } + Bone pp = parent.parent; + a = pp.a; + b = pp.b; + c = pp.c; + d = pp.d; + float id = 1 / (a * d - b * c), x = targetX - pp.worldX, y = targetY - pp.worldY; + float tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py; + x = cwx - pp.worldX; + y = cwy - pp.worldY; + float dx = (x * d - y * b) * id - px, dy = (y * a - x * c) * id - py; + float l1 = (float)Math.Sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2; + if (u) + { + l2 *= psx; + float cos = (tx * tx + ty * ty - l1 * l1 - l2 * l2) / (2 * l1 * l2); + if (cos < -1) + cos = -1; + else if (cos > 1) cos = 1; + a2 = (float)Math.Acos(cos) * bendDir; + a = l1 + l2 * cos; + b = l2 * (float)Math.Sin(a2); + a1 = (float)Math.Atan2(ty * a - tx * b, tx * a + ty * b); + } + else + { + a = psx * l2; + b = psy * l2; + float aa = a * a, bb = b * b, dd = tx * tx + ty * ty, ta = (float)Math.Atan2(ty, tx); + c = bb * l1 * l1 + aa * dd - aa * bb; + float c1 = -2 * bb * l1, c2 = bb - aa; + d = c1 * c1 - 4 * c2 * c; + if (d >= 0) + { + float q = (float)Math.Sqrt(d); + if (c1 < 0) q = -q; + q = -(c1 + q) / 2; + float r0 = q / c2, r1 = c / q; + float r = Math.Abs(r0) < Math.Abs(r1) ? r0 : r1; + if (r * r <= dd) + { + y = (float)Math.Sqrt(dd - r * r) * bendDir; + a1 = ta - (float)Math.Atan2(y, r); + a2 = (float)Math.Atan2(y / psy, (r - l1) / psx); + goto outer; + } + } + float minAngle = 0, minDist = float.MaxValue, minX = 0, minY = 0; + float maxAngle = 0, maxDist = 0, maxX = 0, maxY = 0; + x = l1 + a; + d = x * x; + if (d > maxDist) + { + maxAngle = 0; + maxDist = d; + maxX = x; + } + x = l1 - a; + d = x * x; + if (d < minDist) + { + minAngle = (float)Math.PI; + minDist = d; + minX = x; + } + float angle = (float)Math.Acos(-a * l1 / (aa - bb)); + x = a * (float)Math.Cos(angle) + l1; + y = b * (float)Math.Sin(angle); + d = x * x + y * y; + if (d < minDist) + { + minAngle = angle; + minDist = d; + minX = x; + minY = y; + } + if (d > maxDist) + { + maxAngle = angle; + maxDist = d; + maxX = x; + maxY = y; + } + if (dd <= (minDist + maxDist) / 2) + { + a1 = ta - (float)Math.Atan2(minY * bendDir, minX); + a2 = minAngle * bendDir; + } + else + { + a1 = ta - (float)Math.Atan2(maxY * bendDir, maxX); + a2 = maxAngle * bendDir; + } + } + outer: + float os = (float)Math.Atan2(cy, cx) * s2; + float rotation = parent.arotation; + a1 = (a1 - os) * MathUtils.RadDeg + os1 - rotation; + if (a1 > 180) + a1 -= 360; + else if (a1 < -180) a1 += 360; + parent.UpdateWorldTransform(px, py, rotation + a1 * alpha, parent.scaleX, parent.ascaleY, 0, 0); + rotation = child.arotation; + a2 = ((a2 + os) * MathUtils.RadDeg - child.ashearX) * s2 + os2 - rotation; + if (a2 > 180) + a2 -= 360; + else if (a2 < -180) a2 += 360; + child.UpdateWorldTransform(cx, cy, rotation + a2 * alpha, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/IkConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/IkConstraintData.cs index f55426c..5e58096 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/IkConstraintData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/IkConstraintData.cs @@ -31,29 +31,33 @@ using System; using System.Collections.Generic; -namespace Spine3_5_51 { - public class IkConstraintData { - internal String name; - internal int order; - internal List bones = new List(); - internal BoneData target; - internal int bendDirection = 1; - internal float mix = 1; +namespace Spine3_5_51 +{ + public class IkConstraintData + { + internal String name; + internal int order; + internal List bones = new List(); + internal BoneData target; + internal int bendDirection = 1; + internal float mix = 1; - public String Name { get { return name; } } - public int Order { get { return order; } set { order = value; } } - public List Bones { get { return bones; } } - public BoneData Target { get { return target; } set { target = value; } } - public int BendDirection { get { return bendDirection; } set { bendDirection = value; } } - public float Mix { get { return mix; } set { mix = value; } } + public String Name { get { return name; } } + public int Order { get { return order; } set { order = value; } } + public List Bones { get { return bones; } } + public BoneData Target { get { return target; } set { target = value; } } + public int BendDirection { get { return bendDirection; } set { bendDirection = value; } } + public float Mix { get { return mix; } set { mix = value; } } - public IkConstraintData (String name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; - } + public IkConstraintData(String name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + } - override public String ToString () { - return name; - } - } + override public String ToString() + { + return name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Json.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Json.cs index c855253..8901ac2 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Json.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Json.cs @@ -29,20 +29,22 @@ *****************************************************************************/ using System; +using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Text; -using System.Collections; -using System.Globalization; -using System.Collections.Generic; -namespace Spine3_5_51 { - public static class Json { - public static object Deserialize (TextReader text) { - var parser = new SharpJson3_5_51.JsonDecoder(); - parser.parseNumbersAsFloat = true; - return parser.Decode(text.ReadToEnd()); - } - } +namespace Spine3_5_51 +{ + public static class Json + { + public static object Deserialize(TextReader text) + { + var parser = new SharpJson3_5_51.JsonDecoder(); + parser.parseNumbersAsFloat = true; + return parser.Decode(text.ReadToEnd()); + } + } } /** @@ -76,460 +78,502 @@ public static object Deserialize (TextReader text) { */ namespace SharpJson3_5_51 { - class Lexer - { - public enum Token { - None, - Null, - True, - False, - Colon, - Comma, - String, - Number, - CurlyOpen, - CurlyClose, - SquaredOpen, - SquaredClose, - }; - - public bool hasError { - get { - return !success; - } - } - - public int lineNumber { - get; - private set; - } - - public bool parseNumbersAsFloat { - get; - set; - } - - char[] json; - int index = 0; - bool success = true; - char[] stringBuffer = new char[4096]; - - public Lexer(string text) - { - Reset(); - - json = text.ToCharArray(); - parseNumbersAsFloat = false; - } - - public void Reset() - { - index = 0; - lineNumber = 1; - success = true; - } - - public string ParseString() - { - int idx = 0; - StringBuilder builder = null; - - SkipWhiteSpaces(); - - // " - char c = json[index++]; - - bool failed = false; - bool complete = false; - - while (!complete && !failed) { - if (index == json.Length) - break; - - c = json[index++]; - if (c == '"') { - complete = true; - break; - } else if (c == '\\') { - if (index == json.Length) - break; - - c = json[index++]; - - switch (c) { - case '"': - stringBuffer[idx++] = '"'; - break; - case '\\': - stringBuffer[idx++] = '\\'; - break; - case '/': - stringBuffer[idx++] = '/'; - break; - case 'b': - stringBuffer[idx++] = '\b'; - break; - case'f': - stringBuffer[idx++] = '\f'; - break; - case 'n': - stringBuffer[idx++] = '\n'; - break; - case 'r': - stringBuffer[idx++] = '\r'; - break; - case 't': - stringBuffer[idx++] = '\t'; - break; - case 'u': - int remainingLength = json.Length - index; - if (remainingLength >= 4) { - var hex = new string(json, index, 4); - - // XXX: handle UTF - stringBuffer[idx++] = (char) Convert.ToInt32(hex, 16); - - // skip 4 chars - index += 4; - } else { - failed = true; - } - break; - } - } else { - stringBuffer[idx++] = c; - } - - if (idx >= stringBuffer.Length) { - if (builder == null) - builder = new StringBuilder(); - - builder.Append(stringBuffer, 0, idx); - idx = 0; - } - } - - if (!complete) { - success = false; - return null; - } - - if (builder != null) - return builder.ToString (); - else - return new string (stringBuffer, 0, idx); - } - - string GetNumberString() - { - SkipWhiteSpaces(); - - int lastIndex = GetLastIndexOfNumber(index); - int charLength = (lastIndex - index) + 1; - - var result = new string (json, index, charLength); - - index = lastIndex + 1; - - return result; - } - - public float ParseFloatNumber() - { - float number; - var str = GetNumberString (); - - if (!float.TryParse (str, NumberStyles.Float, CultureInfo.InvariantCulture, out number)) - return 0; - - return number; - } - - public double ParseDoubleNumber() - { - double number; - var str = GetNumberString (); - - if (!double.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out number)) - return 0; - - return number; - } - - int GetLastIndexOfNumber(int index) - { - int lastIndex; - - for (lastIndex = index; lastIndex < json.Length; lastIndex++) { - char ch = json[lastIndex]; - - if ((ch < '0' || ch > '9') && ch != '+' && ch != '-' - && ch != '.' && ch != 'e' && ch != 'E') - break; - } - - return lastIndex - 1; - } - - void SkipWhiteSpaces() - { - for (; index < json.Length; index++) { - char ch = json[index]; - - if (ch == '\n') - lineNumber++; - - if (!char.IsWhiteSpace(json[index])) - break; - } - } - - public Token LookAhead() - { - SkipWhiteSpaces(); - - int savedIndex = index; - return NextToken(json, ref savedIndex); - } - - public Token NextToken() - { - SkipWhiteSpaces(); - return NextToken(json, ref index); - } - - static Token NextToken(char[] json, ref int index) - { - if (index == json.Length) - return Token.None; - - char c = json[index++]; - - switch (c) { - case '{': - return Token.CurlyOpen; - case '}': - return Token.CurlyClose; - case '[': - return Token.SquaredOpen; - case ']': - return Token.SquaredClose; - case ',': - return Token.Comma; - case '"': - return Token.String; - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - case '-': - return Token.Number; - case ':': - return Token.Colon; - } - - index--; - - int remainingLength = json.Length - index; - - // false - if (remainingLength >= 5) { - if (json[index] == 'f' && - json[index + 1] == 'a' && - json[index + 2] == 'l' && - json[index + 3] == 's' && - json[index + 4] == 'e') { - index += 5; - return Token.False; - } - } - - // true - if (remainingLength >= 4) { - if (json[index] == 't' && - json[index + 1] == 'r' && - json[index + 2] == 'u' && - json[index + 3] == 'e') { - index += 4; - return Token.True; - } - } - - // null - if (remainingLength >= 4) { - if (json[index] == 'n' && - json[index + 1] == 'u' && - json[index + 2] == 'l' && - json[index + 3] == 'l') { - index += 4; - return Token.Null; - } - } - - return Token.None; - } - } - - public class JsonDecoder - { - public string errorMessage { - get; - private set; - } - - public bool parseNumbersAsFloat { - get; - set; - } - - Lexer lexer; - - public JsonDecoder() - { - errorMessage = null; - parseNumbersAsFloat = false; - } - - public object Decode(string text) - { - errorMessage = null; - - lexer = new Lexer(text); - lexer.parseNumbersAsFloat = parseNumbersAsFloat; - - return ParseValue(); - } - - public static object DecodeText(string text) - { - var builder = new JsonDecoder(); - return builder.Decode(text); - } - - IDictionary ParseObject() - { - var table = new Dictionary(); - - // { - lexer.NextToken(); - - while (true) { - var token = lexer.LookAhead(); - - switch (token) { - case Lexer.Token.None: - TriggerError("Invalid token"); - return null; - case Lexer.Token.Comma: - lexer.NextToken(); - break; - case Lexer.Token.CurlyClose: - lexer.NextToken(); - return table; - default: - // name - string name = EvalLexer(lexer.ParseString()); - - if (errorMessage != null) - return null; - - // : - token = lexer.NextToken(); - - if (token != Lexer.Token.Colon) { - TriggerError("Invalid token; expected ':'"); - return null; - } - - // value - object value = ParseValue(); - - if (errorMessage != null) - return null; - - table[name] = value; - break; - } - } - - //return null; // Unreachable code - } - - IList ParseArray() - { - var array = new List(); - - // [ - lexer.NextToken(); - - while (true) { - var token = lexer.LookAhead(); - - switch (token) { - case Lexer.Token.None: - TriggerError("Invalid token"); - return null; - case Lexer.Token.Comma: - lexer.NextToken(); - break; - case Lexer.Token.SquaredClose: - lexer.NextToken(); - return array; - default: - object value = ParseValue(); - - if (errorMessage != null) - return null; - - array.Add(value); - break; - } - } - - //return null; // Unreachable code - } - - object ParseValue() - { - switch (lexer.LookAhead()) { - case Lexer.Token.String: - return EvalLexer(lexer.ParseString()); - case Lexer.Token.Number: - if (parseNumbersAsFloat) - return EvalLexer(lexer.ParseFloatNumber()); - else - return EvalLexer(lexer.ParseDoubleNumber()); - case Lexer.Token.CurlyOpen: - return ParseObject(); - case Lexer.Token.SquaredOpen: - return ParseArray(); - case Lexer.Token.True: - lexer.NextToken(); - return true; - case Lexer.Token.False: - lexer.NextToken(); - return false; - case Lexer.Token.Null: - lexer.NextToken(); - return null; - case Lexer.Token.None: - break; - } - - TriggerError("Unable to parse value"); - return null; - } - - void TriggerError(string message) - { - errorMessage = string.Format("Error: '{0}' at line {1}", - message, lexer.lineNumber); - } - - T EvalLexer(T value) - { - if (lexer.hasError) - TriggerError("Lexical error ocurred"); - - return value; - } - } + class Lexer + { + public enum Token + { + None, + Null, + True, + False, + Colon, + Comma, + String, + Number, + CurlyOpen, + CurlyClose, + SquaredOpen, + SquaredClose, + }; + + public bool hasError + { + get + { + return !success; + } + } + + public int lineNumber + { + get; + private set; + } + + public bool parseNumbersAsFloat + { + get; + set; + } + + char[] json; + int index = 0; + bool success = true; + char[] stringBuffer = new char[4096]; + + public Lexer(string text) + { + Reset(); + + json = text.ToCharArray(); + parseNumbersAsFloat = false; + } + + public void Reset() + { + index = 0; + lineNumber = 1; + success = true; + } + + public string ParseString() + { + int idx = 0; + StringBuilder builder = null; + + SkipWhiteSpaces(); + + // " + char c = json[index++]; + + bool failed = false; + bool complete = false; + + while (!complete && !failed) + { + if (index == json.Length) + break; + + c = json[index++]; + if (c == '"') + { + complete = true; + break; + } + else if (c == '\\') + { + if (index == json.Length) + break; + + c = json[index++]; + + switch (c) + { + case '"': + stringBuffer[idx++] = '"'; + break; + case '\\': + stringBuffer[idx++] = '\\'; + break; + case '/': + stringBuffer[idx++] = '/'; + break; + case 'b': + stringBuffer[idx++] = '\b'; + break; + case 'f': + stringBuffer[idx++] = '\f'; + break; + case 'n': + stringBuffer[idx++] = '\n'; + break; + case 'r': + stringBuffer[idx++] = '\r'; + break; + case 't': + stringBuffer[idx++] = '\t'; + break; + case 'u': + int remainingLength = json.Length - index; + if (remainingLength >= 4) + { + var hex = new string(json, index, 4); + + // XXX: handle UTF + stringBuffer[idx++] = (char)Convert.ToInt32(hex, 16); + + // skip 4 chars + index += 4; + } + else + { + failed = true; + } + break; + } + } + else + { + stringBuffer[idx++] = c; + } + + if (idx >= stringBuffer.Length) + { + if (builder == null) + builder = new StringBuilder(); + + builder.Append(stringBuffer, 0, idx); + idx = 0; + } + } + + if (!complete) + { + success = false; + return null; + } + + if (builder != null) + return builder.ToString(); + else + return new string(stringBuffer, 0, idx); + } + + string GetNumberString() + { + SkipWhiteSpaces(); + + int lastIndex = GetLastIndexOfNumber(index); + int charLength = (lastIndex - index) + 1; + + var result = new string(json, index, charLength); + + index = lastIndex + 1; + + return result; + } + + public float ParseFloatNumber() + { + float number; + var str = GetNumberString(); + + if (!float.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out number)) + return 0; + + return number; + } + + public double ParseDoubleNumber() + { + double number; + var str = GetNumberString(); + + if (!double.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out number)) + return 0; + + return number; + } + + int GetLastIndexOfNumber(int index) + { + int lastIndex; + + for (lastIndex = index; lastIndex < json.Length; lastIndex++) + { + char ch = json[lastIndex]; + + if ((ch < '0' || ch > '9') && ch != '+' && ch != '-' + && ch != '.' && ch != 'e' && ch != 'E') + break; + } + + return lastIndex - 1; + } + + void SkipWhiteSpaces() + { + for (; index < json.Length; index++) + { + char ch = json[index]; + + if (ch == '\n') + lineNumber++; + + if (!char.IsWhiteSpace(json[index])) + break; + } + } + + public Token LookAhead() + { + SkipWhiteSpaces(); + + int savedIndex = index; + return NextToken(json, ref savedIndex); + } + + public Token NextToken() + { + SkipWhiteSpaces(); + return NextToken(json, ref index); + } + + static Token NextToken(char[] json, ref int index) + { + if (index == json.Length) + return Token.None; + + char c = json[index++]; + + switch (c) + { + case '{': + return Token.CurlyOpen; + case '}': + return Token.CurlyClose; + case '[': + return Token.SquaredOpen; + case ']': + return Token.SquaredClose; + case ',': + return Token.Comma; + case '"': + return Token.String; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + return Token.Number; + case ':': + return Token.Colon; + } + + index--; + + int remainingLength = json.Length - index; + + // false + if (remainingLength >= 5) + { + if (json[index] == 'f' && + json[index + 1] == 'a' && + json[index + 2] == 'l' && + json[index + 3] == 's' && + json[index + 4] == 'e') + { + index += 5; + return Token.False; + } + } + + // true + if (remainingLength >= 4) + { + if (json[index] == 't' && + json[index + 1] == 'r' && + json[index + 2] == 'u' && + json[index + 3] == 'e') + { + index += 4; + return Token.True; + } + } + + // null + if (remainingLength >= 4) + { + if (json[index] == 'n' && + json[index + 1] == 'u' && + json[index + 2] == 'l' && + json[index + 3] == 'l') + { + index += 4; + return Token.Null; + } + } + + return Token.None; + } + } + + public class JsonDecoder + { + public string errorMessage + { + get; + private set; + } + + public bool parseNumbersAsFloat + { + get; + set; + } + + Lexer lexer; + + public JsonDecoder() + { + errorMessage = null; + parseNumbersAsFloat = false; + } + + public object Decode(string text) + { + errorMessage = null; + + lexer = new Lexer(text); + lexer.parseNumbersAsFloat = parseNumbersAsFloat; + + return ParseValue(); + } + + public static object DecodeText(string text) + { + var builder = new JsonDecoder(); + return builder.Decode(text); + } + + IDictionary ParseObject() + { + var table = new Dictionary(); + + // { + lexer.NextToken(); + + while (true) + { + var token = lexer.LookAhead(); + + switch (token) + { + case Lexer.Token.None: + TriggerError("Invalid token"); + return null; + case Lexer.Token.Comma: + lexer.NextToken(); + break; + case Lexer.Token.CurlyClose: + lexer.NextToken(); + return table; + default: + // name + string name = EvalLexer(lexer.ParseString()); + + if (errorMessage != null) + return null; + + // : + token = lexer.NextToken(); + + if (token != Lexer.Token.Colon) + { + TriggerError("Invalid token; expected ':'"); + return null; + } + + // value + object value = ParseValue(); + + if (errorMessage != null) + return null; + + table[name] = value; + break; + } + } + + //return null; // Unreachable code + } + + IList ParseArray() + { + var array = new List(); + + // [ + lexer.NextToken(); + + while (true) + { + var token = lexer.LookAhead(); + + switch (token) + { + case Lexer.Token.None: + TriggerError("Invalid token"); + return null; + case Lexer.Token.Comma: + lexer.NextToken(); + break; + case Lexer.Token.SquaredClose: + lexer.NextToken(); + return array; + default: + object value = ParseValue(); + + if (errorMessage != null) + return null; + + array.Add(value); + break; + } + } + + //return null; // Unreachable code + } + + object ParseValue() + { + switch (lexer.LookAhead()) + { + case Lexer.Token.String: + return EvalLexer(lexer.ParseString()); + case Lexer.Token.Number: + if (parseNumbersAsFloat) + return EvalLexer(lexer.ParseFloatNumber()); + else + return EvalLexer(lexer.ParseDoubleNumber()); + case Lexer.Token.CurlyOpen: + return ParseObject(); + case Lexer.Token.SquaredOpen: + return ParseArray(); + case Lexer.Token.True: + lexer.NextToken(); + return true; + case Lexer.Token.False: + lexer.NextToken(); + return false; + case Lexer.Token.Null: + lexer.NextToken(); + return null; + case Lexer.Token.None: + break; + } + + TriggerError("Unable to parse value"); + return null; + } + + void TriggerError(string message) + { + errorMessage = string.Format("Error: '{0}' at line {1}", + message, lexer.lineNumber); + } + + T EvalLexer(T value) + { + if (lexer.hasError) + TriggerError("Lexical error ocurred"); + + return value; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/MathUtils.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/MathUtils.cs index 171bc53..91ec35c 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/MathUtils.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/MathUtils.cs @@ -30,71 +30,82 @@ using System; -namespace Spine3_5_51 { - public static class MathUtils { - public const float PI = 3.1415927f; - public const float PI2 = PI * 2; - public const float RadDeg = 180f / PI; - public const float DegRad = PI / 180; +namespace Spine3_5_51 +{ + public static class MathUtils + { + public const float PI = 3.1415927f; + public const float PI2 = PI * 2; + public const float RadDeg = 180f / PI; + public const float DegRad = PI / 180; - const int SIN_BITS = 14; // 16KB. Adjust for accuracy. - const int SIN_MASK = ~(-1 << SIN_BITS); - const int SIN_COUNT = SIN_MASK + 1; - const float RadFull = PI * 2; - const float DegFull = 360; - const float RadToIndex = SIN_COUNT / RadFull; - const float DegToIndex = SIN_COUNT / DegFull; - static float[] sin = new float[SIN_COUNT]; + const int SIN_BITS = 14; // 16KB. Adjust for accuracy. + const int SIN_MASK = ~(-1 << SIN_BITS); + const int SIN_COUNT = SIN_MASK + 1; + const float RadFull = PI * 2; + const float DegFull = 360; + const float RadToIndex = SIN_COUNT / RadFull; + const float DegToIndex = SIN_COUNT / DegFull; + static float[] sin = new float[SIN_COUNT]; - static MathUtils () { - for (int i = 0; i < SIN_COUNT; i++) - sin[i] = (float)Math.Sin((i + 0.5f) / SIN_COUNT * RadFull); - for (int i = 0; i < 360; i += 90) - sin[(int)(i * DegToIndex) & SIN_MASK] = (float)Math.Sin(i * DegRad); - } + static MathUtils() + { + for (int i = 0; i < SIN_COUNT; i++) + sin[i] = (float)Math.Sin((i + 0.5f) / SIN_COUNT * RadFull); + for (int i = 0; i < 360; i += 90) + sin[(int)(i * DegToIndex) & SIN_MASK] = (float)Math.Sin(i * DegRad); + } - /// Returns the sine in radians from a lookup table. - static public float Sin (float radians) { - return sin[(int)(radians * RadToIndex) & SIN_MASK]; - } + /// Returns the sine in radians from a lookup table. + static public float Sin(float radians) + { + return sin[(int)(radians * RadToIndex) & SIN_MASK]; + } - /// Returns the cosine in radians from a lookup table. - static public float Cos (float radians) { - return sin[(int)((radians + PI / 2) * RadToIndex) & SIN_MASK]; - } - - /// Returns the sine in radians from a lookup table. - static public float SinDeg (float degrees) { - return sin[(int)(degrees * DegToIndex) & SIN_MASK]; - } - - /// Returns the cosine in radians from a lookup table. - static public float CosDeg (float degrees) { - return sin[(int)((degrees + 90) * DegToIndex) & SIN_MASK]; - } + /// Returns the cosine in radians from a lookup table. + static public float Cos(float radians) + { + return sin[(int)((radians + PI / 2) * RadToIndex) & SIN_MASK]; + } - /// 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 public float Atan2 (float y, float x) { - if (x == 0f) { - if (y > 0f) return PI / 2; - if (y == 0f) return 0f; - return -PI / 2; - } - float atan, z = y / x; - if (Math.Abs(z) < 1f) { - atan = z / (1f + 0.28f * z * z); - if (x < 0f) return atan + (y < 0f ? -PI : PI); - return atan; - } - atan = PI / 2 - z / (z * z + 0.28f); - return y < 0f ? atan - PI : atan; - } + /// Returns the sine in radians from a lookup table. + static public float SinDeg(float degrees) + { + return sin[(int)(degrees * DegToIndex) & SIN_MASK]; + } - static public float Clamp (float value, float min, float max) { - if (value < min) return min; - if (value > max) return max; - return value; - } - } + /// Returns the cosine in radians from a lookup table. + static public float CosDeg(float degrees) + { + return sin[(int)((degrees + 90) * DegToIndex) & SIN_MASK]; + } + + /// 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 public float Atan2(float y, float x) + { + if (x == 0f) + { + if (y > 0f) return PI / 2; + if (y == 0f) return 0f; + return -PI / 2; + } + float atan, z = y / x; + if (Math.Abs(z) < 1f) + { + atan = z / (1f + 0.28f * z * z); + if (x < 0f) return atan + (y < 0f ? -PI : PI); + return atan; + } + atan = PI / 2 - z / (z * z + 0.28f); + return y < 0f ? atan - PI : atan; + } + + static public float Clamp(float value, float min, float max) + { + if (value < min) return min; + if (value > max) return max; + return value; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/PathConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/PathConstraint.cs index feb7343..14e22da 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/PathConstraint.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/PathConstraint.cs @@ -30,379 +30,434 @@ using System; -namespace Spine3_5_51 { - public class PathConstraint : IConstraint { - private const int NONE = -1, BEFORE = -2, AFTER = -3; +namespace Spine3_5_51 +{ + public class PathConstraint : IConstraint + { + private const int NONE = -1, BEFORE = -2, AFTER = -3; - internal PathConstraintData data; - internal ExposedList bones; - internal Slot target; - internal float position, spacing, rotateMix, translateMix; + internal PathConstraintData data; + internal ExposedList bones; + internal Slot target; + internal float position, spacing, rotateMix, translateMix; - internal ExposedList spaces = new ExposedList(), positions = new ExposedList(); - internal ExposedList world = new ExposedList(), curves = new ExposedList(), lengths = new ExposedList(); - internal float[] segments = new float[10]; + internal ExposedList spaces = new ExposedList(), positions = new ExposedList(); + internal ExposedList world = new ExposedList(), curves = new ExposedList(), lengths = new ExposedList(); + internal float[] segments = new float[10]; - public int Order { get { return data.order; } } - public float Position { get { return position; } set { position = value; } } - public float Spacing { get { return spacing; } set { spacing = value; } } - public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } - public float TranslateMix { get { return translateMix; } set { translateMix = value; } } - public ExposedList Bones { get { return bones; } } - public Slot Target { get { return target; } set { target = value; } } - public PathConstraintData Data { get { return data; } } + public int Order { get { return data.order; } } + public float Position { get { return position; } set { position = value; } } + public float Spacing { get { return spacing; } set { spacing = value; } } + public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } + public float TranslateMix { get { return translateMix; } set { translateMix = value; } } + public ExposedList Bones { get { return bones; } } + public Slot Target { get { return target; } set { target = value; } } + public PathConstraintData Data { get { return data; } } - public PathConstraint (PathConstraintData data, Skeleton skeleton) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.data = data; - bones = new ExposedList(data.Bones.Count); - foreach (BoneData boneData in data.bones) - bones.Add(skeleton.FindBone(boneData.name)); - target = skeleton.FindSlot(data.target.name); - position = data.position; - spacing = data.spacing; - rotateMix = data.rotateMix; - translateMix = data.translateMix; - } + public PathConstraint(PathConstraintData data, Skeleton skeleton) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + bones = new ExposedList(data.Bones.Count); + foreach (BoneData boneData in data.bones) + bones.Add(skeleton.FindBone(boneData.name)); + target = skeleton.FindSlot(data.target.name); + position = data.position; + spacing = data.spacing; + rotateMix = data.rotateMix; + translateMix = data.translateMix; + } - public void Apply () { - Update(); - } - - public void Update () { - PathAttachment attachment = target.Attachment as PathAttachment; - if (attachment == null) return; + public void Apply() + { + Update(); + } - float rotateMix = this.rotateMix, translateMix = this.translateMix; - bool translate = translateMix > 0, rotate = rotateMix > 0; - if (!translate && !rotate) return; + public void Update() + { + PathAttachment attachment = target.Attachment as PathAttachment; + if (attachment == null) return; - PathConstraintData data = this.data; - SpacingMode spacingMode = data.spacingMode; - bool lengthSpacing = spacingMode == SpacingMode.Length; - RotateMode rotateMode = data.rotateMode; - bool tangents = rotateMode == RotateMode.Tangent, scale = rotateMode == RotateMode.ChainScale; - int boneCount = this.bones.Count, spacesCount = tangents ? boneCount : boneCount + 1; - Bone[] bones = this.bones.Items; - ExposedList spaces = this.spaces.Resize(spacesCount), lengths = null; - float spacing = this.spacing; - if (scale || lengthSpacing) { - if (scale) lengths = this.lengths.Resize(boneCount); - for (int i = 0, n = spacesCount - 1; i < n;) { - Bone bone = bones[i]; - float length = bone.data.length, x = length * bone.a, y = length * bone.c; - length = (float)Math.Sqrt(x * x + y * y); - if (scale) lengths.Items[i] = length; - spaces.Items[++i] = lengthSpacing ? Math.Max(0, length + spacing) : spacing; - } - } else { - for (int i = 1; i < spacesCount; i++) - spaces.Items[i] = spacing; - } + float rotateMix = this.rotateMix, translateMix = this.translateMix; + bool translate = translateMix > 0, rotate = rotateMix > 0; + if (!translate && !rotate) return; - float[] positions = ComputeWorldPositions(attachment, spacesCount, tangents, - data.positionMode == PositionMode.Percent, spacingMode == SpacingMode.Percent); - float boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation; - bool tip; - if (offsetRotation == 0) { - tip = rotateMode == RotateMode.Chain; - } else { - tip = false; - Bone p = target.bone; - offsetRotation *= p.a * p.d - p.b * p.c > 0 ? MathUtils.DegRad : -MathUtils.DegRad; - } - for (int i = 0, p = 3; i < boneCount; i++, p += 3) { - Bone bone = (Bone)bones[i]; - bone.worldX += (boneX - bone.worldX) * translateMix; - bone.worldY += (boneY - bone.worldY) * translateMix; - float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY; - if (scale) { - float length = lengths.Items[i]; - if (length != 0) { - float s = ((float)Math.Sqrt(dx * dx + dy * dy) / length - 1) * rotateMix + 1; - bone.a *= s; - bone.c *= s; - } - } - boneX = x; - boneY = y; - if (rotate) { - 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.Items[i + 1] == 0) - r = positions[p + 2]; - else - r = MathUtils.Atan2(dy, dx); - r -= MathUtils.Atan2(c, a); - if (tip) { - cos = MathUtils.Cos(r); - sin = MathUtils.Sin(r); - float length = bone.data.length; - boneX += (length * (cos * a - sin * c) - dx) * rotateMix; - boneY += (length * (sin * a + cos * c) - dy) * rotateMix; - } else { - r += offsetRotation; - } - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) // - r += MathUtils.PI2; - r *= rotateMix; - cos = MathUtils.Cos(r); - sin = MathUtils.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.appliedValid = false; - } - } + PathConstraintData data = this.data; + SpacingMode spacingMode = data.spacingMode; + bool lengthSpacing = spacingMode == SpacingMode.Length; + RotateMode rotateMode = data.rotateMode; + bool tangents = rotateMode == RotateMode.Tangent, scale = rotateMode == RotateMode.ChainScale; + int boneCount = this.bones.Count, spacesCount = tangents ? boneCount : boneCount + 1; + Bone[] bones = this.bones.Items; + ExposedList spaces = this.spaces.Resize(spacesCount), lengths = null; + float spacing = this.spacing; + if (scale || lengthSpacing) + { + if (scale) lengths = this.lengths.Resize(boneCount); + for (int i = 0, n = spacesCount - 1; i < n;) + { + Bone bone = bones[i]; + float length = bone.data.length, x = length * bone.a, y = length * bone.c; + length = (float)Math.Sqrt(x * x + y * y); + if (scale) lengths.Items[i] = length; + spaces.Items[++i] = lengthSpacing ? Math.Max(0, length + spacing) : spacing; + } + } + else + { + for (int i = 1; i < spacesCount; i++) + spaces.Items[i] = spacing; + } - float[] ComputeWorldPositions (PathAttachment path, int spacesCount, bool tangents, bool percentPosition, - bool percentSpacing) { + float[] positions = ComputeWorldPositions(attachment, spacesCount, tangents, + data.positionMode == PositionMode.Percent, spacingMode == SpacingMode.Percent); + float boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation; + bool tip; + if (offsetRotation == 0) + { + tip = rotateMode == RotateMode.Chain; + } + else + { + tip = false; + Bone p = target.bone; + offsetRotation *= p.a * p.d - p.b * p.c > 0 ? MathUtils.DegRad : -MathUtils.DegRad; + } + for (int i = 0, p = 3; i < boneCount; i++, p += 3) + { + Bone bone = bones[i]; + bone.worldX += (boneX - bone.worldX) * translateMix; + bone.worldY += (boneY - bone.worldY) * translateMix; + float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY; + if (scale) + { + float length = lengths.Items[i]; + if (length != 0) + { + float s = ((float)Math.Sqrt(dx * dx + dy * dy) / length - 1) * rotateMix + 1; + bone.a *= s; + bone.c *= s; + } + } + boneX = x; + boneY = y; + if (rotate) + { + 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.Items[i + 1] == 0) + r = positions[p + 2]; + else + r = MathUtils.Atan2(dy, dx); + r -= MathUtils.Atan2(c, a); + if (tip) + { + cos = MathUtils.Cos(r); + sin = MathUtils.Sin(r); + float length = bone.data.length; + boneX += (length * (cos * a - sin * c) - dx) * rotateMix; + boneY += (length * (sin * a + cos * c) - dy) * rotateMix; + } + else + { + r += offsetRotation; + } + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) // + r += MathUtils.PI2; + r *= rotateMix; + cos = MathUtils.Cos(r); + sin = MathUtils.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.appliedValid = false; + } + } - Slot target = this.target; - float position = this.position; - float[] spaces = this.spaces.Items, output = this.positions.Resize(spacesCount * 3 + 2).Items, world; - bool closed = path.Closed; - int verticesLength = path.WorldVerticesLength, curveCount = verticesLength / 6, prevCurve = NONE; + float[] ComputeWorldPositions(PathAttachment path, int spacesCount, bool tangents, bool percentPosition, + bool percentSpacing) + { - float pathLength; - if (!path.ConstantSpeed) { - float[] lengths = path.Lengths; - curveCount -= closed ? 1 : 2; - pathLength = lengths[curveCount]; - if (percentPosition) position *= pathLength; - if (percentSpacing) { - for (int i = 0; i < spacesCount; i++) - spaces[i] *= pathLength; - } - world = this.world.Resize(8).Items; - for (int i = 0, o = 0, curve = 0; i < spacesCount; i++, o += 3) { - float space = spaces[i]; - position += space; - float p = position; + Slot target = this.target; + float position = this.position; + float[] spaces = this.spaces.Items, output = this.positions.Resize(spacesCount * 3 + 2).Items, world; + bool closed = path.Closed; + int verticesLength = path.WorldVerticesLength, curveCount = verticesLength / 6, prevCurve = NONE; - if (closed) { - 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, output, o); - continue; - } else if (p > pathLength) { - if (prevCurve != AFTER) { - prevCurve = AFTER; - path.ComputeWorldVertices(target, verticesLength - 6, 4, world, 0); - } - AddAfterPosition(p - pathLength, world, 0, output, o); - continue; - } + float pathLength; + if (!path.ConstantSpeed) + { + float[] lengths = path.Lengths; + curveCount -= closed ? 1 : 2; + pathLength = lengths[curveCount]; + if (percentPosition) position *= pathLength; + if (percentSpacing) + { + for (int i = 0; i < spacesCount; i++) + spaces[i] *= pathLength; + } + world = this.world.Resize(8).Items; + for (int i = 0, o = 0, curve = 0; i < spacesCount; i++, o += 3) + { + float space = spaces[i]; + position += space; + float p = position; - // 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], output, o, - tangents || (i > 0 && space == 0)); - } - return output; - } + if (closed) + { + 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, output, o); + continue; + } + else if (p > pathLength) + { + if (prevCurve != AFTER) + { + prevCurve = AFTER; + path.ComputeWorldVertices(target, verticesLength - 6, 4, world, 0); + } + AddAfterPosition(p - pathLength, world, 0, output, o); + continue; + } - // World vertices. - if (closed) { - verticesLength += 2; - world = this.world.Resize(verticesLength).Items; - 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 = this.world.Resize(verticesLength).Items; - path.ComputeWorldVertices(target, 2, verticesLength, world, 0); - } + // 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], output, o, + tangents || (i > 0 && space == 0)); + } + return output; + } - // Curve lengths. - float[] curves = this.curves.Resize(curveCount).Items; - 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 += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - dfx += ddfx; - dfy += ddfy; - ddfx += dddfx; - ddfy += dddfy; - pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - dfx += ddfx; - dfy += ddfy; - pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - dfx += ddfx + dddfx; - dfy += ddfy + dddfy; - pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - curves[i] = pathLength; - x1 = x2; - y1 = y2; - } - if (percentPosition) position *= pathLength; - if (percentSpacing) { - for (int i = 0; i < spacesCount; i++) - spaces[i] *= pathLength; - } + // World vertices. + if (closed) + { + verticesLength += 2; + world = this.world.Resize(verticesLength).Items; + 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 = this.world.Resize(verticesLength).Items; + path.ComputeWorldVertices(target, 2, verticesLength, world, 0); + } - float[] segments = this.segments; - float curveLength = 0; - for (int i = 0, o = 0, curve = 0, segment = 0; i < spacesCount; i++, o += 3) { - float space = spaces[i]; - position += space; - float p = position; + // Curve lengths. + float[] curves = this.curves.Resize(curveCount).Items; + 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 += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx; + dfy += ddfy; + pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx + dddfx; + dfy += ddfy + dddfy; + pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + curves[i] = pathLength; + x1 = x2; + y1 = y2; + } + if (percentPosition) position *= pathLength; + if (percentSpacing) + { + for (int i = 0; i < spacesCount; i++) + spaces[i] *= pathLength; + } - if (closed) { - p %= pathLength; - if (p < 0) p += pathLength; - curve = 0; - } else if (p < 0) { - AddBeforePosition(p, world, 0, output, o); - continue; - } else if (p > pathLength) { - AddAfterPosition(p - pathLength, world, verticesLength - 4, output, o); - continue; - } + float[] segments = this.segments; + float curveLength = 0; + for (int i = 0, o = 0, curve = 0, segment = 0; i < spacesCount; i++, o += 3) + { + float space = spaces[i]; + position += space; + float p = position; - // 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; - } + if (closed) + { + p %= pathLength; + if (p < 0) p += pathLength; + curve = 0; + } + else if (p < 0) + { + AddBeforePosition(p, world, 0, output, o); + continue; + } + else if (p > pathLength) + { + AddAfterPosition(p - pathLength, world, verticesLength - 4, output, o); + continue; + } - // 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 = (float)Math.Sqrt(dfx * dfx + dfy * dfy); - segments[0] = curveLength; - for (ii = 1; ii < 8; ii++) { - dfx += ddfx; - dfy += ddfy; - ddfx += dddfx; - ddfy += dddfy; - curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - segments[ii] = curveLength; - } - dfx += ddfx; - dfy += ddfy; - curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - segments[8] = curveLength; - dfx += ddfx + dddfx; - dfy += ddfy + dddfy; - curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - segments[9] = curveLength; - segment = 0; - } + // 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; + } - // 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, output, o, tangents || (i > 0 && space == 0)); - } - return output; - } + // 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 = (float)Math.Sqrt(dfx * dfx + dfy * dfy); + segments[0] = curveLength; + for (ii = 1; ii < 8; ii++) + { + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + segments[ii] = curveLength; + } + dfx += ddfx; + dfy += ddfy; + curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + segments[8] = curveLength; + dfx += ddfx + dddfx; + dfy += ddfy + dddfy; + curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + segments[9] = curveLength; + segment = 0; + } - private void AddBeforePosition (float p, float[] temp, int i, float[] output, int o) { - float x1 = temp[i], y1 = temp[i + 1], dx = temp[i + 2] - x1, dy = temp[i + 3] - y1, r = MathUtils.Atan2(dy, dx); - output[o] = x1 + p * MathUtils.Cos(r); - output[o + 1] = y1 + p * MathUtils.Sin(r); - output[o + 2] = r; - } + // 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, output, o, tangents || (i > 0 && space == 0)); + } + return output; + } - private void AddAfterPosition (float p, float[] temp, int i, float[] output, int o) { - float x1 = temp[i + 2], y1 = temp[i + 3], dx = x1 - temp[i], dy = y1 - temp[i + 1], r = MathUtils.Atan2(dy, dx); - output[o] = x1 + p * MathUtils.Cos(r); - output[o + 1] = y1 + p * MathUtils.Sin(r); - output[o + 2] = r; - } + private void AddBeforePosition(float p, float[] temp, int i, float[] output, int o) + { + float x1 = temp[i], y1 = temp[i + 1], dx = temp[i + 2] - x1, dy = temp[i + 3] - y1, r = MathUtils.Atan2(dy, dx); + output[o] = x1 + p * MathUtils.Cos(r); + output[o + 1] = y1 + p * MathUtils.Sin(r); + output[o + 2] = r; + } - private void AddCurvePosition (float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2, - float[] output, int o, bool tangents) { - if (p == 0 || float.IsNaN(p)) p = 0.0001f; - 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) output[o + 2] = (float)Math.Atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt)); - } - } + private void AddAfterPosition(float p, float[] temp, int i, float[] output, int o) + { + float x1 = temp[i + 2], y1 = temp[i + 3], dx = x1 - temp[i], dy = y1 - temp[i + 1], r = MathUtils.Atan2(dy, dx); + output[o] = x1 + p * MathUtils.Cos(r); + output[o + 1] = y1 + p * MathUtils.Sin(r); + output[o + 2] = r; + } + + private void AddCurvePosition(float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2, + float[] output, int o, bool tangents) + { + if (p == 0 || float.IsNaN(p)) p = 0.0001f; + 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) output[o + 2] = (float)Math.Atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt)); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/PathConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/PathConstraintData.cs index 15784c8..dc99f35 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/PathConstraintData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/PathConstraintData.cs @@ -30,50 +30,57 @@ using System; -namespace Spine3_5_51 { - public class PathConstraintData { - internal string name; - internal int order; - internal ExposedList bones = new ExposedList(); - internal SlotData target; - internal PositionMode positionMode; - internal SpacingMode spacingMode; - internal RotateMode rotateMode; - internal float offsetRotation; - internal float position, spacing, rotateMix, translateMix; +namespace Spine3_5_51 +{ + public class PathConstraintData + { + internal string name; + internal int order; + internal ExposedList bones = new ExposedList(); + internal SlotData target; + internal PositionMode positionMode; + internal SpacingMode spacingMode; + internal RotateMode rotateMode; + internal float offsetRotation; + internal float position, spacing, rotateMix, translateMix; - public string Name { get { return name; } } - public int Order { get { return order; } set { order = value; } } - public ExposedList Bones { get { return bones; } } - public SlotData Target { get { return target; } set { target = value; } } - public PositionMode PositionMode { get { return positionMode; } set { positionMode = value; } } - public SpacingMode SpacingMode { get { return spacingMode; } set { spacingMode = value; } } - public RotateMode RotateMode { get { return rotateMode; } set { rotateMode = value; } } - public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } - public float Position { get { return position; } set { position = value; } } - public float Spacing { get { return spacing; } set { spacing = value; } } - public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } - public float TranslateMix { get { return translateMix; } set { translateMix = value; } } + public string Name { get { return name; } } + public int Order { get { return order; } set { order = value; } } + public ExposedList Bones { get { return bones; } } + public SlotData Target { get { return target; } set { target = value; } } + public PositionMode PositionMode { get { return positionMode; } set { positionMode = value; } } + public SpacingMode SpacingMode { get { return spacingMode; } set { spacingMode = value; } } + public RotateMode RotateMode { get { return rotateMode; } set { rotateMode = value; } } + public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } + public float Position { get { return position; } set { position = value; } } + public float Spacing { get { return spacing; } set { spacing = value; } } + public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } + public float TranslateMix { get { return translateMix; } set { translateMix = value; } } - public PathConstraintData (String name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; - } + public PathConstraintData(String name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + } - public override string ToString () { - return name; - } - } - - public enum PositionMode { - Fixed, Percent - } + public override string ToString() + { + return name; + } + } - public enum SpacingMode { - Length, Fixed, Percent - } + public enum PositionMode + { + Fixed, Percent + } - public enum RotateMode { - Tangent, Chain, ChainScale - } + public enum SpacingMode + { + Length, Fixed, Percent + } + + public enum RotateMode + { + Tangent, Chain, ChainScale + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Skeleton.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Skeleton.cs index 597454d..4bf52ab 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Skeleton.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Skeleton.cs @@ -29,451 +29,512 @@ *****************************************************************************/ using System; -using System.Collections.Generic; - -namespace Spine3_5_51 { - public class Skeleton { - internal SkeletonData data; - internal ExposedList bones; - internal ExposedList slots; - internal ExposedList drawOrder; - internal ExposedList ikConstraints; - internal ExposedList transformConstraints; - internal ExposedList pathConstraints; - internal ExposedList updateCache = new ExposedList(); - internal ExposedList updateCacheReset = new ExposedList(); - internal Skin skin; - internal float r = 1, g = 1, b = 1, a = 1; - internal float time; - internal bool flipX, flipY; - internal float x, y; - - public SkeletonData Data { get { return data; } } - public ExposedList Bones { get { return bones; } } - public ExposedList UpdateCacheList { get { return updateCache; } } - public ExposedList Slots { get { return slots; } } - public ExposedList DrawOrder { get { return drawOrder; } } - public ExposedList IkConstraints { get { return ikConstraints; } } - public ExposedList PathConstraints { get { return pathConstraints; } } - public ExposedList TransformConstraints { get { return transformConstraints; } } - public Skin Skin { get { return skin; } set { skin = value; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } - public float Time { get { return time; } set { time = value; } } - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public bool FlipX { get { return flipX; } set { flipX = value; } } - public bool FlipY { get { return flipY; } set { flipY = value; } } - - public Bone RootBone { - get { return bones.Count == 0 ? null : bones.Items[0]; } - } - - public Skeleton (SkeletonData data) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - this.data = data; - - bones = new ExposedList(data.bones.Count); - foreach (BoneData boneData in data.bones) { - Bone bone; - if (boneData.parent == null) { - bone = new Bone(boneData, this, null); - } else { - Bone parent = bones.Items[boneData.parent.index]; - bone = new Bone(boneData, this, parent); - parent.children.Add(bone); - } - bones.Add(bone); - } - - slots = new ExposedList(data.slots.Count); - drawOrder = new ExposedList(data.slots.Count); - foreach (SlotData slotData in data.slots) { - Bone bone = bones.Items[slotData.boneData.index]; - Slot slot = new Slot(slotData, bone); - slots.Add(slot); - drawOrder.Add(slot); - } - - ikConstraints = new ExposedList(data.ikConstraints.Count); - foreach (IkConstraintData ikConstraintData in data.ikConstraints) - ikConstraints.Add(new IkConstraint(ikConstraintData, this)); - - transformConstraints = new ExposedList(data.transformConstraints.Count); - foreach (TransformConstraintData transformConstraintData in data.transformConstraints) - transformConstraints.Add(new TransformConstraint(transformConstraintData, this)); - - pathConstraints = new ExposedList (data.pathConstraints.Count); - foreach (PathConstraintData pathConstraintData in data.pathConstraints) - pathConstraints.Add(new PathConstraint(pathConstraintData, this)); - - UpdateCache(); - UpdateWorldTransform(); - } - - /// Caches information about bones and constraints. Must be called if bones, constraints or weighted path attachments are added - /// or removed. - public void UpdateCache () { - ExposedList updateCache = this.updateCache; - updateCache.Clear(); - this.updateCacheReset.Clear(); - - ExposedList bones = this.bones; - for (int i = 0, n = bones.Count; i < n; i++) - bones.Items[i].sorted = false; - - ExposedList ikConstraints = this.ikConstraints; - var transformConstraints = this.transformConstraints; - var pathConstraints = this.pathConstraints; - int ikCount = IkConstraints.Count, transformCount = transformConstraints.Count, pathCount = pathConstraints.Count; - int constraintCount = ikCount + transformCount + pathCount; - //outer: - for (int i = 0; i < constraintCount; i++) { - for (int ii = 0; ii < ikCount; ii++) { - IkConstraint constraint = ikConstraints.Items[ii]; - if (constraint.data.order == i) { - SortIkConstraint(constraint); - goto outer; //continue outer; - } - } - for (int ii = 0; ii < transformCount; ii++) { - TransformConstraint constraint = transformConstraints.Items[ii]; - if (constraint.data.order == i) { - SortTransformConstraint(constraint); - goto outer; //continue outer; - } - } - for (int ii = 0; ii < pathCount; ii++) { - PathConstraint constraint = pathConstraints.Items[ii]; - if (constraint.data.order == i) { - SortPathConstraint(constraint); - goto outer; //continue outer; - } - } - outer: {} - } - - for (int i = 0, n = bones.Count; i < n; i++) - SortBone(bones.Items[i]); - } - - private void SortIkConstraint (IkConstraint constraint) { - Bone target = constraint.target; - SortBone(target); - - var constrained = constraint.bones; - Bone parent = constrained.Items[0]; - SortBone(parent); - - if (constrained.Count > 1) { - Bone child = constrained.Items[constrained.Count - 1]; - if (!updateCache.Contains(child)) - updateCacheReset.Add(child); - } - - updateCache.Add(constraint); - - SortReset(parent.children); - constrained.Items[constrained.Count - 1].sorted = true; - } - - private void SortPathConstraint (PathConstraint constraint) { - Slot slot = constraint.target; - int slotIndex = slot.data.index; - Bone slotBone = slot.bone; - if (skin != null) SortPathConstraintAttachment(skin, slotIndex, slotBone); - if (data.defaultSkin != null && data.defaultSkin != skin) - SortPathConstraintAttachment(data.defaultSkin, slotIndex, slotBone); - for (int ii = 0, nn = data.skins.Count; ii < nn; ii++) - SortPathConstraintAttachment(data.skins.Items[ii], slotIndex, slotBone); - - Attachment attachment = slot.attachment; - if (attachment is PathAttachment) SortPathConstraintAttachment(attachment, slotBone); - - var constrained = constraint.bones; - int boneCount = constrained.Count; - for (int ii = 0; ii < boneCount; ii++) - SortBone(constrained.Items[ii]); - - updateCache.Add(constraint); - - for (int ii = 0; ii < boneCount; ii++) - SortReset(constrained.Items[ii].children); - for (int ii = 0; ii < boneCount; ii++) - constrained.Items[ii].sorted = true; - } - - private void SortTransformConstraint (TransformConstraint constraint) { - SortBone(constraint.target); - - var constrained = constraint.bones; - int boneCount = constrained.Count; - for (int ii = 0; ii < boneCount; ii++) - SortBone(constrained.Items[ii]); - - updateCache.Add(constraint); - - for (int ii = 0; ii < boneCount; ii++) - SortReset(constrained.Items[ii].children); - for (int ii = 0; ii < boneCount; ii++) - constrained.Items[ii].sorted = true; - } - - private void SortPathConstraintAttachment (Skin skin, int slotIndex, Bone slotBone) { - foreach (var entry in skin.Attachments) - if (entry.Key.slotIndex == slotIndex) SortPathConstraintAttachment(entry.Value, slotBone); - } - - private void SortPathConstraintAttachment (Attachment attachment, Bone slotBone) { - if (!(attachment is PathAttachment)) return; - int[] pathBones = ((PathAttachment)attachment).bones; - if (pathBones == null) - SortBone(slotBone); - else { - var bones = this.bones; - for (int i = 0, n = pathBones.Length; i < n;) { - int nn = pathBones[i++]; - nn += i; - while (i < nn) - SortBone(bones.Items[pathBones[i++]]); - } - } - } - - private void SortBone (Bone bone) { - if (bone.sorted) return; - Bone parent = bone.parent; - if (parent != null) SortBone(parent); - bone.sorted = true; - updateCache.Add(bone); - } - - private void SortReset (ExposedList bones) { - var bonesItems = bones.Items; - for (int i = 0, n = bones.Count; i < n; i++) { - Bone bone = bonesItems[i]; - if (bone.sorted) SortReset(bone.children); - bone.sorted = false; - } - } - - /// Updates the world transform for each bone and applies constraints. - public void UpdateWorldTransform () { - var updateCacheReset = this.updateCacheReset; - var updateCacheResetItems = updateCacheReset.Items; - for (int i = 0, n = updateCacheReset.Count; i < n; i++) { - Bone bone = updateCacheResetItems[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; - bone.appliedValid = true; - } - var updateItems = this.updateCache.Items; - for (int i = 0, n = updateCache.Count; i < n; i++) - updateItems[i].Update(); - } - - /// Sets the bones, constraints, and slots to their setup pose values. - public void SetToSetupPose () { - SetBonesToSetupPose(); - SetSlotsToSetupPose(); - } - - /// Sets the bones and constraints to their setup pose values. - public void SetBonesToSetupPose () { - var bonesItems = this.bones.Items; - for (int i = 0, n = bones.Count; i < n; i++) - bonesItems[i].SetToSetupPose(); - - var ikConstraintsItems = this.ikConstraints.Items; - for (int i = 0, n = ikConstraints.Count; i < n; i++) { - IkConstraint constraint = ikConstraintsItems[i]; - constraint.bendDirection = constraint.data.bendDirection; - constraint.mix = constraint.data.mix; - } - - var transformConstraintsItems = this.transformConstraints.Items; - for (int i = 0, n = transformConstraints.Count; i < n; i++) { - TransformConstraint constraint = transformConstraintsItems[i]; - TransformConstraintData data = constraint.data; - constraint.rotateMix = data.rotateMix; - constraint.translateMix = data.translateMix; - constraint.scaleMix = data.scaleMix; - constraint.shearMix = data.shearMix; - } - - var pathConstraintItems = this.pathConstraints.Items; - for (int i = 0, n = pathConstraints.Count; i < n; i++) { - PathConstraint constraint = pathConstraintItems[i]; - PathConstraintData data = constraint.data; - constraint.position = data.position; - constraint.spacing = data.spacing; - constraint.rotateMix = data.rotateMix; - constraint.translateMix = data.translateMix; - } - } - - public void SetSlotsToSetupPose () { - var slots = this.slots; - var slotsItems = slots.Items; - drawOrder.Clear(); - for (int i = 0, n = slots.Count; i < n; i++) - drawOrder.Add(slotsItems[i]); - - for (int i = 0, n = slots.Count; i < n; i++) - slotsItems[i].SetToSetupPose(); - } - - /// May be null. - public Bone FindBone (String boneName) { - if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); - var bones = this.bones; - var bonesItems = bones.Items; - for (int i = 0, n = bones.Count; i < n; i++) { - Bone bone = bonesItems[i]; - if (bone.data.name == boneName) return bone; - } - return null; - } - - /// -1 if the bone was not found. - public int FindBoneIndex (String boneName) { - if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); - var bones = this.bones; - var bonesItems = bones.Items; - for (int i = 0, n = bones.Count; i < n; i++) - if (bonesItems[i].data.name == boneName) return i; - return -1; - } - - /// May be null. - public Slot FindSlot (String slotName) { - if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); - var slots = this.slots; - var slotsItems = slots.Items; - for (int i = 0, n = slots.Count; i < n; i++) { - Slot slot = slotsItems[i]; - if (slot.data.name == slotName) return slot; - } - return null; - } - - /// -1 if the bone was not found. - public int FindSlotIndex (String slotName) { - if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); - var slots = this.slots; - var slotsItems = slots.Items; - for (int i = 0, n = slots.Count; i < n; i++) - if (slotsItems[i].data.name.Equals(slotName)) return i; - return -1; - } - - /// Sets a skin by name (see SetSkin). - public void SetSkin (String skinName) { - Skin skin = data.FindSkin(skinName); - if (skin == null) throw new ArgumentException("Skin not found: " + skinName, "skinName"); - SetSkin(skin); - } - - /// Sets the skin used to look up attachments before looking in the {@link SkeletonData#getDefaultSkin() default - /// skin}. Attachmentsfrom 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. - /// May be null. - public void SetSkin (Skin newSkin) { - if (newSkin != null) { - if (skin != null) - newSkin.AttachAll(this, skin); - else { - ExposedList slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) { - Slot slot = slots.Items[i]; - String name = slot.data.attachmentName; - if (name != null) { - Attachment attachment = newSkin.GetAttachment(i, name); - if (attachment != null) slot.Attachment = attachment; - } - } - } - } - skin = newSkin; - } - - /// May be null. - public Attachment GetAttachment (String slotName, String attachmentName) { - return GetAttachment(data.FindSlotIndex(slotName), attachmentName); - } - - /// May be null. - public Attachment GetAttachment (int slotIndex, String attachmentName) { - if (attachmentName == null) throw new ArgumentNullException("attachmentName", "attachmentName cannot be null."); - if (skin != null) { - Attachment attachment = skin.GetAttachment(slotIndex, attachmentName); - if (attachment != null) return attachment; - } - if (data.defaultSkin != null) return data.defaultSkin.GetAttachment(slotIndex, attachmentName); - return null; - } - - /// May be null. - public void SetAttachment (String slotName, String attachmentName) { - if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); - ExposedList slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) { - Slot slot = slots.Items[i]; - if (slot.data.name == slotName) { - Attachment attachment = null; - if (attachmentName != null) { - attachment = GetAttachment(i, attachmentName); - if (attachment == null) throw new Exception("Attachment not found: " + attachmentName + ", for slot: " + slotName); - } - slot.Attachment = attachment; - return; - } - } - throw new Exception("Slot not found: " + slotName); - } - - /// May be null. - public IkConstraint FindIkConstraint (String constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - ExposedList ikConstraints = this.ikConstraints; - for (int i = 0, n = ikConstraints.Count; i < n; i++) { - IkConstraint ikConstraint = ikConstraints.Items[i]; - if (ikConstraint.data.name == constraintName) return ikConstraint; - } - return null; - } - - /// May be null. - public TransformConstraint FindTransformConstraint (String constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - ExposedList transformConstraints = this.transformConstraints; - for (int i = 0, n = transformConstraints.Count; i < n; i++) { - TransformConstraint transformConstraint = transformConstraints.Items[i]; - if (transformConstraint.data.name == constraintName) return transformConstraint; - } - return null; - } - - /// May be null. - public PathConstraint FindPathConstraint (String constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - ExposedList pathConstraints = this.pathConstraints; - for (int i = 0, n = pathConstraints.Count; i < n; i++) { - PathConstraint constraint = pathConstraints.Items[i]; - if (constraint.data.name.Equals(constraintName)) return constraint; - } - return null; - } - - public void Update (float delta) { - time += delta; - } - } + +namespace Spine3_5_51 +{ + public class Skeleton + { + internal SkeletonData data; + internal ExposedList bones; + internal ExposedList slots; + internal ExposedList drawOrder; + internal ExposedList ikConstraints; + internal ExposedList transformConstraints; + internal ExposedList pathConstraints; + internal ExposedList updateCache = new ExposedList(); + internal ExposedList updateCacheReset = new ExposedList(); + internal Skin skin; + internal float r = 1, g = 1, b = 1, a = 1; + internal float time; + internal bool flipX, flipY; + internal float x, y; + + public SkeletonData Data { get { return data; } } + public ExposedList Bones { get { return bones; } } + public ExposedList UpdateCacheList { get { return updateCache; } } + public ExposedList Slots { get { return slots; } } + public ExposedList DrawOrder { get { return drawOrder; } } + public ExposedList IkConstraints { get { return ikConstraints; } } + public ExposedList PathConstraints { get { return pathConstraints; } } + public ExposedList TransformConstraints { get { return transformConstraints; } } + public Skin Skin { get { return skin; } set { skin = value; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } + public float Time { get { return time; } set { time = value; } } + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public bool FlipX { get { return flipX; } set { flipX = value; } } + public bool FlipY { get { return flipY; } set { flipY = value; } } + + public Bone RootBone + { + get { return bones.Count == 0 ? null : bones.Items[0]; } + } + + public Skeleton(SkeletonData data) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + this.data = data; + + bones = new ExposedList(data.bones.Count); + foreach (BoneData boneData in data.bones) + { + Bone bone; + if (boneData.parent == null) + { + bone = new Bone(boneData, this, null); + } + else + { + Bone parent = bones.Items[boneData.parent.index]; + bone = new Bone(boneData, this, parent); + parent.children.Add(bone); + } + bones.Add(bone); + } + + slots = new ExposedList(data.slots.Count); + drawOrder = new ExposedList(data.slots.Count); + foreach (SlotData slotData in data.slots) + { + Bone bone = bones.Items[slotData.boneData.index]; + Slot slot = new Slot(slotData, bone); + slots.Add(slot); + drawOrder.Add(slot); + } + + ikConstraints = new ExposedList(data.ikConstraints.Count); + foreach (IkConstraintData ikConstraintData in data.ikConstraints) + ikConstraints.Add(new IkConstraint(ikConstraintData, this)); + + transformConstraints = new ExposedList(data.transformConstraints.Count); + foreach (TransformConstraintData transformConstraintData in data.transformConstraints) + transformConstraints.Add(new TransformConstraint(transformConstraintData, this)); + + pathConstraints = new ExposedList(data.pathConstraints.Count); + foreach (PathConstraintData pathConstraintData in data.pathConstraints) + pathConstraints.Add(new PathConstraint(pathConstraintData, this)); + + UpdateCache(); + UpdateWorldTransform(); + } + + /// Caches information about bones and constraints. Must be called if bones, constraints or weighted path attachments are added + /// or removed. + public void UpdateCache() + { + ExposedList updateCache = this.updateCache; + updateCache.Clear(); + this.updateCacheReset.Clear(); + + ExposedList bones = this.bones; + for (int i = 0, n = bones.Count; i < n; i++) + bones.Items[i].sorted = false; + + ExposedList ikConstraints = this.ikConstraints; + var transformConstraints = this.transformConstraints; + var pathConstraints = this.pathConstraints; + int ikCount = IkConstraints.Count, transformCount = transformConstraints.Count, pathCount = pathConstraints.Count; + int constraintCount = ikCount + transformCount + pathCount; + //outer: + for (int i = 0; i < constraintCount; i++) + { + for (int ii = 0; ii < ikCount; ii++) + { + IkConstraint constraint = ikConstraints.Items[ii]; + if (constraint.data.order == i) + { + SortIkConstraint(constraint); + goto outer; //continue outer; + } + } + for (int ii = 0; ii < transformCount; ii++) + { + TransformConstraint constraint = transformConstraints.Items[ii]; + if (constraint.data.order == i) + { + SortTransformConstraint(constraint); + goto outer; //continue outer; + } + } + for (int ii = 0; ii < pathCount; ii++) + { + PathConstraint constraint = pathConstraints.Items[ii]; + if (constraint.data.order == i) + { + SortPathConstraint(constraint); + goto outer; //continue outer; + } + } + outer: { } + } + + for (int i = 0, n = bones.Count; i < n; i++) + SortBone(bones.Items[i]); + } + + private void SortIkConstraint(IkConstraint constraint) + { + Bone target = constraint.target; + SortBone(target); + + var constrained = constraint.bones; + Bone parent = constrained.Items[0]; + SortBone(parent); + + if (constrained.Count > 1) + { + Bone child = constrained.Items[constrained.Count - 1]; + if (!updateCache.Contains(child)) + updateCacheReset.Add(child); + } + + updateCache.Add(constraint); + + SortReset(parent.children); + constrained.Items[constrained.Count - 1].sorted = true; + } + + private void SortPathConstraint(PathConstraint constraint) + { + Slot slot = constraint.target; + int slotIndex = slot.data.index; + Bone slotBone = slot.bone; + if (skin != null) SortPathConstraintAttachment(skin, slotIndex, slotBone); + if (data.defaultSkin != null && data.defaultSkin != skin) + SortPathConstraintAttachment(data.defaultSkin, slotIndex, slotBone); + for (int ii = 0, nn = data.skins.Count; ii < nn; ii++) + SortPathConstraintAttachment(data.skins.Items[ii], slotIndex, slotBone); + + Attachment attachment = slot.attachment; + if (attachment is PathAttachment) SortPathConstraintAttachment(attachment, slotBone); + + var constrained = constraint.bones; + int boneCount = constrained.Count; + for (int ii = 0; ii < boneCount; ii++) + SortBone(constrained.Items[ii]); + + updateCache.Add(constraint); + + for (int ii = 0; ii < boneCount; ii++) + SortReset(constrained.Items[ii].children); + for (int ii = 0; ii < boneCount; ii++) + constrained.Items[ii].sorted = true; + } + + private void SortTransformConstraint(TransformConstraint constraint) + { + SortBone(constraint.target); + + var constrained = constraint.bones; + int boneCount = constrained.Count; + for (int ii = 0; ii < boneCount; ii++) + SortBone(constrained.Items[ii]); + + updateCache.Add(constraint); + + for (int ii = 0; ii < boneCount; ii++) + SortReset(constrained.Items[ii].children); + for (int ii = 0; ii < boneCount; ii++) + constrained.Items[ii].sorted = true; + } + + private void SortPathConstraintAttachment(Skin skin, int slotIndex, Bone slotBone) + { + foreach (var entry in skin.Attachments) + if (entry.Key.slotIndex == slotIndex) SortPathConstraintAttachment(entry.Value, slotBone); + } + + private void SortPathConstraintAttachment(Attachment attachment, Bone slotBone) + { + if (!(attachment is PathAttachment)) return; + int[] pathBones = ((PathAttachment)attachment).bones; + if (pathBones == null) + SortBone(slotBone); + else + { + var bones = this.bones; + for (int i = 0, n = pathBones.Length; i < n;) + { + int nn = pathBones[i++]; + nn += i; + while (i < nn) + SortBone(bones.Items[pathBones[i++]]); + } + } + } + + private void SortBone(Bone bone) + { + if (bone.sorted) return; + Bone parent = bone.parent; + if (parent != null) SortBone(parent); + bone.sorted = true; + updateCache.Add(bone); + } + + private void SortReset(ExposedList bones) + { + var bonesItems = bones.Items; + for (int i = 0, n = bones.Count; i < n; i++) + { + Bone bone = bonesItems[i]; + if (bone.sorted) SortReset(bone.children); + bone.sorted = false; + } + } + + /// Updates the world transform for each bone and applies constraints. + public void UpdateWorldTransform() + { + var updateCacheReset = this.updateCacheReset; + var updateCacheResetItems = updateCacheReset.Items; + for (int i = 0, n = updateCacheReset.Count; i < n; i++) + { + Bone bone = updateCacheResetItems[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; + bone.appliedValid = true; + } + var updateItems = this.updateCache.Items; + for (int i = 0, n = updateCache.Count; i < n; i++) + updateItems[i].Update(); + } + + /// Sets the bones, constraints, and slots to their setup pose values. + public void SetToSetupPose() + { + SetBonesToSetupPose(); + SetSlotsToSetupPose(); + } + + /// Sets the bones and constraints to their setup pose values. + public void SetBonesToSetupPose() + { + var bonesItems = this.bones.Items; + for (int i = 0, n = bones.Count; i < n; i++) + bonesItems[i].SetToSetupPose(); + + var ikConstraintsItems = this.ikConstraints.Items; + for (int i = 0, n = ikConstraints.Count; i < n; i++) + { + IkConstraint constraint = ikConstraintsItems[i]; + constraint.bendDirection = constraint.data.bendDirection; + constraint.mix = constraint.data.mix; + } + + var transformConstraintsItems = this.transformConstraints.Items; + for (int i = 0, n = transformConstraints.Count; i < n; i++) + { + TransformConstraint constraint = transformConstraintsItems[i]; + TransformConstraintData data = constraint.data; + constraint.rotateMix = data.rotateMix; + constraint.translateMix = data.translateMix; + constraint.scaleMix = data.scaleMix; + constraint.shearMix = data.shearMix; + } + + var pathConstraintItems = this.pathConstraints.Items; + for (int i = 0, n = pathConstraints.Count; i < n; i++) + { + PathConstraint constraint = pathConstraintItems[i]; + PathConstraintData data = constraint.data; + constraint.position = data.position; + constraint.spacing = data.spacing; + constraint.rotateMix = data.rotateMix; + constraint.translateMix = data.translateMix; + } + } + + public void SetSlotsToSetupPose() + { + var slots = this.slots; + var slotsItems = slots.Items; + drawOrder.Clear(); + for (int i = 0, n = slots.Count; i < n; i++) + drawOrder.Add(slotsItems[i]); + + for (int i = 0, n = slots.Count; i < n; i++) + slotsItems[i].SetToSetupPose(); + } + + /// May be null. + public Bone FindBone(String boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); + var bones = this.bones; + var bonesItems = bones.Items; + for (int i = 0, n = bones.Count; i < n; i++) + { + Bone bone = bonesItems[i]; + if (bone.data.name == boneName) return bone; + } + return null; + } + + /// -1 if the bone was not found. + public int FindBoneIndex(String boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); + var bones = this.bones; + var bonesItems = bones.Items; + for (int i = 0, n = bones.Count; i < n; i++) + if (bonesItems[i].data.name == boneName) return i; + return -1; + } + + /// May be null. + public Slot FindSlot(String slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + var slots = this.slots; + var slotsItems = slots.Items; + for (int i = 0, n = slots.Count; i < n; i++) + { + Slot slot = slotsItems[i]; + if (slot.data.name == slotName) return slot; + } + return null; + } + + /// -1 if the bone was not found. + public int FindSlotIndex(String slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + var slots = this.slots; + var slotsItems = slots.Items; + for (int i = 0, n = slots.Count; i < n; i++) + if (slotsItems[i].data.name.Equals(slotName)) return i; + return -1; + } + + /// Sets a skin by name (see SetSkin). + public void SetSkin(String skinName) + { + Skin skin = data.FindSkin(skinName); + if (skin == null) throw new ArgumentException("Skin not found: " + skinName, "skinName"); + SetSkin(skin); + } + + /// Sets the skin used to look up attachments before looking in the {@link SkeletonData#getDefaultSkin() default + /// skin}. Attachmentsfrom 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. + /// May be null. + public void SetSkin(Skin newSkin) + { + if (newSkin != null) + { + if (skin != null) + newSkin.AttachAll(this, skin); + else + { + ExposedList slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + { + Slot slot = slots.Items[i]; + String name = slot.data.attachmentName; + if (name != null) + { + Attachment attachment = newSkin.GetAttachment(i, name); + if (attachment != null) slot.Attachment = attachment; + } + } + } + } + skin = newSkin; + } + + /// May be null. + public Attachment GetAttachment(String slotName, String attachmentName) + { + return GetAttachment(data.FindSlotIndex(slotName), attachmentName); + } + + /// May be null. + public Attachment GetAttachment(int slotIndex, String attachmentName) + { + if (attachmentName == null) throw new ArgumentNullException("attachmentName", "attachmentName cannot be null."); + if (skin != null) + { + Attachment attachment = skin.GetAttachment(slotIndex, attachmentName); + if (attachment != null) return attachment; + } + if (data.defaultSkin != null) return data.defaultSkin.GetAttachment(slotIndex, attachmentName); + return null; + } + + /// May be null. + public void SetAttachment(String slotName, String attachmentName) + { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + ExposedList slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + { + Slot slot = slots.Items[i]; + if (slot.data.name == slotName) + { + Attachment attachment = null; + if (attachmentName != null) + { + attachment = GetAttachment(i, attachmentName); + if (attachment == null) throw new Exception("Attachment not found: " + attachmentName + ", for slot: " + slotName); + } + slot.Attachment = attachment; + return; + } + } + throw new Exception("Slot not found: " + slotName); + } + + /// May be null. + public IkConstraint FindIkConstraint(String constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + ExposedList ikConstraints = this.ikConstraints; + for (int i = 0, n = ikConstraints.Count; i < n; i++) + { + IkConstraint ikConstraint = ikConstraints.Items[i]; + if (ikConstraint.data.name == constraintName) return ikConstraint; + } + return null; + } + + /// May be null. + public TransformConstraint FindTransformConstraint(String constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + ExposedList transformConstraints = this.transformConstraints; + for (int i = 0, n = transformConstraints.Count; i < n; i++) + { + TransformConstraint transformConstraint = transformConstraints.Items[i]; + if (transformConstraint.data.name == constraintName) return transformConstraint; + } + return null; + } + + /// May be null. + public PathConstraint FindPathConstraint(String constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + ExposedList pathConstraints = this.pathConstraints; + for (int i = 0, n = pathConstraints.Count; i < n; i++) + { + PathConstraint constraint = pathConstraints.Items[i]; + if (constraint.data.name.Equals(constraintName)) return constraint; + } + return null; + } + + public void Update(float delta) + { + time += delta; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/SkeletonBinary.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/SkeletonBinary.cs index 25a0380..bd9705c 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/SkeletonBinary.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/SkeletonBinary.cs @@ -33,49 +33,53 @@ #endif using System; -using System.IO; using System.Collections.Generic; +using System.IO; #if WINDOWS_STOREAPP using System.Threading.Tasks; using Windows.Storage; #endif -namespace Spine3_5_51 { - public class SkeletonBinary { - public const int BONE_ROTATE = 0; - public const int BONE_TRANSLATE = 1; - public const int BONE_SCALE = 2; - public const int BONE_SHEAR = 3; - - public const int SLOT_ATTACHMENT = 0; - public const int SLOT_COLOR = 1; - - public const int PATH_POSITION = 0; - public const int PATH_SPACING = 1; - public const int PATH_MIX = 2; - - public const int CURVE_LINEAR = 0; - public const int CURVE_STEPPED = 1; - public const int CURVE_BEZIER = 2; - - public float Scale { get; set; } - - private AttachmentLoader attachmentLoader; - private byte[] buffer = new byte[32]; - private List linkedMeshes = new List(); - - public SkeletonBinary (params Atlas[] atlasArray) - : this(new AtlasAttachmentLoader(atlasArray)) { - } - - public SkeletonBinary (AttachmentLoader attachmentLoader) { - if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader"); - this.attachmentLoader = attachmentLoader; - Scale = 1; - } - - #if !ISUNITY && WINDOWS_STOREAPP +namespace Spine3_5_51 +{ + public class SkeletonBinary + { + public const int BONE_ROTATE = 0; + public const int BONE_TRANSLATE = 1; + public const int BONE_SCALE = 2; + public const int BONE_SHEAR = 3; + + public const int SLOT_ATTACHMENT = 0; + public const int SLOT_COLOR = 1; + + public const int PATH_POSITION = 0; + public const int PATH_SPACING = 1; + public const int PATH_MIX = 2; + + public const int CURVE_LINEAR = 0; + public const int CURVE_STEPPED = 1; + public const int CURVE_BEZIER = 2; + + public float Scale { get; set; } + + private AttachmentLoader attachmentLoader; + private byte[] buffer = new byte[32]; + private List linkedMeshes = new List(); + + public SkeletonBinary(params Atlas[] atlasArray) + : this(new AtlasAttachmentLoader(atlasArray)) + { + } + + public SkeletonBinary(AttachmentLoader attachmentLoader) + { + if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader"); + this.attachmentLoader = attachmentLoader; + Scale = 1; + } + +#if !ISUNITY && WINDOWS_STOREAPP private async Task ReadFile(string path) { var folder = Windows.ApplicationModel.Package.Current.InstalledLocation; using (var input = new BufferedStream(await folder.GetFileAsync(path).AsTask().ConfigureAwait(false))) { @@ -88,744 +92,842 @@ private async Task ReadFile(string path) { public SkeletonData ReadSkeletonData (String path) { return this.ReadFile(path).Result; } - #else - public SkeletonData ReadSkeletonData (String path) { - #if WINDOWS_PHONE +#else + public SkeletonData ReadSkeletonData(String path) + { +#if WINDOWS_PHONE using (var input = new BufferedStream(Microsoft.Xna.Framework.TitleContainer.OpenStream(path))) { - #else - using (var input = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) { - #endif - SkeletonData skeletonData = ReadSkeletonData(input); - skeletonData.name = Path.GetFileNameWithoutExtension(path); - return skeletonData; - } - } - #endif // WINDOWS_STOREAPP - - public static readonly TransformMode[] TransformModeValues = { - TransformMode.Normal, - TransformMode.OnlyTranslation, - TransformMode.NoRotationOrReflection, - TransformMode.NoScale, - TransformMode.NoScaleOrReflection - }; - - /// Returns the version string of binary skeleton data. - public static string GetVersionString (Stream input) { - if (input == null) throw new ArgumentNullException("input"); - - try { - // Hash. - int byteCount = ReadVarint(input, true); - if (byteCount > 1) input.Position += byteCount - 1; - - // Version. - byteCount = ReadVarint(input, true); - if (byteCount > 1) { - byteCount--; - var buffer = new byte[byteCount]; - ReadFully(input, buffer, 0, byteCount); - return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount); - } - - throw new ArgumentException("Stream does not contain a valid binary Skeleton Data.", "input"); - } catch (Exception e) { - throw new ArgumentException("Stream does not contain a valid binary Skeleton Data.\n" + e, "input"); - } - } - - public SkeletonData ReadSkeletonData (Stream input) { - if (input == null) throw new ArgumentNullException("input"); - float scale = Scale; - - var skeletonData = new SkeletonData(); - skeletonData.hash = ReadString(input); - if (skeletonData.hash.Length == 0) skeletonData.hash = null; - skeletonData.version = ReadString(input); - if (skeletonData.version.Length == 0) skeletonData.version = null; - skeletonData.width = ReadFloat(input); - skeletonData.height = ReadFloat(input); - - bool nonessential = ReadBoolean(input); - - if (nonessential) { - skeletonData.fps = ReadFloat(input); - skeletonData.imagesPath = ReadString(input); - if (skeletonData.imagesPath.Length == 0) skeletonData.imagesPath = null; - } - - // Bones. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - String name = ReadString(input); - BoneData parent = i == 0 ? null : skeletonData.bones.Items[ReadVarint(input, true)]; - BoneData data = new BoneData(i, name, 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.transformMode = TransformModeValues[ReadVarint(input, true)]; - if (nonessential) ReadInt(input); // Skip bone color. - skeletonData.bones.Add(data); - } - - // Slots. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - String slotName = ReadString(input); - BoneData boneData = skeletonData.bones.Items[ReadVarint(input, true)]; - SlotData slotData = new SlotData(i, slotName, boneData); - int color = ReadInt(input); - slotData.r = ((color & 0xff000000) >> 24) / 255f; - slotData.g = ((color & 0x00ff0000) >> 16) / 255f; - slotData.b = ((color & 0x0000ff00) >> 8) / 255f; - slotData.a = ((color & 0x000000ff)) / 255f; - slotData.attachmentName = ReadString(input); - slotData.blendMode = (BlendMode)ReadVarint(input, true); - skeletonData.slots.Add(slotData); - } - - // IK constraints. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - IkConstraintData data = new IkConstraintData(ReadString(input)); - data.order = ReadVarint(input, true); - for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) - data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]); - data.target = skeletonData.bones.Items[ReadVarint(input, true)]; - data.mix = ReadFloat(input); - data.bendDirection = ReadSByte(input); - skeletonData.ikConstraints.Add(data); - } - - // Transform constraints. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - TransformConstraintData data = new TransformConstraintData(ReadString(input)); - data.order = ReadVarint(input, true); - for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) - data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]); - data.target = skeletonData.bones.Items[ReadVarint(input, true)]; - data.offsetRotation = ReadFloat(input); - data.offsetX = ReadFloat(input) * scale; - data.offsetY = ReadFloat(input) * scale; - data.offsetScaleX = ReadFloat(input); - data.offsetScaleY = ReadFloat(input); - data.offsetShearY = ReadFloat(input); - data.rotateMix = ReadFloat(input); - data.translateMix = ReadFloat(input); - data.scaleMix = ReadFloat(input); - data.shearMix = ReadFloat(input); - skeletonData.transformConstraints.Add(data); - } - - // Path constraints - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - PathConstraintData data = new PathConstraintData(ReadString(input)); - data.order = ReadVarint(input, true); - for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) - data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]); - data.target = skeletonData.slots.Items[ReadVarint(input, true)]; - data.positionMode = (PositionMode)Enum.GetValues(typeof(PositionMode)).GetValue(ReadVarint(input, true)); - data.spacingMode = (SpacingMode)Enum.GetValues(typeof(SpacingMode)).GetValue(ReadVarint(input, true)); - data.rotateMode = (RotateMode)Enum.GetValues(typeof(RotateMode)).GetValue(ReadVarint(input, true)); - 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.rotateMix = ReadFloat(input); - data.translateMix = ReadFloat(input); - skeletonData.pathConstraints.Add(data); - } - - // Default skin. - Skin defaultSkin = ReadSkin(input, "default", nonessential); - if (defaultSkin != null) { - skeletonData.defaultSkin = defaultSkin; - skeletonData.skins.Add(defaultSkin); - } - - // Skins. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) - skeletonData.skins.Add(ReadSkin(input, ReadString(input), nonessential)); - - // Linked meshes. - for (int i = 0, n = linkedMeshes.Count; i < n; i++) { - SkeletonJson.LinkedMesh linkedMesh = linkedMeshes[i]; - Skin skin = linkedMesh.skin == null ? skeletonData.DefaultSkin : skeletonData.FindSkin(linkedMesh.skin); - if (skin == null) throw new Exception("Skin not found: " + linkedMesh.skin); - Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); - if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); - linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; - linkedMesh.mesh.UpdateUVs(); - } - linkedMeshes.Clear(); - - // Events. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - EventData data = new EventData(ReadString(input)); - data.Int = ReadVarint(input, false); - data.Float = ReadFloat(input); - data.String = ReadString(input); - skeletonData.events.Add(data); - } - - // Animations. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) - ReadAnimation(ReadString(input), input, skeletonData); - - skeletonData.bones.TrimExcess(); - skeletonData.slots.TrimExcess(); - skeletonData.skins.TrimExcess(); - skeletonData.events.TrimExcess(); - skeletonData.animations.TrimExcess(); - skeletonData.ikConstraints.TrimExcess(); - skeletonData.pathConstraints.TrimExcess(); - return skeletonData; - } - - - /// May be null. - private Skin ReadSkin (Stream input, String skinName, bool nonessential) { - int slotCount = ReadVarint(input, true); - if (slotCount == 0) return null; - Skin skin = new Skin(skinName); - 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 = ReadString(input); - Attachment attachment = ReadAttachment(input, skin, slotIndex, name, nonessential); - if (attachment != null) skin.AddAttachment(slotIndex, name, attachment); - } - } - return skin; - } - - private Attachment ReadAttachment (Stream input, Skin skin, int slotIndex, String attachmentName, bool nonessential) { - float scale = Scale; - - String name = ReadString(input); - if (name == null) name = attachmentName; - - AttachmentType type = (AttachmentType)input.ReadByte(); - switch (type) { - case AttachmentType.Region: { - String path = ReadString(input); - float rotation = ReadFloat(input); - float x = ReadFloat(input); - float y = ReadFloat(input); - float scaleX = ReadFloat(input); - float scaleY = ReadFloat(input); - float width = ReadFloat(input); - float height = ReadFloat(input); - int color = ReadInt(input); - - if (path == null) path = name; - RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); - if (region == null) return null; - region.Path = path; - region.x = x * scale; - region.y = y * scale; - region.scaleX = scaleX; - region.scaleY = scaleY; - region.rotation = rotation; - region.width = width * scale; - region.height = height * scale; - region.r = ((color & 0xff000000) >> 24) / 255f; - region.g = ((color & 0x00ff0000) >> 16) / 255f; - region.b = ((color & 0x0000ff00) >> 8) / 255f; - region.a = ((color & 0x000000ff)) / 255f; - region.UpdateOffset(); - return region; - } - case AttachmentType.Boundingbox: { - int vertexCount = ReadVarint(input, true); - Vertices vertices = ReadVertices(input, vertexCount); - if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0; // Avoid unused local warning. - - BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); - if (box == null) return null; - box.worldVerticesLength = vertexCount << 1; - box.vertices = vertices.vertices; - box.bones = vertices.bones; - return box; - } - case AttachmentType.Mesh: { - String path = ReadString(input); - int color = ReadInt(input); - int vertexCount = ReadVarint(input, true); - float[] uvs = ReadFloatArray(input, vertexCount << 1, 1); - int[] triangles = ReadShortArray(input); - Vertices vertices = ReadVertices(input, vertexCount); - int hullLength = ReadVarint(input, true); - int[] edges = null; - float width = 0, height = 0; - if (nonessential) { - edges = ReadShortArray(input); - width = ReadFloat(input); - height = ReadFloat(input); - } - - if (path == null) path = name; - MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); - if (mesh == null) return null; - mesh.Path = path; - mesh.r = ((color & 0xff000000) >> 24) / 255f; - mesh.g = ((color & 0x00ff0000) >> 16) / 255f; - mesh.b = ((color & 0x0000ff00) >> 8) / 255f; - mesh.a = ((color & 0x000000ff)) / 255f; - mesh.bones = vertices.bones; - mesh.vertices = vertices.vertices; - mesh.WorldVerticesLength = vertexCount << 1; - mesh.triangles = triangles; - mesh.regionUVs = uvs; - mesh.UpdateUVs(); - mesh.HullLength = hullLength << 1; - if (nonessential) { - mesh.Edges = edges; - mesh.Width = width * scale; - mesh.Height = height * scale; - } - return mesh; - } - case AttachmentType.Linkedmesh: { - String path = ReadString(input); - int color = ReadInt(input); - String skinName = ReadString(input); - String parent = ReadString(input); - bool inheritDeform = ReadBoolean(input); - float width = 0, height = 0; - if (nonessential) { - width = ReadFloat(input); - height = ReadFloat(input); - } - - if (path == null) path = name; - MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); - if (mesh == null) return null; - mesh.Path = path; - mesh.r = ((color & 0xff000000) >> 24) / 255f; - mesh.g = ((color & 0x00ff0000) >> 16) / 255f; - mesh.b = ((color & 0x0000ff00) >> 8) / 255f; - mesh.a = ((color & 0x000000ff)) / 255f; - mesh.inheritDeform = inheritDeform; - if (nonessential) { - mesh.Width = width * scale; - mesh.Height = height * scale; - } - linkedMeshes.Add(new SkeletonJson.LinkedMesh(mesh, skinName, slotIndex, parent)); - return mesh; - } - case AttachmentType.Path: { - bool closed = ReadBoolean(input); - bool constantSpeed = ReadBoolean(input); - int vertexCount = ReadVarint(input, true); - Vertices vertices = ReadVertices(input, vertexCount); - float[] lengths = new float[vertexCount / 3]; - for (int i = 0, n = lengths.Length; i < n; i++) - lengths[i] = ReadFloat(input) * scale; - if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0; // Avoid unused local warning. - - PathAttachment path = attachmentLoader.NewPathAttachment(skin, name); - if (path == null) return null; - path.closed = closed; - path.constantSpeed = constantSpeed; - path.worldVerticesLength = vertexCount << 1; - path.vertices = vertices.vertices; - path.bones = vertices.bones; - path.lengths = lengths; - return path; - } - } - return null; - } - - private Vertices ReadVertices (Stream input, int vertexCount) { - float scale = Scale; - int verticesLength = vertexCount << 1; - Vertices vertices = new Vertices(); - if(!ReadBoolean(input)) { - vertices.vertices = ReadFloatArray(input, verticesLength, scale); - return vertices; - } - var weights = new ExposedList(verticesLength * 3 * 3); - var bonesArray = new ExposedList(verticesLength * 3); - for (int i = 0; i < vertexCount; i++) { - int boneCount = ReadVarint(input, true); - bonesArray.Add(boneCount); - for (int ii = 0; ii < boneCount; ii++) { - bonesArray.Add(ReadVarint(input, true)); - weights.Add(ReadFloat(input) * scale); - weights.Add(ReadFloat(input) * scale); - weights.Add(ReadFloat(input)); - } - } - - vertices.vertices = weights.ToArray(); - vertices.bones = bonesArray.ToArray(); - return vertices; - } - - private float[] ReadFloatArray (Stream input, int n, float scale) { - float[] array = new float[n]; - if (scale == 1) { - for (int i = 0; i < n; i++) - array[i] = ReadFloat(input); - } else { - for (int i = 0; i < n; i++) - array[i] = ReadFloat(input) * scale; - } - return array; - } - - private int[] ReadShortArray (Stream input) { - int n = ReadVarint(input, true); - int[] array = new int[n]; - for (int i = 0; i < n; i++) - array[i] = (input.ReadByte() << 8) | input.ReadByte(); - return array; - } - - private void ReadAnimation (String name, Stream input, SkeletonData skeletonData) { - var timelines = new ExposedList(); - float scale = Scale; - float duration = 0; - - // 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++) { - int timelineType = input.ReadByte(); - int frameCount = ReadVarint(input, true); - switch (timelineType) { - case SLOT_COLOR: { - ColorTimeline timeline = new ColorTimeline(frameCount); - timeline.slotIndex = slotIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - float time = ReadFloat(input); - int color = ReadInt(input); - float r = ((color & 0xff000000) >> 24) / 255f; - float g = ((color & 0x00ff0000) >> 16) / 255f; - float b = ((color & 0x0000ff00) >> 8) / 255f; - float a = ((color & 0x000000ff)) / 255f; - timeline.SetFrame(frameIndex, time, r, g, b, a); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * ColorTimeline.ENTRIES]); - break; - } - case SLOT_ATTACHMENT: { - AttachmentTimeline timeline = new AttachmentTimeline(frameCount); - timeline.slotIndex = slotIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) - timeline.SetFrame(frameIndex, ReadFloat(input), ReadString(input)); - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[frameCount - 1]); - break; - } - } - } - } - - // 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++) { - int timelineType = input.ReadByte(); - int frameCount = ReadVarint(input, true); - switch (timelineType) { - case BONE_ROTATE: { - RotateTimeline timeline = new RotateTimeline(frameCount); - timeline.boneIndex = boneIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input)); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(frameCount - 1) * RotateTimeline.ENTRIES]); - break; - } - case BONE_TRANSLATE: - case BONE_SCALE: - case BONE_SHEAR: { - TranslateTimeline timeline; - float timelineScale = 1; - if (timelineType == BONE_SCALE) - timeline = new ScaleTimeline(frameCount); - else if (timelineType == BONE_SHEAR) - timeline = new ShearTimeline(frameCount); - else { - timeline = new TranslateTimeline(frameCount); - timelineScale = scale; - } - timeline.boneIndex = boneIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input) * timelineScale, ReadFloat(input) - * timelineScale); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TranslateTimeline.ENTRIES]); - break; - } - } - } - } - - // IK timelines. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - int index = ReadVarint(input, true); - int frameCount = ReadVarint(input, true); - IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount); - timeline.ikConstraintIndex = index; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadSByte(input)); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(frameCount - 1) * IkConstraintTimeline.ENTRIES]); - } - - // Transform constraint timelines. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - int index = ReadVarint(input, true); - int frameCount = ReadVarint(input, true); - TransformConstraintTimeline timeline = new TransformConstraintTimeline(frameCount); - timeline.transformConstraintIndex = index; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input)); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TransformConstraintTimeline.ENTRIES]); - } - - // Path constraint timelines. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - int index = ReadVarint(input, true); - PathConstraintData data = skeletonData.pathConstraints.Items[index]; - for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) { - int timelineType = ReadSByte(input); - int frameCount = ReadVarint(input, true); - switch(timelineType) { - case PATH_POSITION: - case PATH_SPACING: { - PathConstraintPositionTimeline timeline; - float timelineScale = 1; - if (timelineType == PATH_SPACING) { - timeline = new PathConstraintSpacingTimeline(frameCount); - if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale; - } else { - timeline = new PathConstraintPositionTimeline(frameCount); - if (data.positionMode == PositionMode.Fixed) timelineScale = scale; - } - timeline.pathConstraintIndex = index; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input) * timelineScale); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintPositionTimeline.ENTRIES]); - break; - } - case PATH_MIX: { - PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(frameCount); - timeline.pathConstraintIndex = index; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input)); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintMixTimeline.ENTRIES]); - break; - } - } - } - } - - // Deform timelines. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - Skin skin = skeletonData.skins.Items[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++) { - VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, ReadString(input)); - bool weighted = attachment.bones != null; - float[] vertices = attachment.vertices; - int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length; - - int frameCount = ReadVarint(input, true); - DeformTimeline timeline = new DeformTimeline(frameCount); - timeline.slotIndex = slotIndex; - timeline.attachment = attachment; - - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - float time = ReadFloat(input); - float[] deform; - int end = ReadVarint(input, true); - if (end == 0) - deform = weighted ? new float[deformLength] : vertices; - else { - deform = new float[deformLength]; - int start = ReadVarint(input, true); - end += start; - if (scale == 1) { - for (int v = start; v < end; v++) - deform[v] = ReadFloat(input); - } else { - for (int v = start; v < end; v++) - deform[v] = ReadFloat(input) * scale; - } - if (!weighted) { - for (int v = 0, vn = deform.Length; v < vn; v++) - deform[v] += vertices[v]; - } - } - - timeline.SetFrame(frameIndex, time, deform); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[frameCount - 1]); - } - } - } - - // Draw order timeline. - int drawOrderCount = ReadVarint(input, true); - if (drawOrderCount > 0) { - DrawOrderTimeline timeline = new DrawOrderTimeline(drawOrderCount); - int slotCount = skeletonData.slots.Count; - for (int i = 0; i < drawOrderCount; i++) { - float time = ReadFloat(input); - int offsetCount = ReadVarint(input, true); - int[] drawOrder = new int[slotCount]; - for (int ii = slotCount - 1; ii >= 0; ii--) - drawOrder[ii] = -1; - int[] unchanged = new int[slotCount - offsetCount]; - int originalIndex = 0, unchangedIndex = 0; - for (int ii = 0; ii < offsetCount; ii++) { - int slotIndex = ReadVarint(input, true); - // Collect unchanged items. - while (originalIndex != slotIndex) - unchanged[unchangedIndex++] = originalIndex++; - // Set changed items. - drawOrder[originalIndex + ReadVarint(input, true)] = originalIndex++; - } - // Collect remaining unchanged items. - while (originalIndex < slotCount) - unchanged[unchangedIndex++] = originalIndex++; - // Fill in unchanged items. - for (int ii = slotCount - 1; ii >= 0; ii--) - if (drawOrder[ii] == -1) drawOrder[ii] = unchanged[--unchangedIndex]; - timeline.SetFrame(i, time, drawOrder); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[drawOrderCount - 1]); - } - - // Event timeline. - int eventCount = ReadVarint(input, true); - if (eventCount > 0) { - EventTimeline timeline = new EventTimeline(eventCount); - for (int i = 0; i < eventCount; i++) { - float time = ReadFloat(input); - EventData eventData = skeletonData.events.Items[ReadVarint(input, true)]; - Event e = new Event(time, eventData); - e.Int = ReadVarint(input, false); - e.Float = ReadFloat(input); - e.String = ReadBoolean(input) ? ReadString(input) : eventData.String; - timeline.SetFrame(i, e); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[eventCount - 1]); - } - - timelines.TrimExcess(); - skeletonData.animations.Add(new Animation(name, timelines, duration)); - } - - private void ReadCurve (Stream input, int frameIndex, CurveTimeline timeline) { - switch (input.ReadByte()) { - case CURVE_STEPPED: - timeline.SetStepped(frameIndex); - break; - case CURVE_BEZIER: - timeline.SetCurve(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input)); - break; - } - } - - private static sbyte ReadSByte (Stream input) { - int value = input.ReadByte(); - if (value == -1) throw new EndOfStreamException(); - return (sbyte)value; - } - - private static bool ReadBoolean (Stream input) { - return input.ReadByte() != 0; - } - - private float ReadFloat (Stream input) { - buffer[3] = (byte)input.ReadByte(); - buffer[2] = (byte)input.ReadByte(); - buffer[1] = (byte)input.ReadByte(); - buffer[0] = (byte)input.ReadByte(); - return BitConverter.ToSingle(buffer, 0); - } - - private static int ReadInt (Stream input) { - return (input.ReadByte() << 24) + (input.ReadByte() << 16) + (input.ReadByte() << 8) + input.ReadByte(); - } - - private static int ReadVarint (Stream input, bool optimizePositive) { - int b = input.ReadByte(); - int result = b & 0x7F; - if ((b & 0x80) != 0) { - b = input.ReadByte(); - result |= (b & 0x7F) << 7; - if ((b & 0x80) != 0) { - b = input.ReadByte(); - result |= (b & 0x7F) << 14; - if ((b & 0x80) != 0) { - b = input.ReadByte(); - result |= (b & 0x7F) << 21; - if ((b & 0x80) != 0) result |= (input.ReadByte() & 0x7F) << 28; - } - } - } - return optimizePositive ? result : ((result >> 1) ^ -(result & 1)); - } - - private string ReadString (Stream input) { - int byteCount = ReadVarint(input, true); - switch (byteCount) { - case 0: - return null; - case 1: - return ""; - } - byteCount--; - byte[] buffer = this.buffer; - if (buffer.Length < byteCount) buffer = new byte[byteCount]; - ReadFully(input, buffer, 0, byteCount); - return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount); - } - - private static void ReadFully (Stream input, byte[] buffer, int offset, int length) { - while (length > 0) { - int count = input.Read(buffer, offset, length); - if (count <= 0) throw new EndOfStreamException(); - offset += count; - length -= count; - } - } - - internal class Vertices { - public int[] bones; - public float[] vertices; - } - } +#else + using (var input = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) + { +#endif + SkeletonData skeletonData = ReadSkeletonData(input); + skeletonData.name = Path.GetFileNameWithoutExtension(path); + return skeletonData; + } + } +#endif // WINDOWS_STOREAPP + + public static readonly TransformMode[] TransformModeValues = { + TransformMode.Normal, + TransformMode.OnlyTranslation, + TransformMode.NoRotationOrReflection, + TransformMode.NoScale, + TransformMode.NoScaleOrReflection + }; + + /// Returns the version string of binary skeleton data. + public static string GetVersionString(Stream input) + { + if (input == null) throw new ArgumentNullException("input"); + + try + { + // Hash. + int byteCount = ReadVarint(input, true); + if (byteCount > 1) input.Position += byteCount - 1; + + // Version. + byteCount = ReadVarint(input, true); + if (byteCount > 1) + { + byteCount--; + var buffer = new byte[byteCount]; + ReadFully(input, buffer, 0, byteCount); + return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount); + } + + throw new ArgumentException("Stream does not contain a valid binary Skeleton Data.", "input"); + } + catch (Exception e) + { + throw new ArgumentException("Stream does not contain a valid binary Skeleton Data.\n" + e, "input"); + } + } + + public SkeletonData ReadSkeletonData(Stream input) + { + if (input == null) throw new ArgumentNullException("input"); + float scale = Scale; + + var skeletonData = new SkeletonData(); + skeletonData.hash = ReadString(input); + if (skeletonData.hash.Length == 0) skeletonData.hash = null; + skeletonData.version = ReadString(input); + if (skeletonData.version.Length == 0) skeletonData.version = null; + skeletonData.width = ReadFloat(input); + skeletonData.height = ReadFloat(input); + + bool nonessential = ReadBoolean(input); + + if (nonessential) + { + skeletonData.fps = ReadFloat(input); + skeletonData.imagesPath = ReadString(input); + if (skeletonData.imagesPath.Length == 0) skeletonData.imagesPath = null; + } + + // Bones. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + String name = ReadString(input); + BoneData parent = i == 0 ? null : skeletonData.bones.Items[ReadVarint(input, true)]; + BoneData data = new BoneData(i, name, 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.transformMode = TransformModeValues[ReadVarint(input, true)]; + if (nonessential) ReadInt(input); // Skip bone color. + skeletonData.bones.Add(data); + } + + // Slots. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + String slotName = ReadString(input); + BoneData boneData = skeletonData.bones.Items[ReadVarint(input, true)]; + SlotData slotData = new SlotData(i, slotName, boneData); + int color = ReadInt(input); + slotData.r = ((color & 0xff000000) >> 24) / 255f; + slotData.g = ((color & 0x00ff0000) >> 16) / 255f; + slotData.b = ((color & 0x0000ff00) >> 8) / 255f; + slotData.a = ((color & 0x000000ff)) / 255f; + slotData.attachmentName = ReadString(input); + slotData.blendMode = (BlendMode)ReadVarint(input, true); + skeletonData.slots.Add(slotData); + } + + // IK constraints. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + IkConstraintData data = new IkConstraintData(ReadString(input)); + data.order = ReadVarint(input, true); + for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) + data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]); + data.target = skeletonData.bones.Items[ReadVarint(input, true)]; + data.mix = ReadFloat(input); + data.bendDirection = ReadSByte(input); + skeletonData.ikConstraints.Add(data); + } + + // Transform constraints. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + TransformConstraintData data = new TransformConstraintData(ReadString(input)); + data.order = ReadVarint(input, true); + for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) + data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]); + data.target = skeletonData.bones.Items[ReadVarint(input, true)]; + data.offsetRotation = ReadFloat(input); + data.offsetX = ReadFloat(input) * scale; + data.offsetY = ReadFloat(input) * scale; + data.offsetScaleX = ReadFloat(input); + data.offsetScaleY = ReadFloat(input); + data.offsetShearY = ReadFloat(input); + data.rotateMix = ReadFloat(input); + data.translateMix = ReadFloat(input); + data.scaleMix = ReadFloat(input); + data.shearMix = ReadFloat(input); + skeletonData.transformConstraints.Add(data); + } + + // Path constraints + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + PathConstraintData data = new PathConstraintData(ReadString(input)); + data.order = ReadVarint(input, true); + for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) + data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]); + data.target = skeletonData.slots.Items[ReadVarint(input, true)]; + data.positionMode = (PositionMode)Enum.GetValues(typeof(PositionMode)).GetValue(ReadVarint(input, true)); + data.spacingMode = (SpacingMode)Enum.GetValues(typeof(SpacingMode)).GetValue(ReadVarint(input, true)); + data.rotateMode = (RotateMode)Enum.GetValues(typeof(RotateMode)).GetValue(ReadVarint(input, true)); + 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.rotateMix = ReadFloat(input); + data.translateMix = ReadFloat(input); + skeletonData.pathConstraints.Add(data); + } + + // Default skin. + Skin defaultSkin = ReadSkin(input, "default", nonessential); + if (defaultSkin != null) + { + skeletonData.defaultSkin = defaultSkin; + skeletonData.skins.Add(defaultSkin); + } + + // Skins. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + skeletonData.skins.Add(ReadSkin(input, ReadString(input), nonessential)); + + // Linked meshes. + for (int i = 0, n = linkedMeshes.Count; i < n; i++) + { + SkeletonJson.LinkedMesh linkedMesh = linkedMeshes[i]; + Skin skin = linkedMesh.skin == null ? skeletonData.DefaultSkin : skeletonData.FindSkin(linkedMesh.skin); + if (skin == null) throw new Exception("Skin not found: " + linkedMesh.skin); + Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); + if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); + linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; + linkedMesh.mesh.UpdateUVs(); + } + linkedMeshes.Clear(); + + // Events. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + EventData data = new EventData(ReadString(input)); + data.Int = ReadVarint(input, false); + data.Float = ReadFloat(input); + data.String = ReadString(input); + skeletonData.events.Add(data); + } + + // Animations. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + ReadAnimation(ReadString(input), input, skeletonData); + + skeletonData.bones.TrimExcess(); + skeletonData.slots.TrimExcess(); + skeletonData.skins.TrimExcess(); + skeletonData.events.TrimExcess(); + skeletonData.animations.TrimExcess(); + skeletonData.ikConstraints.TrimExcess(); + skeletonData.pathConstraints.TrimExcess(); + return skeletonData; + } + + + /// May be null. + private Skin ReadSkin(Stream input, String skinName, bool nonessential) + { + int slotCount = ReadVarint(input, true); + if (slotCount == 0) return null; + Skin skin = new Skin(skinName); + 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 = ReadString(input); + Attachment attachment = ReadAttachment(input, skin, slotIndex, name, nonessential); + if (attachment != null) skin.AddAttachment(slotIndex, name, attachment); + } + } + return skin; + } + + private Attachment ReadAttachment(Stream input, Skin skin, int slotIndex, String attachmentName, bool nonessential) + { + float scale = Scale; + + String name = ReadString(input); + if (name == null) name = attachmentName; + + AttachmentType type = (AttachmentType)input.ReadByte(); + switch (type) + { + case AttachmentType.Region: + { + String path = ReadString(input); + float rotation = ReadFloat(input); + float x = ReadFloat(input); + float y = ReadFloat(input); + float scaleX = ReadFloat(input); + float scaleY = ReadFloat(input); + float width = ReadFloat(input); + float height = ReadFloat(input); + int color = ReadInt(input); + + if (path == null) path = name; + RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); + if (region == null) return null; + region.Path = path; + region.x = x * scale; + region.y = y * scale; + region.scaleX = scaleX; + region.scaleY = scaleY; + region.rotation = rotation; + region.width = width * scale; + region.height = height * scale; + region.r = ((color & 0xff000000) >> 24) / 255f; + region.g = ((color & 0x00ff0000) >> 16) / 255f; + region.b = ((color & 0x0000ff00) >> 8) / 255f; + region.a = ((color & 0x000000ff)) / 255f; + region.UpdateOffset(); + return region; + } + case AttachmentType.Boundingbox: + { + int vertexCount = ReadVarint(input, true); + Vertices vertices = ReadVertices(input, vertexCount); + if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0; // Avoid unused local warning. + + BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); + if (box == null) return null; + box.worldVerticesLength = vertexCount << 1; + box.vertices = vertices.vertices; + box.bones = vertices.bones; + return box; + } + case AttachmentType.Mesh: + { + String path = ReadString(input); + int color = ReadInt(input); + int vertexCount = ReadVarint(input, true); + float[] uvs = ReadFloatArray(input, vertexCount << 1, 1); + int[] triangles = ReadShortArray(input); + Vertices vertices = ReadVertices(input, vertexCount); + int hullLength = ReadVarint(input, true); + int[] edges = null; + float width = 0, height = 0; + if (nonessential) + { + edges = ReadShortArray(input); + width = ReadFloat(input); + height = ReadFloat(input); + } + + if (path == null) path = name; + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); + if (mesh == null) return null; + mesh.Path = path; + mesh.r = ((color & 0xff000000) >> 24) / 255f; + mesh.g = ((color & 0x00ff0000) >> 16) / 255f; + mesh.b = ((color & 0x0000ff00) >> 8) / 255f; + mesh.a = ((color & 0x000000ff)) / 255f; + mesh.bones = vertices.bones; + mesh.vertices = vertices.vertices; + mesh.WorldVerticesLength = vertexCount << 1; + mesh.triangles = triangles; + mesh.regionUVs = uvs; + mesh.UpdateUVs(); + mesh.HullLength = hullLength << 1; + if (nonessential) + { + mesh.Edges = edges; + mesh.Width = width * scale; + mesh.Height = height * scale; + } + return mesh; + } + case AttachmentType.Linkedmesh: + { + String path = ReadString(input); + int color = ReadInt(input); + String skinName = ReadString(input); + String parent = ReadString(input); + bool inheritDeform = ReadBoolean(input); + float width = 0, height = 0; + if (nonessential) + { + width = ReadFloat(input); + height = ReadFloat(input); + } + + if (path == null) path = name; + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); + if (mesh == null) return null; + mesh.Path = path; + mesh.r = ((color & 0xff000000) >> 24) / 255f; + mesh.g = ((color & 0x00ff0000) >> 16) / 255f; + mesh.b = ((color & 0x0000ff00) >> 8) / 255f; + mesh.a = ((color & 0x000000ff)) / 255f; + mesh.inheritDeform = inheritDeform; + if (nonessential) + { + mesh.Width = width * scale; + mesh.Height = height * scale; + } + linkedMeshes.Add(new SkeletonJson.LinkedMesh(mesh, skinName, slotIndex, parent)); + return mesh; + } + case AttachmentType.Path: + { + bool closed = ReadBoolean(input); + bool constantSpeed = ReadBoolean(input); + int vertexCount = ReadVarint(input, true); + Vertices vertices = ReadVertices(input, vertexCount); + float[] lengths = new float[vertexCount / 3]; + for (int i = 0, n = lengths.Length; i < n; i++) + lengths[i] = ReadFloat(input) * scale; + if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0; // Avoid unused local warning. + + PathAttachment path = attachmentLoader.NewPathAttachment(skin, name); + if (path == null) return null; + path.closed = closed; + path.constantSpeed = constantSpeed; + path.worldVerticesLength = vertexCount << 1; + path.vertices = vertices.vertices; + path.bones = vertices.bones; + path.lengths = lengths; + return path; + } + } + return null; + } + + private Vertices ReadVertices(Stream input, int vertexCount) + { + float scale = Scale; + int verticesLength = vertexCount << 1; + Vertices vertices = new Vertices(); + if (!ReadBoolean(input)) + { + vertices.vertices = ReadFloatArray(input, verticesLength, scale); + return vertices; + } + var weights = new ExposedList(verticesLength * 3 * 3); + var bonesArray = new ExposedList(verticesLength * 3); + for (int i = 0; i < vertexCount; i++) + { + int boneCount = ReadVarint(input, true); + bonesArray.Add(boneCount); + for (int ii = 0; ii < boneCount; ii++) + { + bonesArray.Add(ReadVarint(input, true)); + weights.Add(ReadFloat(input) * scale); + weights.Add(ReadFloat(input) * scale); + weights.Add(ReadFloat(input)); + } + } + + vertices.vertices = weights.ToArray(); + vertices.bones = bonesArray.ToArray(); + return vertices; + } + + private float[] ReadFloatArray(Stream input, int n, float scale) + { + float[] array = new float[n]; + if (scale == 1) + { + for (int i = 0; i < n; i++) + array[i] = ReadFloat(input); + } + else + { + for (int i = 0; i < n; i++) + array[i] = ReadFloat(input) * scale; + } + return array; + } + + private int[] ReadShortArray(Stream input) + { + int n = ReadVarint(input, true); + int[] array = new int[n]; + for (int i = 0; i < n; i++) + array[i] = (input.ReadByte() << 8) | input.ReadByte(); + return array; + } + + private void ReadAnimation(String name, Stream input, SkeletonData skeletonData) + { + var timelines = new ExposedList(); + float scale = Scale; + float duration = 0; + + // 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++) + { + int timelineType = input.ReadByte(); + int frameCount = ReadVarint(input, true); + switch (timelineType) + { + case SLOT_COLOR: + { + ColorTimeline timeline = new ColorTimeline(frameCount); + timeline.slotIndex = slotIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + float time = ReadFloat(input); + int color = ReadInt(input); + float r = ((color & 0xff000000) >> 24) / 255f; + float g = ((color & 0x00ff0000) >> 16) / 255f; + float b = ((color & 0x0000ff00) >> 8) / 255f; + float a = ((color & 0x000000ff)) / 255f; + timeline.SetFrame(frameIndex, time, r, g, b, a); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * ColorTimeline.ENTRIES]); + break; + } + case SLOT_ATTACHMENT: + { + AttachmentTimeline timeline = new AttachmentTimeline(frameCount); + timeline.slotIndex = slotIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + timeline.SetFrame(frameIndex, ReadFloat(input), ReadString(input)); + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[frameCount - 1]); + break; + } + } + } + } + + // 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++) + { + int timelineType = input.ReadByte(); + int frameCount = ReadVarint(input, true); + switch (timelineType) + { + case BONE_ROTATE: + { + RotateTimeline timeline = new RotateTimeline(frameCount); + timeline.boneIndex = boneIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input)); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(frameCount - 1) * RotateTimeline.ENTRIES]); + break; + } + case BONE_TRANSLATE: + case BONE_SCALE: + case BONE_SHEAR: + { + TranslateTimeline timeline; + float timelineScale = 1; + if (timelineType == BONE_SCALE) + timeline = new ScaleTimeline(frameCount); + else if (timelineType == BONE_SHEAR) + timeline = new ShearTimeline(frameCount); + else + { + timeline = new TranslateTimeline(frameCount); + timelineScale = scale; + } + timeline.boneIndex = boneIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input) * timelineScale, ReadFloat(input) + * timelineScale); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TranslateTimeline.ENTRIES]); + break; + } + } + } + } + + // IK timelines. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + int index = ReadVarint(input, true); + int frameCount = ReadVarint(input, true); + IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount); + timeline.ikConstraintIndex = index; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadSByte(input)); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(frameCount - 1) * IkConstraintTimeline.ENTRIES]); + } + + // Transform constraint timelines. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + int index = ReadVarint(input, true); + int frameCount = ReadVarint(input, true); + TransformConstraintTimeline timeline = new TransformConstraintTimeline(frameCount); + timeline.transformConstraintIndex = index; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input)); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TransformConstraintTimeline.ENTRIES]); + } + + // Path constraint timelines. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + int index = ReadVarint(input, true); + PathConstraintData data = skeletonData.pathConstraints.Items[index]; + for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) + { + int timelineType = ReadSByte(input); + int frameCount = ReadVarint(input, true); + switch (timelineType) + { + case PATH_POSITION: + case PATH_SPACING: + { + PathConstraintPositionTimeline timeline; + float timelineScale = 1; + if (timelineType == PATH_SPACING) + { + timeline = new PathConstraintSpacingTimeline(frameCount); + if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale; + } + else + { + timeline = new PathConstraintPositionTimeline(frameCount); + if (data.positionMode == PositionMode.Fixed) timelineScale = scale; + } + timeline.pathConstraintIndex = index; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input) * timelineScale); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintPositionTimeline.ENTRIES]); + break; + } + case PATH_MIX: + { + PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(frameCount); + timeline.pathConstraintIndex = index; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input)); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintMixTimeline.ENTRIES]); + break; + } + } + } + } + + // Deform timelines. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + Skin skin = skeletonData.skins.Items[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++) + { + VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, ReadString(input)); + bool weighted = attachment.bones != null; + float[] vertices = attachment.vertices; + int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length; + + int frameCount = ReadVarint(input, true); + DeformTimeline timeline = new DeformTimeline(frameCount); + timeline.slotIndex = slotIndex; + timeline.attachment = attachment; + + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + float time = ReadFloat(input); + float[] deform; + int end = ReadVarint(input, true); + if (end == 0) + deform = weighted ? new float[deformLength] : vertices; + else + { + deform = new float[deformLength]; + int start = ReadVarint(input, true); + end += start; + if (scale == 1) + { + for (int v = start; v < end; v++) + deform[v] = ReadFloat(input); + } + else + { + for (int v = start; v < end; v++) + deform[v] = ReadFloat(input) * scale; + } + if (!weighted) + { + for (int v = 0, vn = deform.Length; v < vn; v++) + deform[v] += vertices[v]; + } + } + + timeline.SetFrame(frameIndex, time, deform); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[frameCount - 1]); + } + } + } + + // Draw order timeline. + int drawOrderCount = ReadVarint(input, true); + if (drawOrderCount > 0) + { + DrawOrderTimeline timeline = new DrawOrderTimeline(drawOrderCount); + int slotCount = skeletonData.slots.Count; + for (int i = 0; i < drawOrderCount; i++) + { + float time = ReadFloat(input); + int offsetCount = ReadVarint(input, true); + int[] drawOrder = new int[slotCount]; + for (int ii = slotCount - 1; ii >= 0; ii--) + drawOrder[ii] = -1; + int[] unchanged = new int[slotCount - offsetCount]; + int originalIndex = 0, unchangedIndex = 0; + for (int ii = 0; ii < offsetCount; ii++) + { + int slotIndex = ReadVarint(input, true); + // Collect unchanged items. + while (originalIndex != slotIndex) + unchanged[unchangedIndex++] = originalIndex++; + // Set changed items. + drawOrder[originalIndex + ReadVarint(input, true)] = originalIndex++; + } + // Collect remaining unchanged items. + while (originalIndex < slotCount) + unchanged[unchangedIndex++] = originalIndex++; + // Fill in unchanged items. + for (int ii = slotCount - 1; ii >= 0; ii--) + if (drawOrder[ii] == -1) drawOrder[ii] = unchanged[--unchangedIndex]; + timeline.SetFrame(i, time, drawOrder); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[drawOrderCount - 1]); + } + + // Event timeline. + int eventCount = ReadVarint(input, true); + if (eventCount > 0) + { + EventTimeline timeline = new EventTimeline(eventCount); + for (int i = 0; i < eventCount; i++) + { + float time = ReadFloat(input); + EventData eventData = skeletonData.events.Items[ReadVarint(input, true)]; + Event e = new Event(time, eventData); + e.Int = ReadVarint(input, false); + e.Float = ReadFloat(input); + e.String = ReadBoolean(input) ? ReadString(input) : eventData.String; + timeline.SetFrame(i, e); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[eventCount - 1]); + } + + timelines.TrimExcess(); + skeletonData.animations.Add(new Animation(name, timelines, duration)); + } + + private void ReadCurve(Stream input, int frameIndex, CurveTimeline timeline) + { + switch (input.ReadByte()) + { + case CURVE_STEPPED: + timeline.SetStepped(frameIndex); + break; + case CURVE_BEZIER: + timeline.SetCurve(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input)); + break; + } + } + + private static sbyte ReadSByte(Stream input) + { + int value = input.ReadByte(); + if (value == -1) throw new EndOfStreamException(); + return (sbyte)value; + } + + private static bool ReadBoolean(Stream input) + { + return input.ReadByte() != 0; + } + + private float ReadFloat(Stream input) + { + buffer[3] = (byte)input.ReadByte(); + buffer[2] = (byte)input.ReadByte(); + buffer[1] = (byte)input.ReadByte(); + buffer[0] = (byte)input.ReadByte(); + return BitConverter.ToSingle(buffer, 0); + } + + private static int ReadInt(Stream input) + { + return (input.ReadByte() << 24) + (input.ReadByte() << 16) + (input.ReadByte() << 8) + input.ReadByte(); + } + + private static int ReadVarint(Stream input, bool optimizePositive) + { + int b = input.ReadByte(); + int result = b & 0x7F; + if ((b & 0x80) != 0) + { + b = input.ReadByte(); + result |= (b & 0x7F) << 7; + if ((b & 0x80) != 0) + { + b = input.ReadByte(); + result |= (b & 0x7F) << 14; + if ((b & 0x80) != 0) + { + b = input.ReadByte(); + result |= (b & 0x7F) << 21; + if ((b & 0x80) != 0) result |= (input.ReadByte() & 0x7F) << 28; + } + } + } + return optimizePositive ? result : ((result >> 1) ^ -(result & 1)); + } + + private string ReadString(Stream input) + { + int byteCount = ReadVarint(input, true); + switch (byteCount) + { + case 0: + return null; + case 1: + return ""; + } + byteCount--; + byte[] buffer = this.buffer; + if (buffer.Length < byteCount) buffer = new byte[byteCount]; + ReadFully(input, buffer, 0, byteCount); + return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount); + } + + private static void ReadFully(Stream input, byte[] buffer, int offset, int length) + { + while (length > 0) + { + int count = input.Read(buffer, offset, length); + if (count <= 0) throw new EndOfStreamException(); + offset += count; + length -= count; + } + } + + internal class Vertices + { + public int[] bones; + public float[] vertices; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/SkeletonBounds.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/SkeletonBounds.cs index ca5917f..9769852 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/SkeletonBounds.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/SkeletonBounds.cs @@ -30,192 +30,219 @@ using System; -namespace Spine3_5_51 { - public class SkeletonBounds { - private ExposedList polygonPool = new ExposedList(); - private float minX, minY, maxX, maxY; - - public ExposedList BoundingBoxes { get; private set; } - public ExposedList Polygons { get; private set; } - public float MinX { get { return minX; } set { minX = value; } } - public float MinY { get { return minY; } set { minY = value; } } - public float MaxX { get { return maxX; } set { maxX = value; } } - public float MaxY { get { return maxY; } set { maxY = value; } } - public float Width { get { return maxX - minX; } } - public float Height { get { return maxY - minY; } } - - public SkeletonBounds () { - BoundingBoxes = new ExposedList(); - Polygons = new ExposedList(); - } - - public void Update (Skeleton skeleton, bool updateAabb) { - ExposedList boundingBoxes = BoundingBoxes; - ExposedList polygons = Polygons; - ExposedList slots = skeleton.slots; - int slotCount = slots.Count; - - boundingBoxes.Clear(); - for (int i = 0, n = polygons.Count; i < n; i++) - polygonPool.Add(polygons.Items[i]); - polygons.Clear(); - - for (int i = 0; i < slotCount; i++) { - Slot slot = slots.Items[i]; - BoundingBoxAttachment boundingBox = slot.attachment as BoundingBoxAttachment; - if (boundingBox == null) continue; - boundingBoxes.Add(boundingBox); - - Polygon polygon = null; - int poolCount = polygonPool.Count; - if (poolCount > 0) { - polygon = polygonPool.Items[poolCount - 1]; - polygonPool.RemoveAt(poolCount - 1); - } else - polygon = new Polygon(); - polygons.Add(polygon); - - int count = boundingBox.Vertices.Length; - polygon.Count = count; - if (polygon.Vertices.Length < count) polygon.Vertices = new float[count]; - boundingBox.ComputeWorldVertices(slot, polygon.Vertices); - } - - if (updateAabb) { - AabbCompute(); - } else { - minX = int.MinValue; - minY = int.MinValue; - maxX = int.MaxValue; - maxY = int.MaxValue; - } - } - - private void AabbCompute () { - float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; - ExposedList polygons = Polygons; - for (int i = 0, n = polygons.Count; i < n; i++) { - Polygon polygon = polygons.Items[i]; - float[] vertices = polygon.Vertices; - for (int ii = 0, nn = polygon.Count; ii < nn; ii += 2) { - float x = vertices[ii]; - float y = vertices[ii + 1]; - minX = Math.Min(minX, x); - minY = Math.Min(minY, y); - maxX = Math.Max(maxX, x); - maxY = Math.Max(maxY, y); - } - } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; - } - - - /// Returns true if the axis aligned bounding box contains the point. - public bool AabbContainsPoint (float x, float y) { - return x >= minX && x <= maxX && y >= minY && y <= maxY; - } - - /// Returns true if the axis aligned bounding box intersects the line segment. - public bool AabbIntersectsSegment (float x1, float y1, float x2, float y2) { - float minX = this.minX; - float minY = this.minY; - float maxX = this.maxX; - float maxY = this.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; - } - - /// Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds. - public bool AabbIntersectsSkeleton (SkeletonBounds bounds) { - return minX < bounds.maxX && maxX > bounds.minX && minY < bounds.maxY && maxY > bounds.minY; - } - - /// Returns true if the polygon contains the point. - public bool ContainsPoint (Polygon polygon, float x, float y) { - float[] 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; - } - - /// 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. - public BoundingBoxAttachment ContainsPoint (float x, float y) { - ExposedList polygons = Polygons; - for (int i = 0, n = polygons.Count; i < n; i++) - if (ContainsPoint(polygons.Items[i], x, y)) return BoundingBoxes.Items[i]; - return null; - } - - /// 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. - public BoundingBoxAttachment IntersectsSegment (float x1, float y1, float x2, float y2) { - ExposedList polygons = Polygons; - for (int i = 0, n = polygons.Count; i < n; i++) - if (IntersectsSegment(polygons.Items[i], x1, y1, x2, y2)) return BoundingBoxes.Items[i]; - return null; - } - - /// Returns true if the polygon contains the line segment. - public bool IntersectsSegment (Polygon polygon, float x1, float y1, float x2, float y2) { - float[] vertices = polygon.Vertices; - int 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 (int 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; - } - - public Polygon GetPolygon (BoundingBoxAttachment attachment) { - int index = BoundingBoxes.IndexOf(attachment); - return index == -1 ? null : Polygons.Items[index]; - } - } - - public class Polygon { - public float[] Vertices { get; set; } - public int Count { get; set; } - - public Polygon () { - Vertices = new float[16]; - } - } +namespace Spine3_5_51 +{ + public class SkeletonBounds + { + private ExposedList polygonPool = new ExposedList(); + private float minX, minY, maxX, maxY; + + public ExposedList BoundingBoxes { get; private set; } + public ExposedList Polygons { get; private set; } + public float MinX { get { return minX; } set { minX = value; } } + public float MinY { get { return minY; } set { minY = value; } } + public float MaxX { get { return maxX; } set { maxX = value; } } + public float MaxY { get { return maxY; } set { maxY = value; } } + public float Width { get { return maxX - minX; } } + public float Height { get { return maxY - minY; } } + + public SkeletonBounds() + { + BoundingBoxes = new ExposedList(); + Polygons = new ExposedList(); + } + + public void Update(Skeleton skeleton, bool updateAabb) + { + ExposedList boundingBoxes = BoundingBoxes; + ExposedList polygons = Polygons; + ExposedList slots = skeleton.slots; + int slotCount = slots.Count; + + boundingBoxes.Clear(); + for (int i = 0, n = polygons.Count; i < n; i++) + polygonPool.Add(polygons.Items[i]); + polygons.Clear(); + + for (int i = 0; i < slotCount; i++) + { + Slot slot = slots.Items[i]; + BoundingBoxAttachment boundingBox = slot.attachment as BoundingBoxAttachment; + if (boundingBox == null) continue; + boundingBoxes.Add(boundingBox); + + Polygon polygon = null; + int poolCount = polygonPool.Count; + if (poolCount > 0) + { + polygon = polygonPool.Items[poolCount - 1]; + polygonPool.RemoveAt(poolCount - 1); + } + else + polygon = new Polygon(); + polygons.Add(polygon); + + int count = boundingBox.Vertices.Length; + polygon.Count = count; + if (polygon.Vertices.Length < count) polygon.Vertices = new float[count]; + boundingBox.ComputeWorldVertices(slot, polygon.Vertices); + } + + if (updateAabb) + { + AabbCompute(); + } + else + { + minX = int.MinValue; + minY = int.MinValue; + maxX = int.MaxValue; + maxY = int.MaxValue; + } + } + + private void AabbCompute() + { + float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; + ExposedList polygons = Polygons; + for (int i = 0, n = polygons.Count; i < n; i++) + { + Polygon polygon = polygons.Items[i]; + float[] vertices = polygon.Vertices; + for (int ii = 0, nn = polygon.Count; ii < nn; ii += 2) + { + float x = vertices[ii]; + float y = vertices[ii + 1]; + minX = Math.Min(minX, x); + minY = Math.Min(minY, y); + maxX = Math.Max(maxX, x); + maxY = Math.Max(maxY, y); + } + } + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + + /// Returns true if the axis aligned bounding box contains the point. + public bool AabbContainsPoint(float x, float y) + { + return x >= minX && x <= maxX && y >= minY && y <= maxY; + } + + /// Returns true if the axis aligned bounding box intersects the line segment. + public bool AabbIntersectsSegment(float x1, float y1, float x2, float y2) + { + float minX = this.minX; + float minY = this.minY; + float maxX = this.maxX; + float maxY = this.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; + } + + /// Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds. + public bool AabbIntersectsSkeleton(SkeletonBounds bounds) + { + return minX < bounds.maxX && maxX > bounds.minX && minY < bounds.maxY && maxY > bounds.minY; + } + + /// Returns true if the polygon contains the point. + public bool ContainsPoint(Polygon polygon, float x, float y) + { + float[] 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; + } + + /// 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. + public BoundingBoxAttachment ContainsPoint(float x, float y) + { + ExposedList polygons = Polygons; + for (int i = 0, n = polygons.Count; i < n; i++) + if (ContainsPoint(polygons.Items[i], x, y)) return BoundingBoxes.Items[i]; + return null; + } + + /// 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. + public BoundingBoxAttachment IntersectsSegment(float x1, float y1, float x2, float y2) + { + ExposedList polygons = Polygons; + for (int i = 0, n = polygons.Count; i < n; i++) + if (IntersectsSegment(polygons.Items[i], x1, y1, x2, y2)) return BoundingBoxes.Items[i]; + return null; + } + + /// Returns true if the polygon contains the line segment. + public bool IntersectsSegment(Polygon polygon, float x1, float y1, float x2, float y2) + { + float[] vertices = polygon.Vertices; + int 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 (int 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; + } + + public Polygon GetPolygon(BoundingBoxAttachment attachment) + { + int index = BoundingBoxes.IndexOf(attachment); + return index == -1 ? null : Polygons.Items[index]; + } + } + + public class Polygon + { + public float[] Vertices { get; set; } + public int Count { get; set; } + + public Polygon() + { + Vertices = new float[16]; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/SkeletonData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/SkeletonData.cs index 8669eee..177b977 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/SkeletonData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/SkeletonData.cs @@ -30,174 +30,194 @@ using System; -namespace Spine3_5_51 { - public class SkeletonData { - internal string name; - internal ExposedList bones = new ExposedList(); - internal ExposedList slots = new ExposedList(); - internal ExposedList skins = new ExposedList(); - internal Skin defaultSkin; - internal ExposedList events = new ExposedList(); - internal ExposedList animations = new ExposedList(); - internal ExposedList ikConstraints = new ExposedList(); - internal ExposedList transformConstraints = new ExposedList(); - internal ExposedList pathConstraints = new ExposedList(); - internal float width, height; - internal string version, hash; - - // Nonessential. - internal float fps; - internal string imagesPath; - - public String Name { get { return name; } set { name = value; } } - public ExposedList Bones { get { return bones; } } // Ordered parents first. - public ExposedList Slots { get { return slots; } } // Setup pose draw order. - public ExposedList Skins { get { return skins; } set { skins = value; } } - /// May be null. - public Skin DefaultSkin { get { return defaultSkin; } set { defaultSkin = value; } } - public ExposedList Events { get { return events; } set { events = value; } } - public ExposedList Animations { get { return animations; } set { animations = value; } } - public ExposedList IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } } - public ExposedList TransformConstraints { get { return transformConstraints; } set { transformConstraints = value; } } - public ExposedList PathConstraints { get { return pathConstraints; } set { pathConstraints = value; } } - - public float Width { get { return width; } set { width = value; } } - public float Height { get { return height; } set { height = value; } } - /// The Spine version used to export this data, or null. - public string Version { get { return version; } set { version = value; } } - public string Hash { get { return hash; } set { hash = value; } } - public string ImagesPath { get { return imagesPath; } set { imagesPath = value; } } - public float Fps { get { return fps; } set { fps = value; } } - - // --- Bones. - - /// May be null. - public BoneData FindBone (String boneName) { - if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); - ExposedList bones = this.bones; - for (int i = 0, n = bones.Count; i < n; i++) { - BoneData bone = bones.Items[i]; - if (bone.name == boneName) return bone; - } - return null; - } - - /// -1 if the bone was not found. - public int FindBoneIndex (String boneName) { - if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); - ExposedList bones = this.bones; - for (int i = 0, n = bones.Count; i < n; i++) - if (bones.Items[i].name == boneName) return i; - return -1; - } - - // --- Slots. - - /// May be null. - public SlotData FindSlot (String slotName) { - if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); - ExposedList slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) { - SlotData slot = slots.Items[i]; - if (slot.name == slotName) return slot; - } - return null; - } - - /// -1 if the slot was not found. - public int FindSlotIndex (String slotName) { - if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); - ExposedList slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) - if (slots.Items[i].name == slotName) return i; - return -1; - } - - // --- Skins. - - /// May be null. - public Skin FindSkin (String skinName) { - if (skinName == null) throw new ArgumentNullException("skinName", "skinName cannot be null."); - foreach (Skin skin in skins) - if (skin.name == skinName) return skin; - return null; - } - - // --- Events. - - /// May be null. - public EventData FindEvent (String eventDataName) { - if (eventDataName == null) throw new ArgumentNullException("eventDataName", "eventDataName cannot be null."); - foreach (EventData eventData in events) - if (eventData.name == eventDataName) return eventData; - return null; - } - - // --- Animations. - - /// May be null. - public Animation FindAnimation (String animationName) { - if (animationName == null) throw new ArgumentNullException("animationName", "animationName cannot be null."); - ExposedList animations = this.animations; - for (int i = 0, n = animations.Count; i < n; i++) { - Animation animation = animations.Items[i]; - if (animation.name == animationName) return animation; - } - return null; - } - - // --- IK constraints. - - /// May be null. - public IkConstraintData FindIkConstraint (String constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - ExposedList ikConstraints = this.ikConstraints; - for (int i = 0, n = ikConstraints.Count; i < n; i++) { - IkConstraintData ikConstraint = ikConstraints.Items[i]; - if (ikConstraint.name == constraintName) return ikConstraint; - } - return null; - } - - // --- Transform constraints. - - /// May be null. - public TransformConstraintData FindTransformConstraint (String constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - ExposedList transformConstraints = this.transformConstraints; - for (int i = 0, n = transformConstraints.Count; i < n; i++) { - TransformConstraintData transformConstraint = transformConstraints.Items[i]; - if (transformConstraint.name == constraintName) return transformConstraint; - } - return null; - } - - // --- Path constraints. - - /// May be null. - public PathConstraintData FindPathConstraint (String constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - ExposedList pathConstraints = this.pathConstraints; - for (int i = 0, n = pathConstraints.Count; i < n; i++) { - PathConstraintData constraint = pathConstraints.Items[i]; - if (constraint.name.Equals(constraintName)) return constraint; - } - return null; - } - - /// -1 if the path constraint was not found. - public int FindPathConstraintIndex (String pathConstraintName) { - if (pathConstraintName == null) throw new ArgumentNullException("pathConstraintName", "pathConstraintName cannot be null."); - ExposedList pathConstraints = this.pathConstraints; - for (int i = 0, n = pathConstraints.Count; i < n; i++) - if (pathConstraints.Items[i].name.Equals(pathConstraintName)) return i; - return -1; - } - - // --- - - override public String ToString () { - return name ?? base.ToString(); - } - } +namespace Spine3_5_51 +{ + public class SkeletonData + { + internal string name; + internal ExposedList bones = new ExposedList(); + internal ExposedList slots = new ExposedList(); + internal ExposedList skins = new ExposedList(); + internal Skin defaultSkin; + internal ExposedList events = new ExposedList(); + internal ExposedList animations = new ExposedList(); + internal ExposedList ikConstraints = new ExposedList(); + internal ExposedList transformConstraints = new ExposedList(); + internal ExposedList pathConstraints = new ExposedList(); + internal float width, height; + internal string version, hash; + + // Nonessential. + internal float fps; + internal string imagesPath; + + public String Name { get { return name; } set { name = value; } } + public ExposedList Bones { get { return bones; } } // Ordered parents first. + public ExposedList Slots { get { return slots; } } // Setup pose draw order. + public ExposedList Skins { get { return skins; } set { skins = value; } } + /// May be null. + public Skin DefaultSkin { get { return defaultSkin; } set { defaultSkin = value; } } + public ExposedList Events { get { return events; } set { events = value; } } + public ExposedList Animations { get { return animations; } set { animations = value; } } + public ExposedList IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } } + public ExposedList TransformConstraints { get { return transformConstraints; } set { transformConstraints = value; } } + public ExposedList PathConstraints { get { return pathConstraints; } set { pathConstraints = value; } } + + public float Width { get { return width; } set { width = value; } } + public float Height { get { return height; } set { height = value; } } + /// The Spine version used to export this data, or null. + public string Version { get { return version; } set { version = value; } } + public string Hash { get { return hash; } set { hash = value; } } + public string ImagesPath { get { return imagesPath; } set { imagesPath = value; } } + public float Fps { get { return fps; } set { fps = value; } } + + // --- Bones. + + /// May be null. + public BoneData FindBone(String boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); + ExposedList bones = this.bones; + for (int i = 0, n = bones.Count; i < n; i++) + { + BoneData bone = bones.Items[i]; + if (bone.name == boneName) return bone; + } + return null; + } + + /// -1 if the bone was not found. + public int FindBoneIndex(String boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); + ExposedList bones = this.bones; + for (int i = 0, n = bones.Count; i < n; i++) + if (bones.Items[i].name == boneName) return i; + return -1; + } + + // --- Slots. + + /// May be null. + public SlotData FindSlot(String slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + ExposedList slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + { + SlotData slot = slots.Items[i]; + if (slot.name == slotName) return slot; + } + return null; + } + + /// -1 if the slot was not found. + public int FindSlotIndex(String slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + ExposedList slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + if (slots.Items[i].name == slotName) return i; + return -1; + } + + // --- Skins. + + /// May be null. + public Skin FindSkin(String skinName) + { + if (skinName == null) throw new ArgumentNullException("skinName", "skinName cannot be null."); + foreach (Skin skin in skins) + if (skin.name == skinName) return skin; + return null; + } + + // --- Events. + + /// May be null. + public EventData FindEvent(String eventDataName) + { + if (eventDataName == null) throw new ArgumentNullException("eventDataName", "eventDataName cannot be null."); + foreach (EventData eventData in events) + if (eventData.name == eventDataName) return eventData; + return null; + } + + // --- Animations. + + /// May be null. + public Animation FindAnimation(String animationName) + { + if (animationName == null) throw new ArgumentNullException("animationName", "animationName cannot be null."); + ExposedList animations = this.animations; + for (int i = 0, n = animations.Count; i < n; i++) + { + Animation animation = animations.Items[i]; + if (animation.name == animationName) return animation; + } + return null; + } + + // --- IK constraints. + + /// May be null. + public IkConstraintData FindIkConstraint(String constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + ExposedList ikConstraints = this.ikConstraints; + for (int i = 0, n = ikConstraints.Count; i < n; i++) + { + IkConstraintData ikConstraint = ikConstraints.Items[i]; + if (ikConstraint.name == constraintName) return ikConstraint; + } + return null; + } + + // --- Transform constraints. + + /// May be null. + public TransformConstraintData FindTransformConstraint(String constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + ExposedList transformConstraints = this.transformConstraints; + for (int i = 0, n = transformConstraints.Count; i < n; i++) + { + TransformConstraintData transformConstraint = transformConstraints.Items[i]; + if (transformConstraint.name == constraintName) return transformConstraint; + } + return null; + } + + // --- Path constraints. + + /// May be null. + public PathConstraintData FindPathConstraint(String constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + ExposedList pathConstraints = this.pathConstraints; + for (int i = 0, n = pathConstraints.Count; i < n; i++) + { + PathConstraintData constraint = pathConstraints.Items[i]; + if (constraint.name.Equals(constraintName)) return constraint; + } + return null; + } + + /// -1 if the path constraint was not found. + public int FindPathConstraintIndex(String pathConstraintName) + { + if (pathConstraintName == null) throw new ArgumentNullException("pathConstraintName", "pathConstraintName cannot be null."); + ExposedList pathConstraints = this.pathConstraints; + for (int i = 0, n = pathConstraints.Count; i < n; i++) + if (pathConstraints.Items[i].name.Equals(pathConstraintName)) return i; + return -1; + } + + // --- + + override public String ToString() + { + return name ?? base.ToString(); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/SkeletonJson.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/SkeletonJson.cs index dc05a5f..e0f8ea5 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/SkeletonJson.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/SkeletonJson.cs @@ -33,32 +33,36 @@ #endif using System; -using System.IO; using System.Collections.Generic; +using System.IO; #if WINDOWS_STOREAPP using System.Threading.Tasks; using Windows.Storage; #endif -namespace Spine3_5_51 { - public class SkeletonJson { - public float Scale { get; set; } +namespace Spine3_5_51 +{ + public class SkeletonJson + { + public float Scale { get; set; } - private AttachmentLoader attachmentLoader; - private List linkedMeshes = new List(); + private AttachmentLoader attachmentLoader; + private List linkedMeshes = new List(); - public SkeletonJson (params Atlas[] atlasArray) - : this(new AtlasAttachmentLoader(atlasArray)) { - } + public SkeletonJson(params Atlas[] atlasArray) + : this(new AtlasAttachmentLoader(atlasArray)) + { + } - public SkeletonJson (AttachmentLoader attachmentLoader) { - if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader", "attachmentLoader cannot be null."); - this.attachmentLoader = attachmentLoader; - Scale = 1; - } + public SkeletonJson(AttachmentLoader attachmentLoader) + { + if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader", "attachmentLoader cannot be null."); + this.attachmentLoader = attachmentLoader; + Scale = 1; + } - #if !IS_UNITY && WINDOWS_STOREAPP +#if !IS_UNITY && WINDOWS_STOREAPP private async Task ReadFile(string path) { var folder = Windows.ApplicationModel.Package.Current.InstalledLocation; var file = await folder.GetFileAsync(path).AsTask().ConfigureAwait(false); @@ -72,739 +76,850 @@ private async Task ReadFile(string path) { public SkeletonData ReadSkeletonData (String path) { return this.ReadFile(path).Result; } - #else - public SkeletonData ReadSkeletonData (String path) { - #if WINDOWS_PHONE +#else + public SkeletonData ReadSkeletonData(String path) + { +#if WINDOWS_PHONE using (var reader = new StreamReader(Microsoft.Xna.Framework.TitleContainer.OpenStream(path))) { - #else - using (var reader = new StreamReader(new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))) { - #endif - SkeletonData skeletonData = ReadSkeletonData(reader); - skeletonData.name = Path.GetFileNameWithoutExtension(path); - return skeletonData; - } - } - #endif - - public SkeletonData ReadSkeletonData (TextReader reader) { - if (reader == null) throw new ArgumentNullException("reader", "reader cannot be null."); - - var scale = this.Scale; - var skeletonData = new SkeletonData(); - - var root = Json.Deserialize(reader) as Dictionary; - if (root == null) throw new Exception("Invalid JSON."); - - // Skeleton. - if (root.ContainsKey("skeleton")) { - var skeletonMap = (Dictionary)root["skeleton"]; - skeletonData.hash = (String)skeletonMap["hash"]; - skeletonData.version = (String)skeletonMap["spine"]; - skeletonData.width = GetFloat(skeletonMap, "width", 0); - skeletonData.height = GetFloat(skeletonMap, "height", 0); - skeletonData.fps = GetFloat(skeletonMap, "fps", 0); - skeletonData.imagesPath = GetString(skeletonMap, "images", null); - } - - // Bones. - foreach (Dictionary boneMap in (List)root["bones"]) { - BoneData parent = null; - if (boneMap.ContainsKey("parent")) { - parent = skeletonData.FindBone((String)boneMap["parent"]); - if (parent == null) - throw new Exception("Parent bone not found: " + boneMap["parent"]); - } - var data = new BoneData(skeletonData.Bones.Count, (String)boneMap["name"], parent); - data.length = GetFloat(boneMap, "length", 0) * scale; - data.x = GetFloat(boneMap, "x", 0) * scale; - data.y = GetFloat(boneMap, "y", 0) * scale; - data.rotation = GetFloat(boneMap, "rotation", 0); - data.scaleX = GetFloat(boneMap, "scaleX", 1); - data.scaleY = GetFloat(boneMap, "scaleY", 1); - data.shearX = GetFloat(boneMap, "shearX", 0); - data.shearY = GetFloat(boneMap, "shearY", 0); - - string tm = GetString(boneMap, "transform", TransformMode.Normal.ToString()); - data.transformMode = (TransformMode)Enum.Parse(typeof(TransformMode), tm, true); - - skeletonData.bones.Add(data); - } - - // Slots. - if (root.ContainsKey("slots")) { - foreach (Dictionary slotMap in (List)root["slots"]) { - var slotName = (String)slotMap["name"]; - var boneName = (String)slotMap["bone"]; - BoneData boneData = skeletonData.FindBone(boneName); - if (boneData == null) throw new Exception("Slot bone not found: " + boneName); - var data = new SlotData(skeletonData.Slots.Count, slotName, boneData); - - if (slotMap.ContainsKey("color")) { - var color = (String)slotMap["color"]; - data.r = ToColor(color, 0); - data.g = ToColor(color, 1); - data.b = ToColor(color, 2); - data.a = ToColor(color, 3); - } - - data.attachmentName = GetString(slotMap, "attachment", null); - if (slotMap.ContainsKey("blend")) - data.blendMode = (BlendMode)Enum.Parse(typeof(BlendMode), (String)slotMap["blend"], false); - else - data.blendMode = BlendMode.normal; - skeletonData.slots.Add(data); - } - } - - // IK constraints. - if (root.ContainsKey("ik")) { - foreach (Dictionary constraintMap in (List)root["ik"]) { - IkConstraintData data = new IkConstraintData((String)constraintMap["name"]); - data.order = GetInt(constraintMap, "order", 0); - - foreach (String boneName in (List)constraintMap["bones"]) { - BoneData bone = skeletonData.FindBone(boneName); - if (bone == null) throw new Exception("IK constraint bone not found: " + boneName); - data.bones.Add(bone); - } - - String targetName = (String)constraintMap["target"]; - data.target = skeletonData.FindBone(targetName); - if (data.target == null) throw new Exception("Target bone not found: " + targetName); - - data.bendDirection = GetBoolean(constraintMap, "bendPositive", true) ? 1 : -1; - data.mix = GetFloat(constraintMap, "mix", 1); - - skeletonData.ikConstraints.Add(data); - } - } - - // Transform constraints. - if (root.ContainsKey("transform")) { - foreach (Dictionary constraintMap in (List)root["transform"]) { - TransformConstraintData data = new TransformConstraintData((String)constraintMap["name"]); - data.order = GetInt(constraintMap, "order", 0); - - foreach (String boneName in (List)constraintMap["bones"]) { - BoneData bone = skeletonData.FindBone(boneName); - if (bone == null) throw new Exception("Transform constraint bone not found: " + boneName); - data.bones.Add(bone); - } - - String targetName = (String)constraintMap["target"]; - data.target = skeletonData.FindBone(targetName); - if (data.target == null) throw new Exception("Target bone not found: " + targetName); - - data.offsetRotation = GetFloat(constraintMap, "rotation", 0); - data.offsetX = GetFloat(constraintMap, "x", 0) * scale; - data.offsetY = GetFloat(constraintMap, "y", 0) * scale; - data.offsetScaleX = GetFloat(constraintMap, "scaleX", 0); - data.offsetScaleY = GetFloat(constraintMap, "scaleY", 0); - data.offsetShearY = GetFloat(constraintMap, "shearY", 0); - - data.rotateMix = GetFloat(constraintMap, "rotateMix", 1); - data.translateMix = GetFloat(constraintMap, "translateMix", 1); - data.scaleMix = GetFloat(constraintMap, "scaleMix", 1); - data.shearMix = GetFloat(constraintMap, "shearMix", 1); - - skeletonData.transformConstraints.Add(data); - } - } - - // Path constraints. - if(root.ContainsKey("path")) { - foreach (Dictionary constraintMap in (List)root["path"]) { - PathConstraintData data = new PathConstraintData((String)constraintMap["name"]); - data.order = GetInt(constraintMap, "order", 0); - - foreach (String boneName in (List)constraintMap["bones"]) { - BoneData bone = skeletonData.FindBone(boneName); - if (bone == null) throw new Exception("Path bone not found: " + boneName); - data.bones.Add(bone); - } - - String targetName = (String)constraintMap["target"]; - data.target = skeletonData.FindSlot(targetName); - if (data.target == null) throw new Exception("Target slot not found: " + targetName); - - data.positionMode = (PositionMode)Enum.Parse(typeof(PositionMode), GetString(constraintMap, "positionMode", "percent"), true); - data.spacingMode = (SpacingMode)Enum.Parse(typeof(SpacingMode), GetString(constraintMap, "spacingMode", "length"), true); - data.rotateMode = (RotateMode)Enum.Parse(typeof(RotateMode), GetString(constraintMap, "rotateMode", "tangent"), true); - data.offsetRotation = GetFloat(constraintMap, "rotation", 0); - data.position = GetFloat(constraintMap, "position", 0); - if (data.positionMode == PositionMode.Fixed) data.position *= scale; - data.spacing = GetFloat(constraintMap, "spacing", 0); - if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale; - data.rotateMix = GetFloat(constraintMap, "rotateMix", 1); - data.translateMix = GetFloat(constraintMap, "translateMix", 1); - - skeletonData.pathConstraints.Add(data); - } - } - - // Skins. - if (root.ContainsKey("skins")) { - foreach (KeyValuePair skinMap in (Dictionary)root["skins"]) { - var skin = new Skin(skinMap.Key); - foreach (KeyValuePair slotEntry in (Dictionary)skinMap.Value) { - int slotIndex = skeletonData.FindSlotIndex(slotEntry.Key); - foreach (KeyValuePair entry in ((Dictionary)slotEntry.Value)) { - try { - Attachment attachment = ReadAttachment((Dictionary)entry.Value, skin, slotIndex, entry.Key); - if (attachment != null) skin.AddAttachment(slotIndex, entry.Key, attachment); - } catch (Exception e) { - throw new Exception("Error reading attachment: " + entry.Key + ", skin: " + skin, e); - } - } - } - skeletonData.skins.Add(skin); - if (skin.name == "default") skeletonData.defaultSkin = skin; - } - } - - // Linked meshes. - for (int i = 0, n = linkedMeshes.Count; i < n; i++) { - LinkedMesh linkedMesh = linkedMeshes[i]; - Skin skin = linkedMesh.skin == null ? skeletonData.defaultSkin : skeletonData.FindSkin(linkedMesh.skin); - if (skin == null) throw new Exception("Slot not found: " + linkedMesh.skin); - Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); - if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); - linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; - linkedMesh.mesh.UpdateUVs(); - } - linkedMeshes.Clear(); - - // Events. - if (root.ContainsKey("events")) { - foreach (KeyValuePair entry in (Dictionary)root["events"]) { - var entryMap = (Dictionary)entry.Value; - var data = new EventData(entry.Key); - data.Int = GetInt(entryMap, "int", 0); - data.Float = GetFloat(entryMap, "float", 0); - data.String = GetString(entryMap, "string", string.Empty); - skeletonData.events.Add(data); - } - } - - // Animations. - if (root.ContainsKey("animations")) { - foreach (KeyValuePair entry in (Dictionary)root["animations"]) { - try { - ReadAnimation((Dictionary)entry.Value, entry.Key, skeletonData); - } catch (Exception e) { - throw new Exception("Error reading animation: " + entry.Key, e); - } - } - } - - skeletonData.bones.TrimExcess(); - skeletonData.slots.TrimExcess(); - skeletonData.skins.TrimExcess(); - skeletonData.events.TrimExcess(); - skeletonData.animations.TrimExcess(); - skeletonData.ikConstraints.TrimExcess(); - return skeletonData; - } - - private Attachment ReadAttachment (Dictionary map, Skin skin, int slotIndex, String name) { - var scale = this.Scale; - name = GetString(map, "name", name); - - var typeName = GetString(map, "type", "region"); - if (typeName == "skinnedmesh") typeName = "weightedmesh"; - if (typeName == "weightedmesh") typeName = "mesh"; - if (typeName == "weightedlinkedmesh") typeName = "linkedmesh"; - var type = (AttachmentType)Enum.Parse(typeof(AttachmentType), typeName, true); - - String path = GetString(map, "path", name); - - switch (type) { - case AttachmentType.Region: - RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); - if (region == null) return null; - region.Path = path; - region.x = GetFloat(map, "x", 0) * scale; - region.y = GetFloat(map, "y", 0) * scale; - region.scaleX = GetFloat(map, "scaleX", 1); - region.scaleY = GetFloat(map, "scaleY", 1); - region.rotation = GetFloat(map, "rotation", 0); - region.width = GetFloat(map, "width", 32) * scale; - region.height = GetFloat(map, "height", 32) * scale; - region.UpdateOffset(); - - if (map.ContainsKey("color")) { - var color = (String)map["color"]; - region.r = ToColor(color, 0); - region.g = ToColor(color, 1); - region.b = ToColor(color, 2); - region.a = ToColor(color, 3); - } - - region.UpdateOffset(); - return region; - case AttachmentType.Boundingbox: - BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); - if (box == null) return null; - ReadVertices(map, box, GetInt(map, "vertexCount", 0) << 1); - return box; - case AttachmentType.Mesh: - case AttachmentType.Linkedmesh: { - MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); - if (mesh == null) return null; - mesh.Path = path; - - if (map.ContainsKey("color")) { - var color = (String)map["color"]; - mesh.r = ToColor(color, 0); - mesh.g = ToColor(color, 1); - mesh.b = ToColor(color, 2); - mesh.a = ToColor(color, 3); - } - - mesh.Width = GetFloat(map, "width", 0) * scale; - mesh.Height = GetFloat(map, "height", 0) * scale; - - String parent = GetString(map, "parent", null); - if (parent != null) { - mesh.InheritDeform = GetBoolean(map, "deform", true); - linkedMeshes.Add(new LinkedMesh(mesh, GetString(map, "skin", null), slotIndex, parent)); - return mesh; - } - - float[] uvs = GetFloatArray(map, "uvs", 1); - ReadVertices(map, mesh, uvs.Length); - mesh.triangles = GetIntArray(map, "triangles"); - mesh.regionUVs = uvs; - mesh.UpdateUVs(); - - if (map.ContainsKey("hull")) mesh.HullLength = GetInt(map, "hull", 0) * 2; - if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges"); - return mesh; - } - case AttachmentType.Path: { - PathAttachment pathAttachment = attachmentLoader.NewPathAttachment(skin, name); - if (pathAttachment == null) return null; - pathAttachment.closed = GetBoolean(map, "closed", false); - pathAttachment.constantSpeed = GetBoolean(map, "constantSpeed", true); - - int vertexCount = GetInt(map, "vertexCount", 0); - ReadVertices(map, pathAttachment, vertexCount << 1); - - // potential BOZO see Java impl - pathAttachment.lengths = GetFloatArray(map, "lengths", scale); - return pathAttachment; - } - } - return null; - } - - private void ReadVertices (Dictionary map, VertexAttachment attachment, int verticesLength) { - attachment.WorldVerticesLength = verticesLength; - float[] vertices = GetFloatArray(map, "vertices", 1); - float scale = Scale; - if (verticesLength == vertices.Length) { - if (scale != 1) { - for (int i = 0; i < vertices.Length; i++) { - vertices[i] *= scale; - } - } - attachment.vertices = vertices; - return; - } - ExposedList weights = new ExposedList(verticesLength * 3 * 3); - ExposedList bones = new ExposedList(verticesLength * 3); - for (int i = 0, n = vertices.Length; i < n;) { - int boneCount = (int)vertices[i++]; - bones.Add(boneCount); - for (int nn = i + boneCount * 4; i < nn; i += 4) { - bones.Add((int)vertices[i]); - weights.Add(vertices[i + 1] * this.Scale); - weights.Add(vertices[i + 2] * this.Scale); - weights.Add(vertices[i + 3]); - } - } - attachment.bones = bones.ToArray(); - attachment.vertices = weights.ToArray(); - } - - private void ReadAnimation (Dictionary map, String name, SkeletonData skeletonData) { - var scale = this.Scale; - var timelines = new ExposedList(); - float duration = 0; - - // Slot timelines. - if (map.ContainsKey("slots")) { - foreach (KeyValuePair entry in (Dictionary)map["slots"]) { - String slotName = entry.Key; - int slotIndex = skeletonData.FindSlotIndex(slotName); - var timelineMap = (Dictionary)entry.Value; - foreach (KeyValuePair timelineEntry in timelineMap) { - var values = (List)timelineEntry.Value; - var timelineName = (String)timelineEntry.Key; - if (timelineName == "color") { - var timeline = new ColorTimeline(values.Count); - timeline.slotIndex = slotIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - String c = (String)valueMap["color"]; - timeline.SetFrame(frameIndex, time, ToColor(c, 0), ToColor(c, 1), ToColor(c, 2), ToColor(c, 3)); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * ColorTimeline.ENTRIES]); - - } else if (timelineName == "attachment") { - var timeline = new AttachmentTimeline(values.Count); - timeline.slotIndex = slotIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - timeline.SetFrame(frameIndex++, time, (String)valueMap["name"]); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); - - } else - throw new Exception("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")"); - } - } - } - - // Bone timelines. - if (map.ContainsKey("bones")) { - foreach (KeyValuePair entry in (Dictionary)map["bones"]) { - String boneName = entry.Key; - int boneIndex = skeletonData.FindBoneIndex(boneName); - if (boneIndex == -1) throw new Exception("Bone not found: " + boneName); - var timelineMap = (Dictionary)entry.Value; - foreach (KeyValuePair timelineEntry in timelineMap) { - var values = (List)timelineEntry.Value; - var timelineName = (String)timelineEntry.Key; - if (timelineName == "rotate") { - var timeline = new RotateTimeline(values.Count); - timeline.boneIndex = boneIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - timeline.SetFrame(frameIndex, (float)valueMap["time"], (float)valueMap["angle"]); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * RotateTimeline.ENTRIES]); - - } else if (timelineName == "translate" || timelineName == "scale" || timelineName == "shear") { - TranslateTimeline timeline; - float timelineScale = 1; - if (timelineName == "scale") - timeline = new ScaleTimeline(values.Count); - else if (timelineName == "shear") - timeline = new ShearTimeline(values.Count); - else { - timeline = new TranslateTimeline(values.Count); - timelineScale = scale; - } - timeline.boneIndex = boneIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - float x = GetFloat(valueMap, "x", 0); - float y = GetFloat(valueMap, "y", 0); - timeline.SetFrame(frameIndex, time, x * timelineScale, y * timelineScale); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TranslateTimeline.ENTRIES]); - - } else - throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")"); - } - } - } - - // IK constraint timelines. - if (map.ContainsKey("ik")) { - foreach (KeyValuePair constraintMap in (Dictionary)map["ik"]) { - IkConstraintData constraint = skeletonData.FindIkConstraint(constraintMap.Key); - var values = (List)constraintMap.Value; - var timeline = new IkConstraintTimeline(values.Count); - timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(constraint); - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - float mix = GetFloat(valueMap, "mix", 1); - bool bendPositive = GetBoolean(valueMap, "bendPositive", true); - timeline.SetFrame(frameIndex, time, mix, bendPositive ? 1 : -1); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * IkConstraintTimeline.ENTRIES]); - } - } - - // Transform constraint timelines. - if (map.ContainsKey("transform")) { - foreach (KeyValuePair constraintMap in (Dictionary)map["transform"]) { - TransformConstraintData constraint = skeletonData.FindTransformConstraint(constraintMap.Key); - var values = (List)constraintMap.Value; - var timeline = new TransformConstraintTimeline(values.Count); - timeline.transformConstraintIndex = skeletonData.transformConstraints.IndexOf(constraint); - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - float rotateMix = GetFloat(valueMap, "rotateMix", 1); - float translateMix = GetFloat(valueMap, "translateMix", 1); - float scaleMix = GetFloat(valueMap, "scaleMix", 1); - float shearMix = GetFloat(valueMap, "shearMix", 1); - timeline.SetFrame(frameIndex, time, rotateMix, translateMix, scaleMix, shearMix); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TransformConstraintTimeline.ENTRIES]); - } - } - - // Path constraint timelines. - if (map.ContainsKey("paths")) { - foreach (KeyValuePair constraintMap in (Dictionary)map["paths"]) { - int index = skeletonData.FindPathConstraintIndex(constraintMap.Key); - if (index == -1) throw new Exception("Path constraint not found: " + constraintMap.Key); - PathConstraintData data = skeletonData.pathConstraints.Items[index]; - var timelineMap = (Dictionary)constraintMap.Value; - foreach (KeyValuePair timelineEntry in timelineMap) { - var values = (List)timelineEntry.Value; - var timelineName = (String)timelineEntry.Key; - if (timelineName == "position" || timelineName == "spacing") { - PathConstraintPositionTimeline timeline; - float timelineScale = 1; - if (timelineName == "spacing") { - timeline = new PathConstraintSpacingTimeline(values.Count); - if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale; - } - else { - timeline = new PathConstraintPositionTimeline(values.Count); - if (data.positionMode == PositionMode.Fixed) timelineScale = scale; - } - timeline.pathConstraintIndex = index; - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - timeline.SetFrame(frameIndex, (float)valueMap["time"], GetFloat(valueMap, timelineName, 0) * timelineScale); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintPositionTimeline.ENTRIES]); - } - else if (timelineName == "mix") { - PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(values.Count); - timeline.pathConstraintIndex = index; - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - timeline.SetFrame(frameIndex, (float)valueMap["time"], GetFloat(valueMap, "rotateMix", 1), GetFloat(valueMap, "translateMix", 1)); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintMixTimeline.ENTRIES]); - } - } - } - } - - // Deform timelines. - if (map.ContainsKey("deform")) { - foreach (KeyValuePair deformMap in (Dictionary)map["deform"]) { - Skin skin = skeletonData.FindSkin(deformMap.Key); - foreach (KeyValuePair slotMap in (Dictionary)deformMap.Value) { - int slotIndex = skeletonData.FindSlotIndex(slotMap.Key); - if (slotIndex == -1) throw new Exception("Slot not found: " + slotMap.Key); - foreach (KeyValuePair timelineMap in (Dictionary)slotMap.Value) { - var values = (List)timelineMap.Value; - VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, timelineMap.Key); - if (attachment == null) throw new Exception("Deform attachment not found: " + timelineMap.Key); - bool weighted = attachment.bones != null; - float[] vertices = attachment.vertices; - int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length; - - var timeline = new DeformTimeline(values.Count); - timeline.slotIndex = slotIndex; - timeline.attachment = attachment; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float[] deform; - if (!valueMap.ContainsKey("vertices")) { - deform = weighted ? new float[deformLength] : vertices; - } else { - deform = new float[deformLength]; - int start = GetInt(valueMap, "offset", 0); - float[] verticesValue = GetFloatArray(valueMap, "vertices", 1); - Array.Copy(verticesValue, 0, deform, start, verticesValue.Length); - if (scale != 1) { - for (int i = start, n = i + verticesValue.Length; i < n; i++) - deform[i] *= scale; - } - - if (!weighted) { - for (int i = 0; i < deformLength; i++) - deform[i] += vertices[i]; - } - } - - timeline.SetFrame(frameIndex, (float)valueMap["time"], deform); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); - } - } - } - } - - // Draw order timeline. - if (map.ContainsKey("drawOrder") || map.ContainsKey("draworder")) { - var values = (List)map[map.ContainsKey("drawOrder") ? "drawOrder" : "draworder"]; - var timeline = new DrawOrderTimeline(values.Count); - int slotCount = skeletonData.slots.Count; - int frameIndex = 0; - foreach (Dictionary drawOrderMap in values) { - int[] drawOrder = null; - if (drawOrderMap.ContainsKey("offsets")) { - drawOrder = new int[slotCount]; - for (int i = slotCount - 1; i >= 0; i--) - drawOrder[i] = -1; - var offsets = (List)drawOrderMap["offsets"]; - int[] unchanged = new int[slotCount - offsets.Count]; - int originalIndex = 0, unchangedIndex = 0; - foreach (Dictionary offsetMap in offsets) { - int slotIndex = skeletonData.FindSlotIndex((String)offsetMap["slot"]); - if (slotIndex == -1) throw new Exception("Slot not found: " + offsetMap["slot"]); - // Collect unchanged items. - while (originalIndex != slotIndex) - unchanged[unchangedIndex++] = originalIndex++; - // Set changed items. - int index = originalIndex + (int)(float)offsetMap["offset"]; - drawOrder[index] = originalIndex++; - } - // Collect remaining unchanged items. - while (originalIndex < slotCount) - unchanged[unchangedIndex++] = originalIndex++; - // Fill in unchanged items. - for (int i = slotCount - 1; i >= 0; i--) - if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex]; - } - timeline.SetFrame(frameIndex++, (float)drawOrderMap["time"], drawOrder); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); - } - - // Event timeline. - if (map.ContainsKey("events")) { - var eventsMap = (List)map["events"]; - var timeline = new EventTimeline(eventsMap.Count); - int frameIndex = 0; - foreach (Dictionary eventMap in eventsMap) { - EventData eventData = skeletonData.FindEvent((String)eventMap["name"]); - if (eventData == null) throw new Exception("Event not found: " + eventMap["name"]); - var e = new Event((float)eventMap["time"], eventData); - e.Int = GetInt(eventMap, "int", eventData.Int); - e.Float = GetFloat(eventMap, "float", eventData.Float); - e.String = GetString(eventMap, "string", eventData.String); - timeline.SetFrame(frameIndex++, e); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); - } - - timelines.TrimExcess(); - skeletonData.animations.Add(new Animation(name, timelines, duration)); - } - - static void ReadCurve (Dictionary valueMap, CurveTimeline timeline, int frameIndex) { - if (!valueMap.ContainsKey("curve")) - return; - Object curveObject = valueMap["curve"]; - if (curveObject.Equals("stepped")) - timeline.SetStepped(frameIndex); - else { - var curve = curveObject as List; - if (curve != null) - timeline.SetCurve(frameIndex, (float)curve[0], (float)curve[1], (float)curve[2], (float)curve[3]); - } - } - - internal class LinkedMesh { - internal String parent, skin; - internal int slotIndex; - internal MeshAttachment mesh; - - public LinkedMesh (MeshAttachment mesh, String skin, int slotIndex, String parent) { - this.mesh = mesh; - this.skin = skin; - this.slotIndex = slotIndex; - this.parent = parent; - } - } - - static float[] GetFloatArray(Dictionary map, String name, float scale) { - var list = (List)map[name]; - var values = new float[list.Count]; - if (scale == 1) { - for (int i = 0, n = list.Count; i < n; i++) - values[i] = (float)list[i]; - } else { - for (int i = 0, n = list.Count; i < n; i++) - values[i] = (float)list[i] * scale; - } - return values; - } - - static int[] GetIntArray(Dictionary map, String name) { - var list = (List)map[name]; - var values = new int[list.Count]; - for (int i = 0, n = list.Count; i < n; i++) - values[i] = (int)(float)list[i]; - return values; - } - - static float GetFloat(Dictionary map, String name, float defaultValue) { - if (!map.ContainsKey(name)) - return defaultValue; - return (float)map[name]; - } - - static int GetInt(Dictionary map, String name, int defaultValue) { - if (!map.ContainsKey(name)) - return defaultValue; - return (int)(float)map[name]; - } - - static bool GetBoolean(Dictionary map, String name, bool defaultValue) { - if (!map.ContainsKey(name)) - return defaultValue; - return (bool)map[name]; - } - - static String GetString(Dictionary map, String name, String defaultValue) { - if (!map.ContainsKey(name)) - return defaultValue; - return (String)map[name]; - } +#else + using (var reader = new StreamReader(new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))) + { +#endif + SkeletonData skeletonData = ReadSkeletonData(reader); + skeletonData.name = Path.GetFileNameWithoutExtension(path); + return skeletonData; + } + } +#endif - static float ToColor(String hexString, int colorIndex) { - if (hexString.Length != 8) - throw new ArgumentException("Color hexidecimal length must be 8, received: " + hexString, "hexString"); - return Convert.ToInt32(hexString.Substring(colorIndex * 2, 2), 16) / (float)255; - } - } + public SkeletonData ReadSkeletonData(TextReader reader) + { + if (reader == null) throw new ArgumentNullException("reader", "reader cannot be null."); + + var scale = this.Scale; + var skeletonData = new SkeletonData(); + + var root = Json.Deserialize(reader) as Dictionary; + if (root == null) throw new Exception("Invalid JSON."); + + // Skeleton. + if (root.ContainsKey("skeleton")) + { + var skeletonMap = (Dictionary)root["skeleton"]; + skeletonData.hash = (String)skeletonMap["hash"]; + skeletonData.version = (String)skeletonMap["spine"]; + skeletonData.width = GetFloat(skeletonMap, "width", 0); + skeletonData.height = GetFloat(skeletonMap, "height", 0); + skeletonData.fps = GetFloat(skeletonMap, "fps", 0); + skeletonData.imagesPath = GetString(skeletonMap, "images", null); + } + + // Bones. + foreach (Dictionary boneMap in (List)root["bones"]) + { + BoneData parent = null; + if (boneMap.ContainsKey("parent")) + { + parent = skeletonData.FindBone((String)boneMap["parent"]); + if (parent == null) + throw new Exception("Parent bone not found: " + boneMap["parent"]); + } + var data = new BoneData(skeletonData.Bones.Count, (String)boneMap["name"], parent); + data.length = GetFloat(boneMap, "length", 0) * scale; + data.x = GetFloat(boneMap, "x", 0) * scale; + data.y = GetFloat(boneMap, "y", 0) * scale; + data.rotation = GetFloat(boneMap, "rotation", 0); + data.scaleX = GetFloat(boneMap, "scaleX", 1); + data.scaleY = GetFloat(boneMap, "scaleY", 1); + data.shearX = GetFloat(boneMap, "shearX", 0); + data.shearY = GetFloat(boneMap, "shearY", 0); + + string tm = GetString(boneMap, "transform", TransformMode.Normal.ToString()); + data.transformMode = (TransformMode)Enum.Parse(typeof(TransformMode), tm, true); + + skeletonData.bones.Add(data); + } + + // Slots. + if (root.ContainsKey("slots")) + { + foreach (Dictionary slotMap in (List)root["slots"]) + { + var slotName = (String)slotMap["name"]; + var boneName = (String)slotMap["bone"]; + BoneData boneData = skeletonData.FindBone(boneName); + if (boneData == null) throw new Exception("Slot bone not found: " + boneName); + var data = new SlotData(skeletonData.Slots.Count, slotName, boneData); + + if (slotMap.ContainsKey("color")) + { + var color = (String)slotMap["color"]; + data.r = ToColor(color, 0); + data.g = ToColor(color, 1); + data.b = ToColor(color, 2); + data.a = ToColor(color, 3); + } + + data.attachmentName = GetString(slotMap, "attachment", null); + if (slotMap.ContainsKey("blend")) + data.blendMode = (BlendMode)Enum.Parse(typeof(BlendMode), (String)slotMap["blend"], false); + else + data.blendMode = BlendMode.normal; + skeletonData.slots.Add(data); + } + } + + // IK constraints. + if (root.ContainsKey("ik")) + { + foreach (Dictionary constraintMap in (List)root["ik"]) + { + IkConstraintData data = new IkConstraintData((String)constraintMap["name"]); + data.order = GetInt(constraintMap, "order", 0); + + foreach (String boneName in (List)constraintMap["bones"]) + { + BoneData bone = skeletonData.FindBone(boneName); + if (bone == null) throw new Exception("IK constraint bone not found: " + boneName); + data.bones.Add(bone); + } + + String targetName = (String)constraintMap["target"]; + data.target = skeletonData.FindBone(targetName); + if (data.target == null) throw new Exception("Target bone not found: " + targetName); + + data.bendDirection = GetBoolean(constraintMap, "bendPositive", true) ? 1 : -1; + data.mix = GetFloat(constraintMap, "mix", 1); + + skeletonData.ikConstraints.Add(data); + } + } + + // Transform constraints. + if (root.ContainsKey("transform")) + { + foreach (Dictionary constraintMap in (List)root["transform"]) + { + TransformConstraintData data = new TransformConstraintData((String)constraintMap["name"]); + data.order = GetInt(constraintMap, "order", 0); + + foreach (String boneName in (List)constraintMap["bones"]) + { + BoneData bone = skeletonData.FindBone(boneName); + if (bone == null) throw new Exception("Transform constraint bone not found: " + boneName); + data.bones.Add(bone); + } + + String targetName = (String)constraintMap["target"]; + data.target = skeletonData.FindBone(targetName); + if (data.target == null) throw new Exception("Target bone not found: " + targetName); + + data.offsetRotation = GetFloat(constraintMap, "rotation", 0); + data.offsetX = GetFloat(constraintMap, "x", 0) * scale; + data.offsetY = GetFloat(constraintMap, "y", 0) * scale; + data.offsetScaleX = GetFloat(constraintMap, "scaleX", 0); + data.offsetScaleY = GetFloat(constraintMap, "scaleY", 0); + data.offsetShearY = GetFloat(constraintMap, "shearY", 0); + + data.rotateMix = GetFloat(constraintMap, "rotateMix", 1); + data.translateMix = GetFloat(constraintMap, "translateMix", 1); + data.scaleMix = GetFloat(constraintMap, "scaleMix", 1); + data.shearMix = GetFloat(constraintMap, "shearMix", 1); + + skeletonData.transformConstraints.Add(data); + } + } + + // Path constraints. + if (root.ContainsKey("path")) + { + foreach (Dictionary constraintMap in (List)root["path"]) + { + PathConstraintData data = new PathConstraintData((String)constraintMap["name"]); + data.order = GetInt(constraintMap, "order", 0); + + foreach (String boneName in (List)constraintMap["bones"]) + { + BoneData bone = skeletonData.FindBone(boneName); + if (bone == null) throw new Exception("Path bone not found: " + boneName); + data.bones.Add(bone); + } + + String targetName = (String)constraintMap["target"]; + data.target = skeletonData.FindSlot(targetName); + if (data.target == null) throw new Exception("Target slot not found: " + targetName); + + data.positionMode = (PositionMode)Enum.Parse(typeof(PositionMode), GetString(constraintMap, "positionMode", "percent"), true); + data.spacingMode = (SpacingMode)Enum.Parse(typeof(SpacingMode), GetString(constraintMap, "spacingMode", "length"), true); + data.rotateMode = (RotateMode)Enum.Parse(typeof(RotateMode), GetString(constraintMap, "rotateMode", "tangent"), true); + data.offsetRotation = GetFloat(constraintMap, "rotation", 0); + data.position = GetFloat(constraintMap, "position", 0); + if (data.positionMode == PositionMode.Fixed) data.position *= scale; + data.spacing = GetFloat(constraintMap, "spacing", 0); + if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale; + data.rotateMix = GetFloat(constraintMap, "rotateMix", 1); + data.translateMix = GetFloat(constraintMap, "translateMix", 1); + + skeletonData.pathConstraints.Add(data); + } + } + + // Skins. + if (root.ContainsKey("skins")) + { + foreach (KeyValuePair skinMap in (Dictionary)root["skins"]) + { + var skin = new Skin(skinMap.Key); + foreach (KeyValuePair slotEntry in (Dictionary)skinMap.Value) + { + int slotIndex = skeletonData.FindSlotIndex(slotEntry.Key); + foreach (KeyValuePair entry in ((Dictionary)slotEntry.Value)) + { + try + { + Attachment attachment = ReadAttachment((Dictionary)entry.Value, skin, slotIndex, entry.Key); + if (attachment != null) skin.AddAttachment(slotIndex, entry.Key, attachment); + } + catch (Exception e) + { + throw new Exception("Error reading attachment: " + entry.Key + ", skin: " + skin, e); + } + } + } + skeletonData.skins.Add(skin); + if (skin.name == "default") skeletonData.defaultSkin = skin; + } + } + + // Linked meshes. + for (int i = 0, n = linkedMeshes.Count; i < n; i++) + { + LinkedMesh linkedMesh = linkedMeshes[i]; + Skin skin = linkedMesh.skin == null ? skeletonData.defaultSkin : skeletonData.FindSkin(linkedMesh.skin); + if (skin == null) throw new Exception("Slot not found: " + linkedMesh.skin); + Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); + if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); + linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; + linkedMesh.mesh.UpdateUVs(); + } + linkedMeshes.Clear(); + + // Events. + if (root.ContainsKey("events")) + { + foreach (KeyValuePair entry in (Dictionary)root["events"]) + { + var entryMap = (Dictionary)entry.Value; + var data = new EventData(entry.Key); + data.Int = GetInt(entryMap, "int", 0); + data.Float = GetFloat(entryMap, "float", 0); + data.String = GetString(entryMap, "string", string.Empty); + skeletonData.events.Add(data); + } + } + + // Animations. + if (root.ContainsKey("animations")) + { + foreach (KeyValuePair entry in (Dictionary)root["animations"]) + { + try + { + ReadAnimation((Dictionary)entry.Value, entry.Key, skeletonData); + } + catch (Exception e) + { + throw new Exception("Error reading animation: " + entry.Key, e); + } + } + } + + skeletonData.bones.TrimExcess(); + skeletonData.slots.TrimExcess(); + skeletonData.skins.TrimExcess(); + skeletonData.events.TrimExcess(); + skeletonData.animations.TrimExcess(); + skeletonData.ikConstraints.TrimExcess(); + return skeletonData; + } + + private Attachment ReadAttachment(Dictionary map, Skin skin, int slotIndex, String name) + { + var scale = this.Scale; + name = GetString(map, "name", name); + + var typeName = GetString(map, "type", "region"); + if (typeName == "skinnedmesh") typeName = "weightedmesh"; + if (typeName == "weightedmesh") typeName = "mesh"; + if (typeName == "weightedlinkedmesh") typeName = "linkedmesh"; + var type = (AttachmentType)Enum.Parse(typeof(AttachmentType), typeName, true); + + String path = GetString(map, "path", name); + + switch (type) + { + case AttachmentType.Region: + RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); + if (region == null) return null; + region.Path = path; + region.x = GetFloat(map, "x", 0) * scale; + region.y = GetFloat(map, "y", 0) * scale; + region.scaleX = GetFloat(map, "scaleX", 1); + region.scaleY = GetFloat(map, "scaleY", 1); + region.rotation = GetFloat(map, "rotation", 0); + region.width = GetFloat(map, "width", 32) * scale; + region.height = GetFloat(map, "height", 32) * scale; + region.UpdateOffset(); + + if (map.ContainsKey("color")) + { + var color = (String)map["color"]; + region.r = ToColor(color, 0); + region.g = ToColor(color, 1); + region.b = ToColor(color, 2); + region.a = ToColor(color, 3); + } + + region.UpdateOffset(); + return region; + case AttachmentType.Boundingbox: + BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); + if (box == null) return null; + ReadVertices(map, box, GetInt(map, "vertexCount", 0) << 1); + return box; + case AttachmentType.Mesh: + case AttachmentType.Linkedmesh: + { + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); + if (mesh == null) return null; + mesh.Path = path; + + if (map.ContainsKey("color")) + { + var color = (String)map["color"]; + mesh.r = ToColor(color, 0); + mesh.g = ToColor(color, 1); + mesh.b = ToColor(color, 2); + mesh.a = ToColor(color, 3); + } + + mesh.Width = GetFloat(map, "width", 0) * scale; + mesh.Height = GetFloat(map, "height", 0) * scale; + + String parent = GetString(map, "parent", null); + if (parent != null) + { + mesh.InheritDeform = GetBoolean(map, "deform", true); + linkedMeshes.Add(new LinkedMesh(mesh, GetString(map, "skin", null), slotIndex, parent)); + return mesh; + } + + float[] uvs = GetFloatArray(map, "uvs", 1); + ReadVertices(map, mesh, uvs.Length); + mesh.triangles = GetIntArray(map, "triangles"); + mesh.regionUVs = uvs; + mesh.UpdateUVs(); + + if (map.ContainsKey("hull")) mesh.HullLength = GetInt(map, "hull", 0) * 2; + if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges"); + return mesh; + } + case AttachmentType.Path: + { + PathAttachment pathAttachment = attachmentLoader.NewPathAttachment(skin, name); + if (pathAttachment == null) return null; + pathAttachment.closed = GetBoolean(map, "closed", false); + pathAttachment.constantSpeed = GetBoolean(map, "constantSpeed", true); + + int vertexCount = GetInt(map, "vertexCount", 0); + ReadVertices(map, pathAttachment, vertexCount << 1); + + // potential BOZO see Java impl + pathAttachment.lengths = GetFloatArray(map, "lengths", scale); + return pathAttachment; + } + } + return null; + } + + private void ReadVertices(Dictionary map, VertexAttachment attachment, int verticesLength) + { + attachment.WorldVerticesLength = verticesLength; + float[] vertices = GetFloatArray(map, "vertices", 1); + float scale = Scale; + if (verticesLength == vertices.Length) + { + if (scale != 1) + { + for (int i = 0; i < vertices.Length; i++) + { + vertices[i] *= scale; + } + } + attachment.vertices = vertices; + return; + } + ExposedList weights = new ExposedList(verticesLength * 3 * 3); + ExposedList bones = new ExposedList(verticesLength * 3); + for (int i = 0, n = vertices.Length; i < n;) + { + int boneCount = (int)vertices[i++]; + bones.Add(boneCount); + for (int nn = i + boneCount * 4; i < nn; i += 4) + { + bones.Add((int)vertices[i]); + weights.Add(vertices[i + 1] * this.Scale); + weights.Add(vertices[i + 2] * this.Scale); + weights.Add(vertices[i + 3]); + } + } + attachment.bones = bones.ToArray(); + attachment.vertices = weights.ToArray(); + } + + private void ReadAnimation(Dictionary map, String name, SkeletonData skeletonData) + { + var scale = this.Scale; + var timelines = new ExposedList(); + float duration = 0; + + // Slot timelines. + if (map.ContainsKey("slots")) + { + foreach (KeyValuePair entry in (Dictionary)map["slots"]) + { + String slotName = entry.Key; + int slotIndex = skeletonData.FindSlotIndex(slotName); + var timelineMap = (Dictionary)entry.Value; + foreach (KeyValuePair timelineEntry in timelineMap) + { + var values = (List)timelineEntry.Value; + var timelineName = timelineEntry.Key; + if (timelineName == "color") + { + var timeline = new ColorTimeline(values.Count); + timeline.slotIndex = slotIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + String c = (String)valueMap["color"]; + timeline.SetFrame(frameIndex, time, ToColor(c, 0), ToColor(c, 1), ToColor(c, 2), ToColor(c, 3)); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * ColorTimeline.ENTRIES]); + + } + else if (timelineName == "attachment") + { + var timeline = new AttachmentTimeline(values.Count); + timeline.slotIndex = slotIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + timeline.SetFrame(frameIndex++, time, (String)valueMap["name"]); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); + + } + else + throw new Exception("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")"); + } + } + } + + // Bone timelines. + if (map.ContainsKey("bones")) + { + foreach (KeyValuePair entry in (Dictionary)map["bones"]) + { + String boneName = entry.Key; + int boneIndex = skeletonData.FindBoneIndex(boneName); + if (boneIndex == -1) throw new Exception("Bone not found: " + boneName); + var timelineMap = (Dictionary)entry.Value; + foreach (KeyValuePair timelineEntry in timelineMap) + { + var values = (List)timelineEntry.Value; + var timelineName = timelineEntry.Key; + if (timelineName == "rotate") + { + var timeline = new RotateTimeline(values.Count); + timeline.boneIndex = boneIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + timeline.SetFrame(frameIndex, (float)valueMap["time"], (float)valueMap["angle"]); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * RotateTimeline.ENTRIES]); + + } + else if (timelineName == "translate" || timelineName == "scale" || timelineName == "shear") + { + TranslateTimeline timeline; + float timelineScale = 1; + if (timelineName == "scale") + timeline = new ScaleTimeline(values.Count); + else if (timelineName == "shear") + timeline = new ShearTimeline(values.Count); + else + { + timeline = new TranslateTimeline(values.Count); + timelineScale = scale; + } + timeline.boneIndex = boneIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + float x = GetFloat(valueMap, "x", 0); + float y = GetFloat(valueMap, "y", 0); + timeline.SetFrame(frameIndex, time, x * timelineScale, y * timelineScale); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TranslateTimeline.ENTRIES]); + + } + else + throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")"); + } + } + } + + // IK constraint timelines. + if (map.ContainsKey("ik")) + { + foreach (KeyValuePair constraintMap in (Dictionary)map["ik"]) + { + IkConstraintData constraint = skeletonData.FindIkConstraint(constraintMap.Key); + var values = (List)constraintMap.Value; + var timeline = new IkConstraintTimeline(values.Count); + timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(constraint); + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + float mix = GetFloat(valueMap, "mix", 1); + bool bendPositive = GetBoolean(valueMap, "bendPositive", true); + timeline.SetFrame(frameIndex, time, mix, bendPositive ? 1 : -1); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * IkConstraintTimeline.ENTRIES]); + } + } + + // Transform constraint timelines. + if (map.ContainsKey("transform")) + { + foreach (KeyValuePair constraintMap in (Dictionary)map["transform"]) + { + TransformConstraintData constraint = skeletonData.FindTransformConstraint(constraintMap.Key); + var values = (List)constraintMap.Value; + var timeline = new TransformConstraintTimeline(values.Count); + timeline.transformConstraintIndex = skeletonData.transformConstraints.IndexOf(constraint); + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + float rotateMix = GetFloat(valueMap, "rotateMix", 1); + float translateMix = GetFloat(valueMap, "translateMix", 1); + float scaleMix = GetFloat(valueMap, "scaleMix", 1); + float shearMix = GetFloat(valueMap, "shearMix", 1); + timeline.SetFrame(frameIndex, time, rotateMix, translateMix, scaleMix, shearMix); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TransformConstraintTimeline.ENTRIES]); + } + } + + // Path constraint timelines. + if (map.ContainsKey("paths")) + { + foreach (KeyValuePair constraintMap in (Dictionary)map["paths"]) + { + int index = skeletonData.FindPathConstraintIndex(constraintMap.Key); + if (index == -1) throw new Exception("Path constraint not found: " + constraintMap.Key); + PathConstraintData data = skeletonData.pathConstraints.Items[index]; + var timelineMap = (Dictionary)constraintMap.Value; + foreach (KeyValuePair timelineEntry in timelineMap) + { + var values = (List)timelineEntry.Value; + var timelineName = timelineEntry.Key; + if (timelineName == "position" || timelineName == "spacing") + { + PathConstraintPositionTimeline timeline; + float timelineScale = 1; + if (timelineName == "spacing") + { + timeline = new PathConstraintSpacingTimeline(values.Count); + if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale; + } + else + { + timeline = new PathConstraintPositionTimeline(values.Count); + if (data.positionMode == PositionMode.Fixed) timelineScale = scale; + } + timeline.pathConstraintIndex = index; + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + timeline.SetFrame(frameIndex, (float)valueMap["time"], GetFloat(valueMap, timelineName, 0) * timelineScale); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintPositionTimeline.ENTRIES]); + } + else if (timelineName == "mix") + { + PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(values.Count); + timeline.pathConstraintIndex = index; + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + timeline.SetFrame(frameIndex, (float)valueMap["time"], GetFloat(valueMap, "rotateMix", 1), GetFloat(valueMap, "translateMix", 1)); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintMixTimeline.ENTRIES]); + } + } + } + } + + // Deform timelines. + if (map.ContainsKey("deform")) + { + foreach (KeyValuePair deformMap in (Dictionary)map["deform"]) + { + Skin skin = skeletonData.FindSkin(deformMap.Key); + foreach (KeyValuePair slotMap in (Dictionary)deformMap.Value) + { + int slotIndex = skeletonData.FindSlotIndex(slotMap.Key); + if (slotIndex == -1) throw new Exception("Slot not found: " + slotMap.Key); + foreach (KeyValuePair timelineMap in (Dictionary)slotMap.Value) + { + var values = (List)timelineMap.Value; + VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, timelineMap.Key); + if (attachment == null) throw new Exception("Deform attachment not found: " + timelineMap.Key); + bool weighted = attachment.bones != null; + float[] vertices = attachment.vertices; + int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length; + + var timeline = new DeformTimeline(values.Count); + timeline.slotIndex = slotIndex; + timeline.attachment = attachment; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float[] deform; + if (!valueMap.ContainsKey("vertices")) + { + deform = weighted ? new float[deformLength] : vertices; + } + else + { + deform = new float[deformLength]; + int start = GetInt(valueMap, "offset", 0); + float[] verticesValue = GetFloatArray(valueMap, "vertices", 1); + Array.Copy(verticesValue, 0, deform, start, verticesValue.Length); + if (scale != 1) + { + for (int i = start, n = i + verticesValue.Length; i < n; i++) + deform[i] *= scale; + } + + if (!weighted) + { + for (int i = 0; i < deformLength; i++) + deform[i] += vertices[i]; + } + } + + timeline.SetFrame(frameIndex, (float)valueMap["time"], deform); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); + } + } + } + } + + // Draw order timeline. + if (map.ContainsKey("drawOrder") || map.ContainsKey("draworder")) + { + var values = (List)map[map.ContainsKey("drawOrder") ? "drawOrder" : "draworder"]; + var timeline = new DrawOrderTimeline(values.Count); + int slotCount = skeletonData.slots.Count; + int frameIndex = 0; + foreach (Dictionary drawOrderMap in values) + { + int[] drawOrder = null; + if (drawOrderMap.ContainsKey("offsets")) + { + drawOrder = new int[slotCount]; + for (int i = slotCount - 1; i >= 0; i--) + drawOrder[i] = -1; + var offsets = (List)drawOrderMap["offsets"]; + int[] unchanged = new int[slotCount - offsets.Count]; + int originalIndex = 0, unchangedIndex = 0; + foreach (Dictionary offsetMap in offsets) + { + int slotIndex = skeletonData.FindSlotIndex((String)offsetMap["slot"]); + if (slotIndex == -1) throw new Exception("Slot not found: " + offsetMap["slot"]); + // Collect unchanged items. + while (originalIndex != slotIndex) + unchanged[unchangedIndex++] = originalIndex++; + // Set changed items. + int index = originalIndex + (int)(float)offsetMap["offset"]; + drawOrder[index] = originalIndex++; + } + // Collect remaining unchanged items. + while (originalIndex < slotCount) + unchanged[unchangedIndex++] = originalIndex++; + // Fill in unchanged items. + for (int i = slotCount - 1; i >= 0; i--) + if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex]; + } + timeline.SetFrame(frameIndex++, (float)drawOrderMap["time"], drawOrder); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); + } + + // Event timeline. + if (map.ContainsKey("events")) + { + var eventsMap = (List)map["events"]; + var timeline = new EventTimeline(eventsMap.Count); + int frameIndex = 0; + foreach (Dictionary eventMap in eventsMap) + { + EventData eventData = skeletonData.FindEvent((String)eventMap["name"]); + if (eventData == null) throw new Exception("Event not found: " + eventMap["name"]); + var e = new Event((float)eventMap["time"], eventData); + e.Int = GetInt(eventMap, "int", eventData.Int); + e.Float = GetFloat(eventMap, "float", eventData.Float); + e.String = GetString(eventMap, "string", eventData.String); + timeline.SetFrame(frameIndex++, e); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); + } + + timelines.TrimExcess(); + skeletonData.animations.Add(new Animation(name, timelines, duration)); + } + + static void ReadCurve(Dictionary valueMap, CurveTimeline timeline, int frameIndex) + { + if (!valueMap.ContainsKey("curve")) + return; + Object curveObject = valueMap["curve"]; + if (curveObject.Equals("stepped")) + timeline.SetStepped(frameIndex); + else + { + var curve = curveObject as List; + if (curve != null) + timeline.SetCurve(frameIndex, (float)curve[0], (float)curve[1], (float)curve[2], (float)curve[3]); + } + } + + internal class LinkedMesh + { + internal String parent, skin; + internal int slotIndex; + internal MeshAttachment mesh; + + public LinkedMesh(MeshAttachment mesh, String skin, int slotIndex, String parent) + { + this.mesh = mesh; + this.skin = skin; + this.slotIndex = slotIndex; + this.parent = parent; + } + } + + static float[] GetFloatArray(Dictionary map, String name, float scale) + { + var list = (List)map[name]; + var values = new float[list.Count]; + if (scale == 1) + { + for (int i = 0, n = list.Count; i < n; i++) + values[i] = (float)list[i]; + } + else + { + for (int i = 0, n = list.Count; i < n; i++) + values[i] = (float)list[i] * scale; + } + return values; + } + + static int[] GetIntArray(Dictionary map, String name) + { + var list = (List)map[name]; + var values = new int[list.Count]; + for (int i = 0, n = list.Count; i < n; i++) + values[i] = (int)(float)list[i]; + return values; + } + + static float GetFloat(Dictionary map, String name, float defaultValue) + { + if (!map.ContainsKey(name)) + return defaultValue; + return (float)map[name]; + } + + static int GetInt(Dictionary map, String name, int defaultValue) + { + if (!map.ContainsKey(name)) + return defaultValue; + return (int)(float)map[name]; + } + + static bool GetBoolean(Dictionary map, String name, bool defaultValue) + { + if (!map.ContainsKey(name)) + return defaultValue; + return (bool)map[name]; + } + + static String GetString(Dictionary map, String name, String defaultValue) + { + if (!map.ContainsKey(name)) + return defaultValue; + return (String)map[name]; + } + + static float ToColor(String hexString, int colorIndex) + { + if (hexString.Length != 8) + throw new ArgumentException("Color hexidecimal length must be 8, received: " + hexString, "hexString"); + return Convert.ToInt32(hexString.Substring(colorIndex * 2, 2), 16) / (float)255; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Skin.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Skin.cs index 5630e88..7a81311 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Skin.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Skin.cs @@ -31,93 +31,109 @@ using System; using System.Collections.Generic; -namespace Spine3_5_51 { - /// Stores attachments by slot index and attachment name. - /// See SkeletonData , Skeleton , and - /// Runtime skins in the Spine Runtimes Guide. - /// - public class Skin { - internal String name; - private Dictionary attachments = - new Dictionary(AttachmentKeyTupleComparer.Instance); +namespace Spine3_5_51 +{ + /// Stores attachments by slot index and attachment name. + /// See SkeletonData , Skeleton , and + /// Runtime skins in the Spine Runtimes Guide. + /// + public class Skin + { + internal String name; + private Dictionary attachments = + new Dictionary(AttachmentKeyTupleComparer.Instance); - public string Name { get { return name; } } - public Dictionary Attachments { get { return attachments; } } + public string Name { get { return name; } } + public Dictionary Attachments { get { return attachments; } } - public Skin (string name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; - } + public Skin(string name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + } - public void AddAttachment (int slotIndex, string name, Attachment attachment) { - if (attachment == null) throw new ArgumentNullException("attachment", "attachment cannot be null."); - attachments[new AttachmentKeyTuple(slotIndex, name)] = attachment; - } + public void AddAttachment(int slotIndex, string name, Attachment attachment) + { + if (attachment == null) throw new ArgumentNullException("attachment", "attachment cannot be null."); + attachments[new AttachmentKeyTuple(slotIndex, name)] = attachment; + } - /// May be null. - public Attachment GetAttachment (int slotIndex, string name) { - Attachment attachment; - attachments.TryGetValue(new AttachmentKeyTuple(slotIndex, name), out attachment); - return attachment; - } + /// May be null. + public Attachment GetAttachment(int slotIndex, string name) + { + Attachment attachment; + attachments.TryGetValue(new AttachmentKeyTuple(slotIndex, name), out attachment); + return attachment; + } - /// Finds the skin keys for a given slot. The results are added to the passed List(names). - /// The target slotIndex. To find the slot index, use or - /// Found skin key names will be added to this list. - public void FindNamesForSlot (int slotIndex, List names) { - if (names == null) throw new ArgumentNullException("names", "names cannot be null."); - foreach (AttachmentKeyTuple key in attachments.Keys) - if (key.slotIndex == slotIndex) names.Add(key.name); - } + /// Finds the skin keys for a given slot. The results are added to the passed List(names). + /// The target slotIndex. To find the slot index, use or + /// Found skin key names will be added to this list. + public void FindNamesForSlot(int slotIndex, List names) + { + if (names == null) throw new ArgumentNullException("names", "names cannot be null."); + foreach (AttachmentKeyTuple key in attachments.Keys) + if (key.slotIndex == slotIndex) names.Add(key.name); + } - /// Finds the attachments for a given slot. The results are added to the passed List(Attachment). - /// The target slotIndex. To find the slot index, use or - /// Found Attachments will be added to this list. - public void FindAttachmentsForSlot (int slotIndex, List attachments) { - if (attachments == null) throw new ArgumentNullException("attachments", "attachments cannot be null."); - foreach (KeyValuePair entry in this.attachments) - if (entry.Key.slotIndex == slotIndex) attachments.Add(entry.Value); - } + /// Finds the attachments for a given slot. The results are added to the passed List(Attachment). + /// The target slotIndex. To find the slot index, use or + /// Found Attachments will be added to this list. + public void FindAttachmentsForSlot(int slotIndex, List attachments) + { + if (attachments == null) throw new ArgumentNullException("attachments", "attachments cannot be null."); + foreach (KeyValuePair entry in this.attachments) + if (entry.Key.slotIndex == slotIndex) attachments.Add(entry.Value); + } - override public String ToString () { - return name; - } + override public String ToString() + { + return name; + } - /// Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached. - internal void AttachAll (Skeleton skeleton, Skin oldSkin) { - foreach (KeyValuePair entry in oldSkin.attachments) { - int slotIndex = entry.Key.slotIndex; - Slot slot = skeleton.slots.Items[slotIndex]; - if (slot.Attachment == entry.Value) { - Attachment attachment = GetAttachment(slotIndex, entry.Key.name); - if (attachment != null) slot.Attachment = attachment; - } - } - } + /// Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached. + internal void AttachAll(Skeleton skeleton, Skin oldSkin) + { + foreach (KeyValuePair entry in oldSkin.attachments) + { + int slotIndex = entry.Key.slotIndex; + Slot slot = skeleton.slots.Items[slotIndex]; + if (slot.Attachment == entry.Value) + { + Attachment attachment = GetAttachment(slotIndex, entry.Key.name); + if (attachment != null) slot.Attachment = attachment; + } + } + } - public struct AttachmentKeyTuple { - public readonly int slotIndex; - public readonly string name; - internal readonly int nameHashCode; + public struct AttachmentKeyTuple + { + public readonly int slotIndex; + public readonly string name; + internal readonly int nameHashCode; - public AttachmentKeyTuple (int slotIndex, string name) { - this.slotIndex = slotIndex; - this.name = name; - nameHashCode = this.name.GetHashCode(); - } - } + public AttachmentKeyTuple(int slotIndex, string name) + { + this.slotIndex = slotIndex; + this.name = name; + nameHashCode = this.name.GetHashCode(); + } + } - // Avoids boxing in the dictionary. - class AttachmentKeyTupleComparer : IEqualityComparer { - internal static readonly AttachmentKeyTupleComparer Instance = new AttachmentKeyTupleComparer(); + // Avoids boxing in the dictionary. + class AttachmentKeyTupleComparer : IEqualityComparer + { + internal static readonly AttachmentKeyTupleComparer Instance = new AttachmentKeyTupleComparer(); - bool IEqualityComparer.Equals (AttachmentKeyTuple o1, AttachmentKeyTuple o2) { - return o1.slotIndex == o2.slotIndex && o1.nameHashCode == o2.nameHashCode && o1.name == o2.name; - } + bool IEqualityComparer.Equals(AttachmentKeyTuple o1, AttachmentKeyTuple o2) + { + return o1.slotIndex == o2.slotIndex && o1.nameHashCode == o2.nameHashCode && o1.name == o2.name; + } - int IEqualityComparer.GetHashCode (AttachmentKeyTuple o) { - return o.slotIndex; - } - } - } + int IEqualityComparer.GetHashCode(AttachmentKeyTuple o) + { + return o.slotIndex; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Slot.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Slot.cs index f00f9c1..078850c 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Slot.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/Slot.cs @@ -30,64 +30,73 @@ using System; -namespace Spine3_5_51 { - public class Slot { - internal SlotData data; - internal Bone bone; - internal float r, g, b, a; - internal Attachment attachment; - internal float attachmentTime; - internal ExposedList attachmentVertices = new ExposedList(); +namespace Spine3_5_51 +{ + public class Slot + { + internal SlotData data; + internal Bone bone; + internal float r, g, b, a; + internal Attachment attachment; + internal float attachmentTime; + internal ExposedList attachmentVertices = new ExposedList(); - public SlotData Data { get { return data; } } - public Bone Bone { get { return bone; } } - public Skeleton Skeleton { get { return bone.skeleton; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } + public SlotData Data { get { return data; } } + public Bone Bone { get { return bone; } } + public Skeleton Skeleton { get { return bone.skeleton; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } - /// May be null. - public Attachment Attachment { - get { return attachment; } - set { - if (attachment == value) return; - attachment = value; - attachmentTime = bone.skeleton.time; - attachmentVertices.Clear(false); - } - } + /// May be null. + public Attachment Attachment + { + get { return attachment; } + set + { + if (attachment == value) return; + attachment = value; + attachmentTime = bone.skeleton.time; + attachmentVertices.Clear(false); + } + } - public float AttachmentTime { - get { return bone.skeleton.time - attachmentTime; } - set { attachmentTime = bone.skeleton.time - value; } - } + public float AttachmentTime + { + get { return bone.skeleton.time - attachmentTime; } + set { attachmentTime = bone.skeleton.time - value; } + } - public ExposedList AttachmentVertices { get { return attachmentVertices; } set { attachmentVertices = value; } } + public ExposedList AttachmentVertices { get { return attachmentVertices; } set { attachmentVertices = value; } } - public Slot (SlotData data, Bone bone) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); - this.data = data; - this.bone = bone; - SetToSetupPose(); - } + public Slot(SlotData data, Bone bone) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); + this.data = data; + this.bone = bone; + SetToSetupPose(); + } - public void SetToSetupPose () { - r = data.r; - g = data.g; - b = data.b; - a = data.a; - if (data.attachmentName == null) - Attachment = null; - else { - attachment = null; - Attachment = bone.skeleton.GetAttachment(data.index, data.attachmentName); - } - } + public void SetToSetupPose() + { + r = data.r; + g = data.g; + b = data.b; + a = data.a; + if (data.attachmentName == null) + Attachment = null; + else + { + attachment = null; + Attachment = bone.skeleton.GetAttachment(data.index, data.attachmentName); + } + } - override public String ToString () { - return data.name; - } - } + override public String ToString() + { + return data.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/SlotData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/SlotData.cs index 004108b..c6f52d1 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/SlotData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/SlotData.cs @@ -30,37 +30,41 @@ using System; -namespace Spine3_5_51 { - public class SlotData { - internal int index; - internal String name; - internal BoneData boneData; - internal float r = 1, g = 1, b = 1, a = 1; - internal String attachmentName; - internal BlendMode blendMode; +namespace Spine3_5_51 +{ + public class SlotData + { + internal int index; + internal String name; + internal BoneData boneData; + internal float r = 1, g = 1, b = 1, a = 1; + internal String attachmentName; + internal BlendMode blendMode; - public int Index { get { return index; } } - public String Name { get { return name; } } - public BoneData BoneData { get { return boneData; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } - /// May be null. - public String AttachmentName { get { return attachmentName; } set { attachmentName = value; } } - public BlendMode BlendMode { get { return blendMode; } set { blendMode = value; } } + public int Index { get { return index; } } + public String Name { get { return name; } } + public BoneData BoneData { get { return boneData; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } + /// May be null. + public String AttachmentName { get { return attachmentName; } set { attachmentName = value; } } + public BlendMode BlendMode { get { return blendMode; } set { blendMode = value; } } - public SlotData (int index, String name, BoneData boneData) { - if (index < 0) throw new ArgumentException ("index must be >= 0.", "index"); - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - if (boneData == null) throw new ArgumentNullException("boneData", "boneData cannot be null."); - this.index = index; - this.name = name; - this.boneData = boneData; - } + public SlotData(int index, String name, BoneData boneData) + { + if (index < 0) throw new ArgumentException("index must be >= 0.", "index"); + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + if (boneData == null) throw new ArgumentNullException("boneData", "boneData cannot be null."); + this.index = index; + this.name = name; + this.boneData = boneData; + } - override public String ToString () { - return name; - } - } + override public String ToString() + { + return name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/TransformConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/TransformConstraint.cs index 14947b5..d257f8f 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/TransformConstraint.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/TransformConstraint.cs @@ -30,111 +30,122 @@ using System; -namespace Spine3_5_51 { - public class TransformConstraint : IConstraint { - internal TransformConstraintData data; - internal ExposedList bones; - internal Bone target; - internal float rotateMix, translateMix, scaleMix, shearMix; +namespace Spine3_5_51 +{ + public class TransformConstraint : IConstraint + { + internal TransformConstraintData data; + internal ExposedList bones; + internal Bone target; + internal float rotateMix, translateMix, scaleMix, shearMix; - public TransformConstraintData Data { get { return data; } } - public int Order { get { return data.order; } } - public ExposedList Bones { get { return bones; } } - public Bone Target { get { return target; } set { target = value; } } - public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } - public float TranslateMix { get { return translateMix; } set { translateMix = value; } } - public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } } - public float ShearMix { get { return shearMix; } set { shearMix = value; } } + public TransformConstraintData Data { get { return data; } } + public int Order { get { return data.order; } } + public ExposedList Bones { get { return bones; } } + public Bone Target { get { return target; } set { target = value; } } + public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } + public float TranslateMix { get { return translateMix; } set { translateMix = value; } } + public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } } + public float ShearMix { get { return shearMix; } set { shearMix = value; } } - public TransformConstraint (TransformConstraintData data, Skeleton skeleton) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.data = data; - rotateMix = data.rotateMix; - translateMix = data.translateMix; - scaleMix = data.scaleMix; - shearMix = data.shearMix; + public TransformConstraint(TransformConstraintData data, Skeleton skeleton) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + rotateMix = data.rotateMix; + translateMix = data.translateMix; + scaleMix = data.scaleMix; + shearMix = data.shearMix; - bones = new ExposedList(); - foreach (BoneData boneData in data.bones) - bones.Add (skeleton.FindBone (boneData.name)); - - target = skeleton.FindBone(data.target.name); - } + bones = new ExposedList(); + foreach (BoneData boneData in data.bones) + bones.Add(skeleton.FindBone(boneData.name)); - public void Apply () { - Update(); - } + target = skeleton.FindBone(data.target.name); + } - public void Update () { - float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; - Bone target = this.target; - float ta = target.a, tb = target.b, tc = target.c, td = target.d; - float degRadReflect = (ta * td - tb * tc > 0) ? MathUtils.DegRad : -MathUtils.DegRad; - float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect; - var bones = this.bones; - var bonesItems = bones.Items; - for (int i = 0, n = bones.Count; i < n; i++) { - Bone bone = bonesItems[i]; - bool modified = false; + public void Apply() + { + Update(); + } - if (rotateMix != 0) { - float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - float r = MathUtils.Atan2(tc, ta) - MathUtils.Atan2(c, a) + offsetRotation; - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) r += MathUtils.PI2; - r *= rotateMix; - float cos = MathUtils.Cos(r), sin = MathUtils.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; - modified = true; - } + public void Update() + { + float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; + Bone target = this.target; + float ta = target.a, tb = target.b, tc = target.c, td = target.d; + float degRadReflect = (ta * td - tb * tc > 0) ? MathUtils.DegRad : -MathUtils.DegRad; + float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect; + var bones = this.bones; + var bonesItems = bones.Items; + for (int i = 0, n = bones.Count; i < n; i++) + { + Bone bone = bonesItems[i]; + bool modified = false; - if (translateMix != 0) { - float tempx, tempy; - target.LocalToWorld(data.offsetX, data.offsetY, out tempx, out tempy); - bone.worldX += (tempx - bone.worldX) * translateMix; - bone.worldY += (tempy - bone.worldY) * translateMix; - modified = true; - } + if (rotateMix != 0) + { + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + float r = MathUtils.Atan2(tc, ta) - MathUtils.Atan2(c, a) + offsetRotation; + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) r += MathUtils.PI2; + r *= rotateMix; + float cos = MathUtils.Cos(r), sin = MathUtils.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; + modified = true; + } - if (scaleMix > 0) { - float s = (float)Math.Sqrt(bone.a * bone.a + bone.c * bone.c); - float ts = (float)Math.Sqrt(ta * ta + tc * tc); - if (s > 0.00001f) s = (s + (ts - s + data.offsetScaleX) * scaleMix) / s; - bone.a *= s; - bone.c *= s; - s = (float)Math.Sqrt(bone.b * bone.b + bone.d * bone.d); - ts = (float)Math.Sqrt(tb * tb + td * td); - if (s > 0.00001f) s = (s + (ts - s + data.offsetScaleY) * scaleMix) / s; - bone.b *= s; - bone.d *= s; - modified = true; - } + if (translateMix != 0) + { + float tempx, tempy; + target.LocalToWorld(data.offsetX, data.offsetY, out tempx, out tempy); + bone.worldX += (tempx - bone.worldX) * translateMix; + bone.worldY += (tempy - bone.worldY) * translateMix; + modified = true; + } - if (shearMix > 0) { - float b = bone.b, d = bone.d; - float by = MathUtils.Atan2(d, b); - float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta) - (by - MathUtils.Atan2(bone.c, bone.a)); - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) r += MathUtils.PI2; - r = by + (r + offsetShearY) * shearMix; - float s = (float)Math.Sqrt(b * b + d * d); - bone.b = MathUtils.Cos(r) * s; - bone.d = MathUtils.Sin(r) * s; - modified = true; - } + if (scaleMix > 0) + { + float s = (float)Math.Sqrt(bone.a * bone.a + bone.c * bone.c); + float ts = (float)Math.Sqrt(ta * ta + tc * tc); + if (s > 0.00001f) s = (s + (ts - s + data.offsetScaleX) * scaleMix) / s; + bone.a *= s; + bone.c *= s; + s = (float)Math.Sqrt(bone.b * bone.b + bone.d * bone.d); + ts = (float)Math.Sqrt(tb * tb + td * td); + if (s > 0.00001f) s = (s + (ts - s + data.offsetScaleY) * scaleMix) / s; + bone.b *= s; + bone.d *= s; + modified = true; + } - if (modified) bone.appliedValid = false; - } - } + if (shearMix > 0) + { + float b = bone.b, d = bone.d; + float by = MathUtils.Atan2(d, b); + float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta) - (by - MathUtils.Atan2(bone.c, bone.a)); + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) r += MathUtils.PI2; + r = by + (r + offsetShearY) * shearMix; + float s = (float)Math.Sqrt(b * b + d * d); + bone.b = MathUtils.Cos(r) * s; + bone.d = MathUtils.Sin(r) * s; + modified = true; + } - override public String ToString () { - return data.name; - } - } + if (modified) bone.appliedValid = false; + } + } + + override public String ToString() + { + return data.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/TransformConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/TransformConstraintData.cs index 90b2fcc..f449297 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/TransformConstraintData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/TransformConstraintData.cs @@ -30,38 +30,42 @@ using System; -namespace Spine3_5_51 { - public class TransformConstraintData { - internal String name; - internal int order; - internal ExposedList bones = new ExposedList(); - internal BoneData target; - internal float rotateMix, translateMix, scaleMix, shearMix; - internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY; +namespace Spine3_5_51 +{ + public class TransformConstraintData + { + internal String name; + internal int order; + internal ExposedList bones = new ExposedList(); + internal BoneData target; + internal float rotateMix, translateMix, scaleMix, shearMix; + internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY; - public String Name { get { return name; } } - public int Order { get { return order; } set { order = value; } } - public ExposedList Bones { get { return bones; } } - public BoneData Target { get { return target; } set { target = value; } } - public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } - public float TranslateMix { get { return translateMix; } set { translateMix = value; } } - public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } } - public float ShearMix { get { return shearMix; } set { shearMix = value; } } + public String Name { get { return name; } } + public int Order { get { return order; } set { order = value; } } + public ExposedList Bones { get { return bones; } } + public BoneData Target { get { return target; } set { target = value; } } + public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } + public float TranslateMix { get { return translateMix; } set { translateMix = value; } } + public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } } + public float ShearMix { get { return shearMix; } set { shearMix = value; } } - public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } - public float OffsetX { get { return offsetX; } set { offsetX = value; } } - public float OffsetY { get { return offsetY; } set { offsetY = value; } } - public float OffsetScaleX { get { return offsetScaleX; } set { offsetScaleX = value; } } - public float OffsetScaleY { get { return offsetScaleY; } set { offsetScaleY = value; } } - public float OffsetShearY { get { return offsetShearY; } set { offsetShearY = value; } } + public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } + public float OffsetX { get { return offsetX; } set { offsetX = value; } } + public float OffsetY { get { return offsetY; } set { offsetY = value; } } + public float OffsetScaleX { get { return offsetScaleX; } set { offsetScaleX = value; } } + public float OffsetScaleY { get { return offsetScaleY; } set { offsetScaleY = value; } } + public float OffsetShearY { get { return offsetShearY; } set { offsetShearY = value; } } - public TransformConstraintData (String name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; - } + public TransformConstraintData(String name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + } - override public String ToString () { - return name; - } - } + override public String ToString() + { + return name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/XnaLoader/MeshBatcher.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/XnaLoader/MeshBatcher.cs index 2607082..3cc9f1a 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/XnaLoader/MeshBatcher.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/XnaLoader/MeshBatcher.cs @@ -32,136 +32,146 @@ using System; using System.Collections.Generic; using Microsoft.Xna.Framework.Graphics; -using Microsoft.Xna.Framework; -namespace Spine3_5_51 { - // #region License - // /* - // Microsoft Public License (Ms-PL) - // MonoGame - Copyright ?2009 The MonoGame Team - // - // All rights reserved. - // - // This license governs use of the accompanying software. If you use the software, you accept this license. If you do not - // accept the license, do not use the software. - // - // 1. Definitions - // The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under - // U.S. copyright law. - // - // A "contribution" is the original software, or any additions or changes to the software. - // A "contributor" is any person that distributes its contribution under this license. - // "Licensed patents" are a contributor's patent claims that read directly on its contribution. - // - // 2. Grant of Rights - // (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, - // each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. - // (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, - // each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. - // - // 3. Conditions and Limitations - // (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. - // (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, - // your patent license from such contributor to the software ends automatically. - // (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution - // notices that are present in the software. - // (D) If you distribute any portion of the software in source code form, you may do so only under this license by including - // a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object - // code form, you may only do so under a license that complies with this license. - // (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees - // or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent - // permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular - // purpose and non-infringement. - // */ - // #endregion License - // +namespace Spine3_5_51 +{ + // #region License + // /* + // Microsoft Public License (Ms-PL) + // MonoGame - Copyright ?2009 The MonoGame Team + // + // All rights reserved. + // + // This license governs use of the accompanying software. If you use the software, you accept this license. If you do not + // accept the license, do not use the software. + // + // 1. Definitions + // The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under + // U.S. copyright law. + // + // A "contribution" is the original software, or any additions or changes to the software. + // A "contributor" is any person that distributes its contribution under this license. + // "Licensed patents" are a contributor's patent claims that read directly on its contribution. + // + // 2. Grant of Rights + // (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, + // each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. + // (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, + // each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. + // + // 3. Conditions and Limitations + // (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. + // (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, + // your patent license from such contributor to the software ends automatically. + // (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution + // notices that are present in the software. + // (D) If you distribute any portion of the software in source code form, you may do so only under this license by including + // a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object + // code form, you may only do so under a license that complies with this license. + // (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees + // or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent + // permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular + // purpose and non-infringement. + // */ + // #endregion License + // - /// Draws batched meshes. - public class MeshBatcher { - private readonly List items; - private readonly Queue freeItems; - private VertexPositionColorTexture[] vertexArray = { }; - private short[] triangles = { }; + /// Draws batched meshes. + public class MeshBatcher + { + private readonly List items; + private readonly Queue freeItems; + private VertexPositionColorTexture[] vertexArray = { }; + private short[] triangles = { }; - public MeshBatcher () { - items = new List(256); - freeItems = new Queue(256); - EnsureCapacity(256, 512); - } + public MeshBatcher() + { + items = new List(256); + freeItems = new Queue(256); + EnsureCapacity(256, 512); + } - /// Returns a pooled MeshItem. - public MeshItem NextItem (int vertexCount, int triangleCount) { - MeshItem item = freeItems.Count > 0 ? freeItems.Dequeue() : new MeshItem(); - if (item.vertices.Length < vertexCount) item.vertices = new VertexPositionColorTexture[vertexCount]; - if (item.triangles.Length < triangleCount) item.triangles = new int[triangleCount]; - item.vertexCount = vertexCount; - item.triangleCount = triangleCount; - items.Add(item); - return item; - } + /// Returns a pooled MeshItem. + public MeshItem NextItem(int vertexCount, int triangleCount) + { + MeshItem item = freeItems.Count > 0 ? freeItems.Dequeue() : new MeshItem(); + if (item.vertices.Length < vertexCount) item.vertices = new VertexPositionColorTexture[vertexCount]; + if (item.triangles.Length < triangleCount) item.triangles = new int[triangleCount]; + item.vertexCount = vertexCount; + item.triangleCount = triangleCount; + items.Add(item); + return item; + } - private void EnsureCapacity (int vertexCount, int triangleCount) { - if (vertexArray.Length < vertexCount) vertexArray = new VertexPositionColorTexture[vertexCount]; - if (triangles.Length < triangleCount) triangles = new short[triangleCount]; - } + private void EnsureCapacity(int vertexCount, int triangleCount) + { + if (vertexArray.Length < vertexCount) vertexArray = new VertexPositionColorTexture[vertexCount]; + if (triangles.Length < triangleCount) triangles = new short[triangleCount]; + } - public void Draw (GraphicsDevice device) { - if (items.Count == 0) return; + public void Draw(GraphicsDevice device) + { + if (items.Count == 0) return; - int itemCount = items.Count; - int vertexCount = 0, triangleCount = 0; - for (int i = 0; i < itemCount; i++) { - MeshItem item = items[i]; - vertexCount += item.vertexCount; - triangleCount += item.triangleCount; - } - EnsureCapacity(vertexCount, triangleCount); + int itemCount = items.Count; + int vertexCount = 0, triangleCount = 0; + for (int i = 0; i < itemCount; i++) + { + MeshItem item = items[i]; + vertexCount += item.vertexCount; + triangleCount += item.triangleCount; + } + EnsureCapacity(vertexCount, triangleCount); - vertexCount = 0; - triangleCount = 0; - Texture2D lastTexture = null; - for (int i = 0; i < itemCount; i++) { - MeshItem item = items[i]; - int itemVertexCount = item.vertexCount; + vertexCount = 0; + triangleCount = 0; + Texture2D lastTexture = null; + for (int i = 0; i < itemCount; i++) + { + MeshItem item = items[i]; + int itemVertexCount = item.vertexCount; - if (item.texture != lastTexture || vertexCount + itemVertexCount > short.MaxValue) { - FlushVertexArray(device, vertexCount, triangleCount); - vertexCount = 0; - triangleCount = 0; - lastTexture = item.texture; - device.Textures[0] = lastTexture; - } + if (item.texture != lastTexture || vertexCount + itemVertexCount > short.MaxValue) + { + FlushVertexArray(device, vertexCount, triangleCount); + vertexCount = 0; + triangleCount = 0; + lastTexture = item.texture; + device.Textures[0] = lastTexture; + } - int[] itemTriangles = item.triangles; - int itemTriangleCount = item.triangleCount; - for (int ii = 0, t = triangleCount; ii < itemTriangleCount; ii++, t++) - triangles[t] = (short)(itemTriangles[ii] + vertexCount); - triangleCount += itemTriangleCount; + int[] itemTriangles = item.triangles; + int itemTriangleCount = item.triangleCount; + for (int ii = 0, t = triangleCount; ii < itemTriangleCount; ii++, t++) + triangles[t] = (short)(itemTriangles[ii] + vertexCount); + triangleCount += itemTriangleCount; - Array.Copy(item.vertices, 0, vertexArray, vertexCount, itemVertexCount); - vertexCount += itemVertexCount; + Array.Copy(item.vertices, 0, vertexArray, vertexCount, itemVertexCount); + vertexCount += itemVertexCount; - item.texture = null; - freeItems.Enqueue(item); - } - FlushVertexArray(device, vertexCount, triangleCount); - items.Clear(); - } + item.texture = null; + freeItems.Enqueue(item); + } + FlushVertexArray(device, vertexCount, triangleCount); + items.Clear(); + } - private void FlushVertexArray (GraphicsDevice device, int vertexCount, int triangleCount) { - if (vertexCount == 0) return; - device.DrawUserIndexedPrimitives( - PrimitiveType.TriangleList, - vertexArray, 0, vertexCount, - triangles, 0, triangleCount / 3, - VertexPositionColorTexture.VertexDeclaration); - } - } + private void FlushVertexArray(GraphicsDevice device, int vertexCount, int triangleCount) + { + if (vertexCount == 0) return; + device.DrawUserIndexedPrimitives( + PrimitiveType.TriangleList, + vertexArray, 0, vertexCount, + triangles, 0, triangleCount / 3, + VertexPositionColorTexture.VertexDeclaration); + } + } - public class MeshItem { - public Texture2D texture; - public int vertexCount, triangleCount; - public VertexPositionColorTexture[] vertices = { }; - public int[] triangles = { }; - } + public class MeshItem + { + public Texture2D texture; + public int vertexCount, triangleCount; + public VertexPositionColorTexture[] vertices = { }; + public int[] triangles = { }; + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/XnaLoader/RegionBatcher.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/XnaLoader/RegionBatcher.cs index 6df9908..123958a 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/XnaLoader/RegionBatcher.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/XnaLoader/RegionBatcher.cs @@ -32,151 +32,163 @@ using System; using System.Collections.Generic; using Microsoft.Xna.Framework.Graphics; -using Microsoft.Xna.Framework; -namespace Spine3_5_51 { - // #region License - // /* - // Microsoft Public License (Ms-PL) - // MonoGame - Copyright 2009 The MonoGame Team - // - // All rights reserved. - // - // This license governs use of the accompanying software. If you use the software, you accept this license. If you do not - // accept the license, do not use the software. - // - // 1. Definitions - // The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under - // U.S. copyright law. - // - // A "contribution" is the original software, or any additions or changes to the software. - // A "contributor" is any person that distributes its contribution under this license. - // "Licensed patents" are a contributor's patent claims that read directly on its contribution. - // - // 2. Grant of Rights - // (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, - // each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. - // (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, - // each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. - // - // 3. Conditions and Limitations - // (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. - // (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, - // your patent license from such contributor to the software ends automatically. - // (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution - // notices that are present in the software. - // (D) If you distribute any portion of the software in source code form, you may do so only under this license by including - // a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object - // code form, you may only do so under a license that complies with this license. - // (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees - // or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent - // permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular - // purpose and non-infringement. - // */ - // #endregion License - // +namespace Spine3_5_51 +{ + // #region License + // /* + // Microsoft Public License (Ms-PL) + // MonoGame - Copyright 2009 The MonoGame Team + // + // All rights reserved. + // + // This license governs use of the accompanying software. If you use the software, you accept this license. If you do not + // accept the license, do not use the software. + // + // 1. Definitions + // The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under + // U.S. copyright law. + // + // A "contribution" is the original software, or any additions or changes to the software. + // A "contributor" is any person that distributes its contribution under this license. + // "Licensed patents" are a contributor's patent claims that read directly on its contribution. + // + // 2. Grant of Rights + // (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, + // each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. + // (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, + // each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. + // + // 3. Conditions and Limitations + // (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. + // (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, + // your patent license from such contributor to the software ends automatically. + // (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution + // notices that are present in the software. + // (D) If you distribute any portion of the software in source code form, you may do so only under this license by including + // a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object + // code form, you may only do so under a license that complies with this license. + // (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees + // or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent + // permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular + // purpose and non-infringement. + // */ + // #endregion License + // - /// Draws batched quads using indices. - public class RegionBatcher { - private const int maxBatchSize = short.MaxValue / 6; // 6 = 4 vertices unique and 2 shared, per quad - private readonly List items; - private readonly Queue freeItems; - private VertexPositionColorTexture[] vertexArray; - private short[] indices; + /// Draws batched quads using indices. + public class RegionBatcher + { + private const int maxBatchSize = short.MaxValue / 6; // 6 = 4 vertices unique and 2 shared, per quad + private readonly List items; + private readonly Queue freeItems; + private VertexPositionColorTexture[] vertexArray; + private short[] indices; - public RegionBatcher () { - items = new List(256); - freeItems = new Queue(256); - EnsureArrayCapacity(256); - } + public RegionBatcher() + { + items = new List(256); + freeItems = new Queue(256); + EnsureArrayCapacity(256); + } - /// Returns a pooled RegionItem. - public RegionItem NextItem () { - RegionItem item = freeItems.Count > 0 ? freeItems.Dequeue() : new RegionItem(); - items.Add(item); - return item; - } + /// Returns a pooled RegionItem. + public RegionItem NextItem() + { + RegionItem item = freeItems.Count > 0 ? freeItems.Dequeue() : new RegionItem(); + items.Add(item); + return item; + } - /// Resize and recreate the indices and vertex position color buffers. - private void EnsureArrayCapacity (int itemCount) { - if (indices != null && indices.Length >= 6 * itemCount) return; + /// Resize and recreate the indices and vertex position color buffers. + private void EnsureArrayCapacity(int itemCount) + { + if (indices != null && indices.Length >= 6 * itemCount) return; - short[] newIndices = new short[6 * itemCount]; - int start = 0; - if (indices != null) { - indices.CopyTo(newIndices, 0); - start = indices.Length / 6; - } - for (var i = start; i < itemCount; i++) { - /* TL TR + short[] newIndices = new short[6 * itemCount]; + int start = 0; + if (indices != null) + { + indices.CopyTo(newIndices, 0); + start = indices.Length / 6; + } + for (var i = start; i < itemCount; i++) + { + /* TL TR * 0----1 0,1,2,3 = index offsets for vertex indices * | | TL,TR,BL,BR are vertex references in RegionItem. * 2----3 * BL BR */ - newIndices[i * 6 + 0] = (short)(i * 4); - newIndices[i * 6 + 1] = (short)(i * 4 + 1); - newIndices[i * 6 + 2] = (short)(i * 4 + 2); - newIndices[i * 6 + 3] = (short)(i * 4 + 1); - newIndices[i * 6 + 4] = (short)(i * 4 + 3); - newIndices[i * 6 + 5] = (short)(i * 4 + 2); - } - indices = newIndices; + newIndices[i * 6 + 0] = (short)(i * 4); + newIndices[i * 6 + 1] = (short)(i * 4 + 1); + newIndices[i * 6 + 2] = (short)(i * 4 + 2); + newIndices[i * 6 + 3] = (short)(i * 4 + 1); + newIndices[i * 6 + 4] = (short)(i * 4 + 3); + newIndices[i * 6 + 5] = (short)(i * 4 + 2); + } + indices = newIndices; - vertexArray = new VertexPositionColorTexture[4 * itemCount]; - } + vertexArray = new VertexPositionColorTexture[4 * itemCount]; + } - public void Draw (GraphicsDevice device) { - if (items.Count == 0) return; + public void Draw(GraphicsDevice device) + { + if (items.Count == 0) return; - int itemIndex = 0; - int itemCount = items.Count; - while (itemCount > 0) { - int itemsToProcess = Math.Min(itemCount, maxBatchSize); - EnsureArrayCapacity(itemsToProcess); + int itemIndex = 0; + int itemCount = items.Count; + while (itemCount > 0) + { + int itemsToProcess = Math.Min(itemCount, maxBatchSize); + EnsureArrayCapacity(itemsToProcess); - var count = 0; - Texture2D texture = null; - for (int i = 0; i < itemsToProcess; i++, itemIndex++) { - RegionItem item = items[itemIndex]; - if (item.texture != texture) { - FlushVertexArray(device, count); - texture = item.texture; - count = 0; - device.Textures[0] = texture; - } + var count = 0; + Texture2D texture = null; + for (int i = 0; i < itemsToProcess; i++, itemIndex++) + { + RegionItem item = items[itemIndex]; + if (item.texture != texture) + { + FlushVertexArray(device, count); + texture = item.texture; + count = 0; + device.Textures[0] = texture; + } - vertexArray[count++] = item.vertexTL; - vertexArray[count++] = item.vertexTR; - vertexArray[count++] = item.vertexBL; - vertexArray[count++] = item.vertexBR; + vertexArray[count++] = item.vertexTL; + vertexArray[count++] = item.vertexTR; + vertexArray[count++] = item.vertexBL; + vertexArray[count++] = item.vertexBR; - item.texture = null; - freeItems.Enqueue(item); - } - FlushVertexArray(device, count); - itemCount -= itemsToProcess; - } - items.Clear(); - } + item.texture = null; + freeItems.Enqueue(item); + } + FlushVertexArray(device, count); + itemCount -= itemsToProcess; + } + items.Clear(); + } - /// Sends the triangle list to the graphics device. - /// Start index of vertices to draw. Not used except to compute the count of vertices to draw. - /// End index of vertices to draw. Not used except to compute the count of vertices to draw. - private void FlushVertexArray (GraphicsDevice device, int count) { - if (count == 0) return; - device.DrawUserIndexedPrimitives( - PrimitiveType.TriangleList, - vertexArray, 0, count, - indices, 0, (count / 4) * 2, - VertexPositionColorTexture.VertexDeclaration); - } - } + /// Sends the triangle list to the graphics device. + /// Start index of vertices to draw. Not used except to compute the count of vertices to draw. + /// End index of vertices to draw. Not used except to compute the count of vertices to draw. + private void FlushVertexArray(GraphicsDevice device, int count) + { + if (count == 0) return; + device.DrawUserIndexedPrimitives( + PrimitiveType.TriangleList, + vertexArray, 0, count, + indices, 0, (count / 4) * 2, + VertexPositionColorTexture.VertexDeclaration); + } + } - public class RegionItem { - public Texture2D texture; - public VertexPositionColorTexture vertexTL; - public VertexPositionColorTexture vertexTR; - public VertexPositionColorTexture vertexBL; - public VertexPositionColorTexture vertexBR; - } + public class RegionItem + { + public Texture2D texture; + public VertexPositionColorTexture vertexTL; + public VertexPositionColorTexture vertexTR; + public VertexPositionColorTexture vertexBL; + public VertexPositionColorTexture vertexBR; + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/XnaLoader/SkeletonMeshRenderer.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/XnaLoader/SkeletonMeshRenderer.cs index 8c1005c..ec08ddc 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/XnaLoader/SkeletonMeshRenderer.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/XnaLoader/SkeletonMeshRenderer.cs @@ -29,168 +29,185 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; -using System.Collections.Generic; -using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; -namespace Spine3_5_51 { - /// Draws region and mesh attachments. - public class SkeletonMeshRenderer { - private const int TL = 0; - private const int TR = 1; - private const int BL = 2; - private const int BR = 3; - - GraphicsDevice device; - MeshBatcher batcher; - RasterizerState rasterizerState; - float[] vertices = new float[8]; - int[] quadTriangles = { 0, 1, 2, 1, 3, 2 }; - BlendState defaultBlendState; - - BasicEffect effect; - public BasicEffect Effect { get { return effect; } set { effect = value; } } - - private bool premultipliedAlpha; - public bool PremultipliedAlpha { get { return premultipliedAlpha; } set { premultipliedAlpha = value; } } - - public SkeletonMeshRenderer (GraphicsDevice device) { - this.device = device; - - batcher = new MeshBatcher(); - - effect = new BasicEffect(device); - effect.World = Matrix.Identity; - effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); - effect.TextureEnabled = true; - effect.VertexColorEnabled = true; - - rasterizerState = new RasterizerState(); - rasterizerState.CullMode = CullMode.None; - - Bone.yDown = true; - } - - public void Begin () { - defaultBlendState = premultipliedAlpha ? BlendState.AlphaBlend : BlendState.NonPremultiplied; - - device.RasterizerState = rasterizerState; - device.BlendState = defaultBlendState; - - effect.Projection = Matrix.CreateOrthographicOffCenter(0, device.Viewport.Width, device.Viewport.Height, 0, 1, 0); - } - - public void End () { - foreach (EffectPass pass in effect.CurrentTechnique.Passes) { - pass.Apply(); - batcher.Draw(device); - } - } - - public void Draw (Skeleton skeleton) { - float[] vertices = this.vertices; - var drawOrder = skeleton.DrawOrder; - var drawOrderItems = skeleton.DrawOrder.Items; - float skeletonR = skeleton.R, skeletonG = skeleton.G, skeletonB = skeleton.B, skeletonA = skeleton.A; - for (int i = 0, n = drawOrder.Count; i < n; i++) { - Slot slot = drawOrderItems[i]; - Attachment attachment = slot.Attachment; - if (attachment is RegionAttachment) { - RegionAttachment regionAttachment = (RegionAttachment)attachment; - BlendState blend = slot.Data.BlendMode == BlendMode.additive ? BlendState.Additive : defaultBlendState; - if (device.BlendState != blend) { - End(); - device.BlendState = blend; - } - - MeshItem item = batcher.NextItem(4, 6); - item.triangles = quadTriangles; - VertexPositionColorTexture[] itemVertices = item.vertices; - - AtlasRegion region = (AtlasRegion)regionAttachment.RendererObject; - item.texture = (Texture2D)region.page.rendererObject; - - Color color; - float a = skeletonA * slot.A * regionAttachment.A; - if (premultipliedAlpha) { - color = new Color( - skeletonR * slot.R * regionAttachment.R * a, - skeletonG * slot.G * regionAttachment.G * a, - skeletonB * slot.B * regionAttachment.B * a, a); - } else { - color = new Color( - skeletonR * slot.R * regionAttachment.R, - skeletonG * slot.G * regionAttachment.G, - skeletonB * slot.B * regionAttachment.B, a); - } - itemVertices[TL].Color = color; - itemVertices[BL].Color = color; - itemVertices[BR].Color = color; - itemVertices[TR].Color = color; - - regionAttachment.ComputeWorldVertices(slot.Bone, vertices); - itemVertices[TL].Position.X = vertices[RegionAttachment.X1]; - itemVertices[TL].Position.Y = vertices[RegionAttachment.Y1]; - itemVertices[TL].Position.Z = 0; - itemVertices[BL].Position.X = vertices[RegionAttachment.X2]; - itemVertices[BL].Position.Y = vertices[RegionAttachment.Y2]; - itemVertices[BL].Position.Z = 0; - itemVertices[BR].Position.X = vertices[RegionAttachment.X3]; - itemVertices[BR].Position.Y = vertices[RegionAttachment.Y3]; - itemVertices[BR].Position.Z = 0; - itemVertices[TR].Position.X = vertices[RegionAttachment.X4]; - itemVertices[TR].Position.Y = vertices[RegionAttachment.Y4]; - itemVertices[TR].Position.Z = 0; - - float[] uvs = regionAttachment.UVs; - itemVertices[TL].TextureCoordinate.X = uvs[RegionAttachment.X1]; - itemVertices[TL].TextureCoordinate.Y = uvs[RegionAttachment.Y1]; - itemVertices[BL].TextureCoordinate.X = uvs[RegionAttachment.X2]; - itemVertices[BL].TextureCoordinate.Y = uvs[RegionAttachment.Y2]; - itemVertices[BR].TextureCoordinate.X = uvs[RegionAttachment.X3]; - itemVertices[BR].TextureCoordinate.Y = uvs[RegionAttachment.Y3]; - itemVertices[TR].TextureCoordinate.X = uvs[RegionAttachment.X4]; - itemVertices[TR].TextureCoordinate.Y = uvs[RegionAttachment.Y4]; - } else if (attachment is MeshAttachment) { - MeshAttachment mesh = (MeshAttachment)attachment; - int vertexCount = mesh.WorldVerticesLength; - if (vertices.Length < vertexCount) vertices = new float[vertexCount]; - mesh.ComputeWorldVertices(slot, vertices); - - int[] triangles = mesh.Triangles; - MeshItem item = batcher.NextItem(vertexCount, triangles.Length); - item.triangles = triangles; - - AtlasRegion region = (AtlasRegion)mesh.RendererObject; - item.texture = (Texture2D)region.page.rendererObject; - - Color color; - float a = skeletonA * slot.A * mesh.A; - if (premultipliedAlpha) { - color = new Color( - skeletonR * slot.R * mesh.R * a, - skeletonG * slot.G * mesh.G * a, - skeletonB * slot.B * mesh.B * a, a); - } else { - color = new Color( - skeletonR * slot.R * mesh.R, - skeletonG * slot.G * mesh.G, - skeletonB * slot.B * mesh.B, a); - } - - float[] uvs = mesh.UVs; - VertexPositionColorTexture[] itemVertices = item.vertices; - for (int ii = 0, v = 0; v < vertexCount; ii++, v += 2) { - itemVertices[ii].Color = color; - itemVertices[ii].Position.X = vertices[v]; - itemVertices[ii].Position.Y = vertices[v + 1]; - itemVertices[ii].Position.Z = 0; - itemVertices[ii].TextureCoordinate.X = uvs[v]; - itemVertices[ii].TextureCoordinate.Y = uvs[v + 1]; - } - } - } - } - } +namespace Spine3_5_51 +{ + /// Draws region and mesh attachments. + public class SkeletonMeshRenderer + { + private const int TL = 0; + private const int TR = 1; + private const int BL = 2; + private const int BR = 3; + + GraphicsDevice device; + MeshBatcher batcher; + RasterizerState rasterizerState; + float[] vertices = new float[8]; + int[] quadTriangles = { 0, 1, 2, 1, 3, 2 }; + BlendState defaultBlendState; + + BasicEffect effect; + public BasicEffect Effect { get { return effect; } set { effect = value; } } + + private bool premultipliedAlpha; + public bool PremultipliedAlpha { get { return premultipliedAlpha; } set { premultipliedAlpha = value; } } + + public SkeletonMeshRenderer(GraphicsDevice device) + { + this.device = device; + + batcher = new MeshBatcher(); + + effect = new BasicEffect(device); + effect.World = Matrix.Identity; + effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); + effect.TextureEnabled = true; + effect.VertexColorEnabled = true; + + rasterizerState = new RasterizerState(); + rasterizerState.CullMode = CullMode.None; + + Bone.yDown = true; + } + + public void Begin() + { + defaultBlendState = premultipliedAlpha ? BlendState.AlphaBlend : BlendState.NonPremultiplied; + + device.RasterizerState = rasterizerState; + device.BlendState = defaultBlendState; + + effect.Projection = Matrix.CreateOrthographicOffCenter(0, device.Viewport.Width, device.Viewport.Height, 0, 1, 0); + } + + public void End() + { + foreach (EffectPass pass in effect.CurrentTechnique.Passes) + { + pass.Apply(); + batcher.Draw(device); + } + } + + public void Draw(Skeleton skeleton) + { + float[] vertices = this.vertices; + var drawOrder = skeleton.DrawOrder; + var drawOrderItems = skeleton.DrawOrder.Items; + float skeletonR = skeleton.R, skeletonG = skeleton.G, skeletonB = skeleton.B, skeletonA = skeleton.A; + for (int i = 0, n = drawOrder.Count; i < n; i++) + { + Slot slot = drawOrderItems[i]; + Attachment attachment = slot.Attachment; + if (attachment is RegionAttachment) + { + RegionAttachment regionAttachment = (RegionAttachment)attachment; + BlendState blend = slot.Data.BlendMode == BlendMode.additive ? BlendState.Additive : defaultBlendState; + if (device.BlendState != blend) + { + End(); + device.BlendState = blend; + } + + MeshItem item = batcher.NextItem(4, 6); + item.triangles = quadTriangles; + VertexPositionColorTexture[] itemVertices = item.vertices; + + AtlasRegion region = (AtlasRegion)regionAttachment.RendererObject; + item.texture = (Texture2D)region.page.rendererObject; + + Color color; + float a = skeletonA * slot.A * regionAttachment.A; + if (premultipliedAlpha) + { + color = new Color( + skeletonR * slot.R * regionAttachment.R * a, + skeletonG * slot.G * regionAttachment.G * a, + skeletonB * slot.B * regionAttachment.B * a, a); + } + else + { + color = new Color( + skeletonR * slot.R * regionAttachment.R, + skeletonG * slot.G * regionAttachment.G, + skeletonB * slot.B * regionAttachment.B, a); + } + itemVertices[TL].Color = color; + itemVertices[BL].Color = color; + itemVertices[BR].Color = color; + itemVertices[TR].Color = color; + + regionAttachment.ComputeWorldVertices(slot.Bone, vertices); + itemVertices[TL].Position.X = vertices[RegionAttachment.X1]; + itemVertices[TL].Position.Y = vertices[RegionAttachment.Y1]; + itemVertices[TL].Position.Z = 0; + itemVertices[BL].Position.X = vertices[RegionAttachment.X2]; + itemVertices[BL].Position.Y = vertices[RegionAttachment.Y2]; + itemVertices[BL].Position.Z = 0; + itemVertices[BR].Position.X = vertices[RegionAttachment.X3]; + itemVertices[BR].Position.Y = vertices[RegionAttachment.Y3]; + itemVertices[BR].Position.Z = 0; + itemVertices[TR].Position.X = vertices[RegionAttachment.X4]; + itemVertices[TR].Position.Y = vertices[RegionAttachment.Y4]; + itemVertices[TR].Position.Z = 0; + + float[] uvs = regionAttachment.UVs; + itemVertices[TL].TextureCoordinate.X = uvs[RegionAttachment.X1]; + itemVertices[TL].TextureCoordinate.Y = uvs[RegionAttachment.Y1]; + itemVertices[BL].TextureCoordinate.X = uvs[RegionAttachment.X2]; + itemVertices[BL].TextureCoordinate.Y = uvs[RegionAttachment.Y2]; + itemVertices[BR].TextureCoordinate.X = uvs[RegionAttachment.X3]; + itemVertices[BR].TextureCoordinate.Y = uvs[RegionAttachment.Y3]; + itemVertices[TR].TextureCoordinate.X = uvs[RegionAttachment.X4]; + itemVertices[TR].TextureCoordinate.Y = uvs[RegionAttachment.Y4]; + } + else if (attachment is MeshAttachment) + { + MeshAttachment mesh = (MeshAttachment)attachment; + int vertexCount = mesh.WorldVerticesLength; + if (vertices.Length < vertexCount) vertices = new float[vertexCount]; + mesh.ComputeWorldVertices(slot, vertices); + + int[] triangles = mesh.Triangles; + MeshItem item = batcher.NextItem(vertexCount, triangles.Length); + item.triangles = triangles; + + AtlasRegion region = (AtlasRegion)mesh.RendererObject; + item.texture = (Texture2D)region.page.rendererObject; + + Color color; + float a = skeletonA * slot.A * mesh.A; + if (premultipliedAlpha) + { + color = new Color( + skeletonR * slot.R * mesh.R * a, + skeletonG * slot.G * mesh.G * a, + skeletonB * slot.B * mesh.B * a, a); + } + else + { + color = new Color( + skeletonR * slot.R * mesh.R, + skeletonG * slot.G * mesh.G, + skeletonB * slot.B * mesh.B, a); + } + + float[] uvs = mesh.UVs; + VertexPositionColorTexture[] itemVertices = item.vertices; + for (int ii = 0, v = 0; v < vertexCount; ii++, v += 2) + { + itemVertices[ii].Color = color; + itemVertices[ii].Position.X = vertices[v]; + itemVertices[ii].Position.Y = vertices[v + 1]; + itemVertices[ii].Position.Z = 0; + itemVertices[ii].TextureCoordinate.X = uvs[v]; + itemVertices[ii].TextureCoordinate.Y = uvs[v + 1]; + } + } + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/XnaLoader/SkeletonRegionRenderer.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/XnaLoader/SkeletonRegionRenderer.cs index bf17838..9535b5a 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/XnaLoader/SkeletonRegionRenderer.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/XnaLoader/SkeletonRegionRenderer.cs @@ -29,67 +29,74 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; -using System.Collections.Generic; -using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; -namespace Spine3_5_51 { - /// Draws region attachments. - public class SkeletonRegionRenderer { - GraphicsDevice device; - RegionBatcher batcher; - RasterizerState rasterizerState; - float[] vertices = new float[8]; - BlendState defaultBlendState; - - BasicEffect effect; - public BasicEffect Effect { get { return effect; } set { effect = value; } } - - private bool premultipliedAlpha; - public bool PremultipliedAlpha { get { return premultipliedAlpha; } set { premultipliedAlpha = value; } } - - public SkeletonRegionRenderer (GraphicsDevice device) { - this.device = device; - - batcher = new RegionBatcher(); - - effect = new BasicEffect(device); - effect.World = Matrix.Identity; - effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); - effect.TextureEnabled = true; - effect.VertexColorEnabled = true; - - rasterizerState = new RasterizerState(); - rasterizerState.CullMode = CullMode.None; - - Bone.yDown = true; - } - - public void Begin () { - defaultBlendState = premultipliedAlpha ? BlendState.AlphaBlend : BlendState.NonPremultiplied; - - device.RasterizerState = rasterizerState; - device.BlendState = defaultBlendState; - - effect.Projection = Matrix.CreateOrthographicOffCenter(0, device.Viewport.Width, device.Viewport.Height, 0, 1, 0); - } - - public void End () { - foreach (EffectPass pass in effect.CurrentTechnique.Passes) { - pass.Apply(); - batcher.Draw(device); - } - } - - public void Draw (Skeleton skeleton) { - var drawOrder = skeleton.DrawOrder; - var drawOrderItems = skeleton.DrawOrder.Items; - float skeletonR = skeleton.R, skeletonG = skeleton.G, skeletonB = skeleton.B, skeletonA = skeleton.A; - for (int i = 0, n = drawOrder.Count; i < n; i++) { - Slot slot = drawOrderItems[i]; - RegionAttachment regionAttachment = slot.Attachment as RegionAttachment; - if (regionAttachment != null) { +namespace Spine3_5_51 +{ + /// Draws region attachments. + public class SkeletonRegionRenderer + { + GraphicsDevice device; + RegionBatcher batcher; + RasterizerState rasterizerState; + float[] vertices = new float[8]; + BlendState defaultBlendState; + + BasicEffect effect; + public BasicEffect Effect { get { return effect; } set { effect = value; } } + + private bool premultipliedAlpha; + public bool PremultipliedAlpha { get { return premultipliedAlpha; } set { premultipliedAlpha = value; } } + + public SkeletonRegionRenderer(GraphicsDevice device) + { + this.device = device; + + batcher = new RegionBatcher(); + + effect = new BasicEffect(device); + effect.World = Matrix.Identity; + effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); + effect.TextureEnabled = true; + effect.VertexColorEnabled = true; + + rasterizerState = new RasterizerState(); + rasterizerState.CullMode = CullMode.None; + + Bone.yDown = true; + } + + public void Begin() + { + defaultBlendState = premultipliedAlpha ? BlendState.AlphaBlend : BlendState.NonPremultiplied; + + device.RasterizerState = rasterizerState; + device.BlendState = defaultBlendState; + + effect.Projection = Matrix.CreateOrthographicOffCenter(0, device.Viewport.Width, device.Viewport.Height, 0, 1, 0); + } + + public void End() + { + foreach (EffectPass pass in effect.CurrentTechnique.Passes) + { + pass.Apply(); + batcher.Draw(device); + } + } + + public void Draw(Skeleton skeleton) + { + var drawOrder = skeleton.DrawOrder; + var drawOrderItems = skeleton.DrawOrder.Items; + float skeletonR = skeleton.R, skeletonG = skeleton.G, skeletonB = skeleton.B, skeletonA = skeleton.A; + for (int i = 0, n = drawOrder.Count; i < n; i++) + { + Slot slot = drawOrderItems[i]; + RegionAttachment regionAttachment = slot.Attachment as RegionAttachment; + if (regionAttachment != null) + { BlendState blendState = new BlendState(); Blend blendSrc; @@ -150,46 +157,46 @@ public void Draw (Skeleton skeleton) { RegionItem item = batcher.NextItem(); - AtlasRegion region = (AtlasRegion)regionAttachment.RendererObject; - item.texture = (Texture2D)region.page.rendererObject; - - Color color; - float a = skeletonA * slot.A; - if (premultipliedAlpha) - color = new Color(skeletonR * slot.R * a, skeletonG * slot.G * a, skeletonB * slot.B * a, a); - else - color = new Color(skeletonR * slot.R, skeletonG * slot.G, skeletonB * slot.B, a); - item.vertexTL.Color = color; - item.vertexBL.Color = color; - item.vertexBR.Color = color; - item.vertexTR.Color = color; - - float[] vertices = this.vertices; - regionAttachment.ComputeWorldVertices(slot.Bone, vertices); - item.vertexTL.Position.X = vertices[RegionAttachment.X1]; - item.vertexTL.Position.Y = vertices[RegionAttachment.Y1]; - item.vertexTL.Position.Z = 0; - item.vertexBL.Position.X = vertices[RegionAttachment.X2]; - item.vertexBL.Position.Y = vertices[RegionAttachment.Y2]; - item.vertexBL.Position.Z = 0; - item.vertexBR.Position.X = vertices[RegionAttachment.X3]; - item.vertexBR.Position.Y = vertices[RegionAttachment.Y3]; - item.vertexBR.Position.Z = 0; - item.vertexTR.Position.X = vertices[RegionAttachment.X4]; - item.vertexTR.Position.Y = vertices[RegionAttachment.Y4]; - item.vertexTR.Position.Z = 0; - - float[] uvs = regionAttachment.UVs; - item.vertexTL.TextureCoordinate.X = uvs[RegionAttachment.X1]; - item.vertexTL.TextureCoordinate.Y = uvs[RegionAttachment.Y1]; - item.vertexBL.TextureCoordinate.X = uvs[RegionAttachment.X2]; - item.vertexBL.TextureCoordinate.Y = uvs[RegionAttachment.Y2]; - item.vertexBR.TextureCoordinate.X = uvs[RegionAttachment.X3]; - item.vertexBR.TextureCoordinate.Y = uvs[RegionAttachment.Y3]; - item.vertexTR.TextureCoordinate.X = uvs[RegionAttachment.X4]; - item.vertexTR.TextureCoordinate.Y = uvs[RegionAttachment.Y4]; - } - } - } - } + AtlasRegion region = (AtlasRegion)regionAttachment.RendererObject; + item.texture = (Texture2D)region.page.rendererObject; + + Color color; + float a = skeletonA * slot.A; + if (premultipliedAlpha) + color = new Color(skeletonR * slot.R * a, skeletonG * slot.G * a, skeletonB * slot.B * a, a); + else + color = new Color(skeletonR * slot.R, skeletonG * slot.G, skeletonB * slot.B, a); + item.vertexTL.Color = color; + item.vertexBL.Color = color; + item.vertexBR.Color = color; + item.vertexTR.Color = color; + + float[] vertices = this.vertices; + regionAttachment.ComputeWorldVertices(slot.Bone, vertices); + item.vertexTL.Position.X = vertices[RegionAttachment.X1]; + item.vertexTL.Position.Y = vertices[RegionAttachment.Y1]; + item.vertexTL.Position.Z = 0; + item.vertexBL.Position.X = vertices[RegionAttachment.X2]; + item.vertexBL.Position.Y = vertices[RegionAttachment.Y2]; + item.vertexBL.Position.Z = 0; + item.vertexBR.Position.X = vertices[RegionAttachment.X3]; + item.vertexBR.Position.Y = vertices[RegionAttachment.Y3]; + item.vertexBR.Position.Z = 0; + item.vertexTR.Position.X = vertices[RegionAttachment.X4]; + item.vertexTR.Position.Y = vertices[RegionAttachment.Y4]; + item.vertexTR.Position.Z = 0; + + float[] uvs = regionAttachment.UVs; + item.vertexTL.TextureCoordinate.X = uvs[RegionAttachment.X1]; + item.vertexTL.TextureCoordinate.Y = uvs[RegionAttachment.Y1]; + item.vertexBL.TextureCoordinate.X = uvs[RegionAttachment.X2]; + item.vertexBL.TextureCoordinate.Y = uvs[RegionAttachment.Y2]; + item.vertexBR.TextureCoordinate.X = uvs[RegionAttachment.X3]; + item.vertexBR.TextureCoordinate.Y = uvs[RegionAttachment.Y3]; + item.vertexTR.TextureCoordinate.X = uvs[RegionAttachment.X4]; + item.vertexTR.TextureCoordinate.Y = uvs[RegionAttachment.Y4]; + } + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/XnaLoader/XnaTextureLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/XnaLoader/XnaTextureLoader.cs index 2bf0727..bcd16f4 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/XnaLoader/XnaTextureLoader.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.5.51/XnaLoader/XnaTextureLoader.cs @@ -30,21 +30,23 @@ *****************************************************************************/ using System; -using System.IO; -using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; -namespace Spine3_5_51 { - public class XnaTextureLoader : TextureLoader { - GraphicsDevice device; +namespace Spine3_5_51 +{ + public class XnaTextureLoader : TextureLoader + { + GraphicsDevice device; - public XnaTextureLoader (GraphicsDevice device) { - this.device = device; - } + public XnaTextureLoader(GraphicsDevice device) + { + this.device = device; + } - public void Load (AtlasPage page, String path) { - Texture2D texture = Util.LoadTexture(device, path); - page.rendererObject = texture; + public void Load(AtlasPage page, String path) + { + Texture2D texture = Util.LoadTexture(device, path); + page.rendererObject = texture; if (page.width == 0 || page.height == 0) { page.width = texture.Width; @@ -52,8 +54,9 @@ public void Load (AtlasPage page, String path) { } } - public void Unload (Object texture) { - ((Texture2D)texture).Dispose(); - } - } + public void Unload(Object texture) + { + ((Texture2D)texture).Dispose(); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Animation.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Animation.cs index 9568f26..75eb7a2 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Animation.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Animation.cs @@ -29,1346 +29,1574 @@ *****************************************************************************/ using System; -using System.Collections.Generic; - -namespace Spine3_6_32 { - public class Animation { - internal ExposedList timelines; - internal float duration; - internal String name; - - public string Name { get { return name; } } - public ExposedList Timelines { get { return timelines; } set { timelines = value; } } - public float Duration { get { return duration; } set { duration = value; } } - - public Animation (string name, ExposedList timelines, float duration) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - if (timelines == null) throw new ArgumentNullException("timelines", "timelines cannot be null."); - this.name = name; - this.timelines = timelines; - this.duration = duration; - } - - /// Applies all the animation's timelines to the specified skeleton. - /// - public void Apply (Skeleton skeleton, float lastTime, float time, bool loop, ExposedList events, float alpha, MixPose pose, MixDirection direction) { - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - - if (loop && duration != 0) { - time %= duration; - if (lastTime > 0) lastTime %= duration; - } - - ExposedList timelines = this.timelines; - for (int i = 0, n = timelines.Count; i < n; i++) - timelines.Items[i].Apply(skeleton, lastTime, time, events, alpha, pose, direction); - } - - /// After the first and before the last entry. - internal static int BinarySearch (float[] values, float target, int step) { - int low = 0; - int high = values.Length / step - 2; - if (high == 0) return step; - int current = (int)((uint)high >> 1); - while (true) { - if (values[(current + 1) * step] <= target) - low = current + 1; - else - high = current; - if (low == high) return (low + 1) * step; - current = (int)((uint)(low + high) >> 1); - } - } - - /// After the first and before the last entry. - internal static int BinarySearch (float[] values, float target) { - int low = 0; - int high = values.Length - 2; - if (high == 0) return 1; - int current = (int)((uint)high >> 1); - while (true) { - if (values[(current + 1)] <= target) - low = current + 1; - else - high = current; - if (low == high) return (low + 1); - current = (int)((uint)(low + high) >> 1); - } - } - - internal static int LinearSearch (float[] values, float target, int step) { - for (int i = 0, last = values.Length - step; i <= last; i += step) - if (values[i] > target) return i; - return -1; - } - } - - public interface Timeline { - /// Sets the value(s) for the specified time. - /// The skeleton the timeline is being applied to. This provides access to the bones, slots, and other skeleton components the timeline may change. - /// 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). - /// The time within the animation. Most timelines find the key before and the key after this time so they can interpolate between the keys. - /// If any events are fired, they are added to this list. Can be null to ignore firing events or if the timeline does not fire events. May be null. - /// 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). - /// Controls how mixing is applied when alpha is than 1. - /// Indicates whether the timeline is mixing in or out. Used by timelines which perform instant transitions such as DrawOrderTimeline and AttachmentTimeline. - void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, MixPose pose, MixDirection direction); - int PropertyId { get; } - } - - /// - /// Controls how a timeline is mixed with the setup or current pose. - /// - public enum MixPose { - /// The timeline value is mixed with the setup pose (the current pose is not used). - Setup, - /// The timeline value is mixed with the current pose. The setup pose is used as the timeline value before the first key, - /// except for timelines which perform instant transitions, such as DrawOrderTimeline or AttachmentTimeline. - Current, - /// The timeline value is mixed with the current pose. No change is made before the first key (the current pose is kept until the first key). - CurrentLayered - } - - /// - /// 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). - /// - public enum MixDirection { - In, - Out - } - - internal enum TimelineType { - Rotate = 0, Translate, Scale, Shear, // - Attachment, Color, Deform, // - Event, DrawOrder, // - IkConstraint, TransformConstraint, // - PathConstraintPosition, PathConstraintSpacing, PathConstraintMix, // - TwoColor - } - - /// Base class for frames that use an interpolation bezier curve. - abstract public class CurveTimeline : Timeline { - protected const float LINEAR = 0, STEPPED = 1, BEZIER = 2; - protected const int BEZIER_SIZE = 10 * 2 - 1; - - private float[] curves; // type, x, y, ... - public int FrameCount { get { return curves.Length / BEZIER_SIZE + 1; } } - - public CurveTimeline (int frameCount) { - if (frameCount <= 0) throw new ArgumentException("frameCount must be > 0: " + frameCount, "frameCount"); - curves = new float[(frameCount - 1) * BEZIER_SIZE]; - } - - abstract public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction); - - abstract public int PropertyId { get; } - - public void SetLinear (int frameIndex) { - curves[frameIndex * BEZIER_SIZE] = LINEAR; - } - - public void SetStepped (int frameIndex) { - curves[frameIndex * BEZIER_SIZE] = STEPPED; - } - - /// Sets the control handle positions for an interpolation bezier curve used to transition from this keyframe to the next. - /// cx1 and cx2 are from 0 to 1, representing the percent of time between the two keyframes. cy1 and cy2 are the percent of - /// the difference between the keyframe's values. - public void SetCurve (int frameIndex, float cx1, float cy1, float cx2, float cy2) { - float tmpx = (-cx1 * 2 + cx2) * 0.03f, tmpy = (-cy1 * 2 + cy2) * 0.03f; - float dddfx = ((cx1 - cx2) * 3 + 1) * 0.006f, dddfy = ((cy1 - cy2) * 3 + 1) * 0.006f; - float ddfx = tmpx * 2 + dddfx, ddfy = tmpy * 2 + dddfy; - float dfx = cx1 * 0.3f + tmpx + dddfx * 0.16666667f, dfy = cy1 * 0.3f + tmpy + dddfy * 0.16666667f; - - int i = frameIndex * BEZIER_SIZE; - float[] curves = this.curves; - curves[i++] = BEZIER; - - float x = dfx, y = dfy; - for (int n = i + BEZIER_SIZE - 1; i < n; i += 2) { - curves[i] = x; - curves[i + 1] = y; - dfx += ddfx; - dfy += ddfy; - ddfx += dddfx; - ddfy += dddfy; - x += dfx; - y += dfy; - } - } - - public float GetCurvePercent (int frameIndex, float percent) { - percent = MathUtils.Clamp (percent, 0, 1); - float[] curves = this.curves; - int i = frameIndex * BEZIER_SIZE; - float type = curves[i]; - if (type == LINEAR) return percent; - if (type == STEPPED) return 0; - i++; - float x = 0; - for (int start = i, n = i + BEZIER_SIZE - 1; i < n; i += 2) { - x = curves[i]; - if (x >= percent) { - float prevX, prevY; - if (i == start) { - prevX = 0; - prevY = 0; - } else { - prevX = curves[i - 2]; - prevY = curves[i - 1]; - } - return prevY + (curves[i + 1] - prevY) * (percent - prevX) / (x - prevX); - } - } - float y = curves[i - 1]; - return y + (1 - y) * (percent - x) / (1 - x); // Last point is 1,1. - } - public float GetCurveType (int frameIndex) { - return curves[frameIndex * BEZIER_SIZE]; - } - } - - public class RotateTimeline : CurveTimeline { - public const int ENTRIES = 2; - internal const int PREV_TIME = -2, PREV_ROTATION = -1; - internal const int ROTATION = 1; - - internal int boneIndex; - internal float[] frames; - - public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, angle, ... - - override public int PropertyId { - get { return ((int)TimelineType.Rotate << 24) + boneIndex; } - } - - public RotateTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount << 1]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, float degrees) { - frameIndex <<= 1; - frames[frameIndex] = time; - frames[frameIndex + ROTATION] = degrees; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - - float[] frames = this.frames; - if (time < frames[0]) { - switch (pose) { - case MixPose.Setup: - bone.rotation = bone.data.rotation; - return; - case MixPose.Current: - float rr = bone.data.rotation - bone.rotation; - rr -= (16384 - (int)(16384.499999999996 - rr / 360)) * 360; - bone.rotation += rr * alpha; - return; - } - return; - } - - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - if (pose == MixPose.Setup) { - bone.rotation = bone.data.rotation + frames[frames.Length + PREV_ROTATION] * alpha; - } else { - float rr = bone.data.rotation + frames[frames.Length + PREV_ROTATION] - bone.rotation; - rr -= (16384 - (int)(16384.499999999996 - rr / 360)) * 360; // Wrap within -180 and 180. - bone.rotation += rr * alpha; - } - return; - } - - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - float prevRotation = frames[frame + PREV_ROTATION]; - float frameTime = frames[frame]; - float percent = GetCurvePercent((frame >> 1) - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - float r = frames[frame + ROTATION] - prevRotation; - r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; - r = prevRotation + r * percent; - if (pose == MixPose.Setup) { - r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; - bone.rotation = bone.data.rotation + r * alpha; - } else { - r = bone.data.rotation + r - bone.rotation; - r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; - bone.rotation += r * alpha; - } - } - } - - public class TranslateTimeline : CurveTimeline { - public const int ENTRIES = 3; - protected const int PREV_TIME = -3, PREV_X = -2, PREV_Y = -1; - protected const int X = 1, Y = 2; - - internal int boneIndex; - internal float[] frames; - - public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, value, value, ... - - override public int PropertyId { - get { return ((int)TimelineType.Translate << 24) + boneIndex; } - } - - public TranslateTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, float x, float y) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + X] = x; - frames[frameIndex + Y] = y; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - - float[] frames = this.frames; - if (time < frames[0]) { - switch (pose) { - case MixPose.Setup: - bone.x = bone.data.x; - bone.y = bone.data.y; - return; - case MixPose.Current: - bone.x += (bone.data.x - bone.x) * alpha; - bone.y += (bone.data.y - bone.y) * alpha; - return; - } - return; - } - - float x, y; - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - x = frames[frames.Length + PREV_X]; - y = frames[frames.Length + PREV_Y]; - } else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - x = frames[frame + PREV_X]; - y = frames[frame + PREV_Y]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - x += (frames[frame + X] - x) * percent; - y += (frames[frame + Y] - y) * percent; - } - if (pose == MixPose.Setup) { - bone.x = bone.data.x + x * alpha; - bone.y = bone.data.y + y * alpha; - } else { - bone.x += (bone.data.x + x - bone.x) * alpha; - bone.y += (bone.data.y + y - bone.y) * alpha; - } - } - } - - public class ScaleTimeline : TranslateTimeline { - override public int PropertyId { - get { return ((int)TimelineType.Scale << 24) + boneIndex; } - } - - public ScaleTimeline (int frameCount) - : base(frameCount) { - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - - float[] frames = this.frames; - if (time < frames[0]) { - switch (pose) { - case MixPose.Setup: - bone.scaleX = bone.data.scaleX; - bone.scaleY = bone.data.scaleY; - return; - case MixPose.Current: - bone.scaleX += (bone.data.scaleX - bone.scaleX) * alpha; - bone.scaleY += (bone.data.scaleY - bone.scaleY) * alpha; - return; - } - return; - } - - float x, y; - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - x = frames[frames.Length + PREV_X] * bone.data.scaleX; - y = frames[frames.Length + PREV_Y] * bone.data.scaleY; - } else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - x = frames[frame + PREV_X]; - y = frames[frame + PREV_Y]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - x = (x + (frames[frame + X] - x) * percent) * bone.data.scaleX; - y = (y + (frames[frame + Y] - y) * percent) * bone.data.scaleY; - } - if (alpha == 1) { - bone.scaleX = x; - bone.scaleY = y; - } else { - float bx, by; - if (pose == MixPose.Setup) { - bx = bone.data.scaleX; - by = bone.data.scaleY; - } else { - bx = bone.scaleX; - by = bone.scaleY; - } - // Mixing out uses sign of setup or current pose, else use sign of key. - if (direction == MixDirection.Out) { - x = Math.Abs(x) * Math.Sign(bx); - y = Math.Abs(y) * Math.Sign(by); - } else { - bx = Math.Abs(bx) * Math.Sign(x); - by = Math.Abs(by) * Math.Sign(y); - } - bone.scaleX = bx + (x - bx) * alpha; - bone.scaleY = by + (y - by) * alpha; - } - } - } - - public class ShearTimeline : TranslateTimeline { - override public int PropertyId { - get { return ((int)TimelineType.Shear << 24) + boneIndex; } - } - - public ShearTimeline (int frameCount) - : base(frameCount) { - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - float[] frames = this.frames; - if (time < frames[0]) { - switch (pose) { - case MixPose.Setup: - bone.shearX = bone.data.shearX; - bone.shearY = bone.data.shearY; - return; - case MixPose.Current: - bone.shearX += (bone.data.shearX - bone.shearX) * alpha; - bone.shearY += (bone.data.shearY - bone.shearY) * alpha; - return; - } - return; - } - - float x, y; - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - x = frames[frames.Length + PREV_X]; - y = frames[frames.Length + PREV_Y]; - } else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - x = frames[frame + PREV_X]; - y = frames[frame + PREV_Y]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - x = x + (frames[frame + X] - x) * percent; - y = y + (frames[frame + Y] - y) * percent; - } - if (pose == MixPose.Setup) { - bone.shearX = bone.data.shearX + x * alpha; - bone.shearY = bone.data.shearY + y * alpha; - } else { - bone.shearX += (bone.data.shearX + x - bone.shearX) * alpha; - bone.shearY += (bone.data.shearY + y - bone.shearY) * alpha; - } - } - } - - public class ColorTimeline : CurveTimeline { - public const int ENTRIES = 5; - protected const int PREV_TIME = -5, PREV_R = -4, PREV_G = -3, PREV_B = -2, PREV_A = -1; - protected const int R = 1, G = 2, B = 3, A = 4; - - internal int slotIndex; - internal float[] frames; - - public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, r, g, b, a, ... - - override public int PropertyId { - get { return ((int)TimelineType.Color << 24) + slotIndex; } - } - - public ColorTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, float r, float g, float b, float a) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + R] = r; - frames[frameIndex + G] = g; - frames[frameIndex + B] = b; - frames[frameIndex + A] = a; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { - Slot slot = skeleton.slots.Items[slotIndex]; - float[] frames = this.frames; - if (time < frames[0]) { - var slotData = slot.data; - switch (pose) { - case MixPose.Setup: - slot.r = slotData.r; - slot.g = slotData.g; - slot.b = slotData.b; - slot.a = slotData.a; - return; - case MixPose.Current: - slot.r += (slot.r - slotData.r) * alpha; - slot.g += (slot.g - slotData.g) * alpha; - slot.b += (slot.b - slotData.b) * alpha; - slot.a += (slot.a - slotData.a) * alpha; - return; - } - return; - } - - float r, g, b, a; - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - int i = frames.Length; - r = frames[i + PREV_R]; - g = frames[i + PREV_G]; - b = frames[i + PREV_B]; - a = frames[i + PREV_A]; - } else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - r = frames[frame + PREV_R]; - g = frames[frame + PREV_G]; - b = frames[frame + PREV_B]; - a = frames[frame + PREV_A]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - r += (frames[frame + R] - r) * percent; - g += (frames[frame + G] - g) * percent; - b += (frames[frame + B] - b) * percent; - a += (frames[frame + A] - a) * percent; - } - if (alpha == 1) { - slot.r = r; - slot.g = g; - slot.b = b; - slot.a = a; - } else { - float br, bg, bb, ba; - if (pose == MixPose.Setup) { - br = slot.data.r; - bg = slot.data.g; - bb = slot.data.b; - ba = slot.data.a; - } else { - br = slot.r; - bg = slot.g; - bb = slot.b; - ba = slot.a; - } - slot.r = br + ((r - br) * alpha); - slot.g = bg + ((g - bg) * alpha); - slot.b = bb + ((b - bb) * alpha); - slot.a = ba + ((a - ba) * alpha); - } - } - } - - public class TwoColorTimeline : CurveTimeline { - public const int ENTRIES = 8; - protected const int PREV_TIME = -8, PREV_R = -7, PREV_G = -6, PREV_B = -5, PREV_A = -4; - protected const int PREV_R2 = -3, PREV_G2 = -2, PREV_B2 = -1; - protected const int R = 1, G = 2, B = 3, A = 4, R2 = 5, G2 = 6, B2 = 7; - - internal float[] frames; // time, r, g, b, a, r2, g2, b2, ... - public float[] Frames { get { return frames; } } - - internal int slotIndex; - public int SlotIndex { - get { return slotIndex; } - set { - if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0."); - slotIndex = value; - } - } - - override public int PropertyId { - get { return ((int)TimelineType.TwoColor << 24) + slotIndex; } - } - - public TwoColorTimeline (int frameCount) : - base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, float r, float g, float b, float a, float r2, float g2, float b2) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + R] = r; - frames[frameIndex + G] = g; - frames[frameIndex + B] = b; - frames[frameIndex + A] = a; - frames[frameIndex + R2] = r2; - frames[frameIndex + G2] = g2; - frames[frameIndex + B2] = b2; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { - Slot slot = skeleton.slots.Items[slotIndex]; - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - var slotData = slot.data; - switch (pose) { - case MixPose.Setup: - // slot.color.set(slot.data.color); - // slot.darkColor.set(slot.data.darkColor); - slot.r = slotData.r; - slot.g = slotData.g; - slot.b = slotData.b; - slot.a = slotData.a; - slot.r2 = slotData.r2; - slot.g2 = slotData.g2; - slot.b2 = slotData.b2; - return; - case MixPose.Current: - slot.r += (slot.r - slotData.r) * alpha; - slot.g += (slot.g - slotData.g) * alpha; - slot.b += (slot.b - slotData.b) * alpha; - slot.a += (slot.a - slotData.a) * alpha; - slot.r2 += (slot.r2 - slotData.r2) * alpha; - slot.g2 += (slot.g2 - slotData.g2) * alpha; - slot.b2 += (slot.b2 - slotData.b2) * alpha; - return; - } - return; - } - - float r, g, b, a, r2, g2, b2; - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - int i = frames.Length; - r = frames[i + PREV_R]; - g = frames[i + PREV_G]; - b = frames[i + PREV_B]; - a = frames[i + PREV_A]; - r2 = frames[i + PREV_R2]; - g2 = frames[i + PREV_G2]; - b2 = frames[i + PREV_B2]; - } else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - r = frames[frame + PREV_R]; - g = frames[frame + PREV_G]; - b = frames[frame + PREV_B]; - a = frames[frame + PREV_A]; - r2 = frames[frame + PREV_R2]; - g2 = frames[frame + PREV_G2]; - b2 = frames[frame + PREV_B2]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - r += (frames[frame + R] - r) * percent; - g += (frames[frame + G] - g) * percent; - b += (frames[frame + B] - b) * percent; - a += (frames[frame + A] - a) * percent; - r2 += (frames[frame + R2] - r2) * percent; - g2 += (frames[frame + G2] - g2) * percent; - b2 += (frames[frame + B2] - b2) * percent; - } - if (alpha == 1) { - slot.r = r; - slot.g = g; - slot.b = b; - slot.a = a; - slot.r2 = r2; - slot.g2 = g2; - slot.b2 = b2; - } else { - float br, bg, bb, ba, br2, bg2, bb2; - if (pose == MixPose.Setup) { - br = slot.data.r; - bg = slot.data.g; - bb = slot.data.b; - ba = slot.data.a; - br2 = slot.data.r2; - bg2 = slot.data.g2; - bb2 = slot.data.b2; - } else { - br = slot.r; - bg = slot.g; - bb = slot.b; - ba = slot.a; - br2 = slot.r2; - bg2 = slot.g2; - bb2 = slot.b2; - } - slot.r = br + ((r - br) * alpha); - slot.g = bg + ((g - bg) * alpha); - slot.b = bb + ((b - bb) * alpha); - slot.a = ba + ((a - ba) * alpha); - slot.r2 = br2 + ((r2 - br2) * alpha); - slot.g2 = bg2 + ((g2 - bg2) * alpha); - slot.b2 = bb2 + ((b2 - bb2) * alpha); - } - } - - } - - public class AttachmentTimeline : Timeline { - internal int slotIndex; - internal float[] frames; - private String[] attachmentNames; - - public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, ... - public String[] AttachmentNames { get { return attachmentNames; } set { attachmentNames = value; } } - public int FrameCount { get { return frames.Length; } } - - public int PropertyId { - get { return ((int)TimelineType.Attachment << 24) + slotIndex; } - } - - public AttachmentTimeline (int frameCount) { - frames = new float[frameCount]; - attachmentNames = new String[frameCount]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, String attachmentName) { - frames[frameIndex] = time; - attachmentNames[frameIndex] = attachmentName; - } - - public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { - string attachmentName; - Slot slot = skeleton.slots.Items[slotIndex]; - if (direction == MixDirection.Out && pose == MixPose.Setup) { - attachmentName = slot.data.attachmentName; - slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); - return; - } - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - if (pose == MixPose.Setup) { - attachmentName = slot.data.attachmentName; - slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); - } - return; - } - - int frameIndex; - if (time >= frames[frames.Length - 1]) // Time is after last frame. - frameIndex = frames.Length - 1; - else - frameIndex = Animation.BinarySearch(frames, time, 1) - 1; - - attachmentName = attachmentNames[frameIndex]; - slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); - } - } - - public class DeformTimeline : CurveTimeline { - internal int slotIndex; - internal float[] frames; - internal float[][] frameVertices; - internal VertexAttachment attachment; - - public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, ... - public float[][] Vertices { get { return frameVertices; } set { frameVertices = value; } } - public VertexAttachment Attachment { get { return attachment; } set { attachment = value; } } - - override public int PropertyId { - get { return ((int)TimelineType.Deform << 24) + attachment.id + slotIndex; } - } - - public DeformTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount]; - frameVertices = new float[frameCount][]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, float[] vertices) { - frames[frameIndex] = time; - frameVertices[frameIndex] = vertices; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { - Slot slot = skeleton.slots.Items[slotIndex]; - VertexAttachment slotAttachment = slot.attachment as VertexAttachment; - if (slotAttachment == null || !slotAttachment.ApplyDeform(attachment)) return; - - var verticesArray = slot.attachmentVertices; - float[][] frameVertices = this.frameVertices; - int vertexCount = frameVertices[0].Length; - if (verticesArray.Count != vertexCount && pose != MixPose.Setup) alpha = 1; // Don't mix from uninitialized slot vertices. - // verticesArray.SetSize(vertexCount) // Ensure size and preemptively set count. - if (verticesArray.Capacity < vertexCount) verticesArray.Capacity = vertexCount; - verticesArray.Count = vertexCount; - float[] vertices = verticesArray.Items; - - float[] frames = this.frames; - if (time < frames[0]) { - switch (pose) { - case MixPose.Setup: - verticesArray.Clear(); - return; - case MixPose.Current: - alpha = 1 - alpha; - for (int i = 0; i < vertexCount; i++) - vertices[i] *= alpha; - - return; - } - return; - } - - - - if (time >= frames[frames.Length - 1]) { // Time is after last frame. - float[] lastVertices = frameVertices[frames.Length - 1]; - if (alpha == 1) { - // Vertex positions or deform offsets, no alpha. - Array.Copy(lastVertices, 0, vertices, 0, vertexCount); - } else if (pose == MixPose.Setup) { - VertexAttachment vertexAttachment = slotAttachment; - if (vertexAttachment.bones == null) { - // Unweighted vertex positions, with alpha. - float[] setupVertices = vertexAttachment.vertices; - for (int i = 0; i < vertexCount; i++) { - float setup = setupVertices[i]; - vertices[i] = setup + (lastVertices[i] - setup) * alpha; - } - } else { - // Weighted deform offsets, with alpha. - for (int i = 0; i < vertexCount; i++) - vertices[i] = lastVertices[i] * alpha; - } - } else { - // Vertex positions or deform offsets, with alpha. - for (int i = 0; i < vertexCount; i++) - vertices[i] += (lastVertices[i] - vertices[i]) * alpha; - } - return; - } - - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time); - float[] prevVertices = frameVertices[frame - 1]; - float[] nextVertices = frameVertices[frame]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame - 1, 1 - (time - frameTime) / (frames[frame - 1] - frameTime)); - - if (alpha == 1) { - // Vertex positions or deform offsets, no alpha. - for (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - vertices[i] = prev + (nextVertices[i] - prev) * percent; - } - } else if (pose == MixPose.Setup) { - VertexAttachment vertexAttachment = (VertexAttachment)slotAttachment; - if (vertexAttachment.bones == null) { - // Unweighted vertex positions, with alpha. - var setupVertices = vertexAttachment.vertices; - for (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i], setup = setupVertices[i]; - vertices[i] = setup + (prev + (nextVertices[i] - prev) * percent - setup) * alpha; - } - } else { - // Weighted deform offsets, with alpha. - for (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - vertices[i] = (prev + (nextVertices[i] - prev) * percent) * alpha; - } - } - } else { - // Vertex positions or deform offsets, with alpha. - for (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - vertices[i] += (prev + (nextVertices[i] - prev) * percent - vertices[i]) * alpha; - } - } - } - } - - public class EventTimeline : Timeline { - internal float[] frames; - private Event[] events; - - public float[] Frames { get { return frames; } set { frames = value; } } // time, ... - public Event[] Events { get { return events; } set { events = value; } } - public int FrameCount { get { return frames.Length; } } - - public int PropertyId { - get { return ((int)TimelineType.Event << 24); } - } - - public EventTimeline (int frameCount) { - frames = new float[frameCount]; - events = new Event[frameCount]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, Event e) { - frames[frameIndex] = e.Time; - events[frameIndex] = e; - } - - /// Fires events for frames > lastTime and <= time. - public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { - if (firedEvents == null) return; - float[] frames = this.frames; - int frameCount = frames.Length; - - if (lastTime > time) { // Fire events after last time for looped animations. - Apply(skeleton, lastTime, int.MaxValue, firedEvents, alpha, pose, direction); - lastTime = -1f; - } else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame. - return; - if (time < frames[0]) return; // Time is before first frame. - - int frame; - if (lastTime < frames[0]) - frame = 0; - else { - frame = Animation.BinarySearch(frames, lastTime); - float frameTime = frames[frame]; - while (frame > 0) { // Fire multiple events with the same frame. - if (frames[frame - 1] != frameTime) break; - frame--; - } - } - for (; frame < frameCount && time >= frames[frame]; frame++) - firedEvents.Add(events[frame]); - } - } - - public class DrawOrderTimeline : Timeline { - internal float[] frames; - private int[][] drawOrders; - - public float[] Frames { get { return frames; } set { frames = value; } } // time, ... - public int[][] DrawOrders { get { return drawOrders; } set { drawOrders = value; } } - public int FrameCount { get { return frames.Length; } } - - public int PropertyId { - get { return ((int)TimelineType.DrawOrder << 24); } - } - - public DrawOrderTimeline (int frameCount) { - frames = new float[frameCount]; - drawOrders = new int[frameCount][]; - } - - /// Sets the time and value of the specified keyframe. - /// May be null to use bind pose draw order. - public void SetFrame (int frameIndex, float time, int[] drawOrder) { - frames[frameIndex] = time; - drawOrders[frameIndex] = drawOrder; - } - - public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { - ExposedList drawOrder = skeleton.drawOrder; - ExposedList slots = skeleton.slots; - if (direction == MixDirection.Out && pose == MixPose.Setup) { - Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count); - return; - } - - float[] frames = this.frames; - if (time < frames[0]) { - if (pose == MixPose.Setup) Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count); - return; - } - - int frame; - if (time >= frames[frames.Length - 1]) // Time is after last frame. - frame = frames.Length - 1; - else - frame = Animation.BinarySearch(frames, time) - 1; - - int[] drawOrderToSetupIndex = drawOrders[frame]; - if (drawOrderToSetupIndex == null) { - drawOrder.Clear(); - for (int i = 0, n = slots.Count; i < n; i++) - drawOrder.Add(slots.Items[i]); - } else { - var drawOrderItems = drawOrder.Items; - var slotsItems = slots.Items; - for (int i = 0, n = drawOrderToSetupIndex.Length; i < n; i++) - drawOrderItems[i] = slotsItems[drawOrderToSetupIndex[i]]; - } - } - } - - public class IkConstraintTimeline : CurveTimeline { - public const int ENTRIES = 3; - private const int PREV_TIME = -3, PREV_MIX = -2, PREV_BEND_DIRECTION = -1; - private const int MIX = 1, BEND_DIRECTION = 2; - - internal int ikConstraintIndex; - internal float[] frames; - - public int IkConstraintIndex { get { return ikConstraintIndex; } set { ikConstraintIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, mix, bendDirection, ... - - override public int PropertyId { - get { return ((int)TimelineType.IkConstraint << 24) + ikConstraintIndex; } - } - - public IkConstraintTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - /// Sets the time, mix and bend direction of the specified keyframe. - public void SetFrame (int frameIndex, float time, float mix, int bendDirection) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + MIX] = mix; - frames[frameIndex + BEND_DIRECTION] = bendDirection; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { - IkConstraint constraint = skeleton.ikConstraints.Items[ikConstraintIndex]; - float[] frames = this.frames; - if (time < frames[0]) { - switch (pose) { - case MixPose.Setup: - constraint.mix = constraint.data.mix; - constraint.bendDirection = constraint.data.bendDirection; - return; - case MixPose.Current: - constraint.mix += (constraint.data.mix - constraint.mix) * alpha; - constraint.bendDirection = constraint.data.bendDirection; - return; - } - return; - } - - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - if (pose == MixPose.Setup) { - constraint.mix = constraint.data.mix + (frames[frames.Length + PREV_MIX] - constraint.data.mix) * alpha; - constraint.bendDirection = direction == MixDirection.Out ? constraint.data.bendDirection - : (int)frames[frames.Length + PREV_BEND_DIRECTION]; - } else { - constraint.mix += (frames[frames.Length + PREV_MIX] - constraint.mix) * alpha; - if (direction == MixDirection.In) constraint.bendDirection = (int)frames[frames.Length + PREV_BEND_DIRECTION]; - } - return; - } - - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - float mix = frames[frame + PREV_MIX]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - if (pose == MixPose.Setup) { - constraint.mix = constraint.data.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.data.mix) * alpha; - constraint.bendDirection = direction == MixDirection.Out ? constraint.data.bendDirection : (int)frames[frame + PREV_BEND_DIRECTION]; - } else { - constraint.mix += (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha; - if (direction == MixDirection.In) constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION]; - } - } - } - - public class TransformConstraintTimeline : CurveTimeline { - public const int ENTRIES = 5; - private const int PREV_TIME = -5, PREV_ROTATE = -4, PREV_TRANSLATE = -3, PREV_SCALE = -2, PREV_SHEAR = -1; - private const int ROTATE = 1, TRANSLATE = 2, SCALE = 3, SHEAR = 4; - - internal int transformConstraintIndex; - internal float[] frames; - - public int TransformConstraintIndex { get { return transformConstraintIndex; } set { transformConstraintIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, scale mix, shear mix, ... - - override public int PropertyId { - get { return ((int)TimelineType.TransformConstraint << 24) + transformConstraintIndex; } - } - - public TransformConstraintTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - public void SetFrame (int frameIndex, float time, float rotateMix, float translateMix, float scaleMix, float shearMix) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + ROTATE] = rotateMix; - frames[frameIndex + TRANSLATE] = translateMix; - frames[frameIndex + SCALE] = scaleMix; - frames[frameIndex + SHEAR] = shearMix; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { - TransformConstraint constraint = skeleton.transformConstraints.Items[transformConstraintIndex]; - float[] frames = this.frames; - if (time < frames[0]) { - var data = constraint.data; - switch (pose) { - case MixPose.Setup: - constraint.rotateMix = data.rotateMix; - constraint.translateMix = data.translateMix; - constraint.scaleMix = data.scaleMix; - constraint.shearMix = data.shearMix; - return; - case MixPose.Current: - constraint.rotateMix += (data.rotateMix - constraint.rotateMix) * alpha; - constraint.translateMix += (data.translateMix - constraint.translateMix) * alpha; - constraint.scaleMix += (data.scaleMix - constraint.scaleMix) * alpha; - constraint.shearMix += (data.shearMix - constraint.shearMix) * alpha; - return; - } - return; - } - - float rotate, translate, scale, shear; - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - int i = frames.Length; - rotate = frames[i + PREV_ROTATE]; - translate = frames[i + PREV_TRANSLATE]; - scale = frames[i + PREV_SCALE]; - shear = frames[i + PREV_SHEAR]; - } else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - rotate = frames[frame + PREV_ROTATE]; - translate = frames[frame + PREV_TRANSLATE]; - scale = frames[frame + PREV_SCALE]; - shear = frames[frame + PREV_SHEAR]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - rotate += (frames[frame + ROTATE] - rotate) * percent; - translate += (frames[frame + TRANSLATE] - translate) * percent; - scale += (frames[frame + SCALE] - scale) * percent; - shear += (frames[frame + SHEAR] - shear) * percent; - } - if (pose == MixPose.Setup) { - TransformConstraintData data = constraint.data; - constraint.rotateMix = data.rotateMix + (rotate - data.rotateMix) * alpha; - constraint.translateMix = data.translateMix + (translate - data.translateMix) * alpha; - constraint.scaleMix = data.scaleMix + (scale - data.scaleMix) * alpha; - constraint.shearMix = data.shearMix + (shear - data.shearMix) * alpha; - } else { - constraint.rotateMix += (rotate - constraint.rotateMix) * alpha; - constraint.translateMix += (translate - constraint.translateMix) * alpha; - constraint.scaleMix += (scale - constraint.scaleMix) * alpha; - constraint.shearMix += (shear - constraint.shearMix) * alpha; - } - } - } - - public class PathConstraintPositionTimeline : CurveTimeline { - public const int ENTRIES = 2; - protected const int PREV_TIME = -2, PREV_VALUE = -1; - protected const int VALUE = 1; - - internal int pathConstraintIndex; - internal float[] frames; - - override public int PropertyId { - get { return ((int)TimelineType.PathConstraintPosition << 24) + pathConstraintIndex; } - } - - public PathConstraintPositionTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - public int PathConstraintIndex { get { return pathConstraintIndex; } set { pathConstraintIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, position, ... - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, float value) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + VALUE] = value; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { - PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; - float[] frames = this.frames; - if (time < frames[0]) { - switch (pose) { - case MixPose.Setup: - constraint.position = constraint.data.position; - return; - case MixPose.Current: - constraint.position += (constraint.data.position - constraint.position) * alpha; - return; - } - return; - } - - float position; - if (time >= frames[frames.Length - ENTRIES]) // Time is after last frame. - position = frames[frames.Length + PREV_VALUE]; - else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - position = frames[frame + PREV_VALUE]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - position += (frames[frame + VALUE] - position) * percent; - } - if (pose == MixPose.Setup) - constraint.position = constraint.data.position + (position - constraint.data.position) * alpha; - else - constraint.position += (position - constraint.position) * alpha; - } - } - - public class PathConstraintSpacingTimeline : PathConstraintPositionTimeline { - override public int PropertyId { - get { return ((int)TimelineType.PathConstraintSpacing << 24) + pathConstraintIndex; } - } - - public PathConstraintSpacingTimeline (int frameCount) - : base(frameCount) { - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { - PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; - float[] frames = this.frames; - if (time < frames[0]) { - switch (pose) { - case MixPose.Setup: - constraint.spacing = constraint.data.spacing; - return; - case MixPose.Current: - constraint.spacing += (constraint.data.spacing - constraint.spacing) * alpha; - return; - } - return; - } - - float spacing; - if (time >= frames[frames.Length - ENTRIES]) // Time is after last frame. - spacing = frames[frames.Length + PREV_VALUE]; - else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - spacing = frames[frame + PREV_VALUE]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - spacing += (frames[frame + VALUE] - spacing) * percent; - } - - if (pose == MixPose.Setup) - constraint.spacing = constraint.data.spacing + (spacing - constraint.data.spacing) * alpha; - else - constraint.spacing += (spacing - constraint.spacing) * alpha; - } - } - - public class PathConstraintMixTimeline : CurveTimeline { - public const int ENTRIES = 3; - private const int PREV_TIME = -3, PREV_ROTATE = -2, PREV_TRANSLATE = -1; - private const int ROTATE = 1, TRANSLATE = 2; - - internal int pathConstraintIndex; - internal float[] frames; - - public int PathConstraintIndex { get { return pathConstraintIndex; } set { pathConstraintIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, ... - - override public int PropertyId { - get { return ((int)TimelineType.PathConstraintMix << 24) + pathConstraintIndex; } - } - - public PathConstraintMixTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - /// Sets the time and mixes of the specified keyframe. - public void SetFrame (int frameIndex, float time, float rotateMix, float translateMix) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + ROTATE] = rotateMix; - frames[frameIndex + TRANSLATE] = translateMix; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { - PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; - float[] frames = this.frames; - if (time < frames[0]) { - switch (pose) { - case MixPose.Setup: - constraint.rotateMix = constraint.data.rotateMix; - constraint.translateMix = constraint.data.translateMix; - return; - case MixPose.Current: - constraint.rotateMix += (constraint.data.rotateMix - constraint.rotateMix) * alpha; - constraint.translateMix += (constraint.data.translateMix - constraint.translateMix) * alpha; - return; - } - return; - } - - float rotate, translate; - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - rotate = frames[frames.Length + PREV_ROTATE]; - translate = frames[frames.Length + PREV_TRANSLATE]; - } else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - rotate = frames[frame + PREV_ROTATE]; - translate = frames[frame + PREV_TRANSLATE]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - rotate += (frames[frame + ROTATE] - rotate) * percent; - translate += (frames[frame + TRANSLATE] - translate) * percent; - } - - if (pose == MixPose.Setup) { - constraint.rotateMix = constraint.data.rotateMix + (rotate - constraint.data.rotateMix) * alpha; - constraint.translateMix = constraint.data.translateMix + (translate - constraint.data.translateMix) * alpha; - } else { - constraint.rotateMix += (rotate - constraint.rotateMix) * alpha; - constraint.translateMix += (translate - constraint.translateMix) * alpha; - } - } - } + +namespace Spine3_6_32 +{ + public class Animation + { + internal ExposedList timelines; + internal float duration; + internal String name; + + public string Name { get { return name; } } + public ExposedList Timelines { get { return timelines; } set { timelines = value; } } + public float Duration { get { return duration; } set { duration = value; } } + + public Animation(string name, ExposedList timelines, float duration) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + if (timelines == null) throw new ArgumentNullException("timelines", "timelines cannot be null."); + this.name = name; + this.timelines = timelines; + this.duration = duration; + } + + /// Applies all the animation's timelines to the specified skeleton. + /// + public void Apply(Skeleton skeleton, float lastTime, float time, bool loop, ExposedList events, float alpha, MixPose pose, MixDirection direction) + { + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + + if (loop && duration != 0) + { + time %= duration; + if (lastTime > 0) lastTime %= duration; + } + + ExposedList timelines = this.timelines; + for (int i = 0, n = timelines.Count; i < n; i++) + timelines.Items[i].Apply(skeleton, lastTime, time, events, alpha, pose, direction); + } + + /// After the first and before the last entry. + internal static int BinarySearch(float[] values, float target, int step) + { + int low = 0; + int high = values.Length / step - 2; + if (high == 0) return step; + int current = (int)((uint)high >> 1); + while (true) + { + if (values[(current + 1) * step] <= target) + low = current + 1; + else + high = current; + if (low == high) return (low + 1) * step; + current = (int)((uint)(low + high) >> 1); + } + } + + /// After the first and before the last entry. + internal static int BinarySearch(float[] values, float target) + { + int low = 0; + int high = values.Length - 2; + if (high == 0) return 1; + int current = (int)((uint)high >> 1); + while (true) + { + if (values[(current + 1)] <= target) + low = current + 1; + else + high = current; + if (low == high) return (low + 1); + current = (int)((uint)(low + high) >> 1); + } + } + + internal static int LinearSearch(float[] values, float target, int step) + { + for (int i = 0, last = values.Length - step; i <= last; i += step) + if (values[i] > target) return i; + return -1; + } + } + + public interface Timeline + { + /// Sets the value(s) for the specified time. + /// The skeleton the timeline is being applied to. This provides access to the bones, slots, and other skeleton components the timeline may change. + /// 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). + /// The time within the animation. Most timelines find the key before and the key after this time so they can interpolate between the keys. + /// If any events are fired, they are added to this list. Can be null to ignore firing events or if the timeline does not fire events. May be null. + /// 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). + /// Controls how mixing is applied when alpha is than 1. + /// Indicates whether the timeline is mixing in or out. Used by timelines which perform instant transitions such as DrawOrderTimeline and AttachmentTimeline. + void Apply(Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, MixPose pose, MixDirection direction); + int PropertyId { get; } + } + + /// + /// Controls how a timeline is mixed with the setup or current pose. + /// + public enum MixPose + { + /// The timeline value is mixed with the setup pose (the current pose is not used). + Setup, + /// The timeline value is mixed with the current pose. The setup pose is used as the timeline value before the first key, + /// except for timelines which perform instant transitions, such as DrawOrderTimeline or AttachmentTimeline. + Current, + /// The timeline value is mixed with the current pose. No change is made before the first key (the current pose is kept until the first key). + CurrentLayered + } + + /// + /// 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). + /// + public enum MixDirection + { + In, + Out + } + + internal enum TimelineType + { + Rotate = 0, Translate, Scale, Shear, // + Attachment, Color, Deform, // + Event, DrawOrder, // + IkConstraint, TransformConstraint, // + PathConstraintPosition, PathConstraintSpacing, PathConstraintMix, // + TwoColor + } + + /// Base class for frames that use an interpolation bezier curve. + abstract public class CurveTimeline : Timeline + { + protected const float LINEAR = 0, STEPPED = 1, BEZIER = 2; + protected const int BEZIER_SIZE = 10 * 2 - 1; + + private float[] curves; // type, x, y, ... + public int FrameCount { get { return curves.Length / BEZIER_SIZE + 1; } } + + public CurveTimeline(int frameCount) + { + if (frameCount <= 0) throw new ArgumentException("frameCount must be > 0: " + frameCount, "frameCount"); + curves = new float[(frameCount - 1) * BEZIER_SIZE]; + } + + abstract public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction); + + abstract public int PropertyId { get; } + + public void SetLinear(int frameIndex) + { + curves[frameIndex * BEZIER_SIZE] = LINEAR; + } + + public void SetStepped(int frameIndex) + { + curves[frameIndex * BEZIER_SIZE] = STEPPED; + } + + /// Sets the control handle positions for an interpolation bezier curve used to transition from this keyframe to the next. + /// cx1 and cx2 are from 0 to 1, representing the percent of time between the two keyframes. cy1 and cy2 are the percent of + /// the difference between the keyframe's values. + public void SetCurve(int frameIndex, float cx1, float cy1, float cx2, float cy2) + { + float tmpx = (-cx1 * 2 + cx2) * 0.03f, tmpy = (-cy1 * 2 + cy2) * 0.03f; + float dddfx = ((cx1 - cx2) * 3 + 1) * 0.006f, dddfy = ((cy1 - cy2) * 3 + 1) * 0.006f; + float ddfx = tmpx * 2 + dddfx, ddfy = tmpy * 2 + dddfy; + float dfx = cx1 * 0.3f + tmpx + dddfx * 0.16666667f, dfy = cy1 * 0.3f + tmpy + dddfy * 0.16666667f; + + int i = frameIndex * BEZIER_SIZE; + float[] curves = this.curves; + curves[i++] = BEZIER; + + float x = dfx, y = dfy; + for (int n = i + BEZIER_SIZE - 1; i < n; i += 2) + { + curves[i] = x; + curves[i + 1] = y; + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + x += dfx; + y += dfy; + } + } + + public float GetCurvePercent(int frameIndex, float percent) + { + percent = MathUtils.Clamp(percent, 0, 1); + float[] curves = this.curves; + int i = frameIndex * BEZIER_SIZE; + float type = curves[i]; + if (type == LINEAR) return percent; + if (type == STEPPED) return 0; + i++; + float x = 0; + for (int start = i, n = i + BEZIER_SIZE - 1; i < n; i += 2) + { + x = curves[i]; + if (x >= percent) + { + float prevX, prevY; + if (i == start) + { + prevX = 0; + prevY = 0; + } + else + { + prevX = curves[i - 2]; + prevY = curves[i - 1]; + } + return prevY + (curves[i + 1] - prevY) * (percent - prevX) / (x - prevX); + } + } + float y = curves[i - 1]; + return y + (1 - y) * (percent - x) / (1 - x); // Last point is 1,1. + } + public float GetCurveType(int frameIndex) + { + return curves[frameIndex * BEZIER_SIZE]; + } + } + + public class RotateTimeline : CurveTimeline + { + public const int ENTRIES = 2; + internal const int PREV_TIME = -2, PREV_ROTATION = -1; + internal const int ROTATION = 1; + + internal int boneIndex; + internal float[] frames; + + public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, angle, ... + + override public int PropertyId + { + get { return ((int)TimelineType.Rotate << 24) + boneIndex; } + } + + public RotateTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount << 1]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, float degrees) + { + frameIndex <<= 1; + frames[frameIndex] = time; + frames[frameIndex + ROTATION] = degrees; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + + float[] frames = this.frames; + if (time < frames[0]) + { + switch (pose) + { + case MixPose.Setup: + bone.rotation = bone.data.rotation; + return; + case MixPose.Current: + float rr = bone.data.rotation - bone.rotation; + rr -= (16384 - (int)(16384.499999999996 - rr / 360)) * 360; + bone.rotation += rr * alpha; + return; + } + return; + } + + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + if (pose == MixPose.Setup) + { + bone.rotation = bone.data.rotation + frames[frames.Length + PREV_ROTATION] * alpha; + } + else + { + float rr = bone.data.rotation + frames[frames.Length + PREV_ROTATION] - bone.rotation; + rr -= (16384 - (int)(16384.499999999996 - rr / 360)) * 360; // Wrap within -180 and 180. + bone.rotation += rr * alpha; + } + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + float prevRotation = frames[frame + PREV_ROTATION]; + float frameTime = frames[frame]; + float percent = GetCurvePercent((frame >> 1) - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + float r = frames[frame + ROTATION] - prevRotation; + r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; + r = prevRotation + r * percent; + if (pose == MixPose.Setup) + { + r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; + bone.rotation = bone.data.rotation + r * alpha; + } + else + { + r = bone.data.rotation + r - bone.rotation; + r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; + bone.rotation += r * alpha; + } + } + } + + public class TranslateTimeline : CurveTimeline + { + public const int ENTRIES = 3; + protected const int PREV_TIME = -3, PREV_X = -2, PREV_Y = -1; + protected const int X = 1, Y = 2; + + internal int boneIndex; + internal float[] frames; + + public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, value, value, ... + + override public int PropertyId + { + get { return ((int)TimelineType.Translate << 24) + boneIndex; } + } + + public TranslateTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, float x, float y) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + X] = x; + frames[frameIndex + Y] = y; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + + float[] frames = this.frames; + if (time < frames[0]) + { + switch (pose) + { + case MixPose.Setup: + bone.x = bone.data.x; + bone.y = bone.data.y; + return; + case MixPose.Current: + bone.x += (bone.data.x - bone.x) * alpha; + bone.y += (bone.data.y - bone.y) * alpha; + return; + } + return; + } + + float x, y; + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + x = frames[frames.Length + PREV_X]; + y = frames[frames.Length + PREV_Y]; + } + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + x = frames[frame + PREV_X]; + y = frames[frame + PREV_Y]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + x += (frames[frame + X] - x) * percent; + y += (frames[frame + Y] - y) * percent; + } + if (pose == MixPose.Setup) + { + bone.x = bone.data.x + x * alpha; + bone.y = bone.data.y + y * alpha; + } + else + { + bone.x += (bone.data.x + x - bone.x) * alpha; + bone.y += (bone.data.y + y - bone.y) * alpha; + } + } + } + + public class ScaleTimeline : TranslateTimeline + { + override public int PropertyId + { + get { return ((int)TimelineType.Scale << 24) + boneIndex; } + } + + public ScaleTimeline(int frameCount) + : base(frameCount) + { + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + + float[] frames = this.frames; + if (time < frames[0]) + { + switch (pose) + { + case MixPose.Setup: + bone.scaleX = bone.data.scaleX; + bone.scaleY = bone.data.scaleY; + return; + case MixPose.Current: + bone.scaleX += (bone.data.scaleX - bone.scaleX) * alpha; + bone.scaleY += (bone.data.scaleY - bone.scaleY) * alpha; + return; + } + return; + } + + float x, y; + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + x = frames[frames.Length + PREV_X] * bone.data.scaleX; + y = frames[frames.Length + PREV_Y] * bone.data.scaleY; + } + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + x = frames[frame + PREV_X]; + y = frames[frame + PREV_Y]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + x = (x + (frames[frame + X] - x) * percent) * bone.data.scaleX; + y = (y + (frames[frame + Y] - y) * percent) * bone.data.scaleY; + } + if (alpha == 1) + { + bone.scaleX = x; + bone.scaleY = y; + } + else + { + float bx, by; + if (pose == MixPose.Setup) + { + bx = bone.data.scaleX; + by = bone.data.scaleY; + } + else + { + bx = bone.scaleX; + by = bone.scaleY; + } + // Mixing out uses sign of setup or current pose, else use sign of key. + if (direction == MixDirection.Out) + { + x = Math.Abs(x) * Math.Sign(bx); + y = Math.Abs(y) * Math.Sign(by); + } + else + { + bx = Math.Abs(bx) * Math.Sign(x); + by = Math.Abs(by) * Math.Sign(y); + } + bone.scaleX = bx + (x - bx) * alpha; + bone.scaleY = by + (y - by) * alpha; + } + } + } + + public class ShearTimeline : TranslateTimeline + { + override public int PropertyId + { + get { return ((int)TimelineType.Shear << 24) + boneIndex; } + } + + public ShearTimeline(int frameCount) + : base(frameCount) + { + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + float[] frames = this.frames; + if (time < frames[0]) + { + switch (pose) + { + case MixPose.Setup: + bone.shearX = bone.data.shearX; + bone.shearY = bone.data.shearY; + return; + case MixPose.Current: + bone.shearX += (bone.data.shearX - bone.shearX) * alpha; + bone.shearY += (bone.data.shearY - bone.shearY) * alpha; + return; + } + return; + } + + float x, y; + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + x = frames[frames.Length + PREV_X]; + y = frames[frames.Length + PREV_Y]; + } + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + x = frames[frame + PREV_X]; + y = frames[frame + PREV_Y]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + x = x + (frames[frame + X] - x) * percent; + y = y + (frames[frame + Y] - y) * percent; + } + if (pose == MixPose.Setup) + { + bone.shearX = bone.data.shearX + x * alpha; + bone.shearY = bone.data.shearY + y * alpha; + } + else + { + bone.shearX += (bone.data.shearX + x - bone.shearX) * alpha; + bone.shearY += (bone.data.shearY + y - bone.shearY) * alpha; + } + } + } + + public class ColorTimeline : CurveTimeline + { + public const int ENTRIES = 5; + protected const int PREV_TIME = -5, PREV_R = -4, PREV_G = -3, PREV_B = -2, PREV_A = -1; + protected const int R = 1, G = 2, B = 3, A = 4; + + internal int slotIndex; + internal float[] frames; + + public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, r, g, b, a, ... + + override public int PropertyId + { + get { return ((int)TimelineType.Color << 24) + slotIndex; } + } + + public ColorTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, float r, float g, float b, float a) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + R] = r; + frames[frameIndex + G] = g; + frames[frameIndex + B] = b; + frames[frameIndex + A] = a; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) + { + Slot slot = skeleton.slots.Items[slotIndex]; + float[] frames = this.frames; + if (time < frames[0]) + { + var slotData = slot.data; + switch (pose) + { + case MixPose.Setup: + slot.r = slotData.r; + slot.g = slotData.g; + slot.b = slotData.b; + slot.a = slotData.a; + return; + case MixPose.Current: + slot.r += (slot.r - slotData.r) * alpha; + slot.g += (slot.g - slotData.g) * alpha; + slot.b += (slot.b - slotData.b) * alpha; + slot.a += (slot.a - slotData.a) * alpha; + return; + } + return; + } + + float r, g, b, a; + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + int i = frames.Length; + r = frames[i + PREV_R]; + g = frames[i + PREV_G]; + b = frames[i + PREV_B]; + a = frames[i + PREV_A]; + } + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + r = frames[frame + PREV_R]; + g = frames[frame + PREV_G]; + b = frames[frame + PREV_B]; + a = frames[frame + PREV_A]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + r += (frames[frame + R] - r) * percent; + g += (frames[frame + G] - g) * percent; + b += (frames[frame + B] - b) * percent; + a += (frames[frame + A] - a) * percent; + } + if (alpha == 1) + { + slot.r = r; + slot.g = g; + slot.b = b; + slot.a = a; + } + else + { + float br, bg, bb, ba; + if (pose == MixPose.Setup) + { + br = slot.data.r; + bg = slot.data.g; + bb = slot.data.b; + ba = slot.data.a; + } + else + { + br = slot.r; + bg = slot.g; + bb = slot.b; + ba = slot.a; + } + slot.r = br + ((r - br) * alpha); + slot.g = bg + ((g - bg) * alpha); + slot.b = bb + ((b - bb) * alpha); + slot.a = ba + ((a - ba) * alpha); + } + } + } + + public class TwoColorTimeline : CurveTimeline + { + public const int ENTRIES = 8; + protected const int PREV_TIME = -8, PREV_R = -7, PREV_G = -6, PREV_B = -5, PREV_A = -4; + protected const int PREV_R2 = -3, PREV_G2 = -2, PREV_B2 = -1; + protected const int R = 1, G = 2, B = 3, A = 4, R2 = 5, G2 = 6, B2 = 7; + + internal float[] frames; // time, r, g, b, a, r2, g2, b2, ... + public float[] Frames { get { return frames; } } + + internal int slotIndex; + public int SlotIndex + { + get { return slotIndex; } + set + { + if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0."); + slotIndex = value; + } + } + + override public int PropertyId + { + get { return ((int)TimelineType.TwoColor << 24) + slotIndex; } + } + + public TwoColorTimeline(int frameCount) : + base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, float r, float g, float b, float a, float r2, float g2, float b2) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + R] = r; + frames[frameIndex + G] = g; + frames[frameIndex + B] = b; + frames[frameIndex + A] = a; + frames[frameIndex + R2] = r2; + frames[frameIndex + G2] = g2; + frames[frameIndex + B2] = b2; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) + { + Slot slot = skeleton.slots.Items[slotIndex]; + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + var slotData = slot.data; + switch (pose) + { + case MixPose.Setup: + // slot.color.set(slot.data.color); + // slot.darkColor.set(slot.data.darkColor); + slot.r = slotData.r; + slot.g = slotData.g; + slot.b = slotData.b; + slot.a = slotData.a; + slot.r2 = slotData.r2; + slot.g2 = slotData.g2; + slot.b2 = slotData.b2; + return; + case MixPose.Current: + slot.r += (slot.r - slotData.r) * alpha; + slot.g += (slot.g - slotData.g) * alpha; + slot.b += (slot.b - slotData.b) * alpha; + slot.a += (slot.a - slotData.a) * alpha; + slot.r2 += (slot.r2 - slotData.r2) * alpha; + slot.g2 += (slot.g2 - slotData.g2) * alpha; + slot.b2 += (slot.b2 - slotData.b2) * alpha; + return; + } + return; + } + + float r, g, b, a, r2, g2, b2; + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + int i = frames.Length; + r = frames[i + PREV_R]; + g = frames[i + PREV_G]; + b = frames[i + PREV_B]; + a = frames[i + PREV_A]; + r2 = frames[i + PREV_R2]; + g2 = frames[i + PREV_G2]; + b2 = frames[i + PREV_B2]; + } + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + r = frames[frame + PREV_R]; + g = frames[frame + PREV_G]; + b = frames[frame + PREV_B]; + a = frames[frame + PREV_A]; + r2 = frames[frame + PREV_R2]; + g2 = frames[frame + PREV_G2]; + b2 = frames[frame + PREV_B2]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + r += (frames[frame + R] - r) * percent; + g += (frames[frame + G] - g) * percent; + b += (frames[frame + B] - b) * percent; + a += (frames[frame + A] - a) * percent; + r2 += (frames[frame + R2] - r2) * percent; + g2 += (frames[frame + G2] - g2) * percent; + b2 += (frames[frame + B2] - b2) * percent; + } + if (alpha == 1) + { + slot.r = r; + slot.g = g; + slot.b = b; + slot.a = a; + slot.r2 = r2; + slot.g2 = g2; + slot.b2 = b2; + } + else + { + float br, bg, bb, ba, br2, bg2, bb2; + if (pose == MixPose.Setup) + { + br = slot.data.r; + bg = slot.data.g; + bb = slot.data.b; + ba = slot.data.a; + br2 = slot.data.r2; + bg2 = slot.data.g2; + bb2 = slot.data.b2; + } + else + { + br = slot.r; + bg = slot.g; + bb = slot.b; + ba = slot.a; + br2 = slot.r2; + bg2 = slot.g2; + bb2 = slot.b2; + } + slot.r = br + ((r - br) * alpha); + slot.g = bg + ((g - bg) * alpha); + slot.b = bb + ((b - bb) * alpha); + slot.a = ba + ((a - ba) * alpha); + slot.r2 = br2 + ((r2 - br2) * alpha); + slot.g2 = bg2 + ((g2 - bg2) * alpha); + slot.b2 = bb2 + ((b2 - bb2) * alpha); + } + } + + } + + public class AttachmentTimeline : Timeline + { + internal int slotIndex; + internal float[] frames; + private String[] attachmentNames; + + public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, ... + public String[] AttachmentNames { get { return attachmentNames; } set { attachmentNames = value; } } + public int FrameCount { get { return frames.Length; } } + + public int PropertyId + { + get { return ((int)TimelineType.Attachment << 24) + slotIndex; } + } + + public AttachmentTimeline(int frameCount) + { + frames = new float[frameCount]; + attachmentNames = new String[frameCount]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, String attachmentName) + { + frames[frameIndex] = time; + attachmentNames[frameIndex] = attachmentName; + } + + public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) + { + string attachmentName; + Slot slot = skeleton.slots.Items[slotIndex]; + if (direction == MixDirection.Out && pose == MixPose.Setup) + { + attachmentName = slot.data.attachmentName; + slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); + return; + } + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + if (pose == MixPose.Setup) + { + attachmentName = slot.data.attachmentName; + slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); + } + return; + } + + int frameIndex; + if (time >= frames[frames.Length - 1]) // Time is after last frame. + frameIndex = frames.Length - 1; + else + frameIndex = Animation.BinarySearch(frames, time, 1) - 1; + + attachmentName = attachmentNames[frameIndex]; + slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); + } + } + + public class DeformTimeline : CurveTimeline + { + internal int slotIndex; + internal float[] frames; + internal float[][] frameVertices; + internal VertexAttachment attachment; + + public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, ... + public float[][] Vertices { get { return frameVertices; } set { frameVertices = value; } } + public VertexAttachment Attachment { get { return attachment; } set { attachment = value; } } + + override public int PropertyId + { + get { return ((int)TimelineType.Deform << 24) + attachment.id + slotIndex; } + } + + public DeformTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount]; + frameVertices = new float[frameCount][]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, float[] vertices) + { + frames[frameIndex] = time; + frameVertices[frameIndex] = vertices; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) + { + Slot slot = skeleton.slots.Items[slotIndex]; + VertexAttachment slotAttachment = slot.attachment as VertexAttachment; + if (slotAttachment == null || !slotAttachment.ApplyDeform(attachment)) return; + + var verticesArray = slot.attachmentVertices; + float[][] frameVertices = this.frameVertices; + int vertexCount = frameVertices[0].Length; + if (verticesArray.Count != vertexCount && pose != MixPose.Setup) alpha = 1; // Don't mix from uninitialized slot vertices. + // verticesArray.SetSize(vertexCount) // Ensure size and preemptively set count. + if (verticesArray.Capacity < vertexCount) verticesArray.Capacity = vertexCount; + verticesArray.Count = vertexCount; + float[] vertices = verticesArray.Items; + + float[] frames = this.frames; + if (time < frames[0]) + { + switch (pose) + { + case MixPose.Setup: + verticesArray.Clear(); + return; + case MixPose.Current: + alpha = 1 - alpha; + for (int i = 0; i < vertexCount; i++) + vertices[i] *= alpha; + + return; + } + return; + } + + + + if (time >= frames[frames.Length - 1]) + { // Time is after last frame. + float[] lastVertices = frameVertices[frames.Length - 1]; + if (alpha == 1) + { + // Vertex positions or deform offsets, no alpha. + Array.Copy(lastVertices, 0, vertices, 0, vertexCount); + } + else if (pose == MixPose.Setup) + { + VertexAttachment vertexAttachment = slotAttachment; + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions, with alpha. + float[] setupVertices = vertexAttachment.vertices; + for (int i = 0; i < vertexCount; i++) + { + float setup = setupVertices[i]; + vertices[i] = setup + (lastVertices[i] - setup) * alpha; + } + } + else + { + // Weighted deform offsets, with alpha. + for (int i = 0; i < vertexCount; i++) + vertices[i] = lastVertices[i] * alpha; + } + } + else + { + // Vertex positions or deform offsets, with alpha. + for (int i = 0; i < vertexCount; i++) + vertices[i] += (lastVertices[i] - vertices[i]) * alpha; + } + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time); + float[] prevVertices = frameVertices[frame - 1]; + float[] nextVertices = frameVertices[frame]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame - 1, 1 - (time - frameTime) / (frames[frame - 1] - frameTime)); + + if (alpha == 1) + { + // Vertex positions or deform offsets, no alpha. + for (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i]; + vertices[i] = prev + (nextVertices[i] - prev) * percent; + } + } + else if (pose == MixPose.Setup) + { + VertexAttachment vertexAttachment = slotAttachment; + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions, with alpha. + var setupVertices = vertexAttachment.vertices; + for (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i], setup = setupVertices[i]; + vertices[i] = setup + (prev + (nextVertices[i] - prev) * percent - setup) * alpha; + } + } + else + { + // Weighted deform offsets, with alpha. + for (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i]; + vertices[i] = (prev + (nextVertices[i] - prev) * percent) * alpha; + } + } + } + else + { + // Vertex positions or deform offsets, with alpha. + for (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i]; + vertices[i] += (prev + (nextVertices[i] - prev) * percent - vertices[i]) * alpha; + } + } + } + } + + public class EventTimeline : Timeline + { + internal float[] frames; + private Event[] events; + + public float[] Frames { get { return frames; } set { frames = value; } } // time, ... + public Event[] Events { get { return events; } set { events = value; } } + public int FrameCount { get { return frames.Length; } } + + public int PropertyId + { + get { return ((int)TimelineType.Event << 24); } + } + + public EventTimeline(int frameCount) + { + frames = new float[frameCount]; + events = new Event[frameCount]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, Event e) + { + frames[frameIndex] = e.Time; + events[frameIndex] = e; + } + + /// Fires events for frames > lastTime and <= time. + public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) + { + if (firedEvents == null) return; + float[] frames = this.frames; + int frameCount = frames.Length; + + if (lastTime > time) + { // Fire events after last time for looped animations. + Apply(skeleton, lastTime, int.MaxValue, firedEvents, alpha, pose, direction); + lastTime = -1f; + } + else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame. + return; + if (time < frames[0]) return; // Time is before first frame. + + int frame; + if (lastTime < frames[0]) + frame = 0; + else + { + frame = Animation.BinarySearch(frames, lastTime); + float frameTime = frames[frame]; + while (frame > 0) + { // Fire multiple events with the same frame. + if (frames[frame - 1] != frameTime) break; + frame--; + } + } + for (; frame < frameCount && time >= frames[frame]; frame++) + firedEvents.Add(events[frame]); + } + } + + public class DrawOrderTimeline : Timeline + { + internal float[] frames; + private int[][] drawOrders; + + public float[] Frames { get { return frames; } set { frames = value; } } // time, ... + public int[][] DrawOrders { get { return drawOrders; } set { drawOrders = value; } } + public int FrameCount { get { return frames.Length; } } + + public int PropertyId + { + get { return ((int)TimelineType.DrawOrder << 24); } + } + + public DrawOrderTimeline(int frameCount) + { + frames = new float[frameCount]; + drawOrders = new int[frameCount][]; + } + + /// Sets the time and value of the specified keyframe. + /// May be null to use bind pose draw order. + public void SetFrame(int frameIndex, float time, int[] drawOrder) + { + frames[frameIndex] = time; + drawOrders[frameIndex] = drawOrder; + } + + public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) + { + ExposedList drawOrder = skeleton.drawOrder; + ExposedList slots = skeleton.slots; + if (direction == MixDirection.Out && pose == MixPose.Setup) + { + Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count); + return; + } + + float[] frames = this.frames; + if (time < frames[0]) + { + if (pose == MixPose.Setup) Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count); + return; + } + + int frame; + if (time >= frames[frames.Length - 1]) // Time is after last frame. + frame = frames.Length - 1; + else + frame = Animation.BinarySearch(frames, time) - 1; + + int[] drawOrderToSetupIndex = drawOrders[frame]; + if (drawOrderToSetupIndex == null) + { + drawOrder.Clear(); + for (int i = 0, n = slots.Count; i < n; i++) + drawOrder.Add(slots.Items[i]); + } + else + { + var drawOrderItems = drawOrder.Items; + var slotsItems = slots.Items; + for (int i = 0, n = drawOrderToSetupIndex.Length; i < n; i++) + drawOrderItems[i] = slotsItems[drawOrderToSetupIndex[i]]; + } + } + } + + public class IkConstraintTimeline : CurveTimeline + { + public const int ENTRIES = 3; + private const int PREV_TIME = -3, PREV_MIX = -2, PREV_BEND_DIRECTION = -1; + private const int MIX = 1, BEND_DIRECTION = 2; + + internal int ikConstraintIndex; + internal float[] frames; + + public int IkConstraintIndex { get { return ikConstraintIndex; } set { ikConstraintIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, mix, bendDirection, ... + + override public int PropertyId + { + get { return ((int)TimelineType.IkConstraint << 24) + ikConstraintIndex; } + } + + public IkConstraintTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + /// Sets the time, mix and bend direction of the specified keyframe. + public void SetFrame(int frameIndex, float time, float mix, int bendDirection) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + MIX] = mix; + frames[frameIndex + BEND_DIRECTION] = bendDirection; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) + { + IkConstraint constraint = skeleton.ikConstraints.Items[ikConstraintIndex]; + float[] frames = this.frames; + if (time < frames[0]) + { + switch (pose) + { + case MixPose.Setup: + constraint.mix = constraint.data.mix; + constraint.bendDirection = constraint.data.bendDirection; + return; + case MixPose.Current: + constraint.mix += (constraint.data.mix - constraint.mix) * alpha; + constraint.bendDirection = constraint.data.bendDirection; + return; + } + return; + } + + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + if (pose == MixPose.Setup) + { + constraint.mix = constraint.data.mix + (frames[frames.Length + PREV_MIX] - constraint.data.mix) * alpha; + constraint.bendDirection = direction == MixDirection.Out ? constraint.data.bendDirection + : (int)frames[frames.Length + PREV_BEND_DIRECTION]; + } + else + { + constraint.mix += (frames[frames.Length + PREV_MIX] - constraint.mix) * alpha; + if (direction == MixDirection.In) constraint.bendDirection = (int)frames[frames.Length + PREV_BEND_DIRECTION]; + } + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + float mix = frames[frame + PREV_MIX]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + if (pose == MixPose.Setup) + { + constraint.mix = constraint.data.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.data.mix) * alpha; + constraint.bendDirection = direction == MixDirection.Out ? constraint.data.bendDirection : (int)frames[frame + PREV_BEND_DIRECTION]; + } + else + { + constraint.mix += (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha; + if (direction == MixDirection.In) constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION]; + } + } + } + + public class TransformConstraintTimeline : CurveTimeline + { + public const int ENTRIES = 5; + private const int PREV_TIME = -5, PREV_ROTATE = -4, PREV_TRANSLATE = -3, PREV_SCALE = -2, PREV_SHEAR = -1; + private const int ROTATE = 1, TRANSLATE = 2, SCALE = 3, SHEAR = 4; + + internal int transformConstraintIndex; + internal float[] frames; + + public int TransformConstraintIndex { get { return transformConstraintIndex; } set { transformConstraintIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, scale mix, shear mix, ... + + override public int PropertyId + { + get { return ((int)TimelineType.TransformConstraint << 24) + transformConstraintIndex; } + } + + public TransformConstraintTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + public void SetFrame(int frameIndex, float time, float rotateMix, float translateMix, float scaleMix, float shearMix) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + ROTATE] = rotateMix; + frames[frameIndex + TRANSLATE] = translateMix; + frames[frameIndex + SCALE] = scaleMix; + frames[frameIndex + SHEAR] = shearMix; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) + { + TransformConstraint constraint = skeleton.transformConstraints.Items[transformConstraintIndex]; + float[] frames = this.frames; + if (time < frames[0]) + { + var data = constraint.data; + switch (pose) + { + case MixPose.Setup: + constraint.rotateMix = data.rotateMix; + constraint.translateMix = data.translateMix; + constraint.scaleMix = data.scaleMix; + constraint.shearMix = data.shearMix; + return; + case MixPose.Current: + constraint.rotateMix += (data.rotateMix - constraint.rotateMix) * alpha; + constraint.translateMix += (data.translateMix - constraint.translateMix) * alpha; + constraint.scaleMix += (data.scaleMix - constraint.scaleMix) * alpha; + constraint.shearMix += (data.shearMix - constraint.shearMix) * alpha; + return; + } + return; + } + + float rotate, translate, scale, shear; + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + int i = frames.Length; + rotate = frames[i + PREV_ROTATE]; + translate = frames[i + PREV_TRANSLATE]; + scale = frames[i + PREV_SCALE]; + shear = frames[i + PREV_SHEAR]; + } + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + rotate = frames[frame + PREV_ROTATE]; + translate = frames[frame + PREV_TRANSLATE]; + scale = frames[frame + PREV_SCALE]; + shear = frames[frame + PREV_SHEAR]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + rotate += (frames[frame + ROTATE] - rotate) * percent; + translate += (frames[frame + TRANSLATE] - translate) * percent; + scale += (frames[frame + SCALE] - scale) * percent; + shear += (frames[frame + SHEAR] - shear) * percent; + } + if (pose == MixPose.Setup) + { + TransformConstraintData data = constraint.data; + constraint.rotateMix = data.rotateMix + (rotate - data.rotateMix) * alpha; + constraint.translateMix = data.translateMix + (translate - data.translateMix) * alpha; + constraint.scaleMix = data.scaleMix + (scale - data.scaleMix) * alpha; + constraint.shearMix = data.shearMix + (shear - data.shearMix) * alpha; + } + else + { + constraint.rotateMix += (rotate - constraint.rotateMix) * alpha; + constraint.translateMix += (translate - constraint.translateMix) * alpha; + constraint.scaleMix += (scale - constraint.scaleMix) * alpha; + constraint.shearMix += (shear - constraint.shearMix) * alpha; + } + } + } + + public class PathConstraintPositionTimeline : CurveTimeline + { + public const int ENTRIES = 2; + protected const int PREV_TIME = -2, PREV_VALUE = -1; + protected const int VALUE = 1; + + internal int pathConstraintIndex; + internal float[] frames; + + override public int PropertyId + { + get { return ((int)TimelineType.PathConstraintPosition << 24) + pathConstraintIndex; } + } + + public PathConstraintPositionTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + public int PathConstraintIndex { get { return pathConstraintIndex; } set { pathConstraintIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, position, ... + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, float value) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + VALUE] = value; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) + { + PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; + float[] frames = this.frames; + if (time < frames[0]) + { + switch (pose) + { + case MixPose.Setup: + constraint.position = constraint.data.position; + return; + case MixPose.Current: + constraint.position += (constraint.data.position - constraint.position) * alpha; + return; + } + return; + } + + float position; + if (time >= frames[frames.Length - ENTRIES]) // Time is after last frame. + position = frames[frames.Length + PREV_VALUE]; + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + position = frames[frame + PREV_VALUE]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + position += (frames[frame + VALUE] - position) * percent; + } + if (pose == MixPose.Setup) + constraint.position = constraint.data.position + (position - constraint.data.position) * alpha; + else + constraint.position += (position - constraint.position) * alpha; + } + } + + public class PathConstraintSpacingTimeline : PathConstraintPositionTimeline + { + override public int PropertyId + { + get { return ((int)TimelineType.PathConstraintSpacing << 24) + pathConstraintIndex; } + } + + public PathConstraintSpacingTimeline(int frameCount) + : base(frameCount) + { + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) + { + PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; + float[] frames = this.frames; + if (time < frames[0]) + { + switch (pose) + { + case MixPose.Setup: + constraint.spacing = constraint.data.spacing; + return; + case MixPose.Current: + constraint.spacing += (constraint.data.spacing - constraint.spacing) * alpha; + return; + } + return; + } + + float spacing; + if (time >= frames[frames.Length - ENTRIES]) // Time is after last frame. + spacing = frames[frames.Length + PREV_VALUE]; + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + spacing = frames[frame + PREV_VALUE]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + spacing += (frames[frame + VALUE] - spacing) * percent; + } + + if (pose == MixPose.Setup) + constraint.spacing = constraint.data.spacing + (spacing - constraint.data.spacing) * alpha; + else + constraint.spacing += (spacing - constraint.spacing) * alpha; + } + } + + public class PathConstraintMixTimeline : CurveTimeline + { + public const int ENTRIES = 3; + private const int PREV_TIME = -3, PREV_ROTATE = -2, PREV_TRANSLATE = -1; + private const int ROTATE = 1, TRANSLATE = 2; + + internal int pathConstraintIndex; + internal float[] frames; + + public int PathConstraintIndex { get { return pathConstraintIndex; } set { pathConstraintIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, ... + + override public int PropertyId + { + get { return ((int)TimelineType.PathConstraintMix << 24) + pathConstraintIndex; } + } + + public PathConstraintMixTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + /// Sets the time and mixes of the specified keyframe. + public void SetFrame(int frameIndex, float time, float rotateMix, float translateMix) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + ROTATE] = rotateMix; + frames[frameIndex + TRANSLATE] = translateMix; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) + { + PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; + float[] frames = this.frames; + if (time < frames[0]) + { + switch (pose) + { + case MixPose.Setup: + constraint.rotateMix = constraint.data.rotateMix; + constraint.translateMix = constraint.data.translateMix; + return; + case MixPose.Current: + constraint.rotateMix += (constraint.data.rotateMix - constraint.rotateMix) * alpha; + constraint.translateMix += (constraint.data.translateMix - constraint.translateMix) * alpha; + return; + } + return; + } + + float rotate, translate; + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + rotate = frames[frames.Length + PREV_ROTATE]; + translate = frames[frames.Length + PREV_TRANSLATE]; + } + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + rotate = frames[frame + PREV_ROTATE]; + translate = frames[frame + PREV_TRANSLATE]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + rotate += (frames[frame + ROTATE] - rotate) * percent; + translate += (frames[frame + TRANSLATE] - translate) * percent; + } + + if (pose == MixPose.Setup) + { + constraint.rotateMix = constraint.data.rotateMix + (rotate - constraint.data.rotateMix) * alpha; + constraint.translateMix = constraint.data.translateMix + (translate - constraint.data.translateMix) * alpha; + } + else + { + constraint.rotateMix += (rotate - constraint.rotateMix) * alpha; + constraint.translateMix += (translate - constraint.translateMix) * alpha; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/AnimationState.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/AnimationState.cs index 989a279..0f38a29 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/AnimationState.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/AnimationState.cs @@ -31,1026 +31,1148 @@ using System; using System.Collections.Generic; -namespace Spine3_6_32 { - public class AnimationState { - static readonly Animation EmptyAnimation = new Animation("", new ExposedList(), 0); - internal const int SUBSEQUENT = 0, FIRST = 1, DIP = 2, DIP_MIX = 3; - - private AnimationStateData data; - private readonly ExposedList tracks = new ExposedList(); - private readonly HashSet propertyIDs = new HashSet(); - private readonly ExposedList events = new ExposedList(); - private readonly EventQueue queue; - - private readonly ExposedList mixingTo = new ExposedList(); - private bool animationsChanged; - - private float timeScale = 1; - - Pool trackEntryPool = new Pool(); - - public AnimationStateData Data { get { return data; } } - /// A list of tracks that have animations, which may contain nulls. - public ExposedList Tracks { get { return tracks; } } - public float TimeScale { get { return timeScale; } set { timeScale = value; } } - - public delegate void TrackEntryDelegate (TrackEntry trackEntry); - public event TrackEntryDelegate Start, Interrupt, End, Dispose, Complete; - - public delegate void TrackEntryEventDelegate (TrackEntry trackEntry, Event e); - public event TrackEntryEventDelegate Event; - - public AnimationState (AnimationStateData data) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - this.data = data; - this.queue = new EventQueue(this, HandleAnimationsChanged, trackEntryPool); - } - - void HandleAnimationsChanged () { - this.animationsChanged = true; - } - - /// - /// Increments the track entry times, setting queued animations as current if needed - /// delta time - public void Update (float delta) { - delta *= timeScale; - var tracksItems = tracks.Items; - for (int i = 0, n = tracks.Count; i < n; i++) { - TrackEntry current = tracksItems[i]; - if (current == null) continue; - - 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 = nextTime + (delta * next.timeScale); - current.trackTime += currentDelta; - SetCurrent(i, next, true); - while (next.mixingFrom != null) { - next.mixTime += currentDelta; - 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. - tracksItems[i] = null; - - queue.End(current); - DisposeNext(current); - continue; - } - if (current.mixingFrom != null && UpdateMixingFrom(current, delta)) { - // End mixing from entries once all have completed. - var from = current.mixingFrom; - current.mixingFrom = null; - while (from != null) { - queue.End(from); - from = from.mixingFrom; - } - } - - current.trackTime += currentDelta; - } - - queue.Drain(); - } - - /// Returns true when all mixing from entries are complete. - private bool UpdateMixingFrom (TrackEntry to, float delta) { - TrackEntry from = to.mixingFrom; - if (from == null) return true; - - bool finished = UpdateMixingFrom(from, delta); - - // Require mixTime > 0 to ensure the mixing from entry was applied at least once. - if (to.mixTime > 0 && (to.mixTime >= to.mixDuration || to.timeScale == 0)) { - if (from.totalAlpha == 0) { - to.mixingFrom = from.mixingFrom; - to.interruptAlpha = from.interruptAlpha; - queue.End(from); - } - return finished; - } - - from.animationLast = from.nextAnimationLast; - from.trackLast = from.nextTrackLast; - from.trackTime += delta * from.timeScale; - to.mixTime += delta * to.timeScale; - return false; - } - - - /// - /// 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. - public bool Apply (Skeleton skeleton) { - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - if (animationsChanged) AnimationsChanged(); - - var events = this.events; - - bool applied = false; - var tracksItems = tracks.Items; - for (int i = 0, m = tracks.Count; i < m; i++) { - TrackEntry current = tracksItems[i]; - if (current == null || current.delay > 0) continue; - applied = true; - MixPose currentPose = i == 0 ? MixPose.Current : MixPose.CurrentLayered; - - // Apply mixing from entries first. - float mix = current.alpha; - if (current.mixingFrom != null) - mix *= ApplyMixingFrom(current, skeleton, currentPose); - else if (current.trackTime >= current.trackEnd && current.next == null) // - mix = 0; // Set to setup pose the last time the entry will be applied. - - // Apply current entry. - float animationLast = current.animationLast, animationTime = current.AnimationTime; - int timelineCount = current.animation.timelines.Count; - var timelines = current.animation.timelines; - var timelinesItems = timelines.Items; - if (mix == 1) { - for (int ii = 0; ii < timelineCount; ii++) - timelinesItems[ii].Apply(skeleton, animationLast, animationTime, events, 1, MixPose.Setup, MixDirection.In); - } else { - var timelineData = current.timelineData.Items; - - bool firstFrame = current.timelinesRotation.Count == 0; - if (firstFrame) current.timelinesRotation.EnsureCapacity(timelines.Count << 1); - var timelinesRotation = current.timelinesRotation.Items; - - for (int ii = 0; ii < timelineCount; ii++) { - Timeline timeline = timelinesItems[ii]; - MixPose pose = timelineData[ii] >= FIRST ? MixPose.Setup : currentPose; - var rotateTimeline = timeline as RotateTimeline; - if (rotateTimeline != null) - ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, mix, pose, timelinesRotation, ii << 1, firstFrame); - else - timeline.Apply(skeleton, animationLast, animationTime, events, mix, pose, MixDirection.In); - } - } - QueueEvents(current, animationTime); - events.Clear(false); - current.nextAnimationLast = animationTime; - current.nextTrackLast = current.trackTime; - } - - queue.Drain(); - return applied; - } - - private float ApplyMixingFrom (TrackEntry to, Skeleton skeleton, MixPose currentPose) { - TrackEntry from = to.mixingFrom; - if (from.mixingFrom != null) ApplyMixingFrom(from, skeleton, currentPose); - - float mix; - if (to.mixDuration == 0) // Single frame mix to undo mixingFrom changes. - mix = 1; - else { - mix = to.mixTime / to.mixDuration; - if (mix > 1) mix = 1; - } - - var eventBuffer = mix < from.eventThreshold ? this.events : null; - bool attachments = mix < from.attachmentThreshold, drawOrder = mix < from.drawOrderThreshold; - float animationLast = from.animationLast, animationTime = from.AnimationTime; - var timelines = from.animation.timelines; - int timelineCount = timelines.Count; - var timelinesItems = timelines.Items; - var timelineData = from.timelineData.Items; - var timelineDipMix = from.timelineDipMix.Items; - - bool firstFrame = from.timelinesRotation.Count == 0; - if (firstFrame) from.timelinesRotation.Resize(timelines.Count << 1); // from.timelinesRotation.setSize - var timelinesRotation = from.timelinesRotation.Items; - - MixPose pose; - float alphaDip = from.alpha * to.interruptAlpha, alphaMix = alphaDip * (1 - mix), alpha; - from.totalAlpha = 0; - for (int i = 0; i < timelineCount; i++) { - Timeline timeline = timelinesItems[i]; - switch (timelineData[i]) { - case SUBSEQUENT: - if (!attachments && timeline is AttachmentTimeline) continue; - if (!drawOrder && timeline is DrawOrderTimeline) continue; - pose = currentPose; - alpha = alphaMix; - break; - case FIRST: - pose = MixPose.Setup; - alpha = alphaMix; - break; - case DIP: - pose = MixPose.Setup; - alpha = alphaDip; - break; - default: - pose = MixPose.Setup; - alpha = alphaDip; - var dipMix = timelineDipMix[i]; - alpha *= Math.Max(0, 1 - dipMix.mixTime / dipMix.mixDuration); - break; - } - from.totalAlpha += alpha; - var rotateTimeline = timeline as RotateTimeline; - if (rotateTimeline != null) { - ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, alpha, pose, timelinesRotation, i << 1, firstFrame); - } else { - timeline.Apply(skeleton, animationLast, animationTime, eventBuffer, alpha, pose, MixDirection.Out); - } - } - - if (to.mixDuration > 0) QueueEvents(from, animationTime); - this.events.Clear(false); - from.nextAnimationLast = animationTime; - from.nextTrackLast = from.trackTime; - - return mix; - } - - static private void ApplyRotateTimeline (RotateTimeline rotateTimeline, Skeleton skeleton, float time, float alpha, MixPose pose, - float[] timelinesRotation, int i, bool firstFrame) { - - if (firstFrame) timelinesRotation[i] = 0; - - if (alpha == 1) { - rotateTimeline.Apply(skeleton, 0, time, null, 1, pose, MixDirection.In); - return; - } - - Bone bone = skeleton.bones.Items[rotateTimeline.boneIndex]; - float[] frames = rotateTimeline.frames; - if (time < frames[0]) { - if (pose == MixPose.Setup) bone.rotation = bone.data.rotation; - return; - } - - float r2; - if (time >= frames[frames.Length - RotateTimeline.ENTRIES]) // Time is after last frame. - r2 = bone.data.rotation + frames[frames.Length + RotateTimeline.PREV_ROTATION]; - else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, RotateTimeline.ENTRIES); - float prevRotation = frames[frame + RotateTimeline.PREV_ROTATION]; - float frameTime = frames[frame]; - float percent = rotateTimeline.GetCurvePercent((frame >> 1) - 1, - 1 - (time - frameTime) / (frames[frame + RotateTimeline.PREV_TIME] - frameTime)); - - r2 = frames[frame + RotateTimeline.ROTATION] - prevRotation; - r2 -= (16384 - (int)(16384.499999999996 - r2 / 360)) * 360; - r2 = prevRotation + r2 * percent + bone.data.rotation; - r2 -= (16384 - (int)(16384.499999999996 - r2 / 360)) * 360; - } - - // Mix between rotations using the direction of the shortest route on the first frame while detecting crosses. - float r1 = pose == MixPose.Setup ? bone.data.rotation : bone.rotation; - float total, diff = r2 - r1; - if (diff == 0) { - total = timelinesRotation[i]; - } else { - diff -= (16384 - (int)(16384.499999999996 - diff / 360)) * 360; - float lastTotal, lastDiff; - if (firstFrame) { - lastTotal = 0; - lastDiff = diff; - } else { - lastTotal = timelinesRotation[i]; // Angle and direction of mix, including loops. - lastDiff = timelinesRotation[i + 1]; // Difference between bones. - } - bool current = diff > 0, dir = lastTotal >= 0; - // Detect cross at 0 (not 180). - if (Math.Sign(lastDiff) != Math.Sign(diff) && Math.Abs(lastDiff) <= 90) { - // A cross after a 360 rotation is a loop. - if (Math.Abs(lastTotal) > 180) lastTotal += 360 * Math.Sign(lastTotal); - dir = current; - } - total = diff + lastTotal - lastTotal % 360; // Store loops as part of lastTotal. - if (dir != current) total += 360 * Math.Sign(lastTotal); - timelinesRotation[i] = total; - } - timelinesRotation[i + 1] = diff; - r1 += total * alpha; - bone.rotation = r1 - (16384 - (int)(16384.499999999996 - r1 / 360)) * 360; - } - - private void QueueEvents (TrackEntry entry, float animationTime) { - float animationStart = entry.animationStart, animationEnd = entry.animationEnd; - float duration = animationEnd - animationStart; - float trackLastWrapped = entry.trackLast % duration; - - // Queue events before complete. - var events = this.events; - var eventsItems = events.Items; - int i = 0, n = events.Count; - for (; i < n; i++) { - var e = eventsItems[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. - if (entry.loop ? (trackLastWrapped > entry.trackTime % duration) - : (animationTime >= animationEnd && entry.animationLast < animationEnd)) { - queue.Complete(entry); - } - - // Queue events after complete. - for (; i < n; i++) { - Event e = eventsItems[i]; - if (e.time < animationStart) continue; // Discard events outside animation start/end. - queue.Event(entry, eventsItems[i]); - } - } - - /// - /// Removes all animations from all tracks, leaving skeletons in their previous pose. - /// It may be desired to use to mix the skeletons back to the setup pose, - /// rather than leaving them in their previous pose. - public void ClearTracks () { - bool oldDrainDisabled = queue.drainDisabled; - queue.drainDisabled = true; - for (int i = 0, n = tracks.Count; i < n; i++) { - ClearTrack(i); - } - tracks.Clear(); - queue.drainDisabled = oldDrainDisabled; - queue.Drain(); - } - - /// - /// Removes all animations from the tracks, leaving skeletons in their previous pose. - /// It may be desired to use to mix the skeletons back to the setup pose, - /// rather than leaving them in their previous pose. - public void ClearTrack (int trackIndex) { - if (trackIndex >= tracks.Count) return; - TrackEntry current = tracks.Items[trackIndex]; - if (current == null) return; - - queue.End(current); - - DisposeNext(current); - - TrackEntry entry = current; - while (true) { - TrackEntry from = entry.mixingFrom; - if (from == null) break; - queue.End(from); - entry.mixingFrom = null; - entry = from; - } - - tracks.Items[current.trackIndex] = null; - - queue.Drain(); - } - - private void SetCurrent (int index, TrackEntry current, bool interrupt) { - TrackEntry from = ExpandToIndex(index); - tracks.Items[index] = current; - - if (from != null) { - if (interrupt) queue.Interrupt(from); - current.mixingFrom = from; - current.mixTime = 0; - - // Store interrupted mix percentage. - if (from.mixingFrom != null && from.mixDuration > 0) - current.interruptAlpha *= Math.Min(1, from.mixTime / from.mixDuration); - - from.timelinesRotation.Clear(); // Reset rotation for mixing out, in case entry was mixed in. - } - - queue.Start(current); - } - - - /// Sets an animation by name. - public TrackEntry SetAnimation (int trackIndex, String animationName, bool loop) { - Animation animation = data.skeletonData.FindAnimation(animationName); - if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName"); - return SetAnimation(trackIndex, animation, loop); - } - - /// Sets the current animation for a track, discarding any queued animations. - /// 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 determines when the track is cleared. - /// - /// A track entry to allow further customization of animation playback. References to the track entry must not be kept - /// after . - public TrackEntry SetAnimation (int trackIndex, Animation animation, bool loop) { - if (animation == null) throw new ArgumentNullException("animation", "animation cannot be 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.Items[trackIndex] = current.mixingFrom; - queue.Interrupt(current); - queue.End(current); - DisposeNext(current); - current = current.mixingFrom; - interrupt = false; - } else { - DisposeNext(current); - } - } - TrackEntry entry = NewTrackEntry(trackIndex, animation, loop, current); - SetCurrent(trackIndex, entry, interrupt); - queue.Drain(); - return entry; - } - - /// Queues an animation by name. - /// - public TrackEntry AddAnimation (int trackIndex, String animationName, bool loop, float delay) { - Animation animation = data.skeletonData.FindAnimation(animationName); - if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName"); - return AddAnimation(trackIndex, animation, loop, 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 . - /// - /// 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. - /// - /// A track entry to allow further customization of animation playback. References to the track entry must not be kept - /// after - public TrackEntry AddAnimation (int trackIndex, Animation animation, bool loop, float delay) { - if (animation == null) throw new ArgumentNullException("animation", "animation cannot be 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(); - } else { - last.next = entry; - if (delay <= 0) { - float duration = last.animationEnd - last.animationStart; - if (duration != 0) - delay += duration * (1 + (int)(last.trackTime / duration)) - data.GetMix(last.animation, animation); - else - delay = 0; - } - } - - entry.delay = delay; - return entry; - } - - /// - /// Sets an empty animation for a track, discarding any queued animations, and mixes to it over the specified mix duration. - public TrackEntry SetEmptyAnimation (int trackIndex, float mixDuration) { - TrackEntry entry = SetAnimation(trackIndex, AnimationState.EmptyAnimation, false); - entry.mixDuration = mixDuration; - entry.trackEnd = mixDuration; - return entry; - } - - /// - /// 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. - /// - /// A track entry to allow further customization of animation playback. References to the track entry must not be kept after . - /// - /// Track number. - /// Mix duration. - /// 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. - public TrackEntry AddEmptyAnimation (int trackIndex, float mixDuration, float delay) { - if (delay <= 0) delay -= mixDuration; - TrackEntry entry = AddAnimation(trackIndex, AnimationState.EmptyAnimation, false, delay); - entry.mixDuration = mixDuration; - entry.trackEnd = mixDuration; - return entry; - } - - /// - /// Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix duration. - public void SetEmptyAnimations (float mixDuration) { - bool oldDrainDisabled = queue.drainDisabled; - queue.drainDisabled = true; - for (int i = 0, n = tracks.Count; i < n; i++) { - TrackEntry current = tracks.Items[i]; - if (current != null) SetEmptyAnimation(i, mixDuration); - } - queue.drainDisabled = oldDrainDisabled; - queue.Drain(); - } - - private TrackEntry ExpandToIndex (int index) { - if (index < tracks.Count) return tracks.Items[index]; - while (index >= tracks.Count) - tracks.Add(null); - return null; - } - - /// May be null. - private TrackEntry NewTrackEntry (int trackIndex, Animation animation, bool loop, TrackEntry last) { - TrackEntry entry = trackEntryPool.Obtain(); // Pooling - entry.trackIndex = trackIndex; - entry.animation = animation; - entry.loop = loop; - - entry.eventThreshold = 0; - entry.attachmentThreshold = 0; - entry.drawOrderThreshold = 0; - - entry.animationStart = 0; - entry.animationEnd = animation.Duration; - entry.animationLast = -1; - entry.nextAnimationLast = -1; - - entry.delay = 0; - entry.trackTime = 0; - entry.trackLast = -1; - entry.nextTrackLast = -1; - entry.trackEnd = float.MaxValue; // loop ? float.MaxValue : animation.Duration; - entry.timeScale = 1; - - entry.alpha = 1; - entry.interruptAlpha = 1; - entry.mixTime = 0; - entry.mixDuration = (last == null) ? 0 : data.GetMix(last.animation, animation); - return entry; - } - - private void DisposeNext (TrackEntry entry) { - TrackEntry next = entry.next; - while (next != null) { - queue.Dispose(next); - next = next.next; - } - entry.next = null; - } - - private void AnimationsChanged () { - animationsChanged = false; - - var propertyIDs = this.propertyIDs; - propertyIDs.Clear(); - var mixingTo = this.mixingTo; - - TrackEntry lastEntry = null; - var tracksItems = tracks.Items; - for (int i = 0, n = tracks.Count; i < n; i++) { - var entry = tracksItems[i]; - if (entry != null) { - entry.SetTimelineData(lastEntry, mixingTo, propertyIDs); - lastEntry = entry; - } - } - } - - /// The track entry for the animation currently playing on the track, or null if no animation is currently playing. - public TrackEntry GetCurrent (int trackIndex) { - return (trackIndex >= tracks.Count) ? null : tracks.Items[trackIndex]; - } - - override public String ToString () { - var buffer = new System.Text.StringBuilder(); - for (int i = 0, n = tracks.Count; i < n; i++) { - TrackEntry entry = tracks.Items[i]; - if (entry == null) continue; - if (buffer.Length > 0) buffer.Append(", "); - buffer.Append(entry.ToString()); - } - return buffer.Length == 0 ? "" : buffer.ToString(); - } - - internal void OnStart (TrackEntry entry) { if (Start != null) Start(entry); } - internal void OnInterrupt (TrackEntry entry) { if (Interrupt != null) Interrupt(entry); } - internal void OnEnd (TrackEntry entry) { if (End != null) End(entry); } - internal void OnDispose (TrackEntry entry) { if (Dispose != null) Dispose(entry); } - internal void OnComplete (TrackEntry entry) { if (Complete != null) Complete(entry); } - internal void OnEvent (TrackEntry entry, Event e) { if (Event != null) Event(entry, e); } - } - - /// State for the playback of an animation. - public class TrackEntry : Pool.IPoolable { - internal Animation animation; - - internal TrackEntry next, mixingFrom; - internal int trackIndex; - - internal bool loop; - internal float eventThreshold, attachmentThreshold, drawOrderThreshold; - internal float animationStart, animationEnd, animationLast, nextAnimationLast; - internal float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale = 1f; - internal float alpha, mixTime, mixDuration, interruptAlpha, totalAlpha; - internal readonly ExposedList timelineData = new ExposedList(); - internal readonly ExposedList timelineDipMix = new ExposedList(); - internal readonly ExposedList timelinesRotation = new ExposedList(); - - // IPoolable.Reset() - public void Reset () { - next = null; - mixingFrom = null; - animation = null; - timelineData.Clear(); - timelineDipMix.Clear(); - timelinesRotation.Clear(); - - Start = null; - Interrupt = null; - End = null; - Dispose = null; - Complete = null; - Event = null; - } - - /// May be null. - internal TrackEntry SetTimelineData (TrackEntry to, ExposedList mixingToArray, HashSet propertyIDs) { - if (to != null) mixingToArray.Add(to); - var lastEntry = mixingFrom != null ? mixingFrom.SetTimelineData(this, mixingToArray, propertyIDs) : this; - if (to != null) mixingToArray.RemoveAt(mixingToArray.Count - 1); // mixingToArray.pop(); - - var mixingTo = mixingToArray.Items; - int mixingToLast = mixingToArray.Count - 1; - var timelines = animation.timelines.Items; - int timelinesCount = animation.timelines.Count; - var timelineDataItems = timelineData.Resize(timelinesCount).Items; // timelineData.setSize(timelinesCount); - timelineDipMix.Clear(); - var timelineDipMixItems = timelineDipMix.Resize(timelinesCount).Items; //timelineDipMix.setSize(timelinesCount); - - // outer: - for (int i = 0; i < timelinesCount; i++) { - int id = timelines[i].PropertyId; - if (!propertyIDs.Add(id)) { - timelineDataItems[i] = AnimationState.SUBSEQUENT; - } else if (to == null || !to.HasTimeline(id)) { - timelineDataItems[i] = AnimationState.FIRST; - } else { - for (int ii = mixingToLast; ii >= 0; ii--) { - var entry = mixingTo[ii]; - if (!entry.HasTimeline(id)) { - if (entry.mixDuration > 0) { - timelineDataItems[i] = AnimationState.DIP_MIX; - timelineDipMixItems[i] = entry; - goto outer; // continue outer; - } - } - } - timelineDataItems[i] = AnimationState.DIP; - } - outer: {} - } - return lastEntry; - } - - bool HasTimeline (int id) { - var timelines = animation.timelines.Items; - for (int i = 0, n = animation.timelines.Count; i < n; i++) - if (timelines[i].PropertyId == id) return true; - return false; - } - - /// The index of the track where this entry is either current or queued. - public int TrackIndex { get { return trackIndex; } } - - /// The animation to apply for this track entry. - public Animation Animation { get { return animation; } } - - /// - /// If true, the animation will repeat. If false, it will not, instead its last frame is applied if played beyond its duration. - public bool Loop { get { return loop; } set { loop = value; } } - - /// - /// 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. - public float Delay { get { return delay; } set { delay = value; } } - - /// - /// Current time in seconds this track entry has been the current track entry. The track time determines - /// . The track time can be set to start the animation at a time other than 0, without affecting looping. - public float TrackTime { get { return trackTime; } set { trackTime = value; } } - - /// - /// 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 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 to mix the properties back to the - /// setup pose over time, rather than have it happen instantly. - /// - public float TrackEnd { get { return trackEnd; } set { trackEnd = value; } } - - /// - /// 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 to the same value to - /// prevent timeline keys before the start time from triggering. - /// - public float AnimationStart { get { return animationStart; } set { animationStart = value; } } - - /// - /// Seconds for the last frame of this animation. Non-looping animations won't play past this time. Looping animations will - /// loop back to at this time. Defaults to the animation duration. - public float AnimationEnd { get { return animationEnd; } set { animationEnd = value; } } - - /// - /// 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. - public float AnimationLast { - get { return animationLast; } - set { - animationLast = value; - nextAnimationLast = value; - } - } - - /// - /// Uses to compute the animation time between . and - /// . When the track time is 0, the animation time is equal to the animation start time. - /// - public float AnimationTime { - get { - if (loop) { - float duration = animationEnd - animationStart; - if (duration == 0) return animationStart; - return (trackTime % duration) + animationStart; - } - return Math.Min(trackTime + animationStart, animationEnd); - } - } - - /// - /// Multiplier for the delta time when the animation state is updated, causing time for this animation to play slower or - /// faster. Defaults to 1. - /// - public float TimeScale { get { return timeScale; } set { timeScale = value; } } - - /// - /// 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. - /// - public float Alpha { get { return alpha; } set { alpha = value; } } - - /// - /// 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. - public float EventThreshold { get { return eventThreshold; } set { eventThreshold = value; } } - - /// - /// 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. - public float AttachmentThreshold { get { return attachmentThreshold; } set { attachmentThreshold = value; } } - - /// - /// 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. - /// - public float DrawOrderThreshold { get { return drawOrderThreshold; } set { drawOrderThreshold = value; } } - - /// - /// The animation queued to start after this animation, or null. - public TrackEntry Next { get { return next; } } - - /// - /// Returns true if at least one loop has been completed. - public bool IsComplete { - get { return trackTime >= animationEnd - animationStart; } - } - - /// - /// Seconds from 0 to the mix duration when mixing from the previous animation to this animation. May be slightly more than - /// when the mix is complete. - public float MixTime { get { return mixTime; } set { mixTime = value; } } - - /// - /// Seconds for mixing from the previous animation to this animation. Defaults to the value provided by - /// 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 is next called. - /// - /// When using with a - /// delay less than or equal to 0, note the is set using the mix duration from the - /// - /// - /// - public float MixDuration { get { return mixDuration; } set { mixDuration = value; } } - - /// - /// 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 linked list. - public TrackEntry MixingFrom { get { return mixingFrom; } } - - public event AnimationState.TrackEntryDelegate Start, Interrupt, End, Dispose, Complete; - public event AnimationState.TrackEntryEventDelegate Event; - internal void OnStart () { if (Start != null) Start(this); } - internal void OnInterrupt () { if (Interrupt != null) Interrupt(this); } - internal void OnEnd () { if (End != null) End(this); } - internal void OnDispose () { if (Dispose != null) Dispose(this); } - internal void OnComplete () { if (Complete != null) Complete(this); } - internal void OnEvent (Event e) { if (Event != null) Event(this, e); } - - /// - /// 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 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. - public void ResetRotationDirections () { - timelinesRotation.Clear(); - } - - override public String ToString () { - return animation == null ? "" : animation.name; - } - } - - class EventQueue { - private readonly List eventQueueEntries = new List(); - public bool drainDisabled; - - private readonly AnimationState state; - private readonly Pool trackEntryPool; - public event Action AnimationsChanged; - - public EventQueue (AnimationState state, Action HandleAnimationsChanged, Pool trackEntryPool) { - this.state = state; - this.AnimationsChanged += HandleAnimationsChanged; - this.trackEntryPool = trackEntryPool; - } - - struct EventQueueEntry { - public EventType type; - public TrackEntry entry; - public Event e; - - public EventQueueEntry (EventType eventType, TrackEntry trackEntry, Event e = null) { - this.type = eventType; - this.entry = trackEntry; - this.e = e; - } - } - - enum EventType { - Start, Interrupt, End, Dispose, Complete, Event - } - - public void Start (TrackEntry entry) { - eventQueueEntries.Add(new EventQueueEntry(EventType.Start, entry)); - if (AnimationsChanged != null) AnimationsChanged(); - } - - public void Interrupt (TrackEntry entry) { - eventQueueEntries.Add(new EventQueueEntry(EventType.Interrupt, entry)); - } - - public void End (TrackEntry entry) { - eventQueueEntries.Add(new EventQueueEntry(EventType.End, entry)); - if (AnimationsChanged != null) AnimationsChanged(); - } - - public void Dispose (TrackEntry entry) { - eventQueueEntries.Add(new EventQueueEntry(EventType.Dispose, entry)); - } - - public void Complete (TrackEntry entry) { - eventQueueEntries.Add(new EventQueueEntry(EventType.Complete, entry)); - } - - public void Event (TrackEntry entry, Event e) { - eventQueueEntries.Add(new EventQueueEntry(EventType.Event, entry, e)); - } - - public void Drain () { - if (drainDisabled) return; - drainDisabled = true; - - var entries = this.eventQueueEntries; - AnimationState state = this.state; - - // Don't cache entries.Count so callbacks can queue their own events (eg, call SetAnimation in AnimationState_Complete). - for (int i = 0; i < entries.Count; i++) { - var queueEntry = entries[i]; - TrackEntry trackEntry = queueEntry.entry; - - switch (queueEntry.type) { - case EventType.Start: - trackEntry.OnStart(); - state.OnStart(trackEntry); - break; - case EventType.Interrupt: - trackEntry.OnInterrupt(); - state.OnInterrupt(trackEntry); - break; - case EventType.End: - trackEntry.OnEnd(); - state.OnEnd(trackEntry); - goto case EventType.Dispose; // Fall through. (C#) - case EventType.Dispose: - trackEntry.OnDispose(); - state.OnDispose(trackEntry); - trackEntryPool.Free(trackEntry); // Pooling - break; - case EventType.Complete: - trackEntry.OnComplete(); - state.OnComplete(trackEntry); - break; - case EventType.Event: - trackEntry.OnEvent(queueEntry.e); - state.OnEvent(trackEntry, queueEntry.e); - break; - } - } - eventQueueEntries.Clear(); - - drainDisabled = false; - } - - public void Clear () { - eventQueueEntries.Clear(); - } - } - - public class Pool where T : class, new() { - public readonly int max; - readonly Stack freeObjects; - - public int Count { get { return freeObjects.Count; } } - public int Peak { get; private set; } - - public Pool (int initialCapacity = 16, int max = int.MaxValue) { - freeObjects = new Stack(initialCapacity); - this.max = max; - } - - public T Obtain () { - return freeObjects.Count == 0 ? new T() : freeObjects.Pop(); - } - - public void Free (T obj) { - if (obj == null) throw new ArgumentNullException("obj", "obj cannot be null"); - if (freeObjects.Count < max) { - freeObjects.Push(obj); - Peak = Math.Max(Peak, freeObjects.Count); - } - Reset(obj); - } - -// protected void FreeAll (List objects) { -// if (objects == null) throw new ArgumentNullException("objects", "objects cannot be null."); -// var freeObjects = this.freeObjects; -// int max = this.max; -// for (int i = 0; i < objects.Count; i++) { -// T obj = objects[i]; -// if (obj == null) continue; -// if (freeObjects.Count < max) freeObjects.Push(obj); -// Reset(obj); -// } -// Peak = Math.Max(Peak, freeObjects.Count); -// } - - public void Clear () { - freeObjects.Clear(); - } - - protected void Reset (T obj) { - var poolable = obj as IPoolable; - if (poolable != null) poolable.Reset(); - } - - public interface IPoolable { - void Reset (); - } - } +namespace Spine3_6_32 +{ + public class AnimationState + { + static readonly Animation EmptyAnimation = new Animation("", new ExposedList(), 0); + internal const int SUBSEQUENT = 0, FIRST = 1, DIP = 2, DIP_MIX = 3; + + private AnimationStateData data; + private readonly ExposedList tracks = new ExposedList(); + private readonly HashSet propertyIDs = new HashSet(); + private readonly ExposedList events = new ExposedList(); + private readonly EventQueue queue; + + private readonly ExposedList mixingTo = new ExposedList(); + private bool animationsChanged; + + private float timeScale = 1; + + Pool trackEntryPool = new Pool(); + + public AnimationStateData Data { get { return data; } } + /// A list of tracks that have animations, which may contain nulls. + public ExposedList Tracks { get { return tracks; } } + public float TimeScale { get { return timeScale; } set { timeScale = value; } } + + public delegate void TrackEntryDelegate(TrackEntry trackEntry); + public event TrackEntryDelegate Start, Interrupt, End, Dispose, Complete; + + public delegate void TrackEntryEventDelegate(TrackEntry trackEntry, Event e); + public event TrackEntryEventDelegate Event; + + public AnimationState(AnimationStateData data) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + this.data = data; + this.queue = new EventQueue(this, HandleAnimationsChanged, trackEntryPool); + } + + void HandleAnimationsChanged() + { + this.animationsChanged = true; + } + + /// + /// Increments the track entry times, setting queued animations as current if needed + /// delta time + public void Update(float delta) + { + delta *= timeScale; + var tracksItems = tracks.Items; + for (int i = 0, n = tracks.Count; i < n; i++) + { + TrackEntry current = tracksItems[i]; + if (current == null) continue; + + 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 = nextTime + (delta * next.timeScale); + current.trackTime += currentDelta; + SetCurrent(i, next, true); + while (next.mixingFrom != null) + { + next.mixTime += currentDelta; + 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. + tracksItems[i] = null; + + queue.End(current); + DisposeNext(current); + continue; + } + if (current.mixingFrom != null && UpdateMixingFrom(current, delta)) + { + // End mixing from entries once all have completed. + var from = current.mixingFrom; + current.mixingFrom = null; + while (from != null) + { + queue.End(from); + from = from.mixingFrom; + } + } + + current.trackTime += currentDelta; + } + + queue.Drain(); + } + + /// Returns true when all mixing from entries are complete. + private bool UpdateMixingFrom(TrackEntry to, float delta) + { + TrackEntry from = to.mixingFrom; + if (from == null) return true; + + bool finished = UpdateMixingFrom(from, delta); + + // Require mixTime > 0 to ensure the mixing from entry was applied at least once. + if (to.mixTime > 0 && (to.mixTime >= to.mixDuration || to.timeScale == 0)) + { + if (from.totalAlpha == 0) + { + to.mixingFrom = from.mixingFrom; + to.interruptAlpha = from.interruptAlpha; + queue.End(from); + } + return finished; + } + + from.animationLast = from.nextAnimationLast; + from.trackLast = from.nextTrackLast; + from.trackTime += delta * from.timeScale; + to.mixTime += delta * to.timeScale; + return false; + } + + + /// + /// 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. + public bool Apply(Skeleton skeleton) + { + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + if (animationsChanged) AnimationsChanged(); + + var events = this.events; + + bool applied = false; + var tracksItems = tracks.Items; + for (int i = 0, m = tracks.Count; i < m; i++) + { + TrackEntry current = tracksItems[i]; + if (current == null || current.delay > 0) continue; + applied = true; + MixPose currentPose = i == 0 ? MixPose.Current : MixPose.CurrentLayered; + + // Apply mixing from entries first. + float mix = current.alpha; + if (current.mixingFrom != null) + mix *= ApplyMixingFrom(current, skeleton, currentPose); + else if (current.trackTime >= current.trackEnd && current.next == null) // + mix = 0; // Set to setup pose the last time the entry will be applied. + + // Apply current entry. + float animationLast = current.animationLast, animationTime = current.AnimationTime; + int timelineCount = current.animation.timelines.Count; + var timelines = current.animation.timelines; + var timelinesItems = timelines.Items; + if (mix == 1) + { + for (int ii = 0; ii < timelineCount; ii++) + timelinesItems[ii].Apply(skeleton, animationLast, animationTime, events, 1, MixPose.Setup, MixDirection.In); + } + else + { + var timelineData = current.timelineData.Items; + + bool firstFrame = current.timelinesRotation.Count == 0; + if (firstFrame) current.timelinesRotation.EnsureCapacity(timelines.Count << 1); + var timelinesRotation = current.timelinesRotation.Items; + + for (int ii = 0; ii < timelineCount; ii++) + { + Timeline timeline = timelinesItems[ii]; + MixPose pose = timelineData[ii] >= FIRST ? MixPose.Setup : currentPose; + var rotateTimeline = timeline as RotateTimeline; + if (rotateTimeline != null) + ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, mix, pose, timelinesRotation, ii << 1, firstFrame); + else + timeline.Apply(skeleton, animationLast, animationTime, events, mix, pose, MixDirection.In); + } + } + QueueEvents(current, animationTime); + events.Clear(false); + current.nextAnimationLast = animationTime; + current.nextTrackLast = current.trackTime; + } + + queue.Drain(); + return applied; + } + + private float ApplyMixingFrom(TrackEntry to, Skeleton skeleton, MixPose currentPose) + { + TrackEntry from = to.mixingFrom; + if (from.mixingFrom != null) ApplyMixingFrom(from, skeleton, currentPose); + + float mix; + if (to.mixDuration == 0) // Single frame mix to undo mixingFrom changes. + mix = 1; + else + { + mix = to.mixTime / to.mixDuration; + if (mix > 1) mix = 1; + } + + var eventBuffer = mix < from.eventThreshold ? this.events : null; + bool attachments = mix < from.attachmentThreshold, drawOrder = mix < from.drawOrderThreshold; + float animationLast = from.animationLast, animationTime = from.AnimationTime; + var timelines = from.animation.timelines; + int timelineCount = timelines.Count; + var timelinesItems = timelines.Items; + var timelineData = from.timelineData.Items; + var timelineDipMix = from.timelineDipMix.Items; + + bool firstFrame = from.timelinesRotation.Count == 0; + if (firstFrame) from.timelinesRotation.Resize(timelines.Count << 1); // from.timelinesRotation.setSize + var timelinesRotation = from.timelinesRotation.Items; + + MixPose pose; + float alphaDip = from.alpha * to.interruptAlpha, alphaMix = alphaDip * (1 - mix), alpha; + from.totalAlpha = 0; + for (int i = 0; i < timelineCount; i++) + { + Timeline timeline = timelinesItems[i]; + switch (timelineData[i]) + { + case SUBSEQUENT: + if (!attachments && timeline is AttachmentTimeline) continue; + if (!drawOrder && timeline is DrawOrderTimeline) continue; + pose = currentPose; + alpha = alphaMix; + break; + case FIRST: + pose = MixPose.Setup; + alpha = alphaMix; + break; + case DIP: + pose = MixPose.Setup; + alpha = alphaDip; + break; + default: + pose = MixPose.Setup; + alpha = alphaDip; + var dipMix = timelineDipMix[i]; + alpha *= Math.Max(0, 1 - dipMix.mixTime / dipMix.mixDuration); + break; + } + from.totalAlpha += alpha; + var rotateTimeline = timeline as RotateTimeline; + if (rotateTimeline != null) + { + ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, alpha, pose, timelinesRotation, i << 1, firstFrame); + } + else + { + timeline.Apply(skeleton, animationLast, animationTime, eventBuffer, alpha, pose, MixDirection.Out); + } + } + + if (to.mixDuration > 0) QueueEvents(from, animationTime); + this.events.Clear(false); + from.nextAnimationLast = animationTime; + from.nextTrackLast = from.trackTime; + + return mix; + } + + static private void ApplyRotateTimeline(RotateTimeline rotateTimeline, Skeleton skeleton, float time, float alpha, MixPose pose, + float[] timelinesRotation, int i, bool firstFrame) + { + + if (firstFrame) timelinesRotation[i] = 0; + + if (alpha == 1) + { + rotateTimeline.Apply(skeleton, 0, time, null, 1, pose, MixDirection.In); + return; + } + + Bone bone = skeleton.bones.Items[rotateTimeline.boneIndex]; + float[] frames = rotateTimeline.frames; + if (time < frames[0]) + { + if (pose == MixPose.Setup) bone.rotation = bone.data.rotation; + return; + } + + float r2; + if (time >= frames[frames.Length - RotateTimeline.ENTRIES]) // Time is after last frame. + r2 = bone.data.rotation + frames[frames.Length + RotateTimeline.PREV_ROTATION]; + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, RotateTimeline.ENTRIES); + float prevRotation = frames[frame + RotateTimeline.PREV_ROTATION]; + float frameTime = frames[frame]; + float percent = rotateTimeline.GetCurvePercent((frame >> 1) - 1, + 1 - (time - frameTime) / (frames[frame + RotateTimeline.PREV_TIME] - frameTime)); + + r2 = frames[frame + RotateTimeline.ROTATION] - prevRotation; + r2 -= (16384 - (int)(16384.499999999996 - r2 / 360)) * 360; + r2 = prevRotation + r2 * percent + bone.data.rotation; + r2 -= (16384 - (int)(16384.499999999996 - r2 / 360)) * 360; + } + + // Mix between rotations using the direction of the shortest route on the first frame while detecting crosses. + float r1 = pose == MixPose.Setup ? bone.data.rotation : bone.rotation; + float total, diff = r2 - r1; + if (diff == 0) + { + total = timelinesRotation[i]; + } + else + { + diff -= (16384 - (int)(16384.499999999996 - diff / 360)) * 360; + float lastTotal, lastDiff; + if (firstFrame) + { + lastTotal = 0; + lastDiff = diff; + } + else + { + lastTotal = timelinesRotation[i]; // Angle and direction of mix, including loops. + lastDiff = timelinesRotation[i + 1]; // Difference between bones. + } + bool current = diff > 0, dir = lastTotal >= 0; + // Detect cross at 0 (not 180). + if (Math.Sign(lastDiff) != Math.Sign(diff) && Math.Abs(lastDiff) <= 90) + { + // A cross after a 360 rotation is a loop. + if (Math.Abs(lastTotal) > 180) lastTotal += 360 * Math.Sign(lastTotal); + dir = current; + } + total = diff + lastTotal - lastTotal % 360; // Store loops as part of lastTotal. + if (dir != current) total += 360 * Math.Sign(lastTotal); + timelinesRotation[i] = total; + } + timelinesRotation[i + 1] = diff; + r1 += total * alpha; + bone.rotation = r1 - (16384 - (int)(16384.499999999996 - r1 / 360)) * 360; + } + + private void QueueEvents(TrackEntry entry, float animationTime) + { + float animationStart = entry.animationStart, animationEnd = entry.animationEnd; + float duration = animationEnd - animationStart; + float trackLastWrapped = entry.trackLast % duration; + + // Queue events before complete. + var events = this.events; + var eventsItems = events.Items; + int i = 0, n = events.Count; + for (; i < n; i++) + { + var e = eventsItems[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. + if (entry.loop ? (trackLastWrapped > entry.trackTime % duration) + : (animationTime >= animationEnd && entry.animationLast < animationEnd)) + { + queue.Complete(entry); + } + + // Queue events after complete. + for (; i < n; i++) + { + Event e = eventsItems[i]; + if (e.time < animationStart) continue; // Discard events outside animation start/end. + queue.Event(entry, eventsItems[i]); + } + } + + /// + /// Removes all animations from all tracks, leaving skeletons in their previous pose. + /// It may be desired to use to mix the skeletons back to the setup pose, + /// rather than leaving them in their previous pose. + public void ClearTracks() + { + bool oldDrainDisabled = queue.drainDisabled; + queue.drainDisabled = true; + for (int i = 0, n = tracks.Count; i < n; i++) + { + ClearTrack(i); + } + tracks.Clear(); + queue.drainDisabled = oldDrainDisabled; + queue.Drain(); + } + + /// + /// Removes all animations from the tracks, leaving skeletons in their previous pose. + /// It may be desired to use to mix the skeletons back to the setup pose, + /// rather than leaving them in their previous pose. + public void ClearTrack(int trackIndex) + { + if (trackIndex >= tracks.Count) return; + TrackEntry current = tracks.Items[trackIndex]; + if (current == null) return; + + queue.End(current); + + DisposeNext(current); + + TrackEntry entry = current; + while (true) + { + TrackEntry from = entry.mixingFrom; + if (from == null) break; + queue.End(from); + entry.mixingFrom = null; + entry = from; + } + + tracks.Items[current.trackIndex] = null; + + queue.Drain(); + } + + private void SetCurrent(int index, TrackEntry current, bool interrupt) + { + TrackEntry from = ExpandToIndex(index); + tracks.Items[index] = current; + + if (from != null) + { + if (interrupt) queue.Interrupt(from); + current.mixingFrom = from; + current.mixTime = 0; + + // Store interrupted mix percentage. + if (from.mixingFrom != null && from.mixDuration > 0) + current.interruptAlpha *= Math.Min(1, from.mixTime / from.mixDuration); + + from.timelinesRotation.Clear(); // Reset rotation for mixing out, in case entry was mixed in. + } + + queue.Start(current); + } + + + /// Sets an animation by name. + public TrackEntry SetAnimation(int trackIndex, String animationName, bool loop) + { + Animation animation = data.skeletonData.FindAnimation(animationName); + if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName"); + return SetAnimation(trackIndex, animation, loop); + } + + /// Sets the current animation for a track, discarding any queued animations. + /// 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 determines when the track is cleared. + /// + /// A track entry to allow further customization of animation playback. References to the track entry must not be kept + /// after . + public TrackEntry SetAnimation(int trackIndex, Animation animation, bool loop) + { + if (animation == null) throw new ArgumentNullException("animation", "animation cannot be 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.Items[trackIndex] = current.mixingFrom; + queue.Interrupt(current); + queue.End(current); + DisposeNext(current); + current = current.mixingFrom; + interrupt = false; + } + else + { + DisposeNext(current); + } + } + TrackEntry entry = NewTrackEntry(trackIndex, animation, loop, current); + SetCurrent(trackIndex, entry, interrupt); + queue.Drain(); + return entry; + } + + /// Queues an animation by name. + /// + public TrackEntry AddAnimation(int trackIndex, String animationName, bool loop, float delay) + { + Animation animation = data.skeletonData.FindAnimation(animationName); + if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName"); + return AddAnimation(trackIndex, animation, loop, 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 . + /// + /// 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. + /// + /// A track entry to allow further customization of animation playback. References to the track entry must not be kept + /// after + public TrackEntry AddAnimation(int trackIndex, Animation animation, bool loop, float delay) + { + if (animation == null) throw new ArgumentNullException("animation", "animation cannot be 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(); + } + else + { + last.next = entry; + if (delay <= 0) + { + float duration = last.animationEnd - last.animationStart; + if (duration != 0) + delay += duration * (1 + (int)(last.trackTime / duration)) - data.GetMix(last.animation, animation); + else + delay = 0; + } + } + + entry.delay = delay; + return entry; + } + + /// + /// Sets an empty animation for a track, discarding any queued animations, and mixes to it over the specified mix duration. + public TrackEntry SetEmptyAnimation(int trackIndex, float mixDuration) + { + TrackEntry entry = SetAnimation(trackIndex, AnimationState.EmptyAnimation, false); + entry.mixDuration = mixDuration; + entry.trackEnd = mixDuration; + return entry; + } + + /// + /// 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. + /// + /// A track entry to allow further customization of animation playback. References to the track entry must not be kept after . + /// + /// Track number. + /// Mix duration. + /// 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. + public TrackEntry AddEmptyAnimation(int trackIndex, float mixDuration, float delay) + { + if (delay <= 0) delay -= mixDuration; + TrackEntry entry = AddAnimation(trackIndex, AnimationState.EmptyAnimation, false, delay); + entry.mixDuration = mixDuration; + entry.trackEnd = mixDuration; + return entry; + } + + /// + /// Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix duration. + public void SetEmptyAnimations(float mixDuration) + { + bool oldDrainDisabled = queue.drainDisabled; + queue.drainDisabled = true; + for (int i = 0, n = tracks.Count; i < n; i++) + { + TrackEntry current = tracks.Items[i]; + if (current != null) SetEmptyAnimation(i, mixDuration); + } + queue.drainDisabled = oldDrainDisabled; + queue.Drain(); + } + + private TrackEntry ExpandToIndex(int index) + { + if (index < tracks.Count) return tracks.Items[index]; + while (index >= tracks.Count) + tracks.Add(null); + return null; + } + + /// May be null. + private TrackEntry NewTrackEntry(int trackIndex, Animation animation, bool loop, TrackEntry last) + { + TrackEntry entry = trackEntryPool.Obtain(); // Pooling + entry.trackIndex = trackIndex; + entry.animation = animation; + entry.loop = loop; + + entry.eventThreshold = 0; + entry.attachmentThreshold = 0; + entry.drawOrderThreshold = 0; + + entry.animationStart = 0; + entry.animationEnd = animation.Duration; + entry.animationLast = -1; + entry.nextAnimationLast = -1; + + entry.delay = 0; + entry.trackTime = 0; + entry.trackLast = -1; + entry.nextTrackLast = -1; + entry.trackEnd = float.MaxValue; // loop ? float.MaxValue : animation.Duration; + entry.timeScale = 1; + + entry.alpha = 1; + entry.interruptAlpha = 1; + entry.mixTime = 0; + entry.mixDuration = (last == null) ? 0 : data.GetMix(last.animation, animation); + return entry; + } + + private void DisposeNext(TrackEntry entry) + { + TrackEntry next = entry.next; + while (next != null) + { + queue.Dispose(next); + next = next.next; + } + entry.next = null; + } + + private void AnimationsChanged() + { + animationsChanged = false; + + var propertyIDs = this.propertyIDs; + propertyIDs.Clear(); + var mixingTo = this.mixingTo; + + TrackEntry lastEntry = null; + var tracksItems = tracks.Items; + for (int i = 0, n = tracks.Count; i < n; i++) + { + var entry = tracksItems[i]; + if (entry != null) + { + entry.SetTimelineData(lastEntry, mixingTo, propertyIDs); + lastEntry = entry; + } + } + } + + /// The track entry for the animation currently playing on the track, or null if no animation is currently playing. + public TrackEntry GetCurrent(int trackIndex) + { + return (trackIndex >= tracks.Count) ? null : tracks.Items[trackIndex]; + } + + override public String ToString() + { + var buffer = new System.Text.StringBuilder(); + for (int i = 0, n = tracks.Count; i < n; i++) + { + TrackEntry entry = tracks.Items[i]; + if (entry == null) continue; + if (buffer.Length > 0) buffer.Append(", "); + buffer.Append(entry.ToString()); + } + return buffer.Length == 0 ? "" : buffer.ToString(); + } + + internal void OnStart(TrackEntry entry) { if (Start != null) Start(entry); } + internal void OnInterrupt(TrackEntry entry) { if (Interrupt != null) Interrupt(entry); } + internal void OnEnd(TrackEntry entry) { if (End != null) End(entry); } + internal void OnDispose(TrackEntry entry) { if (Dispose != null) Dispose(entry); } + internal void OnComplete(TrackEntry entry) { if (Complete != null) Complete(entry); } + internal void OnEvent(TrackEntry entry, Event e) { if (Event != null) Event(entry, e); } + } + + /// State for the playback of an animation. + public class TrackEntry : Pool.IPoolable + { + internal Animation animation; + + internal TrackEntry next, mixingFrom; + internal int trackIndex; + + internal bool loop; + internal float eventThreshold, attachmentThreshold, drawOrderThreshold; + internal float animationStart, animationEnd, animationLast, nextAnimationLast; + internal float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale = 1f; + internal float alpha, mixTime, mixDuration, interruptAlpha, totalAlpha; + internal readonly ExposedList timelineData = new ExposedList(); + internal readonly ExposedList timelineDipMix = new ExposedList(); + internal readonly ExposedList timelinesRotation = new ExposedList(); + + // IPoolable.Reset() + public void Reset() + { + next = null; + mixingFrom = null; + animation = null; + timelineData.Clear(); + timelineDipMix.Clear(); + timelinesRotation.Clear(); + + Start = null; + Interrupt = null; + End = null; + Dispose = null; + Complete = null; + Event = null; + } + + /// May be null. + internal TrackEntry SetTimelineData(TrackEntry to, ExposedList mixingToArray, HashSet propertyIDs) + { + if (to != null) mixingToArray.Add(to); + var lastEntry = mixingFrom != null ? mixingFrom.SetTimelineData(this, mixingToArray, propertyIDs) : this; + if (to != null) mixingToArray.RemoveAt(mixingToArray.Count - 1); // mixingToArray.pop(); + + var mixingTo = mixingToArray.Items; + int mixingToLast = mixingToArray.Count - 1; + var timelines = animation.timelines.Items; + int timelinesCount = animation.timelines.Count; + var timelineDataItems = timelineData.Resize(timelinesCount).Items; // timelineData.setSize(timelinesCount); + timelineDipMix.Clear(); + var timelineDipMixItems = timelineDipMix.Resize(timelinesCount).Items; //timelineDipMix.setSize(timelinesCount); + + // outer: + for (int i = 0; i < timelinesCount; i++) + { + int id = timelines[i].PropertyId; + if (!propertyIDs.Add(id)) + { + timelineDataItems[i] = AnimationState.SUBSEQUENT; + } + else if (to == null || !to.HasTimeline(id)) + { + timelineDataItems[i] = AnimationState.FIRST; + } + else + { + for (int ii = mixingToLast; ii >= 0; ii--) + { + var entry = mixingTo[ii]; + if (!entry.HasTimeline(id)) + { + if (entry.mixDuration > 0) + { + timelineDataItems[i] = AnimationState.DIP_MIX; + timelineDipMixItems[i] = entry; + goto outer; // continue outer; + } + } + } + timelineDataItems[i] = AnimationState.DIP; + } + outer: { } + } + return lastEntry; + } + + bool HasTimeline(int id) + { + var timelines = animation.timelines.Items; + for (int i = 0, n = animation.timelines.Count; i < n; i++) + if (timelines[i].PropertyId == id) return true; + return false; + } + + /// The index of the track where this entry is either current or queued. + public int TrackIndex { get { return trackIndex; } } + + /// The animation to apply for this track entry. + public Animation Animation { get { return animation; } } + + /// + /// If true, the animation will repeat. If false, it will not, instead its last frame is applied if played beyond its duration. + public bool Loop { get { return loop; } set { loop = value; } } + + /// + /// 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. + public float Delay { get { return delay; } set { delay = value; } } + + /// + /// Current time in seconds this track entry has been the current track entry. The track time determines + /// . The track time can be set to start the animation at a time other than 0, without affecting looping. + public float TrackTime { get { return trackTime; } set { trackTime = value; } } + + /// + /// 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 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 to mix the properties back to the + /// setup pose over time, rather than have it happen instantly. + /// + public float TrackEnd { get { return trackEnd; } set { trackEnd = value; } } + + /// + /// 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 to the same value to + /// prevent timeline keys before the start time from triggering. + /// + public float AnimationStart { get { return animationStart; } set { animationStart = value; } } + + /// + /// Seconds for the last frame of this animation. Non-looping animations won't play past this time. Looping animations will + /// loop back to at this time. Defaults to the animation duration. + public float AnimationEnd { get { return animationEnd; } set { animationEnd = value; } } + + /// + /// 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. + public float AnimationLast + { + get { return animationLast; } + set + { + animationLast = value; + nextAnimationLast = value; + } + } + + /// + /// Uses to compute the animation time between . and + /// . When the track time is 0, the animation time is equal to the animation start time. + /// + public float AnimationTime + { + get + { + if (loop) + { + float duration = animationEnd - animationStart; + if (duration == 0) return animationStart; + return (trackTime % duration) + animationStart; + } + return Math.Min(trackTime + animationStart, animationEnd); + } + } + + /// + /// Multiplier for the delta time when the animation state is updated, causing time for this animation to play slower or + /// faster. Defaults to 1. + /// + public float TimeScale { get { return timeScale; } set { timeScale = value; } } + + /// + /// 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. + /// + public float Alpha { get { return alpha; } set { alpha = value; } } + + /// + /// 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. + public float EventThreshold { get { return eventThreshold; } set { eventThreshold = value; } } + + /// + /// 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. + public float AttachmentThreshold { get { return attachmentThreshold; } set { attachmentThreshold = value; } } + + /// + /// 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. + /// + public float DrawOrderThreshold { get { return drawOrderThreshold; } set { drawOrderThreshold = value; } } + + /// + /// The animation queued to start after this animation, or null. + public TrackEntry Next { get { return next; } } + + /// + /// Returns true if at least one loop has been completed. + public bool IsComplete + { + get { return trackTime >= animationEnd - animationStart; } + } + + /// + /// Seconds from 0 to the mix duration when mixing from the previous animation to this animation. May be slightly more than + /// when the mix is complete. + public float MixTime { get { return mixTime; } set { mixTime = value; } } + + /// + /// Seconds for mixing from the previous animation to this animation. Defaults to the value provided by + /// 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 is next called. + /// + /// When using with a + /// delay less than or equal to 0, note the is set using the mix duration from the + /// + /// + /// + public float MixDuration { get { return mixDuration; } set { mixDuration = value; } } + + /// + /// 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 linked list. + public TrackEntry MixingFrom { get { return mixingFrom; } } + + public event AnimationState.TrackEntryDelegate Start, Interrupt, End, Dispose, Complete; + public event AnimationState.TrackEntryEventDelegate Event; + internal void OnStart() { if (Start != null) Start(this); } + internal void OnInterrupt() { if (Interrupt != null) Interrupt(this); } + internal void OnEnd() { if (End != null) End(this); } + internal void OnDispose() { if (Dispose != null) Dispose(this); } + internal void OnComplete() { if (Complete != null) Complete(this); } + internal void OnEvent(Event e) { if (Event != null) Event(this, e); } + + /// + /// 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 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. + public void ResetRotationDirections() + { + timelinesRotation.Clear(); + } + + override public String ToString() + { + return animation == null ? "" : animation.name; + } + } + + class EventQueue + { + private readonly List eventQueueEntries = new List(); + public bool drainDisabled; + + private readonly AnimationState state; + private readonly Pool trackEntryPool; + public event Action AnimationsChanged; + + public EventQueue(AnimationState state, Action HandleAnimationsChanged, Pool trackEntryPool) + { + this.state = state; + this.AnimationsChanged += HandleAnimationsChanged; + this.trackEntryPool = trackEntryPool; + } + + struct EventQueueEntry + { + public EventType type; + public TrackEntry entry; + public Event e; + + public EventQueueEntry(EventType eventType, TrackEntry trackEntry, Event e = null) + { + this.type = eventType; + this.entry = trackEntry; + this.e = e; + } + } + + enum EventType + { + Start, Interrupt, End, Dispose, Complete, Event + } + + public void Start(TrackEntry entry) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.Start, entry)); + if (AnimationsChanged != null) AnimationsChanged(); + } + + public void Interrupt(TrackEntry entry) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.Interrupt, entry)); + } + + public void End(TrackEntry entry) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.End, entry)); + if (AnimationsChanged != null) AnimationsChanged(); + } + + public void Dispose(TrackEntry entry) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.Dispose, entry)); + } + + public void Complete(TrackEntry entry) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.Complete, entry)); + } + + public void Event(TrackEntry entry, Event e) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.Event, entry, e)); + } + + public void Drain() + { + if (drainDisabled) return; + drainDisabled = true; + + var entries = this.eventQueueEntries; + AnimationState state = this.state; + + // Don't cache entries.Count so callbacks can queue their own events (eg, call SetAnimation in AnimationState_Complete). + for (int i = 0; i < entries.Count; i++) + { + var queueEntry = entries[i]; + TrackEntry trackEntry = queueEntry.entry; + + switch (queueEntry.type) + { + case EventType.Start: + trackEntry.OnStart(); + state.OnStart(trackEntry); + break; + case EventType.Interrupt: + trackEntry.OnInterrupt(); + state.OnInterrupt(trackEntry); + break; + case EventType.End: + trackEntry.OnEnd(); + state.OnEnd(trackEntry); + goto case EventType.Dispose; // Fall through. (C#) + case EventType.Dispose: + trackEntry.OnDispose(); + state.OnDispose(trackEntry); + trackEntryPool.Free(trackEntry); // Pooling + break; + case EventType.Complete: + trackEntry.OnComplete(); + state.OnComplete(trackEntry); + break; + case EventType.Event: + trackEntry.OnEvent(queueEntry.e); + state.OnEvent(trackEntry, queueEntry.e); + break; + } + } + eventQueueEntries.Clear(); + + drainDisabled = false; + } + + public void Clear() + { + eventQueueEntries.Clear(); + } + } + + public class Pool where T : class, new() + { + public readonly int max; + readonly Stack freeObjects; + + public int Count { get { return freeObjects.Count; } } + public int Peak { get; private set; } + + public Pool(int initialCapacity = 16, int max = int.MaxValue) + { + freeObjects = new Stack(initialCapacity); + this.max = max; + } + + public T Obtain() + { + return freeObjects.Count == 0 ? new T() : freeObjects.Pop(); + } + + public void Free(T obj) + { + if (obj == null) throw new ArgumentNullException("obj", "obj cannot be null"); + if (freeObjects.Count < max) + { + freeObjects.Push(obj); + Peak = Math.Max(Peak, freeObjects.Count); + } + Reset(obj); + } + + // protected void FreeAll (List objects) { + // if (objects == null) throw new ArgumentNullException("objects", "objects cannot be null."); + // var freeObjects = this.freeObjects; + // int max = this.max; + // for (int i = 0; i < objects.Count; i++) { + // T obj = objects[i]; + // if (obj == null) continue; + // if (freeObjects.Count < max) freeObjects.Push(obj); + // Reset(obj); + // } + // Peak = Math.Max(Peak, freeObjects.Count); + // } + + public void Clear() + { + freeObjects.Clear(); + } + + protected void Reset(T obj) + { + var poolable = obj as IPoolable; + if (poolable != null) poolable.Reset(); + } + + public interface IPoolable + { + void Reset(); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/AnimationStateData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/AnimationStateData.cs index 51ea659..10d9f72 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/AnimationStateData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/AnimationStateData.cs @@ -31,85 +31,97 @@ using System; using System.Collections.Generic; -namespace Spine3_6_32 { +namespace Spine3_6_32 +{ - /// Stores mix (crossfade) durations to be applied when AnimationState animations are changed. - public class AnimationStateData { - internal SkeletonData skeletonData; - readonly Dictionary animationToMixTime = new Dictionary(AnimationPairComparer.Instance); - internal float defaultMix; + /// Stores mix (crossfade) durations to be applied when AnimationState animations are changed. + public class AnimationStateData + { + internal SkeletonData skeletonData; + readonly Dictionary animationToMixTime = new Dictionary(AnimationPairComparer.Instance); + internal float defaultMix; - /// The SkeletonData to look up animations when they are specified by name. - public SkeletonData SkeletonData { get { return skeletonData; } } + /// The SkeletonData to look up animations when they are specified by name. + public SkeletonData SkeletonData { get { return skeletonData; } } - /// - /// The mix duration to use when no mix duration has been specifically defined between two animations. - public float DefaultMix { get { return defaultMix; } set { defaultMix = value; } } + /// + /// The mix duration to use when no mix duration has been specifically defined between two animations. + public float DefaultMix { get { return defaultMix; } set { defaultMix = value; } } - public AnimationStateData (SkeletonData skeletonData) { - if (skeletonData == null) throw new ArgumentException ("skeletonData cannot be null."); - this.skeletonData = skeletonData; - } + public AnimationStateData(SkeletonData skeletonData) + { + if (skeletonData == null) throw new ArgumentException("skeletonData cannot be null."); + this.skeletonData = skeletonData; + } - /// Sets a mix duration by animation names. - public void SetMix (string fromName, string toName, float duration) { - Animation from = skeletonData.FindAnimation(fromName); - if (from == null) throw new ArgumentException("Animation not found: " + fromName); - Animation to = skeletonData.FindAnimation(toName); - if (to == null) throw new ArgumentException("Animation not found: " + toName); - SetMix(from, to, duration); - } + /// Sets a mix duration by animation names. + public void SetMix(string fromName, string toName, float duration) + { + Animation from = skeletonData.FindAnimation(fromName); + if (from == null) throw new ArgumentException("Animation not found: " + fromName); + Animation to = skeletonData.FindAnimation(toName); + if (to == null) throw new ArgumentException("Animation not found: " + toName); + SetMix(from, to, duration); + } - /// Sets a mix duration when changing from the specified animation to the other. - /// See TrackEntry.MixDuration. - public void SetMix (Animation from, Animation to, float duration) { - if (from == null) throw new ArgumentNullException("from", "from cannot be null."); - if (to == null) throw new ArgumentNullException("to", "to cannot be null."); - AnimationPair key = new AnimationPair(from, to); - animationToMixTime.Remove(key); - animationToMixTime.Add(key, duration); - } + /// Sets a mix duration when changing from the specified animation to the other. + /// See TrackEntry.MixDuration. + public void SetMix(Animation from, Animation to, float duration) + { + if (from == null) throw new ArgumentNullException("from", "from cannot be null."); + if (to == null) throw new ArgumentNullException("to", "to cannot be null."); + AnimationPair key = new AnimationPair(from, to); + animationToMixTime.Remove(key); + animationToMixTime.Add(key, 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. - /// - public float GetMix (Animation from, Animation to) { - if (from == null) throw new ArgumentNullException("from", "from cannot be null."); - if (to == null) throw new ArgumentNullException("to", "to cannot be null."); - AnimationPair key = new AnimationPair(from, to); - float duration; - if (animationToMixTime.TryGetValue(key, out duration)) return duration; - return defaultMix; - } + /// + /// The mix duration to use when changing from the specified animation to the other, + /// or the DefaultMix if no mix duration has been set. + /// + public float GetMix(Animation from, Animation to) + { + if (from == null) throw new ArgumentNullException("from", "from cannot be null."); + if (to == null) throw new ArgumentNullException("to", "to cannot be null."); + AnimationPair key = new AnimationPair(from, to); + float duration; + if (animationToMixTime.TryGetValue(key, out duration)) return duration; + return defaultMix; + } - struct AnimationPair { - public readonly Animation a1; - public readonly Animation a2; + struct AnimationPair + { + public readonly Animation a1; + public readonly Animation a2; - public AnimationPair (Animation a1, Animation a2) { - this.a1 = a1; - this.a2 = a2; - } + public AnimationPair(Animation a1, Animation a2) + { + this.a1 = a1; + this.a2 = a2; + } - public override string ToString () { - return a1.name + "->" + a2.name; - } - } + public override string ToString() + { + return a1.name + "->" + a2.name; + } + } - // Avoids boxing in the dictionary. - class AnimationPairComparer : IEqualityComparer { - internal static readonly AnimationPairComparer Instance = new AnimationPairComparer(); + // Avoids boxing in the dictionary. + class AnimationPairComparer : IEqualityComparer + { + internal static readonly AnimationPairComparer Instance = new AnimationPairComparer(); - bool IEqualityComparer.Equals (AnimationPair x, AnimationPair y) { - return ReferenceEquals(x.a1, y.a1) && ReferenceEquals(x.a2, y.a2); - } + bool IEqualityComparer.Equals(AnimationPair x, AnimationPair y) + { + return ReferenceEquals(x.a1, y.a1) && ReferenceEquals(x.a2, y.a2); + } - int IEqualityComparer.GetHashCode (AnimationPair obj) { - // from Tuple.CombineHashCodes // return (((h1 << 5) + h1) ^ h2); - int h1 = obj.a1.GetHashCode(); - return (((h1 << 5) + h1) ^ obj.a2.GetHashCode()); - } - } - } + int IEqualityComparer.GetHashCode(AnimationPair obj) + { + // from Tuple.CombineHashCodes // return (((h1 << 5) + h1) ^ h2); + int h1 = obj.a1.GetHashCode(); + return (((h1 << 5) + h1) ^ obj.a2.GetHashCode()); + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Atlas.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Atlas.cs index b3588a8..b93564c 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Atlas.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Atlas.cs @@ -31,21 +31,22 @@ using System; using System.Collections.Generic; using System.IO; -using System.Reflection; #if WINDOWS_STOREAPP using System.Threading.Tasks; using Windows.Storage; #endif -namespace Spine3_6_32 { - public class Atlas { - readonly List pages = new List(); - List regions = new List(); - TextureLoader textureLoader; +namespace Spine3_6_32 +{ + public class Atlas + { + readonly List pages = new List(); + List regions = new List(); + TextureLoader textureLoader; - #if !(UNITY_5 || UNITY_4 || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1) // !UNITY - #if WINDOWS_STOREAPP +#if !(UNITY_5 || UNITY_4 || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1) // !UNITY +#if WINDOWS_STOREAPP private async Task ReadFile(string path, TextureLoader textureLoader) { var folder = Windows.ApplicationModel.Package.Current.InstalledLocation; var file = await folder.GetFileAsync(path).AsTask().ConfigureAwait(false); @@ -61,233 +62,264 @@ private async Task ReadFile(string path, TextureLoader textureLoader) { public Atlas(string path, TextureLoader textureLoader) { this.ReadFile(path, textureLoader).Wait(); } - #else +#else - public Atlas (string path, TextureLoader textureLoader) { + public Atlas(string path, TextureLoader textureLoader) + { - #if WINDOWS_PHONE +#if WINDOWS_PHONE Stream stream = Microsoft.Xna.Framework.TitleContainer.OpenStream(path); using (StreamReader reader = new StreamReader(stream)) { - #else - using (StreamReader reader = new StreamReader(path)) { - #endif // WINDOWS_PHONE - - try { - Load(reader, Path.GetDirectoryName(path), textureLoader); - } catch (Exception ex) { - throw new Exception("Error reading atlas file: " + path, ex); - } - - } - } - #endif // WINDOWS_STOREAPP - - #endif // !(UNITY) - - public Atlas (TextReader reader, string dir, TextureLoader textureLoader) { - Load(reader, dir, textureLoader); - } - - public Atlas (List pages, List regions) { - this.pages = pages; - this.regions = regions; - this.textureLoader = null; - } - - private void Load (TextReader reader, string imagesDir, TextureLoader textureLoader) { - if (textureLoader == null) throw new ArgumentNullException("textureLoader cannot be null."); - this.textureLoader = textureLoader; - - string[] tuple = new string[4]; - AtlasPage page = null; - while (true) { - string line = reader.ReadLine(); - if (line == null) break; - if (line.Trim().Length == 0) - page = null; - else if (page == null) { - page = new AtlasPage(); - page.name = line; - - if (ReadTuple(reader, tuple) == 2) { // size is only optional for an atlas packed with an old TexturePacker. - page.width = int.Parse(tuple[0]); - page.height = int.Parse(tuple[1]); - ReadTuple(reader, tuple); - } - page.format = (Format)Enum.Parse(typeof(Format), tuple[0], false); - - ReadTuple(reader, tuple); - page.minFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[0], false); - page.magFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[1], false); - - string direction = ReadValue(reader); - page.uWrap = TextureWrap.ClampToEdge; - page.vWrap = TextureWrap.ClampToEdge; - if (direction == "x") - page.uWrap = TextureWrap.Repeat; - else if (direction == "y") - page.vWrap = TextureWrap.Repeat; - else if (direction == "xy") - page.uWrap = page.vWrap = TextureWrap.Repeat; - - textureLoader.Load(page, Path.Combine(imagesDir, line)); - - pages.Add(page); - - } else { - AtlasRegion region = new AtlasRegion(); - region.name = line; - region.page = page; - - region.rotate = Boolean.Parse(ReadValue(reader)); - - ReadTuple(reader, tuple); - int x = int.Parse(tuple[0]); - int y = int.Parse(tuple[1]); - - ReadTuple(reader, tuple); - int width = int.Parse(tuple[0]); - int height = int.Parse(tuple[1]); - - region.u = x / (float)page.width; - region.v = y / (float)page.height; - if (region.rotate) { - region.u2 = (x + height) / (float)page.width; - region.v2 = (y + width) / (float)page.height; - } else { - region.u2 = (x + width) / (float)page.width; - region.v2 = (y + height) / (float)page.height; - } - region.x = x; - region.y = y; - region.width = Math.Abs(width); - region.height = Math.Abs(height); - - if (ReadTuple(reader, tuple) == 4) { // split is optional - region.splits = new int[] {int.Parse(tuple[0]), int.Parse(tuple[1]), - int.Parse(tuple[2]), int.Parse(tuple[3])}; - - if (ReadTuple(reader, tuple) == 4) { // pad is optional, but only present with splits - region.pads = new int[] {int.Parse(tuple[0]), int.Parse(tuple[1]), - int.Parse(tuple[2]), int.Parse(tuple[3])}; - - ReadTuple(reader, tuple); - } - } - - region.originalWidth = int.Parse(tuple[0]); - region.originalHeight = int.Parse(tuple[1]); - - ReadTuple(reader, tuple); - region.offsetX = int.Parse(tuple[0]); - region.offsetY = int.Parse(tuple[1]); - - region.index = int.Parse(ReadValue(reader)); - - regions.Add(region); - } - } - } - - static string ReadValue (TextReader reader) { - string line = reader.ReadLine(); - int colon = line.IndexOf(':'); - if (colon == -1) throw new Exception("Invalid line: " + line); - return line.Substring(colon + 1).Trim(); - } - - /// Returns the number of tuple values read (1, 2 or 4). - static int ReadTuple (TextReader reader, string[] tuple) { - string line = reader.ReadLine(); - int colon = line.IndexOf(':'); - if (colon == -1) throw new Exception("Invalid line: " + line); - int i = 0, lastMatch = colon + 1; - for (; i < 3; i++) { - int comma = line.IndexOf(',', lastMatch); - if (comma == -1) break; - tuple[i] = line.Substring(lastMatch, comma - lastMatch).Trim(); - lastMatch = comma + 1; - } - tuple[i] = line.Substring(lastMatch).Trim(); - return i + 1; - } - - public void FlipV () { - for (int i = 0, n = regions.Count; i < n; i++) { - AtlasRegion region = regions[i]; - region.v = 1 - region.v; - region.v2 = 1 - region.v2; - } - } - - /// 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. - /// The region, or null. - public AtlasRegion FindRegion (string name) { - for (int i = 0, n = regions.Count; i < n; i++) - if (regions[i].name == name) return regions[i]; - return null; - } - - public void Dispose () { - if (textureLoader == null) return; - for (int i = 0, n = pages.Count; i < n; i++) - textureLoader.Unload(pages[i].rendererObject); - } - } - - public enum Format { - Alpha, - Intensity, - LuminanceAlpha, - RGB565, - RGBA4444, - RGB888, - RGBA8888 - } - - public enum TextureFilter { - Nearest, - Linear, - MipMap, - MipMapNearestNearest, - MipMapLinearNearest, - MipMapNearestLinear, - MipMapLinearLinear - } - - public enum TextureWrap { - MirroredRepeat, - ClampToEdge, - Repeat - } - - public class AtlasPage { - public string name; - public Format format; - public TextureFilter minFilter; - public TextureFilter magFilter; - public TextureWrap uWrap; - public TextureWrap vWrap; - public Object rendererObject; - public int width, height; - } - - public class AtlasRegion { - public AtlasPage page; - public string name; - public int x, y, width, height; - public float u, v, u2, v2; - public float offsetX, offsetY; - public int originalWidth, originalHeight; - public int index; - public bool rotate; - public int[] splits; - public int[] pads; - } - - public interface TextureLoader { - void Load (AtlasPage page, string path); - void Unload (Object texture); - } +#else + using (StreamReader reader = new StreamReader(path)) + { +#endif // WINDOWS_PHONE + + try + { + Load(reader, Path.GetDirectoryName(path), textureLoader); + } + catch (Exception ex) + { + throw new Exception("Error reading atlas file: " + path, ex); + } + + } + } +#endif // WINDOWS_STOREAPP + +#endif // !(UNITY) + + public Atlas(TextReader reader, string dir, TextureLoader textureLoader) + { + Load(reader, dir, textureLoader); + } + + public Atlas(List pages, List regions) + { + this.pages = pages; + this.regions = regions; + this.textureLoader = null; + } + + private void Load(TextReader reader, string imagesDir, TextureLoader textureLoader) + { + if (textureLoader == null) throw new ArgumentNullException("textureLoader cannot be null."); + this.textureLoader = textureLoader; + + string[] tuple = new string[4]; + AtlasPage page = null; + while (true) + { + string line = reader.ReadLine(); + if (line == null) break; + if (line.Trim().Length == 0) + page = null; + else if (page == null) + { + page = new AtlasPage(); + page.name = line; + + if (ReadTuple(reader, tuple) == 2) + { // size is only optional for an atlas packed with an old TexturePacker. + page.width = int.Parse(tuple[0]); + page.height = int.Parse(tuple[1]); + ReadTuple(reader, tuple); + } + page.format = (Format)Enum.Parse(typeof(Format), tuple[0], false); + + ReadTuple(reader, tuple); + page.minFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[0], false); + page.magFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[1], false); + + string direction = ReadValue(reader); + page.uWrap = TextureWrap.ClampToEdge; + page.vWrap = TextureWrap.ClampToEdge; + if (direction == "x") + page.uWrap = TextureWrap.Repeat; + else if (direction == "y") + page.vWrap = TextureWrap.Repeat; + else if (direction == "xy") + page.uWrap = page.vWrap = TextureWrap.Repeat; + + textureLoader.Load(page, Path.Combine(imagesDir, line)); + + pages.Add(page); + + } + else + { + AtlasRegion region = new AtlasRegion(); + region.name = line; + region.page = page; + + region.rotate = Boolean.Parse(ReadValue(reader)); + + ReadTuple(reader, tuple); + int x = int.Parse(tuple[0]); + int y = int.Parse(tuple[1]); + + ReadTuple(reader, tuple); + int width = int.Parse(tuple[0]); + int height = int.Parse(tuple[1]); + + region.u = x / (float)page.width; + region.v = y / (float)page.height; + if (region.rotate) + { + region.u2 = (x + height) / (float)page.width; + region.v2 = (y + width) / (float)page.height; + } + else + { + region.u2 = (x + width) / (float)page.width; + region.v2 = (y + height) / (float)page.height; + } + region.x = x; + region.y = y; + region.width = Math.Abs(width); + region.height = Math.Abs(height); + + if (ReadTuple(reader, tuple) == 4) + { // split is optional + region.splits = new int[] {int.Parse(tuple[0]), int.Parse(tuple[1]), + int.Parse(tuple[2]), int.Parse(tuple[3])}; + + if (ReadTuple(reader, tuple) == 4) + { // pad is optional, but only present with splits + region.pads = new int[] {int.Parse(tuple[0]), int.Parse(tuple[1]), + int.Parse(tuple[2]), int.Parse(tuple[3])}; + + ReadTuple(reader, tuple); + } + } + + region.originalWidth = int.Parse(tuple[0]); + region.originalHeight = int.Parse(tuple[1]); + + ReadTuple(reader, tuple); + region.offsetX = int.Parse(tuple[0]); + region.offsetY = int.Parse(tuple[1]); + + region.index = int.Parse(ReadValue(reader)); + + regions.Add(region); + } + } + } + + static string ReadValue(TextReader reader) + { + string line = reader.ReadLine(); + int colon = line.IndexOf(':'); + if (colon == -1) throw new Exception("Invalid line: " + line); + return line.Substring(colon + 1).Trim(); + } + + /// Returns the number of tuple values read (1, 2 or 4). + static int ReadTuple(TextReader reader, string[] tuple) + { + string line = reader.ReadLine(); + int colon = line.IndexOf(':'); + if (colon == -1) throw new Exception("Invalid line: " + line); + int i = 0, lastMatch = colon + 1; + for (; i < 3; i++) + { + int comma = line.IndexOf(',', lastMatch); + if (comma == -1) break; + tuple[i] = line.Substring(lastMatch, comma - lastMatch).Trim(); + lastMatch = comma + 1; + } + tuple[i] = line.Substring(lastMatch).Trim(); + return i + 1; + } + + public void FlipV() + { + for (int i = 0, n = regions.Count; i < n; i++) + { + AtlasRegion region = regions[i]; + region.v = 1 - region.v; + region.v2 = 1 - region.v2; + } + } + + /// 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. + /// The region, or null. + public AtlasRegion FindRegion(string name) + { + for (int i = 0, n = regions.Count; i < n; i++) + if (regions[i].name == name) return regions[i]; + return null; + } + + public void Dispose() + { + if (textureLoader == null) return; + for (int i = 0, n = pages.Count; i < n; i++) + textureLoader.Unload(pages[i].rendererObject); + } + } + + public enum Format + { + Alpha, + Intensity, + LuminanceAlpha, + RGB565, + RGBA4444, + RGB888, + RGBA8888 + } + + public enum TextureFilter + { + Nearest, + Linear, + MipMap, + MipMapNearestNearest, + MipMapLinearNearest, + MipMapNearestLinear, + MipMapLinearLinear + } + + public enum TextureWrap + { + MirroredRepeat, + ClampToEdge, + Repeat + } + + public class AtlasPage + { + public string name; + public Format format; + public TextureFilter minFilter; + public TextureFilter magFilter; + public TextureWrap uWrap; + public TextureWrap vWrap; + public Object rendererObject; + public int width, height; + } + + public class AtlasRegion + { + public AtlasPage page; + public string name; + public int x, y, width, height; + public float u, v, u2, v2; + public float offsetX, offsetY; + public int originalWidth, originalHeight; + public int index; + public bool rotate; + public int[] splits; + public int[] pads; + } + + public interface TextureLoader + { + void Load(AtlasPage page, string path); + void Unload(Object texture); + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Attachments/AtlasAttachmentLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Attachments/AtlasAttachmentLoader.cs index 7d79fae..9a453e8 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Attachments/AtlasAttachmentLoader.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Attachments/AtlasAttachmentLoader.cs @@ -30,80 +30,91 @@ using System; -namespace Spine3_6_32 { +namespace Spine3_6_32 +{ - /// - /// An AttachmentLoader that configures attachments using texture regions from an Atlas. - /// See Loading Skeleton Data in the Spine Runtimes Guide. - /// - public class AtlasAttachmentLoader : AttachmentLoader { - private Atlas[] atlasArray; + /// + /// An AttachmentLoader that configures attachments using texture regions from an Atlas. + /// See Loading Skeleton Data in the Spine Runtimes Guide. + /// + public class AtlasAttachmentLoader : AttachmentLoader + { + private Atlas[] atlasArray; - public AtlasAttachmentLoader (params Atlas[] atlasArray) { - if (atlasArray == null) throw new ArgumentNullException("atlas array cannot be null."); - this.atlasArray = atlasArray; - } + public AtlasAttachmentLoader(params Atlas[] atlasArray) + { + if (atlasArray == null) throw new ArgumentNullException("atlas array cannot be null."); + this.atlasArray = atlasArray; + } - public RegionAttachment NewRegionAttachment (Skin skin, string name, string path) { - AtlasRegion region = FindRegion(path); - if (region == null) throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name)); - RegionAttachment attachment = new RegionAttachment(name); - attachment.RendererObject = region; - attachment.SetUVs(region.u, region.v, region.u2, region.v2, region.rotate); - attachment.regionOffsetX = region.offsetX; - attachment.regionOffsetY = region.offsetY; - attachment.regionWidth = region.width; - attachment.regionHeight = region.height; - attachment.regionOriginalWidth = region.originalWidth; - attachment.regionOriginalHeight = region.originalHeight; - return attachment; - } + public RegionAttachment NewRegionAttachment(Skin skin, string name, string path) + { + AtlasRegion region = FindRegion(path); + if (region == null) throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name)); + RegionAttachment attachment = new RegionAttachment(name); + attachment.RendererObject = region; + attachment.SetUVs(region.u, region.v, region.u2, region.v2, region.rotate); + attachment.regionOffsetX = region.offsetX; + attachment.regionOffsetY = region.offsetY; + attachment.regionWidth = region.width; + attachment.regionHeight = region.height; + attachment.regionOriginalWidth = region.originalWidth; + attachment.regionOriginalHeight = region.originalHeight; + return attachment; + } - public MeshAttachment NewMeshAttachment (Skin skin, string name, string path) { - AtlasRegion region = FindRegion(path); - if (region == null) throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name)); - MeshAttachment attachment = new MeshAttachment(name); - attachment.RendererObject = region; - attachment.RegionU = region.u; - attachment.RegionV = region.v; - attachment.RegionU2 = region.u2; - attachment.RegionV2 = region.v2; - attachment.RegionRotate = region.rotate; - attachment.regionOffsetX = region.offsetX; - attachment.regionOffsetY = region.offsetY; - attachment.regionWidth = region.width; - attachment.regionHeight = region.height; - attachment.regionOriginalWidth = region.originalWidth; - attachment.regionOriginalHeight = region.originalHeight; - return attachment; - } + public MeshAttachment NewMeshAttachment(Skin skin, string name, string path) + { + AtlasRegion region = FindRegion(path); + if (region == null) throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name)); + MeshAttachment attachment = new MeshAttachment(name); + attachment.RendererObject = region; + attachment.RegionU = region.u; + attachment.RegionV = region.v; + attachment.RegionU2 = region.u2; + attachment.RegionV2 = region.v2; + attachment.RegionRotate = region.rotate; + attachment.regionOffsetX = region.offsetX; + attachment.regionOffsetY = region.offsetY; + attachment.regionWidth = region.width; + attachment.regionHeight = region.height; + attachment.regionOriginalWidth = region.originalWidth; + attachment.regionOriginalHeight = region.originalHeight; + return attachment; + } - public BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, string name) { - return new BoundingBoxAttachment(name); - } + public BoundingBoxAttachment NewBoundingBoxAttachment(Skin skin, string name) + { + return new BoundingBoxAttachment(name); + } - public PathAttachment NewPathAttachment (Skin skin, string name) { - return new PathAttachment(name); - } + public PathAttachment NewPathAttachment(Skin skin, string name) + { + return new PathAttachment(name); + } - public PointAttachment NewPointAttachment (Skin skin, string name) { - return new PointAttachment(name); - } + public PointAttachment NewPointAttachment(Skin skin, string name) + { + return new PointAttachment(name); + } - public ClippingAttachment NewClippingAttachment(Skin skin, string name) { - return new ClippingAttachment(name); - } + public ClippingAttachment NewClippingAttachment(Skin skin, string name) + { + return new ClippingAttachment(name); + } - public AtlasRegion FindRegion (string name) { - AtlasRegion region; + public AtlasRegion FindRegion(string name) + { + AtlasRegion region; - for (int i = 0; i < atlasArray.Length; i++) { - region = atlasArray[i].FindRegion(name); - if (region != null) - return region; - } + for (int i = 0; i < atlasArray.Length; i++) + { + region = atlasArray[i].FindRegion(name); + if (region != null) + return region; + } - return null; - } - } + return null; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Attachments/Attachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Attachments/Attachment.cs index 5708eba..ab8b220 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Attachments/Attachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Attachments/Attachment.cs @@ -30,17 +30,21 @@ using System; -namespace Spine3_6_32 { - abstract public class Attachment { - public string Name { get; private set; } +namespace Spine3_6_32 +{ + abstract public class Attachment + { + public string Name { get; private set; } - public Attachment (String name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null"); - Name = name; - } + public Attachment(String name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null"); + Name = name; + } - override public string ToString () { - return Name; - } - } + override public string ToString() + { + return Name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Attachments/AttachmentLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Attachments/AttachmentLoader.cs index 683595b..85b2c2b 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Attachments/AttachmentLoader.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Attachments/AttachmentLoader.cs @@ -28,22 +28,24 @@ * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine3_6_32 { - public interface AttachmentLoader { - /// May be null to not load any attachment. - RegionAttachment NewRegionAttachment (Skin skin, string name, string path); +namespace Spine3_6_32 +{ + public interface AttachmentLoader + { + /// May be null to not load any attachment. + RegionAttachment NewRegionAttachment(Skin skin, string name, string path); - /// May be null to not load any attachment. - MeshAttachment NewMeshAttachment (Skin skin, string name, string path); + /// May be null to not load any attachment. + MeshAttachment NewMeshAttachment(Skin skin, string name, string path); - /// May be null to not load any attachment. - BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, string name); + /// May be null to not load any attachment. + BoundingBoxAttachment NewBoundingBoxAttachment(Skin skin, string name); - /// May be null to not load any attachment - PathAttachment NewPathAttachment (Skin skin, string name); + /// May be null to not load any attachment + PathAttachment NewPathAttachment(Skin skin, string name); - PointAttachment NewPointAttachment (Skin skin, string name); + PointAttachment NewPointAttachment(Skin skin, string name); - ClippingAttachment NewClippingAttachment (Skin skin, string name); - } + ClippingAttachment NewClippingAttachment(Skin skin, string name); + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Attachments/AttachmentType.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Attachments/AttachmentType.cs index 69ed600..618b85f 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Attachments/AttachmentType.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Attachments/AttachmentType.cs @@ -28,8 +28,10 @@ * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine3_6_32 { - public enum AttachmentType { - Region, Boundingbox, Mesh, Linkedmesh, Path, Point, Clipping - } +namespace Spine3_6_32 +{ + public enum AttachmentType + { + Region, Boundingbox, Mesh, Linkedmesh, Path, Point, Clipping + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Attachments/BoundingBoxAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Attachments/BoundingBoxAttachment.cs index 87e1bf8..5823d4c 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Attachments/BoundingBoxAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Attachments/BoundingBoxAttachment.cs @@ -28,13 +28,14 @@ * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; - -namespace Spine3_6_32 { - /// Attachment that has a polygon for bounds checking. - public class BoundingBoxAttachment : VertexAttachment { - public BoundingBoxAttachment (string name) - : base(name) { - } - } +namespace Spine3_6_32 +{ + /// Attachment that has a polygon for bounds checking. + public class BoundingBoxAttachment : VertexAttachment + { + public BoundingBoxAttachment(string name) + : base(name) + { + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Attachments/ClippingAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Attachments/ClippingAttachment.cs index c889959..37a85d1 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Attachments/ClippingAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Attachments/ClippingAttachment.cs @@ -28,15 +28,16 @@ * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; - -namespace Spine3_6_32 { - public class ClippingAttachment : VertexAttachment { +namespace Spine3_6_32 +{ + public class ClippingAttachment : VertexAttachment + { internal SlotData endSlot; public SlotData EndSlot { get { return endSlot; } set { endSlot = value; } } - public ClippingAttachment(string name) : base(name) { + public ClippingAttachment(string name) : base(name) + { } } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Attachments/MeshAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Attachments/MeshAttachment.cs index 212f099..804dc4a 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Attachments/MeshAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Attachments/MeshAttachment.cs @@ -28,93 +28,104 @@ * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; +namespace Spine3_6_32 +{ + /// Attachment that displays a texture region using a mesh. + public class MeshAttachment : VertexAttachment + { + internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; + private MeshAttachment parentMesh; + internal float[] uvs, regionUVs; + internal int[] triangles; + internal float r = 1, g = 1, b = 1, a = 1; + internal int hulllength; + internal bool inheritDeform; -namespace Spine3_6_32 { - /// Attachment that displays a texture region using a mesh. - public class MeshAttachment : VertexAttachment { - internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; - private MeshAttachment parentMesh; - internal float[] uvs, regionUVs; - internal int[] triangles; - internal float r = 1, g = 1, b = 1, a = 1; - internal int hulllength; - internal bool inheritDeform; + public int HullLength { get { return hulllength; } set { hulllength = value; } } + public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } } + /// The UV pair for each vertex, normalized within the entire texture. + public float[] UVs { get { return uvs; } set { uvs = value; } } + public int[] Triangles { get { return triangles; } set { triangles = value; } } - public int HullLength { get { return hulllength; } set { hulllength = value; } } - public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } } - /// The UV pair for each vertex, normalized within the entire texture. - public float[] UVs { get { return uvs; } set { uvs = value; } } - public int[] Triangles { get { return triangles; } set { triangles = value; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } + public string Path { get; set; } + public object RendererObject; //public Object RendererObject { get; set; } + public float RegionU { get; set; } + public float RegionV { get; set; } + public float RegionU2 { get; set; } + public float RegionV2 { get; set; } + public bool RegionRotate { get; set; } + public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } + public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. + public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } + public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. + public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } + public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. - public string Path { get; set; } - public object RendererObject; //public Object RendererObject { get; set; } - public float RegionU { get; set; } - public float RegionV { get; set; } - public float RegionU2 { get; set; } - public float RegionV2 { get; set; } - public bool RegionRotate { get; set; } - public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } - public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. - public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } - public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. - public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } - public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. + public bool InheritDeform { get { return inheritDeform; } set { inheritDeform = value; } } - public bool InheritDeform { get { return inheritDeform; } set { inheritDeform = value; } } + public MeshAttachment ParentMesh + { + get { return parentMesh; } + set + { + parentMesh = value; + if (value != null) + { + bones = value.bones; + vertices = value.vertices; + worldVerticesLength = value.worldVerticesLength; + regionUVs = value.regionUVs; + triangles = value.triangles; + HullLength = value.HullLength; + Edges = value.Edges; + Width = value.Width; + Height = value.Height; + } + } + } - public MeshAttachment ParentMesh { - get { return parentMesh; } - set { - parentMesh = value; - if (value != null) { - bones = value.bones; - vertices = value.vertices; - worldVerticesLength = value.worldVerticesLength; - regionUVs = value.regionUVs; - triangles = value.triangles; - HullLength = value.HullLength; - Edges = value.Edges; - Width = value.Width; - Height = value.Height; - } - } - } + // Nonessential. + public int[] Edges { get; set; } + public float Width { get; set; } + public float Height { get; set; } - // Nonessential. - public int[] Edges { get; set; } - public float Width { get; set; } - public float Height { get; set; } + public MeshAttachment(string name) + : base(name) + { + } - public MeshAttachment (string name) - : base(name) { - } + public void UpdateUVs() + { + float u = RegionU, v = RegionV, width = RegionU2 - RegionU, height = RegionV2 - RegionV; + float[] regionUVs = this.regionUVs; + if (this.uvs == null || this.uvs.Length != regionUVs.Length) this.uvs = new float[regionUVs.Length]; + float[] uvs = this.uvs; + if (RegionRotate) + { + for (int i = 0, n = uvs.Length; i < n; i += 2) + { + uvs[i] = u + regionUVs[i + 1] * width; + uvs[i + 1] = v + height - regionUVs[i] * height; + } + } + else + { + for (int i = 0, n = uvs.Length; i < n; i += 2) + { + uvs[i] = u + regionUVs[i] * width; + uvs[i + 1] = v + regionUVs[i + 1] * height; + } + } + } - public void UpdateUVs () { - float u = RegionU, v = RegionV, width = RegionU2 - RegionU, height = RegionV2 - RegionV; - float[] regionUVs = this.regionUVs; - if (this.uvs == null || this.uvs.Length != regionUVs.Length) this.uvs = new float[regionUVs.Length]; - float[] uvs = this.uvs; - if (RegionRotate) { - for (int i = 0, n = uvs.Length; i < n; i += 2) { - uvs[i] = u + regionUVs[i + 1] * width; - uvs[i + 1] = v + height - regionUVs[i] * height; - } - } else { - for (int i = 0, n = uvs.Length; i < n; i += 2) { - uvs[i] = u + regionUVs[i] * width; - uvs[i + 1] = v + regionUVs[i + 1] * height; - } - } - } - - override public bool ApplyDeform (VertexAttachment sourceAttachment) { - return this == sourceAttachment || (inheritDeform && parentMesh == sourceAttachment); - } - } + override public bool ApplyDeform(VertexAttachment sourceAttachment) + { + return this == sourceAttachment || (inheritDeform && parentMesh == sourceAttachment); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Attachments/PathAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Attachments/PathAttachment.cs index 8cd21d6..d905cc3 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Attachments/PathAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Attachments/PathAttachment.cs @@ -29,20 +29,22 @@ *****************************************************************************/ using System; -using System.Collections.Generic; -namespace Spine3_6_32 { - public class PathAttachment : VertexAttachment { - internal float[] lengths; - internal bool closed, constantSpeed; +namespace Spine3_6_32 +{ + public class PathAttachment : VertexAttachment + { + internal float[] lengths; + internal bool closed, constantSpeed; - /// The length in the setup pose from the start of the path to the end of each curve. - public float[] Lengths { get { return lengths; } set { lengths = value; } } - public bool Closed { get { return closed; } set { closed = value; } } - public bool ConstantSpeed { get { return constantSpeed; } set { constantSpeed = value; } } + /// The length in the setup pose from the start of the path to the end of each curve. + public float[] Lengths { get { return lengths; } set { lengths = value; } } + public bool Closed { get { return closed; } set { closed = value; } } + public bool ConstantSpeed { get { return constantSpeed; } set { constantSpeed = value; } } - public PathAttachment (String name) - : base(name) { - } - } + public PathAttachment(String name) + : base(name) + { + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Attachments/PointAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Attachments/PointAttachment.cs index 9cd16a2..eb92174 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Attachments/PointAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Attachments/PointAttachment.cs @@ -28,34 +28,39 @@ * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine3_6_32 { - /// - /// 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 Point Attachments in the Spine User Guide. - ///

- public class PointAttachment : Attachment { - internal float x, y, rotation; - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public float Rotation { get { return rotation; } set { rotation = value; } } +namespace Spine3_6_32 +{ + /// + /// 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 Point Attachments in the Spine User Guide. + ///

+ public class PointAttachment : Attachment + { + internal float x, y, rotation; + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public float Rotation { get { return rotation; } set { rotation = value; } } - public PointAttachment (string name) - : base(name) { - } + public PointAttachment(string name) + : base(name) + { + } - public void ComputeWorldPosition (Bone bone, out float ox, out float oy) { - bone.LocalToWorld(this.x, this.y, out ox, out oy); - } + public void ComputeWorldPosition(Bone bone, out float ox, out float oy) + { + bone.LocalToWorld(this.x, this.y, out ox, out oy); + } - public float ComputeWorldRotation (Bone bone) { - float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation); - float ix = cos * bone.a + sin * bone.b; - float iy = cos * bone.c + sin * bone.d; - return MathUtils.Atan2(iy, ix) * MathUtils.RadDeg; - } - } + public float ComputeWorldRotation(Bone bone) + { + float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation); + float ix = cos * bone.a + sin * bone.b; + float iy = cos * bone.c + sin * bone.d; + return MathUtils.Atan2(iy, ix) * MathUtils.RadDeg; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Attachments/RegionAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Attachments/RegionAttachment.cs index 955757b..19a4fe2 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Attachments/RegionAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Attachments/RegionAttachment.cs @@ -28,148 +28,155 @@ * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; +namespace Spine3_6_32 +{ + /// Attachment that displays a texture region. + public class RegionAttachment : Attachment + { + public const int BLX = 0; + public const int BLY = 1; + public const int ULX = 2; + public const int ULY = 3; + public const int URX = 4; + public const int URY = 5; + public const int BRX = 6; + public const int BRY = 7; -namespace Spine3_6_32 { - /// Attachment that displays a texture region. - public class RegionAttachment : Attachment { - public const int BLX = 0; - public const int BLY = 1; - public const int ULX = 2; - public const int ULY = 3; - public const int URX = 4; - public const int URY = 5; - public const int BRX = 6; - public const int BRY = 7; + internal float x, y, rotation, scaleX = 1, scaleY = 1, width, height; + internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; + internal float[] offset = new float[8], uvs = new float[8]; + internal float r = 1, g = 1, b = 1, a = 1; - internal float x, y, rotation, scaleX = 1, scaleY = 1, width, height; - internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; - internal float[] offset = new float[8], uvs = new float[8]; - internal float r = 1, g = 1, b = 1, a = 1; + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public float Rotation { get { return rotation; } set { rotation = value; } } + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + public float Width { get { return width; } set { width = value; } } + public float Height { get { return height; } set { height = value; } } - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public float Rotation { get { return rotation; } set { rotation = value; } } - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - public float ScaleY { get { return scaleY; } set { scaleY = value; } } - public float Width { get { return width; } set { width = value; } } - public float Height { get { return height; } set { height = value; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } + public string Path { get; set; } + public object RendererObject; //public object RendererObject { get; set; } + public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } + public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. + public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } + public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. + public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } + public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. - public string Path { get; set; } - public object RendererObject; //public object RendererObject { get; set; } - public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } - public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. - public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } - public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. - public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } - public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. + public float[] Offset { get { return offset; } } + public float[] UVs { get { return uvs; } } - public float[] Offset { get { return offset; } } - public float[] UVs { get { return uvs; } } + public RegionAttachment(string name) + : base(name) + { + } - public RegionAttachment (string name) - : base(name) { - } + public void UpdateOffset() + { + float width = this.width; + float height = this.height; + float scaleX = this.scaleX; + float scaleY = this.scaleY; + float regionScaleX = width / regionOriginalWidth * scaleX; + float regionScaleY = height / regionOriginalHeight * scaleY; + float localX = -width / 2 * scaleX + regionOffsetX * regionScaleX; + float localY = -height / 2 * scaleY + regionOffsetY * regionScaleY; + float localX2 = localX + regionWidth * regionScaleX; + float localY2 = localY + regionHeight * regionScaleY; + float rotation = this.rotation; + float cos = MathUtils.CosDeg(rotation); + float sin = MathUtils.SinDeg(rotation); + float x = this.x; + float y = this.y; + 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; + float[] offset = this.offset; + offset[BLX] = localXCos - localYSin; + offset[BLY] = localYCos + localXSin; + offset[ULX] = localXCos - localY2Sin; + offset[ULY] = localY2Cos + localXSin; + offset[URX] = localX2Cos - localY2Sin; + offset[URY] = localY2Cos + localX2Sin; + offset[BRX] = localX2Cos - localYSin; + offset[BRY] = localYCos + localX2Sin; + } - public void UpdateOffset () { - float width = this.width; - float height = this.height; - float scaleX = this.scaleX; - float scaleY = this.scaleY; - float regionScaleX = width / regionOriginalWidth * scaleX; - float regionScaleY = height / regionOriginalHeight * scaleY; - float localX = -width / 2 * scaleX + regionOffsetX * regionScaleX; - float localY = -height / 2 * scaleY + regionOffsetY * regionScaleY; - float localX2 = localX + regionWidth * regionScaleX; - float localY2 = localY + regionHeight * regionScaleY; - float rotation = this.rotation; - float cos = MathUtils.CosDeg(rotation); - float sin = MathUtils.SinDeg(rotation); - float x = this.x; - float y = this.y; - 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; - float[] offset = this.offset; - offset[BLX] = localXCos - localYSin; - offset[BLY] = localYCos + localXSin; - offset[ULX] = localXCos - localY2Sin; - offset[ULY] = localY2Cos + localXSin; - offset[URX] = localX2Cos - localY2Sin; - offset[URY] = localY2Cos + localX2Sin; - offset[BRX] = localX2Cos - localYSin; - offset[BRY] = localYCos + localX2Sin; - } + public void SetUVs(float u, float v, float u2, float v2, bool rotate) + { + float[] uvs = this.uvs; + // UV values differ from RegionAttachment.java + if (rotate) + { + uvs[URX] = u; + uvs[URY] = v2; + uvs[BRX] = u; + uvs[BRY] = v; + uvs[BLX] = u2; + uvs[BLY] = v; + uvs[ULX] = u2; + uvs[ULY] = v2; + } + else + { + uvs[ULX] = u; + uvs[ULY] = v2; + uvs[URX] = u; + uvs[URY] = v; + uvs[BRX] = u2; + uvs[BRY] = v; + uvs[BLX] = u2; + uvs[BLY] = v2; + } + } - public void SetUVs (float u, float v, float u2, float v2, bool rotate) { - float[] uvs = this.uvs; - // UV values differ from RegionAttachment.java - if (rotate) { - uvs[URX] = u; - uvs[URY] = v2; - uvs[BRX] = u; - uvs[BRY] = v; - uvs[BLX] = u2; - uvs[BLY] = v; - uvs[ULX] = u2; - uvs[ULY] = v2; - } else { - uvs[ULX] = u; - uvs[ULY] = v2; - uvs[URX] = u; - uvs[URY] = v; - uvs[BRX] = u2; - uvs[BRY] = v; - uvs[BLX] = u2; - uvs[BLY] = v2; - } - } + /// Transforms the attachment's four vertices to world coordinates. + /// The parent bone. + /// The output world vertices. Must have a length greater than or equal to offset + 8. + /// The worldVertices index to begin writing values. + /// The number of worldVertices entries between the value pairs written. + public void ComputeWorldVertices(Bone bone, float[] worldVertices, int offset, int stride = 2) + { + float[] vertexOffset = this.offset; + float bwx = bone.worldX, bwy = bone.worldY; + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + float offsetX, offsetY; - /// Transforms the attachment's four vertices to world coordinates. - /// The parent bone. - /// The output world vertices. Must have a length greater than or equal to offset + 8. - /// The worldVertices index to begin writing values. - /// The number of worldVertices entries between the value pairs written. - public void ComputeWorldVertices (Bone bone, float[] worldVertices, int offset, int stride = 2) { - float[] vertexOffset = this.offset; - float bwx = bone.worldX, bwy = bone.worldY; - float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - float offsetX, offsetY; + // Vertex order is different from RegionAttachment.java + offsetX = vertexOffset[BRX]; // 0 + offsetY = vertexOffset[BRY]; // 1 + worldVertices[offset] = offsetX * a + offsetY * b + bwx; // bl + worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; + offset += stride; - // Vertex order is different from RegionAttachment.java - offsetX = vertexOffset[BRX]; // 0 - offsetY = vertexOffset[BRY]; // 1 - worldVertices[offset] = offsetX * a + offsetY * b + bwx; // bl - worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; - offset += stride; + offsetX = vertexOffset[BLX]; // 2 + offsetY = vertexOffset[BLY]; // 3 + worldVertices[offset] = offsetX * a + offsetY * b + bwx; // ul + worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; + offset += stride; - offsetX = vertexOffset[BLX]; // 2 - offsetY = vertexOffset[BLY]; // 3 - worldVertices[offset] = offsetX * a + offsetY * b + bwx; // ul - worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; - offset += stride; + offsetX = vertexOffset[ULX]; // 4 + offsetY = vertexOffset[ULY]; // 5 + worldVertices[offset] = offsetX * a + offsetY * b + bwx; // ur + worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; + offset += stride; - offsetX = vertexOffset[ULX]; // 4 - offsetY = vertexOffset[ULY]; // 5 - worldVertices[offset] = offsetX * a + offsetY * b + bwx; // ur - worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; - offset += stride; - - offsetX = vertexOffset[URX]; // 6 - offsetY = vertexOffset[URY]; // 7 - worldVertices[offset] = offsetX * a + offsetY * b + bwx; // br - worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; - //offset += stride; - } - } + offsetX = vertexOffset[URX]; // 6 + offsetY = vertexOffset[URY]; // 7 + worldVertices[offset] = offsetX * a + offsetY * b + bwx; // br + worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; + //offset += stride; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Attachments/VertexAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Attachments/VertexAttachment.cs index f57f32f..f279523 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Attachments/VertexAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Attachments/VertexAttachment.cs @@ -30,101 +30,118 @@ using System; -namespace Spine3_6_32 { - /// >An attachment with vertices that are transformed by one or more bones and can be deformed by a slot's vertices. - public class VertexAttachment : Attachment { - static int nextID = 0; - static readonly Object nextIdLock = new Object(); +namespace Spine3_6_32 +{ + /// >An attachment with vertices that are transformed by one or more bones and can be deformed by a slot's vertices. + public class VertexAttachment : Attachment + { + static int nextID = 0; + static readonly Object nextIdLock = new Object(); - internal readonly int id; - internal int[] bones; - internal float[] vertices; - internal int worldVerticesLength; + internal readonly int id; + internal int[] bones; + internal float[] vertices; + internal int worldVerticesLength; - /// Gets a unique ID for this attachment. - public int Id { get { return id; } } - public int[] Bones { get { return bones; } set { bones = value; } } - public float[] Vertices { get { return vertices; } set { vertices = value; } } - public int WorldVerticesLength { get { return worldVerticesLength; } set { worldVerticesLength = value; } } + /// Gets a unique ID for this attachment. + public int Id { get { return id; } } + public int[] Bones { get { return bones; } set { bones = value; } } + public float[] Vertices { get { return vertices; } set { vertices = value; } } + public int WorldVerticesLength { get { return worldVerticesLength; } set { worldVerticesLength = value; } } - public VertexAttachment (string name) - : base(name) { + public VertexAttachment(string name) + : base(name) + { - lock (VertexAttachment.nextIdLock) { - id = (VertexAttachment.nextID++ & 65535) << 11; - } - } + lock (VertexAttachment.nextIdLock) + { + id = (VertexAttachment.nextID++ & 65535) << 11; + } + } - public void ComputeWorldVertices (Slot slot, float[] worldVertices) { - ComputeWorldVertices(slot, 0, worldVerticesLength, worldVertices, 0); - } + public void ComputeWorldVertices(Slot slot, float[] worldVertices) + { + ComputeWorldVertices(slot, 0, worldVerticesLength, worldVertices, 0); + } - /// Transforms local vertices to world coordinates. - /// The index of the first value to transform. Each vertex has 2 values, x and y. - /// The number of world vertex values to output. Must be less than or equal to - start. - /// The output world vertices. Must have a length greater than or equal to + . - /// The index to begin writing values. - /// The number of entries between the value pairs written. - public void ComputeWorldVertices (Slot slot, int start, int count, float[] worldVertices, int offset, int stride = 2) { - count = offset + (count >> 1) * stride; - Skeleton skeleton = slot.bone.skeleton; - var deformArray = slot.attachmentVertices; - float[] vertices = this.vertices; - int[] bones = this.bones; - if (bones == null) { - if (deformArray.Count > 0) vertices = deformArray.Items; - Bone bone = slot.bone; - float x = bone.worldX, y = bone.worldY; - float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - for (int vv = start, w = offset; w < count; vv += 2, w += stride) { - float vx = vertices[vv], 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 (int i = 0; i < start; i += 2) { - int n = bones[v]; - v += n + 1; - skip += n; - } - var skeletonBones = skeleton.bones.Items; - if (deformArray.Count == 0) { - for (int w = offset, b = skip * 3; w < count; w += stride) { - float wx = 0, wy = 0; - int n = bones[v++]; - n += v; - for (; v < n; v++, b += 3) { - Bone bone = skeletonBones[bones[v]]; - float vx = vertices[b], vy = vertices[b + 1], 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 { - float[] deform = deformArray.Items; - for (int w = offset, b = skip * 3, f = skip << 1; w < count; w += stride) { - float wx = 0, wy = 0; - int n = bones[v++]; - n += v; - for (; v < n; v++, b += 3, f += 2) { - Bone bone = skeletonBones[bones[v]]; - float vx = vertices[b] + deform[f], vy = vertices[b + 1] + deform[f + 1], 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; - } - } - } + /// Transforms local vertices to world coordinates. + /// The index of the first value to transform. Each vertex has 2 values, x and y. + /// The number of world vertex values to output. Must be less than or equal to - start. + /// The output world vertices. Must have a length greater than or equal to + . + /// The index to begin writing values. + /// The number of entries between the value pairs written. + public void ComputeWorldVertices(Slot slot, int start, int count, float[] worldVertices, int offset, int stride = 2) + { + count = offset + (count >> 1) * stride; + Skeleton skeleton = slot.bone.skeleton; + var deformArray = slot.attachmentVertices; + float[] vertices = this.vertices; + int[] bones = this.bones; + if (bones == null) + { + if (deformArray.Count > 0) vertices = deformArray.Items; + Bone bone = slot.bone; + float x = bone.worldX, y = bone.worldY; + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + for (int vv = start, w = offset; w < count; vv += 2, w += stride) + { + float vx = vertices[vv], 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 (int i = 0; i < start; i += 2) + { + int n = bones[v]; + v += n + 1; + skip += n; + } + var skeletonBones = skeleton.bones.Items; + if (deformArray.Count == 0) + { + for (int w = offset, b = skip * 3; w < count; w += stride) + { + float wx = 0, wy = 0; + int n = bones[v++]; + n += v; + for (; v < n; v++, b += 3) + { + Bone bone = skeletonBones[bones[v]]; + float vx = vertices[b], vy = vertices[b + 1], 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 + { + float[] deform = deformArray.Items; + for (int w = offset, b = skip * 3, f = skip << 1; w < count; w += stride) + { + float wx = 0, wy = 0; + int n = bones[v++]; + n += v; + for (; v < n; v++, b += 3, f += 2) + { + Bone bone = skeletonBones[bones[v]]; + float vx = vertices[b] + deform[f], vy = vertices[b + 1] + deform[f + 1], 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; + } + } + } - /// Returns true if a deform originally applied to the specified attachment should be applied to this attachment. - virtual public bool ApplyDeform (VertexAttachment sourceAttachment) { - return this == sourceAttachment; - } - } + /// Returns true if a deform originally applied to the specified attachment should be applied to this attachment. + virtual public bool ApplyDeform(VertexAttachment sourceAttachment) + { + return this == sourceAttachment; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/BlendMode.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/BlendMode.cs index b6535be..652bacc 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/BlendMode.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/BlendMode.cs @@ -28,8 +28,10 @@ * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine3_6_32 { - public enum BlendMode { - Normal, Additive, Multiply, Screen - } +namespace Spine3_6_32 +{ + public enum BlendMode + { + Normal, Additive, Multiply, Screen + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Bone.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Bone.cs index 1245286..532345e 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Bone.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Bone.cs @@ -30,359 +30,395 @@ using System; -namespace Spine3_6_32 { - /// - /// 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. - /// - /// - public class Bone : IUpdatable { - static public bool yDown; - - internal BoneData data; - internal Skeleton skeleton; - internal Bone parent; - internal ExposedList children = new ExposedList(); - internal float x, y, rotation, scaleX, scaleY, shearX, shearY; - internal float ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY; - internal bool appliedValid; - - internal float a, b, worldX; - internal float c, d, worldY; - -// internal float worldSignX, worldSignY; -// public float WorldSignX { get { return worldSignX; } } -// public float WorldSignY { get { return worldSignY; } } - - internal bool sorted; - - public BoneData Data { get { return data; } } - public Skeleton Skeleton { get { return skeleton; } } - public Bone Parent { get { return parent; } } - public ExposedList Children { get { return children; } } - /// The local X translation. - public float X { get { return x; } set { x = value; } } - /// The local Y translation. - public float Y { get { return y; } set { y = value; } } - /// The local rotation. - public float Rotation { get { return rotation; } set { rotation = value; } } - - /// The local scaleX. - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - - /// The local scaleY. - public float ScaleY { get { return scaleY; } set { scaleY = value; } } - - /// The local shearX. - public float ShearX { get { return shearX; } set { shearX = value; } } - - /// The local shearY. - public float ShearY { get { return shearY; } set { shearY = value; } } - - /// The rotation, as calculated by any constraints. - public float AppliedRotation { get { return arotation; } set { arotation = value; } } - - /// The applied local x translation. - public float AX { get { return ax; } set { ax = value; } } - - /// The applied local y translation. - public float AY { get { return ay; } set { ay = value; } } - - /// The applied local scaleX. - public float AScaleX { get { return ascaleX; } set { ascaleX = value; } } - - /// The applied local scaleY. - public float AScaleY { get { return ascaleY; } set { ascaleY = value; } } - - /// The applied local shearX. - public float AShearX { get { return ashearX; } set { ashearX = value; } } - - /// The applied local shearY. - public float AShearY { get { return ashearY; } set { ashearY = value; } } - - public float A { get { return a; } } - public float B { get { return b; } } - public float C { get { return c; } } - public float D { get { return d; } } - - public float WorldX { get { return worldX; } } - public float WorldY { get { return worldY; } } - public float WorldRotationX { get { return MathUtils.Atan2(c, a) * MathUtils.RadDeg; } } - public float WorldRotationY { get { return MathUtils.Atan2(d, b) * MathUtils.RadDeg; } } - - /// Returns the magnitide (always positive) of the world scale X. - public float WorldScaleX { get { return (float)Math.Sqrt(a * a + c * c); } } - /// Returns the magnitide (always positive) of the world scale Y. - public float WorldScaleY { get { return (float)Math.Sqrt(b * b + d * d); } } - - /// May be null. - public Bone (BoneData data, Skeleton skeleton, Bone parent) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.data = data; - this.skeleton = skeleton; - this.parent = parent; - SetToSetupPose(); - } - - /// Same as . This method exists for Bone to implement . - public void Update () { - UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); - } - - /// Computes the world transform using the parent bone and this bone's local transform. - public void UpdateWorldTransform () { - UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); - } - - /// Computes the world transform using the parent bone and the specified local transform. - public void UpdateWorldTransform (float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY) { - ax = x; - ay = y; - arotation = rotation; - ascaleX = scaleX; - ascaleY = scaleY; - ashearX = shearX; - ashearY = shearY; - appliedValid = true; - Skeleton skeleton = this.skeleton; - - Bone parent = this.parent; - if (parent == null) { // Root bone. - float rotationY = rotation + 90 + shearY; - float la = MathUtils.CosDeg(rotation + shearX) * scaleX; - float lb = MathUtils.CosDeg(rotationY) * scaleY; - float lc = MathUtils.SinDeg(rotation + shearX) * scaleX; - float ld = MathUtils.SinDeg(rotationY) * scaleY; - if (skeleton.flipX) { - x = -x; - la = -la; - lb = -lb; - } - if (skeleton.flipY != yDown) { - y = -y; - lc = -lc; - ld = -ld; - } - a = la; - b = lb; - c = lc; - d = ld; - worldX = x + skeleton.x; - worldY = y + skeleton.y; -// worldSignX = Math.Sign(scaleX); -// worldSignY = Math.Sign(scaleY); - return; - } - - float 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; -// worldSignX = parent.worldSignX * Math.Sign(scaleX); -// worldSignY = parent.worldSignY * Math.Sign(scaleY); - - switch (data.transformMode) { - case TransformMode.Normal: { - float rotationY = rotation + 90 + shearY; - float la = MathUtils.CosDeg(rotation + shearX) * scaleX; - float lb = MathUtils.CosDeg(rotationY) * scaleY; - float lc = MathUtils.SinDeg(rotation + shearX) * scaleX; - float ld = MathUtils.SinDeg(rotationY) * scaleY; - a = pa * la + pb * lc; - b = pa * lb + pb * ld; - c = pc * la + pd * lc; - d = pc * lb + pd * ld; - return; - } - case TransformMode.OnlyTranslation: { - float rotationY = rotation + 90 + shearY; - a = MathUtils.CosDeg(rotation + shearX) * scaleX; - b = MathUtils.CosDeg(rotationY) * scaleY; - c = MathUtils.SinDeg(rotation + shearX) * scaleX; - d = MathUtils.SinDeg(rotationY) * scaleY; - break; - } - case TransformMode.NoRotationOrReflection: { - float s = pa * pa + pc * pc, prx; - if (s > 0.0001f) { - s = Math.Abs(pa * pd - pb * pc) / s; - pb = pc * s; - pd = pa * s; - prx = MathUtils.Atan2(pc, pa) * MathUtils.RadDeg; - } else { - pa = 0; - pc = 0; - prx = 90 - MathUtils.Atan2(pd, pb) * MathUtils.RadDeg; - } - float rx = rotation + shearX - prx; - float ry = rotation + shearY - prx + 90; - float la = MathUtils.CosDeg(rx) * scaleX; - float lb = MathUtils.CosDeg(ry) * scaleY; - float lc = MathUtils.SinDeg(rx) * scaleX; - float ld = MathUtils.SinDeg(ry) * scaleY; - a = pa * la - pb * lc; - b = pa * lb - pb * ld; - c = pc * la + pd * lc; - d = pc * lb + pd * ld; - break; - } - case TransformMode.NoScale: - case TransformMode.NoScaleOrReflection: { - float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation); - float za = pa * cos + pb * sin; - float zc = pc * cos + pd * sin; - float s = (float)Math.Sqrt(za * za + zc * zc); - if (s > 0.00001f) s = 1 / s; - za *= s; - zc *= s; - s = (float)Math.Sqrt(za * za + zc * zc); - float r = MathUtils.PI / 2 + MathUtils.Atan2(zc, za); - float zb = MathUtils.Cos(r) * s; - float zd = MathUtils.Sin(r) * s; - float la = MathUtils.CosDeg(shearX) * scaleX; - float lb = MathUtils.CosDeg(90 + shearY) * scaleY; - float lc = MathUtils.SinDeg(shearX) * scaleX; - float ld = MathUtils.SinDeg(90 + shearY) * scaleY; - a = za * la + zb * lc; - b = za * lb + zb * ld; - c = zc * la + zd * lc; - d = zc * lb + zd * ld; - if (data.transformMode != TransformMode.NoScaleOrReflection ? pa * pd - pb * pc < 0 : skeleton.flipX != skeleton.flipY) { - b = -b; - d = -d; - } - return; - } - } - - if (skeleton.flipX) { - a = -a; - b = -b; - } - if (skeleton.flipY != Bone.yDown) { - c = -c; - d = -d; - } - } - - public void SetToSetupPose () { - BoneData data = this.data; - x = data.x; - y = data.y; - rotation = data.rotation; - scaleX = data.scaleX; - scaleY = data.scaleY; - shearX = data.shearX; - shearY = data.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. - /// - internal void UpdateAppliedTransform () { - appliedValid = true; - Bone parent = this.parent; - if (parent == null) { - ax = worldX; - ay = worldY; - arotation = MathUtils.Atan2(c, a) * MathUtils.RadDeg; - ascaleX = (float)Math.Sqrt(a * a + c * c); - ascaleY = (float)Math.Sqrt(b * b + d * d); - ashearX = 0; - ashearY = MathUtils.Atan2(a * b + c * d, a * d - b * c) * MathUtils.RadDeg; - return; - } - float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d; - float pid = 1 / (pa * pd - pb * pc); - float dx = worldX - parent.worldX, dy = worldY - parent.worldY; - ax = (dx * pd * pid - dy * pb * pid); - ay = (dy * pa * pid - dx * pc * pid); - float ia = pid * pd; - float id = pid * pa; - float ib = pid * pb; - float ic = pid * pc; - float ra = ia * a - ib * c; - float rb = ia * b - ib * d; - float rc = id * c - ic * a; - float rd = id * d - ic * b; - ashearX = 0; - ascaleX = (float)Math.Sqrt(ra * ra + rc * rc); - if (ascaleX > 0.0001f) { - float det = ra * rd - rb * rc; - ascaleY = det / ascaleX; - ashearY = MathUtils.Atan2(ra * rb + rc * rd, det) * MathUtils.RadDeg; - arotation = MathUtils.Atan2(rc, ra) * MathUtils.RadDeg; - } else { - ascaleX = 0; - ascaleY = (float)Math.Sqrt(rb * rb + rd * rd); - ashearY = 0; - arotation = 90 - MathUtils.Atan2(rd, rb) * MathUtils.RadDeg; - } - } - - public void WorldToLocal (float worldX, float worldY, out float localX, out float localY) { - float a = this.a, b = this.b, c = this.c, d = this.d; - float invDet = 1 / (a * d - b * c); - float x = worldX - this.worldX, y = worldY - this.worldY; - localX = (x * d * invDet - y * b * invDet); - localY = (y * a * invDet - x * c * invDet); - } - - public void LocalToWorld (float localX, float localY, out float worldX, out float worldY) { - worldX = localX * a + localY * b + this.worldX; - worldY = localX * c + localY * d + this.worldY; - } - - public float WorldToLocalRotationX { - get { - Bone parent = this.parent; - if (parent == null) return arotation; - float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, a = this.a, c = this.c; - return MathUtils.Atan2(pa * c - pc * a, pd * a - pb * c) * MathUtils.RadDeg; - } - } - - public float WorldToLocalRotationY { - get { - Bone parent = this.parent; - if (parent == null) return arotation; - float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, b = this.b, d = this.d; - return MathUtils.Atan2(pa * d - pc * b, pd * b - pb * d) * MathUtils.RadDeg; - } - } - - public float WorldToLocalRotation (float worldRotation) { - float sin = MathUtils.SinDeg(worldRotation), cos = MathUtils.CosDeg(worldRotation); - return MathUtils.Atan2(a * sin - c * cos, d * cos - b * sin) * MathUtils.RadDeg; - } - - public float LocalToWorldRotation (float localRotation) { - float sin = MathUtils.SinDeg(localRotation), cos = MathUtils.CosDeg(localRotation); - return MathUtils.Atan2(cos * c + sin * d, cos * a + sin * b) * MathUtils.RadDeg; - } - - /// - /// Rotates the world transform the specified amount and sets isAppliedValid to false. - /// - /// Degrees. - public void RotateWorld (float degrees) { - float a = this.a, b = this.b, c = this.c, d = this.d; - float cos = MathUtils.CosDeg(degrees), sin = MathUtils.SinDeg(degrees); - this.a = cos * a - sin * c; - this.b = cos * b - sin * d; - this.c = sin * a + cos * c; - this.d = sin * b + cos * d; - appliedValid = false; - } - - override public string ToString () { - return data.name; - } - } +namespace Spine3_6_32 +{ + /// + /// 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. + /// + /// + public class Bone : IUpdatable + { + static public bool yDown; + + internal BoneData data; + internal Skeleton skeleton; + internal Bone parent; + internal ExposedList children = new ExposedList(); + internal float x, y, rotation, scaleX, scaleY, shearX, shearY; + internal float ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY; + internal bool appliedValid; + + internal float a, b, worldX; + internal float c, d, worldY; + + // internal float worldSignX, worldSignY; + // public float WorldSignX { get { return worldSignX; } } + // public float WorldSignY { get { return worldSignY; } } + + internal bool sorted; + + public BoneData Data { get { return data; } } + public Skeleton Skeleton { get { return skeleton; } } + public Bone Parent { get { return parent; } } + public ExposedList Children { get { return children; } } + /// The local X translation. + public float X { get { return x; } set { x = value; } } + /// The local Y translation. + public float Y { get { return y; } set { y = value; } } + /// The local rotation. + public float Rotation { get { return rotation; } set { rotation = value; } } + + /// The local scaleX. + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + + /// The local scaleY. + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + + /// The local shearX. + public float ShearX { get { return shearX; } set { shearX = value; } } + + /// The local shearY. + public float ShearY { get { return shearY; } set { shearY = value; } } + + /// The rotation, as calculated by any constraints. + public float AppliedRotation { get { return arotation; } set { arotation = value; } } + + /// The applied local x translation. + public float AX { get { return ax; } set { ax = value; } } + + /// The applied local y translation. + public float AY { get { return ay; } set { ay = value; } } + + /// The applied local scaleX. + public float AScaleX { get { return ascaleX; } set { ascaleX = value; } } + + /// The applied local scaleY. + public float AScaleY { get { return ascaleY; } set { ascaleY = value; } } + + /// The applied local shearX. + public float AShearX { get { return ashearX; } set { ashearX = value; } } + + /// The applied local shearY. + public float AShearY { get { return ashearY; } set { ashearY = value; } } + + public float A { get { return a; } } + public float B { get { return b; } } + public float C { get { return c; } } + public float D { get { return d; } } + + public float WorldX { get { return worldX; } } + public float WorldY { get { return worldY; } } + public float WorldRotationX { get { return MathUtils.Atan2(c, a) * MathUtils.RadDeg; } } + public float WorldRotationY { get { return MathUtils.Atan2(d, b) * MathUtils.RadDeg; } } + + /// Returns the magnitide (always positive) of the world scale X. + public float WorldScaleX { get { return (float)Math.Sqrt(a * a + c * c); } } + /// Returns the magnitide (always positive) of the world scale Y. + public float WorldScaleY { get { return (float)Math.Sqrt(b * b + d * d); } } + + /// May be null. + public Bone(BoneData data, Skeleton skeleton, Bone parent) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + this.skeleton = skeleton; + this.parent = parent; + SetToSetupPose(); + } + + /// Same as . This method exists for Bone to implement . + public void Update() + { + UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); + } + + /// Computes the world transform using the parent bone and this bone's local transform. + public void UpdateWorldTransform() + { + UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); + } + + /// Computes the world transform using the parent bone and the specified local transform. + public void UpdateWorldTransform(float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY) + { + ax = x; + ay = y; + arotation = rotation; + ascaleX = scaleX; + ascaleY = scaleY; + ashearX = shearX; + ashearY = shearY; + appliedValid = true; + Skeleton skeleton = this.skeleton; + + Bone parent = this.parent; + if (parent == null) + { // Root bone. + float rotationY = rotation + 90 + shearY; + float la = MathUtils.CosDeg(rotation + shearX) * scaleX; + float lb = MathUtils.CosDeg(rotationY) * scaleY; + float lc = MathUtils.SinDeg(rotation + shearX) * scaleX; + float ld = MathUtils.SinDeg(rotationY) * scaleY; + if (skeleton.flipX) + { + x = -x; + la = -la; + lb = -lb; + } + if (skeleton.flipY != yDown) + { + y = -y; + lc = -lc; + ld = -ld; + } + a = la; + b = lb; + c = lc; + d = ld; + worldX = x + skeleton.x; + worldY = y + skeleton.y; + // worldSignX = Math.Sign(scaleX); + // worldSignY = Math.Sign(scaleY); + return; + } + + float 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; + // worldSignX = parent.worldSignX * Math.Sign(scaleX); + // worldSignY = parent.worldSignY * Math.Sign(scaleY); + + switch (data.transformMode) + { + case TransformMode.Normal: + { + float rotationY = rotation + 90 + shearY; + float la = MathUtils.CosDeg(rotation + shearX) * scaleX; + float lb = MathUtils.CosDeg(rotationY) * scaleY; + float lc = MathUtils.SinDeg(rotation + shearX) * scaleX; + float ld = MathUtils.SinDeg(rotationY) * scaleY; + a = pa * la + pb * lc; + b = pa * lb + pb * ld; + c = pc * la + pd * lc; + d = pc * lb + pd * ld; + return; + } + case TransformMode.OnlyTranslation: + { + float rotationY = rotation + 90 + shearY; + a = MathUtils.CosDeg(rotation + shearX) * scaleX; + b = MathUtils.CosDeg(rotationY) * scaleY; + c = MathUtils.SinDeg(rotation + shearX) * scaleX; + d = MathUtils.SinDeg(rotationY) * scaleY; + break; + } + case TransformMode.NoRotationOrReflection: + { + float s = pa * pa + pc * pc, prx; + if (s > 0.0001f) + { + s = Math.Abs(pa * pd - pb * pc) / s; + pb = pc * s; + pd = pa * s; + prx = MathUtils.Atan2(pc, pa) * MathUtils.RadDeg; + } + else + { + pa = 0; + pc = 0; + prx = 90 - MathUtils.Atan2(pd, pb) * MathUtils.RadDeg; + } + float rx = rotation + shearX - prx; + float ry = rotation + shearY - prx + 90; + float la = MathUtils.CosDeg(rx) * scaleX; + float lb = MathUtils.CosDeg(ry) * scaleY; + float lc = MathUtils.SinDeg(rx) * scaleX; + float ld = MathUtils.SinDeg(ry) * scaleY; + a = pa * la - pb * lc; + b = pa * lb - pb * ld; + c = pc * la + pd * lc; + d = pc * lb + pd * ld; + break; + } + case TransformMode.NoScale: + case TransformMode.NoScaleOrReflection: + { + float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation); + float za = pa * cos + pb * sin; + float zc = pc * cos + pd * sin; + float s = (float)Math.Sqrt(za * za + zc * zc); + if (s > 0.00001f) s = 1 / s; + za *= s; + zc *= s; + s = (float)Math.Sqrt(za * za + zc * zc); + float r = MathUtils.PI / 2 + MathUtils.Atan2(zc, za); + float zb = MathUtils.Cos(r) * s; + float zd = MathUtils.Sin(r) * s; + float la = MathUtils.CosDeg(shearX) * scaleX; + float lb = MathUtils.CosDeg(90 + shearY) * scaleY; + float lc = MathUtils.SinDeg(shearX) * scaleX; + float ld = MathUtils.SinDeg(90 + shearY) * scaleY; + a = za * la + zb * lc; + b = za * lb + zb * ld; + c = zc * la + zd * lc; + d = zc * lb + zd * ld; + if (data.transformMode != TransformMode.NoScaleOrReflection ? pa * pd - pb * pc < 0 : skeleton.flipX != skeleton.flipY) + { + b = -b; + d = -d; + } + return; + } + } + + if (skeleton.flipX) + { + a = -a; + b = -b; + } + if (skeleton.flipY != Bone.yDown) + { + c = -c; + d = -d; + } + } + + public void SetToSetupPose() + { + BoneData data = this.data; + x = data.x; + y = data.y; + rotation = data.rotation; + scaleX = data.scaleX; + scaleY = data.scaleY; + shearX = data.shearX; + shearY = data.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. + /// + internal void UpdateAppliedTransform() + { + appliedValid = true; + Bone parent = this.parent; + if (parent == null) + { + ax = worldX; + ay = worldY; + arotation = MathUtils.Atan2(c, a) * MathUtils.RadDeg; + ascaleX = (float)Math.Sqrt(a * a + c * c); + ascaleY = (float)Math.Sqrt(b * b + d * d); + ashearX = 0; + ashearY = MathUtils.Atan2(a * b + c * d, a * d - b * c) * MathUtils.RadDeg; + return; + } + float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d; + float pid = 1 / (pa * pd - pb * pc); + float dx = worldX - parent.worldX, dy = worldY - parent.worldY; + ax = (dx * pd * pid - dy * pb * pid); + ay = (dy * pa * pid - dx * pc * pid); + float ia = pid * pd; + float id = pid * pa; + float ib = pid * pb; + float ic = pid * pc; + float ra = ia * a - ib * c; + float rb = ia * b - ib * d; + float rc = id * c - ic * a; + float rd = id * d - ic * b; + ashearX = 0; + ascaleX = (float)Math.Sqrt(ra * ra + rc * rc); + if (ascaleX > 0.0001f) + { + float det = ra * rd - rb * rc; + ascaleY = det / ascaleX; + ashearY = MathUtils.Atan2(ra * rb + rc * rd, det) * MathUtils.RadDeg; + arotation = MathUtils.Atan2(rc, ra) * MathUtils.RadDeg; + } + else + { + ascaleX = 0; + ascaleY = (float)Math.Sqrt(rb * rb + rd * rd); + ashearY = 0; + arotation = 90 - MathUtils.Atan2(rd, rb) * MathUtils.RadDeg; + } + } + + public void WorldToLocal(float worldX, float worldY, out float localX, out float localY) + { + float a = this.a, b = this.b, c = this.c, d = this.d; + float invDet = 1 / (a * d - b * c); + float x = worldX - this.worldX, y = worldY - this.worldY; + localX = (x * d * invDet - y * b * invDet); + localY = (y * a * invDet - x * c * invDet); + } + + public void LocalToWorld(float localX, float localY, out float worldX, out float worldY) + { + worldX = localX * a + localY * b + this.worldX; + worldY = localX * c + localY * d + this.worldY; + } + + public float WorldToLocalRotationX + { + get + { + Bone parent = this.parent; + if (parent == null) return arotation; + float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, a = this.a, c = this.c; + return MathUtils.Atan2(pa * c - pc * a, pd * a - pb * c) * MathUtils.RadDeg; + } + } + + public float WorldToLocalRotationY + { + get + { + Bone parent = this.parent; + if (parent == null) return arotation; + float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, b = this.b, d = this.d; + return MathUtils.Atan2(pa * d - pc * b, pd * b - pb * d) * MathUtils.RadDeg; + } + } + + public float WorldToLocalRotation(float worldRotation) + { + float sin = MathUtils.SinDeg(worldRotation), cos = MathUtils.CosDeg(worldRotation); + return MathUtils.Atan2(a * sin - c * cos, d * cos - b * sin) * MathUtils.RadDeg; + } + + public float LocalToWorldRotation(float localRotation) + { + float sin = MathUtils.SinDeg(localRotation), cos = MathUtils.CosDeg(localRotation); + return MathUtils.Atan2(cos * c + sin * d, cos * a + sin * b) * MathUtils.RadDeg; + } + + /// + /// Rotates the world transform the specified amount and sets isAppliedValid to false. + /// + /// Degrees. + public void RotateWorld(float degrees) + { + float a = this.a, b = this.b, c = this.c, d = this.d; + float cos = MathUtils.CosDeg(degrees), sin = MathUtils.SinDeg(degrees); + this.a = cos * a - sin * c; + this.b = cos * b - sin * d; + this.c = sin * a + cos * c; + this.d = sin * b + cos * d; + appliedValid = false; + } + + override public string ToString() + { + return data.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/BoneData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/BoneData.cs index 998be14..2627431 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/BoneData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/BoneData.cs @@ -30,71 +30,76 @@ using System; -namespace Spine3_6_32 { - public class BoneData { - internal int index; - internal string name; - internal BoneData parent; - internal float length; - internal float x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY; - internal TransformMode transformMode = TransformMode.Normal; - - /// The index of the bone in Skeleton.Bones - public int Index { get { return index; } } - - /// The name of the bone, which is unique within the skeleton. - public string Name { get { return name; } } - - /// May be null. - public BoneData Parent { get { return parent; } } - - public float Length { get { return length; } set { length = value; } } - - /// Local X translation. - public float X { get { return x; } set { x = value; } } - - /// Local Y translation. - public float Y { get { return y; } set { y = value; } } - - /// Local rotation. - public float Rotation { get { return rotation; } set { rotation = value; } } - - /// Local scaleX. - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - - /// Local scaleY. - public float ScaleY { get { return scaleY; } set { scaleY = value; } } - - /// Local shearX. - public float ShearX { get { return shearX; } set { shearX = value; } } - - /// Local shearY. - public float ShearY { get { return shearY; } set { shearY = value; } } - - /// The transform mode for how parent world transforms affect this bone. - public TransformMode TransformMode { get { return transformMode; } set { transformMode = value; } } - - /// May be null. - public BoneData (int index, string name, BoneData parent) { - if (index < 0) throw new ArgumentException("index must be >= 0", "index"); - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.index = index; - this.name = name; - this.parent = parent; - } - - override public string ToString () { - return name; - } - } - - [Flags] - public enum TransformMode { - //0000 0 Flip Scale Rotation - Normal = 0, // 0000 - OnlyTranslation = 7, // 0111 - NoRotationOrReflection = 1, // 0001 - NoScale = 2, // 0010 - NoScaleOrReflection = 6, // 0110 - } +namespace Spine3_6_32 +{ + public class BoneData + { + internal int index; + internal string name; + internal BoneData parent; + internal float length; + internal float x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY; + internal TransformMode transformMode = TransformMode.Normal; + + /// The index of the bone in Skeleton.Bones + public int Index { get { return index; } } + + /// The name of the bone, which is unique within the skeleton. + public string Name { get { return name; } } + + /// May be null. + public BoneData Parent { get { return parent; } } + + public float Length { get { return length; } set { length = value; } } + + /// Local X translation. + public float X { get { return x; } set { x = value; } } + + /// Local Y translation. + public float Y { get { return y; } set { y = value; } } + + /// Local rotation. + public float Rotation { get { return rotation; } set { rotation = value; } } + + /// Local scaleX. + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + + /// Local scaleY. + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + + /// Local shearX. + public float ShearX { get { return shearX; } set { shearX = value; } } + + /// Local shearY. + public float ShearY { get { return shearY; } set { shearY = value; } } + + /// The transform mode for how parent world transforms affect this bone. + public TransformMode TransformMode { get { return transformMode; } set { transformMode = value; } } + + /// May be null. + public BoneData(int index, string name, BoneData parent) + { + if (index < 0) throw new ArgumentException("index must be >= 0", "index"); + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.index = index; + this.name = name; + this.parent = parent; + } + + override public string ToString() + { + return name; + } + } + + [Flags] + public enum TransformMode + { + //0000 0 Flip Scale Rotation + Normal = 0, // 0000 + OnlyTranslation = 7, // 0111 + NoRotationOrReflection = 1, // 0001 + NoScale = 2, // 0010 + NoScaleOrReflection = 6, // 0110 + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Event.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Event.cs index 7da2611..06ec6b7 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Event.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Event.cs @@ -30,31 +30,35 @@ using System; -namespace Spine3_6_32 { - /// Stores the current pose values for an Event. - public class Event { - internal readonly EventData data; - internal readonly float time; - internal int intValue; - internal float floatValue; - internal string stringValue; +namespace Spine3_6_32 +{ + /// Stores the current pose values for an Event. + public class Event + { + internal readonly EventData data; + internal readonly float time; + internal int intValue; + internal float floatValue; + internal string stringValue; - public EventData Data { get { return data; } } - /// The animation time this event was keyed. - public float Time { get { return time; } } + public EventData Data { get { return data; } } + /// The animation time this event was keyed. + public float Time { get { return time; } } - public int Int { get { return intValue; } set { intValue = value; } } - public float Float { get { return floatValue; } set { floatValue = value; } } - public string String { get { return stringValue; } set { stringValue = value; } } + public int Int { get { return intValue; } set { intValue = value; } } + public float Float { get { return floatValue; } set { floatValue = value; } } + public string String { get { return stringValue; } set { stringValue = value; } } - public Event (float time, EventData data) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - this.time = time; - this.data = data; - } + public Event(float time, EventData data) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + this.time = time; + this.data = data; + } - override public string ToString () { - return this.data.Name; - } - } + override public string ToString() + { + return this.data.Name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/EventData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/EventData.cs index bfa6336..b7f9583 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/EventData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/EventData.cs @@ -30,24 +30,28 @@ using System; -namespace Spine3_6_32 { - /// Stores the setup pose values for an Event. - public class EventData { - internal string name; +namespace Spine3_6_32 +{ + /// Stores the setup pose values for an Event. + public class EventData + { + internal string name; - /// The name of the event, which is unique within the skeleton. - public string Name { get { return name; } } - public int Int { get; set; } - public float Float { get; set; } - public string String { get; set; } + /// The name of the event, which is unique within the skeleton. + public string Name { get { return name; } } + public int Int { get; set; } + public float Float { get; set; } + public string String { get; set; } - public EventData (string name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; - } + public EventData(string name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + } - override public string ToString () { - return Name; - } - } + override public string ToString() + { + return Name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/ExposedList.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/ExposedList.cs index c71cd77..3bb3cd3 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/ExposedList.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/ExposedList.cs @@ -35,566 +35,658 @@ using System.Collections.Generic; using System.Diagnostics; -namespace Spine3_6_32 { - [Serializable] - [DebuggerDisplay("Count={Count}")] - public class ExposedList : IEnumerable { - public T[] Items; - public int Count; - private const int DefaultCapacity = 4; - private static readonly T[] EmptyArray = new T[0]; - private int version; - - public ExposedList () { - Items = EmptyArray; - } - - public ExposedList (IEnumerable collection) { - CheckCollection(collection); - - // initialize to needed size (if determinable) - ICollection c = collection as ICollection; - if (c == null) { - Items = EmptyArray; - AddEnumerable(collection); - } else { - Items = new T[c.Count]; - AddCollection(c); - } - } - - public ExposedList (int capacity) { - if (capacity < 0) - throw new ArgumentOutOfRangeException("capacity"); - Items = new T[capacity]; - } - - internal ExposedList (T[] data, int size) { - Items = data; - Count = size; - } - - public void Add (T item) { - // If we check to see if we need to grow before trying to grow - // we can speed things up by 25% - if (Count == Items.Length) - GrowIfNeeded(1); - Items[Count++] = item; - version++; - } - - public void GrowIfNeeded (int newCount) { - int minimumSize = Count + newCount; - if (minimumSize > Items.Length) - Capacity = Math.Max(Math.Max(Capacity * 2, DefaultCapacity), minimumSize); - } - - public ExposedList Resize (int newSize) { - if (newSize > Items.Length) Array.Resize(ref Items, newSize); - Count = newSize; - return this; - } - - public void EnsureCapacity (int min) { - if (Items.Length < min) { - int newCapacity = Items.Length == 0 ? DefaultCapacity : Items.Length * 2; - //if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength; - if (newCapacity < min) newCapacity = min; - Capacity = newCapacity; - } - } - - private void CheckRange (int idx, int count) { - if (idx < 0) - throw new ArgumentOutOfRangeException("index"); - - if (count < 0) - throw new ArgumentOutOfRangeException("count"); - - if ((uint)idx + (uint)count > (uint)Count) - throw new ArgumentException("index and count exceed length of list"); - } - - private void AddCollection (ICollection collection) { - int collectionCount = collection.Count; - if (collectionCount == 0) - return; - - GrowIfNeeded(collectionCount); - collection.CopyTo(Items, Count); - Count += collectionCount; - } - - private void AddEnumerable (IEnumerable enumerable) { - foreach (T t in enumerable) { - Add(t); - } - } - - public void AddRange (IEnumerable collection) { - CheckCollection(collection); - - ICollection c = collection as ICollection; - if (c != null) - AddCollection(c); - else - AddEnumerable(collection); - version++; - } - - public int BinarySearch (T item) { - return Array.BinarySearch(Items, 0, Count, item); - } - - public int BinarySearch (T item, IComparer comparer) { - return Array.BinarySearch(Items, 0, Count, item, comparer); - } - - public int BinarySearch (int index, int count, T item, IComparer comparer) { - CheckRange(index, count); - return Array.BinarySearch(Items, index, count, item, comparer); - } - - public void Clear (bool clearArray = true) { - if (clearArray) - Array.Clear(Items, 0, Items.Length); - - Count = 0; - version++; - } - - public bool Contains (T item) { - return Array.IndexOf(Items, item, 0, Count) != -1; - } - - public ExposedList ConvertAll (Converter converter) { - if (converter == null) - throw new ArgumentNullException("converter"); - ExposedList u = new ExposedList(Count); - for (int i = 0; i < Count; i++) - u.Items[i] = converter(Items[i]); - - u.Count = Count; - return u; - } - - public void CopyTo (T[] array) { - Array.Copy(Items, 0, array, 0, Count); - } - - public void CopyTo (T[] array, int arrayIndex) { - Array.Copy(Items, 0, array, arrayIndex, Count); - } - - public void CopyTo (int index, T[] array, int arrayIndex, int count) { - CheckRange(index, count); - Array.Copy(Items, index, array, arrayIndex, count); - } - - - - public bool Exists (Predicate match) { - CheckMatch(match); - return GetIndex(0, Count, match) != -1; - } - - public T Find (Predicate match) { - CheckMatch(match); - int i = GetIndex(0, Count, match); - return (i != -1) ? Items[i] : default(T); - } - - private static void CheckMatch (Predicate match) { - if (match == null) - throw new ArgumentNullException("match"); - } - - public ExposedList FindAll (Predicate match) { - CheckMatch(match); - return FindAllList(match); - } - - private ExposedList FindAllList (Predicate match) { - ExposedList results = new ExposedList(); - for (int i = 0; i < Count; i++) - if (match(Items[i])) - results.Add(Items[i]); - - return results; - } - - public int FindIndex (Predicate match) { - CheckMatch(match); - return GetIndex(0, Count, match); - } - - public int FindIndex (int startIndex, Predicate match) { - CheckMatch(match); - CheckIndex(startIndex); - return GetIndex(startIndex, Count - startIndex, match); - } - - public int FindIndex (int startIndex, int count, Predicate match) { - CheckMatch(match); - CheckRange(startIndex, count); - return GetIndex(startIndex, count, match); - } - - private int GetIndex (int startIndex, int count, Predicate match) { - int end = startIndex + count; - for (int i = startIndex; i < end; i++) - if (match(Items[i])) - return i; - - return -1; - } - - public T FindLast (Predicate match) { - CheckMatch(match); - int i = GetLastIndex(0, Count, match); - return i == -1 ? default(T) : Items[i]; - } - - public int FindLastIndex (Predicate match) { - CheckMatch(match); - return GetLastIndex(0, Count, match); - } - - public int FindLastIndex (int startIndex, Predicate match) { - CheckMatch(match); - CheckIndex(startIndex); - return GetLastIndex(0, startIndex + 1, match); - } - - public int FindLastIndex (int startIndex, int count, Predicate match) { - CheckMatch(match); - int start = startIndex - count + 1; - CheckRange(start, count); - return GetLastIndex(start, count, match); - } - - private int GetLastIndex (int startIndex, int count, Predicate match) { - // unlike FindLastIndex, takes regular params for search range - for (int i = startIndex + count; i != startIndex; ) - if (match(Items[--i])) - return i; - return -1; - } - - public void ForEach (Action action) { - if (action == null) - throw new ArgumentNullException("action"); - for (int i = 0; i < Count; i++) - action(Items[i]); - } - - public Enumerator GetEnumerator () { - return new Enumerator(this); - } - - public ExposedList GetRange (int index, int count) { - CheckRange(index, count); - T[] tmpArray = new T[count]; - Array.Copy(Items, index, tmpArray, 0, count); - return new ExposedList(tmpArray, count); - } - - public int IndexOf (T item) { - return Array.IndexOf(Items, item, 0, Count); - } - - public int IndexOf (T item, int index) { - CheckIndex(index); - return Array.IndexOf(Items, item, index, Count - index); - } - - public int IndexOf (T item, int index, int count) { - if (index < 0) - throw new ArgumentOutOfRangeException("index"); - - if (count < 0) - throw new ArgumentOutOfRangeException("count"); - - if ((uint)index + (uint)count > (uint)Count) - throw new ArgumentOutOfRangeException("index and count exceed length of list"); - - return Array.IndexOf(Items, item, index, count); - } - - private void Shift (int start, int delta) { - if (delta < 0) - start -= delta; - - if (start < Count) - Array.Copy(Items, start, Items, start + delta, Count - start); - - Count += delta; - - if (delta < 0) - Array.Clear(Items, Count, -delta); - } - - private void CheckIndex (int index) { - if (index < 0 || (uint)index > (uint)Count) - throw new ArgumentOutOfRangeException("index"); - } - - public void Insert (int index, T item) { - CheckIndex(index); - if (Count == Items.Length) - GrowIfNeeded(1); - Shift(index, 1); - Items[index] = item; - version++; - } - - private void CheckCollection (IEnumerable collection) { - if (collection == null) - throw new ArgumentNullException("collection"); - } - - public void InsertRange (int index, IEnumerable collection) { - CheckCollection(collection); - CheckIndex(index); - if (collection == this) { - T[] buffer = new T[Count]; - CopyTo(buffer, 0); - GrowIfNeeded(Count); - Shift(index, buffer.Length); - Array.Copy(buffer, 0, Items, index, buffer.Length); - } else { - ICollection c = collection as ICollection; - if (c != null) - InsertCollection(index, c); - else - InsertEnumeration(index, collection); - } - version++; - } - - private void InsertCollection (int index, ICollection collection) { - int collectionCount = collection.Count; - GrowIfNeeded(collectionCount); - - Shift(index, collectionCount); - collection.CopyTo(Items, index); - } - - private void InsertEnumeration (int index, IEnumerable enumerable) { - foreach (T t in enumerable) - Insert(index++, t); - } - - public int LastIndexOf (T item) { - return Array.LastIndexOf(Items, item, Count - 1, Count); - } - - public int LastIndexOf (T item, int index) { - CheckIndex(index); - return Array.LastIndexOf(Items, item, index, index + 1); - } - - public int LastIndexOf (T item, int index, int count) { - if (index < 0) - throw new ArgumentOutOfRangeException("index", index, "index is negative"); - - if (count < 0) - throw new ArgumentOutOfRangeException("count", count, "count is negative"); - - if (index - count + 1 < 0) - throw new ArgumentOutOfRangeException("count", count, "count is too large"); - - return Array.LastIndexOf(Items, item, index, count); - } - - public bool Remove (T item) { - int loc = IndexOf(item); - if (loc != -1) - RemoveAt(loc); - - return loc != -1; - } - - public int RemoveAll (Predicate match) { - CheckMatch(match); - int i = 0; - int j = 0; - - // Find the first item to remove - for (i = 0; i < Count; i++) - if (match(Items[i])) - break; - - if (i == Count) - return 0; - - version++; - - // Remove any additional items - for (j = i + 1; j < Count; j++) { - if (!match(Items[j])) - Items[i++] = Items[j]; - } - if (j - i > 0) - Array.Clear(Items, i, j - i); - - Count = i; - return (j - i); - } - - public void RemoveAt (int index) { - if (index < 0 || (uint)index >= (uint)Count) - throw new ArgumentOutOfRangeException("index"); - Shift(index, -1); - Array.Clear(Items, Count, 1); - version++; - } - - public void RemoveRange (int index, int count) { - CheckRange(index, count); - if (count > 0) { - Shift(index, -count); - Array.Clear(Items, Count, count); - version++; - } - } - - public void Reverse () { - Array.Reverse(Items, 0, Count); - version++; - } - - public void Reverse (int index, int count) { - CheckRange(index, count); - Array.Reverse(Items, index, count); - version++; - } - - public void Sort () { - Array.Sort(Items, 0, Count, Comparer.Default); - version++; - } - - public void Sort (IComparer comparer) { - Array.Sort(Items, 0, Count, comparer); - version++; - } - - public void Sort (Comparison comparison) { - Array.Sort(Items, comparison); - version++; - } - - public void Sort (int index, int count, IComparer comparer) { - CheckRange(index, count); - Array.Sort(Items, index, count, comparer); - version++; - } - - public T[] ToArray () { - T[] t = new T[Count]; - Array.Copy(Items, t, Count); - - return t; - } - - public void TrimExcess () { - Capacity = Count; - } - - public bool TrueForAll (Predicate match) { - CheckMatch(match); - - for (int i = 0; i < Count; i++) - if (!match(Items[i])) - return false; - - return true; - } - - public int Capacity { - get { - return Items.Length; - } - set { - if ((uint)value < (uint)Count) - throw new ArgumentOutOfRangeException(); - - Array.Resize(ref Items, value); - } - } - - #region Interface implementations. - - IEnumerator IEnumerable.GetEnumerator () { - return GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator () { - return GetEnumerator(); - } - - #endregion - - [Serializable] - public struct Enumerator : IEnumerator, IDisposable { - private ExposedList l; - private int next; - private int ver; - private T current; - - internal Enumerator (ExposedList l) - : this() { - this.l = l; - ver = l.version; - } - - public void Dispose () { - l = null; - } - - private void VerifyState () { - if (l == null) - throw new ObjectDisposedException(GetType().FullName); - if (ver != l.version) - throw new InvalidOperationException( - "Collection was modified; enumeration operation may not execute."); - } - - public bool MoveNext () { - VerifyState(); - - if (next < 0) - return false; - - if (next < l.Count) { - current = l.Items[next++]; - return true; - } - - next = -1; - return false; - } - - public T Current { - get { - return current; - } - } - - void IEnumerator.Reset () { - VerifyState(); - next = 0; - } - - object IEnumerator.Current { - get { - VerifyState(); - if (next <= 0) - throw new InvalidOperationException(); - return current; - } - } - } - } +namespace Spine3_6_32 +{ + [Serializable] + [DebuggerDisplay("Count={Count}")] + public class ExposedList : IEnumerable + { + public T[] Items; + public int Count; + private const int DefaultCapacity = 4; + private static readonly T[] EmptyArray = new T[0]; + private int version; + + public ExposedList() + { + Items = EmptyArray; + } + + public ExposedList(IEnumerable collection) + { + CheckCollection(collection); + + // initialize to needed size (if determinable) + ICollection c = collection as ICollection; + if (c == null) + { + Items = EmptyArray; + AddEnumerable(collection); + } + else + { + Items = new T[c.Count]; + AddCollection(c); + } + } + + public ExposedList(int capacity) + { + if (capacity < 0) + throw new ArgumentOutOfRangeException("capacity"); + Items = new T[capacity]; + } + + internal ExposedList(T[] data, int size) + { + Items = data; + Count = size; + } + + public void Add(T item) + { + // If we check to see if we need to grow before trying to grow + // we can speed things up by 25% + if (Count == Items.Length) + GrowIfNeeded(1); + Items[Count++] = item; + version++; + } + + public void GrowIfNeeded(int newCount) + { + int minimumSize = Count + newCount; + if (minimumSize > Items.Length) + Capacity = Math.Max(Math.Max(Capacity * 2, DefaultCapacity), minimumSize); + } + + public ExposedList Resize(int newSize) + { + if (newSize > Items.Length) Array.Resize(ref Items, newSize); + Count = newSize; + return this; + } + + public void EnsureCapacity(int min) + { + if (Items.Length < min) + { + int newCapacity = Items.Length == 0 ? DefaultCapacity : Items.Length * 2; + //if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength; + if (newCapacity < min) newCapacity = min; + Capacity = newCapacity; + } + } + + private void CheckRange(int idx, int count) + { + if (idx < 0) + throw new ArgumentOutOfRangeException("index"); + + if (count < 0) + throw new ArgumentOutOfRangeException("count"); + + if ((uint)idx + (uint)count > (uint)Count) + throw new ArgumentException("index and count exceed length of list"); + } + + private void AddCollection(ICollection collection) + { + int collectionCount = collection.Count; + if (collectionCount == 0) + return; + + GrowIfNeeded(collectionCount); + collection.CopyTo(Items, Count); + Count += collectionCount; + } + + private void AddEnumerable(IEnumerable enumerable) + { + foreach (T t in enumerable) + { + Add(t); + } + } + + public void AddRange(IEnumerable collection) + { + CheckCollection(collection); + + ICollection c = collection as ICollection; + if (c != null) + AddCollection(c); + else + AddEnumerable(collection); + version++; + } + + public int BinarySearch(T item) + { + return Array.BinarySearch(Items, 0, Count, item); + } + + public int BinarySearch(T item, IComparer comparer) + { + return Array.BinarySearch(Items, 0, Count, item, comparer); + } + + public int BinarySearch(int index, int count, T item, IComparer comparer) + { + CheckRange(index, count); + return Array.BinarySearch(Items, index, count, item, comparer); + } + + public void Clear(bool clearArray = true) + { + if (clearArray) + Array.Clear(Items, 0, Items.Length); + + Count = 0; + version++; + } + + public bool Contains(T item) + { + return Array.IndexOf(Items, item, 0, Count) != -1; + } + + public ExposedList ConvertAll(Converter converter) + { + if (converter == null) + throw new ArgumentNullException("converter"); + ExposedList u = new ExposedList(Count); + for (int i = 0; i < Count; i++) + u.Items[i] = converter(Items[i]); + + u.Count = Count; + return u; + } + + public void CopyTo(T[] array) + { + Array.Copy(Items, 0, array, 0, Count); + } + + public void CopyTo(T[] array, int arrayIndex) + { + Array.Copy(Items, 0, array, arrayIndex, Count); + } + + public void CopyTo(int index, T[] array, int arrayIndex, int count) + { + CheckRange(index, count); + Array.Copy(Items, index, array, arrayIndex, count); + } + + + + public bool Exists(Predicate match) + { + CheckMatch(match); + return GetIndex(0, Count, match) != -1; + } + + public T Find(Predicate match) + { + CheckMatch(match); + int i = GetIndex(0, Count, match); + return (i != -1) ? Items[i] : default(T); + } + + private static void CheckMatch(Predicate match) + { + if (match == null) + throw new ArgumentNullException("match"); + } + + public ExposedList FindAll(Predicate match) + { + CheckMatch(match); + return FindAllList(match); + } + + private ExposedList FindAllList(Predicate match) + { + ExposedList results = new ExposedList(); + for (int i = 0; i < Count; i++) + if (match(Items[i])) + results.Add(Items[i]); + + return results; + } + + public int FindIndex(Predicate match) + { + CheckMatch(match); + return GetIndex(0, Count, match); + } + + public int FindIndex(int startIndex, Predicate match) + { + CheckMatch(match); + CheckIndex(startIndex); + return GetIndex(startIndex, Count - startIndex, match); + } + + public int FindIndex(int startIndex, int count, Predicate match) + { + CheckMatch(match); + CheckRange(startIndex, count); + return GetIndex(startIndex, count, match); + } + + private int GetIndex(int startIndex, int count, Predicate match) + { + int end = startIndex + count; + for (int i = startIndex; i < end; i++) + if (match(Items[i])) + return i; + + return -1; + } + + public T FindLast(Predicate match) + { + CheckMatch(match); + int i = GetLastIndex(0, Count, match); + return i == -1 ? default(T) : Items[i]; + } + + public int FindLastIndex(Predicate match) + { + CheckMatch(match); + return GetLastIndex(0, Count, match); + } + + public int FindLastIndex(int startIndex, Predicate match) + { + CheckMatch(match); + CheckIndex(startIndex); + return GetLastIndex(0, startIndex + 1, match); + } + + public int FindLastIndex(int startIndex, int count, Predicate match) + { + CheckMatch(match); + int start = startIndex - count + 1; + CheckRange(start, count); + return GetLastIndex(start, count, match); + } + + private int GetLastIndex(int startIndex, int count, Predicate match) + { + // unlike FindLastIndex, takes regular params for search range + for (int i = startIndex + count; i != startIndex;) + if (match(Items[--i])) + return i; + return -1; + } + + public void ForEach(Action action) + { + if (action == null) + throw new ArgumentNullException("action"); + for (int i = 0; i < Count; i++) + action(Items[i]); + } + + public Enumerator GetEnumerator() + { + return new Enumerator(this); + } + + public ExposedList GetRange(int index, int count) + { + CheckRange(index, count); + T[] tmpArray = new T[count]; + Array.Copy(Items, index, tmpArray, 0, count); + return new ExposedList(tmpArray, count); + } + + public int IndexOf(T item) + { + return Array.IndexOf(Items, item, 0, Count); + } + + public int IndexOf(T item, int index) + { + CheckIndex(index); + return Array.IndexOf(Items, item, index, Count - index); + } + + public int IndexOf(T item, int index, int count) + { + if (index < 0) + throw new ArgumentOutOfRangeException("index"); + + if (count < 0) + throw new ArgumentOutOfRangeException("count"); + + if ((uint)index + (uint)count > (uint)Count) + throw new ArgumentOutOfRangeException("index and count exceed length of list"); + + return Array.IndexOf(Items, item, index, count); + } + + private void Shift(int start, int delta) + { + if (delta < 0) + start -= delta; + + if (start < Count) + Array.Copy(Items, start, Items, start + delta, Count - start); + + Count += delta; + + if (delta < 0) + Array.Clear(Items, Count, -delta); + } + + private void CheckIndex(int index) + { + if (index < 0 || (uint)index > (uint)Count) + throw new ArgumentOutOfRangeException("index"); + } + + public void Insert(int index, T item) + { + CheckIndex(index); + if (Count == Items.Length) + GrowIfNeeded(1); + Shift(index, 1); + Items[index] = item; + version++; + } + + private void CheckCollection(IEnumerable collection) + { + if (collection == null) + throw new ArgumentNullException("collection"); + } + + public void InsertRange(int index, IEnumerable collection) + { + CheckCollection(collection); + CheckIndex(index); + if (collection == this) + { + T[] buffer = new T[Count]; + CopyTo(buffer, 0); + GrowIfNeeded(Count); + Shift(index, buffer.Length); + Array.Copy(buffer, 0, Items, index, buffer.Length); + } + else + { + ICollection c = collection as ICollection; + if (c != null) + InsertCollection(index, c); + else + InsertEnumeration(index, collection); + } + version++; + } + + private void InsertCollection(int index, ICollection collection) + { + int collectionCount = collection.Count; + GrowIfNeeded(collectionCount); + + Shift(index, collectionCount); + collection.CopyTo(Items, index); + } + + private void InsertEnumeration(int index, IEnumerable enumerable) + { + foreach (T t in enumerable) + Insert(index++, t); + } + + public int LastIndexOf(T item) + { + return Array.LastIndexOf(Items, item, Count - 1, Count); + } + + public int LastIndexOf(T item, int index) + { + CheckIndex(index); + return Array.LastIndexOf(Items, item, index, index + 1); + } + + public int LastIndexOf(T item, int index, int count) + { + if (index < 0) + throw new ArgumentOutOfRangeException("index", index, "index is negative"); + + if (count < 0) + throw new ArgumentOutOfRangeException("count", count, "count is negative"); + + if (index - count + 1 < 0) + throw new ArgumentOutOfRangeException("count", count, "count is too large"); + + return Array.LastIndexOf(Items, item, index, count); + } + + public bool Remove(T item) + { + int loc = IndexOf(item); + if (loc != -1) + RemoveAt(loc); + + return loc != -1; + } + + public int RemoveAll(Predicate match) + { + CheckMatch(match); + int i = 0; + int j = 0; + + // Find the first item to remove + for (i = 0; i < Count; i++) + if (match(Items[i])) + break; + + if (i == Count) + return 0; + + version++; + + // Remove any additional items + for (j = i + 1; j < Count; j++) + { + if (!match(Items[j])) + Items[i++] = Items[j]; + } + if (j - i > 0) + Array.Clear(Items, i, j - i); + + Count = i; + return (j - i); + } + + public void RemoveAt(int index) + { + if (index < 0 || (uint)index >= (uint)Count) + throw new ArgumentOutOfRangeException("index"); + Shift(index, -1); + Array.Clear(Items, Count, 1); + version++; + } + + public void RemoveRange(int index, int count) + { + CheckRange(index, count); + if (count > 0) + { + Shift(index, -count); + Array.Clear(Items, Count, count); + version++; + } + } + + public void Reverse() + { + Array.Reverse(Items, 0, Count); + version++; + } + + public void Reverse(int index, int count) + { + CheckRange(index, count); + Array.Reverse(Items, index, count); + version++; + } + + public void Sort() + { + Array.Sort(Items, 0, Count, Comparer.Default); + version++; + } + + public void Sort(IComparer comparer) + { + Array.Sort(Items, 0, Count, comparer); + version++; + } + + public void Sort(Comparison comparison) + { + Array.Sort(Items, comparison); + version++; + } + + public void Sort(int index, int count, IComparer comparer) + { + CheckRange(index, count); + Array.Sort(Items, index, count, comparer); + version++; + } + + public T[] ToArray() + { + T[] t = new T[Count]; + Array.Copy(Items, t, Count); + + return t; + } + + public void TrimExcess() + { + Capacity = Count; + } + + public bool TrueForAll(Predicate match) + { + CheckMatch(match); + + for (int i = 0; i < Count; i++) + if (!match(Items[i])) + return false; + + return true; + } + + public int Capacity + { + get + { + return Items.Length; + } + set + { + if ((uint)value < (uint)Count) + throw new ArgumentOutOfRangeException(); + + Array.Resize(ref Items, value); + } + } + + #region Interface implementations. + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion + + [Serializable] + public struct Enumerator : IEnumerator, IDisposable + { + private ExposedList l; + private int next; + private int ver; + private T current; + + internal Enumerator(ExposedList l) + : this() + { + this.l = l; + ver = l.version; + } + + public void Dispose() + { + l = null; + } + + private void VerifyState() + { + if (l == null) + throw new ObjectDisposedException(GetType().FullName); + if (ver != l.version) + throw new InvalidOperationException( + "Collection was modified; enumeration operation may not execute."); + } + + public bool MoveNext() + { + VerifyState(); + + if (next < 0) + return false; + + if (next < l.Count) + { + current = l.Items[next++]; + return true; + } + + next = -1; + return false; + } + + public T Current + { + get + { + return current; + } + } + + void IEnumerator.Reset() + { + VerifyState(); + next = 0; + } + + object IEnumerator.Current + { + get + { + VerifyState(); + if (next <= 0) + throw new InvalidOperationException(); + return current; + } + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/IConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/IConstraint.cs index 3066283..f805945 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/IConstraint.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/IConstraint.cs @@ -28,13 +28,15 @@ * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine3_6_32 { - - /// The interface for all constraints. - public interface IConstraint : IUpdatable { - /// The ordinal for the order a skeleton's constraints will be applied. - int Order { get; } +namespace Spine3_6_32 +{ - } + /// The interface for all constraints. + public interface IConstraint : IUpdatable + { + /// The ordinal for the order a skeleton's constraints will be applied. + int Order { get; } + + } } \ No newline at end of file diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/IUpdatable.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/IUpdatable.cs index 8b98f44..1203c02 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/IUpdatable.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/IUpdatable.cs @@ -28,8 +28,10 @@ * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine3_6_32 { - public interface IUpdatable { - void Update (); - } +namespace Spine3_6_32 +{ + public interface IUpdatable + { + void Update(); + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/IkConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/IkConstraint.cs index b2a6f24..92c6336 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/IkConstraint.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/IkConstraint.cs @@ -30,198 +30,228 @@ using System; -namespace Spine3_6_32 { - public class IkConstraint : IConstraint { - internal IkConstraintData data; - internal ExposedList bones = new ExposedList(); - internal Bone target; - internal float mix; - internal int bendDirection; +namespace Spine3_6_32 +{ + public class IkConstraint : IConstraint + { + internal IkConstraintData data; + internal ExposedList bones = new ExposedList(); + internal Bone target; + internal float mix; + internal int bendDirection; - public IkConstraintData Data { get { return data; } } - public int Order { get { return data.order; } } - public ExposedList Bones { get { return bones; } } - public Bone Target { get { return target; } set { target = value; } } - public int BendDirection { get { return bendDirection; } set { bendDirection = value; } } - public float Mix { get { return mix; } set { mix = value; } } + public IkConstraintData Data { get { return data; } } + public int Order { get { return data.order; } } + public ExposedList Bones { get { return bones; } } + public Bone Target { get { return target; } set { target = value; } } + public int BendDirection { get { return bendDirection; } set { bendDirection = value; } } + public float Mix { get { return mix; } set { mix = value; } } - public IkConstraint (IkConstraintData data, Skeleton skeleton) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.data = data; - mix = data.mix; - bendDirection = data.bendDirection; + public IkConstraint(IkConstraintData data, Skeleton skeleton) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + mix = data.mix; + bendDirection = data.bendDirection; - bones = new ExposedList(data.bones.Count); - foreach (BoneData boneData in data.bones) - bones.Add(skeleton.FindBone(boneData.name)); - target = skeleton.FindBone(data.target.name); - } + bones = new ExposedList(data.bones.Count); + foreach (BoneData boneData in data.bones) + bones.Add(skeleton.FindBone(boneData.name)); + target = skeleton.FindBone(data.target.name); + } - /// Applies the constraint to the constrained bones. - public void Apply () { - Update(); - } + /// Applies the constraint to the constrained bones. + public void Apply() + { + Update(); + } - public void Update () { - Bone target = this.target; - ExposedList bones = this.bones; - switch (bones.Count) { - case 1: - Apply(bones.Items[0], target.worldX, target.worldY, mix); - break; - case 2: - Apply(bones.Items[0], bones.Items[1], target.worldX, target.worldY, bendDirection, mix); - break; - } - } + public void Update() + { + Bone target = this.target; + ExposedList bones = this.bones; + switch (bones.Count) + { + case 1: + Apply(bones.Items[0], target.worldX, target.worldY, mix); + break; + case 2: + Apply(bones.Items[0], bones.Items[1], target.worldX, target.worldY, bendDirection, mix); + break; + } + } - override public string ToString () { - return data.name; - } + override public string ToString() + { + return data.name; + } - /// 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 public void Apply (Bone bone, float targetX, float targetY, float alpha) { - if (!bone.appliedValid) bone.UpdateAppliedTransform(); - Bone p = bone.parent; - float id = 1 / (p.a * p.d - p.b * p.c); - float x = targetX - p.worldX, y = targetY - p.worldY; - float tx = (x * p.d - y * p.b) * id - bone.ax, ty = (y * p.a - x * p.c) * id - bone.ay; - float rotationIK = (float)Math.Atan2(ty, tx) * MathUtils.RadDeg - bone.ashearX - bone.arotation; - if (bone.ascaleX < 0) rotationIK += 180; - if (rotationIK > 180) - rotationIK -= 360; - else if (rotationIK < -180) rotationIK += 360; - bone.UpdateWorldTransform(bone.ax, bone.ay, bone.arotation + rotationIK * alpha, bone.ascaleX, bone.ascaleY, bone.ashearX, - bone.ashearY); - } + /// 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 public void Apply(Bone bone, float targetX, float targetY, float alpha) + { + if (!bone.appliedValid) bone.UpdateAppliedTransform(); + Bone p = bone.parent; + float id = 1 / (p.a * p.d - p.b * p.c); + float x = targetX - p.worldX, y = targetY - p.worldY; + float tx = (x * p.d - y * p.b) * id - bone.ax, ty = (y * p.a - x * p.c) * id - bone.ay; + float rotationIK = (float)Math.Atan2(ty, tx) * MathUtils.RadDeg - bone.ashearX - bone.arotation; + if (bone.ascaleX < 0) rotationIK += 180; + if (rotationIK > 180) + rotationIK -= 360; + else if (rotationIK < -180) rotationIK += 360; + bone.UpdateWorldTransform(bone.ax, bone.ay, bone.arotation + rotationIK * alpha, bone.ascaleX, bone.ascaleY, bone.ashearX, + bone.ashearY); + } - /// 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. - /// A direct descendant of the parent bone. - static public void Apply (Bone parent, Bone child, float targetX, float targetY, int bendDir, float alpha) { - if (alpha == 0) { - child.UpdateWorldTransform (); - return; - } - //float px = parent.x, py = parent.y, psx = parent.scaleX, psy = parent.scaleY, csx = child.scaleX; - if (!parent.appliedValid) parent.UpdateAppliedTransform(); - if (!child.appliedValid) child.UpdateAppliedTransform(); - float px = parent.ax, py = parent.ay, psx = parent.ascaleX, psy = parent.ascaleY, csx = child.ascaleX; - int os1, os2, s2; - if (psx < 0) { - psx = -psx; - os1 = 180; - s2 = -1; - } else { - os1 = 0; - s2 = 1; - } - if (psy < 0) { - psy = -psy; - s2 = -s2; - } - if (csx < 0) { - csx = -csx; - os2 = 180; - } else - os2 = 0; - float cx = child.ax, cy, cwx, cwy, a = parent.a, b = parent.b, c = parent.c, d = parent.d; - bool u = Math.Abs(psx - psy) <= 0.0001f; - if (!u) { - cy = 0; - cwx = a * cx + parent.worldX; - cwy = c * cx + parent.worldY; - } else { - cy = child.ay; - cwx = a * cx + b * cy + parent.worldX; - cwy = c * cx + d * cy + parent.worldY; - } - Bone pp = parent.parent; - a = pp.a; - b = pp.b; - c = pp.c; - d = pp.d; - float id = 1 / (a * d - b * c), x = targetX - pp.worldX, y = targetY - pp.worldY; - float tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py; - x = cwx - pp.worldX; - y = cwy - pp.worldY; - float dx = (x * d - y * b) * id - px, dy = (y * a - x * c) * id - py; - float l1 = (float)Math.Sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2; - if (u) { - l2 *= psx; - float cos = (tx * tx + ty * ty - l1 * l1 - l2 * l2) / (2 * l1 * l2); - if (cos < -1) - cos = -1; - else if (cos > 1) cos = 1; - a2 = (float)Math.Acos(cos) * bendDir; - a = l1 + l2 * cos; - b = l2 * (float)Math.Sin(a2); - a1 = (float)Math.Atan2(ty * a - tx * b, tx * a + ty * b); - } else { - a = psx * l2; - b = psy * l2; - float aa = a * a, bb = b * b, dd = tx * tx + ty * ty, ta = (float)Math.Atan2(ty, tx); - c = bb * l1 * l1 + aa * dd - aa * bb; - float c1 = -2 * bb * l1, c2 = bb - aa; - d = c1 * c1 - 4 * c2 * c; - if (d >= 0) { - float q = (float)Math.Sqrt(d); - if (c1 < 0) q = -q; - q = -(c1 + q) / 2; - float r0 = q / c2, r1 = c / q; - float r = Math.Abs(r0) < Math.Abs(r1) ? r0 : r1; - if (r * r <= dd) { - y = (float)Math.Sqrt(dd - r * r) * bendDir; - a1 = ta - (float)Math.Atan2(y, r); - a2 = (float)Math.Atan2(y / psy, (r - l1) / psx); - goto outer; // break outer; - } - } - float minAngle = MathUtils.PI, minX = l1 - a, minDist = minX * minX, minY = 0; - float maxAngle = 0, maxX = l1 + a, maxDist = maxX * maxX, maxY = 0; - c = -a * l1 / (aa - bb); - if (c >= -1 && c <= 1) { - c = (float)Math.Acos(c); - x = a * (float)Math.Cos(c) + l1; - y = b * (float)Math.Sin(c); - d = x * x + y * y; - if (d < minDist) { - minAngle = c; - minDist = d; - minX = x; - minY = y; - } - if (d > maxDist) { - maxAngle = c; - maxDist = d; - maxX = x; - maxY = y; - } - } - if (dd <= (minDist + maxDist) / 2) { - a1 = ta - (float)Math.Atan2(minY * bendDir, minX); - a2 = minAngle * bendDir; - } else { - a1 = ta - (float)Math.Atan2(maxY * bendDir, maxX); - a2 = maxAngle * bendDir; - } - } - outer: - float os = (float)Math.Atan2(cy, cx) * s2; - float rotation = parent.arotation; - a1 = (a1 - os) * MathUtils.RadDeg + os1 - rotation; - if (a1 > 180) - a1 -= 360; - else if (a1 < -180) a1 += 360; - parent.UpdateWorldTransform(px, py, rotation + a1 * alpha, parent.scaleX, parent.ascaleY, 0, 0); - rotation = child.arotation; - a2 = ((a2 + os) * MathUtils.RadDeg - child.ashearX) * s2 + os2 - rotation; - if (a2 > 180) - a2 -= 360; - else if (a2 < -180) a2 += 360; - child.UpdateWorldTransform(cx, cy, rotation + a2 * alpha, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY); - } - } + /// 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. + /// A direct descendant of the parent bone. + static public void Apply(Bone parent, Bone child, float targetX, float targetY, int bendDir, float alpha) + { + if (alpha == 0) + { + child.UpdateWorldTransform(); + return; + } + //float px = parent.x, py = parent.y, psx = parent.scaleX, psy = parent.scaleY, csx = child.scaleX; + if (!parent.appliedValid) parent.UpdateAppliedTransform(); + if (!child.appliedValid) child.UpdateAppliedTransform(); + float px = parent.ax, py = parent.ay, psx = parent.ascaleX, psy = parent.ascaleY, csx = child.ascaleX; + int os1, os2, s2; + if (psx < 0) + { + psx = -psx; + os1 = 180; + s2 = -1; + } + else + { + os1 = 0; + s2 = 1; + } + if (psy < 0) + { + psy = -psy; + s2 = -s2; + } + if (csx < 0) + { + csx = -csx; + os2 = 180; + } + else + os2 = 0; + float cx = child.ax, cy, cwx, cwy, a = parent.a, b = parent.b, c = parent.c, d = parent.d; + bool u = Math.Abs(psx - psy) <= 0.0001f; + if (!u) + { + cy = 0; + cwx = a * cx + parent.worldX; + cwy = c * cx + parent.worldY; + } + else + { + cy = child.ay; + cwx = a * cx + b * cy + parent.worldX; + cwy = c * cx + d * cy + parent.worldY; + } + Bone pp = parent.parent; + a = pp.a; + b = pp.b; + c = pp.c; + d = pp.d; + float id = 1 / (a * d - b * c), x = targetX - pp.worldX, y = targetY - pp.worldY; + float tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py; + x = cwx - pp.worldX; + y = cwy - pp.worldY; + float dx = (x * d - y * b) * id - px, dy = (y * a - x * c) * id - py; + float l1 = (float)Math.Sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2; + if (u) + { + l2 *= psx; + float cos = (tx * tx + ty * ty - l1 * l1 - l2 * l2) / (2 * l1 * l2); + if (cos < -1) + cos = -1; + else if (cos > 1) cos = 1; + a2 = (float)Math.Acos(cos) * bendDir; + a = l1 + l2 * cos; + b = l2 * (float)Math.Sin(a2); + a1 = (float)Math.Atan2(ty * a - tx * b, tx * a + ty * b); + } + else + { + a = psx * l2; + b = psy * l2; + float aa = a * a, bb = b * b, dd = tx * tx + ty * ty, ta = (float)Math.Atan2(ty, tx); + c = bb * l1 * l1 + aa * dd - aa * bb; + float c1 = -2 * bb * l1, c2 = bb - aa; + d = c1 * c1 - 4 * c2 * c; + if (d >= 0) + { + float q = (float)Math.Sqrt(d); + if (c1 < 0) q = -q; + q = -(c1 + q) / 2; + float r0 = q / c2, r1 = c / q; + float r = Math.Abs(r0) < Math.Abs(r1) ? r0 : r1; + if (r * r <= dd) + { + y = (float)Math.Sqrt(dd - r * r) * bendDir; + a1 = ta - (float)Math.Atan2(y, r); + a2 = (float)Math.Atan2(y / psy, (r - l1) / psx); + goto outer; // break outer; + } + } + float minAngle = MathUtils.PI, minX = l1 - a, minDist = minX * minX, minY = 0; + float maxAngle = 0, maxX = l1 + a, maxDist = maxX * maxX, maxY = 0; + c = -a * l1 / (aa - bb); + if (c >= -1 && c <= 1) + { + c = (float)Math.Acos(c); + x = a * (float)Math.Cos(c) + l1; + y = b * (float)Math.Sin(c); + d = x * x + y * y; + if (d < minDist) + { + minAngle = c; + minDist = d; + minX = x; + minY = y; + } + if (d > maxDist) + { + maxAngle = c; + maxDist = d; + maxX = x; + maxY = y; + } + } + if (dd <= (minDist + maxDist) / 2) + { + a1 = ta - (float)Math.Atan2(minY * bendDir, minX); + a2 = minAngle * bendDir; + } + else + { + a1 = ta - (float)Math.Atan2(maxY * bendDir, maxX); + a2 = maxAngle * bendDir; + } + } + outer: + float os = (float)Math.Atan2(cy, cx) * s2; + float rotation = parent.arotation; + a1 = (a1 - os) * MathUtils.RadDeg + os1 - rotation; + if (a1 > 180) + a1 -= 360; + else if (a1 < -180) a1 += 360; + parent.UpdateWorldTransform(px, py, rotation + a1 * alpha, parent.scaleX, parent.ascaleY, 0, 0); + rotation = child.arotation; + a2 = ((a2 + os) * MathUtils.RadDeg - child.ashearX) * s2 + os2 - rotation; + if (a2 > 180) + a2 -= 360; + else if (a2 < -180) a2 += 360; + child.UpdateWorldTransform(cx, cy, rotation + a2 * alpha, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/IkConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/IkConstraintData.cs index 4bac255..a3c4b9e 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/IkConstraintData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/IkConstraintData.cs @@ -31,52 +31,61 @@ using System; using System.Collections.Generic; -namespace Spine3_6_32 { - /// Stores the setup pose for an IkConstraint. - public class IkConstraintData { - internal string name; - internal int order; - internal List bones = new List(); - internal BoneData target; - internal int bendDirection = 1; - internal float mix = 1; +namespace Spine3_6_32 +{ + /// Stores the setup pose for an IkConstraint. + public class IkConstraintData + { + internal string name; + internal int order; + internal List bones = new List(); + internal BoneData target; + internal int bendDirection = 1; + internal float mix = 1; - /// The IK constraint's name, which is unique within the skeleton. - public string Name { - get { return name; } - } + /// The IK constraint's name, which is unique within the skeleton. + public string Name + { + get { return name; } + } - public int Order { - get { return order; } - set { order = value; } - } + public int Order + { + get { return order; } + set { order = value; } + } - /// The bones that are constrained by this IK Constraint. - public List Bones { - get { return bones; } - } + /// The bones that are constrained by this IK Constraint. + public List Bones + { + get { return bones; } + } - /// The bone that is the IK target. - public BoneData Target { - get { return target; } - set { target = value; } - } + /// The bone that is the IK target. + public BoneData Target + { + get { return target; } + set { target = value; } + } - /// Controls the bend direction of the IK bones, either 1 or -1. - public int BendDirection { - get { return bendDirection; } - set { bendDirection = value; } - } + /// Controls the bend direction of the IK bones, either 1 or -1. + public int BendDirection + { + get { return bendDirection; } + set { bendDirection = value; } + } - public float Mix { get { return mix; } set { mix = value; } } + public float Mix { get { return mix; } set { mix = value; } } - public IkConstraintData (string name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; - } + public IkConstraintData(string name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + } - override public string ToString () { - return name; - } - } + override public string ToString() + { + return name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Json.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Json.cs index be15e4b..9fe15fd 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Json.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Json.cs @@ -29,20 +29,22 @@ *****************************************************************************/ using System; +using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Text; -using System.Collections; -using System.Globalization; -using System.Collections.Generic; -namespace Spine3_6_32 { - public static class Json { - public static object Deserialize (TextReader text) { - var parser = new SharpJson3_6_32.JsonDecoder(); - parser.parseNumbersAsFloat = true; - return parser.Decode(text.ReadToEnd()); - } - } +namespace Spine3_6_32 +{ + public static class Json + { + public static object Deserialize(TextReader text) + { + var parser = new SharpJson3_6_32.JsonDecoder(); + parser.parseNumbersAsFloat = true; + return parser.Decode(text.ReadToEnd()); + } + } } /** @@ -76,460 +78,502 @@ public static object Deserialize (TextReader text) { */ namespace SharpJson3_6_32 { - class Lexer - { - public enum Token { - None, - Null, - True, - False, - Colon, - Comma, - String, - Number, - CurlyOpen, - CurlyClose, - SquaredOpen, - SquaredClose, - }; - - public bool hasError { - get { - return !success; - } - } - - public int lineNumber { - get; - private set; - } - - public bool parseNumbersAsFloat { - get; - set; - } - - char[] json; - int index = 0; - bool success = true; - char[] stringBuffer = new char[4096]; - - public Lexer(string text) - { - Reset(); - - json = text.ToCharArray(); - parseNumbersAsFloat = false; - } - - public void Reset() - { - index = 0; - lineNumber = 1; - success = true; - } - - public string ParseString() - { - int idx = 0; - StringBuilder builder = null; - - SkipWhiteSpaces(); - - // " - char c = json[index++]; - - bool failed = false; - bool complete = false; - - while (!complete && !failed) { - if (index == json.Length) - break; - - c = json[index++]; - if (c == '"') { - complete = true; - break; - } else if (c == '\\') { - if (index == json.Length) - break; - - c = json[index++]; - - switch (c) { - case '"': - stringBuffer[idx++] = '"'; - break; - case '\\': - stringBuffer[idx++] = '\\'; - break; - case '/': - stringBuffer[idx++] = '/'; - break; - case 'b': - stringBuffer[idx++] = '\b'; - break; - case'f': - stringBuffer[idx++] = '\f'; - break; - case 'n': - stringBuffer[idx++] = '\n'; - break; - case 'r': - stringBuffer[idx++] = '\r'; - break; - case 't': - stringBuffer[idx++] = '\t'; - break; - case 'u': - int remainingLength = json.Length - index; - if (remainingLength >= 4) { - var hex = new string(json, index, 4); - - // XXX: handle UTF - stringBuffer[idx++] = (char) Convert.ToInt32(hex, 16); - - // skip 4 chars - index += 4; - } else { - failed = true; - } - break; - } - } else { - stringBuffer[idx++] = c; - } - - if (idx >= stringBuffer.Length) { - if (builder == null) - builder = new StringBuilder(); - - builder.Append(stringBuffer, 0, idx); - idx = 0; - } - } - - if (!complete) { - success = false; - return null; - } - - if (builder != null) - return builder.ToString (); - else - return new string (stringBuffer, 0, idx); - } - - string GetNumberString() - { - SkipWhiteSpaces(); - - int lastIndex = GetLastIndexOfNumber(index); - int charLength = (lastIndex - index) + 1; - - var result = new string (json, index, charLength); - - index = lastIndex + 1; - - return result; - } - - public float ParseFloatNumber() - { - float number; - var str = GetNumberString (); - - if (!float.TryParse (str, NumberStyles.Float, CultureInfo.InvariantCulture, out number)) - return 0; - - return number; - } - - public double ParseDoubleNumber() - { - double number; - var str = GetNumberString (); - - if (!double.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out number)) - return 0; - - return number; - } - - int GetLastIndexOfNumber(int index) - { - int lastIndex; - - for (lastIndex = index; lastIndex < json.Length; lastIndex++) { - char ch = json[lastIndex]; - - if ((ch < '0' || ch > '9') && ch != '+' && ch != '-' - && ch != '.' && ch != 'e' && ch != 'E') - break; - } - - return lastIndex - 1; - } - - void SkipWhiteSpaces() - { - for (; index < json.Length; index++) { - char ch = json[index]; - - if (ch == '\n') - lineNumber++; - - if (!char.IsWhiteSpace(json[index])) - break; - } - } - - public Token LookAhead() - { - SkipWhiteSpaces(); - - int savedIndex = index; - return NextToken(json, ref savedIndex); - } - - public Token NextToken() - { - SkipWhiteSpaces(); - return NextToken(json, ref index); - } - - static Token NextToken(char[] json, ref int index) - { - if (index == json.Length) - return Token.None; - - char c = json[index++]; - - switch (c) { - case '{': - return Token.CurlyOpen; - case '}': - return Token.CurlyClose; - case '[': - return Token.SquaredOpen; - case ']': - return Token.SquaredClose; - case ',': - return Token.Comma; - case '"': - return Token.String; - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - case '-': - return Token.Number; - case ':': - return Token.Colon; - } - - index--; - - int remainingLength = json.Length - index; - - // false - if (remainingLength >= 5) { - if (json[index] == 'f' && - json[index + 1] == 'a' && - json[index + 2] == 'l' && - json[index + 3] == 's' && - json[index + 4] == 'e') { - index += 5; - return Token.False; - } - } - - // true - if (remainingLength >= 4) { - if (json[index] == 't' && - json[index + 1] == 'r' && - json[index + 2] == 'u' && - json[index + 3] == 'e') { - index += 4; - return Token.True; - } - } - - // null - if (remainingLength >= 4) { - if (json[index] == 'n' && - json[index + 1] == 'u' && - json[index + 2] == 'l' && - json[index + 3] == 'l') { - index += 4; - return Token.Null; - } - } - - return Token.None; - } - } - - public class JsonDecoder - { - public string errorMessage { - get; - private set; - } - - public bool parseNumbersAsFloat { - get; - set; - } - - Lexer lexer; - - public JsonDecoder() - { - errorMessage = null; - parseNumbersAsFloat = false; - } - - public object Decode(string text) - { - errorMessage = null; - - lexer = new Lexer(text); - lexer.parseNumbersAsFloat = parseNumbersAsFloat; - - return ParseValue(); - } - - public static object DecodeText(string text) - { - var builder = new JsonDecoder(); - return builder.Decode(text); - } - - IDictionary ParseObject() - { - var table = new Dictionary(); - - // { - lexer.NextToken(); - - while (true) { - var token = lexer.LookAhead(); - - switch (token) { - case Lexer.Token.None: - TriggerError("Invalid token"); - return null; - case Lexer.Token.Comma: - lexer.NextToken(); - break; - case Lexer.Token.CurlyClose: - lexer.NextToken(); - return table; - default: - // name - string name = EvalLexer(lexer.ParseString()); - - if (errorMessage != null) - return null; - - // : - token = lexer.NextToken(); - - if (token != Lexer.Token.Colon) { - TriggerError("Invalid token; expected ':'"); - return null; - } - - // value - object value = ParseValue(); - - if (errorMessage != null) - return null; - - table[name] = value; - break; - } - } - - //return null; // Unreachable code - } - - IList ParseArray() - { - var array = new List(); - - // [ - lexer.NextToken(); - - while (true) { - var token = lexer.LookAhead(); - - switch (token) { - case Lexer.Token.None: - TriggerError("Invalid token"); - return null; - case Lexer.Token.Comma: - lexer.NextToken(); - break; - case Lexer.Token.SquaredClose: - lexer.NextToken(); - return array; - default: - object value = ParseValue(); - - if (errorMessage != null) - return null; - - array.Add(value); - break; - } - } - - //return null; // Unreachable code - } - - object ParseValue() - { - switch (lexer.LookAhead()) { - case Lexer.Token.String: - return EvalLexer(lexer.ParseString()); - case Lexer.Token.Number: - if (parseNumbersAsFloat) - return EvalLexer(lexer.ParseFloatNumber()); - else - return EvalLexer(lexer.ParseDoubleNumber()); - case Lexer.Token.CurlyOpen: - return ParseObject(); - case Lexer.Token.SquaredOpen: - return ParseArray(); - case Lexer.Token.True: - lexer.NextToken(); - return true; - case Lexer.Token.False: - lexer.NextToken(); - return false; - case Lexer.Token.Null: - lexer.NextToken(); - return null; - case Lexer.Token.None: - break; - } - - TriggerError("Unable to parse value"); - return null; - } - - void TriggerError(string message) - { - errorMessage = string.Format("Error: '{0}' at line {1}", - message, lexer.lineNumber); - } - - T EvalLexer(T value) - { - if (lexer.hasError) - TriggerError("Lexical error ocurred"); - - return value; - } - } + class Lexer + { + public enum Token + { + None, + Null, + True, + False, + Colon, + Comma, + String, + Number, + CurlyOpen, + CurlyClose, + SquaredOpen, + SquaredClose, + }; + + public bool hasError + { + get + { + return !success; + } + } + + public int lineNumber + { + get; + private set; + } + + public bool parseNumbersAsFloat + { + get; + set; + } + + char[] json; + int index = 0; + bool success = true; + char[] stringBuffer = new char[4096]; + + public Lexer(string text) + { + Reset(); + + json = text.ToCharArray(); + parseNumbersAsFloat = false; + } + + public void Reset() + { + index = 0; + lineNumber = 1; + success = true; + } + + public string ParseString() + { + int idx = 0; + StringBuilder builder = null; + + SkipWhiteSpaces(); + + // " + char c = json[index++]; + + bool failed = false; + bool complete = false; + + while (!complete && !failed) + { + if (index == json.Length) + break; + + c = json[index++]; + if (c == '"') + { + complete = true; + break; + } + else if (c == '\\') + { + if (index == json.Length) + break; + + c = json[index++]; + + switch (c) + { + case '"': + stringBuffer[idx++] = '"'; + break; + case '\\': + stringBuffer[idx++] = '\\'; + break; + case '/': + stringBuffer[idx++] = '/'; + break; + case 'b': + stringBuffer[idx++] = '\b'; + break; + case 'f': + stringBuffer[idx++] = '\f'; + break; + case 'n': + stringBuffer[idx++] = '\n'; + break; + case 'r': + stringBuffer[idx++] = '\r'; + break; + case 't': + stringBuffer[idx++] = '\t'; + break; + case 'u': + int remainingLength = json.Length - index; + if (remainingLength >= 4) + { + var hex = new string(json, index, 4); + + // XXX: handle UTF + stringBuffer[idx++] = (char)Convert.ToInt32(hex, 16); + + // skip 4 chars + index += 4; + } + else + { + failed = true; + } + break; + } + } + else + { + stringBuffer[idx++] = c; + } + + if (idx >= stringBuffer.Length) + { + if (builder == null) + builder = new StringBuilder(); + + builder.Append(stringBuffer, 0, idx); + idx = 0; + } + } + + if (!complete) + { + success = false; + return null; + } + + if (builder != null) + return builder.ToString(); + else + return new string(stringBuffer, 0, idx); + } + + string GetNumberString() + { + SkipWhiteSpaces(); + + int lastIndex = GetLastIndexOfNumber(index); + int charLength = (lastIndex - index) + 1; + + var result = new string(json, index, charLength); + + index = lastIndex + 1; + + return result; + } + + public float ParseFloatNumber() + { + float number; + var str = GetNumberString(); + + if (!float.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out number)) + return 0; + + return number; + } + + public double ParseDoubleNumber() + { + double number; + var str = GetNumberString(); + + if (!double.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out number)) + return 0; + + return number; + } + + int GetLastIndexOfNumber(int index) + { + int lastIndex; + + for (lastIndex = index; lastIndex < json.Length; lastIndex++) + { + char ch = json[lastIndex]; + + if ((ch < '0' || ch > '9') && ch != '+' && ch != '-' + && ch != '.' && ch != 'e' && ch != 'E') + break; + } + + return lastIndex - 1; + } + + void SkipWhiteSpaces() + { + for (; index < json.Length; index++) + { + char ch = json[index]; + + if (ch == '\n') + lineNumber++; + + if (!char.IsWhiteSpace(json[index])) + break; + } + } + + public Token LookAhead() + { + SkipWhiteSpaces(); + + int savedIndex = index; + return NextToken(json, ref savedIndex); + } + + public Token NextToken() + { + SkipWhiteSpaces(); + return NextToken(json, ref index); + } + + static Token NextToken(char[] json, ref int index) + { + if (index == json.Length) + return Token.None; + + char c = json[index++]; + + switch (c) + { + case '{': + return Token.CurlyOpen; + case '}': + return Token.CurlyClose; + case '[': + return Token.SquaredOpen; + case ']': + return Token.SquaredClose; + case ',': + return Token.Comma; + case '"': + return Token.String; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + return Token.Number; + case ':': + return Token.Colon; + } + + index--; + + int remainingLength = json.Length - index; + + // false + if (remainingLength >= 5) + { + if (json[index] == 'f' && + json[index + 1] == 'a' && + json[index + 2] == 'l' && + json[index + 3] == 's' && + json[index + 4] == 'e') + { + index += 5; + return Token.False; + } + } + + // true + if (remainingLength >= 4) + { + if (json[index] == 't' && + json[index + 1] == 'r' && + json[index + 2] == 'u' && + json[index + 3] == 'e') + { + index += 4; + return Token.True; + } + } + + // null + if (remainingLength >= 4) + { + if (json[index] == 'n' && + json[index + 1] == 'u' && + json[index + 2] == 'l' && + json[index + 3] == 'l') + { + index += 4; + return Token.Null; + } + } + + return Token.None; + } + } + + public class JsonDecoder + { + public string errorMessage + { + get; + private set; + } + + public bool parseNumbersAsFloat + { + get; + set; + } + + Lexer lexer; + + public JsonDecoder() + { + errorMessage = null; + parseNumbersAsFloat = false; + } + + public object Decode(string text) + { + errorMessage = null; + + lexer = new Lexer(text); + lexer.parseNumbersAsFloat = parseNumbersAsFloat; + + return ParseValue(); + } + + public static object DecodeText(string text) + { + var builder = new JsonDecoder(); + return builder.Decode(text); + } + + IDictionary ParseObject() + { + var table = new Dictionary(); + + // { + lexer.NextToken(); + + while (true) + { + var token = lexer.LookAhead(); + + switch (token) + { + case Lexer.Token.None: + TriggerError("Invalid token"); + return null; + case Lexer.Token.Comma: + lexer.NextToken(); + break; + case Lexer.Token.CurlyClose: + lexer.NextToken(); + return table; + default: + // name + string name = EvalLexer(lexer.ParseString()); + + if (errorMessage != null) + return null; + + // : + token = lexer.NextToken(); + + if (token != Lexer.Token.Colon) + { + TriggerError("Invalid token; expected ':'"); + return null; + } + + // value + object value = ParseValue(); + + if (errorMessage != null) + return null; + + table[name] = value; + break; + } + } + + //return null; // Unreachable code + } + + IList ParseArray() + { + var array = new List(); + + // [ + lexer.NextToken(); + + while (true) + { + var token = lexer.LookAhead(); + + switch (token) + { + case Lexer.Token.None: + TriggerError("Invalid token"); + return null; + case Lexer.Token.Comma: + lexer.NextToken(); + break; + case Lexer.Token.SquaredClose: + lexer.NextToken(); + return array; + default: + object value = ParseValue(); + + if (errorMessage != null) + return null; + + array.Add(value); + break; + } + } + + //return null; // Unreachable code + } + + object ParseValue() + { + switch (lexer.LookAhead()) + { + case Lexer.Token.String: + return EvalLexer(lexer.ParseString()); + case Lexer.Token.Number: + if (parseNumbersAsFloat) + return EvalLexer(lexer.ParseFloatNumber()); + else + return EvalLexer(lexer.ParseDoubleNumber()); + case Lexer.Token.CurlyOpen: + return ParseObject(); + case Lexer.Token.SquaredOpen: + return ParseArray(); + case Lexer.Token.True: + lexer.NextToken(); + return true; + case Lexer.Token.False: + lexer.NextToken(); + return false; + case Lexer.Token.Null: + lexer.NextToken(); + return null; + case Lexer.Token.None: + break; + } + + TriggerError("Unable to parse value"); + return null; + } + + void TriggerError(string message) + { + errorMessage = string.Format("Error: '{0}' at line {1}", + message, lexer.lineNumber); + } + + T EvalLexer(T value) + { + if (lexer.hasError) + TriggerError("Lexical error ocurred"); + + return value; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/MathUtils.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/MathUtils.cs index 6b797e6..46991e1 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/MathUtils.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/MathUtils.cs @@ -30,71 +30,82 @@ using System; -namespace Spine3_6_32 { - public static class MathUtils { - public const float PI = 3.1415927f; - public const float PI2 = PI * 2; - public const float RadDeg = 180f / PI; - public const float DegRad = PI / 180; +namespace Spine3_6_32 +{ + public static class MathUtils + { + public const float PI = 3.1415927f; + public const float PI2 = PI * 2; + public const float RadDeg = 180f / PI; + public const float DegRad = PI / 180; - const int SIN_BITS = 14; // 16KB. Adjust for accuracy. - const int SIN_MASK = ~(-1 << SIN_BITS); - const int SIN_COUNT = SIN_MASK + 1; - const float RadFull = PI * 2; - const float DegFull = 360; - const float RadToIndex = SIN_COUNT / RadFull; - const float DegToIndex = SIN_COUNT / DegFull; - static float[] sin = new float[SIN_COUNT]; + const int SIN_BITS = 14; // 16KB. Adjust for accuracy. + const int SIN_MASK = ~(-1 << SIN_BITS); + const int SIN_COUNT = SIN_MASK + 1; + const float RadFull = PI * 2; + const float DegFull = 360; + const float RadToIndex = SIN_COUNT / RadFull; + const float DegToIndex = SIN_COUNT / DegFull; + static float[] sin = new float[SIN_COUNT]; - static MathUtils () { - for (int i = 0; i < SIN_COUNT; i++) - sin[i] = (float)Math.Sin((i + 0.5f) / SIN_COUNT * RadFull); - for (int i = 0; i < 360; i += 90) - sin[(int)(i * DegToIndex) & SIN_MASK] = (float)Math.Sin(i * DegRad); - } + static MathUtils() + { + for (int i = 0; i < SIN_COUNT; i++) + sin[i] = (float)Math.Sin((i + 0.5f) / SIN_COUNT * RadFull); + for (int i = 0; i < 360; i += 90) + sin[(int)(i * DegToIndex) & SIN_MASK] = (float)Math.Sin(i * DegRad); + } - /// Returns the sine in radians from a lookup table. - static public float Sin (float radians) { - return sin[(int)(radians * RadToIndex) & SIN_MASK]; - } + /// Returns the sine in radians from a lookup table. + static public float Sin(float radians) + { + return sin[(int)(radians * RadToIndex) & SIN_MASK]; + } - /// Returns the cosine in radians from a lookup table. - static public float Cos (float radians) { - return sin[(int)((radians + PI / 2) * RadToIndex) & SIN_MASK]; - } - - /// Returns the sine in radians from a lookup table. - static public float SinDeg (float degrees) { - return sin[(int)(degrees * DegToIndex) & SIN_MASK]; - } - - /// Returns the cosine in radians from a lookup table. - static public float CosDeg (float degrees) { - return sin[(int)((degrees + 90) * DegToIndex) & SIN_MASK]; - } + /// Returns the cosine in radians from a lookup table. + static public float Cos(float radians) + { + return sin[(int)((radians + PI / 2) * RadToIndex) & SIN_MASK]; + } - /// 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 public float Atan2 (float y, float x) { - if (x == 0f) { - if (y > 0f) return PI / 2; - if (y == 0f) return 0f; - return -PI / 2; - } - float atan, z = y / x; - if (Math.Abs(z) < 1f) { - atan = z / (1f + 0.28f * z * z); - if (x < 0f) return atan + (y < 0f ? -PI : PI); - return atan; - } - atan = PI / 2 - z / (z * z + 0.28f); - return y < 0f ? atan - PI : atan; - } + /// Returns the sine in radians from a lookup table. + static public float SinDeg(float degrees) + { + return sin[(int)(degrees * DegToIndex) & SIN_MASK]; + } - static public float Clamp (float value, float min, float max) { - if (value < min) return min; - if (value > max) return max; - return value; - } - } + /// Returns the cosine in radians from a lookup table. + static public float CosDeg(float degrees) + { + return sin[(int)((degrees + 90) * DegToIndex) & SIN_MASK]; + } + + /// 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 public float Atan2(float y, float x) + { + if (x == 0f) + { + if (y > 0f) return PI / 2; + if (y == 0f) return 0f; + return -PI / 2; + } + float atan, z = y / x; + if (Math.Abs(z) < 1f) + { + atan = z / (1f + 0.28f * z * z); + if (x < 0f) return atan + (y < 0f ? -PI : PI); + return atan; + } + atan = PI / 2 - z / (z * z + 0.28f); + return y < 0f ? atan - PI : atan; + } + + static public float Clamp(float value, float min, float max) + { + if (value < min) return min; + if (value > max) return max; + return value; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/PathConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/PathConstraint.cs index 822e831..68c09ea 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/PathConstraint.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/PathConstraint.cs @@ -30,379 +30,434 @@ using System; -namespace Spine3_6_32 { - public class PathConstraint : IConstraint { - const int NONE = -1, BEFORE = -2, AFTER = -3; +namespace Spine3_6_32 +{ + public class PathConstraint : IConstraint + { + const int NONE = -1, BEFORE = -2, AFTER = -3; - internal PathConstraintData data; - internal ExposedList bones; - internal Slot target; - internal float position, spacing, rotateMix, translateMix; + internal PathConstraintData data; + internal ExposedList bones; + internal Slot target; + internal float position, spacing, rotateMix, translateMix; - internal ExposedList spaces = new ExposedList(), positions = new ExposedList(); - internal ExposedList world = new ExposedList(), curves = new ExposedList(), lengths = new ExposedList(); - internal float[] segments = new float[10]; + internal ExposedList spaces = new ExposedList(), positions = new ExposedList(); + internal ExposedList world = new ExposedList(), curves = new ExposedList(), lengths = new ExposedList(); + internal float[] segments = new float[10]; - public int Order { get { return data.order; } } - public float Position { get { return position; } set { position = value; } } - public float Spacing { get { return spacing; } set { spacing = value; } } - public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } - public float TranslateMix { get { return translateMix; } set { translateMix = value; } } - public ExposedList Bones { get { return bones; } } - public Slot Target { get { return target; } set { target = value; } } - public PathConstraintData Data { get { return data; } } + public int Order { get { return data.order; } } + public float Position { get { return position; } set { position = value; } } + public float Spacing { get { return spacing; } set { spacing = value; } } + public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } + public float TranslateMix { get { return translateMix; } set { translateMix = value; } } + public ExposedList Bones { get { return bones; } } + public Slot Target { get { return target; } set { target = value; } } + public PathConstraintData Data { get { return data; } } - public PathConstraint (PathConstraintData data, Skeleton skeleton) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.data = data; - bones = new ExposedList(data.Bones.Count); - foreach (BoneData boneData in data.bones) - bones.Add(skeleton.FindBone(boneData.name)); - target = skeleton.FindSlot(data.target.name); - position = data.position; - spacing = data.spacing; - rotateMix = data.rotateMix; - translateMix = data.translateMix; - } + public PathConstraint(PathConstraintData data, Skeleton skeleton) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + bones = new ExposedList(data.Bones.Count); + foreach (BoneData boneData in data.bones) + bones.Add(skeleton.FindBone(boneData.name)); + target = skeleton.FindSlot(data.target.name); + position = data.position; + spacing = data.spacing; + rotateMix = data.rotateMix; + translateMix = data.translateMix; + } - public void Apply () { - Update(); - } - - public void Update () { - PathAttachment attachment = target.Attachment as PathAttachment; - if (attachment == null) return; + public void Apply() + { + Update(); + } - float rotateMix = this.rotateMix, translateMix = this.translateMix; - bool translate = translateMix > 0, rotate = rotateMix > 0; - if (!translate && !rotate) return; + public void Update() + { + PathAttachment attachment = target.Attachment as PathAttachment; + if (attachment == null) return; - PathConstraintData data = this.data; - SpacingMode spacingMode = data.spacingMode; - bool lengthSpacing = spacingMode == SpacingMode.Length; - RotateMode rotateMode = data.rotateMode; - bool tangents = rotateMode == RotateMode.Tangent, scale = rotateMode == RotateMode.ChainScale; - int boneCount = this.bones.Count, spacesCount = tangents ? boneCount : boneCount + 1; - Bone[] bonesItems = this.bones.Items; - ExposedList spaces = this.spaces.Resize(spacesCount), lengths = null; - float spacing = this.spacing; - if (scale || lengthSpacing) { - if (scale) lengths = this.lengths.Resize(boneCount); - for (int i = 0, n = spacesCount - 1; i < n;) { - Bone bone = bonesItems[i]; - float setupLength = bone.data.length, x = setupLength * bone.a, y = setupLength * bone.c; - float length = (float)Math.Sqrt(x * x + y * y); - if (scale) lengths.Items[i] = setupLength; - spaces.Items[++i] = (lengthSpacing ? Math.Max(0, setupLength + spacing) : spacing) * length / setupLength; - } - } else { - for (int i = 1; i < spacesCount; i++) - spaces.Items[i] = spacing; - } + float rotateMix = this.rotateMix, translateMix = this.translateMix; + bool translate = translateMix > 0, rotate = rotateMix > 0; + if (!translate && !rotate) return; - float[] positions = ComputeWorldPositions(attachment, spacesCount, tangents, - data.positionMode == PositionMode.Percent, spacingMode == SpacingMode.Percent); - float boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation; - bool tip; - if (offsetRotation == 0) { - tip = rotateMode == RotateMode.Chain; - } else { - tip = false; - Bone p = target.bone; - offsetRotation *= p.a * p.d - p.b * p.c > 0 ? MathUtils.DegRad : -MathUtils.DegRad; - } - for (int i = 0, p = 3; i < boneCount; i++, p += 3) { - Bone bone = bonesItems[i]; - bone.worldX += (boneX - bone.worldX) * translateMix; - bone.worldY += (boneY - bone.worldY) * translateMix; - float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY; - if (scale) { - float length = lengths.Items[i]; - if (length != 0) { - float s = ((float)Math.Sqrt(dx * dx + dy * dy) / length - 1) * rotateMix + 1; - bone.a *= s; - bone.c *= s; - } - } - boneX = x; - boneY = y; - if (rotate) { - 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.Items[i + 1] == 0) - r = positions[p + 2]; - else - r = MathUtils.Atan2(dy, dx); - r -= MathUtils.Atan2(c, a); - if (tip) { - cos = MathUtils.Cos(r); - sin = MathUtils.Sin(r); - float length = bone.data.length; - boneX += (length * (cos * a - sin * c) - dx) * rotateMix; - boneY += (length * (sin * a + cos * c) - dy) * rotateMix; - } else { - r += offsetRotation; - } - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) // - r += MathUtils.PI2; - r *= rotateMix; - cos = MathUtils.Cos(r); - sin = MathUtils.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.appliedValid = false; - } - } + PathConstraintData data = this.data; + SpacingMode spacingMode = data.spacingMode; + bool lengthSpacing = spacingMode == SpacingMode.Length; + RotateMode rotateMode = data.rotateMode; + bool tangents = rotateMode == RotateMode.Tangent, scale = rotateMode == RotateMode.ChainScale; + int boneCount = this.bones.Count, spacesCount = tangents ? boneCount : boneCount + 1; + Bone[] bonesItems = this.bones.Items; + ExposedList spaces = this.spaces.Resize(spacesCount), lengths = null; + float spacing = this.spacing; + if (scale || lengthSpacing) + { + if (scale) lengths = this.lengths.Resize(boneCount); + for (int i = 0, n = spacesCount - 1; i < n;) + { + Bone bone = bonesItems[i]; + float setupLength = bone.data.length, x = setupLength * bone.a, y = setupLength * bone.c; + float length = (float)Math.Sqrt(x * x + y * y); + if (scale) lengths.Items[i] = setupLength; + spaces.Items[++i] = (lengthSpacing ? Math.Max(0, setupLength + spacing) : spacing) * length / setupLength; + } + } + else + { + for (int i = 1; i < spacesCount; i++) + spaces.Items[i] = spacing; + } - float[] ComputeWorldPositions (PathAttachment path, int spacesCount, bool tangents, bool percentPosition, - bool percentSpacing) { + float[] positions = ComputeWorldPositions(attachment, spacesCount, tangents, + data.positionMode == PositionMode.Percent, spacingMode == SpacingMode.Percent); + float boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation; + bool tip; + if (offsetRotation == 0) + { + tip = rotateMode == RotateMode.Chain; + } + else + { + tip = false; + Bone p = target.bone; + offsetRotation *= p.a * p.d - p.b * p.c > 0 ? MathUtils.DegRad : -MathUtils.DegRad; + } + for (int i = 0, p = 3; i < boneCount; i++, p += 3) + { + Bone bone = bonesItems[i]; + bone.worldX += (boneX - bone.worldX) * translateMix; + bone.worldY += (boneY - bone.worldY) * translateMix; + float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY; + if (scale) + { + float length = lengths.Items[i]; + if (length != 0) + { + float s = ((float)Math.Sqrt(dx * dx + dy * dy) / length - 1) * rotateMix + 1; + bone.a *= s; + bone.c *= s; + } + } + boneX = x; + boneY = y; + if (rotate) + { + 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.Items[i + 1] == 0) + r = positions[p + 2]; + else + r = MathUtils.Atan2(dy, dx); + r -= MathUtils.Atan2(c, a); + if (tip) + { + cos = MathUtils.Cos(r); + sin = MathUtils.Sin(r); + float length = bone.data.length; + boneX += (length * (cos * a - sin * c) - dx) * rotateMix; + boneY += (length * (sin * a + cos * c) - dy) * rotateMix; + } + else + { + r += offsetRotation; + } + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) // + r += MathUtils.PI2; + r *= rotateMix; + cos = MathUtils.Cos(r); + sin = MathUtils.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.appliedValid = false; + } + } - Slot target = this.target; - float position = this.position; - float[] spacesItems = this.spaces.Items, output = this.positions.Resize(spacesCount * 3 + 2).Items, world; - bool closed = path.Closed; - int verticesLength = path.WorldVerticesLength, curveCount = verticesLength / 6, prevCurve = NONE; + float[] ComputeWorldPositions(PathAttachment path, int spacesCount, bool tangents, bool percentPosition, + bool percentSpacing) + { - float pathLength; - if (!path.ConstantSpeed) { - float[] lengths = path.Lengths; - curveCount -= closed ? 1 : 2; - pathLength = lengths[curveCount]; - if (percentPosition) position *= pathLength; - if (percentSpacing) { - for (int i = 0; i < spacesCount; i++) - spacesItems[i] *= pathLength; - } - world = this.world.Resize(8).Items; - for (int i = 0, o = 0, curve = 0; i < spacesCount; i++, o += 3) { - float space = spacesItems[i]; - position += space; - float p = position; + Slot target = this.target; + float position = this.position; + float[] spacesItems = this.spaces.Items, output = this.positions.Resize(spacesCount * 3 + 2).Items, world; + bool closed = path.Closed; + int verticesLength = path.WorldVerticesLength, curveCount = verticesLength / 6, prevCurve = NONE; - if (closed) { - 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, output, o); - continue; - } else if (p > pathLength) { - if (prevCurve != AFTER) { - prevCurve = AFTER; - path.ComputeWorldVertices(target, verticesLength - 6, 4, world, 0); - } - AddAfterPosition(p - pathLength, world, 0, output, o); - continue; - } + float pathLength; + if (!path.ConstantSpeed) + { + float[] lengths = path.Lengths; + curveCount -= closed ? 1 : 2; + pathLength = lengths[curveCount]; + if (percentPosition) position *= pathLength; + if (percentSpacing) + { + for (int i = 0; i < spacesCount; i++) + spacesItems[i] *= pathLength; + } + world = this.world.Resize(8).Items; + for (int i = 0, o = 0, curve = 0; i < spacesCount; i++, o += 3) + { + float space = spacesItems[i]; + position += space; + float p = position; - // 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], output, o, - tangents || (i > 0 && space == 0)); - } - return output; - } + if (closed) + { + 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, output, o); + continue; + } + else if (p > pathLength) + { + if (prevCurve != AFTER) + { + prevCurve = AFTER; + path.ComputeWorldVertices(target, verticesLength - 6, 4, world, 0); + } + AddAfterPosition(p - pathLength, world, 0, output, o); + continue; + } - // World vertices. - if (closed) { - verticesLength += 2; - world = this.world.Resize(verticesLength).Items; - 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 = this.world.Resize(verticesLength).Items; - path.ComputeWorldVertices(target, 2, verticesLength, world, 0); - } + // 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], output, o, + tangents || (i > 0 && space == 0)); + } + return output; + } - // Curve lengths. - float[] curves = this.curves.Resize(curveCount).Items; - 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 += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - dfx += ddfx; - dfy += ddfy; - ddfx += dddfx; - ddfy += dddfy; - pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - dfx += ddfx; - dfy += ddfy; - pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - dfx += ddfx + dddfx; - dfy += ddfy + dddfy; - pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - curves[i] = pathLength; - x1 = x2; - y1 = y2; - } - if (percentPosition) position *= pathLength; - if (percentSpacing) { - for (int i = 0; i < spacesCount; i++) - spacesItems[i] *= pathLength; - } + // World vertices. + if (closed) + { + verticesLength += 2; + world = this.world.Resize(verticesLength).Items; + 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 = this.world.Resize(verticesLength).Items; + path.ComputeWorldVertices(target, 2, verticesLength, world, 0); + } - float[] segments = this.segments; - float curveLength = 0; - for (int i = 0, o = 0, curve = 0, segment = 0; i < spacesCount; i++, o += 3) { - float space = spacesItems[i]; - position += space; - float p = position; + // Curve lengths. + float[] curves = this.curves.Resize(curveCount).Items; + 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 += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx; + dfy += ddfy; + pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx + dddfx; + dfy += ddfy + dddfy; + pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + curves[i] = pathLength; + x1 = x2; + y1 = y2; + } + if (percentPosition) position *= pathLength; + if (percentSpacing) + { + for (int i = 0; i < spacesCount; i++) + spacesItems[i] *= pathLength; + } - if (closed) { - p %= pathLength; - if (p < 0) p += pathLength; - curve = 0; - } else if (p < 0) { - AddBeforePosition(p, world, 0, output, o); - continue; - } else if (p > pathLength) { - AddAfterPosition(p - pathLength, world, verticesLength - 4, output, o); - continue; - } + float[] segments = this.segments; + float curveLength = 0; + for (int i = 0, o = 0, curve = 0, segment = 0; i < spacesCount; i++, o += 3) + { + float space = spacesItems[i]; + position += space; + float p = position; - // 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; - } + if (closed) + { + p %= pathLength; + if (p < 0) p += pathLength; + curve = 0; + } + else if (p < 0) + { + AddBeforePosition(p, world, 0, output, o); + continue; + } + else if (p > pathLength) + { + AddAfterPosition(p - pathLength, world, verticesLength - 4, output, o); + continue; + } - // 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 = (float)Math.Sqrt(dfx * dfx + dfy * dfy); - segments[0] = curveLength; - for (ii = 1; ii < 8; ii++) { - dfx += ddfx; - dfy += ddfy; - ddfx += dddfx; - ddfy += dddfy; - curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - segments[ii] = curveLength; - } - dfx += ddfx; - dfy += ddfy; - curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - segments[8] = curveLength; - dfx += ddfx + dddfx; - dfy += ddfy + dddfy; - curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - segments[9] = curveLength; - segment = 0; - } + // 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; + } - // 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, output, o, tangents || (i > 0 && space == 0)); - } - return output; - } + // 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 = (float)Math.Sqrt(dfx * dfx + dfy * dfy); + segments[0] = curveLength; + for (ii = 1; ii < 8; ii++) + { + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + segments[ii] = curveLength; + } + dfx += ddfx; + dfy += ddfy; + curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + segments[8] = curveLength; + dfx += ddfx + dddfx; + dfy += ddfy + dddfy; + curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + segments[9] = curveLength; + segment = 0; + } - static void AddBeforePosition (float p, float[] temp, int i, float[] output, int o) { - float x1 = temp[i], y1 = temp[i + 1], dx = temp[i + 2] - x1, dy = temp[i + 3] - y1, r = MathUtils.Atan2(dy, dx); - output[o] = x1 + p * MathUtils.Cos(r); - output[o + 1] = y1 + p * MathUtils.Sin(r); - output[o + 2] = r; - } + // 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, output, o, tangents || (i > 0 && space == 0)); + } + return output; + } - static void AddAfterPosition (float p, float[] temp, int i, float[] output, int o) { - float x1 = temp[i + 2], y1 = temp[i + 3], dx = x1 - temp[i], dy = y1 - temp[i + 1], r = MathUtils.Atan2(dy, dx); - output[o] = x1 + p * MathUtils.Cos(r); - output[o + 1] = y1 + p * MathUtils.Sin(r); - output[o + 2] = r; - } + static void AddBeforePosition(float p, float[] temp, int i, float[] output, int o) + { + float x1 = temp[i], y1 = temp[i + 1], dx = temp[i + 2] - x1, dy = temp[i + 3] - y1, r = MathUtils.Atan2(dy, dx); + output[o] = x1 + p * MathUtils.Cos(r); + output[o + 1] = y1 + p * MathUtils.Sin(r); + output[o + 2] = r; + } - static void AddCurvePosition (float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2, - float[] output, int o, bool tangents) { - if (p == 0 || float.IsNaN(p)) p = 0.0001f; - 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) output[o + 2] = (float)Math.Atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt)); - } - } + static void AddAfterPosition(float p, float[] temp, int i, float[] output, int o) + { + float x1 = temp[i + 2], y1 = temp[i + 3], dx = x1 - temp[i], dy = y1 - temp[i + 1], r = MathUtils.Atan2(dy, dx); + output[o] = x1 + p * MathUtils.Cos(r); + output[o + 1] = y1 + p * MathUtils.Sin(r); + output[o + 2] = r; + } + + static void AddCurvePosition(float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2, + float[] output, int o, bool tangents) + { + if (p == 0 || float.IsNaN(p)) p = 0.0001f; + 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) output[o + 2] = (float)Math.Atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt)); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/PathConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/PathConstraintData.cs index b9bdf77..22acdd1 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/PathConstraintData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/PathConstraintData.cs @@ -30,50 +30,57 @@ using System; -namespace Spine3_6_32 { - public class PathConstraintData { - internal string name; - internal int order; - internal ExposedList bones = new ExposedList(); - internal SlotData target; - internal PositionMode positionMode; - internal SpacingMode spacingMode; - internal RotateMode rotateMode; - internal float offsetRotation; - internal float position, spacing, rotateMix, translateMix; +namespace Spine3_6_32 +{ + public class PathConstraintData + { + internal string name; + internal int order; + internal ExposedList bones = new ExposedList(); + internal SlotData target; + internal PositionMode positionMode; + internal SpacingMode spacingMode; + internal RotateMode rotateMode; + internal float offsetRotation; + internal float position, spacing, rotateMix, translateMix; - public string Name { get { return name; } } - public int Order { get { return order; } set { order = value; } } - public ExposedList Bones { get { return bones; } } - public SlotData Target { get { return target; } set { target = value; } } - public PositionMode PositionMode { get { return positionMode; } set { positionMode = value; } } - public SpacingMode SpacingMode { get { return spacingMode; } set { spacingMode = value; } } - public RotateMode RotateMode { get { return rotateMode; } set { rotateMode = value; } } - public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } - public float Position { get { return position; } set { position = value; } } - public float Spacing { get { return spacing; } set { spacing = value; } } - public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } - public float TranslateMix { get { return translateMix; } set { translateMix = value; } } + public string Name { get { return name; } } + public int Order { get { return order; } set { order = value; } } + public ExposedList Bones { get { return bones; } } + public SlotData Target { get { return target; } set { target = value; } } + public PositionMode PositionMode { get { return positionMode; } set { positionMode = value; } } + public SpacingMode SpacingMode { get { return spacingMode; } set { spacingMode = value; } } + public RotateMode RotateMode { get { return rotateMode; } set { rotateMode = value; } } + public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } + public float Position { get { return position; } set { position = value; } } + public float Spacing { get { return spacing; } set { spacing = value; } } + public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } + public float TranslateMix { get { return translateMix; } set { translateMix = value; } } - public PathConstraintData (String name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; - } + public PathConstraintData(String name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + } - public override string ToString () { - return name; - } - } - - public enum PositionMode { - Fixed, Percent - } + public override string ToString() + { + return name; + } + } - public enum SpacingMode { - Length, Fixed, Percent - } + public enum PositionMode + { + Fixed, Percent + } - public enum RotateMode { - Tangent, Chain, ChainScale - } + public enum SpacingMode + { + Length, Fixed, Percent + } + + public enum RotateMode + { + Tangent, Chain, ChainScale + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Skeleton.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Skeleton.cs index f6f339d..2665dae 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Skeleton.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Skeleton.cs @@ -29,506 +29,579 @@ *****************************************************************************/ using System; -using System.Collections.Generic; - -namespace Spine3_6_32 { - public class Skeleton { - internal SkeletonData data; - internal ExposedList bones; - internal ExposedList slots; - internal ExposedList drawOrder; - internal ExposedList ikConstraints; - internal ExposedList transformConstraints; - internal ExposedList pathConstraints; - internal ExposedList updateCache = new ExposedList(); - internal ExposedList updateCacheReset = new ExposedList(); - internal Skin skin; - internal float r = 1, g = 1, b = 1, a = 1; - internal float time; - internal bool flipX, flipY; - internal float x, y; - - public SkeletonData Data { get { return data; } } - public ExposedList Bones { get { return bones; } } - public ExposedList UpdateCacheList { get { return updateCache; } } - public ExposedList Slots { get { return slots; } } - public ExposedList DrawOrder { get { return drawOrder; } } - public ExposedList IkConstraints { get { return ikConstraints; } } - public ExposedList PathConstraints { get { return pathConstraints; } } - public ExposedList TransformConstraints { get { return transformConstraints; } } - public Skin Skin { get { return skin; } set { skin = value; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } - public float Time { get { return time; } set { time = value; } } - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public bool FlipX { get { return flipX; } set { flipX = value; } } - public bool FlipY { get { return flipY; } set { flipY = value; } } - - public Bone RootBone { - get { return bones.Count == 0 ? null : bones.Items[0]; } - } - - public Skeleton (SkeletonData data) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - this.data = data; - - bones = new ExposedList(data.bones.Count); - foreach (BoneData boneData in data.bones) { - Bone bone; - if (boneData.parent == null) { - bone = new Bone(boneData, this, null); - } else { - Bone parent = bones.Items[boneData.parent.index]; - bone = new Bone(boneData, this, parent); - parent.children.Add(bone); - } - bones.Add(bone); - } - - slots = new ExposedList(data.slots.Count); - drawOrder = new ExposedList(data.slots.Count); - foreach (SlotData slotData in data.slots) { - Bone bone = bones.Items[slotData.boneData.index]; - Slot slot = new Slot(slotData, bone); - slots.Add(slot); - drawOrder.Add(slot); - } - - ikConstraints = new ExposedList(data.ikConstraints.Count); - foreach (IkConstraintData ikConstraintData in data.ikConstraints) - ikConstraints.Add(new IkConstraint(ikConstraintData, this)); - - transformConstraints = new ExposedList(data.transformConstraints.Count); - foreach (TransformConstraintData transformConstraintData in data.transformConstraints) - transformConstraints.Add(new TransformConstraint(transformConstraintData, this)); - - pathConstraints = new ExposedList (data.pathConstraints.Count); - foreach (PathConstraintData pathConstraintData in data.pathConstraints) - pathConstraints.Add(new PathConstraint(pathConstraintData, this)); - - UpdateCache(); - UpdateWorldTransform(); - } - - /// Caches information about bones and constraints. Must be called if bones, constraints or weighted path attachments are added - /// or removed. - public void UpdateCache () { - ExposedList updateCache = this.updateCache; - updateCache.Clear(); - this.updateCacheReset.Clear(); - - ExposedList bones = this.bones; - for (int i = 0, n = bones.Count; i < n; i++) - bones.Items[i].sorted = false; - - ExposedList ikConstraints = this.ikConstraints; - var transformConstraints = this.transformConstraints; - var pathConstraints = this.pathConstraints; - int ikCount = IkConstraints.Count, transformCount = transformConstraints.Count, pathCount = pathConstraints.Count; - int constraintCount = ikCount + transformCount + pathCount; - //outer: - for (int i = 0; i < constraintCount; i++) { - for (int ii = 0; ii < ikCount; ii++) { - IkConstraint constraint = ikConstraints.Items[ii]; - if (constraint.data.order == i) { - SortIkConstraint(constraint); - goto outer; //continue outer; - } - } - for (int ii = 0; ii < transformCount; ii++) { - TransformConstraint constraint = transformConstraints.Items[ii]; - if (constraint.data.order == i) { - SortTransformConstraint(constraint); - goto outer; //continue outer; - } - } - for (int ii = 0; ii < pathCount; ii++) { - PathConstraint constraint = pathConstraints.Items[ii]; - if (constraint.data.order == i) { - SortPathConstraint(constraint); - goto outer; //continue outer; - } - } - outer: {} - } - - for (int i = 0, n = bones.Count; i < n; i++) - SortBone(bones.Items[i]); - } - - private void SortIkConstraint (IkConstraint constraint) { - Bone target = constraint.target; - SortBone(target); - - var constrained = constraint.bones; - Bone parent = constrained.Items[0]; - SortBone(parent); - - if (constrained.Count > 1) { - Bone child = constrained.Items[constrained.Count - 1]; - if (!updateCache.Contains(child)) - updateCacheReset.Add(child); - } - - updateCache.Add(constraint); - - SortReset(parent.children); - constrained.Items[constrained.Count - 1].sorted = true; - } - - private void SortPathConstraint (PathConstraint constraint) { - Slot slot = constraint.target; - int slotIndex = slot.data.index; - Bone slotBone = slot.bone; - if (skin != null) SortPathConstraintAttachment(skin, slotIndex, slotBone); - if (data.defaultSkin != null && data.defaultSkin != skin) - SortPathConstraintAttachment(data.defaultSkin, slotIndex, slotBone); - for (int ii = 0, nn = data.skins.Count; ii < nn; ii++) - SortPathConstraintAttachment(data.skins.Items[ii], slotIndex, slotBone); - - Attachment attachment = slot.attachment; - if (attachment is PathAttachment) SortPathConstraintAttachment(attachment, slotBone); - - var constrained = constraint.bones; - int boneCount = constrained.Count; - for (int i = 0; i < boneCount; i++) - SortBone(constrained.Items[i]); - - updateCache.Add(constraint); - - for (int i = 0; i < boneCount; i++) - SortReset(constrained.Items[i].children); - for (int i = 0; i < boneCount; i++) - constrained.Items[i].sorted = true; - } - - private void SortTransformConstraint (TransformConstraint constraint) { - SortBone(constraint.target); - - var constrained = constraint.bones; - int boneCount = constrained.Count; - if (constraint.data.local) { - for (int i = 0; i < boneCount; i++) { - Bone child = constrained.Items[i]; - SortBone(child.parent); - if (!updateCache.Contains(child)) updateCacheReset.Add(child); - } - } else { - for (int i = 0; i < boneCount; i++) - SortBone(constrained.Items[i]); - } - - updateCache.Add(constraint); - - for (int i = 0; i < boneCount; i++) - SortReset(constrained.Items[i].children); - for (int i = 0; i < boneCount; i++) - constrained.Items[i].sorted = true; - } - - private void SortPathConstraintAttachment (Skin skin, int slotIndex, Bone slotBone) { - foreach (var entry in skin.Attachments) - if (entry.Key.slotIndex == slotIndex) SortPathConstraintAttachment(entry.Value, slotBone); - } - - private void SortPathConstraintAttachment (Attachment attachment, Bone slotBone) { - if (!(attachment is PathAttachment)) return; - int[] pathBones = ((PathAttachment)attachment).bones; - if (pathBones == null) - SortBone(slotBone); - else { - var bones = this.bones; - for (int i = 0, n = pathBones.Length; i < n;) { - int nn = pathBones[i++]; - nn += i; - while (i < nn) - SortBone(bones.Items[pathBones[i++]]); - } - } - } - - private void SortBone (Bone bone) { - if (bone.sorted) return; - Bone parent = bone.parent; - if (parent != null) SortBone(parent); - bone.sorted = true; - updateCache.Add(bone); - } - - private static void SortReset (ExposedList bones) { - var bonesItems = bones.Items; - for (int i = 0, n = bones.Count; i < n; i++) { - Bone bone = bonesItems[i]; - if (bone.sorted) SortReset(bone.children); - bone.sorted = false; - } - } - - /// Updates the world transform for each bone and applies constraints. - public void UpdateWorldTransform () { - var updateCacheReset = this.updateCacheReset; - var updateCacheResetItems = updateCacheReset.Items; - for (int i = 0, n = updateCacheReset.Count; i < n; i++) { - Bone bone = updateCacheResetItems[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; - bone.appliedValid = true; - } - var updateItems = this.updateCache.Items; - for (int i = 0, n = updateCache.Count; i < n; i++) - updateItems[i].Update(); - } - - /// Sets the bones, constraints, and slots to their setup pose values. - public void SetToSetupPose () { - SetBonesToSetupPose(); - SetSlotsToSetupPose(); - } - - /// Sets the bones and constraints to their setup pose values. - public void SetBonesToSetupPose () { - var bonesItems = this.bones.Items; - for (int i = 0, n = bones.Count; i < n; i++) - bonesItems[i].SetToSetupPose(); - - var ikConstraintsItems = this.ikConstraints.Items; - for (int i = 0, n = ikConstraints.Count; i < n; i++) { - IkConstraint constraint = ikConstraintsItems[i]; - constraint.bendDirection = constraint.data.bendDirection; - constraint.mix = constraint.data.mix; - } - - var transformConstraintsItems = this.transformConstraints.Items; - for (int i = 0, n = transformConstraints.Count; i < n; i++) { - TransformConstraint constraint = transformConstraintsItems[i]; - TransformConstraintData constraintData = constraint.data; - constraint.rotateMix = constraintData.rotateMix; - constraint.translateMix = constraintData.translateMix; - constraint.scaleMix = constraintData.scaleMix; - constraint.shearMix = constraintData.shearMix; - } - - var pathConstraintItems = this.pathConstraints.Items; - for (int i = 0, n = pathConstraints.Count; i < n; i++) { - PathConstraint constraint = pathConstraintItems[i]; - PathConstraintData constraintData = constraint.data; - constraint.position = constraintData.position; - constraint.spacing = constraintData.spacing; - constraint.rotateMix = constraintData.rotateMix; - constraint.translateMix = constraintData.translateMix; - } - } - - public void SetSlotsToSetupPose () { - var slots = this.slots; - var slotsItems = slots.Items; - drawOrder.Clear(); - for (int i = 0, n = slots.Count; i < n; i++) - drawOrder.Add(slotsItems[i]); - - for (int i = 0, n = slots.Count; i < n; i++) - slotsItems[i].SetToSetupPose(); - } - - /// May be null. - public Bone FindBone (string boneName) { - if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); - var bones = this.bones; - var bonesItems = bones.Items; - for (int i = 0, n = bones.Count; i < n; i++) { - Bone bone = bonesItems[i]; - if (bone.data.name == boneName) return bone; - } - return null; - } - - /// -1 if the bone was not found. - public int FindBoneIndex (string boneName) { - if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); - var bones = this.bones; - var bonesItems = bones.Items; - for (int i = 0, n = bones.Count; i < n; i++) - if (bonesItems[i].data.name == boneName) return i; - return -1; - } - - /// May be null. - public Slot FindSlot (string slotName) { - if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); - var slots = this.slots; - var slotsItems = slots.Items; - for (int i = 0, n = slots.Count; i < n; i++) { - Slot slot = slotsItems[i]; - if (slot.data.name == slotName) return slot; - } - return null; - } - - /// -1 if the bone was not found. - public int FindSlotIndex (string slotName) { - if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); - var slots = this.slots; - var slotsItems = slots.Items; - for (int i = 0, n = slots.Count; i < n; i++) - if (slotsItems[i].data.name.Equals(slotName)) return i; - return -1; - } - - /// Sets a skin by name (see SetSkin). - public void SetSkin (string skinName) { - Skin foundSkin = data.FindSkin(skinName); - if (foundSkin == null) throw new ArgumentException("Skin not found: " + skinName, "skinName"); - SetSkin(foundSkin); - } - - /// Sets the skin used to look up attachments before looking in the {@link SkeletonData#getDefaultSkin() default - /// skin}. Attachmentsfrom 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. - /// May be null. - public void SetSkin (Skin newSkin) { - if (newSkin != null) { - if (skin != null) - newSkin.AttachAll(this, skin); - else { - ExposedList slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) { - Slot slot = slots.Items[i]; - string name = slot.data.attachmentName; - if (name != null) { - Attachment attachment = newSkin.GetAttachment(i, name); - if (attachment != null) slot.Attachment = attachment; - } - } - } - } - skin = newSkin; - } - - /// May be null. - public Attachment GetAttachment (string slotName, string attachmentName) { - return GetAttachment(data.FindSlotIndex(slotName), attachmentName); - } - - /// May be null. - public Attachment GetAttachment (int slotIndex, string attachmentName) { - if (attachmentName == null) throw new ArgumentNullException("attachmentName", "attachmentName cannot be null."); - if (skin != null) { - Attachment attachment = skin.GetAttachment(slotIndex, attachmentName); - if (attachment != null) return attachment; - } - return data.defaultSkin != null ? data.defaultSkin.GetAttachment(slotIndex, attachmentName) : null; - } - - /// May be null. - public void SetAttachment (string slotName, string attachmentName) { - if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); - ExposedList slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) { - Slot slot = slots.Items[i]; - if (slot.data.name == slotName) { - Attachment attachment = null; - if (attachmentName != null) { - attachment = GetAttachment(i, attachmentName); - if (attachment == null) throw new Exception("Attachment not found: " + attachmentName + ", for slot: " + slotName); - } - slot.Attachment = attachment; - return; - } - } - throw new Exception("Slot not found: " + slotName); - } - - /// May be null. - public IkConstraint FindIkConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - ExposedList ikConstraints = this.ikConstraints; - for (int i = 0, n = ikConstraints.Count; i < n; i++) { - IkConstraint ikConstraint = ikConstraints.Items[i]; - if (ikConstraint.data.name == constraintName) return ikConstraint; - } - return null; - } - - /// May be null. - public TransformConstraint FindTransformConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - ExposedList transformConstraints = this.transformConstraints; - for (int i = 0, n = transformConstraints.Count; i < n; i++) { - TransformConstraint transformConstraint = transformConstraints.Items[i]; - if (transformConstraint.data.name == constraintName) return transformConstraint; - } - return null; - } - - /// May be null. - public PathConstraint FindPathConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - ExposedList pathConstraints = this.pathConstraints; - for (int i = 0, n = pathConstraints.Count; i < n; i++) { - PathConstraint constraint = pathConstraints.Items[i]; - if (constraint.data.name.Equals(constraintName)) return constraint; - } - return null; - } - - public void Update (float delta) { - time += delta; - } - - /// Returns the axis aligned bounding box (AABB) of the region and mesh attachments for the current pose. - /// The horizontal distance between the skeleton origin and the left side of the AABB. - /// The vertical distance between the skeleton origin and the bottom side of the AABB. - /// The width of the AABB - /// The height of the AABB. - /// Reference to hold a float[]. May be a null reference. This method will assign it a new float[] with the appropriate size as needed. - public void GetBounds (out float x, out float y, out float width, out float height, ref float[] vertexBuffer) { - float[] temp = vertexBuffer; - temp = temp ?? new float[8]; - var drawOrderItems = this.drawOrder.Items; - float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; - for (int i = 0, n = this.drawOrder.Count; i < n; i++) { - Slot slot = drawOrderItems[i]; - int verticesLength = 0; - float[] vertices = null; - Attachment attachment = slot.attachment; - var regionAttachment = attachment as RegionAttachment; - if (regionAttachment != null) { - verticesLength = 8; - if (temp.Length < 8) temp = new float[8]; - regionAttachment.ComputeWorldVertices(slot.bone, temp, 0); - } else { - var meshAttachment = attachment as MeshAttachment; - if (meshAttachment != null) { - MeshAttachment mesh = meshAttachment; - verticesLength = mesh.WorldVerticesLength; - if (temp.Length < verticesLength) temp = new float[verticesLength]; - mesh.ComputeWorldVertices(slot, 0, verticesLength, temp, 0); - } - } - - if (vertices != null) { - for (int ii = 0; ii < verticesLength; ii += 2) { - float vx = vertices[ii], vy = vertices[ii + 1]; - minX = Math.Min(minX, vx); - minY = Math.Min(minY, vy); - maxX = Math.Max(maxX, vx); - maxY = Math.Max(maxY, vy); - } - } - } - x = minX; - y = minY; - width = maxX - minX; - height = maxY - minY; - vertexBuffer = temp; - } - } + +namespace Spine3_6_32 +{ + public class Skeleton + { + internal SkeletonData data; + internal ExposedList bones; + internal ExposedList slots; + internal ExposedList drawOrder; + internal ExposedList ikConstraints; + internal ExposedList transformConstraints; + internal ExposedList pathConstraints; + internal ExposedList updateCache = new ExposedList(); + internal ExposedList updateCacheReset = new ExposedList(); + internal Skin skin; + internal float r = 1, g = 1, b = 1, a = 1; + internal float time; + internal bool flipX, flipY; + internal float x, y; + + public SkeletonData Data { get { return data; } } + public ExposedList Bones { get { return bones; } } + public ExposedList UpdateCacheList { get { return updateCache; } } + public ExposedList Slots { get { return slots; } } + public ExposedList DrawOrder { get { return drawOrder; } } + public ExposedList IkConstraints { get { return ikConstraints; } } + public ExposedList PathConstraints { get { return pathConstraints; } } + public ExposedList TransformConstraints { get { return transformConstraints; } } + public Skin Skin { get { return skin; } set { skin = value; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } + public float Time { get { return time; } set { time = value; } } + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public bool FlipX { get { return flipX; } set { flipX = value; } } + public bool FlipY { get { return flipY; } set { flipY = value; } } + + public Bone RootBone + { + get { return bones.Count == 0 ? null : bones.Items[0]; } + } + + public Skeleton(SkeletonData data) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + this.data = data; + + bones = new ExposedList(data.bones.Count); + foreach (BoneData boneData in data.bones) + { + Bone bone; + if (boneData.parent == null) + { + bone = new Bone(boneData, this, null); + } + else + { + Bone parent = bones.Items[boneData.parent.index]; + bone = new Bone(boneData, this, parent); + parent.children.Add(bone); + } + bones.Add(bone); + } + + slots = new ExposedList(data.slots.Count); + drawOrder = new ExposedList(data.slots.Count); + foreach (SlotData slotData in data.slots) + { + Bone bone = bones.Items[slotData.boneData.index]; + Slot slot = new Slot(slotData, bone); + slots.Add(slot); + drawOrder.Add(slot); + } + + ikConstraints = new ExposedList(data.ikConstraints.Count); + foreach (IkConstraintData ikConstraintData in data.ikConstraints) + ikConstraints.Add(new IkConstraint(ikConstraintData, this)); + + transformConstraints = new ExposedList(data.transformConstraints.Count); + foreach (TransformConstraintData transformConstraintData in data.transformConstraints) + transformConstraints.Add(new TransformConstraint(transformConstraintData, this)); + + pathConstraints = new ExposedList(data.pathConstraints.Count); + foreach (PathConstraintData pathConstraintData in data.pathConstraints) + pathConstraints.Add(new PathConstraint(pathConstraintData, this)); + + UpdateCache(); + UpdateWorldTransform(); + } + + /// Caches information about bones and constraints. Must be called if bones, constraints or weighted path attachments are added + /// or removed. + public void UpdateCache() + { + ExposedList updateCache = this.updateCache; + updateCache.Clear(); + this.updateCacheReset.Clear(); + + ExposedList bones = this.bones; + for (int i = 0, n = bones.Count; i < n; i++) + bones.Items[i].sorted = false; + + ExposedList ikConstraints = this.ikConstraints; + var transformConstraints = this.transformConstraints; + var pathConstraints = this.pathConstraints; + int ikCount = IkConstraints.Count, transformCount = transformConstraints.Count, pathCount = pathConstraints.Count; + int constraintCount = ikCount + transformCount + pathCount; + //outer: + for (int i = 0; i < constraintCount; i++) + { + for (int ii = 0; ii < ikCount; ii++) + { + IkConstraint constraint = ikConstraints.Items[ii]; + if (constraint.data.order == i) + { + SortIkConstraint(constraint); + goto outer; //continue outer; + } + } + for (int ii = 0; ii < transformCount; ii++) + { + TransformConstraint constraint = transformConstraints.Items[ii]; + if (constraint.data.order == i) + { + SortTransformConstraint(constraint); + goto outer; //continue outer; + } + } + for (int ii = 0; ii < pathCount; ii++) + { + PathConstraint constraint = pathConstraints.Items[ii]; + if (constraint.data.order == i) + { + SortPathConstraint(constraint); + goto outer; //continue outer; + } + } + outer: { } + } + + for (int i = 0, n = bones.Count; i < n; i++) + SortBone(bones.Items[i]); + } + + private void SortIkConstraint(IkConstraint constraint) + { + Bone target = constraint.target; + SortBone(target); + + var constrained = constraint.bones; + Bone parent = constrained.Items[0]; + SortBone(parent); + + if (constrained.Count > 1) + { + Bone child = constrained.Items[constrained.Count - 1]; + if (!updateCache.Contains(child)) + updateCacheReset.Add(child); + } + + updateCache.Add(constraint); + + SortReset(parent.children); + constrained.Items[constrained.Count - 1].sorted = true; + } + + private void SortPathConstraint(PathConstraint constraint) + { + Slot slot = constraint.target; + int slotIndex = slot.data.index; + Bone slotBone = slot.bone; + if (skin != null) SortPathConstraintAttachment(skin, slotIndex, slotBone); + if (data.defaultSkin != null && data.defaultSkin != skin) + SortPathConstraintAttachment(data.defaultSkin, slotIndex, slotBone); + for (int ii = 0, nn = data.skins.Count; ii < nn; ii++) + SortPathConstraintAttachment(data.skins.Items[ii], slotIndex, slotBone); + + Attachment attachment = slot.attachment; + if (attachment is PathAttachment) SortPathConstraintAttachment(attachment, slotBone); + + var constrained = constraint.bones; + int boneCount = constrained.Count; + for (int i = 0; i < boneCount; i++) + SortBone(constrained.Items[i]); + + updateCache.Add(constraint); + + for (int i = 0; i < boneCount; i++) + SortReset(constrained.Items[i].children); + for (int i = 0; i < boneCount; i++) + constrained.Items[i].sorted = true; + } + + private void SortTransformConstraint(TransformConstraint constraint) + { + SortBone(constraint.target); + + var constrained = constraint.bones; + int boneCount = constrained.Count; + if (constraint.data.local) + { + for (int i = 0; i < boneCount; i++) + { + Bone child = constrained.Items[i]; + SortBone(child.parent); + if (!updateCache.Contains(child)) updateCacheReset.Add(child); + } + } + else + { + for (int i = 0; i < boneCount; i++) + SortBone(constrained.Items[i]); + } + + updateCache.Add(constraint); + + for (int i = 0; i < boneCount; i++) + SortReset(constrained.Items[i].children); + for (int i = 0; i < boneCount; i++) + constrained.Items[i].sorted = true; + } + + private void SortPathConstraintAttachment(Skin skin, int slotIndex, Bone slotBone) + { + foreach (var entry in skin.Attachments) + if (entry.Key.slotIndex == slotIndex) SortPathConstraintAttachment(entry.Value, slotBone); + } + + private void SortPathConstraintAttachment(Attachment attachment, Bone slotBone) + { + if (!(attachment is PathAttachment)) return; + int[] pathBones = ((PathAttachment)attachment).bones; + if (pathBones == null) + SortBone(slotBone); + else + { + var bones = this.bones; + for (int i = 0, n = pathBones.Length; i < n;) + { + int nn = pathBones[i++]; + nn += i; + while (i < nn) + SortBone(bones.Items[pathBones[i++]]); + } + } + } + + private void SortBone(Bone bone) + { + if (bone.sorted) return; + Bone parent = bone.parent; + if (parent != null) SortBone(parent); + bone.sorted = true; + updateCache.Add(bone); + } + + private static void SortReset(ExposedList bones) + { + var bonesItems = bones.Items; + for (int i = 0, n = bones.Count; i < n; i++) + { + Bone bone = bonesItems[i]; + if (bone.sorted) SortReset(bone.children); + bone.sorted = false; + } + } + + /// Updates the world transform for each bone and applies constraints. + public void UpdateWorldTransform() + { + var updateCacheReset = this.updateCacheReset; + var updateCacheResetItems = updateCacheReset.Items; + for (int i = 0, n = updateCacheReset.Count; i < n; i++) + { + Bone bone = updateCacheResetItems[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; + bone.appliedValid = true; + } + var updateItems = this.updateCache.Items; + for (int i = 0, n = updateCache.Count; i < n; i++) + updateItems[i].Update(); + } + + /// Sets the bones, constraints, and slots to their setup pose values. + public void SetToSetupPose() + { + SetBonesToSetupPose(); + SetSlotsToSetupPose(); + } + + /// Sets the bones and constraints to their setup pose values. + public void SetBonesToSetupPose() + { + var bonesItems = this.bones.Items; + for (int i = 0, n = bones.Count; i < n; i++) + bonesItems[i].SetToSetupPose(); + + var ikConstraintsItems = this.ikConstraints.Items; + for (int i = 0, n = ikConstraints.Count; i < n; i++) + { + IkConstraint constraint = ikConstraintsItems[i]; + constraint.bendDirection = constraint.data.bendDirection; + constraint.mix = constraint.data.mix; + } + + var transformConstraintsItems = this.transformConstraints.Items; + for (int i = 0, n = transformConstraints.Count; i < n; i++) + { + TransformConstraint constraint = transformConstraintsItems[i]; + TransformConstraintData constraintData = constraint.data; + constraint.rotateMix = constraintData.rotateMix; + constraint.translateMix = constraintData.translateMix; + constraint.scaleMix = constraintData.scaleMix; + constraint.shearMix = constraintData.shearMix; + } + + var pathConstraintItems = this.pathConstraints.Items; + for (int i = 0, n = pathConstraints.Count; i < n; i++) + { + PathConstraint constraint = pathConstraintItems[i]; + PathConstraintData constraintData = constraint.data; + constraint.position = constraintData.position; + constraint.spacing = constraintData.spacing; + constraint.rotateMix = constraintData.rotateMix; + constraint.translateMix = constraintData.translateMix; + } + } + + public void SetSlotsToSetupPose() + { + var slots = this.slots; + var slotsItems = slots.Items; + drawOrder.Clear(); + for (int i = 0, n = slots.Count; i < n; i++) + drawOrder.Add(slotsItems[i]); + + for (int i = 0, n = slots.Count; i < n; i++) + slotsItems[i].SetToSetupPose(); + } + + /// May be null. + public Bone FindBone(string boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); + var bones = this.bones; + var bonesItems = bones.Items; + for (int i = 0, n = bones.Count; i < n; i++) + { + Bone bone = bonesItems[i]; + if (bone.data.name == boneName) return bone; + } + return null; + } + + /// -1 if the bone was not found. + public int FindBoneIndex(string boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); + var bones = this.bones; + var bonesItems = bones.Items; + for (int i = 0, n = bones.Count; i < n; i++) + if (bonesItems[i].data.name == boneName) return i; + return -1; + } + + /// May be null. + public Slot FindSlot(string slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + var slots = this.slots; + var slotsItems = slots.Items; + for (int i = 0, n = slots.Count; i < n; i++) + { + Slot slot = slotsItems[i]; + if (slot.data.name == slotName) return slot; + } + return null; + } + + /// -1 if the bone was not found. + public int FindSlotIndex(string slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + var slots = this.slots; + var slotsItems = slots.Items; + for (int i = 0, n = slots.Count; i < n; i++) + if (slotsItems[i].data.name.Equals(slotName)) return i; + return -1; + } + + /// Sets a skin by name (see SetSkin). + public void SetSkin(string skinName) + { + Skin foundSkin = data.FindSkin(skinName); + if (foundSkin == null) throw new ArgumentException("Skin not found: " + skinName, "skinName"); + SetSkin(foundSkin); + } + + /// Sets the skin used to look up attachments before looking in the {@link SkeletonData#getDefaultSkin() default + /// skin}. Attachmentsfrom 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. + /// May be null. + public void SetSkin(Skin newSkin) + { + if (newSkin != null) + { + if (skin != null) + newSkin.AttachAll(this, skin); + else + { + ExposedList slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + { + Slot slot = slots.Items[i]; + string name = slot.data.attachmentName; + if (name != null) + { + Attachment attachment = newSkin.GetAttachment(i, name); + if (attachment != null) slot.Attachment = attachment; + } + } + } + } + skin = newSkin; + } + + /// May be null. + public Attachment GetAttachment(string slotName, string attachmentName) + { + return GetAttachment(data.FindSlotIndex(slotName), attachmentName); + } + + /// May be null. + public Attachment GetAttachment(int slotIndex, string attachmentName) + { + if (attachmentName == null) throw new ArgumentNullException("attachmentName", "attachmentName cannot be null."); + if (skin != null) + { + Attachment attachment = skin.GetAttachment(slotIndex, attachmentName); + if (attachment != null) return attachment; + } + return data.defaultSkin != null ? data.defaultSkin.GetAttachment(slotIndex, attachmentName) : null; + } + + /// May be null. + public void SetAttachment(string slotName, string attachmentName) + { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + ExposedList slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + { + Slot slot = slots.Items[i]; + if (slot.data.name == slotName) + { + Attachment attachment = null; + if (attachmentName != null) + { + attachment = GetAttachment(i, attachmentName); + if (attachment == null) throw new Exception("Attachment not found: " + attachmentName + ", for slot: " + slotName); + } + slot.Attachment = attachment; + return; + } + } + throw new Exception("Slot not found: " + slotName); + } + + /// May be null. + public IkConstraint FindIkConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + ExposedList ikConstraints = this.ikConstraints; + for (int i = 0, n = ikConstraints.Count; i < n; i++) + { + IkConstraint ikConstraint = ikConstraints.Items[i]; + if (ikConstraint.data.name == constraintName) return ikConstraint; + } + return null; + } + + /// May be null. + public TransformConstraint FindTransformConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + ExposedList transformConstraints = this.transformConstraints; + for (int i = 0, n = transformConstraints.Count; i < n; i++) + { + TransformConstraint transformConstraint = transformConstraints.Items[i]; + if (transformConstraint.data.name == constraintName) return transformConstraint; + } + return null; + } + + /// May be null. + public PathConstraint FindPathConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + ExposedList pathConstraints = this.pathConstraints; + for (int i = 0, n = pathConstraints.Count; i < n; i++) + { + PathConstraint constraint = pathConstraints.Items[i]; + if (constraint.data.name.Equals(constraintName)) return constraint; + } + return null; + } + + public void Update(float delta) + { + time += delta; + } + + /// Returns the axis aligned bounding box (AABB) of the region and mesh attachments for the current pose. + /// The horizontal distance between the skeleton origin and the left side of the AABB. + /// The vertical distance between the skeleton origin and the bottom side of the AABB. + /// The width of the AABB + /// The height of the AABB. + /// Reference to hold a float[]. May be a null reference. This method will assign it a new float[] with the appropriate size as needed. + public void GetBounds(out float x, out float y, out float width, out float height, ref float[] vertexBuffer) + { + float[] temp = vertexBuffer; + temp = temp ?? new float[8]; + var drawOrderItems = this.drawOrder.Items; + float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; + for (int i = 0, n = this.drawOrder.Count; i < n; i++) + { + Slot slot = drawOrderItems[i]; + int verticesLength = 0; + float[] vertices = null; + Attachment attachment = slot.attachment; + var regionAttachment = attachment as RegionAttachment; + if (regionAttachment != null) + { + verticesLength = 8; + if (temp.Length < 8) temp = new float[8]; + regionAttachment.ComputeWorldVertices(slot.bone, temp, 0); + } + else + { + var meshAttachment = attachment as MeshAttachment; + if (meshAttachment != null) + { + MeshAttachment mesh = meshAttachment; + verticesLength = mesh.WorldVerticesLength; + if (temp.Length < verticesLength) temp = new float[verticesLength]; + mesh.ComputeWorldVertices(slot, 0, verticesLength, temp, 0); + } + } + + if (vertices != null) + { + for (int ii = 0; ii < verticesLength; ii += 2) + { + float vx = vertices[ii], vy = vertices[ii + 1]; + minX = Math.Min(minX, vx); + minY = Math.Min(minY, vy); + maxX = Math.Max(maxX, vx); + maxY = Math.Max(maxY, vy); + } + } + } + x = minX; + y = minY; + width = maxX - minX; + height = maxY - minY; + vertexBuffer = temp; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/SkeletonBinary.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/SkeletonBinary.cs index e7de1fa..404b937 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/SkeletonBinary.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/SkeletonBinary.cs @@ -33,50 +33,54 @@ #endif using System; -using System.IO; using System.Collections.Generic; +using System.IO; #if WINDOWS_STOREAPP using System.Threading.Tasks; using Windows.Storage; #endif -namespace Spine3_6_32 { - public class SkeletonBinary { - public const int BONE_ROTATE = 0; - public const int BONE_TRANSLATE = 1; - public const int BONE_SCALE = 2; - public const int BONE_SHEAR = 3; - - public const int SLOT_ATTACHMENT = 0; - public const int SLOT_COLOR = 1; - public const int SLOT_TWO_COLOR = 2; - - public const int PATH_POSITION = 0; - public const int PATH_SPACING = 1; - public const int PATH_MIX = 2; - - public const int CURVE_LINEAR = 0; - public const int CURVE_STEPPED = 1; - public const int CURVE_BEZIER = 2; - - public float Scale { get; set; } - - private AttachmentLoader attachmentLoader; - private byte[] buffer = new byte[32]; - private List linkedMeshes = new List(); - - public SkeletonBinary (params Atlas[] atlasArray) - : this(new AtlasAttachmentLoader(atlasArray)) { - } - - public SkeletonBinary (AttachmentLoader attachmentLoader) { - if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader"); - this.attachmentLoader = attachmentLoader; - Scale = 1; - } - - #if !ISUNITY && WINDOWS_STOREAPP +namespace Spine3_6_32 +{ + public class SkeletonBinary + { + public const int BONE_ROTATE = 0; + public const int BONE_TRANSLATE = 1; + public const int BONE_SCALE = 2; + public const int BONE_SHEAR = 3; + + public const int SLOT_ATTACHMENT = 0; + public const int SLOT_COLOR = 1; + public const int SLOT_TWO_COLOR = 2; + + public const int PATH_POSITION = 0; + public const int PATH_SPACING = 1; + public const int PATH_MIX = 2; + + public const int CURVE_LINEAR = 0; + public const int CURVE_STEPPED = 1; + public const int CURVE_BEZIER = 2; + + public float Scale { get; set; } + + private AttachmentLoader attachmentLoader; + private byte[] buffer = new byte[32]; + private List linkedMeshes = new List(); + + public SkeletonBinary(params Atlas[] atlasArray) + : this(new AtlasAttachmentLoader(atlasArray)) + { + } + + public SkeletonBinary(AttachmentLoader attachmentLoader) + { + if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader"); + this.attachmentLoader = attachmentLoader; + Scale = 1; + } + +#if !ISUNITY && WINDOWS_STOREAPP private async Task ReadFile(string path) { var folder = Windows.ApplicationModel.Package.Current.InstalledLocation; using (var input = new BufferedStream(await folder.GetFileAsync(path).AsTask().ConfigureAwait(false))) { @@ -89,805 +93,908 @@ private async Task ReadFile(string path) { public SkeletonData ReadSkeletonData (String path) { return this.ReadFile(path).Result; } - #else - public SkeletonData ReadSkeletonData (String path) { - #if WINDOWS_PHONE +#else + public SkeletonData ReadSkeletonData(String path) + { +#if WINDOWS_PHONE using (var input = new BufferedStream(Microsoft.Xna.Framework.TitleContainer.OpenStream(path))) { - #else - using (var input = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) { - #endif - SkeletonData skeletonData = ReadSkeletonData(input); - skeletonData.name = Path.GetFileNameWithoutExtension(path); - return skeletonData; - } - } - #endif // WINDOWS_STOREAPP - - public static readonly TransformMode[] TransformModeValues = { - TransformMode.Normal, - TransformMode.OnlyTranslation, - TransformMode.NoRotationOrReflection, - TransformMode.NoScale, - TransformMode.NoScaleOrReflection - }; - - /// Returns the version string of binary skeleton data. - public static string GetVersionString (Stream input) { - if (input == null) throw new ArgumentNullException("input"); - - try { - // Hash. - int byteCount = ReadVarint(input, true); - if (byteCount > 1) input.Position += byteCount - 1; - - // Version. - byteCount = ReadVarint(input, true); - if (byteCount > 1) { - byteCount--; - var buffer = new byte[byteCount]; - ReadFully(input, buffer, 0, byteCount); - return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount); - } - - throw new ArgumentException("Stream does not contain a valid binary Skeleton Data.", "input"); - } catch (Exception e) { - throw new ArgumentException("Stream does not contain a valid binary Skeleton Data.\n" + e, "input"); - } - } - - public SkeletonData ReadSkeletonData (Stream input) { - if (input == null) throw new ArgumentNullException("input"); - float scale = Scale; - - var skeletonData = new SkeletonData(); - skeletonData.hash = ReadString(input); - if (skeletonData.hash.Length == 0) skeletonData.hash = null; - skeletonData.version = ReadString(input); - if (skeletonData.version.Length == 0) skeletonData.version = null; - skeletonData.width = ReadFloat(input); - skeletonData.height = ReadFloat(input); - - bool nonessential = ReadBoolean(input); - - if (nonessential) { - skeletonData.fps = ReadFloat(input); - skeletonData.imagesPath = ReadString(input); - if (skeletonData.imagesPath.Length == 0) skeletonData.imagesPath = null; - } - - // Bones. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - String name = ReadString(input); - BoneData parent = i == 0 ? null : skeletonData.bones.Items[ReadVarint(input, true)]; - BoneData data = new BoneData(i, name, 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.transformMode = TransformModeValues[ReadVarint(input, true)]; - if (nonessential) ReadInt(input); // Skip bone color. - skeletonData.bones.Add(data); - } - - // Slots. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - String slotName = ReadString(input); - BoneData boneData = skeletonData.bones.Items[ReadVarint(input, true)]; - SlotData slotData = new SlotData(i, slotName, boneData); - int color = ReadInt(input); - slotData.r = ((color & 0xff000000) >> 24) / 255f; - slotData.g = ((color & 0x00ff0000) >> 16) / 255f; - slotData.b = ((color & 0x0000ff00) >> 8) / 255f; - slotData.a = ((color & 0x000000ff)) / 255f; - - int darkColor = ReadInt(input); // 0x00rrggbb - if (darkColor != -1) { - slotData.hasSecondColor = true; - slotData.r2 = ((darkColor & 0x00ff0000) >> 16) / 255f; - slotData.g2 = ((darkColor & 0x0000ff00) >> 8) / 255f; - slotData.b2 = ((darkColor & 0x000000ff)) / 255f; - } - - slotData.attachmentName = ReadString(input); - slotData.blendMode = (BlendMode)ReadVarint(input, true); - skeletonData.slots.Add(slotData); - } - - // IK constraints. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - IkConstraintData data = new IkConstraintData(ReadString(input)); - data.order = ReadVarint(input, true); - for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) - data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]); - data.target = skeletonData.bones.Items[ReadVarint(input, true)]; - data.mix = ReadFloat(input); - data.bendDirection = ReadSByte(input); - skeletonData.ikConstraints.Add(data); - } - - // Transform constraints. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - TransformConstraintData data = new TransformConstraintData(ReadString(input)); - data.order = ReadVarint(input, true); - for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) - data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]); - data.target = skeletonData.bones.Items[ReadVarint(input, true)]; - data.local = ReadBoolean(input); - data.relative = ReadBoolean(input); - data.offsetRotation = ReadFloat(input); - data.offsetX = ReadFloat(input) * scale; - data.offsetY = ReadFloat(input) * scale; - data.offsetScaleX = ReadFloat(input); - data.offsetScaleY = ReadFloat(input); - data.offsetShearY = ReadFloat(input); - data.rotateMix = ReadFloat(input); - data.translateMix = ReadFloat(input); - data.scaleMix = ReadFloat(input); - data.shearMix = ReadFloat(input); - skeletonData.transformConstraints.Add(data); - } - - // Path constraints - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - PathConstraintData data = new PathConstraintData(ReadString(input)); - data.order = ReadVarint(input, true); - for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) - data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]); - data.target = skeletonData.slots.Items[ReadVarint(input, true)]; - data.positionMode = (PositionMode)Enum.GetValues(typeof(PositionMode)).GetValue(ReadVarint(input, true)); - data.spacingMode = (SpacingMode)Enum.GetValues(typeof(SpacingMode)).GetValue(ReadVarint(input, true)); - data.rotateMode = (RotateMode)Enum.GetValues(typeof(RotateMode)).GetValue(ReadVarint(input, true)); - 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.rotateMix = ReadFloat(input); - data.translateMix = ReadFloat(input); - skeletonData.pathConstraints.Add(data); - } - - // Default skin. - Skin defaultSkin = ReadSkin(input, skeletonData, "default", nonessential); - if (defaultSkin != null) { - skeletonData.defaultSkin = defaultSkin; - skeletonData.skins.Add(defaultSkin); - } - - // Skins. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) - skeletonData.skins.Add(ReadSkin(input, skeletonData, ReadString(input), nonessential)); - - // Linked meshes. - for (int i = 0, n = linkedMeshes.Count; i < n; i++) { - SkeletonJson.LinkedMesh linkedMesh = linkedMeshes[i]; - Skin skin = linkedMesh.skin == null ? skeletonData.DefaultSkin : skeletonData.FindSkin(linkedMesh.skin); - if (skin == null) throw new Exception("Skin not found: " + linkedMesh.skin); - Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); - if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); - linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; - linkedMesh.mesh.UpdateUVs(); - } - linkedMeshes.Clear(); - - // Events. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - EventData data = new EventData(ReadString(input)); - data.Int = ReadVarint(input, false); - data.Float = ReadFloat(input); - data.String = ReadString(input); - skeletonData.events.Add(data); - } - - // Animations. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) - ReadAnimation(ReadString(input), input, skeletonData); - - skeletonData.bones.TrimExcess(); - skeletonData.slots.TrimExcess(); - skeletonData.skins.TrimExcess(); - skeletonData.events.TrimExcess(); - skeletonData.animations.TrimExcess(); - skeletonData.ikConstraints.TrimExcess(); - skeletonData.pathConstraints.TrimExcess(); - return skeletonData; - } - - - /// May be null. - private Skin ReadSkin (Stream input, SkeletonData skeletonData, String skinName, bool nonessential) { - int slotCount = ReadVarint(input, true); - if (slotCount == 0) return null; - Skin skin = new Skin(skinName); - 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 = ReadString(input); - Attachment attachment = ReadAttachment(input, skeletonData, skin, slotIndex, name, nonessential); - if (attachment != null) skin.AddAttachment(slotIndex, name, attachment); - } - } - return skin; - } - - private Attachment ReadAttachment (Stream input, SkeletonData skeletonData, Skin skin, int slotIndex, String attachmentName, bool nonessential) { - float scale = Scale; - - String name = ReadString(input); - if (name == null) name = attachmentName; - - AttachmentType type = (AttachmentType)input.ReadByte(); - switch (type) { - case AttachmentType.Region: { - String path = ReadString(input); - float rotation = ReadFloat(input); - float x = ReadFloat(input); - float y = ReadFloat(input); - float scaleX = ReadFloat(input); - float scaleY = ReadFloat(input); - float width = ReadFloat(input); - float height = ReadFloat(input); - int color = ReadInt(input); - - if (path == null) path = name; - RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); - if (region == null) return null; - region.Path = path; - region.x = x * scale; - region.y = y * scale; - region.scaleX = scaleX; - region.scaleY = scaleY; - region.rotation = rotation; - region.width = width * scale; - region.height = height * scale; - region.r = ((color & 0xff000000) >> 24) / 255f; - region.g = ((color & 0x00ff0000) >> 16) / 255f; - region.b = ((color & 0x0000ff00) >> 8) / 255f; - region.a = ((color & 0x000000ff)) / 255f; - region.UpdateOffset(); - return region; - } - case AttachmentType.Boundingbox: { - int vertexCount = ReadVarint(input, true); - Vertices vertices = ReadVertices(input, vertexCount); - if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0; // Avoid unused local warning. - - BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); - if (box == null) return null; - box.worldVerticesLength = vertexCount << 1; - box.vertices = vertices.vertices; - box.bones = vertices.bones; - return box; - } - case AttachmentType.Mesh: { - String path = ReadString(input); - int color = ReadInt(input); - int vertexCount = ReadVarint(input, true); - float[] uvs = ReadFloatArray(input, vertexCount << 1, 1); - int[] triangles = ReadShortArray(input); - Vertices vertices = ReadVertices(input, vertexCount); - int hullLength = ReadVarint(input, true); - int[] edges = null; - float width = 0, height = 0; - if (nonessential) { - edges = ReadShortArray(input); - width = ReadFloat(input); - height = ReadFloat(input); - } - - if (path == null) path = name; - MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); - if (mesh == null) return null; - mesh.Path = path; - mesh.r = ((color & 0xff000000) >> 24) / 255f; - mesh.g = ((color & 0x00ff0000) >> 16) / 255f; - mesh.b = ((color & 0x0000ff00) >> 8) / 255f; - mesh.a = ((color & 0x000000ff)) / 255f; - mesh.bones = vertices.bones; - mesh.vertices = vertices.vertices; - mesh.WorldVerticesLength = vertexCount << 1; - mesh.triangles = triangles; - mesh.regionUVs = uvs; - mesh.UpdateUVs(); - mesh.HullLength = hullLength << 1; - if (nonessential) { - mesh.Edges = edges; - mesh.Width = width * scale; - mesh.Height = height * scale; - } - return mesh; - } - case AttachmentType.Linkedmesh: { - String path = ReadString(input); - int color = ReadInt(input); - String skinName = ReadString(input); - String parent = ReadString(input); - bool inheritDeform = ReadBoolean(input); - float width = 0, height = 0; - if (nonessential) { - width = ReadFloat(input); - height = ReadFloat(input); - } - - if (path == null) path = name; - MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); - if (mesh == null) return null; - mesh.Path = path; - mesh.r = ((color & 0xff000000) >> 24) / 255f; - mesh.g = ((color & 0x00ff0000) >> 16) / 255f; - mesh.b = ((color & 0x0000ff00) >> 8) / 255f; - mesh.a = ((color & 0x000000ff)) / 255f; - mesh.inheritDeform = inheritDeform; - if (nonessential) { - mesh.Width = width * scale; - mesh.Height = height * scale; - } - linkedMeshes.Add(new SkeletonJson.LinkedMesh(mesh, skinName, slotIndex, parent)); - return mesh; - } - case AttachmentType.Path: { - bool closed = ReadBoolean(input); - bool constantSpeed = ReadBoolean(input); - int vertexCount = ReadVarint(input, true); - Vertices vertices = ReadVertices(input, vertexCount); - float[] lengths = new float[vertexCount / 3]; - for (int i = 0, n = lengths.Length; i < n; i++) - lengths[i] = ReadFloat(input) * scale; - if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0; - - PathAttachment path = attachmentLoader.NewPathAttachment(skin, name); - if (path == null) return null; - path.closed = closed; - path.constantSpeed = constantSpeed; - path.worldVerticesLength = vertexCount << 1; - path.vertices = vertices.vertices; - path.bones = vertices.bones; - path.lengths = lengths; - return path; - } - case AttachmentType.Point: { - float rotation = ReadFloat(input); - float x = ReadFloat(input); - float y = ReadFloat(input); - if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0; - - PointAttachment point = attachmentLoader.NewPointAttachment(skin, name); - if (point == null) return null; - point.x = x * scale; - point.y = y * scale; - point.rotation = rotation; - //if (nonessential) point.color = color; - return point; - } - case AttachmentType.Clipping: { - int endSlotIndex = ReadVarint(input, true); - int vertexCount = ReadVarint(input, true); - Vertices vertices = ReadVertices(input, vertexCount); - if (nonessential) ReadInt(input); - - ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name); - if (clip == null) return null; - clip.EndSlot = skeletonData.slots.Items[endSlotIndex]; - clip.worldVerticesLength = vertexCount << 1; - clip.vertices = vertices.vertices; - clip.bones = vertices.bones; - return clip; - } - } - return null; - } - - private Vertices ReadVertices (Stream input, int vertexCount) { - float scale = Scale; - int verticesLength = vertexCount << 1; - Vertices vertices = new Vertices(); - if(!ReadBoolean(input)) { - vertices.vertices = ReadFloatArray(input, verticesLength, scale); - return vertices; - } - var weights = new ExposedList(verticesLength * 3 * 3); - var bonesArray = new ExposedList(verticesLength * 3); - for (int i = 0; i < vertexCount; i++) { - int boneCount = ReadVarint(input, true); - bonesArray.Add(boneCount); - for (int ii = 0; ii < boneCount; ii++) { - bonesArray.Add(ReadVarint(input, true)); - weights.Add(ReadFloat(input) * scale); - weights.Add(ReadFloat(input) * scale); - weights.Add(ReadFloat(input)); - } - } - - vertices.vertices = weights.ToArray(); - vertices.bones = bonesArray.ToArray(); - return vertices; - } - - private float[] ReadFloatArray (Stream input, int n, float scale) { - float[] array = new float[n]; - if (scale == 1) { - for (int i = 0; i < n; i++) - array[i] = ReadFloat(input); - } else { - for (int i = 0; i < n; i++) - array[i] = ReadFloat(input) * scale; - } - return array; - } - - private int[] ReadShortArray (Stream input) { - int n = ReadVarint(input, true); - int[] array = new int[n]; - for (int i = 0; i < n; i++) - array[i] = (input.ReadByte() << 8) | input.ReadByte(); - return array; - } - - private void ReadAnimation (String name, Stream input, SkeletonData skeletonData) { - var timelines = new ExposedList(); - float scale = Scale; - float duration = 0; - - // 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++) { - int timelineType = input.ReadByte(); - int frameCount = ReadVarint(input, true); - switch (timelineType) { - case SLOT_ATTACHMENT: { - AttachmentTimeline timeline = new AttachmentTimeline(frameCount); - timeline.slotIndex = slotIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) - timeline.SetFrame(frameIndex, ReadFloat(input), ReadString(input)); - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[frameCount - 1]); - break; - } - case SLOT_COLOR: { - ColorTimeline timeline = new ColorTimeline(frameCount); - timeline.slotIndex = slotIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - float time = ReadFloat(input); - int color = ReadInt(input); - float r = ((color & 0xff000000) >> 24) / 255f; - float g = ((color & 0x00ff0000) >> 16) / 255f; - float b = ((color & 0x0000ff00) >> 8) / 255f; - float a = ((color & 0x000000ff)) / 255f; - timeline.SetFrame(frameIndex, time, r, g, b, a); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * ColorTimeline.ENTRIES]); - break; - } - case SLOT_TWO_COLOR: { - TwoColorTimeline timeline = new TwoColorTimeline(frameCount); - timeline.slotIndex = slotIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - float time = ReadFloat(input); - int color = ReadInt(input); - float r = ((color & 0xff000000) >> 24) / 255f; - float g = ((color & 0x00ff0000) >> 16) / 255f; - float b = ((color & 0x0000ff00) >> 8) / 255f; - float a = ((color & 0x000000ff)) / 255f; - int color2 = ReadInt(input); // 0x00rrggbb - float r2 = ((color2 & 0x00ff0000) >> 16) / 255f; - float g2 = ((color2 & 0x0000ff00) >> 8) / 255f; - float b2 = ((color2 & 0x000000ff)) / 255f; - - timeline.SetFrame(frameIndex, time, r, g, b, a, r2, g2, b2); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TwoColorTimeline.ENTRIES]); - break; - } - } - } - } - - // 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++) { - int timelineType = input.ReadByte(); - int frameCount = ReadVarint(input, true); - switch (timelineType) { - case BONE_ROTATE: { - RotateTimeline timeline = new RotateTimeline(frameCount); - timeline.boneIndex = boneIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input)); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(frameCount - 1) * RotateTimeline.ENTRIES]); - break; - } - case BONE_TRANSLATE: - case BONE_SCALE: - case BONE_SHEAR: { - TranslateTimeline timeline; - float timelineScale = 1; - if (timelineType == BONE_SCALE) - timeline = new ScaleTimeline(frameCount); - else if (timelineType == BONE_SHEAR) - timeline = new ShearTimeline(frameCount); - else { - timeline = new TranslateTimeline(frameCount); - timelineScale = scale; - } - timeline.boneIndex = boneIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input) * timelineScale, ReadFloat(input) - * timelineScale); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TranslateTimeline.ENTRIES]); - break; - } - } - } - } - - // IK timelines. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - int index = ReadVarint(input, true); - int frameCount = ReadVarint(input, true); - IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount); - timeline.ikConstraintIndex = index; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadSByte(input)); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(frameCount - 1) * IkConstraintTimeline.ENTRIES]); - } - - // Transform constraint timelines. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - int index = ReadVarint(input, true); - int frameCount = ReadVarint(input, true); - TransformConstraintTimeline timeline = new TransformConstraintTimeline(frameCount); - timeline.transformConstraintIndex = index; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input)); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TransformConstraintTimeline.ENTRIES]); - } - - // Path constraint timelines. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - int index = ReadVarint(input, true); - PathConstraintData data = skeletonData.pathConstraints.Items[index]; - for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) { - int timelineType = ReadSByte(input); - int frameCount = ReadVarint(input, true); - switch(timelineType) { - case PATH_POSITION: - case PATH_SPACING: { - PathConstraintPositionTimeline timeline; - float timelineScale = 1; - if (timelineType == PATH_SPACING) { - timeline = new PathConstraintSpacingTimeline(frameCount); - if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale; - } else { - timeline = new PathConstraintPositionTimeline(frameCount); - if (data.positionMode == PositionMode.Fixed) timelineScale = scale; - } - timeline.pathConstraintIndex = index; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input) * timelineScale); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintPositionTimeline.ENTRIES]); - break; - } - case PATH_MIX: { - PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(frameCount); - timeline.pathConstraintIndex = index; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input)); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintMixTimeline.ENTRIES]); - break; - } - } - } - } - - // Deform timelines. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - Skin skin = skeletonData.skins.Items[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++) { - VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, ReadString(input)); - bool weighted = attachment.bones != null; - float[] vertices = attachment.vertices; - int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length; - - int frameCount = ReadVarint(input, true); - DeformTimeline timeline = new DeformTimeline(frameCount); - timeline.slotIndex = slotIndex; - timeline.attachment = attachment; - - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - float time = ReadFloat(input); - float[] deform; - int end = ReadVarint(input, true); - if (end == 0) - deform = weighted ? new float[deformLength] : vertices; - else { - deform = new float[deformLength]; - int start = ReadVarint(input, true); - end += start; - if (scale == 1) { - for (int v = start; v < end; v++) - deform[v] = ReadFloat(input); - } else { - for (int v = start; v < end; v++) - deform[v] = ReadFloat(input) * scale; - } - if (!weighted) { - for (int v = 0, vn = deform.Length; v < vn; v++) - deform[v] += vertices[v]; - } - } - - timeline.SetFrame(frameIndex, time, deform); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[frameCount - 1]); - } - } - } - - // Draw order timeline. - int drawOrderCount = ReadVarint(input, true); - if (drawOrderCount > 0) { - DrawOrderTimeline timeline = new DrawOrderTimeline(drawOrderCount); - int slotCount = skeletonData.slots.Count; - for (int i = 0; i < drawOrderCount; i++) { - float time = ReadFloat(input); - int offsetCount = ReadVarint(input, true); - int[] drawOrder = new int[slotCount]; - for (int ii = slotCount - 1; ii >= 0; ii--) - drawOrder[ii] = -1; - int[] unchanged = new int[slotCount - offsetCount]; - int originalIndex = 0, unchangedIndex = 0; - for (int ii = 0; ii < offsetCount; ii++) { - int slotIndex = ReadVarint(input, true); - // Collect unchanged items. - while (originalIndex != slotIndex) - unchanged[unchangedIndex++] = originalIndex++; - // Set changed items. - drawOrder[originalIndex + ReadVarint(input, true)] = originalIndex++; - } - // Collect remaining unchanged items. - while (originalIndex < slotCount) - unchanged[unchangedIndex++] = originalIndex++; - // Fill in unchanged items. - for (int ii = slotCount - 1; ii >= 0; ii--) - if (drawOrder[ii] == -1) drawOrder[ii] = unchanged[--unchangedIndex]; - timeline.SetFrame(i, time, drawOrder); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[drawOrderCount - 1]); - } - - // Event timeline. - int eventCount = ReadVarint(input, true); - if (eventCount > 0) { - EventTimeline timeline = new EventTimeline(eventCount); - for (int i = 0; i < eventCount; i++) { - float time = ReadFloat(input); - EventData eventData = skeletonData.events.Items[ReadVarint(input, true)]; - Event e = new Event(time, eventData); - e.Int = ReadVarint(input, false); - e.Float = ReadFloat(input); - e.String = ReadBoolean(input) ? ReadString(input) : eventData.String; - timeline.SetFrame(i, e); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[eventCount - 1]); - } - - timelines.TrimExcess(); - skeletonData.animations.Add(new Animation(name, timelines, duration)); - } - - private void ReadCurve (Stream input, int frameIndex, CurveTimeline timeline) { - switch (input.ReadByte()) { - case CURVE_STEPPED: - timeline.SetStepped(frameIndex); - break; - case CURVE_BEZIER: - timeline.SetCurve(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input)); - break; - } - } - - private static sbyte ReadSByte (Stream input) { - int value = input.ReadByte(); - if (value == -1) throw new EndOfStreamException(); - return (sbyte)value; - } - - private static bool ReadBoolean (Stream input) { - return input.ReadByte() != 0; - } - - private float ReadFloat (Stream input) { - buffer[3] = (byte)input.ReadByte(); - buffer[2] = (byte)input.ReadByte(); - buffer[1] = (byte)input.ReadByte(); - buffer[0] = (byte)input.ReadByte(); - return BitConverter.ToSingle(buffer, 0); - } - - private static int ReadInt (Stream input) { - return (input.ReadByte() << 24) + (input.ReadByte() << 16) + (input.ReadByte() << 8) + input.ReadByte(); - } - - private static int ReadVarint (Stream input, bool optimizePositive) { - int b = input.ReadByte(); - int result = b & 0x7F; - if ((b & 0x80) != 0) { - b = input.ReadByte(); - result |= (b & 0x7F) << 7; - if ((b & 0x80) != 0) { - b = input.ReadByte(); - result |= (b & 0x7F) << 14; - if ((b & 0x80) != 0) { - b = input.ReadByte(); - result |= (b & 0x7F) << 21; - if ((b & 0x80) != 0) result |= (input.ReadByte() & 0x7F) << 28; - } - } - } - return optimizePositive ? result : ((result >> 1) ^ -(result & 1)); - } - - private string ReadString (Stream input) { - int byteCount = ReadVarint(input, true); - switch (byteCount) { - case 0: - return null; - case 1: - return ""; - } - byteCount--; - byte[] buffer = this.buffer; - if (buffer.Length < byteCount) buffer = new byte[byteCount]; - ReadFully(input, buffer, 0, byteCount); - return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount); - } - - private static void ReadFully (Stream input, byte[] buffer, int offset, int length) { - while (length > 0) { - int count = input.Read(buffer, offset, length); - if (count <= 0) throw new EndOfStreamException(); - offset += count; - length -= count; - } - } - - internal class Vertices { - public int[] bones; - public float[] vertices; - } - } +#else + using (var input = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) + { +#endif + SkeletonData skeletonData = ReadSkeletonData(input); + skeletonData.name = Path.GetFileNameWithoutExtension(path); + return skeletonData; + } + } +#endif // WINDOWS_STOREAPP + + public static readonly TransformMode[] TransformModeValues = { + TransformMode.Normal, + TransformMode.OnlyTranslation, + TransformMode.NoRotationOrReflection, + TransformMode.NoScale, + TransformMode.NoScaleOrReflection + }; + + /// Returns the version string of binary skeleton data. + public static string GetVersionString(Stream input) + { + if (input == null) throw new ArgumentNullException("input"); + + try + { + // Hash. + int byteCount = ReadVarint(input, true); + if (byteCount > 1) input.Position += byteCount - 1; + + // Version. + byteCount = ReadVarint(input, true); + if (byteCount > 1) + { + byteCount--; + var buffer = new byte[byteCount]; + ReadFully(input, buffer, 0, byteCount); + return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount); + } + + throw new ArgumentException("Stream does not contain a valid binary Skeleton Data.", "input"); + } + catch (Exception e) + { + throw new ArgumentException("Stream does not contain a valid binary Skeleton Data.\n" + e, "input"); + } + } + + public SkeletonData ReadSkeletonData(Stream input) + { + if (input == null) throw new ArgumentNullException("input"); + float scale = Scale; + + var skeletonData = new SkeletonData(); + skeletonData.hash = ReadString(input); + if (skeletonData.hash.Length == 0) skeletonData.hash = null; + skeletonData.version = ReadString(input); + if (skeletonData.version.Length == 0) skeletonData.version = null; + skeletonData.width = ReadFloat(input); + skeletonData.height = ReadFloat(input); + + bool nonessential = ReadBoolean(input); + + if (nonessential) + { + skeletonData.fps = ReadFloat(input); + skeletonData.imagesPath = ReadString(input); + if (skeletonData.imagesPath.Length == 0) skeletonData.imagesPath = null; + } + + // Bones. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + String name = ReadString(input); + BoneData parent = i == 0 ? null : skeletonData.bones.Items[ReadVarint(input, true)]; + BoneData data = new BoneData(i, name, 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.transformMode = TransformModeValues[ReadVarint(input, true)]; + if (nonessential) ReadInt(input); // Skip bone color. + skeletonData.bones.Add(data); + } + + // Slots. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + String slotName = ReadString(input); + BoneData boneData = skeletonData.bones.Items[ReadVarint(input, true)]; + SlotData slotData = new SlotData(i, slotName, boneData); + int color = ReadInt(input); + slotData.r = ((color & 0xff000000) >> 24) / 255f; + slotData.g = ((color & 0x00ff0000) >> 16) / 255f; + slotData.b = ((color & 0x0000ff00) >> 8) / 255f; + slotData.a = ((color & 0x000000ff)) / 255f; + + int darkColor = ReadInt(input); // 0x00rrggbb + if (darkColor != -1) + { + slotData.hasSecondColor = true; + slotData.r2 = ((darkColor & 0x00ff0000) >> 16) / 255f; + slotData.g2 = ((darkColor & 0x0000ff00) >> 8) / 255f; + slotData.b2 = ((darkColor & 0x000000ff)) / 255f; + } + + slotData.attachmentName = ReadString(input); + slotData.blendMode = (BlendMode)ReadVarint(input, true); + skeletonData.slots.Add(slotData); + } + + // IK constraints. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + IkConstraintData data = new IkConstraintData(ReadString(input)); + data.order = ReadVarint(input, true); + for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) + data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]); + data.target = skeletonData.bones.Items[ReadVarint(input, true)]; + data.mix = ReadFloat(input); + data.bendDirection = ReadSByte(input); + skeletonData.ikConstraints.Add(data); + } + + // Transform constraints. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + TransformConstraintData data = new TransformConstraintData(ReadString(input)); + data.order = ReadVarint(input, true); + for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) + data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]); + data.target = skeletonData.bones.Items[ReadVarint(input, true)]; + data.local = ReadBoolean(input); + data.relative = ReadBoolean(input); + data.offsetRotation = ReadFloat(input); + data.offsetX = ReadFloat(input) * scale; + data.offsetY = ReadFloat(input) * scale; + data.offsetScaleX = ReadFloat(input); + data.offsetScaleY = ReadFloat(input); + data.offsetShearY = ReadFloat(input); + data.rotateMix = ReadFloat(input); + data.translateMix = ReadFloat(input); + data.scaleMix = ReadFloat(input); + data.shearMix = ReadFloat(input); + skeletonData.transformConstraints.Add(data); + } + + // Path constraints + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + PathConstraintData data = new PathConstraintData(ReadString(input)); + data.order = ReadVarint(input, true); + for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) + data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]); + data.target = skeletonData.slots.Items[ReadVarint(input, true)]; + data.positionMode = (PositionMode)Enum.GetValues(typeof(PositionMode)).GetValue(ReadVarint(input, true)); + data.spacingMode = (SpacingMode)Enum.GetValues(typeof(SpacingMode)).GetValue(ReadVarint(input, true)); + data.rotateMode = (RotateMode)Enum.GetValues(typeof(RotateMode)).GetValue(ReadVarint(input, true)); + 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.rotateMix = ReadFloat(input); + data.translateMix = ReadFloat(input); + skeletonData.pathConstraints.Add(data); + } + + // Default skin. + Skin defaultSkin = ReadSkin(input, skeletonData, "default", nonessential); + if (defaultSkin != null) + { + skeletonData.defaultSkin = defaultSkin; + skeletonData.skins.Add(defaultSkin); + } + + // Skins. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + skeletonData.skins.Add(ReadSkin(input, skeletonData, ReadString(input), nonessential)); + + // Linked meshes. + for (int i = 0, n = linkedMeshes.Count; i < n; i++) + { + SkeletonJson.LinkedMesh linkedMesh = linkedMeshes[i]; + Skin skin = linkedMesh.skin == null ? skeletonData.DefaultSkin : skeletonData.FindSkin(linkedMesh.skin); + if (skin == null) throw new Exception("Skin not found: " + linkedMesh.skin); + Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); + if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); + linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; + linkedMesh.mesh.UpdateUVs(); + } + linkedMeshes.Clear(); + + // Events. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + EventData data = new EventData(ReadString(input)); + data.Int = ReadVarint(input, false); + data.Float = ReadFloat(input); + data.String = ReadString(input); + skeletonData.events.Add(data); + } + + // Animations. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + ReadAnimation(ReadString(input), input, skeletonData); + + skeletonData.bones.TrimExcess(); + skeletonData.slots.TrimExcess(); + skeletonData.skins.TrimExcess(); + skeletonData.events.TrimExcess(); + skeletonData.animations.TrimExcess(); + skeletonData.ikConstraints.TrimExcess(); + skeletonData.pathConstraints.TrimExcess(); + return skeletonData; + } + + + /// May be null. + private Skin ReadSkin(Stream input, SkeletonData skeletonData, String skinName, bool nonessential) + { + int slotCount = ReadVarint(input, true); + if (slotCount == 0) return null; + Skin skin = new Skin(skinName); + 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 = ReadString(input); + Attachment attachment = ReadAttachment(input, skeletonData, skin, slotIndex, name, nonessential); + if (attachment != null) skin.AddAttachment(slotIndex, name, attachment); + } + } + return skin; + } + + private Attachment ReadAttachment(Stream input, SkeletonData skeletonData, Skin skin, int slotIndex, String attachmentName, bool nonessential) + { + float scale = Scale; + + String name = ReadString(input); + if (name == null) name = attachmentName; + + AttachmentType type = (AttachmentType)input.ReadByte(); + switch (type) + { + case AttachmentType.Region: + { + String path = ReadString(input); + float rotation = ReadFloat(input); + float x = ReadFloat(input); + float y = ReadFloat(input); + float scaleX = ReadFloat(input); + float scaleY = ReadFloat(input); + float width = ReadFloat(input); + float height = ReadFloat(input); + int color = ReadInt(input); + + if (path == null) path = name; + RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); + if (region == null) return null; + region.Path = path; + region.x = x * scale; + region.y = y * scale; + region.scaleX = scaleX; + region.scaleY = scaleY; + region.rotation = rotation; + region.width = width * scale; + region.height = height * scale; + region.r = ((color & 0xff000000) >> 24) / 255f; + region.g = ((color & 0x00ff0000) >> 16) / 255f; + region.b = ((color & 0x0000ff00) >> 8) / 255f; + region.a = ((color & 0x000000ff)) / 255f; + region.UpdateOffset(); + return region; + } + case AttachmentType.Boundingbox: + { + int vertexCount = ReadVarint(input, true); + Vertices vertices = ReadVertices(input, vertexCount); + if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0; // Avoid unused local warning. + + BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); + if (box == null) return null; + box.worldVerticesLength = vertexCount << 1; + box.vertices = vertices.vertices; + box.bones = vertices.bones; + return box; + } + case AttachmentType.Mesh: + { + String path = ReadString(input); + int color = ReadInt(input); + int vertexCount = ReadVarint(input, true); + float[] uvs = ReadFloatArray(input, vertexCount << 1, 1); + int[] triangles = ReadShortArray(input); + Vertices vertices = ReadVertices(input, vertexCount); + int hullLength = ReadVarint(input, true); + int[] edges = null; + float width = 0, height = 0; + if (nonessential) + { + edges = ReadShortArray(input); + width = ReadFloat(input); + height = ReadFloat(input); + } + + if (path == null) path = name; + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); + if (mesh == null) return null; + mesh.Path = path; + mesh.r = ((color & 0xff000000) >> 24) / 255f; + mesh.g = ((color & 0x00ff0000) >> 16) / 255f; + mesh.b = ((color & 0x0000ff00) >> 8) / 255f; + mesh.a = ((color & 0x000000ff)) / 255f; + mesh.bones = vertices.bones; + mesh.vertices = vertices.vertices; + mesh.WorldVerticesLength = vertexCount << 1; + mesh.triangles = triangles; + mesh.regionUVs = uvs; + mesh.UpdateUVs(); + mesh.HullLength = hullLength << 1; + if (nonessential) + { + mesh.Edges = edges; + mesh.Width = width * scale; + mesh.Height = height * scale; + } + return mesh; + } + case AttachmentType.Linkedmesh: + { + String path = ReadString(input); + int color = ReadInt(input); + String skinName = ReadString(input); + String parent = ReadString(input); + bool inheritDeform = ReadBoolean(input); + float width = 0, height = 0; + if (nonessential) + { + width = ReadFloat(input); + height = ReadFloat(input); + } + + if (path == null) path = name; + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); + if (mesh == null) return null; + mesh.Path = path; + mesh.r = ((color & 0xff000000) >> 24) / 255f; + mesh.g = ((color & 0x00ff0000) >> 16) / 255f; + mesh.b = ((color & 0x0000ff00) >> 8) / 255f; + mesh.a = ((color & 0x000000ff)) / 255f; + mesh.inheritDeform = inheritDeform; + if (nonessential) + { + mesh.Width = width * scale; + mesh.Height = height * scale; + } + linkedMeshes.Add(new SkeletonJson.LinkedMesh(mesh, skinName, slotIndex, parent)); + return mesh; + } + case AttachmentType.Path: + { + bool closed = ReadBoolean(input); + bool constantSpeed = ReadBoolean(input); + int vertexCount = ReadVarint(input, true); + Vertices vertices = ReadVertices(input, vertexCount); + float[] lengths = new float[vertexCount / 3]; + for (int i = 0, n = lengths.Length; i < n; i++) + lengths[i] = ReadFloat(input) * scale; + if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0; + + PathAttachment path = attachmentLoader.NewPathAttachment(skin, name); + if (path == null) return null; + path.closed = closed; + path.constantSpeed = constantSpeed; + path.worldVerticesLength = vertexCount << 1; + path.vertices = vertices.vertices; + path.bones = vertices.bones; + path.lengths = lengths; + return path; + } + case AttachmentType.Point: + { + float rotation = ReadFloat(input); + float x = ReadFloat(input); + float y = ReadFloat(input); + if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0; + + PointAttachment point = attachmentLoader.NewPointAttachment(skin, name); + if (point == null) return null; + point.x = x * scale; + point.y = y * scale; + point.rotation = rotation; + //if (nonessential) point.color = color; + return point; + } + case AttachmentType.Clipping: + { + int endSlotIndex = ReadVarint(input, true); + int vertexCount = ReadVarint(input, true); + Vertices vertices = ReadVertices(input, vertexCount); + if (nonessential) ReadInt(input); + + ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name); + if (clip == null) return null; + clip.EndSlot = skeletonData.slots.Items[endSlotIndex]; + clip.worldVerticesLength = vertexCount << 1; + clip.vertices = vertices.vertices; + clip.bones = vertices.bones; + return clip; + } + } + return null; + } + + private Vertices ReadVertices(Stream input, int vertexCount) + { + float scale = Scale; + int verticesLength = vertexCount << 1; + Vertices vertices = new Vertices(); + if (!ReadBoolean(input)) + { + vertices.vertices = ReadFloatArray(input, verticesLength, scale); + return vertices; + } + var weights = new ExposedList(verticesLength * 3 * 3); + var bonesArray = new ExposedList(verticesLength * 3); + for (int i = 0; i < vertexCount; i++) + { + int boneCount = ReadVarint(input, true); + bonesArray.Add(boneCount); + for (int ii = 0; ii < boneCount; ii++) + { + bonesArray.Add(ReadVarint(input, true)); + weights.Add(ReadFloat(input) * scale); + weights.Add(ReadFloat(input) * scale); + weights.Add(ReadFloat(input)); + } + } + + vertices.vertices = weights.ToArray(); + vertices.bones = bonesArray.ToArray(); + return vertices; + } + + private float[] ReadFloatArray(Stream input, int n, float scale) + { + float[] array = new float[n]; + if (scale == 1) + { + for (int i = 0; i < n; i++) + array[i] = ReadFloat(input); + } + else + { + for (int i = 0; i < n; i++) + array[i] = ReadFloat(input) * scale; + } + return array; + } + + private int[] ReadShortArray(Stream input) + { + int n = ReadVarint(input, true); + int[] array = new int[n]; + for (int i = 0; i < n; i++) + array[i] = (input.ReadByte() << 8) | input.ReadByte(); + return array; + } + + private void ReadAnimation(String name, Stream input, SkeletonData skeletonData) + { + var timelines = new ExposedList(); + float scale = Scale; + float duration = 0; + + // 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++) + { + int timelineType = input.ReadByte(); + int frameCount = ReadVarint(input, true); + switch (timelineType) + { + case SLOT_ATTACHMENT: + { + AttachmentTimeline timeline = new AttachmentTimeline(frameCount); + timeline.slotIndex = slotIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + timeline.SetFrame(frameIndex, ReadFloat(input), ReadString(input)); + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[frameCount - 1]); + break; + } + case SLOT_COLOR: + { + ColorTimeline timeline = new ColorTimeline(frameCount); + timeline.slotIndex = slotIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + float time = ReadFloat(input); + int color = ReadInt(input); + float r = ((color & 0xff000000) >> 24) / 255f; + float g = ((color & 0x00ff0000) >> 16) / 255f; + float b = ((color & 0x0000ff00) >> 8) / 255f; + float a = ((color & 0x000000ff)) / 255f; + timeline.SetFrame(frameIndex, time, r, g, b, a); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * ColorTimeline.ENTRIES]); + break; + } + case SLOT_TWO_COLOR: + { + TwoColorTimeline timeline = new TwoColorTimeline(frameCount); + timeline.slotIndex = slotIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + float time = ReadFloat(input); + int color = ReadInt(input); + float r = ((color & 0xff000000) >> 24) / 255f; + float g = ((color & 0x00ff0000) >> 16) / 255f; + float b = ((color & 0x0000ff00) >> 8) / 255f; + float a = ((color & 0x000000ff)) / 255f; + int color2 = ReadInt(input); // 0x00rrggbb + float r2 = ((color2 & 0x00ff0000) >> 16) / 255f; + float g2 = ((color2 & 0x0000ff00) >> 8) / 255f; + float b2 = ((color2 & 0x000000ff)) / 255f; + + timeline.SetFrame(frameIndex, time, r, g, b, a, r2, g2, b2); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TwoColorTimeline.ENTRIES]); + break; + } + } + } + } + + // 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++) + { + int timelineType = input.ReadByte(); + int frameCount = ReadVarint(input, true); + switch (timelineType) + { + case BONE_ROTATE: + { + RotateTimeline timeline = new RotateTimeline(frameCount); + timeline.boneIndex = boneIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input)); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(frameCount - 1) * RotateTimeline.ENTRIES]); + break; + } + case BONE_TRANSLATE: + case BONE_SCALE: + case BONE_SHEAR: + { + TranslateTimeline timeline; + float timelineScale = 1; + if (timelineType == BONE_SCALE) + timeline = new ScaleTimeline(frameCount); + else if (timelineType == BONE_SHEAR) + timeline = new ShearTimeline(frameCount); + else + { + timeline = new TranslateTimeline(frameCount); + timelineScale = scale; + } + timeline.boneIndex = boneIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input) * timelineScale, ReadFloat(input) + * timelineScale); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TranslateTimeline.ENTRIES]); + break; + } + } + } + } + + // IK timelines. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + int index = ReadVarint(input, true); + int frameCount = ReadVarint(input, true); + IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount); + timeline.ikConstraintIndex = index; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadSByte(input)); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(frameCount - 1) * IkConstraintTimeline.ENTRIES]); + } + + // Transform constraint timelines. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + int index = ReadVarint(input, true); + int frameCount = ReadVarint(input, true); + TransformConstraintTimeline timeline = new TransformConstraintTimeline(frameCount); + timeline.transformConstraintIndex = index; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input)); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TransformConstraintTimeline.ENTRIES]); + } + + // Path constraint timelines. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + int index = ReadVarint(input, true); + PathConstraintData data = skeletonData.pathConstraints.Items[index]; + for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) + { + int timelineType = ReadSByte(input); + int frameCount = ReadVarint(input, true); + switch (timelineType) + { + case PATH_POSITION: + case PATH_SPACING: + { + PathConstraintPositionTimeline timeline; + float timelineScale = 1; + if (timelineType == PATH_SPACING) + { + timeline = new PathConstraintSpacingTimeline(frameCount); + if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale; + } + else + { + timeline = new PathConstraintPositionTimeline(frameCount); + if (data.positionMode == PositionMode.Fixed) timelineScale = scale; + } + timeline.pathConstraintIndex = index; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input) * timelineScale); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintPositionTimeline.ENTRIES]); + break; + } + case PATH_MIX: + { + PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(frameCount); + timeline.pathConstraintIndex = index; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input)); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintMixTimeline.ENTRIES]); + break; + } + } + } + } + + // Deform timelines. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + Skin skin = skeletonData.skins.Items[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++) + { + VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, ReadString(input)); + bool weighted = attachment.bones != null; + float[] vertices = attachment.vertices; + int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length; + + int frameCount = ReadVarint(input, true); + DeformTimeline timeline = new DeformTimeline(frameCount); + timeline.slotIndex = slotIndex; + timeline.attachment = attachment; + + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + float time = ReadFloat(input); + float[] deform; + int end = ReadVarint(input, true); + if (end == 0) + deform = weighted ? new float[deformLength] : vertices; + else + { + deform = new float[deformLength]; + int start = ReadVarint(input, true); + end += start; + if (scale == 1) + { + for (int v = start; v < end; v++) + deform[v] = ReadFloat(input); + } + else + { + for (int v = start; v < end; v++) + deform[v] = ReadFloat(input) * scale; + } + if (!weighted) + { + for (int v = 0, vn = deform.Length; v < vn; v++) + deform[v] += vertices[v]; + } + } + + timeline.SetFrame(frameIndex, time, deform); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[frameCount - 1]); + } + } + } + + // Draw order timeline. + int drawOrderCount = ReadVarint(input, true); + if (drawOrderCount > 0) + { + DrawOrderTimeline timeline = new DrawOrderTimeline(drawOrderCount); + int slotCount = skeletonData.slots.Count; + for (int i = 0; i < drawOrderCount; i++) + { + float time = ReadFloat(input); + int offsetCount = ReadVarint(input, true); + int[] drawOrder = new int[slotCount]; + for (int ii = slotCount - 1; ii >= 0; ii--) + drawOrder[ii] = -1; + int[] unchanged = new int[slotCount - offsetCount]; + int originalIndex = 0, unchangedIndex = 0; + for (int ii = 0; ii < offsetCount; ii++) + { + int slotIndex = ReadVarint(input, true); + // Collect unchanged items. + while (originalIndex != slotIndex) + unchanged[unchangedIndex++] = originalIndex++; + // Set changed items. + drawOrder[originalIndex + ReadVarint(input, true)] = originalIndex++; + } + // Collect remaining unchanged items. + while (originalIndex < slotCount) + unchanged[unchangedIndex++] = originalIndex++; + // Fill in unchanged items. + for (int ii = slotCount - 1; ii >= 0; ii--) + if (drawOrder[ii] == -1) drawOrder[ii] = unchanged[--unchangedIndex]; + timeline.SetFrame(i, time, drawOrder); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[drawOrderCount - 1]); + } + + // Event timeline. + int eventCount = ReadVarint(input, true); + if (eventCount > 0) + { + EventTimeline timeline = new EventTimeline(eventCount); + for (int i = 0; i < eventCount; i++) + { + float time = ReadFloat(input); + EventData eventData = skeletonData.events.Items[ReadVarint(input, true)]; + Event e = new Event(time, eventData); + e.Int = ReadVarint(input, false); + e.Float = ReadFloat(input); + e.String = ReadBoolean(input) ? ReadString(input) : eventData.String; + timeline.SetFrame(i, e); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[eventCount - 1]); + } + + timelines.TrimExcess(); + skeletonData.animations.Add(new Animation(name, timelines, duration)); + } + + private void ReadCurve(Stream input, int frameIndex, CurveTimeline timeline) + { + switch (input.ReadByte()) + { + case CURVE_STEPPED: + timeline.SetStepped(frameIndex); + break; + case CURVE_BEZIER: + timeline.SetCurve(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input)); + break; + } + } + + private static sbyte ReadSByte(Stream input) + { + int value = input.ReadByte(); + if (value == -1) throw new EndOfStreamException(); + return (sbyte)value; + } + + private static bool ReadBoolean(Stream input) + { + return input.ReadByte() != 0; + } + + private float ReadFloat(Stream input) + { + buffer[3] = (byte)input.ReadByte(); + buffer[2] = (byte)input.ReadByte(); + buffer[1] = (byte)input.ReadByte(); + buffer[0] = (byte)input.ReadByte(); + return BitConverter.ToSingle(buffer, 0); + } + + private static int ReadInt(Stream input) + { + return (input.ReadByte() << 24) + (input.ReadByte() << 16) + (input.ReadByte() << 8) + input.ReadByte(); + } + + private static int ReadVarint(Stream input, bool optimizePositive) + { + int b = input.ReadByte(); + int result = b & 0x7F; + if ((b & 0x80) != 0) + { + b = input.ReadByte(); + result |= (b & 0x7F) << 7; + if ((b & 0x80) != 0) + { + b = input.ReadByte(); + result |= (b & 0x7F) << 14; + if ((b & 0x80) != 0) + { + b = input.ReadByte(); + result |= (b & 0x7F) << 21; + if ((b & 0x80) != 0) result |= (input.ReadByte() & 0x7F) << 28; + } + } + } + return optimizePositive ? result : ((result >> 1) ^ -(result & 1)); + } + + private string ReadString(Stream input) + { + int byteCount = ReadVarint(input, true); + switch (byteCount) + { + case 0: + return null; + case 1: + return ""; + } + byteCount--; + byte[] buffer = this.buffer; + if (buffer.Length < byteCount) buffer = new byte[byteCount]; + ReadFully(input, buffer, 0, byteCount); + return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount); + } + + private static void ReadFully(Stream input, byte[] buffer, int offset, int length) + { + while (length > 0) + { + int count = input.Read(buffer, offset, length); + if (count <= 0) throw new EndOfStreamException(); + offset += count; + length -= count; + } + } + + internal class Vertices + { + public int[] bones; + public float[] vertices; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/SkeletonBounds.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/SkeletonBounds.cs index 3675155..5d31b1e 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/SkeletonBounds.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/SkeletonBounds.cs @@ -30,205 +30,232 @@ using System; -namespace Spine3_6_32 { - - /// - /// 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. - /// - public class SkeletonBounds { - private ExposedList polygonPool = new ExposedList(); - private float minX, minY, maxX, maxY; - - public ExposedList BoundingBoxes { get; private set; } - public ExposedList Polygons { get; private set; } - public float MinX { get { return minX; } set { minX = value; } } - public float MinY { get { return minY; } set { minY = value; } } - public float MaxX { get { return maxX; } set { maxX = value; } } - public float MaxY { get { return maxY; } set { maxY = value; } } - public float Width { get { return maxX - minX; } } - public float Height { get { return maxY - minY; } } - - public SkeletonBounds () { - BoundingBoxes = new ExposedList(); - Polygons = new ExposedList(); - } - - /// - /// Clears any previous polygons, finds all visible bounding box attachments, - /// and computes the world vertices for each bounding box's polygon. - /// The skeleton. - /// - /// If true, the axis aligned bounding box containing all the polygons is computed. - /// If false, the SkeletonBounds AABB methods will always return true. - /// - public void Update (Skeleton skeleton, bool updateAabb) { - ExposedList boundingBoxes = BoundingBoxes; - ExposedList polygons = Polygons; - ExposedList slots = skeleton.slots; - int slotCount = slots.Count; - - boundingBoxes.Clear(); - for (int i = 0, n = polygons.Count; i < n; i++) - polygonPool.Add(polygons.Items[i]); - polygons.Clear(); - - for (int i = 0; i < slotCount; i++) { - Slot slot = slots.Items[i]; - BoundingBoxAttachment boundingBox = slot.attachment as BoundingBoxAttachment; - if (boundingBox == null) continue; - boundingBoxes.Add(boundingBox); - - Polygon polygon = null; - int poolCount = polygonPool.Count; - if (poolCount > 0) { - polygon = polygonPool.Items[poolCount - 1]; - polygonPool.RemoveAt(poolCount - 1); - } else - polygon = new Polygon(); - polygons.Add(polygon); - - int count = boundingBox.worldVerticesLength; - polygon.Count = count; - if (polygon.Vertices.Length < count) polygon.Vertices = new float[count]; - boundingBox.ComputeWorldVertices(slot, polygon.Vertices); - } - - if (updateAabb) { - AabbCompute(); - } else { - minX = int.MinValue; - minY = int.MinValue; - maxX = int.MaxValue; - maxY = int.MaxValue; - } - } - - private void AabbCompute () { - float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; - ExposedList polygons = Polygons; - for (int i = 0, n = polygons.Count; i < n; i++) { - Polygon polygon = polygons.Items[i]; - float[] vertices = polygon.Vertices; - for (int ii = 0, nn = polygon.Count; ii < nn; ii += 2) { - float x = vertices[ii]; - float y = vertices[ii + 1]; - minX = Math.Min(minX, x); - minY = Math.Min(minY, y); - maxX = Math.Max(maxX, x); - maxY = Math.Max(maxY, y); - } - } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; - } - - - /// Returns true if the axis aligned bounding box contains the point. - public bool AabbContainsPoint (float x, float y) { - return x >= minX && x <= maxX && y >= minY && y <= maxY; - } - - /// Returns true if the axis aligned bounding box intersects the line segment. - public bool AabbIntersectsSegment (float x1, float y1, float x2, float y2) { - float minX = this.minX; - float minY = this.minY; - float maxX = this.maxX; - float maxY = this.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; - } - - /// Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds. - public bool AabbIntersectsSkeleton (SkeletonBounds bounds) { - return minX < bounds.maxX && maxX > bounds.minX && minY < bounds.maxY && maxY > bounds.minY; - } - - /// Returns true if the polygon contains the point. - public bool ContainsPoint (Polygon polygon, float x, float y) { - float[] 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; - } - - /// 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. - public BoundingBoxAttachment ContainsPoint (float x, float y) { - ExposedList polygons = Polygons; - for (int i = 0, n = polygons.Count; i < n; i++) - if (ContainsPoint(polygons.Items[i], x, y)) return BoundingBoxes.Items[i]; - return null; - } - - /// 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. - public BoundingBoxAttachment IntersectsSegment (float x1, float y1, float x2, float y2) { - ExposedList polygons = Polygons; - for (int i = 0, n = polygons.Count; i < n; i++) - if (IntersectsSegment(polygons.Items[i], x1, y1, x2, y2)) return BoundingBoxes.Items[i]; - return null; - } - - /// Returns true if the polygon contains the line segment. - public bool IntersectsSegment (Polygon polygon, float x1, float y1, float x2, float y2) { - float[] vertices = polygon.Vertices; - int 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 (int 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; - } - - public Polygon GetPolygon (BoundingBoxAttachment attachment) { - int index = BoundingBoxes.IndexOf(attachment); - return index == -1 ? null : Polygons.Items[index]; - } - } - - public class Polygon { - public float[] Vertices { get; set; } - public int Count { get; set; } - - public Polygon () { - Vertices = new float[16]; - } - } +namespace Spine3_6_32 +{ + + /// + /// 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. + /// + public class SkeletonBounds + { + private ExposedList polygonPool = new ExposedList(); + private float minX, minY, maxX, maxY; + + public ExposedList BoundingBoxes { get; private set; } + public ExposedList Polygons { get; private set; } + public float MinX { get { return minX; } set { minX = value; } } + public float MinY { get { return minY; } set { minY = value; } } + public float MaxX { get { return maxX; } set { maxX = value; } } + public float MaxY { get { return maxY; } set { maxY = value; } } + public float Width { get { return maxX - minX; } } + public float Height { get { return maxY - minY; } } + + public SkeletonBounds() + { + BoundingBoxes = new ExposedList(); + Polygons = new ExposedList(); + } + + /// + /// Clears any previous polygons, finds all visible bounding box attachments, + /// and computes the world vertices for each bounding box's polygon. + /// The skeleton. + /// + /// If true, the axis aligned bounding box containing all the polygons is computed. + /// If false, the SkeletonBounds AABB methods will always return true. + /// + public void Update(Skeleton skeleton, bool updateAabb) + { + ExposedList boundingBoxes = BoundingBoxes; + ExposedList polygons = Polygons; + ExposedList slots = skeleton.slots; + int slotCount = slots.Count; + + boundingBoxes.Clear(); + for (int i = 0, n = polygons.Count; i < n; i++) + polygonPool.Add(polygons.Items[i]); + polygons.Clear(); + + for (int i = 0; i < slotCount; i++) + { + Slot slot = slots.Items[i]; + BoundingBoxAttachment boundingBox = slot.attachment as BoundingBoxAttachment; + if (boundingBox == null) continue; + boundingBoxes.Add(boundingBox); + + Polygon polygon = null; + int poolCount = polygonPool.Count; + if (poolCount > 0) + { + polygon = polygonPool.Items[poolCount - 1]; + polygonPool.RemoveAt(poolCount - 1); + } + else + polygon = new Polygon(); + polygons.Add(polygon); + + int count = boundingBox.worldVerticesLength; + polygon.Count = count; + if (polygon.Vertices.Length < count) polygon.Vertices = new float[count]; + boundingBox.ComputeWorldVertices(slot, polygon.Vertices); + } + + if (updateAabb) + { + AabbCompute(); + } + else + { + minX = int.MinValue; + minY = int.MinValue; + maxX = int.MaxValue; + maxY = int.MaxValue; + } + } + + private void AabbCompute() + { + float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; + ExposedList polygons = Polygons; + for (int i = 0, n = polygons.Count; i < n; i++) + { + Polygon polygon = polygons.Items[i]; + float[] vertices = polygon.Vertices; + for (int ii = 0, nn = polygon.Count; ii < nn; ii += 2) + { + float x = vertices[ii]; + float y = vertices[ii + 1]; + minX = Math.Min(minX, x); + minY = Math.Min(minY, y); + maxX = Math.Max(maxX, x); + maxY = Math.Max(maxY, y); + } + } + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + + /// Returns true if the axis aligned bounding box contains the point. + public bool AabbContainsPoint(float x, float y) + { + return x >= minX && x <= maxX && y >= minY && y <= maxY; + } + + /// Returns true if the axis aligned bounding box intersects the line segment. + public bool AabbIntersectsSegment(float x1, float y1, float x2, float y2) + { + float minX = this.minX; + float minY = this.minY; + float maxX = this.maxX; + float maxY = this.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; + } + + /// Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds. + public bool AabbIntersectsSkeleton(SkeletonBounds bounds) + { + return minX < bounds.maxX && maxX > bounds.minX && minY < bounds.maxY && maxY > bounds.minY; + } + + /// Returns true if the polygon contains the point. + public bool ContainsPoint(Polygon polygon, float x, float y) + { + float[] 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; + } + + /// 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. + public BoundingBoxAttachment ContainsPoint(float x, float y) + { + ExposedList polygons = Polygons; + for (int i = 0, n = polygons.Count; i < n; i++) + if (ContainsPoint(polygons.Items[i], x, y)) return BoundingBoxes.Items[i]; + return null; + } + + /// 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. + public BoundingBoxAttachment IntersectsSegment(float x1, float y1, float x2, float y2) + { + ExposedList polygons = Polygons; + for (int i = 0, n = polygons.Count; i < n; i++) + if (IntersectsSegment(polygons.Items[i], x1, y1, x2, y2)) return BoundingBoxes.Items[i]; + return null; + } + + /// Returns true if the polygon contains the line segment. + public bool IntersectsSegment(Polygon polygon, float x1, float y1, float x2, float y2) + { + float[] vertices = polygon.Vertices; + int 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 (int 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; + } + + public Polygon GetPolygon(BoundingBoxAttachment attachment) + { + int index = BoundingBoxes.IndexOf(attachment); + return index == -1 ? null : Polygons.Items[index]; + } + } + + public class Polygon + { + public float[] Vertices { get; set; } + public int Count { get; set; } + + public Polygon() + { + Vertices = new float[16]; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/SkeletonClipping.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/SkeletonClipping.cs index 0d760df..6a91c9f 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/SkeletonClipping.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/SkeletonClipping.cs @@ -28,258 +28,282 @@ * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; +namespace Spine3_6_32 +{ + public class SkeletonClipping + { + internal readonly Triangulator triangulator = new Triangulator(); + internal readonly ExposedList clippingPolygon = new ExposedList(); + internal readonly ExposedList clipOutput = new ExposedList(128); + internal readonly ExposedList clippedVertices = new ExposedList(128); + internal readonly ExposedList clippedTriangles = new ExposedList(128); + internal readonly ExposedList clippedUVs = new ExposedList(128); + internal readonly ExposedList scratch = new ExposedList(); -namespace Spine3_6_32 { - public class SkeletonClipping { - internal readonly Triangulator triangulator = new Triangulator(); - internal readonly ExposedList clippingPolygon = new ExposedList(); - internal readonly ExposedList clipOutput = new ExposedList(128); - internal readonly ExposedList clippedVertices = new ExposedList(128); - internal readonly ExposedList clippedTriangles = new ExposedList(128); - internal readonly ExposedList clippedUVs = new ExposedList(128); - internal readonly ExposedList scratch = new ExposedList(); + internal ClippingAttachment clipAttachment; + internal ExposedList> clippingPolygons; - internal ClippingAttachment clipAttachment; - internal ExposedList> clippingPolygons; + public ExposedList ClippedVertices { get { return clippedVertices; } } + public ExposedList ClippedTriangles { get { return clippedTriangles; } } + public ExposedList ClippedUVs { get { return clippedUVs; } } - public ExposedList ClippedVertices { get { return clippedVertices; } } - public ExposedList ClippedTriangles { get { return clippedTriangles; } } - public ExposedList ClippedUVs { get { return clippedUVs; } } + public bool IsClipping() { return clipAttachment != null; } - public bool IsClipping () { return clipAttachment != null; } + public int ClipStart(Slot slot, ClippingAttachment clip) + { + if (clipAttachment != null) return 0; + clipAttachment = clip; - public int ClipStart (Slot slot, ClippingAttachment clip) { - if (clipAttachment != null) return 0; - clipAttachment = clip; + int n = clip.worldVerticesLength; + float[] vertices = clippingPolygon.Resize(n).Items; + clip.ComputeWorldVertices(slot, 0, n, vertices, 0, 2); + MakeClockwise(clippingPolygon); + clippingPolygons = triangulator.Decompose(clippingPolygon, triangulator.Triangulate(clippingPolygon)); + foreach (var polygon in clippingPolygons) + { + MakeClockwise(polygon); + polygon.Add(polygon.Items[0]); + polygon.Add(polygon.Items[1]); + } + return clippingPolygons.Count; + } - int n = clip.worldVerticesLength; - float[] vertices = clippingPolygon.Resize(n).Items; - clip.ComputeWorldVertices(slot, 0, n, vertices, 0, 2); - MakeClockwise(clippingPolygon); - clippingPolygons = triangulator.Decompose(clippingPolygon, triangulator.Triangulate(clippingPolygon)); - foreach (var polygon in clippingPolygons) { - MakeClockwise(polygon); - polygon.Add(polygon.Items[0]); - polygon.Add(polygon.Items[1]); - } - return clippingPolygons.Count; - } + public void ClipEnd(Slot slot) + { + if (clipAttachment != null && clipAttachment.endSlot == slot.data) ClipEnd(); + } - public void ClipEnd (Slot slot) { - if (clipAttachment != null && clipAttachment.endSlot == slot.data) ClipEnd(); - } + public void ClipEnd() + { + if (clipAttachment == null) return; + clipAttachment = null; + clippingPolygons = null; + clippedVertices.Clear(); + clippedTriangles.Clear(); + clippingPolygon.Clear(); + } - public void ClipEnd () { - if (clipAttachment == null) return; - clipAttachment = null; - clippingPolygons = null; - clippedVertices.Clear(); - clippedTriangles.Clear(); - clippingPolygon.Clear(); - } - - public void ClipTriangles (float[] vertices, int verticesLength, int[] triangles, int trianglesLength, float[] uvs) { - ExposedList clipOutput = this.clipOutput, clippedVertices = this.clippedVertices; - var clippedTriangles = this.clippedTriangles; - var polygons = clippingPolygons.Items; - int polygonsCount = clippingPolygons.Count; + public void ClipTriangles(float[] vertices, int verticesLength, int[] triangles, int trianglesLength, float[] uvs) + { + ExposedList clipOutput = this.clipOutput, clippedVertices = this.clippedVertices; + var clippedTriangles = this.clippedTriangles; + var polygons = clippingPolygons.Items; + int polygonsCount = clippingPolygons.Count; - int index = 0; - clippedVertices.Clear(); - clippedUVs.Clear(); - clippedTriangles.Clear(); - //outer: // libgdx - for (int i = 0; i < trianglesLength; i += 3) { - int vertexOffset = triangles[i] << 1; - float x1 = vertices[vertexOffset], y1 = vertices[vertexOffset + 1]; - float u1 = uvs[vertexOffset], v1 = uvs[vertexOffset + 1]; + int index = 0; + clippedVertices.Clear(); + clippedUVs.Clear(); + clippedTriangles.Clear(); + //outer: // libgdx + for (int i = 0; i < trianglesLength; i += 3) + { + int vertexOffset = triangles[i] << 1; + float x1 = vertices[vertexOffset], y1 = vertices[vertexOffset + 1]; + float u1 = uvs[vertexOffset], v1 = uvs[vertexOffset + 1]; - vertexOffset = triangles[i + 1] << 1; - float x2 = vertices[vertexOffset], y2 = vertices[vertexOffset + 1]; - float u2 = uvs[vertexOffset], v2 = uvs[vertexOffset + 1]; + vertexOffset = triangles[i + 1] << 1; + float x2 = vertices[vertexOffset], y2 = vertices[vertexOffset + 1]; + float u2 = uvs[vertexOffset], v2 = uvs[vertexOffset + 1]; - vertexOffset = triangles[i + 2] << 1; - float x3 = vertices[vertexOffset], y3 = vertices[vertexOffset + 1]; - float u3 = uvs[vertexOffset], v3 = uvs[vertexOffset + 1]; + vertexOffset = triangles[i + 2] << 1; + float x3 = vertices[vertexOffset], y3 = vertices[vertexOffset + 1]; + float u3 = uvs[vertexOffset], v3 = uvs[vertexOffset + 1]; - for (int p = 0; p < polygonsCount; p++) { - int s = clippedVertices.Count; - if (Clip(x1, y1, x2, y2, x3, y3, polygons[p], clipOutput)) { - int clipOutputLength = clipOutput.Count; - 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)); + for (int p = 0; p < polygonsCount; p++) + { + int s = clippedVertices.Count; + if (Clip(x1, y1, x2, y2, x3, y3, polygons[p], clipOutput)) + { + int clipOutputLength = clipOutput.Count; + 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)); - int clipOutputCount = clipOutputLength >> 1; - float[] clipOutputItems = clipOutput.Items; - float[] clippedVerticesItems = clippedVertices.Resize(s + clipOutputCount * 2).Items; - float[] clippedUVsItems = clippedUVs.Resize(s + clipOutputCount * 2).Items; - for (int ii = 0; ii < clipOutputLength; ii += 2) { - float x = clipOutputItems[ii], y = clipOutputItems[ii + 1]; - clippedVerticesItems[s] = x; - clippedVerticesItems[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; - clippedUVsItems[s] = u1 * a + u2 * b + u3 * c; - clippedUVsItems[s + 1] = v1 * a + v2 * b + v3 * c; - s += 2; - } + int clipOutputCount = clipOutputLength >> 1; + float[] clipOutputItems = clipOutput.Items; + float[] clippedVerticesItems = clippedVertices.Resize(s + clipOutputCount * 2).Items; + float[] clippedUVsItems = clippedUVs.Resize(s + clipOutputCount * 2).Items; + for (int ii = 0; ii < clipOutputLength; ii += 2) + { + float x = clipOutputItems[ii], y = clipOutputItems[ii + 1]; + clippedVerticesItems[s] = x; + clippedVerticesItems[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; + clippedUVsItems[s] = u1 * a + u2 * b + u3 * c; + clippedUVsItems[s + 1] = v1 * a + v2 * b + v3 * c; + s += 2; + } - s = clippedTriangles.Count; - int[] clippedTrianglesItems = clippedTriangles.Resize(s + 3 * (clipOutputCount - 2)).Items; - clipOutputCount--; - for (int ii = 1; ii < clipOutputCount; ii++) { - clippedTrianglesItems[s] = index; - clippedTrianglesItems[s + 1] = index + ii; - clippedTrianglesItems[s + 2] = index + ii + 1; - s += 3; - } - index += clipOutputCount + 1; - } - else { - float[] clippedVerticesItems = clippedVertices.Resize(s + 3 * 2).Items; - float[] clippedUVsItems = clippedUVs.Resize(s + 3 * 2).Items; - clippedVerticesItems[s] = x1; - clippedVerticesItems[s + 1] = y1; - clippedVerticesItems[s + 2] = x2; - clippedVerticesItems[s + 3] = y2; - clippedVerticesItems[s + 4] = x3; - clippedVerticesItems[s + 5] = y3; + s = clippedTriangles.Count; + int[] clippedTrianglesItems = clippedTriangles.Resize(s + 3 * (clipOutputCount - 2)).Items; + clipOutputCount--; + for (int ii = 1; ii < clipOutputCount; ii++) + { + clippedTrianglesItems[s] = index; + clippedTrianglesItems[s + 1] = index + ii; + clippedTrianglesItems[s + 2] = index + ii + 1; + s += 3; + } + index += clipOutputCount + 1; + } + else + { + float[] clippedVerticesItems = clippedVertices.Resize(s + 3 * 2).Items; + float[] clippedUVsItems = clippedUVs.Resize(s + 3 * 2).Items; + clippedVerticesItems[s] = x1; + clippedVerticesItems[s + 1] = y1; + clippedVerticesItems[s + 2] = x2; + clippedVerticesItems[s + 3] = y2; + clippedVerticesItems[s + 4] = x3; + clippedVerticesItems[s + 5] = y3; - clippedUVsItems[s] = u1; - clippedUVsItems[s + 1] = v1; - clippedUVsItems[s + 2] = u2; - clippedUVsItems[s + 3] = v2; - clippedUVsItems[s + 4] = u3; - clippedUVsItems[s + 5] = v3; + clippedUVsItems[s] = u1; + clippedUVsItems[s + 1] = v1; + clippedUVsItems[s + 2] = u2; + clippedUVsItems[s + 3] = v2; + clippedUVsItems[s + 4] = u3; + clippedUVsItems[s + 5] = v3; - s = clippedTriangles.Count; - int[] clippedTrianglesItems = clippedTriangles.Resize(s + 3).Items; - clippedTrianglesItems[s] = index; - clippedTrianglesItems[s + 1] = index + 1; - clippedTrianglesItems[s + 2] = index + 2; - index += 3; - break; //continue outer; - } - } - } + s = clippedTriangles.Count; + int[] clippedTrianglesItems = clippedTriangles.Resize(s + 3).Items; + clippedTrianglesItems[s] = index; + clippedTrianglesItems[s + 1] = index + 1; + clippedTrianglesItems[s + 2] = index + 2; + index += 3; + break; //continue outer; + } + } + } - } + } - /** Clips the input triangle against the convex, clockwise clipping area. If the triangle lies entirely within the clipping + /** 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. */ - internal bool Clip (float x1, float y1, float x2, float y2, float x3, float y3, ExposedList clippingArea, ExposedList output) { - var originalOutput = output; - var clipped = false; + internal bool Clip(float x1, float y1, float x2, float y2, float x3, float y3, ExposedList clippingArea, ExposedList output) + { + var originalOutput = output; + var clipped = false; - // Avoid copy at the end. - ExposedList input = null; - if (clippingArea.Count % 4 >= 2) { - input = output; - output = scratch; - } - else - input = scratch; + // Avoid copy at the end. + ExposedList input = null; + if (clippingArea.Count % 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(); + 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(); - float[] clippingVertices = clippingArea.Items; - int clippingVerticesLast = clippingArea.Count - 4; - for (int i = 0; ; i += 2) { - float edgeX = clippingVertices[i], edgeY = clippingVertices[i + 1]; - float edgeX2 = clippingVertices[i + 2], edgeY2 = clippingVertices[i + 3]; - float deltaX = edgeX - edgeX2, deltaY = edgeY - edgeY2; + float[] clippingVertices = clippingArea.Items; + int clippingVerticesLast = clippingArea.Count - 4; + for (int i = 0; ; i += 2) + { + float edgeX = clippingVertices[i], edgeY = clippingVertices[i + 1]; + float edgeX2 = clippingVertices[i + 2], edgeY2 = clippingVertices[i + 3]; + float deltaX = edgeX - edgeX2, deltaY = edgeY - edgeY2; - float[] inputVertices = input.Items; - int inputVerticesLength = input.Count - 2, outputStart = output.Count; - for (int ii = 0; ii < inputVerticesLength; ii += 2) { - float inputX = inputVertices[ii], inputY = inputVertices[ii + 1]; - float inputX2 = inputVertices[ii + 2], inputY2 = inputVertices[ii + 3]; - bool side2 = deltaX * (inputY2 - edgeY2) - deltaY * (inputX2 - edgeX2) > 0; - if (deltaX * (inputY - edgeY2) - deltaY * (inputX - edgeX2) > 0) { - if (side2) { // v1 inside, v2 inside - output.Add(inputX2); - output.Add(inputY2); - continue; - } - // v1 inside, v2 outside - float c0 = inputY2 - inputY, c2 = inputX2 - inputX; - float ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / (c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY)); - output.Add(edgeX + (edgeX2 - edgeX) * ua); - output.Add(edgeY + (edgeY2 - edgeY) * ua); - } - else if (side2) { // v1 outside, v2 inside - float c0 = inputY2 - inputY, c2 = inputX2 - inputX; - float ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / (c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY)); - output.Add(edgeX + (edgeX2 - edgeX) * ua); - output.Add(edgeY + (edgeY2 - edgeY) * ua); - output.Add(inputX2); - output.Add(inputY2); - } - clipped = true; - } + float[] inputVertices = input.Items; + int inputVerticesLength = input.Count - 2, outputStart = output.Count; + for (int ii = 0; ii < inputVerticesLength; ii += 2) + { + float inputX = inputVertices[ii], inputY = inputVertices[ii + 1]; + float inputX2 = inputVertices[ii + 2], inputY2 = inputVertices[ii + 3]; + bool side2 = deltaX * (inputY2 - edgeY2) - deltaY * (inputX2 - edgeX2) > 0; + if (deltaX * (inputY - edgeY2) - deltaY * (inputX - edgeX2) > 0) + { + if (side2) + { // v1 inside, v2 inside + output.Add(inputX2); + output.Add(inputY2); + continue; + } + // v1 inside, v2 outside + float c0 = inputY2 - inputY, c2 = inputX2 - inputX; + float ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / (c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY)); + output.Add(edgeX + (edgeX2 - edgeX) * ua); + output.Add(edgeY + (edgeY2 - edgeY) * ua); + } + else if (side2) + { // v1 outside, v2 inside + float c0 = inputY2 - inputY, c2 = inputX2 - inputX; + float ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / (c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY)); + output.Add(edgeX + (edgeX2 - edgeX) * ua); + output.Add(edgeY + (edgeY2 - edgeY) * ua); + output.Add(inputX2); + output.Add(inputY2); + } + clipped = true; + } - if (outputStart == output.Count) { // All edges outside. - originalOutput.Clear(); - return true; - } + if (outputStart == output.Count) + { // All edges outside. + originalOutput.Clear(); + return true; + } - output.Add(output.Items[0]); - output.Add(output.Items[1]); + output.Add(output.Items[0]); + output.Add(output.Items[1]); - if (i == clippingVerticesLast) break; - var temp = output; - output = input; - output.Clear(); - input = temp; - } + if (i == clippingVerticesLast) break; + var temp = output; + output = input; + output.Clear(); + input = temp; + } - if (originalOutput != output) { - originalOutput.Clear(); - for (int i = 0, n = output.Count - 2; i < n; i++) { - originalOutput.Add(output.Items[i]); - } - } - else - originalOutput.Resize(originalOutput.Count - 2); + if (originalOutput != output) + { + originalOutput.Clear(); + for (int i = 0, n = output.Count - 2; i < n; i++) + { + originalOutput.Add(output.Items[i]); + } + } + else + originalOutput.Resize(originalOutput.Count - 2); - return clipped; - } + return clipped; + } - static void MakeClockwise (ExposedList polygon) { - float[] vertices = polygon.Items; - int verticeslength = polygon.Count; + static void MakeClockwise(ExposedList polygon) + { + float[] vertices = polygon.Items; + int verticeslength = polygon.Count; - float area = vertices[verticeslength - 2] * vertices[1] - vertices[0] * vertices[verticeslength - 1], p1x, p1y, p2x, p2y; - for (int i = 0, n = verticeslength - 3; i < n; i += 2) { - p1x = vertices[i]; - p1y = vertices[i + 1]; - p2x = vertices[i + 2]; - p2y = vertices[i + 3]; - area += p1x * p2y - p2x * p1y; - } - if (area < 0) return; + float area = vertices[verticeslength - 2] * vertices[1] - vertices[0] * vertices[verticeslength - 1], p1x, p1y, p2x, p2y; + for (int i = 0, n = verticeslength - 3; i < n; i += 2) + { + p1x = vertices[i]; + p1y = vertices[i + 1]; + p2x = vertices[i + 2]; + p2y = vertices[i + 3]; + area += p1x * p2y - p2x * p1y; + } + if (area < 0) return; - for (int i = 0, lastX = verticeslength - 2, n = verticeslength >> 1; i < n; i += 2) { - float x = vertices[i], y = vertices[i + 1]; - int other = lastX - i; - vertices[i] = vertices[other]; - vertices[i + 1] = vertices[other + 1]; - vertices[other] = x; - vertices[other + 1] = y; - } - } - } + for (int i = 0, lastX = verticeslength - 2, n = verticeslength >> 1; i < n; i += 2) + { + float x = vertices[i], y = vertices[i + 1]; + int other = lastX - i; + vertices[i] = vertices[other]; + vertices[i + 1] = vertices[other + 1]; + vertices[other] = x; + vertices[other + 1] = y; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/SkeletonData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/SkeletonData.cs index dc10c64..6057339 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/SkeletonData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/SkeletonData.cs @@ -30,195 +30,215 @@ using System; -namespace Spine3_6_32 { - - /// Stores the setup pose and all of the stateless data for a skeleton. - public class SkeletonData { - internal string name; - internal ExposedList bones = new ExposedList(); // Ordered parents first - internal ExposedList slots = new ExposedList(); // Setup pose draw order. - internal ExposedList skins = new ExposedList(); - internal Skin defaultSkin; - internal ExposedList events = new ExposedList(); - internal ExposedList animations = new ExposedList(); - internal ExposedList ikConstraints = new ExposedList(); - internal ExposedList transformConstraints = new ExposedList(); - internal ExposedList pathConstraints = new ExposedList(); - internal float width, height; - internal string version, hash; - - // Nonessential. - internal float fps; - internal string imagesPath; - - public string Name { get { return name; } set { name = value; } } - - /// The skeleton's bones, sorted parent first. The root bone is always the first bone. - public ExposedList Bones { get { return bones; } } - - public ExposedList Slots { get { return slots; } } - - /// All skins, including the default skin. - public ExposedList Skins { get { return skins; } set { skins = value; } } - - /// - /// The skeleton's default skin. - /// By default this skin contains all attachments that were not in a skin in Spine. - /// - /// May be null. - public Skin DefaultSkin { get { return defaultSkin; } set { defaultSkin = value; } } - - public ExposedList Events { get { return events; } set { events = value; } } - public ExposedList Animations { get { return animations; } set { animations = value; } } - public ExposedList IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } } - public ExposedList TransformConstraints { get { return transformConstraints; } set { transformConstraints = value; } } - public ExposedList PathConstraints { get { return pathConstraints; } set { pathConstraints = value; } } - - public float Width { get { return width; } set { width = value; } } - public float Height { get { return height; } set { height = value; } } - /// The Spine version used to export this data, or null. - public string Version { get { return version; } set { version = value; } } - public string Hash { get { return hash; } set { hash = value; } } - public string ImagesPath { get { return imagesPath; } set { imagesPath = value; } } - - /// - /// The dopesheet FPS in Spine. Available only when nonessential data was exported. - public float Fps { get { return fps; } set { fps = value; } } - - // --- Bones. - - /// - /// 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. - /// May be null. - public BoneData FindBone (string boneName) { - if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); - var bones = this.bones; - var bonesItems = bones.Items; - for (int i = 0, n = bones.Count; i < n; i++) { - BoneData bone = bonesItems[i]; - if (bone.name == boneName) return bone; - } - return null; - } - - /// -1 if the bone was not found. - public int FindBoneIndex (string boneName) { - if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); - var bones = this.bones; - var bonesItems = bones.Items; - for (int i = 0, n = bones.Count; i < n; i++) - if (bonesItems[i].name == boneName) return i; - return -1; - } - - // --- Slots. - - /// May be null. - public SlotData FindSlot (string slotName) { - if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); - ExposedList slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) { - SlotData slot = slots.Items[i]; - if (slot.name == slotName) return slot; - } - return null; - } - - /// -1 if the slot was not found. - public int FindSlotIndex (string slotName) { - if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); - ExposedList slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) - if (slots.Items[i].name == slotName) return i; - return -1; - } - - // --- Skins. - - /// May be null. - public Skin FindSkin (string skinName) { - if (skinName == null) throw new ArgumentNullException("skinName", "skinName cannot be null."); - foreach (Skin skin in skins) - if (skin.name == skinName) return skin; - return null; - } - - // --- Events. - - /// May be null. - public EventData FindEvent (string eventDataName) { - if (eventDataName == null) throw new ArgumentNullException("eventDataName", "eventDataName cannot be null."); - foreach (EventData eventData in events) - if (eventData.name == eventDataName) return eventData; - return null; - } - - // --- Animations. - - /// May be null. - public Animation FindAnimation (string animationName) { - if (animationName == null) throw new ArgumentNullException("animationName", "animationName cannot be null."); - ExposedList animations = this.animations; - for (int i = 0, n = animations.Count; i < n; i++) { - Animation animation = animations.Items[i]; - if (animation.name == animationName) return animation; - } - return null; - } - - // --- IK constraints. - - /// May be null. - public IkConstraintData FindIkConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - ExposedList ikConstraints = this.ikConstraints; - for (int i = 0, n = ikConstraints.Count; i < n; i++) { - IkConstraintData ikConstraint = ikConstraints.Items[i]; - if (ikConstraint.name == constraintName) return ikConstraint; - } - return null; - } - - // --- Transform constraints. - - /// May be null. - public TransformConstraintData FindTransformConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - ExposedList transformConstraints = this.transformConstraints; - for (int i = 0, n = transformConstraints.Count; i < n; i++) { - TransformConstraintData transformConstraint = transformConstraints.Items[i]; - if (transformConstraint.name == constraintName) return transformConstraint; - } - return null; - } - - // --- Path constraints. - - /// May be null. - public PathConstraintData FindPathConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - ExposedList pathConstraints = this.pathConstraints; - for (int i = 0, n = pathConstraints.Count; i < n; i++) { - PathConstraintData constraint = pathConstraints.Items[i]; - if (constraint.name.Equals(constraintName)) return constraint; - } - return null; - } - - /// -1 if the path constraint was not found. - public int FindPathConstraintIndex (string pathConstraintName) { - if (pathConstraintName == null) throw new ArgumentNullException("pathConstraintName", "pathConstraintName cannot be null."); - ExposedList pathConstraints = this.pathConstraints; - for (int i = 0, n = pathConstraints.Count; i < n; i++) - if (pathConstraints.Items[i].name.Equals(pathConstraintName)) return i; - return -1; - } - - // --- - - override public string ToString () { - return name ?? base.ToString(); - } - } +namespace Spine3_6_32 +{ + + /// Stores the setup pose and all of the stateless data for a skeleton. + public class SkeletonData + { + internal string name; + internal ExposedList bones = new ExposedList(); // Ordered parents first + internal ExposedList slots = new ExposedList(); // Setup pose draw order. + internal ExposedList skins = new ExposedList(); + internal Skin defaultSkin; + internal ExposedList events = new ExposedList(); + internal ExposedList animations = new ExposedList(); + internal ExposedList ikConstraints = new ExposedList(); + internal ExposedList transformConstraints = new ExposedList(); + internal ExposedList pathConstraints = new ExposedList(); + internal float width, height; + internal string version, hash; + + // Nonessential. + internal float fps; + internal string imagesPath; + + public string Name { get { return name; } set { name = value; } } + + /// The skeleton's bones, sorted parent first. The root bone is always the first bone. + public ExposedList Bones { get { return bones; } } + + public ExposedList Slots { get { return slots; } } + + /// All skins, including the default skin. + public ExposedList Skins { get { return skins; } set { skins = value; } } + + /// + /// The skeleton's default skin. + /// By default this skin contains all attachments that were not in a skin in Spine. + /// + /// May be null. + public Skin DefaultSkin { get { return defaultSkin; } set { defaultSkin = value; } } + + public ExposedList Events { get { return events; } set { events = value; } } + public ExposedList Animations { get { return animations; } set { animations = value; } } + public ExposedList IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } } + public ExposedList TransformConstraints { get { return transformConstraints; } set { transformConstraints = value; } } + public ExposedList PathConstraints { get { return pathConstraints; } set { pathConstraints = value; } } + + public float Width { get { return width; } set { width = value; } } + public float Height { get { return height; } set { height = value; } } + /// The Spine version used to export this data, or null. + public string Version { get { return version; } set { version = value; } } + public string Hash { get { return hash; } set { hash = value; } } + public string ImagesPath { get { return imagesPath; } set { imagesPath = value; } } + + /// + /// The dopesheet FPS in Spine. Available only when nonessential data was exported. + public float Fps { get { return fps; } set { fps = value; } } + + // --- Bones. + + /// + /// 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. + /// May be null. + public BoneData FindBone(string boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); + var bones = this.bones; + var bonesItems = bones.Items; + for (int i = 0, n = bones.Count; i < n; i++) + { + BoneData bone = bonesItems[i]; + if (bone.name == boneName) return bone; + } + return null; + } + + /// -1 if the bone was not found. + public int FindBoneIndex(string boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); + var bones = this.bones; + var bonesItems = bones.Items; + for (int i = 0, n = bones.Count; i < n; i++) + if (bonesItems[i].name == boneName) return i; + return -1; + } + + // --- Slots. + + /// May be null. + public SlotData FindSlot(string slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + ExposedList slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + { + SlotData slot = slots.Items[i]; + if (slot.name == slotName) return slot; + } + return null; + } + + /// -1 if the slot was not found. + public int FindSlotIndex(string slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + ExposedList slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + if (slots.Items[i].name == slotName) return i; + return -1; + } + + // --- Skins. + + /// May be null. + public Skin FindSkin(string skinName) + { + if (skinName == null) throw new ArgumentNullException("skinName", "skinName cannot be null."); + foreach (Skin skin in skins) + if (skin.name == skinName) return skin; + return null; + } + + // --- Events. + + /// May be null. + public EventData FindEvent(string eventDataName) + { + if (eventDataName == null) throw new ArgumentNullException("eventDataName", "eventDataName cannot be null."); + foreach (EventData eventData in events) + if (eventData.name == eventDataName) return eventData; + return null; + } + + // --- Animations. + + /// May be null. + public Animation FindAnimation(string animationName) + { + if (animationName == null) throw new ArgumentNullException("animationName", "animationName cannot be null."); + ExposedList animations = this.animations; + for (int i = 0, n = animations.Count; i < n; i++) + { + Animation animation = animations.Items[i]; + if (animation.name == animationName) return animation; + } + return null; + } + + // --- IK constraints. + + /// May be null. + public IkConstraintData FindIkConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + ExposedList ikConstraints = this.ikConstraints; + for (int i = 0, n = ikConstraints.Count; i < n; i++) + { + IkConstraintData ikConstraint = ikConstraints.Items[i]; + if (ikConstraint.name == constraintName) return ikConstraint; + } + return null; + } + + // --- Transform constraints. + + /// May be null. + public TransformConstraintData FindTransformConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + ExposedList transformConstraints = this.transformConstraints; + for (int i = 0, n = transformConstraints.Count; i < n; i++) + { + TransformConstraintData transformConstraint = transformConstraints.Items[i]; + if (transformConstraint.name == constraintName) return transformConstraint; + } + return null; + } + + // --- Path constraints. + + /// May be null. + public PathConstraintData FindPathConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + ExposedList pathConstraints = this.pathConstraints; + for (int i = 0, n = pathConstraints.Count; i < n; i++) + { + PathConstraintData constraint = pathConstraints.Items[i]; + if (constraint.name.Equals(constraintName)) return constraint; + } + return null; + } + + /// -1 if the path constraint was not found. + public int FindPathConstraintIndex(string pathConstraintName) + { + if (pathConstraintName == null) throw new ArgumentNullException("pathConstraintName", "pathConstraintName cannot be null."); + ExposedList pathConstraints = this.pathConstraints; + for (int i = 0, n = pathConstraints.Count; i < n; i++) + if (pathConstraints.Items[i].name.Equals(pathConstraintName)) return i; + return -1; + } + + // --- + + override public string ToString() + { + return name ?? base.ToString(); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/SkeletonJson.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/SkeletonJson.cs index 2f4613c..9199d13 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/SkeletonJson.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/SkeletonJson.cs @@ -33,32 +33,36 @@ #endif using System; -using System.IO; using System.Collections.Generic; +using System.IO; #if WINDOWS_STOREAPP using System.Threading.Tasks; using Windows.Storage; #endif -namespace Spine3_6_32 { - public class SkeletonJson { - public float Scale { get; set; } +namespace Spine3_6_32 +{ + public class SkeletonJson + { + public float Scale { get; set; } - private AttachmentLoader attachmentLoader; - private List linkedMeshes = new List(); + private AttachmentLoader attachmentLoader; + private List linkedMeshes = new List(); - public SkeletonJson (params Atlas[] atlasArray) - : this(new AtlasAttachmentLoader(atlasArray)) { - } + public SkeletonJson(params Atlas[] atlasArray) + : this(new AtlasAttachmentLoader(atlasArray)) + { + } - public SkeletonJson (AttachmentLoader attachmentLoader) { - if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader", "attachmentLoader cannot be null."); - this.attachmentLoader = attachmentLoader; - Scale = 1; - } + public SkeletonJson(AttachmentLoader attachmentLoader) + { + if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader", "attachmentLoader cannot be null."); + this.attachmentLoader = attachmentLoader; + Scale = 1; + } - #if !IS_UNITY && WINDOWS_STOREAPP +#if !IS_UNITY && WINDOWS_STOREAPP private async Task ReadFile(string path) { var folder = Windows.ApplicationModel.Package.Current.InstalledLocation; var file = await folder.GetFileAsync(path).AsTask().ConfigureAwait(false); @@ -72,788 +76,905 @@ private async Task ReadFile(string path) { public SkeletonData ReadSkeletonData (String path) { return this.ReadFile(path).Result; } - #else - public SkeletonData ReadSkeletonData (String path) { - #if WINDOWS_PHONE +#else + public SkeletonData ReadSkeletonData(String path) + { +#if WINDOWS_PHONE using (var reader = new StreamReader(Microsoft.Xna.Framework.TitleContainer.OpenStream(path))) { - #else - using (var reader = new StreamReader(new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))) { - #endif - SkeletonData skeletonData = ReadSkeletonData(reader); - skeletonData.name = Path.GetFileNameWithoutExtension(path); - return skeletonData; - } - } - #endif - - public SkeletonData ReadSkeletonData (TextReader reader) { - if (reader == null) throw new ArgumentNullException("reader", "reader cannot be null."); - - var scale = this.Scale; - var skeletonData = new SkeletonData(); - - var root = Json.Deserialize(reader) as Dictionary; - if (root == null) throw new Exception("Invalid JSON."); - - // Skeleton. - if (root.ContainsKey("skeleton")) { - var skeletonMap = (Dictionary)root["skeleton"]; - skeletonData.hash = (String)skeletonMap["hash"]; - skeletonData.version = (String)skeletonMap["spine"]; - skeletonData.width = GetFloat(skeletonMap, "width", 0); - skeletonData.height = GetFloat(skeletonMap, "height", 0); - skeletonData.fps = GetFloat(skeletonMap, "fps", 0); - skeletonData.imagesPath = GetString(skeletonMap, "images", null); - } - - // Bones. - foreach (Dictionary boneMap in (List)root["bones"]) { - BoneData parent = null; - if (boneMap.ContainsKey("parent")) { - parent = skeletonData.FindBone((String)boneMap["parent"]); - if (parent == null) - throw new Exception("Parent bone not found: " + boneMap["parent"]); - } - var data = new BoneData(skeletonData.Bones.Count, (String)boneMap["name"], parent); - data.length = GetFloat(boneMap, "length", 0) * scale; - data.x = GetFloat(boneMap, "x", 0) * scale; - data.y = GetFloat(boneMap, "y", 0) * scale; - data.rotation = GetFloat(boneMap, "rotation", 0); - data.scaleX = GetFloat(boneMap, "scaleX", 1); - data.scaleY = GetFloat(boneMap, "scaleY", 1); - data.shearX = GetFloat(boneMap, "shearX", 0); - data.shearY = GetFloat(boneMap, "shearY", 0); - - string tm = GetString(boneMap, "transform", TransformMode.Normal.ToString()); - data.transformMode = (TransformMode)Enum.Parse(typeof(TransformMode), tm, true); - - skeletonData.bones.Add(data); - } - - // Slots. - if (root.ContainsKey("slots")) { - foreach (Dictionary slotMap in (List)root["slots"]) { - var slotName = (String)slotMap["name"]; - var boneName = (String)slotMap["bone"]; - BoneData boneData = skeletonData.FindBone(boneName); - if (boneData == null) throw new Exception("Slot bone not found: " + boneName); - var data = new SlotData(skeletonData.Slots.Count, slotName, boneData); - - if (slotMap.ContainsKey("color")) { - var color = (String)slotMap["color"]; - data.r = ToColor(color, 0); - data.g = ToColor(color, 1); - data.b = ToColor(color, 2); - data.a = ToColor(color, 3); - } - - if (slotMap.ContainsKey("dark")) { - var color2 = (String)slotMap["dark"]; - data.r2 = ToColor(color2, 0, 6); // expectedLength = 6. ie. "RRGGBB" - data.g2 = ToColor(color2, 1, 6); - data.b2 = ToColor(color2, 2, 6); - data.hasSecondColor = true; - } - - data.attachmentName = GetString(slotMap, "attachment", null); - if (slotMap.ContainsKey("blend")) - data.blendMode = (BlendMode)Enum.Parse(typeof(BlendMode), (String)slotMap["blend"], true); - else - data.blendMode = BlendMode.Normal; - skeletonData.slots.Add(data); - } - } - - // IK constraints. - if (root.ContainsKey("ik")) { - foreach (Dictionary constraintMap in (List)root["ik"]) { - IkConstraintData data = new IkConstraintData((String)constraintMap["name"]); - data.order = GetInt(constraintMap, "order", 0); - - foreach (String boneName in (List)constraintMap["bones"]) { - BoneData bone = skeletonData.FindBone(boneName); - if (bone == null) throw new Exception("IK constraint bone not found: " + boneName); - data.bones.Add(bone); - } - - String targetName = (String)constraintMap["target"]; - data.target = skeletonData.FindBone(targetName); - if (data.target == null) throw new Exception("Target bone not found: " + targetName); - - data.bendDirection = GetBoolean(constraintMap, "bendPositive", true) ? 1 : -1; - data.mix = GetFloat(constraintMap, "mix", 1); - - skeletonData.ikConstraints.Add(data); - } - } - - // Transform constraints. - if (root.ContainsKey("transform")) { - foreach (Dictionary constraintMap in (List)root["transform"]) { - TransformConstraintData data = new TransformConstraintData((String)constraintMap["name"]); - data.order = GetInt(constraintMap, "order", 0); - - foreach (String boneName in (List)constraintMap["bones"]) { - BoneData bone = skeletonData.FindBone(boneName); - if (bone == null) throw new Exception("Transform constraint bone not found: " + boneName); - data.bones.Add(bone); - } - - String targetName = (String)constraintMap["target"]; - data.target = skeletonData.FindBone(targetName); - if (data.target == null) throw new Exception("Target bone not found: " + targetName); - - data.local = GetBoolean(constraintMap, "local", false); - data.relative = GetBoolean(constraintMap, "relative", false); - - data.offsetRotation = GetFloat(constraintMap, "rotation", 0); - data.offsetX = GetFloat(constraintMap, "x", 0) * scale; - data.offsetY = GetFloat(constraintMap, "y", 0) * scale; - data.offsetScaleX = GetFloat(constraintMap, "scaleX", 0); - data.offsetScaleY = GetFloat(constraintMap, "scaleY", 0); - data.offsetShearY = GetFloat(constraintMap, "shearY", 0); - - data.rotateMix = GetFloat(constraintMap, "rotateMix", 1); - data.translateMix = GetFloat(constraintMap, "translateMix", 1); - data.scaleMix = GetFloat(constraintMap, "scaleMix", 1); - data.shearMix = GetFloat(constraintMap, "shearMix", 1); - - skeletonData.transformConstraints.Add(data); - } - } - - // Path constraints. - if(root.ContainsKey("path")) { - foreach (Dictionary constraintMap in (List)root["path"]) { - PathConstraintData data = new PathConstraintData((String)constraintMap["name"]); - data.order = GetInt(constraintMap, "order", 0); - - foreach (String boneName in (List)constraintMap["bones"]) { - BoneData bone = skeletonData.FindBone(boneName); - if (bone == null) throw new Exception("Path bone not found: " + boneName); - data.bones.Add(bone); - } - - String targetName = (String)constraintMap["target"]; - data.target = skeletonData.FindSlot(targetName); - if (data.target == null) throw new Exception("Target slot not found: " + targetName); - - data.positionMode = (PositionMode)Enum.Parse(typeof(PositionMode), GetString(constraintMap, "positionMode", "percent"), true); - data.spacingMode = (SpacingMode)Enum.Parse(typeof(SpacingMode), GetString(constraintMap, "spacingMode", "length"), true); - data.rotateMode = (RotateMode)Enum.Parse(typeof(RotateMode), GetString(constraintMap, "rotateMode", "tangent"), true); - data.offsetRotation = GetFloat(constraintMap, "rotation", 0); - data.position = GetFloat(constraintMap, "position", 0); - if (data.positionMode == PositionMode.Fixed) data.position *= scale; - data.spacing = GetFloat(constraintMap, "spacing", 0); - if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale; - data.rotateMix = GetFloat(constraintMap, "rotateMix", 1); - data.translateMix = GetFloat(constraintMap, "translateMix", 1); - - skeletonData.pathConstraints.Add(data); - } - } - - // Skins. - if (root.ContainsKey("skins")) { - foreach (KeyValuePair skinMap in (Dictionary)root["skins"]) { - var skin = new Skin(skinMap.Key); - foreach (KeyValuePair slotEntry in (Dictionary)skinMap.Value) { - int slotIndex = skeletonData.FindSlotIndex(slotEntry.Key); - foreach (KeyValuePair entry in ((Dictionary)slotEntry.Value)) { - try { - Attachment attachment = ReadAttachment((Dictionary)entry.Value, skin, slotIndex, entry.Key, skeletonData); - if (attachment != null) skin.AddAttachment(slotIndex, entry.Key, attachment); - } catch (Exception e) { - throw new Exception("Error reading attachment: " + entry.Key + ", skin: " + skin, e); - } - } - } - skeletonData.skins.Add(skin); - if (skin.name == "default") skeletonData.defaultSkin = skin; - } - } - - // Linked meshes. - for (int i = 0, n = linkedMeshes.Count; i < n; i++) { - LinkedMesh linkedMesh = linkedMeshes[i]; - Skin skin = linkedMesh.skin == null ? skeletonData.defaultSkin : skeletonData.FindSkin(linkedMesh.skin); - if (skin == null) throw new Exception("Slot not found: " + linkedMesh.skin); - Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); - if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); - linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; - linkedMesh.mesh.UpdateUVs(); - } - linkedMeshes.Clear(); - - // Events. - if (root.ContainsKey("events")) { - foreach (KeyValuePair entry in (Dictionary)root["events"]) { - var entryMap = (Dictionary)entry.Value; - var data = new EventData(entry.Key); - data.Int = GetInt(entryMap, "int", 0); - data.Float = GetFloat(entryMap, "float", 0); - data.String = GetString(entryMap, "string", string.Empty); - skeletonData.events.Add(data); - } - } - - // Animations. - if (root.ContainsKey("animations")) { - foreach (KeyValuePair entry in (Dictionary)root["animations"]) { - try { - ReadAnimation((Dictionary)entry.Value, entry.Key, skeletonData); - } catch (Exception e) { - throw new Exception("Error reading animation: " + entry.Key, e); - } - } - } - - skeletonData.bones.TrimExcess(); - skeletonData.slots.TrimExcess(); - skeletonData.skins.TrimExcess(); - skeletonData.events.TrimExcess(); - skeletonData.animations.TrimExcess(); - skeletonData.ikConstraints.TrimExcess(); - return skeletonData; - } - - private Attachment ReadAttachment (Dictionary map, Skin skin, int slotIndex, String name, SkeletonData skeletonData) { - var scale = this.Scale; - name = GetString(map, "name", name); - - var typeName = GetString(map, "type", "region"); - if (typeName == "skinnedmesh") typeName = "weightedmesh"; - if (typeName == "weightedmesh") typeName = "mesh"; - if (typeName == "weightedlinkedmesh") typeName = "linkedmesh"; - var type = (AttachmentType)Enum.Parse(typeof(AttachmentType), typeName, true); - - String path = GetString(map, "path", name); - - switch (type) { - case AttachmentType.Region: - RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); - if (region == null) return null; - region.Path = path; - region.x = GetFloat(map, "x", 0) * scale; - region.y = GetFloat(map, "y", 0) * scale; - region.scaleX = GetFloat(map, "scaleX", 1); - region.scaleY = GetFloat(map, "scaleY", 1); - region.rotation = GetFloat(map, "rotation", 0); - region.width = GetFloat(map, "width", 32) * scale; - region.height = GetFloat(map, "height", 32) * scale; - region.UpdateOffset(); - - if (map.ContainsKey("color")) { - var color = (String)map["color"]; - region.r = ToColor(color, 0); - region.g = ToColor(color, 1); - region.b = ToColor(color, 2); - region.a = ToColor(color, 3); - } - - region.UpdateOffset(); - return region; - case AttachmentType.Boundingbox: - BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); - if (box == null) return null; - ReadVertices(map, box, GetInt(map, "vertexCount", 0) << 1); - return box; - case AttachmentType.Mesh: - case AttachmentType.Linkedmesh: { - MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); - if (mesh == null) return null; - mesh.Path = path; - - if (map.ContainsKey("color")) { - var color = (String)map["color"]; - mesh.r = ToColor(color, 0); - mesh.g = ToColor(color, 1); - mesh.b = ToColor(color, 2); - mesh.a = ToColor(color, 3); - } - - mesh.Width = GetFloat(map, "width", 0) * scale; - mesh.Height = GetFloat(map, "height", 0) * scale; - - String parent = GetString(map, "parent", null); - if (parent != null) { - mesh.InheritDeform = GetBoolean(map, "deform", true); - linkedMeshes.Add(new LinkedMesh(mesh, GetString(map, "skin", null), slotIndex, parent)); - return mesh; - } - - float[] uvs = GetFloatArray(map, "uvs", 1); - ReadVertices(map, mesh, uvs.Length); - mesh.triangles = GetIntArray(map, "triangles"); - mesh.regionUVs = uvs; - mesh.UpdateUVs(); - - if (map.ContainsKey("hull")) mesh.HullLength = GetInt(map, "hull", 0) * 2; - if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges"); - return mesh; - } - case AttachmentType.Path: { - PathAttachment pathAttachment = attachmentLoader.NewPathAttachment(skin, name); - if (pathAttachment == null) return null; - pathAttachment.closed = GetBoolean(map, "closed", false); - pathAttachment.constantSpeed = GetBoolean(map, "constantSpeed", true); - - int vertexCount = GetInt(map, "vertexCount", 0); - ReadVertices(map, pathAttachment, vertexCount << 1); - - // potential BOZO see Java impl - pathAttachment.lengths = GetFloatArray(map, "lengths", scale); - return pathAttachment; - } - case AttachmentType.Point: { - PointAttachment point = attachmentLoader.NewPointAttachment(skin, name); - if (point == null) return null; - point.x = GetFloat(map, "x", 0) * scale; - point.y = GetFloat(map, "y", 0) * scale; - point.rotation = GetFloat(map, "rotation", 0); - - //string color = GetString(map, "color", null); - //if (color != null) point.color = color; - return point; - } - case AttachmentType.Clipping: { - ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name); - if (clip == null) return null; - - SlotData slot = skeletonData.FindSlot(GetString(map, "end", null)); - if (slot == null) throw new Exception("Clipping end slot not found: " + GetString(map, "end", null)); - clip.endSlot = slot; - ReadVertices(map, clip, GetInt(map, "vertexCount", 0) << 1); - return clip; - } - } - return null; - } - - private void ReadVertices (Dictionary map, VertexAttachment attachment, int verticesLength) { - attachment.WorldVerticesLength = verticesLength; - float[] vertices = GetFloatArray(map, "vertices", 1); - float scale = Scale; - if (verticesLength == vertices.Length) { - if (scale != 1) { - for (int i = 0; i < vertices.Length; i++) { - vertices[i] *= scale; - } - } - attachment.vertices = vertices; - return; - } - ExposedList weights = new ExposedList(verticesLength * 3 * 3); - ExposedList bones = new ExposedList(verticesLength * 3); - for (int i = 0, n = vertices.Length; i < n;) { - int boneCount = (int)vertices[i++]; - bones.Add(boneCount); - for (int nn = i + boneCount * 4; i < nn; i += 4) { - bones.Add((int)vertices[i]); - weights.Add(vertices[i + 1] * this.Scale); - weights.Add(vertices[i + 2] * this.Scale); - weights.Add(vertices[i + 3]); - } - } - attachment.bones = bones.ToArray(); - attachment.vertices = weights.ToArray(); - } - - private void ReadAnimation (Dictionary map, String name, SkeletonData skeletonData) { - var scale = this.Scale; - var timelines = new ExposedList(); - float duration = 0; - - // Slot timelines. - if (map.ContainsKey("slots")) { - foreach (KeyValuePair entry in (Dictionary)map["slots"]) { - String slotName = entry.Key; - int slotIndex = skeletonData.FindSlotIndex(slotName); - var timelineMap = (Dictionary)entry.Value; - foreach (KeyValuePair timelineEntry in timelineMap) { - var values = (List)timelineEntry.Value; - var timelineName = (String)timelineEntry.Key; - if (timelineName == "attachment") { - var timeline = new AttachmentTimeline(values.Count); - timeline.slotIndex = slotIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - timeline.SetFrame(frameIndex++, time, (String)valueMap["name"]); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); - - } else if (timelineName == "color") { - var timeline = new ColorTimeline(values.Count); - timeline.slotIndex = slotIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - string c = (string)valueMap["color"]; - timeline.SetFrame(frameIndex, time, ToColor(c, 0), ToColor(c, 1), ToColor(c, 2), ToColor(c, 3)); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * ColorTimeline.ENTRIES]); - - } else if (timelineName == "twoColor") { - var timeline = new TwoColorTimeline(values.Count); - timeline.slotIndex = slotIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - string light = (string)valueMap["light"]; - string dark = (string)valueMap["dark"]; - timeline.SetFrame(frameIndex, time, ToColor(light, 0), ToColor(light, 1), ToColor(light, 2), ToColor(light, 3), - ToColor(dark, 0, 6), ToColor(dark, 1, 6), ToColor(dark, 2, 6)); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TwoColorTimeline.ENTRIES]); - - } else - throw new Exception("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")"); - } - } - } - - // Bone timelines. - if (map.ContainsKey("bones")) { - foreach (KeyValuePair entry in (Dictionary)map["bones"]) { - String boneName = entry.Key; - int boneIndex = skeletonData.FindBoneIndex(boneName); - if (boneIndex == -1) throw new Exception("Bone not found: " + boneName); - var timelineMap = (Dictionary)entry.Value; - foreach (KeyValuePair timelineEntry in timelineMap) { - var values = (List)timelineEntry.Value; - var timelineName = (String)timelineEntry.Key; - if (timelineName == "rotate") { - var timeline = new RotateTimeline(values.Count); - timeline.boneIndex = boneIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - timeline.SetFrame(frameIndex, (float)valueMap["time"], (float)valueMap["angle"]); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * RotateTimeline.ENTRIES]); - - } else if (timelineName == "translate" || timelineName == "scale" || timelineName == "shear") { - TranslateTimeline timeline; - float timelineScale = 1; - if (timelineName == "scale") - timeline = new ScaleTimeline(values.Count); - else if (timelineName == "shear") - timeline = new ShearTimeline(values.Count); - else { - timeline = new TranslateTimeline(values.Count); - timelineScale = scale; - } - timeline.boneIndex = boneIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - float x = GetFloat(valueMap, "x", 0); - float y = GetFloat(valueMap, "y", 0); - timeline.SetFrame(frameIndex, time, x * timelineScale, y * timelineScale); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TranslateTimeline.ENTRIES]); - - } else - throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")"); - } - } - } - - // IK constraint timelines. - if (map.ContainsKey("ik")) { - foreach (KeyValuePair constraintMap in (Dictionary)map["ik"]) { - IkConstraintData constraint = skeletonData.FindIkConstraint(constraintMap.Key); - var values = (List)constraintMap.Value; - var timeline = new IkConstraintTimeline(values.Count); - timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(constraint); - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - float mix = GetFloat(valueMap, "mix", 1); - bool bendPositive = GetBoolean(valueMap, "bendPositive", true); - timeline.SetFrame(frameIndex, time, mix, bendPositive ? 1 : -1); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * IkConstraintTimeline.ENTRIES]); - } - } - - // Transform constraint timelines. - if (map.ContainsKey("transform")) { - foreach (KeyValuePair constraintMap in (Dictionary)map["transform"]) { - TransformConstraintData constraint = skeletonData.FindTransformConstraint(constraintMap.Key); - var values = (List)constraintMap.Value; - var timeline = new TransformConstraintTimeline(values.Count); - timeline.transformConstraintIndex = skeletonData.transformConstraints.IndexOf(constraint); - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - float rotateMix = GetFloat(valueMap, "rotateMix", 1); - float translateMix = GetFloat(valueMap, "translateMix", 1); - float scaleMix = GetFloat(valueMap, "scaleMix", 1); - float shearMix = GetFloat(valueMap, "shearMix", 1); - timeline.SetFrame(frameIndex, time, rotateMix, translateMix, scaleMix, shearMix); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TransformConstraintTimeline.ENTRIES]); - } - } - - // Path constraint timelines. - if (map.ContainsKey("paths")) { - foreach (KeyValuePair constraintMap in (Dictionary)map["paths"]) { - int index = skeletonData.FindPathConstraintIndex(constraintMap.Key); - if (index == -1) throw new Exception("Path constraint not found: " + constraintMap.Key); - PathConstraintData data = skeletonData.pathConstraints.Items[index]; - var timelineMap = (Dictionary)constraintMap.Value; - foreach (KeyValuePair timelineEntry in timelineMap) { - var values = (List)timelineEntry.Value; - var timelineName = (String)timelineEntry.Key; - if (timelineName == "position" || timelineName == "spacing") { - PathConstraintPositionTimeline timeline; - float timelineScale = 1; - if (timelineName == "spacing") { - timeline = new PathConstraintSpacingTimeline(values.Count); - if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale; - } - else { - timeline = new PathConstraintPositionTimeline(values.Count); - if (data.positionMode == PositionMode.Fixed) timelineScale = scale; - } - timeline.pathConstraintIndex = index; - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - timeline.SetFrame(frameIndex, (float)valueMap["time"], GetFloat(valueMap, timelineName, 0) * timelineScale); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintPositionTimeline.ENTRIES]); - } - else if (timelineName == "mix") { - PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(values.Count); - timeline.pathConstraintIndex = index; - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - timeline.SetFrame(frameIndex, (float)valueMap["time"], GetFloat(valueMap, "rotateMix", 1), GetFloat(valueMap, "translateMix", 1)); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintMixTimeline.ENTRIES]); - } - } - } - } - - // Deform timelines. - if (map.ContainsKey("deform")) { - foreach (KeyValuePair deformMap in (Dictionary)map["deform"]) { - Skin skin = skeletonData.FindSkin(deformMap.Key); - foreach (KeyValuePair slotMap in (Dictionary)deformMap.Value) { - int slotIndex = skeletonData.FindSlotIndex(slotMap.Key); - if (slotIndex == -1) throw new Exception("Slot not found: " + slotMap.Key); - foreach (KeyValuePair timelineMap in (Dictionary)slotMap.Value) { - var values = (List)timelineMap.Value; - VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, timelineMap.Key); - if (attachment == null) throw new Exception("Deform attachment not found: " + timelineMap.Key); - bool weighted = attachment.bones != null; - float[] vertices = attachment.vertices; - int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length; - - var timeline = new DeformTimeline(values.Count); - timeline.slotIndex = slotIndex; - timeline.attachment = attachment; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float[] deform; - if (!valueMap.ContainsKey("vertices")) { - deform = weighted ? new float[deformLength] : vertices; - } else { - deform = new float[deformLength]; - int start = GetInt(valueMap, "offset", 0); - float[] verticesValue = GetFloatArray(valueMap, "vertices", 1); - Array.Copy(verticesValue, 0, deform, start, verticesValue.Length); - if (scale != 1) { - for (int i = start, n = i + verticesValue.Length; i < n; i++) - deform[i] *= scale; - } - - if (!weighted) { - for (int i = 0; i < deformLength; i++) - deform[i] += vertices[i]; - } - } - - timeline.SetFrame(frameIndex, (float)valueMap["time"], deform); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); - } - } - } - } - - // Draw order timeline. - if (map.ContainsKey("drawOrder") || map.ContainsKey("draworder")) { - var values = (List)map[map.ContainsKey("drawOrder") ? "drawOrder" : "draworder"]; - var timeline = new DrawOrderTimeline(values.Count); - int slotCount = skeletonData.slots.Count; - int frameIndex = 0; - foreach (Dictionary drawOrderMap in values) { - int[] drawOrder = null; - if (drawOrderMap.ContainsKey("offsets")) { - drawOrder = new int[slotCount]; - for (int i = slotCount - 1; i >= 0; i--) - drawOrder[i] = -1; - var offsets = (List)drawOrderMap["offsets"]; - int[] unchanged = new int[slotCount - offsets.Count]; - int originalIndex = 0, unchangedIndex = 0; - foreach (Dictionary offsetMap in offsets) { - int slotIndex = skeletonData.FindSlotIndex((String)offsetMap["slot"]); - if (slotIndex == -1) throw new Exception("Slot not found: " + offsetMap["slot"]); - // Collect unchanged items. - while (originalIndex != slotIndex) - unchanged[unchangedIndex++] = originalIndex++; - // Set changed items. - int index = originalIndex + (int)(float)offsetMap["offset"]; - drawOrder[index] = originalIndex++; - } - // Collect remaining unchanged items. - while (originalIndex < slotCount) - unchanged[unchangedIndex++] = originalIndex++; - // Fill in unchanged items. - for (int i = slotCount - 1; i >= 0; i--) - if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex]; - } - timeline.SetFrame(frameIndex++, (float)drawOrderMap["time"], drawOrder); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); - } - - // Event timeline. - if (map.ContainsKey("events")) { - var eventsMap = (List)map["events"]; - var timeline = new EventTimeline(eventsMap.Count); - int frameIndex = 0; - foreach (Dictionary eventMap in eventsMap) { - EventData eventData = skeletonData.FindEvent((String)eventMap["name"]); - if (eventData == null) throw new Exception("Event not found: " + eventMap["name"]); - var e = new Event((float)eventMap["time"], eventData); - e.Int = GetInt(eventMap, "int", eventData.Int); - e.Float = GetFloat(eventMap, "float", eventData.Float); - e.String = GetString(eventMap, "string", eventData.String); - timeline.SetFrame(frameIndex++, e); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); - } - - timelines.TrimExcess(); - skeletonData.animations.Add(new Animation(name, timelines, duration)); - } - - static void ReadCurve (Dictionary valueMap, CurveTimeline timeline, int frameIndex) { - if (!valueMap.ContainsKey("curve")) - return; - Object curveObject = valueMap["curve"]; - if (curveObject.Equals("stepped")) - timeline.SetStepped(frameIndex); - else { - var curve = curveObject as List; - if (curve != null) - timeline.SetCurve(frameIndex, (float)curve[0], (float)curve[1], (float)curve[2], (float)curve[3]); - } - } - - internal class LinkedMesh { - internal String parent, skin; - internal int slotIndex; - internal MeshAttachment mesh; - - public LinkedMesh (MeshAttachment mesh, String skin, int slotIndex, String parent) { - this.mesh = mesh; - this.skin = skin; - this.slotIndex = slotIndex; - this.parent = parent; - } - } - - static float[] GetFloatArray(Dictionary map, String name, float scale) { - var list = (List)map[name]; - var values = new float[list.Count]; - if (scale == 1) { - for (int i = 0, n = list.Count; i < n; i++) - values[i] = (float)list[i]; - } else { - for (int i = 0, n = list.Count; i < n; i++) - values[i] = (float)list[i] * scale; - } - return values; - } - - static int[] GetIntArray(Dictionary map, String name) { - var list = (List)map[name]; - var values = new int[list.Count]; - for (int i = 0, n = list.Count; i < n; i++) - values[i] = (int)(float)list[i]; - return values; - } - - static float GetFloat(Dictionary map, String name, float defaultValue) { - if (!map.ContainsKey(name)) - return defaultValue; - return (float)map[name]; - } - - static int GetInt(Dictionary map, String name, int defaultValue) { - if (!map.ContainsKey(name)) - return defaultValue; - return (int)(float)map[name]; - } - - static bool GetBoolean(Dictionary map, String name, bool defaultValue) { - if (!map.ContainsKey(name)) - return defaultValue; - return (bool)map[name]; - } - - static String GetString(Dictionary map, String name, String defaultValue) { - if (!map.ContainsKey(name)) - return defaultValue; - return (String)map[name]; - } +#else + using (var reader = new StreamReader(new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))) + { +#endif + SkeletonData skeletonData = ReadSkeletonData(reader); + skeletonData.name = Path.GetFileNameWithoutExtension(path); + return skeletonData; + } + } +#endif - static float ToColor(String hexString, int colorIndex, int expectedLength = 8) { - if (hexString.Length != expectedLength) - throw new ArgumentException("Color hexidecimal length must be " + expectedLength + ", recieved: " + hexString, "hexString"); - return Convert.ToInt32(hexString.Substring(colorIndex * 2, 2), 16) / (float)255; - } - } + public SkeletonData ReadSkeletonData(TextReader reader) + { + if (reader == null) throw new ArgumentNullException("reader", "reader cannot be null."); + + var scale = this.Scale; + var skeletonData = new SkeletonData(); + + var root = Json.Deserialize(reader) as Dictionary; + if (root == null) throw new Exception("Invalid JSON."); + + // Skeleton. + if (root.ContainsKey("skeleton")) + { + var skeletonMap = (Dictionary)root["skeleton"]; + skeletonData.hash = (String)skeletonMap["hash"]; + skeletonData.version = (String)skeletonMap["spine"]; + skeletonData.width = GetFloat(skeletonMap, "width", 0); + skeletonData.height = GetFloat(skeletonMap, "height", 0); + skeletonData.fps = GetFloat(skeletonMap, "fps", 0); + skeletonData.imagesPath = GetString(skeletonMap, "images", null); + } + + // Bones. + foreach (Dictionary boneMap in (List)root["bones"]) + { + BoneData parent = null; + if (boneMap.ContainsKey("parent")) + { + parent = skeletonData.FindBone((String)boneMap["parent"]); + if (parent == null) + throw new Exception("Parent bone not found: " + boneMap["parent"]); + } + var data = new BoneData(skeletonData.Bones.Count, (String)boneMap["name"], parent); + data.length = GetFloat(boneMap, "length", 0) * scale; + data.x = GetFloat(boneMap, "x", 0) * scale; + data.y = GetFloat(boneMap, "y", 0) * scale; + data.rotation = GetFloat(boneMap, "rotation", 0); + data.scaleX = GetFloat(boneMap, "scaleX", 1); + data.scaleY = GetFloat(boneMap, "scaleY", 1); + data.shearX = GetFloat(boneMap, "shearX", 0); + data.shearY = GetFloat(boneMap, "shearY", 0); + + string tm = GetString(boneMap, "transform", TransformMode.Normal.ToString()); + data.transformMode = (TransformMode)Enum.Parse(typeof(TransformMode), tm, true); + + skeletonData.bones.Add(data); + } + + // Slots. + if (root.ContainsKey("slots")) + { + foreach (Dictionary slotMap in (List)root["slots"]) + { + var slotName = (String)slotMap["name"]; + var boneName = (String)slotMap["bone"]; + BoneData boneData = skeletonData.FindBone(boneName); + if (boneData == null) throw new Exception("Slot bone not found: " + boneName); + var data = new SlotData(skeletonData.Slots.Count, slotName, boneData); + + if (slotMap.ContainsKey("color")) + { + var color = (String)slotMap["color"]; + data.r = ToColor(color, 0); + data.g = ToColor(color, 1); + data.b = ToColor(color, 2); + data.a = ToColor(color, 3); + } + + if (slotMap.ContainsKey("dark")) + { + var color2 = (String)slotMap["dark"]; + data.r2 = ToColor(color2, 0, 6); // expectedLength = 6. ie. "RRGGBB" + data.g2 = ToColor(color2, 1, 6); + data.b2 = ToColor(color2, 2, 6); + data.hasSecondColor = true; + } + + data.attachmentName = GetString(slotMap, "attachment", null); + if (slotMap.ContainsKey("blend")) + data.blendMode = (BlendMode)Enum.Parse(typeof(BlendMode), (String)slotMap["blend"], true); + else + data.blendMode = BlendMode.Normal; + skeletonData.slots.Add(data); + } + } + + // IK constraints. + if (root.ContainsKey("ik")) + { + foreach (Dictionary constraintMap in (List)root["ik"]) + { + IkConstraintData data = new IkConstraintData((String)constraintMap["name"]); + data.order = GetInt(constraintMap, "order", 0); + + foreach (String boneName in (List)constraintMap["bones"]) + { + BoneData bone = skeletonData.FindBone(boneName); + if (bone == null) throw new Exception("IK constraint bone not found: " + boneName); + data.bones.Add(bone); + } + + String targetName = (String)constraintMap["target"]; + data.target = skeletonData.FindBone(targetName); + if (data.target == null) throw new Exception("Target bone not found: " + targetName); + + data.bendDirection = GetBoolean(constraintMap, "bendPositive", true) ? 1 : -1; + data.mix = GetFloat(constraintMap, "mix", 1); + + skeletonData.ikConstraints.Add(data); + } + } + + // Transform constraints. + if (root.ContainsKey("transform")) + { + foreach (Dictionary constraintMap in (List)root["transform"]) + { + TransformConstraintData data = new TransformConstraintData((String)constraintMap["name"]); + data.order = GetInt(constraintMap, "order", 0); + + foreach (String boneName in (List)constraintMap["bones"]) + { + BoneData bone = skeletonData.FindBone(boneName); + if (bone == null) throw new Exception("Transform constraint bone not found: " + boneName); + data.bones.Add(bone); + } + + String targetName = (String)constraintMap["target"]; + data.target = skeletonData.FindBone(targetName); + if (data.target == null) throw new Exception("Target bone not found: " + targetName); + + data.local = GetBoolean(constraintMap, "local", false); + data.relative = GetBoolean(constraintMap, "relative", false); + + data.offsetRotation = GetFloat(constraintMap, "rotation", 0); + data.offsetX = GetFloat(constraintMap, "x", 0) * scale; + data.offsetY = GetFloat(constraintMap, "y", 0) * scale; + data.offsetScaleX = GetFloat(constraintMap, "scaleX", 0); + data.offsetScaleY = GetFloat(constraintMap, "scaleY", 0); + data.offsetShearY = GetFloat(constraintMap, "shearY", 0); + + data.rotateMix = GetFloat(constraintMap, "rotateMix", 1); + data.translateMix = GetFloat(constraintMap, "translateMix", 1); + data.scaleMix = GetFloat(constraintMap, "scaleMix", 1); + data.shearMix = GetFloat(constraintMap, "shearMix", 1); + + skeletonData.transformConstraints.Add(data); + } + } + + // Path constraints. + if (root.ContainsKey("path")) + { + foreach (Dictionary constraintMap in (List)root["path"]) + { + PathConstraintData data = new PathConstraintData((String)constraintMap["name"]); + data.order = GetInt(constraintMap, "order", 0); + + foreach (String boneName in (List)constraintMap["bones"]) + { + BoneData bone = skeletonData.FindBone(boneName); + if (bone == null) throw new Exception("Path bone not found: " + boneName); + data.bones.Add(bone); + } + + String targetName = (String)constraintMap["target"]; + data.target = skeletonData.FindSlot(targetName); + if (data.target == null) throw new Exception("Target slot not found: " + targetName); + + data.positionMode = (PositionMode)Enum.Parse(typeof(PositionMode), GetString(constraintMap, "positionMode", "percent"), true); + data.spacingMode = (SpacingMode)Enum.Parse(typeof(SpacingMode), GetString(constraintMap, "spacingMode", "length"), true); + data.rotateMode = (RotateMode)Enum.Parse(typeof(RotateMode), GetString(constraintMap, "rotateMode", "tangent"), true); + data.offsetRotation = GetFloat(constraintMap, "rotation", 0); + data.position = GetFloat(constraintMap, "position", 0); + if (data.positionMode == PositionMode.Fixed) data.position *= scale; + data.spacing = GetFloat(constraintMap, "spacing", 0); + if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale; + data.rotateMix = GetFloat(constraintMap, "rotateMix", 1); + data.translateMix = GetFloat(constraintMap, "translateMix", 1); + + skeletonData.pathConstraints.Add(data); + } + } + + // Skins. + if (root.ContainsKey("skins")) + { + foreach (KeyValuePair skinMap in (Dictionary)root["skins"]) + { + var skin = new Skin(skinMap.Key); + foreach (KeyValuePair slotEntry in (Dictionary)skinMap.Value) + { + int slotIndex = skeletonData.FindSlotIndex(slotEntry.Key); + foreach (KeyValuePair entry in ((Dictionary)slotEntry.Value)) + { + try + { + Attachment attachment = ReadAttachment((Dictionary)entry.Value, skin, slotIndex, entry.Key, skeletonData); + if (attachment != null) skin.AddAttachment(slotIndex, entry.Key, attachment); + } + catch (Exception e) + { + throw new Exception("Error reading attachment: " + entry.Key + ", skin: " + skin, e); + } + } + } + skeletonData.skins.Add(skin); + if (skin.name == "default") skeletonData.defaultSkin = skin; + } + } + + // Linked meshes. + for (int i = 0, n = linkedMeshes.Count; i < n; i++) + { + LinkedMesh linkedMesh = linkedMeshes[i]; + Skin skin = linkedMesh.skin == null ? skeletonData.defaultSkin : skeletonData.FindSkin(linkedMesh.skin); + if (skin == null) throw new Exception("Slot not found: " + linkedMesh.skin); + Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); + if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); + linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; + linkedMesh.mesh.UpdateUVs(); + } + linkedMeshes.Clear(); + + // Events. + if (root.ContainsKey("events")) + { + foreach (KeyValuePair entry in (Dictionary)root["events"]) + { + var entryMap = (Dictionary)entry.Value; + var data = new EventData(entry.Key); + data.Int = GetInt(entryMap, "int", 0); + data.Float = GetFloat(entryMap, "float", 0); + data.String = GetString(entryMap, "string", string.Empty); + skeletonData.events.Add(data); + } + } + + // Animations. + if (root.ContainsKey("animations")) + { + foreach (KeyValuePair entry in (Dictionary)root["animations"]) + { + try + { + ReadAnimation((Dictionary)entry.Value, entry.Key, skeletonData); + } + catch (Exception e) + { + throw new Exception("Error reading animation: " + entry.Key, e); + } + } + } + + skeletonData.bones.TrimExcess(); + skeletonData.slots.TrimExcess(); + skeletonData.skins.TrimExcess(); + skeletonData.events.TrimExcess(); + skeletonData.animations.TrimExcess(); + skeletonData.ikConstraints.TrimExcess(); + return skeletonData; + } + + private Attachment ReadAttachment(Dictionary map, Skin skin, int slotIndex, String name, SkeletonData skeletonData) + { + var scale = this.Scale; + name = GetString(map, "name", name); + + var typeName = GetString(map, "type", "region"); + if (typeName == "skinnedmesh") typeName = "weightedmesh"; + if (typeName == "weightedmesh") typeName = "mesh"; + if (typeName == "weightedlinkedmesh") typeName = "linkedmesh"; + var type = (AttachmentType)Enum.Parse(typeof(AttachmentType), typeName, true); + + String path = GetString(map, "path", name); + + switch (type) + { + case AttachmentType.Region: + RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); + if (region == null) return null; + region.Path = path; + region.x = GetFloat(map, "x", 0) * scale; + region.y = GetFloat(map, "y", 0) * scale; + region.scaleX = GetFloat(map, "scaleX", 1); + region.scaleY = GetFloat(map, "scaleY", 1); + region.rotation = GetFloat(map, "rotation", 0); + region.width = GetFloat(map, "width", 32) * scale; + region.height = GetFloat(map, "height", 32) * scale; + region.UpdateOffset(); + + if (map.ContainsKey("color")) + { + var color = (String)map["color"]; + region.r = ToColor(color, 0); + region.g = ToColor(color, 1); + region.b = ToColor(color, 2); + region.a = ToColor(color, 3); + } + + region.UpdateOffset(); + return region; + case AttachmentType.Boundingbox: + BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); + if (box == null) return null; + ReadVertices(map, box, GetInt(map, "vertexCount", 0) << 1); + return box; + case AttachmentType.Mesh: + case AttachmentType.Linkedmesh: + { + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); + if (mesh == null) return null; + mesh.Path = path; + + if (map.ContainsKey("color")) + { + var color = (String)map["color"]; + mesh.r = ToColor(color, 0); + mesh.g = ToColor(color, 1); + mesh.b = ToColor(color, 2); + mesh.a = ToColor(color, 3); + } + + mesh.Width = GetFloat(map, "width", 0) * scale; + mesh.Height = GetFloat(map, "height", 0) * scale; + + String parent = GetString(map, "parent", null); + if (parent != null) + { + mesh.InheritDeform = GetBoolean(map, "deform", true); + linkedMeshes.Add(new LinkedMesh(mesh, GetString(map, "skin", null), slotIndex, parent)); + return mesh; + } + + float[] uvs = GetFloatArray(map, "uvs", 1); + ReadVertices(map, mesh, uvs.Length); + mesh.triangles = GetIntArray(map, "triangles"); + mesh.regionUVs = uvs; + mesh.UpdateUVs(); + + if (map.ContainsKey("hull")) mesh.HullLength = GetInt(map, "hull", 0) * 2; + if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges"); + return mesh; + } + case AttachmentType.Path: + { + PathAttachment pathAttachment = attachmentLoader.NewPathAttachment(skin, name); + if (pathAttachment == null) return null; + pathAttachment.closed = GetBoolean(map, "closed", false); + pathAttachment.constantSpeed = GetBoolean(map, "constantSpeed", true); + + int vertexCount = GetInt(map, "vertexCount", 0); + ReadVertices(map, pathAttachment, vertexCount << 1); + + // potential BOZO see Java impl + pathAttachment.lengths = GetFloatArray(map, "lengths", scale); + return pathAttachment; + } + case AttachmentType.Point: + { + PointAttachment point = attachmentLoader.NewPointAttachment(skin, name); + if (point == null) return null; + point.x = GetFloat(map, "x", 0) * scale; + point.y = GetFloat(map, "y", 0) * scale; + point.rotation = GetFloat(map, "rotation", 0); + + //string color = GetString(map, "color", null); + //if (color != null) point.color = color; + return point; + } + case AttachmentType.Clipping: + { + ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name); + if (clip == null) return null; + + SlotData slot = skeletonData.FindSlot(GetString(map, "end", null)); + if (slot == null) throw new Exception("Clipping end slot not found: " + GetString(map, "end", null)); + clip.endSlot = slot; + ReadVertices(map, clip, GetInt(map, "vertexCount", 0) << 1); + return clip; + } + } + return null; + } + + private void ReadVertices(Dictionary map, VertexAttachment attachment, int verticesLength) + { + attachment.WorldVerticesLength = verticesLength; + float[] vertices = GetFloatArray(map, "vertices", 1); + float scale = Scale; + if (verticesLength == vertices.Length) + { + if (scale != 1) + { + for (int i = 0; i < vertices.Length; i++) + { + vertices[i] *= scale; + } + } + attachment.vertices = vertices; + return; + } + ExposedList weights = new ExposedList(verticesLength * 3 * 3); + ExposedList bones = new ExposedList(verticesLength * 3); + for (int i = 0, n = vertices.Length; i < n;) + { + int boneCount = (int)vertices[i++]; + bones.Add(boneCount); + for (int nn = i + boneCount * 4; i < nn; i += 4) + { + bones.Add((int)vertices[i]); + weights.Add(vertices[i + 1] * this.Scale); + weights.Add(vertices[i + 2] * this.Scale); + weights.Add(vertices[i + 3]); + } + } + attachment.bones = bones.ToArray(); + attachment.vertices = weights.ToArray(); + } + + private void ReadAnimation(Dictionary map, String name, SkeletonData skeletonData) + { + var scale = this.Scale; + var timelines = new ExposedList(); + float duration = 0; + + // Slot timelines. + if (map.ContainsKey("slots")) + { + foreach (KeyValuePair entry in (Dictionary)map["slots"]) + { + String slotName = entry.Key; + int slotIndex = skeletonData.FindSlotIndex(slotName); + var timelineMap = (Dictionary)entry.Value; + foreach (KeyValuePair timelineEntry in timelineMap) + { + var values = (List)timelineEntry.Value; + var timelineName = timelineEntry.Key; + if (timelineName == "attachment") + { + var timeline = new AttachmentTimeline(values.Count); + timeline.slotIndex = slotIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + timeline.SetFrame(frameIndex++, time, (String)valueMap["name"]); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); + + } + else if (timelineName == "color") + { + var timeline = new ColorTimeline(values.Count); + timeline.slotIndex = slotIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + string c = (string)valueMap["color"]; + timeline.SetFrame(frameIndex, time, ToColor(c, 0), ToColor(c, 1), ToColor(c, 2), ToColor(c, 3)); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * ColorTimeline.ENTRIES]); + + } + else if (timelineName == "twoColor") + { + var timeline = new TwoColorTimeline(values.Count); + timeline.slotIndex = slotIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + string light = (string)valueMap["light"]; + string dark = (string)valueMap["dark"]; + timeline.SetFrame(frameIndex, time, ToColor(light, 0), ToColor(light, 1), ToColor(light, 2), ToColor(light, 3), + ToColor(dark, 0, 6), ToColor(dark, 1, 6), ToColor(dark, 2, 6)); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TwoColorTimeline.ENTRIES]); + + } + else + throw new Exception("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")"); + } + } + } + + // Bone timelines. + if (map.ContainsKey("bones")) + { + foreach (KeyValuePair entry in (Dictionary)map["bones"]) + { + String boneName = entry.Key; + int boneIndex = skeletonData.FindBoneIndex(boneName); + if (boneIndex == -1) throw new Exception("Bone not found: " + boneName); + var timelineMap = (Dictionary)entry.Value; + foreach (KeyValuePair timelineEntry in timelineMap) + { + var values = (List)timelineEntry.Value; + var timelineName = timelineEntry.Key; + if (timelineName == "rotate") + { + var timeline = new RotateTimeline(values.Count); + timeline.boneIndex = boneIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + timeline.SetFrame(frameIndex, (float)valueMap["time"], (float)valueMap["angle"]); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * RotateTimeline.ENTRIES]); + + } + else if (timelineName == "translate" || timelineName == "scale" || timelineName == "shear") + { + TranslateTimeline timeline; + float timelineScale = 1; + if (timelineName == "scale") + timeline = new ScaleTimeline(values.Count); + else if (timelineName == "shear") + timeline = new ShearTimeline(values.Count); + else + { + timeline = new TranslateTimeline(values.Count); + timelineScale = scale; + } + timeline.boneIndex = boneIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + float x = GetFloat(valueMap, "x", 0); + float y = GetFloat(valueMap, "y", 0); + timeline.SetFrame(frameIndex, time, x * timelineScale, y * timelineScale); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TranslateTimeline.ENTRIES]); + + } + else + throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")"); + } + } + } + + // IK constraint timelines. + if (map.ContainsKey("ik")) + { + foreach (KeyValuePair constraintMap in (Dictionary)map["ik"]) + { + IkConstraintData constraint = skeletonData.FindIkConstraint(constraintMap.Key); + var values = (List)constraintMap.Value; + var timeline = new IkConstraintTimeline(values.Count); + timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(constraint); + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + float mix = GetFloat(valueMap, "mix", 1); + bool bendPositive = GetBoolean(valueMap, "bendPositive", true); + timeline.SetFrame(frameIndex, time, mix, bendPositive ? 1 : -1); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * IkConstraintTimeline.ENTRIES]); + } + } + + // Transform constraint timelines. + if (map.ContainsKey("transform")) + { + foreach (KeyValuePair constraintMap in (Dictionary)map["transform"]) + { + TransformConstraintData constraint = skeletonData.FindTransformConstraint(constraintMap.Key); + var values = (List)constraintMap.Value; + var timeline = new TransformConstraintTimeline(values.Count); + timeline.transformConstraintIndex = skeletonData.transformConstraints.IndexOf(constraint); + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + float rotateMix = GetFloat(valueMap, "rotateMix", 1); + float translateMix = GetFloat(valueMap, "translateMix", 1); + float scaleMix = GetFloat(valueMap, "scaleMix", 1); + float shearMix = GetFloat(valueMap, "shearMix", 1); + timeline.SetFrame(frameIndex, time, rotateMix, translateMix, scaleMix, shearMix); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TransformConstraintTimeline.ENTRIES]); + } + } + + // Path constraint timelines. + if (map.ContainsKey("paths")) + { + foreach (KeyValuePair constraintMap in (Dictionary)map["paths"]) + { + int index = skeletonData.FindPathConstraintIndex(constraintMap.Key); + if (index == -1) throw new Exception("Path constraint not found: " + constraintMap.Key); + PathConstraintData data = skeletonData.pathConstraints.Items[index]; + var timelineMap = (Dictionary)constraintMap.Value; + foreach (KeyValuePair timelineEntry in timelineMap) + { + var values = (List)timelineEntry.Value; + var timelineName = timelineEntry.Key; + if (timelineName == "position" || timelineName == "spacing") + { + PathConstraintPositionTimeline timeline; + float timelineScale = 1; + if (timelineName == "spacing") + { + timeline = new PathConstraintSpacingTimeline(values.Count); + if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale; + } + else + { + timeline = new PathConstraintPositionTimeline(values.Count); + if (data.positionMode == PositionMode.Fixed) timelineScale = scale; + } + timeline.pathConstraintIndex = index; + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + timeline.SetFrame(frameIndex, (float)valueMap["time"], GetFloat(valueMap, timelineName, 0) * timelineScale); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintPositionTimeline.ENTRIES]); + } + else if (timelineName == "mix") + { + PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(values.Count); + timeline.pathConstraintIndex = index; + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + timeline.SetFrame(frameIndex, (float)valueMap["time"], GetFloat(valueMap, "rotateMix", 1), GetFloat(valueMap, "translateMix", 1)); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintMixTimeline.ENTRIES]); + } + } + } + } + + // Deform timelines. + if (map.ContainsKey("deform")) + { + foreach (KeyValuePair deformMap in (Dictionary)map["deform"]) + { + Skin skin = skeletonData.FindSkin(deformMap.Key); + foreach (KeyValuePair slotMap in (Dictionary)deformMap.Value) + { + int slotIndex = skeletonData.FindSlotIndex(slotMap.Key); + if (slotIndex == -1) throw new Exception("Slot not found: " + slotMap.Key); + foreach (KeyValuePair timelineMap in (Dictionary)slotMap.Value) + { + var values = (List)timelineMap.Value; + VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, timelineMap.Key); + if (attachment == null) throw new Exception("Deform attachment not found: " + timelineMap.Key); + bool weighted = attachment.bones != null; + float[] vertices = attachment.vertices; + int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length; + + var timeline = new DeformTimeline(values.Count); + timeline.slotIndex = slotIndex; + timeline.attachment = attachment; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float[] deform; + if (!valueMap.ContainsKey("vertices")) + { + deform = weighted ? new float[deformLength] : vertices; + } + else + { + deform = new float[deformLength]; + int start = GetInt(valueMap, "offset", 0); + float[] verticesValue = GetFloatArray(valueMap, "vertices", 1); + Array.Copy(verticesValue, 0, deform, start, verticesValue.Length); + if (scale != 1) + { + for (int i = start, n = i + verticesValue.Length; i < n; i++) + deform[i] *= scale; + } + + if (!weighted) + { + for (int i = 0; i < deformLength; i++) + deform[i] += vertices[i]; + } + } + + timeline.SetFrame(frameIndex, (float)valueMap["time"], deform); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); + } + } + } + } + + // Draw order timeline. + if (map.ContainsKey("drawOrder") || map.ContainsKey("draworder")) + { + var values = (List)map[map.ContainsKey("drawOrder") ? "drawOrder" : "draworder"]; + var timeline = new DrawOrderTimeline(values.Count); + int slotCount = skeletonData.slots.Count; + int frameIndex = 0; + foreach (Dictionary drawOrderMap in values) + { + int[] drawOrder = null; + if (drawOrderMap.ContainsKey("offsets")) + { + drawOrder = new int[slotCount]; + for (int i = slotCount - 1; i >= 0; i--) + drawOrder[i] = -1; + var offsets = (List)drawOrderMap["offsets"]; + int[] unchanged = new int[slotCount - offsets.Count]; + int originalIndex = 0, unchangedIndex = 0; + foreach (Dictionary offsetMap in offsets) + { + int slotIndex = skeletonData.FindSlotIndex((String)offsetMap["slot"]); + if (slotIndex == -1) throw new Exception("Slot not found: " + offsetMap["slot"]); + // Collect unchanged items. + while (originalIndex != slotIndex) + unchanged[unchangedIndex++] = originalIndex++; + // Set changed items. + int index = originalIndex + (int)(float)offsetMap["offset"]; + drawOrder[index] = originalIndex++; + } + // Collect remaining unchanged items. + while (originalIndex < slotCount) + unchanged[unchangedIndex++] = originalIndex++; + // Fill in unchanged items. + for (int i = slotCount - 1; i >= 0; i--) + if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex]; + } + timeline.SetFrame(frameIndex++, (float)drawOrderMap["time"], drawOrder); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); + } + + // Event timeline. + if (map.ContainsKey("events")) + { + var eventsMap = (List)map["events"]; + var timeline = new EventTimeline(eventsMap.Count); + int frameIndex = 0; + foreach (Dictionary eventMap in eventsMap) + { + EventData eventData = skeletonData.FindEvent((String)eventMap["name"]); + if (eventData == null) throw new Exception("Event not found: " + eventMap["name"]); + var e = new Event((float)eventMap["time"], eventData); + e.Int = GetInt(eventMap, "int", eventData.Int); + e.Float = GetFloat(eventMap, "float", eventData.Float); + e.String = GetString(eventMap, "string", eventData.String); + timeline.SetFrame(frameIndex++, e); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); + } + + timelines.TrimExcess(); + skeletonData.animations.Add(new Animation(name, timelines, duration)); + } + + static void ReadCurve(Dictionary valueMap, CurveTimeline timeline, int frameIndex) + { + if (!valueMap.ContainsKey("curve")) + return; + Object curveObject = valueMap["curve"]; + if (curveObject.Equals("stepped")) + timeline.SetStepped(frameIndex); + else + { + var curve = curveObject as List; + if (curve != null) + timeline.SetCurve(frameIndex, (float)curve[0], (float)curve[1], (float)curve[2], (float)curve[3]); + } + } + + internal class LinkedMesh + { + internal String parent, skin; + internal int slotIndex; + internal MeshAttachment mesh; + + public LinkedMesh(MeshAttachment mesh, String skin, int slotIndex, String parent) + { + this.mesh = mesh; + this.skin = skin; + this.slotIndex = slotIndex; + this.parent = parent; + } + } + + static float[] GetFloatArray(Dictionary map, String name, float scale) + { + var list = (List)map[name]; + var values = new float[list.Count]; + if (scale == 1) + { + for (int i = 0, n = list.Count; i < n; i++) + values[i] = (float)list[i]; + } + else + { + for (int i = 0, n = list.Count; i < n; i++) + values[i] = (float)list[i] * scale; + } + return values; + } + + static int[] GetIntArray(Dictionary map, String name) + { + var list = (List)map[name]; + var values = new int[list.Count]; + for (int i = 0, n = list.Count; i < n; i++) + values[i] = (int)(float)list[i]; + return values; + } + + static float GetFloat(Dictionary map, String name, float defaultValue) + { + if (!map.ContainsKey(name)) + return defaultValue; + return (float)map[name]; + } + + static int GetInt(Dictionary map, String name, int defaultValue) + { + if (!map.ContainsKey(name)) + return defaultValue; + return (int)(float)map[name]; + } + + static bool GetBoolean(Dictionary map, String name, bool defaultValue) + { + if (!map.ContainsKey(name)) + return defaultValue; + return (bool)map[name]; + } + + static String GetString(Dictionary map, String name, String defaultValue) + { + if (!map.ContainsKey(name)) + return defaultValue; + return (String)map[name]; + } + + static float ToColor(String hexString, int colorIndex, int expectedLength = 8) + { + if (hexString.Length != expectedLength) + throw new ArgumentException("Color hexidecimal length must be " + expectedLength + ", recieved: " + hexString, "hexString"); + return Convert.ToInt32(hexString.Substring(colorIndex * 2, 2), 16) / (float)255; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Skin.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Skin.cs index 4e22e08..7bcf62a 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Skin.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Skin.cs @@ -31,95 +31,111 @@ using System; using System.Collections.Generic; -namespace Spine3_6_32 { - /// Stores attachments by slot index and attachment name. - /// See SkeletonData , Skeleton , and - /// Runtime skins in the Spine Runtimes Guide. - /// - public class Skin { - internal string name; - private Dictionary attachments = - new Dictionary(AttachmentKeyTupleComparer.Instance); +namespace Spine3_6_32 +{ + /// Stores attachments by slot index and attachment name. + /// See SkeletonData , Skeleton , and + /// Runtime skins in the Spine Runtimes Guide. + /// + public class Skin + { + internal string name; + private Dictionary attachments = + new Dictionary(AttachmentKeyTupleComparer.Instance); - public string Name { get { return name; } } - public Dictionary Attachments { get { return attachments; } } + public string Name { get { return name; } } + public Dictionary Attachments { get { return attachments; } } - public Skin (string name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; - } + public Skin(string name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + } - /// 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. - public void AddAttachment (int slotIndex, string name, Attachment attachment) { - if (attachment == null) throw new ArgumentNullException("attachment", "attachment cannot be null."); - attachments[new AttachmentKeyTuple(slotIndex, name)] = attachment; - } + /// 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. + public void AddAttachment(int slotIndex, string name, Attachment attachment) + { + if (attachment == null) throw new ArgumentNullException("attachment", "attachment cannot be null."); + attachments[new AttachmentKeyTuple(slotIndex, name)] = attachment; + } - /// Returns the attachment for the specified slot index and name, or null. - /// May be null. - public Attachment GetAttachment (int slotIndex, string name) { - Attachment attachment; - attachments.TryGetValue(new AttachmentKeyTuple(slotIndex, name), out attachment); - return attachment; - } + /// Returns the attachment for the specified slot index and name, or null. + /// May be null. + public Attachment GetAttachment(int slotIndex, string name) + { + Attachment attachment; + attachments.TryGetValue(new AttachmentKeyTuple(slotIndex, name), out attachment); + return attachment; + } - /// Finds the skin keys for a given slot. The results are added to the passed List(names). - /// The target slotIndex. To find the slot index, use or - /// Found skin key names will be added to this list. - public void FindNamesForSlot (int slotIndex, List names) { - if (names == null) throw new ArgumentNullException("names", "names cannot be null."); - foreach (AttachmentKeyTuple key in attachments.Keys) - if (key.slotIndex == slotIndex) names.Add(key.name); - } + /// Finds the skin keys for a given slot. The results are added to the passed List(names). + /// The target slotIndex. To find the slot index, use or + /// Found skin key names will be added to this list. + public void FindNamesForSlot(int slotIndex, List names) + { + if (names == null) throw new ArgumentNullException("names", "names cannot be null."); + foreach (AttachmentKeyTuple key in attachments.Keys) + if (key.slotIndex == slotIndex) names.Add(key.name); + } - /// Finds the attachments for a given slot. The results are added to the passed List(Attachment). - /// The target slotIndex. To find the slot index, use or - /// Found Attachments will be added to this list. - public void FindAttachmentsForSlot (int slotIndex, List attachments) { - if (attachments == null) throw new ArgumentNullException("attachments", "attachments cannot be null."); - foreach (KeyValuePair entry in this.attachments) - if (entry.Key.slotIndex == slotIndex) attachments.Add(entry.Value); - } + /// Finds the attachments for a given slot. The results are added to the passed List(Attachment). + /// The target slotIndex. To find the slot index, use or + /// Found Attachments will be added to this list. + public void FindAttachmentsForSlot(int slotIndex, List attachments) + { + if (attachments == null) throw new ArgumentNullException("attachments", "attachments cannot be null."); + foreach (KeyValuePair entry in this.attachments) + if (entry.Key.slotIndex == slotIndex) attachments.Add(entry.Value); + } - override public string ToString () { - return name; - } + override public string ToString() + { + return name; + } - /// Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached. - internal void AttachAll (Skeleton skeleton, Skin oldSkin) { - foreach (KeyValuePair entry in oldSkin.attachments) { - int slotIndex = entry.Key.slotIndex; - Slot slot = skeleton.slots.Items[slotIndex]; - if (slot.Attachment == entry.Value) { - Attachment attachment = GetAttachment(slotIndex, entry.Key.name); - if (attachment != null) slot.Attachment = attachment; - } - } - } + /// Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached. + internal void AttachAll(Skeleton skeleton, Skin oldSkin) + { + foreach (KeyValuePair entry in oldSkin.attachments) + { + int slotIndex = entry.Key.slotIndex; + Slot slot = skeleton.slots.Items[slotIndex]; + if (slot.Attachment == entry.Value) + { + Attachment attachment = GetAttachment(slotIndex, entry.Key.name); + if (attachment != null) slot.Attachment = attachment; + } + } + } - public struct AttachmentKeyTuple { - public readonly int slotIndex; - public readonly string name; - internal readonly int nameHashCode; + public struct AttachmentKeyTuple + { + public readonly int slotIndex; + public readonly string name; + internal readonly int nameHashCode; - public AttachmentKeyTuple (int slotIndex, string name) { - this.slotIndex = slotIndex; - this.name = name; - nameHashCode = this.name.GetHashCode(); - } - } + public AttachmentKeyTuple(int slotIndex, string name) + { + this.slotIndex = slotIndex; + this.name = name; + nameHashCode = this.name.GetHashCode(); + } + } - // Avoids boxing in the dictionary. - class AttachmentKeyTupleComparer : IEqualityComparer { - internal static readonly AttachmentKeyTupleComparer Instance = new AttachmentKeyTupleComparer(); + // Avoids boxing in the dictionary. + class AttachmentKeyTupleComparer : IEqualityComparer + { + internal static readonly AttachmentKeyTupleComparer Instance = new AttachmentKeyTupleComparer(); - bool IEqualityComparer.Equals (AttachmentKeyTuple o1, AttachmentKeyTuple o2) { - return o1.slotIndex == o2.slotIndex && o1.nameHashCode == o2.nameHashCode && o1.name == o2.name; - } + bool IEqualityComparer.Equals(AttachmentKeyTuple o1, AttachmentKeyTuple o2) + { + return o1.slotIndex == o2.slotIndex && o1.nameHashCode == o2.nameHashCode && o1.name == o2.name; + } - int IEqualityComparer.GetHashCode (AttachmentKeyTuple o) { - return o.slotIndex; - } - } - } + int IEqualityComparer.GetHashCode(AttachmentKeyTuple o) + { + return o.slotIndex; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Slot.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Slot.cs index ffb3654..3e36ef8 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Slot.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Slot.cs @@ -30,71 +30,80 @@ using System; -namespace Spine3_6_32 { - public class Slot { - internal SlotData data; - internal Bone bone; - internal float r, g, b, a; - internal float r2, g2, b2; - internal bool hasSecondColor; - internal Attachment attachment; - internal float attachmentTime; - internal ExposedList attachmentVertices = new ExposedList(); +namespace Spine3_6_32 +{ + public class Slot + { + internal SlotData data; + internal Bone bone; + internal float r, g, b, a; + internal float r2, g2, b2; + internal bool hasSecondColor; + internal Attachment attachment; + internal float attachmentTime; + internal ExposedList attachmentVertices = new ExposedList(); - public SlotData Data { get { return data; } } - public Bone Bone { get { return bone; } } - public Skeleton Skeleton { get { return bone.skeleton; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } + public SlotData Data { get { return data; } } + public Bone Bone { get { return bone; } } + public Skeleton Skeleton { get { return bone.skeleton; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } - public float R2 { get { return r2; } set { r2 = value; } } - public float G2 { get { return g2; } set { g2 = value; } } - public float B2 { get { return b2; } set { b2 = value; } } - public bool HasSecondColor { get { return data.hasSecondColor; } set { data.hasSecondColor = value; } } + public float R2 { get { return r2; } set { r2 = value; } } + public float G2 { get { return g2; } set { g2 = value; } } + public float B2 { get { return b2; } set { b2 = value; } } + public bool HasSecondColor { get { return data.hasSecondColor; } set { data.hasSecondColor = value; } } - /// May be null. - public Attachment Attachment { - get { return attachment; } - set { - if (attachment == value) return; - attachment = value; - attachmentTime = bone.skeleton.time; - attachmentVertices.Clear(false); - } - } + /// May be null. + public Attachment Attachment + { + get { return attachment; } + set + { + if (attachment == value) return; + attachment = value; + attachmentTime = bone.skeleton.time; + attachmentVertices.Clear(false); + } + } - public float AttachmentTime { - get { return bone.skeleton.time - attachmentTime; } - set { attachmentTime = bone.skeleton.time - value; } - } + public float AttachmentTime + { + get { return bone.skeleton.time - attachmentTime; } + set { attachmentTime = bone.skeleton.time - value; } + } - public ExposedList AttachmentVertices { get { return attachmentVertices; } set { attachmentVertices = value; } } + public ExposedList AttachmentVertices { get { return attachmentVertices; } set { attachmentVertices = value; } } - public Slot (SlotData data, Bone bone) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); - this.data = data; - this.bone = bone; - SetToSetupPose(); - } + public Slot(SlotData data, Bone bone) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); + this.data = data; + this.bone = bone; + SetToSetupPose(); + } - public void SetToSetupPose () { - r = data.r; - g = data.g; - b = data.b; - a = data.a; - if (data.attachmentName == null) - Attachment = null; - else { - attachment = null; - Attachment = bone.skeleton.GetAttachment(data.index, data.attachmentName); - } - } + public void SetToSetupPose() + { + r = data.r; + g = data.g; + b = data.b; + a = data.a; + if (data.attachmentName == null) + Attachment = null; + else + { + attachment = null; + Attachment = bone.skeleton.GetAttachment(data.index, data.attachmentName); + } + } - override public string ToString () { - return data.name; - } - } + override public string ToString() + { + return data.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/SlotData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/SlotData.cs index 2e16fca..f46818c 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/SlotData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/SlotData.cs @@ -30,45 +30,49 @@ using System; -namespace Spine3_6_32 { - public class SlotData { - internal int index; - internal string name; - internal BoneData boneData; - internal float r = 1, g = 1, b = 1, a = 1; - internal float r2 = 0, g2 = 0, b2 = 0; - internal bool hasSecondColor = false; - internal string attachmentName; - internal BlendMode blendMode; +namespace Spine3_6_32 +{ + public class SlotData + { + internal int index; + internal string name; + internal BoneData boneData; + internal float r = 1, g = 1, b = 1, a = 1; + internal float r2 = 0, g2 = 0, b2 = 0; + internal bool hasSecondColor = false; + internal string attachmentName; + internal BlendMode blendMode; - public int Index { get { return index; } } - public string Name { get { return name; } } - public BoneData BoneData { get { return boneData; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } + public int Index { get { return index; } } + public string Name { get { return name; } } + public BoneData BoneData { get { return boneData; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } - public float R2 { get { return r2; } set { r2 = value; } } - public float G2 { get { return g2; } set { g2 = value; } } - public float B2 { get { return b2; } set { b2 = value; } } - public bool HasSecondColor { get { return hasSecondColor; } set { hasSecondColor = value; } } + public float R2 { get { return r2; } set { r2 = value; } } + public float G2 { get { return g2; } set { g2 = value; } } + public float B2 { get { return b2; } set { b2 = value; } } + public bool HasSecondColor { get { return hasSecondColor; } set { hasSecondColor = value; } } - /// May be null. - public String AttachmentName { get { return attachmentName; } set { attachmentName = value; } } - public BlendMode BlendMode { get { return blendMode; } set { blendMode = value; } } + /// May be null. + public String AttachmentName { get { return attachmentName; } set { attachmentName = value; } } + public BlendMode BlendMode { get { return blendMode; } set { blendMode = value; } } - public SlotData (int index, String name, BoneData boneData) { - if (index < 0) throw new ArgumentException ("index must be >= 0.", "index"); - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - if (boneData == null) throw new ArgumentNullException("boneData", "boneData cannot be null."); - this.index = index; - this.name = name; - this.boneData = boneData; - } + public SlotData(int index, String name, BoneData boneData) + { + if (index < 0) throw new ArgumentException("index must be >= 0.", "index"); + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + if (boneData == null) throw new ArgumentNullException("boneData", "boneData cannot be null."); + this.index = index; + this.name = name; + this.boneData = boneData; + } - override public string ToString () { - return name; - } - } + override public string ToString() + { + return name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/TransformConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/TransformConstraint.cs index 4cae2ae..55dfcc3 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/TransformConstraint.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/TransformConstraint.cs @@ -30,255 +30,286 @@ using System; -namespace Spine3_6_32 { - public class TransformConstraint : IConstraint { - internal TransformConstraintData data; - internal ExposedList bones; - internal Bone target; - internal float rotateMix, translateMix, scaleMix, shearMix; - - public TransformConstraintData Data { get { return data; } } - public int Order { get { return data.order; } } - public ExposedList Bones { get { return bones; } } - public Bone Target { get { return target; } set { target = value; } } - public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } - public float TranslateMix { get { return translateMix; } set { translateMix = value; } } - public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } } - public float ShearMix { get { return shearMix; } set { shearMix = value; } } - - public TransformConstraint (TransformConstraintData data, Skeleton skeleton) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.data = data; - rotateMix = data.rotateMix; - translateMix = data.translateMix; - scaleMix = data.scaleMix; - shearMix = data.shearMix; - - bones = new ExposedList(); - foreach (BoneData boneData in data.bones) - bones.Add (skeleton.FindBone (boneData.name)); - - target = skeleton.FindBone(data.target.name); - } - - public void Apply () { - Update(); - } - - public void Update () { - if (data.local) { - if (data.relative) - ApplyRelativeLocal(); - else - ApplyAbsoluteLocal(); - } else { - if (data.relative) - ApplyRelativeWorld(); - else - ApplyAbsoluteWorld(); - } - } - - void ApplyAbsoluteWorld () { - float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; - Bone target = this.target; - float ta = target.a, tb = target.b, tc = target.c, td = target.d; - float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad; - float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect; - var bones = this.bones; - for (int i = 0, n = bones.Count; i < n; i++) { - Bone bone = bones.Items[i]; - bool modified = false; - - if (rotateMix != 0) { - float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - float r = MathUtils.Atan2(tc, ta) - MathUtils.Atan2(c, a) + offsetRotation; - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) r += MathUtils.PI2; - r *= rotateMix; - float cos = MathUtils.Cos(r), sin = MathUtils.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; - modified = true; - } - - if (translateMix != 0) { - float tx, ty; //Vector2 temp = this.temp; - target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY)); - bone.worldX += (tx - bone.worldX) * translateMix; - bone.worldY += (ty - bone.worldY) * translateMix; - modified = true; - } - - if (scaleMix > 0) { - float s = (float)Math.Sqrt(bone.a * bone.a + bone.c * bone.c); - //float ts = (float)Math.sqrt(ta * ta + tc * tc); - if (s > 0.00001f) s = (s + ((float)Math.Sqrt(ta * ta + tc * tc) - s + data.offsetScaleX) * scaleMix) / s; - bone.a *= s; - bone.c *= s; - s = (float)Math.Sqrt(bone.b * bone.b + bone.d * bone.d); - //ts = (float)Math.Sqrt(tb * tb + td * td); - if (s > 0.00001f) s = (s + ((float)Math.Sqrt(tb * tb + td * td) - s + data.offsetScaleY) * scaleMix) / s; - bone.b *= s; - bone.d *= s; - modified = true; - } - - if (shearMix > 0) { - float b = bone.b, d = bone.d; - float by = MathUtils.Atan2(d, b); - float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta) - (by - MathUtils.Atan2(bone.c, bone.a)); - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) r += MathUtils.PI2; - r = by + (r + offsetShearY) * shearMix; - float s = (float)Math.Sqrt(b * b + d * d); - bone.b = MathUtils.Cos(r) * s; - bone.d = MathUtils.Sin(r) * s; - modified = true; - } - - if (modified) bone.appliedValid = false; - } - } - - void ApplyRelativeWorld () { - float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; - Bone target = this.target; - float ta = target.a, tb = target.b, tc = target.c, td = target.d; - float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad; - float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect; - var bones = this.bones; - for (int i = 0, n = bones.Count; i < n; i++) { - Bone bone = bones.Items[i]; - bool modified = false; - - if (rotateMix != 0) { - float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - float r = MathUtils.Atan2(tc, ta) + offsetRotation; - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) r += MathUtils.PI2; - r *= rotateMix; - float cos = MathUtils.Cos(r), sin = MathUtils.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; - modified = true; - } - - if (translateMix != 0) { - float tx, ty; //Vector2 temp = this.temp; - target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY)); - bone.worldX += tx * translateMix; - bone.worldY += ty * translateMix; - modified = true; - } - - if (scaleMix > 0) { - float s = ((float)Math.Sqrt(ta * ta + tc * tc) - 1 + data.offsetScaleX) * scaleMix + 1; - bone.a *= s; - bone.c *= s; - s = ((float)Math.Sqrt(tb * tb + td * td) - 1 + data.offsetScaleY) * scaleMix + 1; - bone.b *= s; - bone.d *= s; - modified = true; - } - - if (shearMix > 0) { - float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta); - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) r += MathUtils.PI2; - float b = bone.b, d = bone.d; - r = MathUtils.Atan2(d, b) + (r - MathUtils.PI / 2 + offsetShearY) * shearMix; - float s = (float)Math.Sqrt(b * b + d * d); - bone.b = MathUtils.Cos(r) * s; - bone.d = MathUtils.Sin(r) * s; - modified = true; - } - - if (modified) bone.appliedValid = false; - } - } - - void ApplyAbsoluteLocal () { - float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; - Bone target = this.target; - if (!target.appliedValid) target.UpdateAppliedTransform(); - var bonesItems = this.bones.Items; - for (int i = 0, n = this.bones.Count; i < n; i++) { - Bone bone = bonesItems[i]; - if (!bone.appliedValid) bone.UpdateAppliedTransform(); - - float rotation = bone.arotation; - if (rotateMix != 0) { - float r = target.arotation - rotation + data.offsetRotation; - r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; - rotation += r * rotateMix; - } - - float x = bone.ax, y = bone.ay; - if (translateMix != 0) { - x += (target.ax - x + data.offsetX) * translateMix; - y += (target.ay - y + data.offsetY) * translateMix; - } - - float scaleX = bone.ascaleX, scaleY = bone.ascaleY; - if (scaleMix > 0) { - if (scaleX > 0.00001f) scaleX = (scaleX + (target.ascaleX - scaleX + data.offsetScaleX) * scaleMix) / scaleX; - if (scaleY > 0.00001f) scaleY = (scaleY + (target.ascaleY - scaleY + data.offsetScaleY) * scaleMix) / scaleY; - } - - float shearY = bone.ashearY; - if (shearMix > 0) { - float r = target.ashearY - shearY + data.offsetShearY; - r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; - bone.shearY += r * shearMix; - } - - bone.UpdateWorldTransform(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY); - } - } - - void ApplyRelativeLocal () { - float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; - Bone target = this.target; - if (!target.appliedValid) target.UpdateAppliedTransform(); - var bonesItems = this.bones.Items; - for (int i = 0, n = this.bones.Count; i < n; i++) { - Bone bone = bonesItems[i]; - if (!bone.appliedValid) bone.UpdateAppliedTransform(); - - float rotation = bone.arotation; - if (rotateMix != 0) rotation += (target.arotation + data.offsetRotation) * rotateMix; - - float x = bone.ax, y = bone.ay; - if (translateMix != 0) { - x += (target.ax + data.offsetX) * translateMix; - y += (target.ay + data.offsetY) * translateMix; - } - - float scaleX = bone.ascaleX, scaleY = bone.ascaleY; - if (scaleMix > 0) { - if (scaleX > 0.00001f) scaleX *= ((target.ascaleX - 1 + data.offsetScaleX) * scaleMix) + 1; - if (scaleY > 0.00001f) scaleY *= ((target.ascaleY - 1 + data.offsetScaleY) * scaleMix) + 1; - } - - float shearY = bone.ashearY; - if (shearMix > 0) shearY += (target.ashearY + data.offsetShearY) * shearMix; - - bone.UpdateWorldTransform(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY); - } - } - - override public string ToString () { - return data.name; - } - } +namespace Spine3_6_32 +{ + public class TransformConstraint : IConstraint + { + internal TransformConstraintData data; + internal ExposedList bones; + internal Bone target; + internal float rotateMix, translateMix, scaleMix, shearMix; + + public TransformConstraintData Data { get { return data; } } + public int Order { get { return data.order; } } + public ExposedList Bones { get { return bones; } } + public Bone Target { get { return target; } set { target = value; } } + public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } + public float TranslateMix { get { return translateMix; } set { translateMix = value; } } + public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } } + public float ShearMix { get { return shearMix; } set { shearMix = value; } } + + public TransformConstraint(TransformConstraintData data, Skeleton skeleton) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + rotateMix = data.rotateMix; + translateMix = data.translateMix; + scaleMix = data.scaleMix; + shearMix = data.shearMix; + + bones = new ExposedList(); + foreach (BoneData boneData in data.bones) + bones.Add(skeleton.FindBone(boneData.name)); + + target = skeleton.FindBone(data.target.name); + } + + public void Apply() + { + Update(); + } + + public void Update() + { + if (data.local) + { + if (data.relative) + ApplyRelativeLocal(); + else + ApplyAbsoluteLocal(); + } + else + { + if (data.relative) + ApplyRelativeWorld(); + else + ApplyAbsoluteWorld(); + } + } + + void ApplyAbsoluteWorld() + { + float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; + Bone target = this.target; + float ta = target.a, tb = target.b, tc = target.c, td = target.d; + float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad; + float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect; + var bones = this.bones; + for (int i = 0, n = bones.Count; i < n; i++) + { + Bone bone = bones.Items[i]; + bool modified = false; + + if (rotateMix != 0) + { + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + float r = MathUtils.Atan2(tc, ta) - MathUtils.Atan2(c, a) + offsetRotation; + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) r += MathUtils.PI2; + r *= rotateMix; + float cos = MathUtils.Cos(r), sin = MathUtils.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; + modified = true; + } + + if (translateMix != 0) + { + float tx, ty; //Vector2 temp = this.temp; + target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY)); + bone.worldX += (tx - bone.worldX) * translateMix; + bone.worldY += (ty - bone.worldY) * translateMix; + modified = true; + } + + if (scaleMix > 0) + { + float s = (float)Math.Sqrt(bone.a * bone.a + bone.c * bone.c); + //float ts = (float)Math.sqrt(ta * ta + tc * tc); + if (s > 0.00001f) s = (s + ((float)Math.Sqrt(ta * ta + tc * tc) - s + data.offsetScaleX) * scaleMix) / s; + bone.a *= s; + bone.c *= s; + s = (float)Math.Sqrt(bone.b * bone.b + bone.d * bone.d); + //ts = (float)Math.Sqrt(tb * tb + td * td); + if (s > 0.00001f) s = (s + ((float)Math.Sqrt(tb * tb + td * td) - s + data.offsetScaleY) * scaleMix) / s; + bone.b *= s; + bone.d *= s; + modified = true; + } + + if (shearMix > 0) + { + float b = bone.b, d = bone.d; + float by = MathUtils.Atan2(d, b); + float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta) - (by - MathUtils.Atan2(bone.c, bone.a)); + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) r += MathUtils.PI2; + r = by + (r + offsetShearY) * shearMix; + float s = (float)Math.Sqrt(b * b + d * d); + bone.b = MathUtils.Cos(r) * s; + bone.d = MathUtils.Sin(r) * s; + modified = true; + } + + if (modified) bone.appliedValid = false; + } + } + + void ApplyRelativeWorld() + { + float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; + Bone target = this.target; + float ta = target.a, tb = target.b, tc = target.c, td = target.d; + float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad; + float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect; + var bones = this.bones; + for (int i = 0, n = bones.Count; i < n; i++) + { + Bone bone = bones.Items[i]; + bool modified = false; + + if (rotateMix != 0) + { + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + float r = MathUtils.Atan2(tc, ta) + offsetRotation; + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) r += MathUtils.PI2; + r *= rotateMix; + float cos = MathUtils.Cos(r), sin = MathUtils.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; + modified = true; + } + + if (translateMix != 0) + { + float tx, ty; //Vector2 temp = this.temp; + target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY)); + bone.worldX += tx * translateMix; + bone.worldY += ty * translateMix; + modified = true; + } + + if (scaleMix > 0) + { + float s = ((float)Math.Sqrt(ta * ta + tc * tc) - 1 + data.offsetScaleX) * scaleMix + 1; + bone.a *= s; + bone.c *= s; + s = ((float)Math.Sqrt(tb * tb + td * td) - 1 + data.offsetScaleY) * scaleMix + 1; + bone.b *= s; + bone.d *= s; + modified = true; + } + + if (shearMix > 0) + { + float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta); + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) r += MathUtils.PI2; + float b = bone.b, d = bone.d; + r = MathUtils.Atan2(d, b) + (r - MathUtils.PI / 2 + offsetShearY) * shearMix; + float s = (float)Math.Sqrt(b * b + d * d); + bone.b = MathUtils.Cos(r) * s; + bone.d = MathUtils.Sin(r) * s; + modified = true; + } + + if (modified) bone.appliedValid = false; + } + } + + void ApplyAbsoluteLocal() + { + float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; + Bone target = this.target; + if (!target.appliedValid) target.UpdateAppliedTransform(); + var bonesItems = this.bones.Items; + for (int i = 0, n = this.bones.Count; i < n; i++) + { + Bone bone = bonesItems[i]; + if (!bone.appliedValid) bone.UpdateAppliedTransform(); + + float rotation = bone.arotation; + if (rotateMix != 0) + { + float r = target.arotation - rotation + data.offsetRotation; + r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; + rotation += r * rotateMix; + } + + float x = bone.ax, y = bone.ay; + if (translateMix != 0) + { + x += (target.ax - x + data.offsetX) * translateMix; + y += (target.ay - y + data.offsetY) * translateMix; + } + + float scaleX = bone.ascaleX, scaleY = bone.ascaleY; + if (scaleMix > 0) + { + if (scaleX > 0.00001f) scaleX = (scaleX + (target.ascaleX - scaleX + data.offsetScaleX) * scaleMix) / scaleX; + if (scaleY > 0.00001f) scaleY = (scaleY + (target.ascaleY - scaleY + data.offsetScaleY) * scaleMix) / scaleY; + } + + float shearY = bone.ashearY; + if (shearMix > 0) + { + float r = target.ashearY - shearY + data.offsetShearY; + r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; + bone.shearY += r * shearMix; + } + + bone.UpdateWorldTransform(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY); + } + } + + void ApplyRelativeLocal() + { + float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; + Bone target = this.target; + if (!target.appliedValid) target.UpdateAppliedTransform(); + var bonesItems = this.bones.Items; + for (int i = 0, n = this.bones.Count; i < n; i++) + { + Bone bone = bonesItems[i]; + if (!bone.appliedValid) bone.UpdateAppliedTransform(); + + float rotation = bone.arotation; + if (rotateMix != 0) rotation += (target.arotation + data.offsetRotation) * rotateMix; + + float x = bone.ax, y = bone.ay; + if (translateMix != 0) + { + x += (target.ax + data.offsetX) * translateMix; + y += (target.ay + data.offsetY) * translateMix; + } + + float scaleX = bone.ascaleX, scaleY = bone.ascaleY; + if (scaleMix > 0) + { + if (scaleX > 0.00001f) scaleX *= ((target.ascaleX - 1 + data.offsetScaleX) * scaleMix) + 1; + if (scaleY > 0.00001f) scaleY *= ((target.ascaleY - 1 + data.offsetScaleY) * scaleMix) + 1; + } + + float shearY = bone.ashearY; + if (shearMix > 0) shearY += (target.ashearY + data.offsetShearY) * shearMix; + + bone.UpdateWorldTransform(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY); + } + } + + override public string ToString() + { + return data.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/TransformConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/TransformConstraintData.cs index 7a71492..c5c4a9c 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/TransformConstraintData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/TransformConstraintData.cs @@ -30,42 +30,46 @@ using System; -namespace Spine3_6_32 { - public class TransformConstraintData { - internal string name; - internal int order; - internal ExposedList bones = new ExposedList(); - internal BoneData target; - internal float rotateMix, translateMix, scaleMix, shearMix; - internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY; - internal bool relative, local; +namespace Spine3_6_32 +{ + public class TransformConstraintData + { + internal string name; + internal int order; + internal ExposedList bones = new ExposedList(); + internal BoneData target; + internal float rotateMix, translateMix, scaleMix, shearMix; + internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY; + internal bool relative, local; - public string Name { get { return name; } } - public int Order { get { return order; } set { order = value; } } - public ExposedList Bones { get { return bones; } } - public BoneData Target { get { return target; } set { target = value; } } - public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } - public float TranslateMix { get { return translateMix; } set { translateMix = value; } } - public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } } - public float ShearMix { get { return shearMix; } set { shearMix = value; } } + public string Name { get { return name; } } + public int Order { get { return order; } set { order = value; } } + public ExposedList Bones { get { return bones; } } + public BoneData Target { get { return target; } set { target = value; } } + public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } + public float TranslateMix { get { return translateMix; } set { translateMix = value; } } + public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } } + public float ShearMix { get { return shearMix; } set { shearMix = value; } } - public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } - public float OffsetX { get { return offsetX; } set { offsetX = value; } } - public float OffsetY { get { return offsetY; } set { offsetY = value; } } - public float OffsetScaleX { get { return offsetScaleX; } set { offsetScaleX = value; } } - public float OffsetScaleY { get { return offsetScaleY; } set { offsetScaleY = value; } } - public float OffsetShearY { get { return offsetShearY; } set { offsetShearY = value; } } + public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } + public float OffsetX { get { return offsetX; } set { offsetX = value; } } + public float OffsetY { get { return offsetY; } set { offsetY = value; } } + public float OffsetScaleX { get { return offsetScaleX; } set { offsetScaleX = value; } } + public float OffsetScaleY { get { return offsetScaleY; } set { offsetScaleY = value; } } + public float OffsetShearY { get { return offsetShearY; } set { offsetShearY = value; } } - public bool Relative { get { return relative; } set { relative = value; } } - public bool Local { get { return local; } set { local = value; } } + public bool Relative { get { return relative; } set { relative = value; } } + public bool Local { get { return local; } set { local = value; } } - public TransformConstraintData (string name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; - } + public TransformConstraintData(string name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + } - override public string ToString () { - return name; - } - } + override public string ToString() + { + return name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Triangulator.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Triangulator.cs index 822a028..da11ed8 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Triangulator.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/Triangulator.cs @@ -30,249 +30,280 @@ using System; -namespace Spine3_6_32 { - internal class Triangulator { - private readonly ExposedList> convexPolygons = new ExposedList>(); - private readonly ExposedList> convexPolygonsIndices = new ExposedList>(); - - private readonly ExposedList indicesArray = new ExposedList(); - private readonly ExposedList isConcaveArray = new ExposedList(); - private readonly ExposedList triangles = new ExposedList(); - - private readonly Pool> polygonPool = new Pool>(); - private readonly Pool> polygonIndicesPool = new Pool>(); - - public ExposedList Triangulate (ExposedList verticesArray) { - var vertices = verticesArray.Items; - int vertexCount = verticesArray.Count >> 1; - - var indicesArray = this.indicesArray; - indicesArray.Clear(); - int[] indices = indicesArray.Resize(vertexCount).Items; - for (int i = 0; i < vertexCount; i++) - indices[i] = i; - - var isConcaveArray = this.isConcaveArray; - bool[] isConcave = isConcaveArray.Resize(vertexCount).Items; - for (int i = 0, n = vertexCount; i < n; ++i) - isConcave[i] = IsConcave(i, vertexCount, vertices, indices); - - var triangles = this.triangles; - triangles.Clear(); - triangles.EnsureCapacity(Math.Max(0, vertexCount - 2) << 2); - - while (vertexCount > 3) { - // Find ear tip. - int previous = vertexCount - 1, i = 0, next = 1; - - // outer: - while (true) { - if (!isConcave[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 (int ii = (next + 1) % vertexCount; ii != previous; ii = (ii + 1) % vertexCount) { - if (!isConcave[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 outer; // break outer; - } - } - } - break; - } - - if (next == 0) { - do { - if (!isConcave[i]) break; - i--; - } while (i > 0); - break; - } - - previous = i; - i = next; - next = (next + 1) % vertexCount; - } - outer: - - // Cut ear tip. - triangles.Add(indices[(vertexCount + i - 1) % vertexCount]); - triangles.Add(indices[i]); - triangles.Add(indices[(i + 1) % vertexCount]); - indicesArray.RemoveAt(i); - isConcaveArray.RemoveAt(i); - vertexCount--; - - int previousIndex = (vertexCount + i - 1) % vertexCount; - int nextIndex = i == vertexCount ? 0 : i; - isConcave[previousIndex] = IsConcave(previousIndex, vertexCount, vertices, indices); - isConcave[nextIndex] = IsConcave(nextIndex, vertexCount, vertices, indices); - } - - if (vertexCount == 3) { - triangles.Add(indices[2]); - triangles.Add(indices[0]); - triangles.Add(indices[1]); - } - - return triangles; - } - - public ExposedList> Decompose (ExposedList verticesArray, ExposedList triangles) { - var vertices = verticesArray.Items; - var convexPolygons = this.convexPolygons; - for (int i = 0, n = convexPolygons.Count; i < n; i++) { - polygonPool.Free(convexPolygons.Items[i]); - } - convexPolygons.Clear(); - - var convexPolygonsIndices = this.convexPolygonsIndices; - for (int i = 0, n = convexPolygonsIndices.Count; i < n; i++) { - polygonIndicesPool.Free(convexPolygonsIndices.Items[i]); - } - convexPolygonsIndices.Clear(); - - var polygonIndices = polygonIndicesPool.Obtain(); - polygonIndices.Clear(); - - var polygon = polygonPool.Obtain(); - polygon.Clear(); - - // Merge subsequent triangles if they form a triangle fan. - int fanBaseIndex = -1, lastWinding = 0; - int[] trianglesItems = triangles.Items; - for (int i = 0, n = triangles.Count; i < n; i += 3) { - int t1 = trianglesItems[i] << 1, t2 = trianglesItems[i + 1] << 1, t3 = trianglesItems[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). - var merged = false; - if (fanBaseIndex == t1) { - int o = polygon.Count - 4; - float[] p = polygon.Items; - 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.Count > 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.Count > 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 (int i = 0, n = convexPolygons.Count; i < n; i++) { - polygonIndices = convexPolygonsIndices.Items[i]; - if (polygonIndices.Count == 0) continue; - int firstIndex = polygonIndices.Items[0]; - int lastIndex = polygonIndices.Items[polygonIndices.Count - 1]; - - polygon = convexPolygons.Items[i]; - int o = polygon.Count - 4; - float[] p = polygon.Items; - 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 winding = Winding(prevPrevX, prevPrevY, prevX, prevY, firstX, firstY); - - for (int ii = 0; ii < n; ii++) { - if (ii == i) continue; - var otherIndices = convexPolygonsIndices.Items[ii]; - if (otherIndices.Count != 3) continue; - int otherFirstIndex = otherIndices.Items[0]; - int otherSecondIndex = otherIndices.Items[1]; - int otherLastIndex = otherIndices.Items[2]; - - var otherPoly = convexPolygons.Items[ii]; - float x3 = otherPoly.Items[otherPoly.Count - 2], y3 = otherPoly.Items[otherPoly.Count - 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 == winding && winding2 == winding) { - 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 = convexPolygons.Count - 1; i >= 0; i--) { - polygon = convexPolygons.Items[i]; - if (polygon.Count == 0) { - convexPolygons.RemoveAt(i); - polygonPool.Free(polygon); - polygonIndices = convexPolygonsIndices.Items[i]; - convexPolygonsIndices.RemoveAt(i); - polygonIndicesPool.Free(polygonIndices); - } - } - - return convexPolygons; - } - - static private bool IsConcave (int index, int vertexCount, float[] vertices, int[] 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]); - } - - static private bool PositiveArea (float p1x, float p1y, float p2x, float p2y, float p3x, float p3y) { - return p1x * (p3y - p2y) + p2x * (p1y - p3y) + p3x * (p2y - p1y) >= 0; - } - - static private int 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; - } - } +namespace Spine3_6_32 +{ + internal class Triangulator + { + private readonly ExposedList> convexPolygons = new ExposedList>(); + private readonly ExposedList> convexPolygonsIndices = new ExposedList>(); + + private readonly ExposedList indicesArray = new ExposedList(); + private readonly ExposedList isConcaveArray = new ExposedList(); + private readonly ExposedList triangles = new ExposedList(); + + private readonly Pool> polygonPool = new Pool>(); + private readonly Pool> polygonIndicesPool = new Pool>(); + + public ExposedList Triangulate(ExposedList verticesArray) + { + var vertices = verticesArray.Items; + int vertexCount = verticesArray.Count >> 1; + + var indicesArray = this.indicesArray; + indicesArray.Clear(); + int[] indices = indicesArray.Resize(vertexCount).Items; + for (int i = 0; i < vertexCount; i++) + indices[i] = i; + + var isConcaveArray = this.isConcaveArray; + bool[] isConcave = isConcaveArray.Resize(vertexCount).Items; + for (int i = 0, n = vertexCount; i < n; ++i) + isConcave[i] = IsConcave(i, vertexCount, vertices, indices); + + var triangles = this.triangles; + triangles.Clear(); + triangles.EnsureCapacity(Math.Max(0, vertexCount - 2) << 2); + + while (vertexCount > 3) + { + // Find ear tip. + int previous = vertexCount - 1, i = 0, next = 1; + + // outer: + while (true) + { + if (!isConcave[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 (int ii = (next + 1) % vertexCount; ii != previous; ii = (ii + 1) % vertexCount) + { + if (!isConcave[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 outer; // break outer; + } + } + } + break; + } + + if (next == 0) + { + do + { + if (!isConcave[i]) break; + i--; + } while (i > 0); + break; + } + + previous = i; + i = next; + next = (next + 1) % vertexCount; + } + outer: + + // Cut ear tip. + triangles.Add(indices[(vertexCount + i - 1) % vertexCount]); + triangles.Add(indices[i]); + triangles.Add(indices[(i + 1) % vertexCount]); + indicesArray.RemoveAt(i); + isConcaveArray.RemoveAt(i); + vertexCount--; + + int previousIndex = (vertexCount + i - 1) % vertexCount; + int nextIndex = i == vertexCount ? 0 : i; + isConcave[previousIndex] = IsConcave(previousIndex, vertexCount, vertices, indices); + isConcave[nextIndex] = IsConcave(nextIndex, vertexCount, vertices, indices); + } + + if (vertexCount == 3) + { + triangles.Add(indices[2]); + triangles.Add(indices[0]); + triangles.Add(indices[1]); + } + + return triangles; + } + + public ExposedList> Decompose(ExposedList verticesArray, ExposedList triangles) + { + var vertices = verticesArray.Items; + var convexPolygons = this.convexPolygons; + for (int i = 0, n = convexPolygons.Count; i < n; i++) + { + polygonPool.Free(convexPolygons.Items[i]); + } + convexPolygons.Clear(); + + var convexPolygonsIndices = this.convexPolygonsIndices; + for (int i = 0, n = convexPolygonsIndices.Count; i < n; i++) + { + polygonIndicesPool.Free(convexPolygonsIndices.Items[i]); + } + convexPolygonsIndices.Clear(); + + var polygonIndices = polygonIndicesPool.Obtain(); + polygonIndices.Clear(); + + var polygon = polygonPool.Obtain(); + polygon.Clear(); + + // Merge subsequent triangles if they form a triangle fan. + int fanBaseIndex = -1, lastWinding = 0; + int[] trianglesItems = triangles.Items; + for (int i = 0, n = triangles.Count; i < n; i += 3) + { + int t1 = trianglesItems[i] << 1, t2 = trianglesItems[i + 1] << 1, t3 = trianglesItems[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). + var merged = false; + if (fanBaseIndex == t1) + { + int o = polygon.Count - 4; + float[] p = polygon.Items; + 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.Count > 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.Count > 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 (int i = 0, n = convexPolygons.Count; i < n; i++) + { + polygonIndices = convexPolygonsIndices.Items[i]; + if (polygonIndices.Count == 0) continue; + int firstIndex = polygonIndices.Items[0]; + int lastIndex = polygonIndices.Items[polygonIndices.Count - 1]; + + polygon = convexPolygons.Items[i]; + int o = polygon.Count - 4; + float[] p = polygon.Items; + 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 winding = Winding(prevPrevX, prevPrevY, prevX, prevY, firstX, firstY); + + for (int ii = 0; ii < n; ii++) + { + if (ii == i) continue; + var otherIndices = convexPolygonsIndices.Items[ii]; + if (otherIndices.Count != 3) continue; + int otherFirstIndex = otherIndices.Items[0]; + int otherSecondIndex = otherIndices.Items[1]; + int otherLastIndex = otherIndices.Items[2]; + + var otherPoly = convexPolygons.Items[ii]; + float x3 = otherPoly.Items[otherPoly.Count - 2], y3 = otherPoly.Items[otherPoly.Count - 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 == winding && winding2 == winding) + { + 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 = convexPolygons.Count - 1; i >= 0; i--) + { + polygon = convexPolygons.Items[i]; + if (polygon.Count == 0) + { + convexPolygons.RemoveAt(i); + polygonPool.Free(polygon); + polygonIndices = convexPolygonsIndices.Items[i]; + convexPolygonsIndices.RemoveAt(i); + polygonIndicesPool.Free(polygonIndices); + } + } + + return convexPolygons; + } + + static private bool IsConcave(int index, int vertexCount, float[] vertices, int[] 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]); + } + + static private bool PositiveArea(float p1x, float p1y, float p2x, float p2y, float p3x, float p3y) + { + return p1x * (p3y - p2y) + p2x * (p1y - p3y) + p3x * (p2y - p1y) >= 0; + } + + static private int 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/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/XnaLoader/MeshBatcher.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/XnaLoader/MeshBatcher.cs index 118a0a4..67e53c8 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/XnaLoader/MeshBatcher.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/XnaLoader/MeshBatcher.cs @@ -31,156 +31,169 @@ using System; using System.Collections.Generic; -using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; -namespace Spine3_6_32 { - public struct VertexPositionColorTextureColor : IVertexType { - public Vector3 Position; - public Color Color; - public Vector2 TextureCoordinate; - public Color Color2; - - public readonly static VertexDeclaration VertexDeclaration = new VertexDeclaration - ( - new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0), - new VertexElement(12, VertexElementFormat.Color, VertexElementUsage.Color, 0), - new VertexElement(16, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0), - new VertexElement(24, VertexElementFormat.Color, VertexElementUsage.Color, 1) - ); - - VertexDeclaration IVertexType.VertexDeclaration { - get { return VertexDeclaration; } - } - } - - // #region License - // /* - // Microsoft Public License (Ms-PL) - // MonoGame - Copyright 2009 The MonoGame Team - // - // All rights reserved. - // - // This license governs use of the accompanying software. If you use the software, you accept this license. If you do not - // accept the license, do not use the software. - // - // 1. Definitions - // The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under - // U.S. copyright law. - // - // A "contribution" is the original software, or any additions or changes to the software. - // A "contributor" is any person that distributes its contribution under this license. - // "Licensed patents" are a contributor's patent claims that read directly on its contribution. - // - // 2. Grant of Rights - // (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, - // each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. - // (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, - // each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. - // - // 3. Conditions and Limitations - // (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. - // (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, - // your patent license from such contributor to the software ends automatically. - // (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution - // notices that are present in the software. - // (D) If you distribute any portion of the software in source code form, you may do so only under this license by including - // a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object - // code form, you may only do so under a license that complies with this license. - // (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees - // or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent - // permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular - // purpose and non-infringement. - // */ - // #endregion License - // - - /// Draws batched meshes. - public class MeshBatcher { - private readonly List items; - private readonly Queue freeItems; - private VertexPositionColorTextureColor[] vertexArray = { }; - private short[] triangles = { }; - - public MeshBatcher () { - items = new List(256); - freeItems = new Queue(256); - EnsureCapacity(256, 512); - } - - /// Returns a pooled MeshItem. - public MeshItem NextItem (int vertexCount, int triangleCount) { - MeshItem item = freeItems.Count > 0 ? freeItems.Dequeue() : new MeshItem(); - if (item.vertices.Length < vertexCount) item.vertices = new VertexPositionColorTextureColor[vertexCount]; - if (item.triangles.Length < triangleCount) item.triangles = new int[triangleCount]; - item.vertexCount = vertexCount; - item.triangleCount = triangleCount; - items.Add(item); - return item; - } - - private void EnsureCapacity (int vertexCount, int triangleCount) { - if (vertexArray.Length < vertexCount) vertexArray = new VertexPositionColorTextureColor[vertexCount]; - if (triangles.Length < triangleCount) triangles = new short[triangleCount]; - } - - public void Draw (GraphicsDevice device) { - if (items.Count == 0) return; - - int itemCount = items.Count; - int vertexCount = 0, triangleCount = 0; - for (int i = 0; i < itemCount; i++) { - MeshItem item = items[i]; - vertexCount += item.vertexCount; - triangleCount += item.triangleCount; - } - EnsureCapacity(vertexCount, triangleCount); - - vertexCount = 0; - triangleCount = 0; - Texture2D lastTexture = null; - for (int i = 0; i < itemCount; i++) { - MeshItem item = items[i]; - int itemVertexCount = item.vertexCount; - - if (item.texture != lastTexture || vertexCount + itemVertexCount > short.MaxValue) { - FlushVertexArray(device, vertexCount, triangleCount); - vertexCount = 0; - triangleCount = 0; - lastTexture = item.texture; - device.Textures[0] = lastTexture; - } - - int[] itemTriangles = item.triangles; - int itemTriangleCount = item.triangleCount; - for (int ii = 0, t = triangleCount; ii < itemTriangleCount; ii++, t++) - triangles[t] = (short)(itemTriangles[ii] + vertexCount); - triangleCount += itemTriangleCount; - - Array.Copy(item.vertices, 0, vertexArray, vertexCount, itemVertexCount); - vertexCount += itemVertexCount; - - item.texture = null; - freeItems.Enqueue(item); - } - FlushVertexArray(device, vertexCount, triangleCount); - items.Clear(); - } - - private void FlushVertexArray (GraphicsDevice device, int vertexCount, int triangleCount) { - if (vertexCount == 0) return; - device.DrawUserIndexedPrimitives( - PrimitiveType.TriangleList, - vertexArray, 0, vertexCount, - triangles, 0, triangleCount / 3, - VertexPositionColorTextureColor.VertexDeclaration); - } - } - - public class MeshItem { - public Texture2D texture; - public int vertexCount, triangleCount; - public VertexPositionColorTextureColor[] vertices = { }; - public int[] triangles = { }; - } +namespace Spine3_6_32 +{ + public struct VertexPositionColorTextureColor : IVertexType + { + public Vector3 Position; + public Color Color; + public Vector2 TextureCoordinate; + public Color Color2; + + public readonly static VertexDeclaration VertexDeclaration = new VertexDeclaration + ( + new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0), + new VertexElement(12, VertexElementFormat.Color, VertexElementUsage.Color, 0), + new VertexElement(16, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0), + new VertexElement(24, VertexElementFormat.Color, VertexElementUsage.Color, 1) + ); + + VertexDeclaration IVertexType.VertexDeclaration + { + get { return VertexDeclaration; } + } + } + + // #region License + // /* + // Microsoft Public License (Ms-PL) + // MonoGame - Copyright 2009 The MonoGame Team + // + // All rights reserved. + // + // This license governs use of the accompanying software. If you use the software, you accept this license. If you do not + // accept the license, do not use the software. + // + // 1. Definitions + // The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under + // U.S. copyright law. + // + // A "contribution" is the original software, or any additions or changes to the software. + // A "contributor" is any person that distributes its contribution under this license. + // "Licensed patents" are a contributor's patent claims that read directly on its contribution. + // + // 2. Grant of Rights + // (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, + // each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. + // (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, + // each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. + // + // 3. Conditions and Limitations + // (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. + // (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, + // your patent license from such contributor to the software ends automatically. + // (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution + // notices that are present in the software. + // (D) If you distribute any portion of the software in source code form, you may do so only under this license by including + // a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object + // code form, you may only do so under a license that complies with this license. + // (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees + // or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent + // permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular + // purpose and non-infringement. + // */ + // #endregion License + // + + /// Draws batched meshes. + public class MeshBatcher + { + private readonly List items; + private readonly Queue freeItems; + private VertexPositionColorTextureColor[] vertexArray = { }; + private short[] triangles = { }; + + public MeshBatcher() + { + items = new List(256); + freeItems = new Queue(256); + EnsureCapacity(256, 512); + } + + /// Returns a pooled MeshItem. + public MeshItem NextItem(int vertexCount, int triangleCount) + { + MeshItem item = freeItems.Count > 0 ? freeItems.Dequeue() : new MeshItem(); + if (item.vertices.Length < vertexCount) item.vertices = new VertexPositionColorTextureColor[vertexCount]; + if (item.triangles.Length < triangleCount) item.triangles = new int[triangleCount]; + item.vertexCount = vertexCount; + item.triangleCount = triangleCount; + items.Add(item); + return item; + } + + private void EnsureCapacity(int vertexCount, int triangleCount) + { + if (vertexArray.Length < vertexCount) vertexArray = new VertexPositionColorTextureColor[vertexCount]; + if (triangles.Length < triangleCount) triangles = new short[triangleCount]; + } + + public void Draw(GraphicsDevice device) + { + if (items.Count == 0) return; + + int itemCount = items.Count; + int vertexCount = 0, triangleCount = 0; + for (int i = 0; i < itemCount; i++) + { + MeshItem item = items[i]; + vertexCount += item.vertexCount; + triangleCount += item.triangleCount; + } + EnsureCapacity(vertexCount, triangleCount); + + vertexCount = 0; + triangleCount = 0; + Texture2D lastTexture = null; + for (int i = 0; i < itemCount; i++) + { + MeshItem item = items[i]; + int itemVertexCount = item.vertexCount; + + if (item.texture != lastTexture || vertexCount + itemVertexCount > short.MaxValue) + { + FlushVertexArray(device, vertexCount, triangleCount); + vertexCount = 0; + triangleCount = 0; + lastTexture = item.texture; + device.Textures[0] = lastTexture; + } + + int[] itemTriangles = item.triangles; + int itemTriangleCount = item.triangleCount; + for (int ii = 0, t = triangleCount; ii < itemTriangleCount; ii++, t++) + triangles[t] = (short)(itemTriangles[ii] + vertexCount); + triangleCount += itemTriangleCount; + + Array.Copy(item.vertices, 0, vertexArray, vertexCount, itemVertexCount); + vertexCount += itemVertexCount; + + item.texture = null; + freeItems.Enqueue(item); + } + FlushVertexArray(device, vertexCount, triangleCount); + items.Clear(); + } + + private void FlushVertexArray(GraphicsDevice device, int vertexCount, int triangleCount) + { + if (vertexCount == 0) return; + device.DrawUserIndexedPrimitives( + PrimitiveType.TriangleList, + vertexArray, 0, vertexCount, + triangles, 0, triangleCount / 3, + VertexPositionColorTextureColor.VertexDeclaration); + } + } + + public class MeshItem + { + public Texture2D texture; + public int vertexCount, triangleCount; + public VertexPositionColorTextureColor[] vertices = { }; + public int[] triangles = { }; + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/XnaLoader/SkeletonRenderer.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/XnaLoader/SkeletonRenderer.cs index d1050d5..c24b010 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/XnaLoader/SkeletonRenderer.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/XnaLoader/SkeletonRenderer.cs @@ -29,115 +29,125 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; -using System.Collections.Generic; -using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Spine3_6_32 +{ + /// Draws region and mesh attachments. + public class SkeletonRenderer + { + private const int TL = 0; + private const int TR = 1; + private const int BL = 2; + private const int BR = 3; + + SkeletonClipping clipper = new SkeletonClipping(); + GraphicsDevice device; + MeshBatcher batcher; + RasterizerState rasterizerState; + float[] vertices = new float[8]; + int[] quadTriangles = { 0, 1, 2, 2, 3, 0 }; + BlendState defaultBlendState; + + Effect effect; + public Effect Effect { get { return effect; } set { effect = value; } } + + private bool premultipliedAlpha; + public bool PremultipliedAlpha { get { return premultipliedAlpha; } set { premultipliedAlpha = value; } } + + public SkeletonRenderer(GraphicsDevice device) + { + this.device = device; + + batcher = new MeshBatcher(); + + var basicEffect = new BasicEffect(device); + basicEffect.World = Matrix.Identity; + basicEffect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); + basicEffect.TextureEnabled = true; + basicEffect.VertexColorEnabled = true; + effect = basicEffect; + + rasterizerState = new RasterizerState(); + rasterizerState.CullMode = CullMode.None; + + Bone.yDown = true; + } + + public void Begin() + { + defaultBlendState = premultipliedAlpha ? BlendState.AlphaBlend : BlendState.NonPremultiplied; -namespace Spine3_6_32 { - /// Draws region and mesh attachments. - public class SkeletonRenderer { - private const int TL = 0; - private const int TR = 1; - private const int BL = 2; - private const int BR = 3; - - SkeletonClipping clipper = new SkeletonClipping(); - GraphicsDevice device; - MeshBatcher batcher; - RasterizerState rasterizerState; - float[] vertices = new float[8]; - int[] quadTriangles = { 0, 1, 2, 2, 3, 0 }; - BlendState defaultBlendState; - - Effect effect; - public Effect Effect { get { return effect; } set { effect = value; } } - - private bool premultipliedAlpha; - public bool PremultipliedAlpha { get { return premultipliedAlpha; } set { premultipliedAlpha = value; } } - - public SkeletonRenderer (GraphicsDevice device) { - this.device = device; - - batcher = new MeshBatcher(); - - var basicEffect = new BasicEffect(device); - basicEffect.World = Matrix.Identity; - basicEffect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); - basicEffect.TextureEnabled = true; - basicEffect.VertexColorEnabled = true; - effect = basicEffect; - - rasterizerState = new RasterizerState(); - rasterizerState.CullMode = CullMode.None; - - Bone.yDown = true; - } - - public void Begin () { - defaultBlendState = premultipliedAlpha ? BlendState.AlphaBlend : BlendState.NonPremultiplied; - - device.RasterizerState = rasterizerState; - device.BlendState = defaultBlendState; - } - - public void End () { - foreach (EffectPass pass in effect.CurrentTechnique.Passes) { - pass.Apply(); - batcher.Draw(device); - } - } - - public void Draw(Skeleton skeleton) { - var drawOrder = skeleton.DrawOrder; - var drawOrderItems = skeleton.DrawOrder.Items; - float skeletonR = skeleton.R, skeletonG = skeleton.G, skeletonB = skeleton.B, skeletonA = skeleton.A; - Color color = new Color(); - - for (int i = 0, n = drawOrder.Count; i < n; i++) { - Slot slot = drawOrderItems[i]; - Attachment attachment = slot.Attachment; - - float attachmentColorR, attachmentColorG, attachmentColorB, attachmentColorA; - Texture2D texture = null; - int verticesCount = 0; - float[] vertices = this.vertices; - int indicesCount = 0; - int[] indices = null; - float[] uvs = null; - - if (attachment is RegionAttachment) { - RegionAttachment regionAttachment = (RegionAttachment)attachment; - attachmentColorR = regionAttachment.R; attachmentColorG = regionAttachment.G; attachmentColorB = regionAttachment.B; attachmentColorA = regionAttachment.A; - AtlasRegion region = (AtlasRegion)regionAttachment.RendererObject; - texture = (Texture2D)region.page.rendererObject; - verticesCount = 4; - regionAttachment.ComputeWorldVertices(slot.Bone, vertices, 0, 2); - indicesCount = 6; - indices = quadTriangles; - uvs = regionAttachment.UVs; - } - else if (attachment is MeshAttachment) { - MeshAttachment mesh = (MeshAttachment)attachment; - attachmentColorR = mesh.R; attachmentColorG = mesh.G; attachmentColorB = mesh.B; attachmentColorA = mesh.A; - AtlasRegion region = (AtlasRegion)mesh.RendererObject; - texture = (Texture2D)region.page.rendererObject; - int vertexCount = mesh.WorldVerticesLength; - if (vertices.Length < vertexCount) vertices = new float[vertexCount]; - verticesCount = vertexCount >> 1; - mesh.ComputeWorldVertices(slot, vertices); - indicesCount = mesh.Triangles.Length; - indices = mesh.Triangles; - uvs = mesh.UVs; - } - else if (attachment is ClippingAttachment) { - ClippingAttachment clip = (ClippingAttachment)attachment; - clipper.ClipStart(slot, clip); - continue; - } - else { - continue; - } + device.RasterizerState = rasterizerState; + device.BlendState = defaultBlendState; + } + + public void End() + { + foreach (EffectPass pass in effect.CurrentTechnique.Passes) + { + pass.Apply(); + batcher.Draw(device); + } + } + + public void Draw(Skeleton skeleton) + { + var drawOrder = skeleton.DrawOrder; + var drawOrderItems = skeleton.DrawOrder.Items; + float skeletonR = skeleton.R, skeletonG = skeleton.G, skeletonB = skeleton.B, skeletonA = skeleton.A; + Color color = new Color(); + + for (int i = 0, n = drawOrder.Count; i < n; i++) + { + Slot slot = drawOrderItems[i]; + Attachment attachment = slot.Attachment; + + float attachmentColorR, attachmentColorG, attachmentColorB, attachmentColorA; + Texture2D texture = null; + int verticesCount = 0; + float[] vertices = this.vertices; + int indicesCount = 0; + int[] indices = null; + float[] uvs = null; + + if (attachment is RegionAttachment) + { + RegionAttachment regionAttachment = (RegionAttachment)attachment; + attachmentColorR = regionAttachment.R; attachmentColorG = regionAttachment.G; attachmentColorB = regionAttachment.B; attachmentColorA = regionAttachment.A; + AtlasRegion region = (AtlasRegion)regionAttachment.RendererObject; + texture = (Texture2D)region.page.rendererObject; + verticesCount = 4; + regionAttachment.ComputeWorldVertices(slot.Bone, vertices, 0, 2); + indicesCount = 6; + indices = quadTriangles; + uvs = regionAttachment.UVs; + } + else if (attachment is MeshAttachment) + { + MeshAttachment mesh = (MeshAttachment)attachment; + attachmentColorR = mesh.R; attachmentColorG = mesh.G; attachmentColorB = mesh.B; attachmentColorA = mesh.A; + AtlasRegion region = (AtlasRegion)mesh.RendererObject; + texture = (Texture2D)region.page.rendererObject; + int vertexCount = mesh.WorldVerticesLength; + if (vertices.Length < vertexCount) vertices = new float[vertexCount]; + verticesCount = vertexCount >> 1; + mesh.ComputeWorldVertices(slot, vertices); + indicesCount = mesh.Triangles.Length; + indices = mesh.Triangles; + uvs = mesh.UVs; + } + else if (attachment is ClippingAttachment) + { + ClippingAttachment clip = (ClippingAttachment)attachment; + clipper.ClipStart(slot, clip); + continue; + } + else + { + continue; + } // set blend state BlendState blendState = new BlendState(); @@ -199,57 +209,63 @@ public void Draw(Skeleton skeleton) { // calculate color float a = skeletonA * slot.A * attachmentColorA; - if (premultipliedAlpha) { - color = new Color( - skeletonR * slot.R * attachmentColorR * a, - skeletonG * slot.G * attachmentColorG * a, - skeletonB * slot.B * attachmentColorB * a, a); - } - else { - color = new Color( - skeletonR * slot.R * attachmentColorR, - skeletonG * slot.G * attachmentColorG, - skeletonB * slot.B * attachmentColorB, a); - } - - Color darkColor = new Color(); - if (slot.HasSecondColor) { - darkColor = new Color(slot.R2, slot.G2, slot.B2); - } - - // clip - if (clipper.IsClipping()) { - clipper.ClipTriangles(vertices, verticesCount << 1, indices, indicesCount, uvs); - vertices = clipper.ClippedVertices.Items; - verticesCount = clipper.ClippedVertices.Count >> 1; - indices = clipper.ClippedTriangles.Items; - indicesCount = clipper.ClippedTriangles.Count; - uvs = clipper.ClippedUVs.Items; - } - - if (verticesCount == 0 || indicesCount == 0) - continue; - - // submit to batch - MeshItem item = batcher.NextItem(verticesCount, indicesCount); - item.texture = texture; - for (int ii = 0, nn = indicesCount; ii < nn; ii++) { - item.triangles[ii] = indices[ii]; - } - VertexPositionColorTextureColor[] itemVertices = item.vertices; - for (int ii = 0, v = 0, nn = verticesCount << 1; v < nn; ii++, v += 2) { - itemVertices[ii].Color = color; - itemVertices[ii].Color2 = darkColor; - itemVertices[ii].Position.X = vertices[v]; - itemVertices[ii].Position.Y = vertices[v + 1]; - itemVertices[ii].Position.Z = 0; - itemVertices[ii].TextureCoordinate.X = uvs[v]; - itemVertices[ii].TextureCoordinate.Y = uvs[v + 1]; - } - - clipper.ClipEnd(slot); - } - clipper.ClipEnd(); - } - } + if (premultipliedAlpha) + { + color = new Color( + skeletonR * slot.R * attachmentColorR * a, + skeletonG * slot.G * attachmentColorG * a, + skeletonB * slot.B * attachmentColorB * a, a); + } + else + { + color = new Color( + skeletonR * slot.R * attachmentColorR, + skeletonG * slot.G * attachmentColorG, + skeletonB * slot.B * attachmentColorB, a); + } + + Color darkColor = new Color(); + if (slot.HasSecondColor) + { + darkColor = new Color(slot.R2, slot.G2, slot.B2); + } + + // clip + if (clipper.IsClipping()) + { + clipper.ClipTriangles(vertices, verticesCount << 1, indices, indicesCount, uvs); + vertices = clipper.ClippedVertices.Items; + verticesCount = clipper.ClippedVertices.Count >> 1; + indices = clipper.ClippedTriangles.Items; + indicesCount = clipper.ClippedTriangles.Count; + uvs = clipper.ClippedUVs.Items; + } + + if (verticesCount == 0 || indicesCount == 0) + continue; + + // submit to batch + MeshItem item = batcher.NextItem(verticesCount, indicesCount); + item.texture = texture; + for (int ii = 0, nn = indicesCount; ii < nn; ii++) + { + item.triangles[ii] = indices[ii]; + } + VertexPositionColorTextureColor[] itemVertices = item.vertices; + for (int ii = 0, v = 0, nn = verticesCount << 1; v < nn; ii++, v += 2) + { + itemVertices[ii].Color = color; + itemVertices[ii].Color2 = darkColor; + itemVertices[ii].Position.X = vertices[v]; + itemVertices[ii].Position.Y = vertices[v + 1]; + itemVertices[ii].Position.Z = 0; + itemVertices[ii].TextureCoordinate.X = uvs[v]; + itemVertices[ii].TextureCoordinate.Y = uvs[v + 1]; + } + + clipper.ClipEnd(slot); + } + clipper.ClipEnd(); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/XnaLoader/XnaTextureLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/XnaLoader/XnaTextureLoader.cs index 1a462a1..c16f195 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/XnaLoader/XnaTextureLoader.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.32/XnaLoader/XnaTextureLoader.cs @@ -30,21 +30,23 @@ *****************************************************************************/ using System; -using System.IO; -using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; -namespace Spine3_6_32 { - public class XnaTextureLoader : TextureLoader { - GraphicsDevice device; +namespace Spine3_6_32 +{ + public class XnaTextureLoader : TextureLoader + { + GraphicsDevice device; - public XnaTextureLoader (GraphicsDevice device) { - this.device = device; - } + public XnaTextureLoader(GraphicsDevice device) + { + this.device = device; + } - public void Load (AtlasPage page, String path) { - Texture2D texture = Util.LoadTexture(device, path); - page.rendererObject = texture; + public void Load(AtlasPage page, String path) + { + Texture2D texture = Util.LoadTexture(device, path); + page.rendererObject = texture; if (page.width == 0 || page.height == 0) { page.width = texture.Width; @@ -52,8 +54,9 @@ public void Load (AtlasPage page, String path) { } } - public void Unload (Object texture) { - ((Texture2D)texture).Dispose(); - } - } + public void Unload(Object texture) + { + ((Texture2D)texture).Dispose(); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Animation.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Animation.cs index 8d11692..35cd637 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Animation.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Animation.cs @@ -29,1362 +29,1596 @@ *****************************************************************************/ using System; -using System.Collections.Generic; - -namespace Spine3_6_39 { - public class Animation { - internal ExposedList timelines; - internal float duration; - internal String name; - - public string Name { get { return name; } } - public ExposedList Timelines { get { return timelines; } set { timelines = value; } } - public float Duration { get { return duration; } set { duration = value; } } - - public Animation (string name, ExposedList timelines, float duration) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - if (timelines == null) throw new ArgumentNullException("timelines", "timelines cannot be null."); - this.name = name; - this.timelines = timelines; - this.duration = duration; - } - - /// Applies all the animation's timelines to the specified skeleton. - /// - public void Apply (Skeleton skeleton, float lastTime, float time, bool loop, ExposedList events, float alpha, MixPose pose, MixDirection direction) { - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - - if (loop && duration != 0) { - time %= duration; - if (lastTime > 0) lastTime %= duration; - } - - ExposedList timelines = this.timelines; - for (int i = 0, n = timelines.Count; i < n; i++) - timelines.Items[i].Apply(skeleton, lastTime, time, events, alpha, pose, direction); - } - - /// After the first and before the last entry. - internal static int BinarySearch (float[] values, float target, int step) { - int low = 0; - int high = values.Length / step - 2; - if (high == 0) return step; - int current = (int)((uint)high >> 1); - while (true) { - if (values[(current + 1) * step] <= target) - low = current + 1; - else - high = current; - if (low == high) return (low + 1) * step; - current = (int)((uint)(low + high) >> 1); - } - } - - /// After the first and before the last entry. - internal static int BinarySearch (float[] values, float target) { - int low = 0; - int high = values.Length - 2; - if (high == 0) return 1; - int current = (int)((uint)high >> 1); - while (true) { - if (values[(current + 1)] <= target) - low = current + 1; - else - high = current; - if (low == high) return (low + 1); - current = (int)((uint)(low + high) >> 1); - } - } - - internal static int LinearSearch (float[] values, float target, int step) { - for (int i = 0, last = values.Length - step; i <= last; i += step) - if (values[i] > target) return i; - return -1; - } - } - - public interface Timeline { - /// Sets the value(s) for the specified time. - /// The skeleton the timeline is being applied to. This provides access to the bones, slots, and other skeleton components the timeline may change. - /// 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). - /// The time within the animation. Most timelines find the key before and the key after this time so they can interpolate between the keys. - /// If any events are fired, they are added to this list. Can be null to ignore firing events or if the timeline does not fire events. May be null. - /// 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). - /// Controls how mixing is applied when alpha is than 1. - /// Indicates whether the timeline is mixing in or out. Used by timelines which perform instant transitions such as DrawOrderTimeline and AttachmentTimeline. - void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, MixPose pose, MixDirection direction); - int PropertyId { get; } - } - - /// - /// Controls how a timeline is mixed with the setup or current pose. - /// - public enum MixPose { - /// The timeline value is mixed with the setup pose (the current pose is not used). - Setup, - /// The timeline value is mixed with the current pose. The setup pose is used as the timeline value before the first key, - /// except for timelines which perform instant transitions, such as DrawOrderTimeline or AttachmentTimeline. - Current, - /// The timeline value is mixed with the current pose. No change is made before the first key (the current pose is kept until the first key). - CurrentLayered - } - - /// - /// 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). - /// - public enum MixDirection { - In, - Out - } - - internal enum TimelineType { - Rotate = 0, Translate, Scale, Shear, // - Attachment, Color, Deform, // - Event, DrawOrder, // - IkConstraint, TransformConstraint, // - PathConstraintPosition, PathConstraintSpacing, PathConstraintMix, // - TwoColor - } - - /// Base class for frames that use an interpolation bezier curve. - abstract public class CurveTimeline : Timeline { - protected const float LINEAR = 0, STEPPED = 1, BEZIER = 2; - protected const int BEZIER_SIZE = 10 * 2 - 1; - - private float[] curves; // type, x, y, ... - public int FrameCount { get { return curves.Length / BEZIER_SIZE + 1; } } - - public CurveTimeline (int frameCount) { - if (frameCount <= 0) throw new ArgumentException("frameCount must be > 0: " + frameCount, "frameCount"); - curves = new float[(frameCount - 1) * BEZIER_SIZE]; - } - - abstract public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction); - - abstract public int PropertyId { get; } - - public void SetLinear (int frameIndex) { - curves[frameIndex * BEZIER_SIZE] = LINEAR; - } - - public void SetStepped (int frameIndex) { - curves[frameIndex * BEZIER_SIZE] = STEPPED; - } - - /// Sets the control handle positions for an interpolation bezier curve used to transition from this keyframe to the next. - /// cx1 and cx2 are from 0 to 1, representing the percent of time between the two keyframes. cy1 and cy2 are the percent of - /// the difference between the keyframe's values. - public void SetCurve (int frameIndex, float cx1, float cy1, float cx2, float cy2) { - float tmpx = (-cx1 * 2 + cx2) * 0.03f, tmpy = (-cy1 * 2 + cy2) * 0.03f; - float dddfx = ((cx1 - cx2) * 3 + 1) * 0.006f, dddfy = ((cy1 - cy2) * 3 + 1) * 0.006f; - float ddfx = tmpx * 2 + dddfx, ddfy = tmpy * 2 + dddfy; - float dfx = cx1 * 0.3f + tmpx + dddfx * 0.16666667f, dfy = cy1 * 0.3f + tmpy + dddfy * 0.16666667f; - - int i = frameIndex * BEZIER_SIZE; - float[] curves = this.curves; - curves[i++] = BEZIER; - - float x = dfx, y = dfy; - for (int n = i + BEZIER_SIZE - 1; i < n; i += 2) { - curves[i] = x; - curves[i + 1] = y; - dfx += ddfx; - dfy += ddfy; - ddfx += dddfx; - ddfy += dddfy; - x += dfx; - y += dfy; - } - } - - public float GetCurvePercent (int frameIndex, float percent) { - percent = MathUtils.Clamp (percent, 0, 1); - float[] curves = this.curves; - int i = frameIndex * BEZIER_SIZE; - float type = curves[i]; - if (type == LINEAR) return percent; - if (type == STEPPED) return 0; - i++; - float x = 0; - for (int start = i, n = i + BEZIER_SIZE - 1; i < n; i += 2) { - x = curves[i]; - if (x >= percent) { - float prevX, prevY; - if (i == start) { - prevX = 0; - prevY = 0; - } else { - prevX = curves[i - 2]; - prevY = curves[i - 1]; - } - return prevY + (curves[i + 1] - prevY) * (percent - prevX) / (x - prevX); - } - } - float y = curves[i - 1]; - return y + (1 - y) * (percent - x) / (1 - x); // Last point is 1,1. - } - public float GetCurveType (int frameIndex) { - return curves[frameIndex * BEZIER_SIZE]; - } - } - - public class RotateTimeline : CurveTimeline { - public const int ENTRIES = 2; - internal const int PREV_TIME = -2, PREV_ROTATION = -1; - internal const int ROTATION = 1; - - internal int boneIndex; - internal float[] frames; - - public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, angle, ... - - override public int PropertyId { - get { return ((int)TimelineType.Rotate << 24) + boneIndex; } - } - - public RotateTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount << 1]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, float degrees) { - frameIndex <<= 1; - frames[frameIndex] = time; - frames[frameIndex + ROTATION] = degrees; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - - float[] frames = this.frames; - if (time < frames[0]) { - switch (pose) { - case MixPose.Setup: - bone.rotation = bone.data.rotation; - return; - case MixPose.Current: - float rr = bone.data.rotation - bone.rotation; - rr -= (16384 - (int)(16384.499999999996 - rr / 360)) * 360; - bone.rotation += rr * alpha; - return; - } - return; - } - - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - if (pose == MixPose.Setup) { - bone.rotation = bone.data.rotation + frames[frames.Length + PREV_ROTATION] * alpha; - } else { - float rr = bone.data.rotation + frames[frames.Length + PREV_ROTATION] - bone.rotation; - rr -= (16384 - (int)(16384.499999999996 - rr / 360)) * 360; // Wrap within -180 and 180. - bone.rotation += rr * alpha; - } - return; - } - - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - float prevRotation = frames[frame + PREV_ROTATION]; - float frameTime = frames[frame]; - float percent = GetCurvePercent((frame >> 1) - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - float r = frames[frame + ROTATION] - prevRotation; - r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; - r = prevRotation + r * percent; - if (pose == MixPose.Setup) { - r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; - bone.rotation = bone.data.rotation + r * alpha; - } else { - r = bone.data.rotation + r - bone.rotation; - r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; - bone.rotation += r * alpha; - } - } - } - - public class TranslateTimeline : CurveTimeline { - public const int ENTRIES = 3; - protected const int PREV_TIME = -3, PREV_X = -2, PREV_Y = -1; - protected const int X = 1, Y = 2; - - internal int boneIndex; - internal float[] frames; - - public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, value, value, ... - - override public int PropertyId { - get { return ((int)TimelineType.Translate << 24) + boneIndex; } - } - - public TranslateTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, float x, float y) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + X] = x; - frames[frameIndex + Y] = y; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - - float[] frames = this.frames; - if (time < frames[0]) { - switch (pose) { - case MixPose.Setup: - bone.x = bone.data.x; - bone.y = bone.data.y; - return; - case MixPose.Current: - bone.x += (bone.data.x - bone.x) * alpha; - bone.y += (bone.data.y - bone.y) * alpha; - return; - } - return; - } - - float x, y; - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - x = frames[frames.Length + PREV_X]; - y = frames[frames.Length + PREV_Y]; - } else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - x = frames[frame + PREV_X]; - y = frames[frame + PREV_Y]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - x += (frames[frame + X] - x) * percent; - y += (frames[frame + Y] - y) * percent; - } - if (pose == MixPose.Setup) { - bone.x = bone.data.x + x * alpha; - bone.y = bone.data.y + y * alpha; - } else { - bone.x += (bone.data.x + x - bone.x) * alpha; - bone.y += (bone.data.y + y - bone.y) * alpha; - } - } - } - - public class ScaleTimeline : TranslateTimeline { - override public int PropertyId { - get { return ((int)TimelineType.Scale << 24) + boneIndex; } - } - - public ScaleTimeline (int frameCount) - : base(frameCount) { - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - - float[] frames = this.frames; - if (time < frames[0]) { - switch (pose) { - case MixPose.Setup: - bone.scaleX = bone.data.scaleX; - bone.scaleY = bone.data.scaleY; - return; - case MixPose.Current: - bone.scaleX += (bone.data.scaleX - bone.scaleX) * alpha; - bone.scaleY += (bone.data.scaleY - bone.scaleY) * alpha; - return; - } - return; - } - - float x, y; - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - x = frames[frames.Length + PREV_X] * bone.data.scaleX; - y = frames[frames.Length + PREV_Y] * bone.data.scaleY; - } else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - x = frames[frame + PREV_X]; - y = frames[frame + PREV_Y]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - x = (x + (frames[frame + X] - x) * percent) * bone.data.scaleX; - y = (y + (frames[frame + Y] - y) * percent) * bone.data.scaleY; - } - if (alpha == 1) { - bone.scaleX = x; - bone.scaleY = y; - } else { - float bx, by; - if (pose == MixPose.Setup) { - bx = bone.data.scaleX; - by = bone.data.scaleY; - } else { - bx = bone.scaleX; - by = bone.scaleY; - } - // Mixing out uses sign of setup or current pose, else use sign of key. - if (direction == MixDirection.Out) { - x = (x >= 0 ? x : -x) * (bx >= 0 ? 1 : -1); - y = (y >= 0 ? y : -y) * (by >= 0 ? 1 : -1); - } else { - bx = (bx >= 0 ? bx : -bx) * (x >= 0 ? 1 : -1); - by = (by >= 0 ? by : -by) * (y >= 0 ? 1 : -1); - } - bone.scaleX = bx + (x - bx) * alpha; - bone.scaleY = by + (y - by) * alpha; - } - } - } - - public class ShearTimeline : TranslateTimeline { - override public int PropertyId { - get { return ((int)TimelineType.Shear << 24) + boneIndex; } - } - - public ShearTimeline (int frameCount) - : base(frameCount) { - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - float[] frames = this.frames; - if (time < frames[0]) { - switch (pose) { - case MixPose.Setup: - bone.shearX = bone.data.shearX; - bone.shearY = bone.data.shearY; - return; - case MixPose.Current: - bone.shearX += (bone.data.shearX - bone.shearX) * alpha; - bone.shearY += (bone.data.shearY - bone.shearY) * alpha; - return; - } - return; - } - - float x, y; - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - x = frames[frames.Length + PREV_X]; - y = frames[frames.Length + PREV_Y]; - } else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - x = frames[frame + PREV_X]; - y = frames[frame + PREV_Y]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - x = x + (frames[frame + X] - x) * percent; - y = y + (frames[frame + Y] - y) * percent; - } - if (pose == MixPose.Setup) { - bone.shearX = bone.data.shearX + x * alpha; - bone.shearY = bone.data.shearY + y * alpha; - } else { - bone.shearX += (bone.data.shearX + x - bone.shearX) * alpha; - bone.shearY += (bone.data.shearY + y - bone.shearY) * alpha; - } - } - } - - public class ColorTimeline : CurveTimeline { - public const int ENTRIES = 5; - protected const int PREV_TIME = -5, PREV_R = -4, PREV_G = -3, PREV_B = -2, PREV_A = -1; - protected const int R = 1, G = 2, B = 3, A = 4; - - internal int slotIndex; - internal float[] frames; - - public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, r, g, b, a, ... - - override public int PropertyId { - get { return ((int)TimelineType.Color << 24) + slotIndex; } - } - - public ColorTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, float r, float g, float b, float a) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + R] = r; - frames[frameIndex + G] = g; - frames[frameIndex + B] = b; - frames[frameIndex + A] = a; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { - Slot slot = skeleton.slots.Items[slotIndex]; - float[] frames = this.frames; - if (time < frames[0]) { - var slotData = slot.data; - switch (pose) { - case MixPose.Setup: - slot.r = slotData.r; - slot.g = slotData.g; - slot.b = slotData.b; - slot.a = slotData.a; - return; - case MixPose.Current: - slot.r += (slot.r - slotData.r) * alpha; - slot.g += (slot.g - slotData.g) * alpha; - slot.b += (slot.b - slotData.b) * alpha; - slot.a += (slot.a - slotData.a) * alpha; - return; - } - return; - } - - float r, g, b, a; - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - int i = frames.Length; - r = frames[i + PREV_R]; - g = frames[i + PREV_G]; - b = frames[i + PREV_B]; - a = frames[i + PREV_A]; - } else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - r = frames[frame + PREV_R]; - g = frames[frame + PREV_G]; - b = frames[frame + PREV_B]; - a = frames[frame + PREV_A]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - r += (frames[frame + R] - r) * percent; - g += (frames[frame + G] - g) * percent; - b += (frames[frame + B] - b) * percent; - a += (frames[frame + A] - a) * percent; - } - if (alpha == 1) { - slot.r = r; - slot.g = g; - slot.b = b; - slot.a = a; - } else { - float br, bg, bb, ba; - if (pose == MixPose.Setup) { - br = slot.data.r; - bg = slot.data.g; - bb = slot.data.b; - ba = slot.data.a; - } else { - br = slot.r; - bg = slot.g; - bb = slot.b; - ba = slot.a; - } - slot.r = br + ((r - br) * alpha); - slot.g = bg + ((g - bg) * alpha); - slot.b = bb + ((b - bb) * alpha); - slot.a = ba + ((a - ba) * alpha); - } - } - } - - public class TwoColorTimeline : CurveTimeline { - public const int ENTRIES = 8; - protected const int PREV_TIME = -8, PREV_R = -7, PREV_G = -6, PREV_B = -5, PREV_A = -4; - protected const int PREV_R2 = -3, PREV_G2 = -2, PREV_B2 = -1; - protected const int R = 1, G = 2, B = 3, A = 4, R2 = 5, G2 = 6, B2 = 7; - - internal float[] frames; // time, r, g, b, a, r2, g2, b2, ... - public float[] Frames { get { return frames; } } - - internal int slotIndex; - public int SlotIndex { - get { return slotIndex; } - set { - if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0."); - slotIndex = value; - } - } - - override public int PropertyId { - get { return ((int)TimelineType.TwoColor << 24) + slotIndex; } - } - - public TwoColorTimeline (int frameCount) : - base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, float r, float g, float b, float a, float r2, float g2, float b2) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + R] = r; - frames[frameIndex + G] = g; - frames[frameIndex + B] = b; - frames[frameIndex + A] = a; - frames[frameIndex + R2] = r2; - frames[frameIndex + G2] = g2; - frames[frameIndex + B2] = b2; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { - Slot slot = skeleton.slots.Items[slotIndex]; - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - var slotData = slot.data; - switch (pose) { - case MixPose.Setup: - // slot.color.set(slot.data.color); - // slot.darkColor.set(slot.data.darkColor); - slot.r = slotData.r; - slot.g = slotData.g; - slot.b = slotData.b; - slot.a = slotData.a; - slot.r2 = slotData.r2; - slot.g2 = slotData.g2; - slot.b2 = slotData.b2; - return; - case MixPose.Current: - slot.r += (slot.r - slotData.r) * alpha; - slot.g += (slot.g - slotData.g) * alpha; - slot.b += (slot.b - slotData.b) * alpha; - slot.a += (slot.a - slotData.a) * alpha; - slot.r2 += (slot.r2 - slotData.r2) * alpha; - slot.g2 += (slot.g2 - slotData.g2) * alpha; - slot.b2 += (slot.b2 - slotData.b2) * alpha; - return; - } - return; - } - - float r, g, b, a, r2, g2, b2; - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - int i = frames.Length; - r = frames[i + PREV_R]; - g = frames[i + PREV_G]; - b = frames[i + PREV_B]; - a = frames[i + PREV_A]; - r2 = frames[i + PREV_R2]; - g2 = frames[i + PREV_G2]; - b2 = frames[i + PREV_B2]; - } else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - r = frames[frame + PREV_R]; - g = frames[frame + PREV_G]; - b = frames[frame + PREV_B]; - a = frames[frame + PREV_A]; - r2 = frames[frame + PREV_R2]; - g2 = frames[frame + PREV_G2]; - b2 = frames[frame + PREV_B2]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - r += (frames[frame + R] - r) * percent; - g += (frames[frame + G] - g) * percent; - b += (frames[frame + B] - b) * percent; - a += (frames[frame + A] - a) * percent; - r2 += (frames[frame + R2] - r2) * percent; - g2 += (frames[frame + G2] - g2) * percent; - b2 += (frames[frame + B2] - b2) * percent; - } - if (alpha == 1) { - slot.r = r; - slot.g = g; - slot.b = b; - slot.a = a; - slot.r2 = r2; - slot.g2 = g2; - slot.b2 = b2; - } else { - float br, bg, bb, ba, br2, bg2, bb2; - if (pose == MixPose.Setup) { - br = slot.data.r; - bg = slot.data.g; - bb = slot.data.b; - ba = slot.data.a; - br2 = slot.data.r2; - bg2 = slot.data.g2; - bb2 = slot.data.b2; - } else { - br = slot.r; - bg = slot.g; - bb = slot.b; - ba = slot.a; - br2 = slot.r2; - bg2 = slot.g2; - bb2 = slot.b2; - } - slot.r = br + ((r - br) * alpha); - slot.g = bg + ((g - bg) * alpha); - slot.b = bb + ((b - bb) * alpha); - slot.a = ba + ((a - ba) * alpha); - slot.r2 = br2 + ((r2 - br2) * alpha); - slot.g2 = bg2 + ((g2 - bg2) * alpha); - slot.b2 = bb2 + ((b2 - bb2) * alpha); - } - } - - } - - public class AttachmentTimeline : Timeline { - internal int slotIndex; - internal float[] frames; - private String[] attachmentNames; - - public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, ... - public String[] AttachmentNames { get { return attachmentNames; } set { attachmentNames = value; } } - public int FrameCount { get { return frames.Length; } } - - public int PropertyId { - get { return ((int)TimelineType.Attachment << 24) + slotIndex; } - } - - public AttachmentTimeline (int frameCount) { - frames = new float[frameCount]; - attachmentNames = new String[frameCount]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, String attachmentName) { - frames[frameIndex] = time; - attachmentNames[frameIndex] = attachmentName; - } - - public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { - string attachmentName; - Slot slot = skeleton.slots.Items[slotIndex]; - if (direction == MixDirection.Out && pose == MixPose.Setup) { - attachmentName = slot.data.attachmentName; - slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); - return; - } - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - if (pose == MixPose.Setup) { - attachmentName = slot.data.attachmentName; - slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); - } - return; - } - - int frameIndex; - if (time >= frames[frames.Length - 1]) // Time is after last frame. - frameIndex = frames.Length - 1; - else - frameIndex = Animation.BinarySearch(frames, time, 1) - 1; - - attachmentName = attachmentNames[frameIndex]; - slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); - } - } - - public class DeformTimeline : CurveTimeline { - static float[] zeros = new float[64]; - - internal int slotIndex; - internal float[] frames; - internal float[][] frameVertices; - internal VertexAttachment attachment; - - public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, ... - public float[][] Vertices { get { return frameVertices; } set { frameVertices = value; } } - public VertexAttachment Attachment { get { return attachment; } set { attachment = value; } } - - override public int PropertyId { - get { return ((int)TimelineType.Deform << 24) + attachment.id + slotIndex; } - } - - public DeformTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount]; - frameVertices = new float[frameCount][]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, float[] vertices) { - frames[frameIndex] = time; - frameVertices[frameIndex] = vertices; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { - Slot slot = skeleton.slots.Items[slotIndex]; - VertexAttachment vertexAttachment = slot.attachment as VertexAttachment; - if (vertexAttachment == null || !vertexAttachment.ApplyDeform(attachment)) return; - - var verticesArray = slot.attachmentVertices; - float[][] frameVertices = this.frameVertices; - int vertexCount = frameVertices[0].Length; - if (verticesArray.Capacity < vertexCount) verticesArray.Capacity = vertexCount; // verticesArray.SetSize(vertexCount) // Ensure size and preemptively set count. - verticesArray.Count = vertexCount; - float[] vertices = verticesArray.Items; - - float[] frames = this.frames; - if (time < frames[0]) { - - switch (pose) { - case MixPose.Setup: - float[] zeroVertices; - if (vertexAttachment.bones == null) { - // Unweighted vertex positions (setup pose). - zeroVertices = vertexAttachment.vertices; - } else { - // Weighted deform offsets (zeros). - zeroVertices = DeformTimeline.zeros; - if (zeroVertices.Length < vertexCount) DeformTimeline.zeros = zeroVertices = new float[vertexCount]; - } - Array.Copy(zeroVertices, 0, vertices, 0, vertexCount); - return; - case MixPose.Current: - if (alpha == 1) return; - if (vertexAttachment.bones == null) { - // Unweighted vertex positions. - float[] setupVertices = vertexAttachment.vertices; - for (int i = 0; i < vertexCount; i++) - vertices[i] += (setupVertices[i] - vertices[i]) * alpha; - } else { - // Weighted deform offsets. - alpha = 1 - alpha; - for (int i = 0; i < vertexCount; i++) - vertices[i] *= alpha; - } - return; - default: - return; - } - - } - - if (time >= frames[frames.Length - 1]) { // Time is after last frame. - float[] lastVertices = frameVertices[frames.Length - 1]; - if (alpha == 1) { - // Vertex positions or deform offsets, no alpha. - Array.Copy(lastVertices, 0, vertices, 0, vertexCount); - } else if (pose == MixPose.Setup) { - if (vertexAttachment.bones == null) { - // Unweighted vertex positions, with alpha. - float[] setupVertices = vertexAttachment.vertices; - for (int i = 0; i < vertexCount; i++) { - float setup = setupVertices[i]; - vertices[i] = setup + (lastVertices[i] - setup) * alpha; - } - } else { - // Weighted deform offsets, with alpha. - for (int i = 0; i < vertexCount; i++) - vertices[i] = lastVertices[i] * alpha; - } - } else { - // Vertex positions or deform offsets, with alpha. - for (int i = 0; i < vertexCount; i++) - vertices[i] += (lastVertices[i] - vertices[i]) * alpha; - } - return; - } - - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time); - float[] prevVertices = frameVertices[frame - 1]; - float[] nextVertices = frameVertices[frame]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame - 1, 1 - (time - frameTime) / (frames[frame - 1] - frameTime)); - - if (alpha == 1) { - // Vertex positions or deform offsets, no alpha. - for (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - vertices[i] = prev + (nextVertices[i] - prev) * percent; - } - } else if (pose == MixPose.Setup) { - if (vertexAttachment.bones == null) { - // Unweighted vertex positions, with alpha. - var setupVertices = vertexAttachment.vertices; - for (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i], setup = setupVertices[i]; - vertices[i] = setup + (prev + (nextVertices[i] - prev) * percent - setup) * alpha; - } - } else { - // Weighted deform offsets, with alpha. - for (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - vertices[i] = (prev + (nextVertices[i] - prev) * percent) * alpha; - } - } - } else { - // Vertex positions or deform offsets, with alpha. - for (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - vertices[i] += (prev + (nextVertices[i] - prev) * percent - vertices[i]) * alpha; - } - } - } - } - - public class EventTimeline : Timeline { - internal float[] frames; - private Event[] events; - - public float[] Frames { get { return frames; } set { frames = value; } } // time, ... - public Event[] Events { get { return events; } set { events = value; } } - public int FrameCount { get { return frames.Length; } } - - public int PropertyId { - get { return ((int)TimelineType.Event << 24); } - } - - public EventTimeline (int frameCount) { - frames = new float[frameCount]; - events = new Event[frameCount]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, Event e) { - frames[frameIndex] = e.Time; - events[frameIndex] = e; - } - - /// Fires events for frames > lastTime and <= time. - public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { - if (firedEvents == null) return; - float[] frames = this.frames; - int frameCount = frames.Length; - - if (lastTime > time) { // Fire events after last time for looped animations. - Apply(skeleton, lastTime, int.MaxValue, firedEvents, alpha, pose, direction); - lastTime = -1f; - } else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame. - return; - if (time < frames[0]) return; // Time is before first frame. - - int frame; - if (lastTime < frames[0]) - frame = 0; - else { - frame = Animation.BinarySearch(frames, lastTime); - float frameTime = frames[frame]; - while (frame > 0) { // Fire multiple events with the same frame. - if (frames[frame - 1] != frameTime) break; - frame--; - } - } - for (; frame < frameCount && time >= frames[frame]; frame++) - firedEvents.Add(events[frame]); - } - } - - public class DrawOrderTimeline : Timeline { - internal float[] frames; - private int[][] drawOrders; - - public float[] Frames { get { return frames; } set { frames = value; } } // time, ... - public int[][] DrawOrders { get { return drawOrders; } set { drawOrders = value; } } - public int FrameCount { get { return frames.Length; } } - - public int PropertyId { - get { return ((int)TimelineType.DrawOrder << 24); } - } - - public DrawOrderTimeline (int frameCount) { - frames = new float[frameCount]; - drawOrders = new int[frameCount][]; - } - - /// Sets the time and value of the specified keyframe. - /// May be null to use bind pose draw order. - public void SetFrame (int frameIndex, float time, int[] drawOrder) { - frames[frameIndex] = time; - drawOrders[frameIndex] = drawOrder; - } - - public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { - ExposedList drawOrder = skeleton.drawOrder; - ExposedList slots = skeleton.slots; - if (direction == MixDirection.Out && pose == MixPose.Setup) { - Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count); - return; - } - - float[] frames = this.frames; - if (time < frames[0]) { - if (pose == MixPose.Setup) Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count); - return; - } - - int frame; - if (time >= frames[frames.Length - 1]) // Time is after last frame. - frame = frames.Length - 1; - else - frame = Animation.BinarySearch(frames, time) - 1; - - int[] drawOrderToSetupIndex = drawOrders[frame]; - if (drawOrderToSetupIndex == null) { - drawOrder.Clear(); - for (int i = 0, n = slots.Count; i < n; i++) - drawOrder.Add(slots.Items[i]); - } else { - var drawOrderItems = drawOrder.Items; - var slotsItems = slots.Items; - for (int i = 0, n = drawOrderToSetupIndex.Length; i < n; i++) - drawOrderItems[i] = slotsItems[drawOrderToSetupIndex[i]]; - } - } - } - - public class IkConstraintTimeline : CurveTimeline { - public const int ENTRIES = 3; - private const int PREV_TIME = -3, PREV_MIX = -2, PREV_BEND_DIRECTION = -1; - private const int MIX = 1, BEND_DIRECTION = 2; - - internal int ikConstraintIndex; - internal float[] frames; - - public int IkConstraintIndex { get { return ikConstraintIndex; } set { ikConstraintIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, mix, bendDirection, ... - - override public int PropertyId { - get { return ((int)TimelineType.IkConstraint << 24) + ikConstraintIndex; } - } - - public IkConstraintTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - /// Sets the time, mix and bend direction of the specified keyframe. - public void SetFrame (int frameIndex, float time, float mix, int bendDirection) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + MIX] = mix; - frames[frameIndex + BEND_DIRECTION] = bendDirection; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { - IkConstraint constraint = skeleton.ikConstraints.Items[ikConstraintIndex]; - float[] frames = this.frames; - if (time < frames[0]) { - switch (pose) { - case MixPose.Setup: - constraint.mix = constraint.data.mix; - constraint.bendDirection = constraint.data.bendDirection; - return; - case MixPose.Current: - constraint.mix += (constraint.data.mix - constraint.mix) * alpha; - constraint.bendDirection = constraint.data.bendDirection; - return; - } - return; - } - - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - if (pose == MixPose.Setup) { - constraint.mix = constraint.data.mix + (frames[frames.Length + PREV_MIX] - constraint.data.mix) * alpha; - constraint.bendDirection = direction == MixDirection.Out ? constraint.data.bendDirection - : (int)frames[frames.Length + PREV_BEND_DIRECTION]; - } else { - constraint.mix += (frames[frames.Length + PREV_MIX] - constraint.mix) * alpha; - if (direction == MixDirection.In) constraint.bendDirection = (int)frames[frames.Length + PREV_BEND_DIRECTION]; - } - return; - } - - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - float mix = frames[frame + PREV_MIX]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - if (pose == MixPose.Setup) { - constraint.mix = constraint.data.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.data.mix) * alpha; - constraint.bendDirection = direction == MixDirection.Out ? constraint.data.bendDirection : (int)frames[frame + PREV_BEND_DIRECTION]; - } else { - constraint.mix += (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha; - if (direction == MixDirection.In) constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION]; - } - } - } - - public class TransformConstraintTimeline : CurveTimeline { - public const int ENTRIES = 5; - private const int PREV_TIME = -5, PREV_ROTATE = -4, PREV_TRANSLATE = -3, PREV_SCALE = -2, PREV_SHEAR = -1; - private const int ROTATE = 1, TRANSLATE = 2, SCALE = 3, SHEAR = 4; - - internal int transformConstraintIndex; - internal float[] frames; - - public int TransformConstraintIndex { get { return transformConstraintIndex; } set { transformConstraintIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, scale mix, shear mix, ... - - override public int PropertyId { - get { return ((int)TimelineType.TransformConstraint << 24) + transformConstraintIndex; } - } - - public TransformConstraintTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - public void SetFrame (int frameIndex, float time, float rotateMix, float translateMix, float scaleMix, float shearMix) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + ROTATE] = rotateMix; - frames[frameIndex + TRANSLATE] = translateMix; - frames[frameIndex + SCALE] = scaleMix; - frames[frameIndex + SHEAR] = shearMix; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { - TransformConstraint constraint = skeleton.transformConstraints.Items[transformConstraintIndex]; - float[] frames = this.frames; - if (time < frames[0]) { - var data = constraint.data; - switch (pose) { - case MixPose.Setup: - constraint.rotateMix = data.rotateMix; - constraint.translateMix = data.translateMix; - constraint.scaleMix = data.scaleMix; - constraint.shearMix = data.shearMix; - return; - case MixPose.Current: - constraint.rotateMix += (data.rotateMix - constraint.rotateMix) * alpha; - constraint.translateMix += (data.translateMix - constraint.translateMix) * alpha; - constraint.scaleMix += (data.scaleMix - constraint.scaleMix) * alpha; - constraint.shearMix += (data.shearMix - constraint.shearMix) * alpha; - return; - } - return; - } - - float rotate, translate, scale, shear; - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - int i = frames.Length; - rotate = frames[i + PREV_ROTATE]; - translate = frames[i + PREV_TRANSLATE]; - scale = frames[i + PREV_SCALE]; - shear = frames[i + PREV_SHEAR]; - } else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - rotate = frames[frame + PREV_ROTATE]; - translate = frames[frame + PREV_TRANSLATE]; - scale = frames[frame + PREV_SCALE]; - shear = frames[frame + PREV_SHEAR]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - rotate += (frames[frame + ROTATE] - rotate) * percent; - translate += (frames[frame + TRANSLATE] - translate) * percent; - scale += (frames[frame + SCALE] - scale) * percent; - shear += (frames[frame + SHEAR] - shear) * percent; - } - if (pose == MixPose.Setup) { - TransformConstraintData data = constraint.data; - constraint.rotateMix = data.rotateMix + (rotate - data.rotateMix) * alpha; - constraint.translateMix = data.translateMix + (translate - data.translateMix) * alpha; - constraint.scaleMix = data.scaleMix + (scale - data.scaleMix) * alpha; - constraint.shearMix = data.shearMix + (shear - data.shearMix) * alpha; - } else { - constraint.rotateMix += (rotate - constraint.rotateMix) * alpha; - constraint.translateMix += (translate - constraint.translateMix) * alpha; - constraint.scaleMix += (scale - constraint.scaleMix) * alpha; - constraint.shearMix += (shear - constraint.shearMix) * alpha; - } - } - } - - public class PathConstraintPositionTimeline : CurveTimeline { - public const int ENTRIES = 2; - protected const int PREV_TIME = -2, PREV_VALUE = -1; - protected const int VALUE = 1; - - internal int pathConstraintIndex; - internal float[] frames; - - override public int PropertyId { - get { return ((int)TimelineType.PathConstraintPosition << 24) + pathConstraintIndex; } - } - - public PathConstraintPositionTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - public int PathConstraintIndex { get { return pathConstraintIndex; } set { pathConstraintIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, position, ... - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, float value) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + VALUE] = value; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { - PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; - float[] frames = this.frames; - if (time < frames[0]) { - switch (pose) { - case MixPose.Setup: - constraint.position = constraint.data.position; - return; - case MixPose.Current: - constraint.position += (constraint.data.position - constraint.position) * alpha; - return; - } - return; - } - - float position; - if (time >= frames[frames.Length - ENTRIES]) // Time is after last frame. - position = frames[frames.Length + PREV_VALUE]; - else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - position = frames[frame + PREV_VALUE]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - position += (frames[frame + VALUE] - position) * percent; - } - if (pose == MixPose.Setup) - constraint.position = constraint.data.position + (position - constraint.data.position) * alpha; - else - constraint.position += (position - constraint.position) * alpha; - } - } - - public class PathConstraintSpacingTimeline : PathConstraintPositionTimeline { - override public int PropertyId { - get { return ((int)TimelineType.PathConstraintSpacing << 24) + pathConstraintIndex; } - } - - public PathConstraintSpacingTimeline (int frameCount) - : base(frameCount) { - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { - PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; - float[] frames = this.frames; - if (time < frames[0]) { - switch (pose) { - case MixPose.Setup: - constraint.spacing = constraint.data.spacing; - return; - case MixPose.Current: - constraint.spacing += (constraint.data.spacing - constraint.spacing) * alpha; - return; - } - return; - } - - float spacing; - if (time >= frames[frames.Length - ENTRIES]) // Time is after last frame. - spacing = frames[frames.Length + PREV_VALUE]; - else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - spacing = frames[frame + PREV_VALUE]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - spacing += (frames[frame + VALUE] - spacing) * percent; - } - - if (pose == MixPose.Setup) - constraint.spacing = constraint.data.spacing + (spacing - constraint.data.spacing) * alpha; - else - constraint.spacing += (spacing - constraint.spacing) * alpha; - } - } - - public class PathConstraintMixTimeline : CurveTimeline { - public const int ENTRIES = 3; - private const int PREV_TIME = -3, PREV_ROTATE = -2, PREV_TRANSLATE = -1; - private const int ROTATE = 1, TRANSLATE = 2; - - internal int pathConstraintIndex; - internal float[] frames; - - public int PathConstraintIndex { get { return pathConstraintIndex; } set { pathConstraintIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, ... - - override public int PropertyId { - get { return ((int)TimelineType.PathConstraintMix << 24) + pathConstraintIndex; } - } - - public PathConstraintMixTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - /// Sets the time and mixes of the specified keyframe. - public void SetFrame (int frameIndex, float time, float rotateMix, float translateMix) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + ROTATE] = rotateMix; - frames[frameIndex + TRANSLATE] = translateMix; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { - PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; - float[] frames = this.frames; - if (time < frames[0]) { - switch (pose) { - case MixPose.Setup: - constraint.rotateMix = constraint.data.rotateMix; - constraint.translateMix = constraint.data.translateMix; - return; - case MixPose.Current: - constraint.rotateMix += (constraint.data.rotateMix - constraint.rotateMix) * alpha; - constraint.translateMix += (constraint.data.translateMix - constraint.translateMix) * alpha; - return; - } - return; - } - - float rotate, translate; - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - rotate = frames[frames.Length + PREV_ROTATE]; - translate = frames[frames.Length + PREV_TRANSLATE]; - } else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - rotate = frames[frame + PREV_ROTATE]; - translate = frames[frame + PREV_TRANSLATE]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - rotate += (frames[frame + ROTATE] - rotate) * percent; - translate += (frames[frame + TRANSLATE] - translate) * percent; - } - - if (pose == MixPose.Setup) { - constraint.rotateMix = constraint.data.rotateMix + (rotate - constraint.data.rotateMix) * alpha; - constraint.translateMix = constraint.data.translateMix + (translate - constraint.data.translateMix) * alpha; - } else { - constraint.rotateMix += (rotate - constraint.rotateMix) * alpha; - constraint.translateMix += (translate - constraint.translateMix) * alpha; - } - } - } + +namespace Spine3_6_39 +{ + public class Animation + { + internal ExposedList timelines; + internal float duration; + internal String name; + + public string Name { get { return name; } } + public ExposedList Timelines { get { return timelines; } set { timelines = value; } } + public float Duration { get { return duration; } set { duration = value; } } + + public Animation(string name, ExposedList timelines, float duration) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + if (timelines == null) throw new ArgumentNullException("timelines", "timelines cannot be null."); + this.name = name; + this.timelines = timelines; + this.duration = duration; + } + + /// Applies all the animation's timelines to the specified skeleton. + /// + public void Apply(Skeleton skeleton, float lastTime, float time, bool loop, ExposedList events, float alpha, MixPose pose, MixDirection direction) + { + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + + if (loop && duration != 0) + { + time %= duration; + if (lastTime > 0) lastTime %= duration; + } + + ExposedList timelines = this.timelines; + for (int i = 0, n = timelines.Count; i < n; i++) + timelines.Items[i].Apply(skeleton, lastTime, time, events, alpha, pose, direction); + } + + /// After the first and before the last entry. + internal static int BinarySearch(float[] values, float target, int step) + { + int low = 0; + int high = values.Length / step - 2; + if (high == 0) return step; + int current = (int)((uint)high >> 1); + while (true) + { + if (values[(current + 1) * step] <= target) + low = current + 1; + else + high = current; + if (low == high) return (low + 1) * step; + current = (int)((uint)(low + high) >> 1); + } + } + + /// After the first and before the last entry. + internal static int BinarySearch(float[] values, float target) + { + int low = 0; + int high = values.Length - 2; + if (high == 0) return 1; + int current = (int)((uint)high >> 1); + while (true) + { + if (values[(current + 1)] <= target) + low = current + 1; + else + high = current; + if (low == high) return (low + 1); + current = (int)((uint)(low + high) >> 1); + } + } + + internal static int LinearSearch(float[] values, float target, int step) + { + for (int i = 0, last = values.Length - step; i <= last; i += step) + if (values[i] > target) return i; + return -1; + } + } + + public interface Timeline + { + /// Sets the value(s) for the specified time. + /// The skeleton the timeline is being applied to. This provides access to the bones, slots, and other skeleton components the timeline may change. + /// 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). + /// The time within the animation. Most timelines find the key before and the key after this time so they can interpolate between the keys. + /// If any events are fired, they are added to this list. Can be null to ignore firing events or if the timeline does not fire events. May be null. + /// 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). + /// Controls how mixing is applied when alpha is than 1. + /// Indicates whether the timeline is mixing in or out. Used by timelines which perform instant transitions such as DrawOrderTimeline and AttachmentTimeline. + void Apply(Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, MixPose pose, MixDirection direction); + int PropertyId { get; } + } + + /// + /// Controls how a timeline is mixed with the setup or current pose. + /// + public enum MixPose + { + /// The timeline value is mixed with the setup pose (the current pose is not used). + Setup, + /// The timeline value is mixed with the current pose. The setup pose is used as the timeline value before the first key, + /// except for timelines which perform instant transitions, such as DrawOrderTimeline or AttachmentTimeline. + Current, + /// The timeline value is mixed with the current pose. No change is made before the first key (the current pose is kept until the first key). + CurrentLayered + } + + /// + /// 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). + /// + public enum MixDirection + { + In, + Out + } + + internal enum TimelineType + { + Rotate = 0, Translate, Scale, Shear, // + Attachment, Color, Deform, // + Event, DrawOrder, // + IkConstraint, TransformConstraint, // + PathConstraintPosition, PathConstraintSpacing, PathConstraintMix, // + TwoColor + } + + /// Base class for frames that use an interpolation bezier curve. + abstract public class CurveTimeline : Timeline + { + protected const float LINEAR = 0, STEPPED = 1, BEZIER = 2; + protected const int BEZIER_SIZE = 10 * 2 - 1; + + private float[] curves; // type, x, y, ... + public int FrameCount { get { return curves.Length / BEZIER_SIZE + 1; } } + + public CurveTimeline(int frameCount) + { + if (frameCount <= 0) throw new ArgumentException("frameCount must be > 0: " + frameCount, "frameCount"); + curves = new float[(frameCount - 1) * BEZIER_SIZE]; + } + + abstract public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction); + + abstract public int PropertyId { get; } + + public void SetLinear(int frameIndex) + { + curves[frameIndex * BEZIER_SIZE] = LINEAR; + } + + public void SetStepped(int frameIndex) + { + curves[frameIndex * BEZIER_SIZE] = STEPPED; + } + + /// Sets the control handle positions for an interpolation bezier curve used to transition from this keyframe to the next. + /// cx1 and cx2 are from 0 to 1, representing the percent of time between the two keyframes. cy1 and cy2 are the percent of + /// the difference between the keyframe's values. + public void SetCurve(int frameIndex, float cx1, float cy1, float cx2, float cy2) + { + float tmpx = (-cx1 * 2 + cx2) * 0.03f, tmpy = (-cy1 * 2 + cy2) * 0.03f; + float dddfx = ((cx1 - cx2) * 3 + 1) * 0.006f, dddfy = ((cy1 - cy2) * 3 + 1) * 0.006f; + float ddfx = tmpx * 2 + dddfx, ddfy = tmpy * 2 + dddfy; + float dfx = cx1 * 0.3f + tmpx + dddfx * 0.16666667f, dfy = cy1 * 0.3f + tmpy + dddfy * 0.16666667f; + + int i = frameIndex * BEZIER_SIZE; + float[] curves = this.curves; + curves[i++] = BEZIER; + + float x = dfx, y = dfy; + for (int n = i + BEZIER_SIZE - 1; i < n; i += 2) + { + curves[i] = x; + curves[i + 1] = y; + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + x += dfx; + y += dfy; + } + } + + public float GetCurvePercent(int frameIndex, float percent) + { + percent = MathUtils.Clamp(percent, 0, 1); + float[] curves = this.curves; + int i = frameIndex * BEZIER_SIZE; + float type = curves[i]; + if (type == LINEAR) return percent; + if (type == STEPPED) return 0; + i++; + float x = 0; + for (int start = i, n = i + BEZIER_SIZE - 1; i < n; i += 2) + { + x = curves[i]; + if (x >= percent) + { + float prevX, prevY; + if (i == start) + { + prevX = 0; + prevY = 0; + } + else + { + prevX = curves[i - 2]; + prevY = curves[i - 1]; + } + return prevY + (curves[i + 1] - prevY) * (percent - prevX) / (x - prevX); + } + } + float y = curves[i - 1]; + return y + (1 - y) * (percent - x) / (1 - x); // Last point is 1,1. + } + public float GetCurveType(int frameIndex) + { + return curves[frameIndex * BEZIER_SIZE]; + } + } + + public class RotateTimeline : CurveTimeline + { + public const int ENTRIES = 2; + internal const int PREV_TIME = -2, PREV_ROTATION = -1; + internal const int ROTATION = 1; + + internal int boneIndex; + internal float[] frames; + + public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, angle, ... + + override public int PropertyId + { + get { return ((int)TimelineType.Rotate << 24) + boneIndex; } + } + + public RotateTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount << 1]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, float degrees) + { + frameIndex <<= 1; + frames[frameIndex] = time; + frames[frameIndex + ROTATION] = degrees; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + + float[] frames = this.frames; + if (time < frames[0]) + { + switch (pose) + { + case MixPose.Setup: + bone.rotation = bone.data.rotation; + return; + case MixPose.Current: + float rr = bone.data.rotation - bone.rotation; + rr -= (16384 - (int)(16384.499999999996 - rr / 360)) * 360; + bone.rotation += rr * alpha; + return; + } + return; + } + + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + if (pose == MixPose.Setup) + { + bone.rotation = bone.data.rotation + frames[frames.Length + PREV_ROTATION] * alpha; + } + else + { + float rr = bone.data.rotation + frames[frames.Length + PREV_ROTATION] - bone.rotation; + rr -= (16384 - (int)(16384.499999999996 - rr / 360)) * 360; // Wrap within -180 and 180. + bone.rotation += rr * alpha; + } + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + float prevRotation = frames[frame + PREV_ROTATION]; + float frameTime = frames[frame]; + float percent = GetCurvePercent((frame >> 1) - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + float r = frames[frame + ROTATION] - prevRotation; + r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; + r = prevRotation + r * percent; + if (pose == MixPose.Setup) + { + r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; + bone.rotation = bone.data.rotation + r * alpha; + } + else + { + r = bone.data.rotation + r - bone.rotation; + r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; + bone.rotation += r * alpha; + } + } + } + + public class TranslateTimeline : CurveTimeline + { + public const int ENTRIES = 3; + protected const int PREV_TIME = -3, PREV_X = -2, PREV_Y = -1; + protected const int X = 1, Y = 2; + + internal int boneIndex; + internal float[] frames; + + public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, value, value, ... + + override public int PropertyId + { + get { return ((int)TimelineType.Translate << 24) + boneIndex; } + } + + public TranslateTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, float x, float y) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + X] = x; + frames[frameIndex + Y] = y; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + + float[] frames = this.frames; + if (time < frames[0]) + { + switch (pose) + { + case MixPose.Setup: + bone.x = bone.data.x; + bone.y = bone.data.y; + return; + case MixPose.Current: + bone.x += (bone.data.x - bone.x) * alpha; + bone.y += (bone.data.y - bone.y) * alpha; + return; + } + return; + } + + float x, y; + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + x = frames[frames.Length + PREV_X]; + y = frames[frames.Length + PREV_Y]; + } + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + x = frames[frame + PREV_X]; + y = frames[frame + PREV_Y]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + x += (frames[frame + X] - x) * percent; + y += (frames[frame + Y] - y) * percent; + } + if (pose == MixPose.Setup) + { + bone.x = bone.data.x + x * alpha; + bone.y = bone.data.y + y * alpha; + } + else + { + bone.x += (bone.data.x + x - bone.x) * alpha; + bone.y += (bone.data.y + y - bone.y) * alpha; + } + } + } + + public class ScaleTimeline : TranslateTimeline + { + override public int PropertyId + { + get { return ((int)TimelineType.Scale << 24) + boneIndex; } + } + + public ScaleTimeline(int frameCount) + : base(frameCount) + { + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + + float[] frames = this.frames; + if (time < frames[0]) + { + switch (pose) + { + case MixPose.Setup: + bone.scaleX = bone.data.scaleX; + bone.scaleY = bone.data.scaleY; + return; + case MixPose.Current: + bone.scaleX += (bone.data.scaleX - bone.scaleX) * alpha; + bone.scaleY += (bone.data.scaleY - bone.scaleY) * alpha; + return; + } + return; + } + + float x, y; + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + x = frames[frames.Length + PREV_X] * bone.data.scaleX; + y = frames[frames.Length + PREV_Y] * bone.data.scaleY; + } + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + x = frames[frame + PREV_X]; + y = frames[frame + PREV_Y]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + x = (x + (frames[frame + X] - x) * percent) * bone.data.scaleX; + y = (y + (frames[frame + Y] - y) * percent) * bone.data.scaleY; + } + if (alpha == 1) + { + bone.scaleX = x; + bone.scaleY = y; + } + else + { + float bx, by; + if (pose == MixPose.Setup) + { + bx = bone.data.scaleX; + by = bone.data.scaleY; + } + else + { + bx = bone.scaleX; + by = bone.scaleY; + } + // Mixing out uses sign of setup or current pose, else use sign of key. + if (direction == MixDirection.Out) + { + x = (x >= 0 ? x : -x) * (bx >= 0 ? 1 : -1); + y = (y >= 0 ? y : -y) * (by >= 0 ? 1 : -1); + } + else + { + bx = (bx >= 0 ? bx : -bx) * (x >= 0 ? 1 : -1); + by = (by >= 0 ? by : -by) * (y >= 0 ? 1 : -1); + } + bone.scaleX = bx + (x - bx) * alpha; + bone.scaleY = by + (y - by) * alpha; + } + } + } + + public class ShearTimeline : TranslateTimeline + { + override public int PropertyId + { + get { return ((int)TimelineType.Shear << 24) + boneIndex; } + } + + public ShearTimeline(int frameCount) + : base(frameCount) + { + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + float[] frames = this.frames; + if (time < frames[0]) + { + switch (pose) + { + case MixPose.Setup: + bone.shearX = bone.data.shearX; + bone.shearY = bone.data.shearY; + return; + case MixPose.Current: + bone.shearX += (bone.data.shearX - bone.shearX) * alpha; + bone.shearY += (bone.data.shearY - bone.shearY) * alpha; + return; + } + return; + } + + float x, y; + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + x = frames[frames.Length + PREV_X]; + y = frames[frames.Length + PREV_Y]; + } + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + x = frames[frame + PREV_X]; + y = frames[frame + PREV_Y]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + x = x + (frames[frame + X] - x) * percent; + y = y + (frames[frame + Y] - y) * percent; + } + if (pose == MixPose.Setup) + { + bone.shearX = bone.data.shearX + x * alpha; + bone.shearY = bone.data.shearY + y * alpha; + } + else + { + bone.shearX += (bone.data.shearX + x - bone.shearX) * alpha; + bone.shearY += (bone.data.shearY + y - bone.shearY) * alpha; + } + } + } + + public class ColorTimeline : CurveTimeline + { + public const int ENTRIES = 5; + protected const int PREV_TIME = -5, PREV_R = -4, PREV_G = -3, PREV_B = -2, PREV_A = -1; + protected const int R = 1, G = 2, B = 3, A = 4; + + internal int slotIndex; + internal float[] frames; + + public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, r, g, b, a, ... + + override public int PropertyId + { + get { return ((int)TimelineType.Color << 24) + slotIndex; } + } + + public ColorTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, float r, float g, float b, float a) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + R] = r; + frames[frameIndex + G] = g; + frames[frameIndex + B] = b; + frames[frameIndex + A] = a; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) + { + Slot slot = skeleton.slots.Items[slotIndex]; + float[] frames = this.frames; + if (time < frames[0]) + { + var slotData = slot.data; + switch (pose) + { + case MixPose.Setup: + slot.r = slotData.r; + slot.g = slotData.g; + slot.b = slotData.b; + slot.a = slotData.a; + return; + case MixPose.Current: + slot.r += (slot.r - slotData.r) * alpha; + slot.g += (slot.g - slotData.g) * alpha; + slot.b += (slot.b - slotData.b) * alpha; + slot.a += (slot.a - slotData.a) * alpha; + return; + } + return; + } + + float r, g, b, a; + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + int i = frames.Length; + r = frames[i + PREV_R]; + g = frames[i + PREV_G]; + b = frames[i + PREV_B]; + a = frames[i + PREV_A]; + } + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + r = frames[frame + PREV_R]; + g = frames[frame + PREV_G]; + b = frames[frame + PREV_B]; + a = frames[frame + PREV_A]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + r += (frames[frame + R] - r) * percent; + g += (frames[frame + G] - g) * percent; + b += (frames[frame + B] - b) * percent; + a += (frames[frame + A] - a) * percent; + } + if (alpha == 1) + { + slot.r = r; + slot.g = g; + slot.b = b; + slot.a = a; + } + else + { + float br, bg, bb, ba; + if (pose == MixPose.Setup) + { + br = slot.data.r; + bg = slot.data.g; + bb = slot.data.b; + ba = slot.data.a; + } + else + { + br = slot.r; + bg = slot.g; + bb = slot.b; + ba = slot.a; + } + slot.r = br + ((r - br) * alpha); + slot.g = bg + ((g - bg) * alpha); + slot.b = bb + ((b - bb) * alpha); + slot.a = ba + ((a - ba) * alpha); + } + } + } + + public class TwoColorTimeline : CurveTimeline + { + public const int ENTRIES = 8; + protected const int PREV_TIME = -8, PREV_R = -7, PREV_G = -6, PREV_B = -5, PREV_A = -4; + protected const int PREV_R2 = -3, PREV_G2 = -2, PREV_B2 = -1; + protected const int R = 1, G = 2, B = 3, A = 4, R2 = 5, G2 = 6, B2 = 7; + + internal float[] frames; // time, r, g, b, a, r2, g2, b2, ... + public float[] Frames { get { return frames; } } + + internal int slotIndex; + public int SlotIndex + { + get { return slotIndex; } + set + { + if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0."); + slotIndex = value; + } + } + + override public int PropertyId + { + get { return ((int)TimelineType.TwoColor << 24) + slotIndex; } + } + + public TwoColorTimeline(int frameCount) : + base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, float r, float g, float b, float a, float r2, float g2, float b2) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + R] = r; + frames[frameIndex + G] = g; + frames[frameIndex + B] = b; + frames[frameIndex + A] = a; + frames[frameIndex + R2] = r2; + frames[frameIndex + G2] = g2; + frames[frameIndex + B2] = b2; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) + { + Slot slot = skeleton.slots.Items[slotIndex]; + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + var slotData = slot.data; + switch (pose) + { + case MixPose.Setup: + // slot.color.set(slot.data.color); + // slot.darkColor.set(slot.data.darkColor); + slot.r = slotData.r; + slot.g = slotData.g; + slot.b = slotData.b; + slot.a = slotData.a; + slot.r2 = slotData.r2; + slot.g2 = slotData.g2; + slot.b2 = slotData.b2; + return; + case MixPose.Current: + slot.r += (slot.r - slotData.r) * alpha; + slot.g += (slot.g - slotData.g) * alpha; + slot.b += (slot.b - slotData.b) * alpha; + slot.a += (slot.a - slotData.a) * alpha; + slot.r2 += (slot.r2 - slotData.r2) * alpha; + slot.g2 += (slot.g2 - slotData.g2) * alpha; + slot.b2 += (slot.b2 - slotData.b2) * alpha; + return; + } + return; + } + + float r, g, b, a, r2, g2, b2; + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + int i = frames.Length; + r = frames[i + PREV_R]; + g = frames[i + PREV_G]; + b = frames[i + PREV_B]; + a = frames[i + PREV_A]; + r2 = frames[i + PREV_R2]; + g2 = frames[i + PREV_G2]; + b2 = frames[i + PREV_B2]; + } + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + r = frames[frame + PREV_R]; + g = frames[frame + PREV_G]; + b = frames[frame + PREV_B]; + a = frames[frame + PREV_A]; + r2 = frames[frame + PREV_R2]; + g2 = frames[frame + PREV_G2]; + b2 = frames[frame + PREV_B2]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + r += (frames[frame + R] - r) * percent; + g += (frames[frame + G] - g) * percent; + b += (frames[frame + B] - b) * percent; + a += (frames[frame + A] - a) * percent; + r2 += (frames[frame + R2] - r2) * percent; + g2 += (frames[frame + G2] - g2) * percent; + b2 += (frames[frame + B2] - b2) * percent; + } + if (alpha == 1) + { + slot.r = r; + slot.g = g; + slot.b = b; + slot.a = a; + slot.r2 = r2; + slot.g2 = g2; + slot.b2 = b2; + } + else + { + float br, bg, bb, ba, br2, bg2, bb2; + if (pose == MixPose.Setup) + { + br = slot.data.r; + bg = slot.data.g; + bb = slot.data.b; + ba = slot.data.a; + br2 = slot.data.r2; + bg2 = slot.data.g2; + bb2 = slot.data.b2; + } + else + { + br = slot.r; + bg = slot.g; + bb = slot.b; + ba = slot.a; + br2 = slot.r2; + bg2 = slot.g2; + bb2 = slot.b2; + } + slot.r = br + ((r - br) * alpha); + slot.g = bg + ((g - bg) * alpha); + slot.b = bb + ((b - bb) * alpha); + slot.a = ba + ((a - ba) * alpha); + slot.r2 = br2 + ((r2 - br2) * alpha); + slot.g2 = bg2 + ((g2 - bg2) * alpha); + slot.b2 = bb2 + ((b2 - bb2) * alpha); + } + } + + } + + public class AttachmentTimeline : Timeline + { + internal int slotIndex; + internal float[] frames; + private String[] attachmentNames; + + public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, ... + public String[] AttachmentNames { get { return attachmentNames; } set { attachmentNames = value; } } + public int FrameCount { get { return frames.Length; } } + + public int PropertyId + { + get { return ((int)TimelineType.Attachment << 24) + slotIndex; } + } + + public AttachmentTimeline(int frameCount) + { + frames = new float[frameCount]; + attachmentNames = new String[frameCount]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, String attachmentName) + { + frames[frameIndex] = time; + attachmentNames[frameIndex] = attachmentName; + } + + public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) + { + string attachmentName; + Slot slot = skeleton.slots.Items[slotIndex]; + if (direction == MixDirection.Out && pose == MixPose.Setup) + { + attachmentName = slot.data.attachmentName; + slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); + return; + } + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + if (pose == MixPose.Setup) + { + attachmentName = slot.data.attachmentName; + slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); + } + return; + } + + int frameIndex; + if (time >= frames[frames.Length - 1]) // Time is after last frame. + frameIndex = frames.Length - 1; + else + frameIndex = Animation.BinarySearch(frames, time, 1) - 1; + + attachmentName = attachmentNames[frameIndex]; + slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); + } + } + + public class DeformTimeline : CurveTimeline + { + static float[] zeros = new float[64]; + + internal int slotIndex; + internal float[] frames; + internal float[][] frameVertices; + internal VertexAttachment attachment; + + public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, ... + public float[][] Vertices { get { return frameVertices; } set { frameVertices = value; } } + public VertexAttachment Attachment { get { return attachment; } set { attachment = value; } } + + override public int PropertyId + { + get { return ((int)TimelineType.Deform << 24) + attachment.id + slotIndex; } + } + + public DeformTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount]; + frameVertices = new float[frameCount][]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, float[] vertices) + { + frames[frameIndex] = time; + frameVertices[frameIndex] = vertices; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) + { + Slot slot = skeleton.slots.Items[slotIndex]; + VertexAttachment vertexAttachment = slot.attachment as VertexAttachment; + if (vertexAttachment == null || !vertexAttachment.ApplyDeform(attachment)) return; + + var verticesArray = slot.attachmentVertices; + float[][] frameVertices = this.frameVertices; + int vertexCount = frameVertices[0].Length; + if (verticesArray.Capacity < vertexCount) verticesArray.Capacity = vertexCount; // verticesArray.SetSize(vertexCount) // Ensure size and preemptively set count. + verticesArray.Count = vertexCount; + float[] vertices = verticesArray.Items; + + float[] frames = this.frames; + if (time < frames[0]) + { + + switch (pose) + { + case MixPose.Setup: + float[] zeroVertices; + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions (setup pose). + zeroVertices = vertexAttachment.vertices; + } + else + { + // Weighted deform offsets (zeros). + zeroVertices = DeformTimeline.zeros; + if (zeroVertices.Length < vertexCount) DeformTimeline.zeros = zeroVertices = new float[vertexCount]; + } + Array.Copy(zeroVertices, 0, vertices, 0, vertexCount); + return; + case MixPose.Current: + if (alpha == 1) return; + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions. + float[] setupVertices = vertexAttachment.vertices; + for (int i = 0; i < vertexCount; i++) + vertices[i] += (setupVertices[i] - vertices[i]) * alpha; + } + else + { + // Weighted deform offsets. + alpha = 1 - alpha; + for (int i = 0; i < vertexCount; i++) + vertices[i] *= alpha; + } + return; + default: + return; + } + + } + + if (time >= frames[frames.Length - 1]) + { // Time is after last frame. + float[] lastVertices = frameVertices[frames.Length - 1]; + if (alpha == 1) + { + // Vertex positions or deform offsets, no alpha. + Array.Copy(lastVertices, 0, vertices, 0, vertexCount); + } + else if (pose == MixPose.Setup) + { + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions, with alpha. + float[] setupVertices = vertexAttachment.vertices; + for (int i = 0; i < vertexCount; i++) + { + float setup = setupVertices[i]; + vertices[i] = setup + (lastVertices[i] - setup) * alpha; + } + } + else + { + // Weighted deform offsets, with alpha. + for (int i = 0; i < vertexCount; i++) + vertices[i] = lastVertices[i] * alpha; + } + } + else + { + // Vertex positions or deform offsets, with alpha. + for (int i = 0; i < vertexCount; i++) + vertices[i] += (lastVertices[i] - vertices[i]) * alpha; + } + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time); + float[] prevVertices = frameVertices[frame - 1]; + float[] nextVertices = frameVertices[frame]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame - 1, 1 - (time - frameTime) / (frames[frame - 1] - frameTime)); + + if (alpha == 1) + { + // Vertex positions or deform offsets, no alpha. + for (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i]; + vertices[i] = prev + (nextVertices[i] - prev) * percent; + } + } + else if (pose == MixPose.Setup) + { + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions, with alpha. + var setupVertices = vertexAttachment.vertices; + for (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i], setup = setupVertices[i]; + vertices[i] = setup + (prev + (nextVertices[i] - prev) * percent - setup) * alpha; + } + } + else + { + // Weighted deform offsets, with alpha. + for (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i]; + vertices[i] = (prev + (nextVertices[i] - prev) * percent) * alpha; + } + } + } + else + { + // Vertex positions or deform offsets, with alpha. + for (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i]; + vertices[i] += (prev + (nextVertices[i] - prev) * percent - vertices[i]) * alpha; + } + } + } + } + + public class EventTimeline : Timeline + { + internal float[] frames; + private Event[] events; + + public float[] Frames { get { return frames; } set { frames = value; } } // time, ... + public Event[] Events { get { return events; } set { events = value; } } + public int FrameCount { get { return frames.Length; } } + + public int PropertyId + { + get { return ((int)TimelineType.Event << 24); } + } + + public EventTimeline(int frameCount) + { + frames = new float[frameCount]; + events = new Event[frameCount]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, Event e) + { + frames[frameIndex] = e.Time; + events[frameIndex] = e; + } + + /// Fires events for frames > lastTime and <= time. + public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) + { + if (firedEvents == null) return; + float[] frames = this.frames; + int frameCount = frames.Length; + + if (lastTime > time) + { // Fire events after last time for looped animations. + Apply(skeleton, lastTime, int.MaxValue, firedEvents, alpha, pose, direction); + lastTime = -1f; + } + else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame. + return; + if (time < frames[0]) return; // Time is before first frame. + + int frame; + if (lastTime < frames[0]) + frame = 0; + else + { + frame = Animation.BinarySearch(frames, lastTime); + float frameTime = frames[frame]; + while (frame > 0) + { // Fire multiple events with the same frame. + if (frames[frame - 1] != frameTime) break; + frame--; + } + } + for (; frame < frameCount && time >= frames[frame]; frame++) + firedEvents.Add(events[frame]); + } + } + + public class DrawOrderTimeline : Timeline + { + internal float[] frames; + private int[][] drawOrders; + + public float[] Frames { get { return frames; } set { frames = value; } } // time, ... + public int[][] DrawOrders { get { return drawOrders; } set { drawOrders = value; } } + public int FrameCount { get { return frames.Length; } } + + public int PropertyId + { + get { return ((int)TimelineType.DrawOrder << 24); } + } + + public DrawOrderTimeline(int frameCount) + { + frames = new float[frameCount]; + drawOrders = new int[frameCount][]; + } + + /// Sets the time and value of the specified keyframe. + /// May be null to use bind pose draw order. + public void SetFrame(int frameIndex, float time, int[] drawOrder) + { + frames[frameIndex] = time; + drawOrders[frameIndex] = drawOrder; + } + + public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) + { + ExposedList drawOrder = skeleton.drawOrder; + ExposedList slots = skeleton.slots; + if (direction == MixDirection.Out && pose == MixPose.Setup) + { + Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count); + return; + } + + float[] frames = this.frames; + if (time < frames[0]) + { + if (pose == MixPose.Setup) Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count); + return; + } + + int frame; + if (time >= frames[frames.Length - 1]) // Time is after last frame. + frame = frames.Length - 1; + else + frame = Animation.BinarySearch(frames, time) - 1; + + int[] drawOrderToSetupIndex = drawOrders[frame]; + if (drawOrderToSetupIndex == null) + { + drawOrder.Clear(); + for (int i = 0, n = slots.Count; i < n; i++) + drawOrder.Add(slots.Items[i]); + } + else + { + var drawOrderItems = drawOrder.Items; + var slotsItems = slots.Items; + for (int i = 0, n = drawOrderToSetupIndex.Length; i < n; i++) + drawOrderItems[i] = slotsItems[drawOrderToSetupIndex[i]]; + } + } + } + + public class IkConstraintTimeline : CurveTimeline + { + public const int ENTRIES = 3; + private const int PREV_TIME = -3, PREV_MIX = -2, PREV_BEND_DIRECTION = -1; + private const int MIX = 1, BEND_DIRECTION = 2; + + internal int ikConstraintIndex; + internal float[] frames; + + public int IkConstraintIndex { get { return ikConstraintIndex; } set { ikConstraintIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, mix, bendDirection, ... + + override public int PropertyId + { + get { return ((int)TimelineType.IkConstraint << 24) + ikConstraintIndex; } + } + + public IkConstraintTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + /// Sets the time, mix and bend direction of the specified keyframe. + public void SetFrame(int frameIndex, float time, float mix, int bendDirection) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + MIX] = mix; + frames[frameIndex + BEND_DIRECTION] = bendDirection; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) + { + IkConstraint constraint = skeleton.ikConstraints.Items[ikConstraintIndex]; + float[] frames = this.frames; + if (time < frames[0]) + { + switch (pose) + { + case MixPose.Setup: + constraint.mix = constraint.data.mix; + constraint.bendDirection = constraint.data.bendDirection; + return; + case MixPose.Current: + constraint.mix += (constraint.data.mix - constraint.mix) * alpha; + constraint.bendDirection = constraint.data.bendDirection; + return; + } + return; + } + + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + if (pose == MixPose.Setup) + { + constraint.mix = constraint.data.mix + (frames[frames.Length + PREV_MIX] - constraint.data.mix) * alpha; + constraint.bendDirection = direction == MixDirection.Out ? constraint.data.bendDirection + : (int)frames[frames.Length + PREV_BEND_DIRECTION]; + } + else + { + constraint.mix += (frames[frames.Length + PREV_MIX] - constraint.mix) * alpha; + if (direction == MixDirection.In) constraint.bendDirection = (int)frames[frames.Length + PREV_BEND_DIRECTION]; + } + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + float mix = frames[frame + PREV_MIX]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + if (pose == MixPose.Setup) + { + constraint.mix = constraint.data.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.data.mix) * alpha; + constraint.bendDirection = direction == MixDirection.Out ? constraint.data.bendDirection : (int)frames[frame + PREV_BEND_DIRECTION]; + } + else + { + constraint.mix += (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha; + if (direction == MixDirection.In) constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION]; + } + } + } + + public class TransformConstraintTimeline : CurveTimeline + { + public const int ENTRIES = 5; + private const int PREV_TIME = -5, PREV_ROTATE = -4, PREV_TRANSLATE = -3, PREV_SCALE = -2, PREV_SHEAR = -1; + private const int ROTATE = 1, TRANSLATE = 2, SCALE = 3, SHEAR = 4; + + internal int transformConstraintIndex; + internal float[] frames; + + public int TransformConstraintIndex { get { return transformConstraintIndex; } set { transformConstraintIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, scale mix, shear mix, ... + + override public int PropertyId + { + get { return ((int)TimelineType.TransformConstraint << 24) + transformConstraintIndex; } + } + + public TransformConstraintTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + public void SetFrame(int frameIndex, float time, float rotateMix, float translateMix, float scaleMix, float shearMix) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + ROTATE] = rotateMix; + frames[frameIndex + TRANSLATE] = translateMix; + frames[frameIndex + SCALE] = scaleMix; + frames[frameIndex + SHEAR] = shearMix; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) + { + TransformConstraint constraint = skeleton.transformConstraints.Items[transformConstraintIndex]; + float[] frames = this.frames; + if (time < frames[0]) + { + var data = constraint.data; + switch (pose) + { + case MixPose.Setup: + constraint.rotateMix = data.rotateMix; + constraint.translateMix = data.translateMix; + constraint.scaleMix = data.scaleMix; + constraint.shearMix = data.shearMix; + return; + case MixPose.Current: + constraint.rotateMix += (data.rotateMix - constraint.rotateMix) * alpha; + constraint.translateMix += (data.translateMix - constraint.translateMix) * alpha; + constraint.scaleMix += (data.scaleMix - constraint.scaleMix) * alpha; + constraint.shearMix += (data.shearMix - constraint.shearMix) * alpha; + return; + } + return; + } + + float rotate, translate, scale, shear; + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + int i = frames.Length; + rotate = frames[i + PREV_ROTATE]; + translate = frames[i + PREV_TRANSLATE]; + scale = frames[i + PREV_SCALE]; + shear = frames[i + PREV_SHEAR]; + } + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + rotate = frames[frame + PREV_ROTATE]; + translate = frames[frame + PREV_TRANSLATE]; + scale = frames[frame + PREV_SCALE]; + shear = frames[frame + PREV_SHEAR]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + rotate += (frames[frame + ROTATE] - rotate) * percent; + translate += (frames[frame + TRANSLATE] - translate) * percent; + scale += (frames[frame + SCALE] - scale) * percent; + shear += (frames[frame + SHEAR] - shear) * percent; + } + if (pose == MixPose.Setup) + { + TransformConstraintData data = constraint.data; + constraint.rotateMix = data.rotateMix + (rotate - data.rotateMix) * alpha; + constraint.translateMix = data.translateMix + (translate - data.translateMix) * alpha; + constraint.scaleMix = data.scaleMix + (scale - data.scaleMix) * alpha; + constraint.shearMix = data.shearMix + (shear - data.shearMix) * alpha; + } + else + { + constraint.rotateMix += (rotate - constraint.rotateMix) * alpha; + constraint.translateMix += (translate - constraint.translateMix) * alpha; + constraint.scaleMix += (scale - constraint.scaleMix) * alpha; + constraint.shearMix += (shear - constraint.shearMix) * alpha; + } + } + } + + public class PathConstraintPositionTimeline : CurveTimeline + { + public const int ENTRIES = 2; + protected const int PREV_TIME = -2, PREV_VALUE = -1; + protected const int VALUE = 1; + + internal int pathConstraintIndex; + internal float[] frames; + + override public int PropertyId + { + get { return ((int)TimelineType.PathConstraintPosition << 24) + pathConstraintIndex; } + } + + public PathConstraintPositionTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + public int PathConstraintIndex { get { return pathConstraintIndex; } set { pathConstraintIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, position, ... + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, float value) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + VALUE] = value; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) + { + PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; + float[] frames = this.frames; + if (time < frames[0]) + { + switch (pose) + { + case MixPose.Setup: + constraint.position = constraint.data.position; + return; + case MixPose.Current: + constraint.position += (constraint.data.position - constraint.position) * alpha; + return; + } + return; + } + + float position; + if (time >= frames[frames.Length - ENTRIES]) // Time is after last frame. + position = frames[frames.Length + PREV_VALUE]; + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + position = frames[frame + PREV_VALUE]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + position += (frames[frame + VALUE] - position) * percent; + } + if (pose == MixPose.Setup) + constraint.position = constraint.data.position + (position - constraint.data.position) * alpha; + else + constraint.position += (position - constraint.position) * alpha; + } + } + + public class PathConstraintSpacingTimeline : PathConstraintPositionTimeline + { + override public int PropertyId + { + get { return ((int)TimelineType.PathConstraintSpacing << 24) + pathConstraintIndex; } + } + + public PathConstraintSpacingTimeline(int frameCount) + : base(frameCount) + { + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) + { + PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; + float[] frames = this.frames; + if (time < frames[0]) + { + switch (pose) + { + case MixPose.Setup: + constraint.spacing = constraint.data.spacing; + return; + case MixPose.Current: + constraint.spacing += (constraint.data.spacing - constraint.spacing) * alpha; + return; + } + return; + } + + float spacing; + if (time >= frames[frames.Length - ENTRIES]) // Time is after last frame. + spacing = frames[frames.Length + PREV_VALUE]; + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + spacing = frames[frame + PREV_VALUE]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + spacing += (frames[frame + VALUE] - spacing) * percent; + } + + if (pose == MixPose.Setup) + constraint.spacing = constraint.data.spacing + (spacing - constraint.data.spacing) * alpha; + else + constraint.spacing += (spacing - constraint.spacing) * alpha; + } + } + + public class PathConstraintMixTimeline : CurveTimeline + { + public const int ENTRIES = 3; + private const int PREV_TIME = -3, PREV_ROTATE = -2, PREV_TRANSLATE = -1; + private const int ROTATE = 1, TRANSLATE = 2; + + internal int pathConstraintIndex; + internal float[] frames; + + public int PathConstraintIndex { get { return pathConstraintIndex; } set { pathConstraintIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, ... + + override public int PropertyId + { + get { return ((int)TimelineType.PathConstraintMix << 24) + pathConstraintIndex; } + } + + public PathConstraintMixTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + /// Sets the time and mixes of the specified keyframe. + public void SetFrame(int frameIndex, float time, float rotateMix, float translateMix) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + ROTATE] = rotateMix; + frames[frameIndex + TRANSLATE] = translateMix; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) + { + PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; + float[] frames = this.frames; + if (time < frames[0]) + { + switch (pose) + { + case MixPose.Setup: + constraint.rotateMix = constraint.data.rotateMix; + constraint.translateMix = constraint.data.translateMix; + return; + case MixPose.Current: + constraint.rotateMix += (constraint.data.rotateMix - constraint.rotateMix) * alpha; + constraint.translateMix += (constraint.data.translateMix - constraint.translateMix) * alpha; + return; + } + return; + } + + float rotate, translate; + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + rotate = frames[frames.Length + PREV_ROTATE]; + translate = frames[frames.Length + PREV_TRANSLATE]; + } + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + rotate = frames[frame + PREV_ROTATE]; + translate = frames[frame + PREV_TRANSLATE]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + rotate += (frames[frame + ROTATE] - rotate) * percent; + translate += (frames[frame + TRANSLATE] - translate) * percent; + } + + if (pose == MixPose.Setup) + { + constraint.rotateMix = constraint.data.rotateMix + (rotate - constraint.data.rotateMix) * alpha; + constraint.translateMix = constraint.data.translateMix + (translate - constraint.data.translateMix) * alpha; + } + else + { + constraint.rotateMix += (rotate - constraint.rotateMix) * alpha; + constraint.translateMix += (translate - constraint.translateMix) * alpha; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/AnimationState.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/AnimationState.cs index b7077c8..7ef0741 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/AnimationState.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/AnimationState.cs @@ -31,1028 +31,1148 @@ using System; using System.Collections.Generic; -namespace Spine3_6_39 { - public class AnimationState { - static readonly Animation EmptyAnimation = new Animation("", new ExposedList(), 0); - internal const int Subsequent = 0, First = 1, Dip = 2, DipMix = 3; - - private AnimationStateData data; - - Pool trackEntryPool = new Pool(); - private readonly ExposedList tracks = new ExposedList(); - private readonly ExposedList events = new ExposedList(); - private readonly EventQueue queue; // Initialized by constructor. - - private readonly HashSet propertyIDs = new HashSet(); - private readonly ExposedList mixingTo = new ExposedList(); - private bool animationsChanged; - - private float timeScale = 1; - - public AnimationStateData Data { get { return data; } } - /// A list of tracks that have animations, which may contain nulls. - public ExposedList Tracks { get { return tracks; } } - public float TimeScale { get { return timeScale; } set { timeScale = value; } } - - public delegate void TrackEntryDelegate (TrackEntry trackEntry); - public event TrackEntryDelegate Start, Interrupt, End, Dispose, Complete; - - public delegate void TrackEntryEventDelegate (TrackEntry trackEntry, Event e); - public event TrackEntryEventDelegate Event; - - public AnimationState (AnimationStateData data) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - this.data = data; - this.queue = new EventQueue( - this, - delegate { this.animationsChanged = true; }, - trackEntryPool - ); - } - - /// - /// Increments the track entry times, setting queued animations as current if needed - /// delta time - public void Update (float delta) { - delta *= timeScale; - var tracksItems = tracks.Items; - for (int i = 0, n = tracks.Count; i < n; i++) { - TrackEntry current = tracksItems[i]; - if (current == null) continue; - - 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 = nextTime + (delta * next.timeScale); - current.trackTime += currentDelta; - SetCurrent(i, next, true); - while (next.mixingFrom != null) { - next.mixTime += currentDelta; - 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. - tracksItems[i] = null; - - queue.End(current); - DisposeNext(current); - continue; - } - if (current.mixingFrom != null && UpdateMixingFrom(current, delta)) { - // End mixing from entries once all have completed. - var from = current.mixingFrom; - current.mixingFrom = null; - while (from != null) { - queue.End(from); - from = from.mixingFrom; - } - } - - current.trackTime += currentDelta; - } - - queue.Drain(); - } - - /// Returns true when all mixing from entries are complete. - private bool UpdateMixingFrom (TrackEntry to, float delta) { - TrackEntry from = to.mixingFrom; - if (from == null) return true; - - bool finished = UpdateMixingFrom(from, delta); - - // Require mixTime > 0 to ensure the mixing from entry was applied at least once. - if (to.mixTime > 0 && (to.mixTime >= to.mixDuration || to.timeScale == 0)) { - // Require totalAlpha == 0 to ensure mixing is complete, unless mixDuration == 0 (the transition is a single frame). - if (from.totalAlpha == 0 || to.mixDuration == 0) { - to.mixingFrom = from.mixingFrom; - to.interruptAlpha = from.interruptAlpha; - queue.End(from); - } - return finished; - } - - from.animationLast = from.nextAnimationLast; - from.trackLast = from.nextTrackLast; - from.trackTime += delta * from.timeScale; - to.mixTime += delta * to.timeScale; - return false; - } - - /// - /// 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. - public bool Apply (Skeleton skeleton) { - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - if (animationsChanged) AnimationsChanged(); - - var events = this.events; - - bool applied = false; - var tracksItems = tracks.Items; - for (int i = 0, m = tracks.Count; i < m; i++) { - TrackEntry current = tracksItems[i]; - if (current == null || current.delay > 0) continue; - applied = true; - MixPose currentPose = i == 0 ? MixPose.Current : MixPose.CurrentLayered; - - // Apply mixing from entries first. - float mix = current.alpha; - if (current.mixingFrom != null) - mix *= ApplyMixingFrom(current, skeleton, currentPose); - else if (current.trackTime >= current.trackEnd && current.next == null) // - mix = 0; // Set to setup pose the last time the entry will be applied. - - // Apply current entry. - float animationLast = current.animationLast, animationTime = current.AnimationTime; - int timelineCount = current.animation.timelines.Count; - var timelines = current.animation.timelines; - var timelinesItems = timelines.Items; - if (mix == 1) { - for (int ii = 0; ii < timelineCount; ii++) - timelinesItems[ii].Apply(skeleton, animationLast, animationTime, events, 1, MixPose.Setup, MixDirection.In); - } else { - var timelineData = current.timelineData.Items; - - bool firstFrame = current.timelinesRotation.Count == 0; - if (firstFrame) current.timelinesRotation.EnsureCapacity(timelines.Count << 1); - var timelinesRotation = current.timelinesRotation.Items; - - for (int ii = 0; ii < timelineCount; ii++) { - Timeline timeline = timelinesItems[ii]; - MixPose pose = timelineData[ii] >= AnimationState.First ? MixPose.Setup : currentPose; - var rotateTimeline = timeline as RotateTimeline; - if (rotateTimeline != null) - ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, mix, pose, timelinesRotation, ii << 1, firstFrame); - else - timeline.Apply(skeleton, animationLast, animationTime, events, mix, pose, MixDirection.In); - } - } - QueueEvents(current, animationTime); - events.Clear(false); - current.nextAnimationLast = animationTime; - current.nextTrackLast = current.trackTime; - } - - queue.Drain(); - return applied; - } - - private float ApplyMixingFrom (TrackEntry to, Skeleton skeleton, MixPose currentPose) { - TrackEntry from = to.mixingFrom; - if (from.mixingFrom != null) ApplyMixingFrom(from, skeleton, currentPose); - - float mix; - if (to.mixDuration == 0) // Single frame mix to undo mixingFrom changes. - mix = 1; - else { - mix = to.mixTime / to.mixDuration; - if (mix > 1) mix = 1; - } - - var eventBuffer = mix < from.eventThreshold ? this.events : null; - bool attachments = mix < from.attachmentThreshold, drawOrder = mix < from.drawOrderThreshold; - float animationLast = from.animationLast, animationTime = from.AnimationTime; - var timelines = from.animation.timelines; - int timelineCount = timelines.Count; - var timelinesItems = timelines.Items; - var timelineData = from.timelineData.Items; - var timelineDipMix = from.timelineDipMix.Items; - - bool firstFrame = from.timelinesRotation.Count == 0; - if (firstFrame) from.timelinesRotation.Resize(timelines.Count << 1); // from.timelinesRotation.setSize - var timelinesRotation = from.timelinesRotation.Items; - - MixPose pose; - float alphaDip = from.alpha * to.interruptAlpha, alphaMix = alphaDip * (1 - mix), alpha; - from.totalAlpha = 0; - for (int i = 0; i < timelineCount; i++) { - Timeline timeline = timelinesItems[i]; - switch (timelineData[i]) { - case Subsequent: - if (!attachments && timeline is AttachmentTimeline) continue; - if (!drawOrder && timeline is DrawOrderTimeline) continue; - pose = currentPose; - alpha = alphaMix; - break; - case First: - pose = MixPose.Setup; - alpha = alphaMix; - break; - case Dip: - pose = MixPose.Setup; - alpha = alphaDip; - break; - default: - pose = MixPose.Setup; - alpha = alphaDip; - var dipMix = timelineDipMix[i]; - alpha *= Math.Max(0, 1 - dipMix.mixTime / dipMix.mixDuration); - break; - } - from.totalAlpha += alpha; - var rotateTimeline = timeline as RotateTimeline; - if (rotateTimeline != null) { - ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, alpha, pose, timelinesRotation, i << 1, firstFrame); - } else { - timeline.Apply(skeleton, animationLast, animationTime, eventBuffer, alpha, pose, MixDirection.Out); - } - } - - if (to.mixDuration > 0) QueueEvents(from, animationTime); - this.events.Clear(false); - from.nextAnimationLast = animationTime; - from.nextTrackLast = from.trackTime; - - return mix; - } - - static private void ApplyRotateTimeline (RotateTimeline rotateTimeline, Skeleton skeleton, float time, float alpha, MixPose pose, - float[] timelinesRotation, int i, bool firstFrame) { - - if (firstFrame) timelinesRotation[i] = 0; - - if (alpha == 1) { - rotateTimeline.Apply(skeleton, 0, time, null, 1, pose, MixDirection.In); - return; - } - - Bone bone = skeleton.bones.Items[rotateTimeline.boneIndex]; - float[] frames = rotateTimeline.frames; - if (time < frames[0]) { - if (pose == MixPose.Setup) bone.rotation = bone.data.rotation; - return; - } - - float r2; - if (time >= frames[frames.Length - RotateTimeline.ENTRIES]) // Time is after last frame. - r2 = bone.data.rotation + frames[frames.Length + RotateTimeline.PREV_ROTATION]; - else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, RotateTimeline.ENTRIES); - float prevRotation = frames[frame + RotateTimeline.PREV_ROTATION]; - float frameTime = frames[frame]; - float percent = rotateTimeline.GetCurvePercent((frame >> 1) - 1, - 1 - (time - frameTime) / (frames[frame + RotateTimeline.PREV_TIME] - frameTime)); - - r2 = frames[frame + RotateTimeline.ROTATION] - prevRotation; - r2 -= (16384 - (int)(16384.499999999996 - r2 / 360)) * 360; - r2 = prevRotation + r2 * percent + bone.data.rotation; - r2 -= (16384 - (int)(16384.499999999996 - r2 / 360)) * 360; - } - - // Mix between rotations using the direction of the shortest route on the first frame while detecting crosses. - float r1 = pose == MixPose.Setup ? bone.data.rotation : bone.rotation; - float total, diff = r2 - r1; - if (diff == 0) { - total = timelinesRotation[i]; - } else { - diff -= (16384 - (int)(16384.499999999996 - diff / 360)) * 360; - float lastTotal, lastDiff; - if (firstFrame) { - lastTotal = 0; - lastDiff = diff; - } else { - lastTotal = timelinesRotation[i]; // Angle and direction of mix, including loops. - lastDiff = timelinesRotation[i + 1]; // Difference between bones. - } - bool current = diff > 0, dir = lastTotal >= 0; - // Detect cross at 0 (not 180). - if (Math.Sign(lastDiff) != Math.Sign(diff) && Math.Abs(lastDiff) <= 90) { - // A cross after a 360 rotation is a loop. - if (Math.Abs(lastTotal) > 180) lastTotal += 360 * Math.Sign(lastTotal); - dir = current; - } - total = diff + lastTotal - lastTotal % 360; // Store loops as part of lastTotal. - if (dir != current) total += 360 * Math.Sign(lastTotal); - timelinesRotation[i] = total; - } - timelinesRotation[i + 1] = diff; - r1 += total * alpha; - bone.rotation = r1 - (16384 - (int)(16384.499999999996 - r1 / 360)) * 360; - } - - private void QueueEvents (TrackEntry entry, float animationTime) { - float animationStart = entry.animationStart, animationEnd = entry.animationEnd; - float duration = animationEnd - animationStart; - float trackLastWrapped = entry.trackLast % duration; - - // Queue events before complete. - var events = this.events; - var eventsItems = events.Items; - int i = 0, n = events.Count; - for (; i < n; i++) { - var e = eventsItems[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. - if (entry.loop ? (trackLastWrapped > entry.trackTime % duration) - : (animationTime >= animationEnd && entry.animationLast < animationEnd)) { - queue.Complete(entry); - } - - // Queue events after complete. - for (; i < n; i++) { - Event e = eventsItems[i]; - if (e.time < animationStart) continue; // Discard events outside animation start/end. - queue.Event(entry, eventsItems[i]); - } - } - - /// - /// Removes all animations from all tracks, leaving skeletons in their previous pose. - /// It may be desired to use to mix the skeletons back to the setup pose, - /// rather than leaving them in their previous pose. - public void ClearTracks () { - bool oldDrainDisabled = queue.drainDisabled; - queue.drainDisabled = true; - for (int i = 0, n = tracks.Count; i < n; i++) { - ClearTrack(i); - } - tracks.Clear(); - queue.drainDisabled = oldDrainDisabled; - queue.Drain(); - } - - /// - /// Removes all animations from the tracks, leaving skeletons in their previous pose. - /// It may be desired to use to mix the skeletons back to the setup pose, - /// rather than leaving them in their previous pose. - public void ClearTrack (int trackIndex) { - if (trackIndex >= tracks.Count) return; - TrackEntry current = tracks.Items[trackIndex]; - if (current == null) return; - - queue.End(current); - - DisposeNext(current); - - TrackEntry entry = current; - while (true) { - TrackEntry from = entry.mixingFrom; - if (from == null) break; - queue.End(from); - entry.mixingFrom = null; - entry = from; - } - - tracks.Items[current.trackIndex] = null; - - queue.Drain(); - } - - /// Sets the active TrackEntry for a given track number. - private void SetCurrent (int index, TrackEntry current, bool interrupt) { - TrackEntry from = ExpandToIndex(index); - tracks.Items[index] = current; - - if (from != null) { - if (interrupt) queue.Interrupt(from); - current.mixingFrom = from; - current.mixTime = 0; - - // Store interrupted mix percentage. - if (from.mixingFrom != null && from.mixDuration > 0) - current.interruptAlpha *= Math.Min(1, from.mixTime / from.mixDuration); - - from.timelinesRotation.Clear(); // Reset rotation for mixing out, in case entry was mixed in. - } - - queue.Start(current); // triggers AnimationsChanged - } - - - /// Sets an animation by name. - public TrackEntry SetAnimation (int trackIndex, string animationName, bool loop) { - Animation animation = data.skeletonData.FindAnimation(animationName); - if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName"); - return SetAnimation(trackIndex, animation, loop); - } - - /// Sets the current animation for a track, discarding any queued animations. - /// 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 determines when the track is cleared. - /// - /// A track entry to allow further customization of animation playback. References to the track entry must not be kept - /// after . - public TrackEntry SetAnimation (int trackIndex, Animation animation, bool loop) { - if (animation == null) throw new ArgumentNullException("animation", "animation cannot be 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.Items[trackIndex] = current.mixingFrom; - queue.Interrupt(current); - queue.End(current); - DisposeNext(current); - current = current.mixingFrom; - interrupt = false; - } else { - DisposeNext(current); - } - } - TrackEntry entry = NewTrackEntry(trackIndex, animation, loop, current); - SetCurrent(trackIndex, entry, interrupt); - queue.Drain(); - return entry; - } - - /// Queues an animation by name. - /// - public TrackEntry AddAnimation (int trackIndex, string animationName, bool loop, float delay) { - Animation animation = data.skeletonData.FindAnimation(animationName); - if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName"); - return AddAnimation(trackIndex, animation, loop, 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 . - /// - /// 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. - /// - /// A track entry to allow further customization of animation playback. References to the track entry must not be kept - /// after - public TrackEntry AddAnimation (int trackIndex, Animation animation, bool loop, float delay) { - if (animation == null) throw new ArgumentNullException("animation", "animation cannot be 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(); - } else { - last.next = entry; - if (delay <= 0) { - float duration = last.animationEnd - last.animationStart; - if (duration != 0) - delay += duration * (1 + (int)(last.trackTime / duration)) - data.GetMix(last.animation, animation); - else - delay = 0; - } - } - - entry.delay = delay; - return entry; - } - - /// - /// Sets an empty animation for a track, discarding any queued animations, and mixes to it over the specified mix duration. - public TrackEntry SetEmptyAnimation (int trackIndex, float mixDuration) { - TrackEntry entry = SetAnimation(trackIndex, AnimationState.EmptyAnimation, false); - entry.mixDuration = mixDuration; - entry.trackEnd = mixDuration; - return entry; - } - - /// - /// 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. - /// - /// A track entry to allow further customization of animation playback. References to the track entry must not be kept after . - /// - /// Track number. - /// Mix duration. - /// 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. - public TrackEntry AddEmptyAnimation (int trackIndex, float mixDuration, float delay) { - if (delay <= 0) delay -= mixDuration; - TrackEntry entry = AddAnimation(trackIndex, AnimationState.EmptyAnimation, false, delay); - entry.mixDuration = mixDuration; - entry.trackEnd = mixDuration; - return entry; - } - - /// - /// Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix duration. - public void SetEmptyAnimations (float mixDuration) { - bool oldDrainDisabled = queue.drainDisabled; - queue.drainDisabled = true; - for (int i = 0, n = tracks.Count; i < n; i++) { - TrackEntry current = tracks.Items[i]; - if (current != null) SetEmptyAnimation(i, mixDuration); - } - queue.drainDisabled = oldDrainDisabled; - queue.Drain(); - } - - private TrackEntry ExpandToIndex (int index) { - if (index < tracks.Count) return tracks.Items[index]; - while (index >= tracks.Count) - tracks.Add(null); - return null; - } - - /// Object-pooling version of new TrackEntry. Obtain an unused TrackEntry from the pool and clear/initialize its values. - /// May be null. - private TrackEntry NewTrackEntry (int trackIndex, Animation animation, bool loop, TrackEntry last) { - TrackEntry entry = trackEntryPool.Obtain(); // Pooling - entry.trackIndex = trackIndex; - entry.animation = animation; - entry.loop = loop; - - entry.eventThreshold = 0; - entry.attachmentThreshold = 0; - entry.drawOrderThreshold = 0; - - entry.animationStart = 0; - entry.animationEnd = animation.Duration; - 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 = float.MaxValue; // loop ? float.MaxValue : animation.Duration; - entry.timeScale = 1; - - entry.alpha = 1; - entry.interruptAlpha = 1; - entry.mixTime = 0; - entry.mixDuration = (last == null) ? 0 : data.GetMix(last.animation, animation); - return entry; - } - - /// Dispose all track entries queued after the given TrackEntry. - private void DisposeNext (TrackEntry entry) { - TrackEntry next = entry.next; - while (next != null) { - queue.Dispose(next); - next = next.next; - } - entry.next = null; - } - - private void AnimationsChanged () { - animationsChanged = false; - - var propertyIDs = this.propertyIDs; - propertyIDs.Clear(); - var mixingTo = this.mixingTo; - - var tracksItems = tracks.Items; - for (int i = 0, n = tracks.Count; i < n; i++) { - var entry = tracksItems[i]; - if (entry != null) entry.SetTimelineData(null, mixingTo, propertyIDs); - } - } - - /// The track entry for the animation currently playing on the track, or null if no animation is currently playing. - public TrackEntry GetCurrent (int trackIndex) { - return (trackIndex >= tracks.Count) ? null : tracks.Items[trackIndex]; - } - - override public string ToString () { - var buffer = new System.Text.StringBuilder(); - for (int i = 0, n = tracks.Count; i < n; i++) { - TrackEntry entry = tracks.Items[i]; - if (entry == null) continue; - if (buffer.Length > 0) buffer.Append(", "); - buffer.Append(entry.ToString()); - } - return buffer.Length == 0 ? "" : buffer.ToString(); - } - - internal void OnStart (TrackEntry entry) { if (Start != null) Start(entry); } - internal void OnInterrupt (TrackEntry entry) { if (Interrupt != null) Interrupt(entry); } - internal void OnEnd (TrackEntry entry) { if (End != null) End(entry); } - internal void OnDispose (TrackEntry entry) { if (Dispose != null) Dispose(entry); } - internal void OnComplete (TrackEntry entry) { if (Complete != null) Complete(entry); } - internal void OnEvent (TrackEntry entry, Event e) { if (Event != null) Event(entry, e); } - } - - /// State for the playback of an animation. - public class TrackEntry : Pool.IPoolable { - internal Animation animation; - - internal TrackEntry next, mixingFrom; - internal int trackIndex; - - internal bool loop; - internal float eventThreshold, attachmentThreshold, drawOrderThreshold; - internal float animationStart, animationEnd, animationLast, nextAnimationLast; - internal float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale = 1f; - internal float alpha, mixTime, mixDuration, interruptAlpha, totalAlpha; - internal readonly ExposedList timelineData = new ExposedList(); - internal readonly ExposedList timelineDipMix = new ExposedList(); - internal readonly ExposedList timelinesRotation = new ExposedList(); - - // IPoolable.Reset() - public void Reset () { - next = null; - mixingFrom = null; - animation = null; - timelineData.Clear(); - timelineDipMix.Clear(); - timelinesRotation.Clear(); - - Start = null; - Interrupt = null; - End = null; - Dispose = null; - Complete = null; - Event = null; - } - - /// Sets the timeline data. - /// May be null. - internal TrackEntry SetTimelineData (TrackEntry to, ExposedList mixingToArray, HashSet propertyIDs) { - if (to != null) mixingToArray.Add(to); - var lastEntry = mixingFrom != null ? mixingFrom.SetTimelineData(this, mixingToArray, propertyIDs) : this; - if (to != null) mixingToArray.Pop(); - - var mixingTo = mixingToArray.Items; - int mixingToLast = mixingToArray.Count - 1; - var timelines = animation.timelines.Items; - int timelinesCount = animation.timelines.Count; - var timelineDataItems = timelineData.Resize(timelinesCount).Items; // timelineData.setSize(timelinesCount); - timelineDipMix.Clear(); - var timelineDipMixItems = timelineDipMix.Resize(timelinesCount).Items; //timelineDipMix.setSize(timelinesCount); - - // outer: - for (int i = 0; i < timelinesCount; i++) { - int id = timelines[i].PropertyId; - if (!propertyIDs.Add(id)) { - timelineDataItems[i] = AnimationState.Subsequent; - } else if (to == null || !to.HasTimeline(id)) { - timelineDataItems[i] = AnimationState.First; - } else { - for (int ii = mixingToLast; ii >= 0; ii--) { - var entry = mixingTo[ii]; - if (!entry.HasTimeline(id)) { - if (entry.mixDuration > 0) { - timelineDataItems[i] = AnimationState.DipMix; - timelineDipMixItems[i] = entry; - goto outer; // continue outer; - } - break; - } - } - timelineDataItems[i] = AnimationState.Dip; - } - outer: {} - } - return lastEntry; - } - - bool HasTimeline (int id) { - var timelines = animation.timelines.Items; - for (int i = 0, n = animation.timelines.Count; i < n; i++) - if (timelines[i].PropertyId == id) return true; - return false; - } - - /// The index of the track where this entry is either current or queued. - public int TrackIndex { get { return trackIndex; } } - - /// The animation to apply for this track entry. - public Animation Animation { get { return animation; } } - - /// - /// If true, the animation will repeat. If false, it will not, instead its last frame is applied if played beyond its duration. - public bool Loop { get { return loop; } set { loop = value; } } - - /// - /// 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. - public float Delay { get { return delay; } set { delay = value; } } - - /// - /// Current time in seconds this track entry has been the current track entry. The track time determines - /// . The track time can be set to start the animation at a time other than 0, without affecting looping. - public float TrackTime { get { return trackTime; } set { trackTime = value; } } - - /// - /// 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 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 to mix the properties back to the - /// setup pose over time, rather than have it happen instantly. - /// - public float TrackEnd { get { return trackEnd; } set { trackEnd = value; } } - - /// - /// 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 to the same value to - /// prevent timeline keys before the start time from triggering. - /// - public float AnimationStart { get { return animationStart; } set { animationStart = value; } } - - /// - /// Seconds for the last frame of this animation. Non-looping animations won't play past this time. Looping animations will - /// loop back to at this time. Defaults to the animation duration. - public float AnimationEnd { get { return animationEnd; } set { animationEnd = value; } } - - /// - /// 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. - public float AnimationLast { - get { return animationLast; } - set { - animationLast = value; - nextAnimationLast = value; - } - } - - /// - /// Uses to compute the animation time between . and - /// . When the track time is 0, the animation time is equal to the animation start time. - /// - public float AnimationTime { - get { - if (loop) { - float duration = animationEnd - animationStart; - if (duration == 0) return animationStart; - return (trackTime % duration) + animationStart; - } - return Math.Min(trackTime + animationStart, animationEnd); - } - } - - /// - /// Multiplier for the delta time when the animation state is updated, causing time for this animation to play slower or - /// faster. Defaults to 1. - /// - public float TimeScale { get { return timeScale; } set { timeScale = value; } } - - /// - /// 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. - /// - public float Alpha { get { return alpha; } set { alpha = value; } } - - /// - /// 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. - public float EventThreshold { get { return eventThreshold; } set { eventThreshold = value; } } - - /// - /// 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. - public float AttachmentThreshold { get { return attachmentThreshold; } set { attachmentThreshold = value; } } - - /// - /// 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. - /// - public float DrawOrderThreshold { get { return drawOrderThreshold; } set { drawOrderThreshold = value; } } - - /// - /// The animation queued to start after this animation, or null. - public TrackEntry Next { get { return next; } } - - /// - /// Returns true if at least one loop has been completed. - public bool IsComplete { - get { return trackTime >= animationEnd - animationStart; } - } - - /// - /// Seconds from 0 to the mix duration when mixing from the previous animation to this animation. May be slightly more than - /// when the mix is complete. - public float MixTime { get { return mixTime; } set { mixTime = value; } } - - /// - /// Seconds for mixing from the previous animation to this animation. Defaults to the value provided by - /// 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 is next called. - /// - /// When using with a - /// delay less than or equal to 0, note the is set using the mix duration from the - /// - /// - /// - public float MixDuration { get { return mixDuration; } set { mixDuration = value; } } - - /// - /// 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 linked list. - public TrackEntry MixingFrom { get { return mixingFrom; } } - - public event AnimationState.TrackEntryDelegate Start, Interrupt, End, Dispose, Complete; - public event AnimationState.TrackEntryEventDelegate Event; - internal void OnStart () { if (Start != null) Start(this); } - internal void OnInterrupt () { if (Interrupt != null) Interrupt(this); } - internal void OnEnd () { if (End != null) End(this); } - internal void OnDispose () { if (Dispose != null) Dispose(this); } - internal void OnComplete () { if (Complete != null) Complete(this); } - internal void OnEvent (Event e) { if (Event != null) Event(this, e); } - - /// - /// 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 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. - public void ResetRotationDirections () { - timelinesRotation.Clear(); - } - - override public string ToString () { - return animation == null ? "" : animation.name; - } - } - - class EventQueue { - private readonly List eventQueueEntries = new List(); - internal bool drainDisabled; - - private readonly AnimationState state; - private readonly Pool trackEntryPool; - internal event Action AnimationsChanged; - - internal EventQueue (AnimationState state, Action HandleAnimationsChanged, Pool trackEntryPool) { - this.state = state; - this.AnimationsChanged += HandleAnimationsChanged; - this.trackEntryPool = trackEntryPool; - } - - struct EventQueueEntry { - public EventType type; - public TrackEntry entry; - public Event e; - - public EventQueueEntry (EventType eventType, TrackEntry trackEntry, Event e = null) { - this.type = eventType; - this.entry = trackEntry; - this.e = e; - } - } - - enum EventType { - Start, Interrupt, End, Dispose, Complete, Event - } - - internal void Start (TrackEntry entry) { - eventQueueEntries.Add(new EventQueueEntry(EventType.Start, entry)); - if (AnimationsChanged != null) AnimationsChanged(); - } - - internal void Interrupt (TrackEntry entry) { - eventQueueEntries.Add(new EventQueueEntry(EventType.Interrupt, entry)); - } - - internal void End (TrackEntry entry) { - eventQueueEntries.Add(new EventQueueEntry(EventType.End, entry)); - if (AnimationsChanged != null) AnimationsChanged(); - } - - internal void Dispose (TrackEntry entry) { - eventQueueEntries.Add(new EventQueueEntry(EventType.Dispose, entry)); - } - - internal void Complete (TrackEntry entry) { - eventQueueEntries.Add(new EventQueueEntry(EventType.Complete, entry)); - } - - internal void Event (TrackEntry entry, Event e) { - eventQueueEntries.Add(new EventQueueEntry(EventType.Event, entry, e)); - } - - /// Raises all events in the queue and drains the queue. - internal void Drain () { - if (drainDisabled) return; - drainDisabled = true; - - var entries = this.eventQueueEntries; - AnimationState state = this.state; - - // Don't cache entries.Count so callbacks can queue their own events (eg, call SetAnimation in AnimationState_Complete). - for (int i = 0; i < entries.Count; i++) { - var queueEntry = entries[i]; - TrackEntry trackEntry = queueEntry.entry; - - switch (queueEntry.type) { - case EventType.Start: - trackEntry.OnStart(); - state.OnStart(trackEntry); - break; - case EventType.Interrupt: - trackEntry.OnInterrupt(); - state.OnInterrupt(trackEntry); - break; - case EventType.End: - trackEntry.OnEnd(); - state.OnEnd(trackEntry); - goto case EventType.Dispose; // Fall through. (C#) - case EventType.Dispose: - trackEntry.OnDispose(); - state.OnDispose(trackEntry); - trackEntryPool.Free(trackEntry); // Pooling - break; - case EventType.Complete: - trackEntry.OnComplete(); - state.OnComplete(trackEntry); - break; - case EventType.Event: - trackEntry.OnEvent(queueEntry.e); - state.OnEvent(trackEntry, queueEntry.e); - break; - } - } - eventQueueEntries.Clear(); - - drainDisabled = false; - } - - internal void Clear () { - eventQueueEntries.Clear(); - } - } - - public class Pool where T : class, new() { - public readonly int max; - readonly Stack freeObjects; - - public int Count { get { return freeObjects.Count; } } - public int Peak { get; private set; } - - public Pool (int initialCapacity = 16, int max = int.MaxValue) { - freeObjects = new Stack(initialCapacity); - this.max = max; - } - - public T Obtain () { - return freeObjects.Count == 0 ? new T() : freeObjects.Pop(); - } - - public void Free (T obj) { - if (obj == null) throw new ArgumentNullException("obj", "obj cannot be null"); - if (freeObjects.Count < max) { - freeObjects.Push(obj); - Peak = Math.Max(Peak, freeObjects.Count); - } - Reset(obj); - } - -// protected void FreeAll (List objects) { -// if (objects == null) throw new ArgumentNullException("objects", "objects cannot be null."); -// var freeObjects = this.freeObjects; -// int max = this.max; -// for (int i = 0; i < objects.Count; i++) { -// T obj = objects[i]; -// if (obj == null) continue; -// if (freeObjects.Count < max) freeObjects.Push(obj); -// Reset(obj); -// } -// Peak = Math.Max(Peak, freeObjects.Count); -// } - - public void Clear () { - freeObjects.Clear(); - } - - protected void Reset (T obj) { - var poolable = obj as IPoolable; - if (poolable != null) poolable.Reset(); - } - - public interface IPoolable { - void Reset (); - } - } +namespace Spine3_6_39 +{ + public class AnimationState + { + static readonly Animation EmptyAnimation = new Animation("", new ExposedList(), 0); + internal const int Subsequent = 0, First = 1, Dip = 2, DipMix = 3; + + private AnimationStateData data; + + Pool trackEntryPool = new Pool(); + private readonly ExposedList tracks = new ExposedList(); + private readonly ExposedList events = new ExposedList(); + private readonly EventQueue queue; // Initialized by constructor. + + private readonly HashSet propertyIDs = new HashSet(); + private readonly ExposedList mixingTo = new ExposedList(); + private bool animationsChanged; + + private float timeScale = 1; + + public AnimationStateData Data { get { return data; } } + /// A list of tracks that have animations, which may contain nulls. + public ExposedList Tracks { get { return tracks; } } + public float TimeScale { get { return timeScale; } set { timeScale = value; } } + + public delegate void TrackEntryDelegate(TrackEntry trackEntry); + public event TrackEntryDelegate Start, Interrupt, End, Dispose, Complete; + + public delegate void TrackEntryEventDelegate(TrackEntry trackEntry, Event e); + public event TrackEntryEventDelegate Event; + + public AnimationState(AnimationStateData data) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + this.data = data; + this.queue = new EventQueue( + this, + delegate { this.animationsChanged = true; }, + trackEntryPool + ); + } + + /// + /// Increments the track entry times, setting queued animations as current if needed + /// delta time + public void Update(float delta) + { + delta *= timeScale; + var tracksItems = tracks.Items; + for (int i = 0, n = tracks.Count; i < n; i++) + { + TrackEntry current = tracksItems[i]; + if (current == null) continue; + + 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 = nextTime + (delta * next.timeScale); + current.trackTime += currentDelta; + SetCurrent(i, next, true); + while (next.mixingFrom != null) + { + next.mixTime += currentDelta; + 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. + tracksItems[i] = null; + + queue.End(current); + DisposeNext(current); + continue; + } + if (current.mixingFrom != null && UpdateMixingFrom(current, delta)) + { + // End mixing from entries once all have completed. + var from = current.mixingFrom; + current.mixingFrom = null; + while (from != null) + { + queue.End(from); + from = from.mixingFrom; + } + } + + current.trackTime += currentDelta; + } + + queue.Drain(); + } + + /// Returns true when all mixing from entries are complete. + private bool UpdateMixingFrom(TrackEntry to, float delta) + { + TrackEntry from = to.mixingFrom; + if (from == null) return true; + + bool finished = UpdateMixingFrom(from, delta); + + // Require mixTime > 0 to ensure the mixing from entry was applied at least once. + if (to.mixTime > 0 && (to.mixTime >= to.mixDuration || to.timeScale == 0)) + { + // Require totalAlpha == 0 to ensure mixing is complete, unless mixDuration == 0 (the transition is a single frame). + if (from.totalAlpha == 0 || to.mixDuration == 0) + { + to.mixingFrom = from.mixingFrom; + to.interruptAlpha = from.interruptAlpha; + queue.End(from); + } + return finished; + } + + from.animationLast = from.nextAnimationLast; + from.trackLast = from.nextTrackLast; + from.trackTime += delta * from.timeScale; + to.mixTime += delta * to.timeScale; + return false; + } + + /// + /// 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. + public bool Apply(Skeleton skeleton) + { + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + if (animationsChanged) AnimationsChanged(); + + var events = this.events; + + bool applied = false; + var tracksItems = tracks.Items; + for (int i = 0, m = tracks.Count; i < m; i++) + { + TrackEntry current = tracksItems[i]; + if (current == null || current.delay > 0) continue; + applied = true; + MixPose currentPose = i == 0 ? MixPose.Current : MixPose.CurrentLayered; + + // Apply mixing from entries first. + float mix = current.alpha; + if (current.mixingFrom != null) + mix *= ApplyMixingFrom(current, skeleton, currentPose); + else if (current.trackTime >= current.trackEnd && current.next == null) // + mix = 0; // Set to setup pose the last time the entry will be applied. + + // Apply current entry. + float animationLast = current.animationLast, animationTime = current.AnimationTime; + int timelineCount = current.animation.timelines.Count; + var timelines = current.animation.timelines; + var timelinesItems = timelines.Items; + if (mix == 1) + { + for (int ii = 0; ii < timelineCount; ii++) + timelinesItems[ii].Apply(skeleton, animationLast, animationTime, events, 1, MixPose.Setup, MixDirection.In); + } + else + { + var timelineData = current.timelineData.Items; + + bool firstFrame = current.timelinesRotation.Count == 0; + if (firstFrame) current.timelinesRotation.EnsureCapacity(timelines.Count << 1); + var timelinesRotation = current.timelinesRotation.Items; + + for (int ii = 0; ii < timelineCount; ii++) + { + Timeline timeline = timelinesItems[ii]; + MixPose pose = timelineData[ii] >= AnimationState.First ? MixPose.Setup : currentPose; + var rotateTimeline = timeline as RotateTimeline; + if (rotateTimeline != null) + ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, mix, pose, timelinesRotation, ii << 1, firstFrame); + else + timeline.Apply(skeleton, animationLast, animationTime, events, mix, pose, MixDirection.In); + } + } + QueueEvents(current, animationTime); + events.Clear(false); + current.nextAnimationLast = animationTime; + current.nextTrackLast = current.trackTime; + } + + queue.Drain(); + return applied; + } + + private float ApplyMixingFrom(TrackEntry to, Skeleton skeleton, MixPose currentPose) + { + TrackEntry from = to.mixingFrom; + if (from.mixingFrom != null) ApplyMixingFrom(from, skeleton, currentPose); + + float mix; + if (to.mixDuration == 0) // Single frame mix to undo mixingFrom changes. + mix = 1; + else + { + mix = to.mixTime / to.mixDuration; + if (mix > 1) mix = 1; + } + + var eventBuffer = mix < from.eventThreshold ? this.events : null; + bool attachments = mix < from.attachmentThreshold, drawOrder = mix < from.drawOrderThreshold; + float animationLast = from.animationLast, animationTime = from.AnimationTime; + var timelines = from.animation.timelines; + int timelineCount = timelines.Count; + var timelinesItems = timelines.Items; + var timelineData = from.timelineData.Items; + var timelineDipMix = from.timelineDipMix.Items; + + bool firstFrame = from.timelinesRotation.Count == 0; + if (firstFrame) from.timelinesRotation.Resize(timelines.Count << 1); // from.timelinesRotation.setSize + var timelinesRotation = from.timelinesRotation.Items; + + MixPose pose; + float alphaDip = from.alpha * to.interruptAlpha, alphaMix = alphaDip * (1 - mix), alpha; + from.totalAlpha = 0; + for (int i = 0; i < timelineCount; i++) + { + Timeline timeline = timelinesItems[i]; + switch (timelineData[i]) + { + case Subsequent: + if (!attachments && timeline is AttachmentTimeline) continue; + if (!drawOrder && timeline is DrawOrderTimeline) continue; + pose = currentPose; + alpha = alphaMix; + break; + case First: + pose = MixPose.Setup; + alpha = alphaMix; + break; + case Dip: + pose = MixPose.Setup; + alpha = alphaDip; + break; + default: + pose = MixPose.Setup; + alpha = alphaDip; + var dipMix = timelineDipMix[i]; + alpha *= Math.Max(0, 1 - dipMix.mixTime / dipMix.mixDuration); + break; + } + from.totalAlpha += alpha; + var rotateTimeline = timeline as RotateTimeline; + if (rotateTimeline != null) + { + ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, alpha, pose, timelinesRotation, i << 1, firstFrame); + } + else + { + timeline.Apply(skeleton, animationLast, animationTime, eventBuffer, alpha, pose, MixDirection.Out); + } + } + + if (to.mixDuration > 0) QueueEvents(from, animationTime); + this.events.Clear(false); + from.nextAnimationLast = animationTime; + from.nextTrackLast = from.trackTime; + + return mix; + } + + static private void ApplyRotateTimeline(RotateTimeline rotateTimeline, Skeleton skeleton, float time, float alpha, MixPose pose, + float[] timelinesRotation, int i, bool firstFrame) + { + + if (firstFrame) timelinesRotation[i] = 0; + + if (alpha == 1) + { + rotateTimeline.Apply(skeleton, 0, time, null, 1, pose, MixDirection.In); + return; + } + + Bone bone = skeleton.bones.Items[rotateTimeline.boneIndex]; + float[] frames = rotateTimeline.frames; + if (time < frames[0]) + { + if (pose == MixPose.Setup) bone.rotation = bone.data.rotation; + return; + } + + float r2; + if (time >= frames[frames.Length - RotateTimeline.ENTRIES]) // Time is after last frame. + r2 = bone.data.rotation + frames[frames.Length + RotateTimeline.PREV_ROTATION]; + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, RotateTimeline.ENTRIES); + float prevRotation = frames[frame + RotateTimeline.PREV_ROTATION]; + float frameTime = frames[frame]; + float percent = rotateTimeline.GetCurvePercent((frame >> 1) - 1, + 1 - (time - frameTime) / (frames[frame + RotateTimeline.PREV_TIME] - frameTime)); + + r2 = frames[frame + RotateTimeline.ROTATION] - prevRotation; + r2 -= (16384 - (int)(16384.499999999996 - r2 / 360)) * 360; + r2 = prevRotation + r2 * percent + bone.data.rotation; + r2 -= (16384 - (int)(16384.499999999996 - r2 / 360)) * 360; + } + + // Mix between rotations using the direction of the shortest route on the first frame while detecting crosses. + float r1 = pose == MixPose.Setup ? bone.data.rotation : bone.rotation; + float total, diff = r2 - r1; + if (diff == 0) + { + total = timelinesRotation[i]; + } + else + { + diff -= (16384 - (int)(16384.499999999996 - diff / 360)) * 360; + float lastTotal, lastDiff; + if (firstFrame) + { + lastTotal = 0; + lastDiff = diff; + } + else + { + lastTotal = timelinesRotation[i]; // Angle and direction of mix, including loops. + lastDiff = timelinesRotation[i + 1]; // Difference between bones. + } + bool current = diff > 0, dir = lastTotal >= 0; + // Detect cross at 0 (not 180). + if (Math.Sign(lastDiff) != Math.Sign(diff) && Math.Abs(lastDiff) <= 90) + { + // A cross after a 360 rotation is a loop. + if (Math.Abs(lastTotal) > 180) lastTotal += 360 * Math.Sign(lastTotal); + dir = current; + } + total = diff + lastTotal - lastTotal % 360; // Store loops as part of lastTotal. + if (dir != current) total += 360 * Math.Sign(lastTotal); + timelinesRotation[i] = total; + } + timelinesRotation[i + 1] = diff; + r1 += total * alpha; + bone.rotation = r1 - (16384 - (int)(16384.499999999996 - r1 / 360)) * 360; + } + + private void QueueEvents(TrackEntry entry, float animationTime) + { + float animationStart = entry.animationStart, animationEnd = entry.animationEnd; + float duration = animationEnd - animationStart; + float trackLastWrapped = entry.trackLast % duration; + + // Queue events before complete. + var events = this.events; + var eventsItems = events.Items; + int i = 0, n = events.Count; + for (; i < n; i++) + { + var e = eventsItems[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. + if (entry.loop ? (trackLastWrapped > entry.trackTime % duration) + : (animationTime >= animationEnd && entry.animationLast < animationEnd)) + { + queue.Complete(entry); + } + + // Queue events after complete. + for (; i < n; i++) + { + Event e = eventsItems[i]; + if (e.time < animationStart) continue; // Discard events outside animation start/end. + queue.Event(entry, eventsItems[i]); + } + } + + /// + /// Removes all animations from all tracks, leaving skeletons in their previous pose. + /// It may be desired to use to mix the skeletons back to the setup pose, + /// rather than leaving them in their previous pose. + public void ClearTracks() + { + bool oldDrainDisabled = queue.drainDisabled; + queue.drainDisabled = true; + for (int i = 0, n = tracks.Count; i < n; i++) + { + ClearTrack(i); + } + tracks.Clear(); + queue.drainDisabled = oldDrainDisabled; + queue.Drain(); + } + + /// + /// Removes all animations from the tracks, leaving skeletons in their previous pose. + /// It may be desired to use to mix the skeletons back to the setup pose, + /// rather than leaving them in their previous pose. + public void ClearTrack(int trackIndex) + { + if (trackIndex >= tracks.Count) return; + TrackEntry current = tracks.Items[trackIndex]; + if (current == null) return; + + queue.End(current); + + DisposeNext(current); + + TrackEntry entry = current; + while (true) + { + TrackEntry from = entry.mixingFrom; + if (from == null) break; + queue.End(from); + entry.mixingFrom = null; + entry = from; + } + + tracks.Items[current.trackIndex] = null; + + queue.Drain(); + } + + /// Sets the active TrackEntry for a given track number. + private void SetCurrent(int index, TrackEntry current, bool interrupt) + { + TrackEntry from = ExpandToIndex(index); + tracks.Items[index] = current; + + if (from != null) + { + if (interrupt) queue.Interrupt(from); + current.mixingFrom = from; + current.mixTime = 0; + + // Store interrupted mix percentage. + if (from.mixingFrom != null && from.mixDuration > 0) + current.interruptAlpha *= Math.Min(1, from.mixTime / from.mixDuration); + + from.timelinesRotation.Clear(); // Reset rotation for mixing out, in case entry was mixed in. + } + + queue.Start(current); // triggers AnimationsChanged + } + + + /// Sets an animation by name. + public TrackEntry SetAnimation(int trackIndex, string animationName, bool loop) + { + Animation animation = data.skeletonData.FindAnimation(animationName); + if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName"); + return SetAnimation(trackIndex, animation, loop); + } + + /// Sets the current animation for a track, discarding any queued animations. + /// 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 determines when the track is cleared. + /// + /// A track entry to allow further customization of animation playback. References to the track entry must not be kept + /// after . + public TrackEntry SetAnimation(int trackIndex, Animation animation, bool loop) + { + if (animation == null) throw new ArgumentNullException("animation", "animation cannot be 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.Items[trackIndex] = current.mixingFrom; + queue.Interrupt(current); + queue.End(current); + DisposeNext(current); + current = current.mixingFrom; + interrupt = false; + } + else + { + DisposeNext(current); + } + } + TrackEntry entry = NewTrackEntry(trackIndex, animation, loop, current); + SetCurrent(trackIndex, entry, interrupt); + queue.Drain(); + return entry; + } + + /// Queues an animation by name. + /// + public TrackEntry AddAnimation(int trackIndex, string animationName, bool loop, float delay) + { + Animation animation = data.skeletonData.FindAnimation(animationName); + if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName"); + return AddAnimation(trackIndex, animation, loop, 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 . + /// + /// 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. + /// + /// A track entry to allow further customization of animation playback. References to the track entry must not be kept + /// after + public TrackEntry AddAnimation(int trackIndex, Animation animation, bool loop, float delay) + { + if (animation == null) throw new ArgumentNullException("animation", "animation cannot be 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(); + } + else + { + last.next = entry; + if (delay <= 0) + { + float duration = last.animationEnd - last.animationStart; + if (duration != 0) + delay += duration * (1 + (int)(last.trackTime / duration)) - data.GetMix(last.animation, animation); + else + delay = 0; + } + } + + entry.delay = delay; + return entry; + } + + /// + /// Sets an empty animation for a track, discarding any queued animations, and mixes to it over the specified mix duration. + public TrackEntry SetEmptyAnimation(int trackIndex, float mixDuration) + { + TrackEntry entry = SetAnimation(trackIndex, AnimationState.EmptyAnimation, false); + entry.mixDuration = mixDuration; + entry.trackEnd = mixDuration; + return entry; + } + + /// + /// 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. + /// + /// A track entry to allow further customization of animation playback. References to the track entry must not be kept after . + /// + /// Track number. + /// Mix duration. + /// 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. + public TrackEntry AddEmptyAnimation(int trackIndex, float mixDuration, float delay) + { + if (delay <= 0) delay -= mixDuration; + TrackEntry entry = AddAnimation(trackIndex, AnimationState.EmptyAnimation, false, delay); + entry.mixDuration = mixDuration; + entry.trackEnd = mixDuration; + return entry; + } + + /// + /// Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix duration. + public void SetEmptyAnimations(float mixDuration) + { + bool oldDrainDisabled = queue.drainDisabled; + queue.drainDisabled = true; + for (int i = 0, n = tracks.Count; i < n; i++) + { + TrackEntry current = tracks.Items[i]; + if (current != null) SetEmptyAnimation(i, mixDuration); + } + queue.drainDisabled = oldDrainDisabled; + queue.Drain(); + } + + private TrackEntry ExpandToIndex(int index) + { + if (index < tracks.Count) return tracks.Items[index]; + while (index >= tracks.Count) + tracks.Add(null); + return null; + } + + /// Object-pooling version of new TrackEntry. Obtain an unused TrackEntry from the pool and clear/initialize its values. + /// May be null. + private TrackEntry NewTrackEntry(int trackIndex, Animation animation, bool loop, TrackEntry last) + { + TrackEntry entry = trackEntryPool.Obtain(); // Pooling + entry.trackIndex = trackIndex; + entry.animation = animation; + entry.loop = loop; + + entry.eventThreshold = 0; + entry.attachmentThreshold = 0; + entry.drawOrderThreshold = 0; + + entry.animationStart = 0; + entry.animationEnd = animation.Duration; + 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 = float.MaxValue; // loop ? float.MaxValue : animation.Duration; + entry.timeScale = 1; + + entry.alpha = 1; + entry.interruptAlpha = 1; + entry.mixTime = 0; + entry.mixDuration = (last == null) ? 0 : data.GetMix(last.animation, animation); + return entry; + } + + /// Dispose all track entries queued after the given TrackEntry. + private void DisposeNext(TrackEntry entry) + { + TrackEntry next = entry.next; + while (next != null) + { + queue.Dispose(next); + next = next.next; + } + entry.next = null; + } + + private void AnimationsChanged() + { + animationsChanged = false; + + var propertyIDs = this.propertyIDs; + propertyIDs.Clear(); + var mixingTo = this.mixingTo; + + var tracksItems = tracks.Items; + for (int i = 0, n = tracks.Count; i < n; i++) + { + var entry = tracksItems[i]; + if (entry != null) entry.SetTimelineData(null, mixingTo, propertyIDs); + } + } + + /// The track entry for the animation currently playing on the track, or null if no animation is currently playing. + public TrackEntry GetCurrent(int trackIndex) + { + return (trackIndex >= tracks.Count) ? null : tracks.Items[trackIndex]; + } + + override public string ToString() + { + var buffer = new System.Text.StringBuilder(); + for (int i = 0, n = tracks.Count; i < n; i++) + { + TrackEntry entry = tracks.Items[i]; + if (entry == null) continue; + if (buffer.Length > 0) buffer.Append(", "); + buffer.Append(entry.ToString()); + } + return buffer.Length == 0 ? "" : buffer.ToString(); + } + + internal void OnStart(TrackEntry entry) { if (Start != null) Start(entry); } + internal void OnInterrupt(TrackEntry entry) { if (Interrupt != null) Interrupt(entry); } + internal void OnEnd(TrackEntry entry) { if (End != null) End(entry); } + internal void OnDispose(TrackEntry entry) { if (Dispose != null) Dispose(entry); } + internal void OnComplete(TrackEntry entry) { if (Complete != null) Complete(entry); } + internal void OnEvent(TrackEntry entry, Event e) { if (Event != null) Event(entry, e); } + } + + /// State for the playback of an animation. + public class TrackEntry : Pool.IPoolable + { + internal Animation animation; + + internal TrackEntry next, mixingFrom; + internal int trackIndex; + + internal bool loop; + internal float eventThreshold, attachmentThreshold, drawOrderThreshold; + internal float animationStart, animationEnd, animationLast, nextAnimationLast; + internal float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale = 1f; + internal float alpha, mixTime, mixDuration, interruptAlpha, totalAlpha; + internal readonly ExposedList timelineData = new ExposedList(); + internal readonly ExposedList timelineDipMix = new ExposedList(); + internal readonly ExposedList timelinesRotation = new ExposedList(); + + // IPoolable.Reset() + public void Reset() + { + next = null; + mixingFrom = null; + animation = null; + timelineData.Clear(); + timelineDipMix.Clear(); + timelinesRotation.Clear(); + + Start = null; + Interrupt = null; + End = null; + Dispose = null; + Complete = null; + Event = null; + } + + /// Sets the timeline data. + /// May be null. + internal TrackEntry SetTimelineData(TrackEntry to, ExposedList mixingToArray, HashSet propertyIDs) + { + if (to != null) mixingToArray.Add(to); + var lastEntry = mixingFrom != null ? mixingFrom.SetTimelineData(this, mixingToArray, propertyIDs) : this; + if (to != null) mixingToArray.Pop(); + + var mixingTo = mixingToArray.Items; + int mixingToLast = mixingToArray.Count - 1; + var timelines = animation.timelines.Items; + int timelinesCount = animation.timelines.Count; + var timelineDataItems = timelineData.Resize(timelinesCount).Items; // timelineData.setSize(timelinesCount); + timelineDipMix.Clear(); + var timelineDipMixItems = timelineDipMix.Resize(timelinesCount).Items; //timelineDipMix.setSize(timelinesCount); + + // outer: + for (int i = 0; i < timelinesCount; i++) + { + int id = timelines[i].PropertyId; + if (!propertyIDs.Add(id)) + { + timelineDataItems[i] = AnimationState.Subsequent; + } + else if (to == null || !to.HasTimeline(id)) + { + timelineDataItems[i] = AnimationState.First; + } + else + { + for (int ii = mixingToLast; ii >= 0; ii--) + { + var entry = mixingTo[ii]; + if (!entry.HasTimeline(id)) + { + if (entry.mixDuration > 0) + { + timelineDataItems[i] = AnimationState.DipMix; + timelineDipMixItems[i] = entry; + goto outer; // continue outer; + } + break; + } + } + timelineDataItems[i] = AnimationState.Dip; + } + outer: { } + } + return lastEntry; + } + + bool HasTimeline(int id) + { + var timelines = animation.timelines.Items; + for (int i = 0, n = animation.timelines.Count; i < n; i++) + if (timelines[i].PropertyId == id) return true; + return false; + } + + /// The index of the track where this entry is either current or queued. + public int TrackIndex { get { return trackIndex; } } + + /// The animation to apply for this track entry. + public Animation Animation { get { return animation; } } + + /// + /// If true, the animation will repeat. If false, it will not, instead its last frame is applied if played beyond its duration. + public bool Loop { get { return loop; } set { loop = value; } } + + /// + /// 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. + public float Delay { get { return delay; } set { delay = value; } } + + /// + /// Current time in seconds this track entry has been the current track entry. The track time determines + /// . The track time can be set to start the animation at a time other than 0, without affecting looping. + public float TrackTime { get { return trackTime; } set { trackTime = value; } } + + /// + /// 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 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 to mix the properties back to the + /// setup pose over time, rather than have it happen instantly. + /// + public float TrackEnd { get { return trackEnd; } set { trackEnd = value; } } + + /// + /// 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 to the same value to + /// prevent timeline keys before the start time from triggering. + /// + public float AnimationStart { get { return animationStart; } set { animationStart = value; } } + + /// + /// Seconds for the last frame of this animation. Non-looping animations won't play past this time. Looping animations will + /// loop back to at this time. Defaults to the animation duration. + public float AnimationEnd { get { return animationEnd; } set { animationEnd = value; } } + + /// + /// 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. + public float AnimationLast + { + get { return animationLast; } + set + { + animationLast = value; + nextAnimationLast = value; + } + } + + /// + /// Uses to compute the animation time between . and + /// . When the track time is 0, the animation time is equal to the animation start time. + /// + public float AnimationTime + { + get + { + if (loop) + { + float duration = animationEnd - animationStart; + if (duration == 0) return animationStart; + return (trackTime % duration) + animationStart; + } + return Math.Min(trackTime + animationStart, animationEnd); + } + } + + /// + /// Multiplier for the delta time when the animation state is updated, causing time for this animation to play slower or + /// faster. Defaults to 1. + /// + public float TimeScale { get { return timeScale; } set { timeScale = value; } } + + /// + /// 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. + /// + public float Alpha { get { return alpha; } set { alpha = value; } } + + /// + /// 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. + public float EventThreshold { get { return eventThreshold; } set { eventThreshold = value; } } + + /// + /// 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. + public float AttachmentThreshold { get { return attachmentThreshold; } set { attachmentThreshold = value; } } + + /// + /// 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. + /// + public float DrawOrderThreshold { get { return drawOrderThreshold; } set { drawOrderThreshold = value; } } + + /// + /// The animation queued to start after this animation, or null. + public TrackEntry Next { get { return next; } } + + /// + /// Returns true if at least one loop has been completed. + public bool IsComplete + { + get { return trackTime >= animationEnd - animationStart; } + } + + /// + /// Seconds from 0 to the mix duration when mixing from the previous animation to this animation. May be slightly more than + /// when the mix is complete. + public float MixTime { get { return mixTime; } set { mixTime = value; } } + + /// + /// Seconds for mixing from the previous animation to this animation. Defaults to the value provided by + /// 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 is next called. + /// + /// When using with a + /// delay less than or equal to 0, note the is set using the mix duration from the + /// + /// + /// + public float MixDuration { get { return mixDuration; } set { mixDuration = value; } } + + /// + /// 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 linked list. + public TrackEntry MixingFrom { get { return mixingFrom; } } + + public event AnimationState.TrackEntryDelegate Start, Interrupt, End, Dispose, Complete; + public event AnimationState.TrackEntryEventDelegate Event; + internal void OnStart() { if (Start != null) Start(this); } + internal void OnInterrupt() { if (Interrupt != null) Interrupt(this); } + internal void OnEnd() { if (End != null) End(this); } + internal void OnDispose() { if (Dispose != null) Dispose(this); } + internal void OnComplete() { if (Complete != null) Complete(this); } + internal void OnEvent(Event e) { if (Event != null) Event(this, e); } + + /// + /// 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 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. + public void ResetRotationDirections() + { + timelinesRotation.Clear(); + } + + override public string ToString() + { + return animation == null ? "" : animation.name; + } + } + + class EventQueue + { + private readonly List eventQueueEntries = new List(); + internal bool drainDisabled; + + private readonly AnimationState state; + private readonly Pool trackEntryPool; + internal event Action AnimationsChanged; + + internal EventQueue(AnimationState state, Action HandleAnimationsChanged, Pool trackEntryPool) + { + this.state = state; + this.AnimationsChanged += HandleAnimationsChanged; + this.trackEntryPool = trackEntryPool; + } + + struct EventQueueEntry + { + public EventType type; + public TrackEntry entry; + public Event e; + + public EventQueueEntry(EventType eventType, TrackEntry trackEntry, Event e = null) + { + this.type = eventType; + this.entry = trackEntry; + this.e = e; + } + } + + enum EventType + { + Start, Interrupt, End, Dispose, Complete, Event + } + + internal void Start(TrackEntry entry) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.Start, entry)); + if (AnimationsChanged != null) AnimationsChanged(); + } + + internal void Interrupt(TrackEntry entry) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.Interrupt, entry)); + } + + internal void End(TrackEntry entry) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.End, entry)); + if (AnimationsChanged != null) AnimationsChanged(); + } + + internal void Dispose(TrackEntry entry) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.Dispose, entry)); + } + + internal void Complete(TrackEntry entry) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.Complete, entry)); + } + + internal void Event(TrackEntry entry, Event e) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.Event, entry, e)); + } + + /// Raises all events in the queue and drains the queue. + internal void Drain() + { + if (drainDisabled) return; + drainDisabled = true; + + var entries = this.eventQueueEntries; + AnimationState state = this.state; + + // Don't cache entries.Count so callbacks can queue their own events (eg, call SetAnimation in AnimationState_Complete). + for (int i = 0; i < entries.Count; i++) + { + var queueEntry = entries[i]; + TrackEntry trackEntry = queueEntry.entry; + + switch (queueEntry.type) + { + case EventType.Start: + trackEntry.OnStart(); + state.OnStart(trackEntry); + break; + case EventType.Interrupt: + trackEntry.OnInterrupt(); + state.OnInterrupt(trackEntry); + break; + case EventType.End: + trackEntry.OnEnd(); + state.OnEnd(trackEntry); + goto case EventType.Dispose; // Fall through. (C#) + case EventType.Dispose: + trackEntry.OnDispose(); + state.OnDispose(trackEntry); + trackEntryPool.Free(trackEntry); // Pooling + break; + case EventType.Complete: + trackEntry.OnComplete(); + state.OnComplete(trackEntry); + break; + case EventType.Event: + trackEntry.OnEvent(queueEntry.e); + state.OnEvent(trackEntry, queueEntry.e); + break; + } + } + eventQueueEntries.Clear(); + + drainDisabled = false; + } + + internal void Clear() + { + eventQueueEntries.Clear(); + } + } + + public class Pool where T : class, new() + { + public readonly int max; + readonly Stack freeObjects; + + public int Count { get { return freeObjects.Count; } } + public int Peak { get; private set; } + + public Pool(int initialCapacity = 16, int max = int.MaxValue) + { + freeObjects = new Stack(initialCapacity); + this.max = max; + } + + public T Obtain() + { + return freeObjects.Count == 0 ? new T() : freeObjects.Pop(); + } + + public void Free(T obj) + { + if (obj == null) throw new ArgumentNullException("obj", "obj cannot be null"); + if (freeObjects.Count < max) + { + freeObjects.Push(obj); + Peak = Math.Max(Peak, freeObjects.Count); + } + Reset(obj); + } + + // protected void FreeAll (List objects) { + // if (objects == null) throw new ArgumentNullException("objects", "objects cannot be null."); + // var freeObjects = this.freeObjects; + // int max = this.max; + // for (int i = 0; i < objects.Count; i++) { + // T obj = objects[i]; + // if (obj == null) continue; + // if (freeObjects.Count < max) freeObjects.Push(obj); + // Reset(obj); + // } + // Peak = Math.Max(Peak, freeObjects.Count); + // } + + public void Clear() + { + freeObjects.Clear(); + } + + protected void Reset(T obj) + { + var poolable = obj as IPoolable; + if (poolable != null) poolable.Reset(); + } + + public interface IPoolable + { + void Reset(); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/AnimationStateData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/AnimationStateData.cs index 60e1c83..ba6d974 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/AnimationStateData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/AnimationStateData.cs @@ -31,85 +31,97 @@ using System; using System.Collections.Generic; -namespace Spine3_6_39 { +namespace Spine3_6_39 +{ - /// Stores mix (crossfade) durations to be applied when AnimationState animations are changed. - public class AnimationStateData { - internal SkeletonData skeletonData; - readonly Dictionary animationToMixTime = new Dictionary(AnimationPairComparer.Instance); - internal float defaultMix; + /// Stores mix (crossfade) durations to be applied when AnimationState animations are changed. + public class AnimationStateData + { + internal SkeletonData skeletonData; + readonly Dictionary animationToMixTime = new Dictionary(AnimationPairComparer.Instance); + internal float defaultMix; - /// The SkeletonData to look up animations when they are specified by name. - public SkeletonData SkeletonData { get { return skeletonData; } } + /// The SkeletonData to look up animations when they are specified by name. + public SkeletonData SkeletonData { get { return skeletonData; } } - /// - /// The mix duration to use when no mix duration has been specifically defined between two animations. - public float DefaultMix { get { return defaultMix; } set { defaultMix = value; } } + /// + /// The mix duration to use when no mix duration has been specifically defined between two animations. + public float DefaultMix { get { return defaultMix; } set { defaultMix = value; } } - public AnimationStateData (SkeletonData skeletonData) { - if (skeletonData == null) throw new ArgumentException ("skeletonData cannot be null."); - this.skeletonData = skeletonData; - } + public AnimationStateData(SkeletonData skeletonData) + { + if (skeletonData == null) throw new ArgumentException("skeletonData cannot be null."); + this.skeletonData = skeletonData; + } - /// Sets a mix duration by animation names. - public void SetMix (string fromName, string toName, float duration) { - Animation from = skeletonData.FindAnimation(fromName); - if (from == null) throw new ArgumentException("Animation not found: " + fromName); - Animation to = skeletonData.FindAnimation(toName); - if (to == null) throw new ArgumentException("Animation not found: " + toName); - SetMix(from, to, duration); - } + /// Sets a mix duration by animation names. + public void SetMix(string fromName, string toName, float duration) + { + Animation from = skeletonData.FindAnimation(fromName); + if (from == null) throw new ArgumentException("Animation not found: " + fromName); + Animation to = skeletonData.FindAnimation(toName); + if (to == null) throw new ArgumentException("Animation not found: " + toName); + SetMix(from, to, duration); + } - /// Sets a mix duration when changing from the specified animation to the other. - /// See TrackEntry.MixDuration. - public void SetMix (Animation from, Animation to, float duration) { - if (from == null) throw new ArgumentNullException("from", "from cannot be null."); - if (to == null) throw new ArgumentNullException("to", "to cannot be null."); - AnimationPair key = new AnimationPair(from, to); - animationToMixTime.Remove(key); - animationToMixTime.Add(key, duration); - } + /// Sets a mix duration when changing from the specified animation to the other. + /// See TrackEntry.MixDuration. + public void SetMix(Animation from, Animation to, float duration) + { + if (from == null) throw new ArgumentNullException("from", "from cannot be null."); + if (to == null) throw new ArgumentNullException("to", "to cannot be null."); + AnimationPair key = new AnimationPair(from, to); + animationToMixTime.Remove(key); + animationToMixTime.Add(key, 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. - /// - public float GetMix (Animation from, Animation to) { - if (from == null) throw new ArgumentNullException("from", "from cannot be null."); - if (to == null) throw new ArgumentNullException("to", "to cannot be null."); - AnimationPair key = new AnimationPair(from, to); - float duration; - if (animationToMixTime.TryGetValue(key, out duration)) return duration; - return defaultMix; - } + /// + /// The mix duration to use when changing from the specified animation to the other, + /// or the DefaultMix if no mix duration has been set. + /// + public float GetMix(Animation from, Animation to) + { + if (from == null) throw new ArgumentNullException("from", "from cannot be null."); + if (to == null) throw new ArgumentNullException("to", "to cannot be null."); + AnimationPair key = new AnimationPair(from, to); + float duration; + if (animationToMixTime.TryGetValue(key, out duration)) return duration; + return defaultMix; + } - struct AnimationPair { - public readonly Animation a1; - public readonly Animation a2; + struct AnimationPair + { + public readonly Animation a1; + public readonly Animation a2; - public AnimationPair (Animation a1, Animation a2) { - this.a1 = a1; - this.a2 = a2; - } + public AnimationPair(Animation a1, Animation a2) + { + this.a1 = a1; + this.a2 = a2; + } - public override string ToString () { - return a1.name + "->" + a2.name; - } - } + public override string ToString() + { + return a1.name + "->" + a2.name; + } + } - // Avoids boxing in the dictionary. - class AnimationPairComparer : IEqualityComparer { - internal static readonly AnimationPairComparer Instance = new AnimationPairComparer(); + // Avoids boxing in the dictionary. + class AnimationPairComparer : IEqualityComparer + { + internal static readonly AnimationPairComparer Instance = new AnimationPairComparer(); - bool IEqualityComparer.Equals (AnimationPair x, AnimationPair y) { - return ReferenceEquals(x.a1, y.a1) && ReferenceEquals(x.a2, y.a2); - } + bool IEqualityComparer.Equals(AnimationPair x, AnimationPair y) + { + return ReferenceEquals(x.a1, y.a1) && ReferenceEquals(x.a2, y.a2); + } - int IEqualityComparer.GetHashCode (AnimationPair obj) { - // from Tuple.CombineHashCodes // return (((h1 << 5) + h1) ^ h2); - int h1 = obj.a1.GetHashCode(); - return (((h1 << 5) + h1) ^ obj.a2.GetHashCode()); - } - } - } + int IEqualityComparer.GetHashCode(AnimationPair obj) + { + // from Tuple.CombineHashCodes // return (((h1 << 5) + h1) ^ h2); + int h1 = obj.a1.GetHashCode(); + return (((h1 << 5) + h1) ^ obj.a2.GetHashCode()); + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Atlas.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Atlas.cs index 633398f..1246f89 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Atlas.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Atlas.cs @@ -35,31 +35,34 @@ using System; using System.Collections.Generic; using System.IO; -using System.Reflection; #if WINDOWS_STOREAPP using System.Threading.Tasks; using Windows.Storage; #endif -namespace Spine3_6_39 { - public class Atlas : IEnumerable { - readonly List pages = new List(); - List regions = new List(); - TextureLoader textureLoader; - - #region IEnumerable implementation - public IEnumerator GetEnumerator () { - return regions.GetEnumerator(); - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () { - return regions.GetEnumerator(); - } - #endregion - - #if !(IS_UNITY) - #if WINDOWS_STOREAPP +namespace Spine3_6_39 +{ + public class Atlas : IEnumerable + { + readonly List pages = new List(); + List regions = new List(); + TextureLoader textureLoader; + + #region IEnumerable implementation + public IEnumerator GetEnumerator() + { + return regions.GetEnumerator(); + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return regions.GetEnumerator(); + } + #endregion + +#if !(IS_UNITY) +#if WINDOWS_STOREAPP private async Task ReadFile(string path, TextureLoader textureLoader) { var folder = Windows.ApplicationModel.Package.Current.InstalledLocation; var file = await folder.GetFileAsync(path).AsTask().ConfigureAwait(false); @@ -75,233 +78,264 @@ private async Task ReadFile(string path, TextureLoader textureLoader) { public Atlas(string path, TextureLoader textureLoader) { this.ReadFile(path, textureLoader).Wait(); } - #else +#else - public Atlas (string path, TextureLoader textureLoader) { + public Atlas(string path, TextureLoader textureLoader) + { - #if WINDOWS_PHONE +#if WINDOWS_PHONE Stream stream = Microsoft.Xna.Framework.TitleContainer.OpenStream(path); using (StreamReader reader = new StreamReader(stream)) { - #else - using (StreamReader reader = new StreamReader(path)) { - #endif // WINDOWS_PHONE - - try { - Load(reader, Path.GetDirectoryName(path), textureLoader); - } catch (Exception ex) { - throw new Exception("Error reading atlas file: " + path, ex); - } - - } - } - #endif // WINDOWS_STOREAPP - - #endif - - public Atlas (TextReader reader, string dir, TextureLoader textureLoader) { - Load(reader, dir, textureLoader); - } +#else + using (StreamReader reader = new StreamReader(path)) + { +#endif // WINDOWS_PHONE + + try + { + Load(reader, Path.GetDirectoryName(path), textureLoader); + } + catch (Exception ex) + { + throw new Exception("Error reading atlas file: " + path, ex); + } + + } + } +#endif // WINDOWS_STOREAPP - public Atlas (List pages, List regions) { - this.pages = pages; - this.regions = regions; - this.textureLoader = null; - } - - private void Load (TextReader reader, string imagesDir, TextureLoader textureLoader) { - if (textureLoader == null) throw new ArgumentNullException("textureLoader", "textureLoader cannot be null."); - this.textureLoader = textureLoader; - - string[] tuple = new string[4]; - AtlasPage page = null; - while (true) { - string line = reader.ReadLine(); - if (line == null) break; - if (line.Trim().Length == 0) - page = null; - else if (page == null) { - page = new AtlasPage(); - page.name = line; - - if (ReadTuple(reader, tuple) == 2) { // size is only optional for an atlas packed with an old TexturePacker. - page.width = int.Parse(tuple[0]); - page.height = int.Parse(tuple[1]); - ReadTuple(reader, tuple); - } - page.format = (Format)Enum.Parse(typeof(Format), tuple[0], false); - - ReadTuple(reader, tuple); - page.minFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[0], false); - page.magFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[1], false); - - string direction = ReadValue(reader); - page.uWrap = TextureWrap.ClampToEdge; - page.vWrap = TextureWrap.ClampToEdge; - if (direction == "x") - page.uWrap = TextureWrap.Repeat; - else if (direction == "y") - page.vWrap = TextureWrap.Repeat; - else if (direction == "xy") - page.uWrap = page.vWrap = TextureWrap.Repeat; - - textureLoader.Load(page, Path.Combine(imagesDir, line)); - - pages.Add(page); - - } else { - AtlasRegion region = new AtlasRegion(); - region.name = line; - region.page = page; - - region.rotate = Boolean.Parse(ReadValue(reader)); - - ReadTuple(reader, tuple); - int x = int.Parse(tuple[0]); - int y = int.Parse(tuple[1]); - - ReadTuple(reader, tuple); - int width = int.Parse(tuple[0]); - int height = int.Parse(tuple[1]); - - region.u = x / (float)page.width; - region.v = y / (float)page.height; - if (region.rotate) { - region.u2 = (x + height) / (float)page.width; - region.v2 = (y + width) / (float)page.height; - } else { - region.u2 = (x + width) / (float)page.width; - region.v2 = (y + height) / (float)page.height; - } - region.x = x; - region.y = y; - region.width = Math.Abs(width); - region.height = Math.Abs(height); - - if (ReadTuple(reader, tuple) == 4) { // split is optional - region.splits = new [] {int.Parse(tuple[0]), int.Parse(tuple[1]), - int.Parse(tuple[2]), int.Parse(tuple[3])}; - - if (ReadTuple(reader, tuple) == 4) { // pad is optional, but only present with splits - region.pads = new [] {int.Parse(tuple[0]), int.Parse(tuple[1]), - int.Parse(tuple[2]), int.Parse(tuple[3])}; - - ReadTuple(reader, tuple); - } - } - - region.originalWidth = int.Parse(tuple[0]); - region.originalHeight = int.Parse(tuple[1]); - - ReadTuple(reader, tuple); - region.offsetX = int.Parse(tuple[0]); - region.offsetY = int.Parse(tuple[1]); - - region.index = int.Parse(ReadValue(reader)); - - regions.Add(region); - } - } - } - - static string ReadValue (TextReader reader) { - string line = reader.ReadLine(); - int colon = line.IndexOf(':'); - if (colon == -1) throw new Exception("Invalid line: " + line); - return line.Substring(colon + 1).Trim(); - } - - /// Returns the number of tuple values read (1, 2 or 4). - static int ReadTuple (TextReader reader, string[] tuple) { - string line = reader.ReadLine(); - int colon = line.IndexOf(':'); - if (colon == -1) throw new Exception("Invalid line: " + line); - int i = 0, lastMatch = colon + 1; - for (; i < 3; i++) { - int comma = line.IndexOf(',', lastMatch); - if (comma == -1) break; - tuple[i] = line.Substring(lastMatch, comma - lastMatch).Trim(); - lastMatch = comma + 1; - } - tuple[i] = line.Substring(lastMatch).Trim(); - return i + 1; - } - - public void FlipV () { - for (int i = 0, n = regions.Count; i < n; i++) { - AtlasRegion region = regions[i]; - region.v = 1 - region.v; - region.v2 = 1 - region.v2; - } - } - - /// 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. - /// The region, or null. - public AtlasRegion FindRegion (string name) { - for (int i = 0, n = regions.Count; i < n; i++) - if (regions[i].name == name) return regions[i]; - return null; - } +#endif - public void Dispose () { - if (textureLoader == null) return; - for (int i = 0, n = pages.Count; i < n; i++) - textureLoader.Unload(pages[i].rendererObject); - } - } - - public enum Format { - Alpha, - Intensity, - LuminanceAlpha, - RGB565, - RGBA4444, - RGB888, - RGBA8888 - } - - public enum TextureFilter { - Nearest, - Linear, - MipMap, - MipMapNearestNearest, - MipMapLinearNearest, - MipMapNearestLinear, - MipMapLinearLinear - } - - public enum TextureWrap { - MirroredRepeat, - ClampToEdge, - Repeat - } - - public class AtlasPage { - public string name; - public Format format; - public TextureFilter minFilter; - public TextureFilter magFilter; - public TextureWrap uWrap; - public TextureWrap vWrap; - public Object rendererObject; - public int width, height; - } - - public class AtlasRegion { - public AtlasPage page; - public string name; - public int x, y, width, height; - public float u, v, u2, v2; - public float offsetX, offsetY; - public int originalWidth, originalHeight; - public int index; - public bool rotate; - public int[] splits; - public int[] pads; - } - - public interface TextureLoader { - void Load (AtlasPage page, string path); - void Unload (Object texture); - } + public Atlas(TextReader reader, string dir, TextureLoader textureLoader) + { + Load(reader, dir, textureLoader); + } + + public Atlas(List pages, List regions) + { + this.pages = pages; + this.regions = regions; + this.textureLoader = null; + } + + private void Load(TextReader reader, string imagesDir, TextureLoader textureLoader) + { + if (textureLoader == null) throw new ArgumentNullException("textureLoader", "textureLoader cannot be null."); + this.textureLoader = textureLoader; + + string[] tuple = new string[4]; + AtlasPage page = null; + while (true) + { + string line = reader.ReadLine(); + if (line == null) break; + if (line.Trim().Length == 0) + page = null; + else if (page == null) + { + page = new AtlasPage(); + page.name = line; + + if (ReadTuple(reader, tuple) == 2) + { // size is only optional for an atlas packed with an old TexturePacker. + page.width = int.Parse(tuple[0]); + page.height = int.Parse(tuple[1]); + ReadTuple(reader, tuple); + } + page.format = (Format)Enum.Parse(typeof(Format), tuple[0], false); + + ReadTuple(reader, tuple); + page.minFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[0], false); + page.magFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[1], false); + + string direction = ReadValue(reader); + page.uWrap = TextureWrap.ClampToEdge; + page.vWrap = TextureWrap.ClampToEdge; + if (direction == "x") + page.uWrap = TextureWrap.Repeat; + else if (direction == "y") + page.vWrap = TextureWrap.Repeat; + else if (direction == "xy") + page.uWrap = page.vWrap = TextureWrap.Repeat; + + textureLoader.Load(page, Path.Combine(imagesDir, line)); + + pages.Add(page); + + } + else + { + AtlasRegion region = new AtlasRegion(); + region.name = line; + region.page = page; + + region.rotate = Boolean.Parse(ReadValue(reader)); + + ReadTuple(reader, tuple); + int x = int.Parse(tuple[0]); + int y = int.Parse(tuple[1]); + + ReadTuple(reader, tuple); + int width = int.Parse(tuple[0]); + int height = int.Parse(tuple[1]); + + region.u = x / (float)page.width; + region.v = y / (float)page.height; + if (region.rotate) + { + region.u2 = (x + height) / (float)page.width; + region.v2 = (y + width) / (float)page.height; + } + else + { + region.u2 = (x + width) / (float)page.width; + region.v2 = (y + height) / (float)page.height; + } + region.x = x; + region.y = y; + region.width = Math.Abs(width); + region.height = Math.Abs(height); + + if (ReadTuple(reader, tuple) == 4) + { // split is optional + region.splits = new[] {int.Parse(tuple[0]), int.Parse(tuple[1]), + int.Parse(tuple[2]), int.Parse(tuple[3])}; + + if (ReadTuple(reader, tuple) == 4) + { // pad is optional, but only present with splits + region.pads = new[] {int.Parse(tuple[0]), int.Parse(tuple[1]), + int.Parse(tuple[2]), int.Parse(tuple[3])}; + + ReadTuple(reader, tuple); + } + } + + region.originalWidth = int.Parse(tuple[0]); + region.originalHeight = int.Parse(tuple[1]); + + ReadTuple(reader, tuple); + region.offsetX = int.Parse(tuple[0]); + region.offsetY = int.Parse(tuple[1]); + + region.index = int.Parse(ReadValue(reader)); + + regions.Add(region); + } + } + } + + static string ReadValue(TextReader reader) + { + string line = reader.ReadLine(); + int colon = line.IndexOf(':'); + if (colon == -1) throw new Exception("Invalid line: " + line); + return line.Substring(colon + 1).Trim(); + } + + /// Returns the number of tuple values read (1, 2 or 4). + static int ReadTuple(TextReader reader, string[] tuple) + { + string line = reader.ReadLine(); + int colon = line.IndexOf(':'); + if (colon == -1) throw new Exception("Invalid line: " + line); + int i = 0, lastMatch = colon + 1; + for (; i < 3; i++) + { + int comma = line.IndexOf(',', lastMatch); + if (comma == -1) break; + tuple[i] = line.Substring(lastMatch, comma - lastMatch).Trim(); + lastMatch = comma + 1; + } + tuple[i] = line.Substring(lastMatch).Trim(); + return i + 1; + } + + public void FlipV() + { + for (int i = 0, n = regions.Count; i < n; i++) + { + AtlasRegion region = regions[i]; + region.v = 1 - region.v; + region.v2 = 1 - region.v2; + } + } + + /// 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. + /// The region, or null. + public AtlasRegion FindRegion(string name) + { + for (int i = 0, n = regions.Count; i < n; i++) + if (regions[i].name == name) return regions[i]; + return null; + } + + public void Dispose() + { + if (textureLoader == null) return; + for (int i = 0, n = pages.Count; i < n; i++) + textureLoader.Unload(pages[i].rendererObject); + } + } + + public enum Format + { + Alpha, + Intensity, + LuminanceAlpha, + RGB565, + RGBA4444, + RGB888, + RGBA8888 + } + + public enum TextureFilter + { + Nearest, + Linear, + MipMap, + MipMapNearestNearest, + MipMapLinearNearest, + MipMapNearestLinear, + MipMapLinearLinear + } + + public enum TextureWrap + { + MirroredRepeat, + ClampToEdge, + Repeat + } + + public class AtlasPage + { + public string name; + public Format format; + public TextureFilter minFilter; + public TextureFilter magFilter; + public TextureWrap uWrap; + public TextureWrap vWrap; + public Object rendererObject; + public int width, height; + } + + public class AtlasRegion + { + public AtlasPage page; + public string name; + public int x, y, width, height; + public float u, v, u2, v2; + public float offsetX, offsetY; + public int originalWidth, originalHeight; + public int index; + public bool rotate; + public int[] splits; + public int[] pads; + } + + public interface TextureLoader + { + void Load(AtlasPage page, string path); + void Unload(Object texture); + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Attachments/AtlasAttachmentLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Attachments/AtlasAttachmentLoader.cs index b791542..5ed876a 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Attachments/AtlasAttachmentLoader.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Attachments/AtlasAttachmentLoader.cs @@ -30,80 +30,91 @@ using System; -namespace Spine3_6_39 { +namespace Spine3_6_39 +{ - /// - /// An AttachmentLoader that configures attachments using texture regions from an Atlas. - /// See Loading Skeleton Data in the Spine Runtimes Guide. - /// - public class AtlasAttachmentLoader : AttachmentLoader { - private Atlas[] atlasArray; + /// + /// An AttachmentLoader that configures attachments using texture regions from an Atlas. + /// See Loading Skeleton Data in the Spine Runtimes Guide. + /// + public class AtlasAttachmentLoader : AttachmentLoader + { + private Atlas[] atlasArray; - public AtlasAttachmentLoader (params Atlas[] atlasArray) { - if (atlasArray == null) throw new ArgumentNullException("atlas array cannot be null."); - this.atlasArray = atlasArray; - } + public AtlasAttachmentLoader(params Atlas[] atlasArray) + { + if (atlasArray == null) throw new ArgumentNullException("atlas array cannot be null."); + this.atlasArray = atlasArray; + } - public RegionAttachment NewRegionAttachment (Skin skin, string name, string path) { - AtlasRegion region = FindRegion(path); - if (region == null) throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name)); - RegionAttachment attachment = new RegionAttachment(name); - attachment.RendererObject = region; - attachment.SetUVs(region.u, region.v, region.u2, region.v2, region.rotate); - attachment.regionOffsetX = region.offsetX; - attachment.regionOffsetY = region.offsetY; - attachment.regionWidth = region.width; - attachment.regionHeight = region.height; - attachment.regionOriginalWidth = region.originalWidth; - attachment.regionOriginalHeight = region.originalHeight; - return attachment; - } + public RegionAttachment NewRegionAttachment(Skin skin, string name, string path) + { + AtlasRegion region = FindRegion(path); + if (region == null) throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name)); + RegionAttachment attachment = new RegionAttachment(name); + attachment.RendererObject = region; + attachment.SetUVs(region.u, region.v, region.u2, region.v2, region.rotate); + attachment.regionOffsetX = region.offsetX; + attachment.regionOffsetY = region.offsetY; + attachment.regionWidth = region.width; + attachment.regionHeight = region.height; + attachment.regionOriginalWidth = region.originalWidth; + attachment.regionOriginalHeight = region.originalHeight; + return attachment; + } - public MeshAttachment NewMeshAttachment (Skin skin, string name, string path) { - AtlasRegion region = FindRegion(path); - if (region == null) throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name)); - MeshAttachment attachment = new MeshAttachment(name); - attachment.RendererObject = region; - attachment.RegionU = region.u; - attachment.RegionV = region.v; - attachment.RegionU2 = region.u2; - attachment.RegionV2 = region.v2; - attachment.RegionRotate = region.rotate; - attachment.regionOffsetX = region.offsetX; - attachment.regionOffsetY = region.offsetY; - attachment.regionWidth = region.width; - attachment.regionHeight = region.height; - attachment.regionOriginalWidth = region.originalWidth; - attachment.regionOriginalHeight = region.originalHeight; - return attachment; - } + public MeshAttachment NewMeshAttachment(Skin skin, string name, string path) + { + AtlasRegion region = FindRegion(path); + if (region == null) throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name)); + MeshAttachment attachment = new MeshAttachment(name); + attachment.RendererObject = region; + attachment.RegionU = region.u; + attachment.RegionV = region.v; + attachment.RegionU2 = region.u2; + attachment.RegionV2 = region.v2; + attachment.RegionRotate = region.rotate; + attachment.regionOffsetX = region.offsetX; + attachment.regionOffsetY = region.offsetY; + attachment.regionWidth = region.width; + attachment.regionHeight = region.height; + attachment.regionOriginalWidth = region.originalWidth; + attachment.regionOriginalHeight = region.originalHeight; + return attachment; + } - public BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, string name) { - return new BoundingBoxAttachment(name); - } + public BoundingBoxAttachment NewBoundingBoxAttachment(Skin skin, string name) + { + return new BoundingBoxAttachment(name); + } - public PathAttachment NewPathAttachment (Skin skin, string name) { - return new PathAttachment(name); - } + public PathAttachment NewPathAttachment(Skin skin, string name) + { + return new PathAttachment(name); + } - public PointAttachment NewPointAttachment (Skin skin, string name) { - return new PointAttachment(name); - } + public PointAttachment NewPointAttachment(Skin skin, string name) + { + return new PointAttachment(name); + } - public ClippingAttachment NewClippingAttachment(Skin skin, string name) { - return new ClippingAttachment(name); - } + public ClippingAttachment NewClippingAttachment(Skin skin, string name) + { + return new ClippingAttachment(name); + } - public AtlasRegion FindRegion (string name) { - AtlasRegion region; + public AtlasRegion FindRegion(string name) + { + AtlasRegion region; - for (int i = 0; i < atlasArray.Length; i++) { - region = atlasArray[i].FindRegion(name); - if (region != null) - return region; - } + for (int i = 0; i < atlasArray.Length; i++) + { + region = atlasArray[i].FindRegion(name); + if (region != null) + return region; + } - return null; - } - } + return null; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Attachments/Attachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Attachments/Attachment.cs index 0bbbcc3..615f8ec 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Attachments/Attachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Attachments/Attachment.cs @@ -30,17 +30,21 @@ using System; -namespace Spine3_6_39 { - abstract public class Attachment { - public string Name { get; private set; } +namespace Spine3_6_39 +{ + abstract public class Attachment + { + public string Name { get; private set; } - public Attachment (String name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null"); - Name = name; - } + public Attachment(String name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null"); + Name = name; + } - override public string ToString () { - return Name; - } - } + override public string ToString() + { + return Name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Attachments/AttachmentLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Attachments/AttachmentLoader.cs index e919f75..cfb61a9 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Attachments/AttachmentLoader.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Attachments/AttachmentLoader.cs @@ -28,22 +28,24 @@ * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine3_6_39 { - public interface AttachmentLoader { - /// May be null to not load any attachment. - RegionAttachment NewRegionAttachment (Skin skin, string name, string path); +namespace Spine3_6_39 +{ + public interface AttachmentLoader + { + /// May be null to not load any attachment. + RegionAttachment NewRegionAttachment(Skin skin, string name, string path); - /// May be null to not load any attachment. - MeshAttachment NewMeshAttachment (Skin skin, string name, string path); + /// May be null to not load any attachment. + MeshAttachment NewMeshAttachment(Skin skin, string name, string path); - /// May be null to not load any attachment. - BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, string name); + /// May be null to not load any attachment. + BoundingBoxAttachment NewBoundingBoxAttachment(Skin skin, string name); - /// May be null to not load any attachment - PathAttachment NewPathAttachment (Skin skin, string name); + /// May be null to not load any attachment + PathAttachment NewPathAttachment(Skin skin, string name); - PointAttachment NewPointAttachment (Skin skin, string name); + PointAttachment NewPointAttachment(Skin skin, string name); - ClippingAttachment NewClippingAttachment (Skin skin, string name); - } + ClippingAttachment NewClippingAttachment(Skin skin, string name); + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Attachments/AttachmentType.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Attachments/AttachmentType.cs index 9e8e0b0..9b05c82 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Attachments/AttachmentType.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Attachments/AttachmentType.cs @@ -28,8 +28,10 @@ * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine3_6_39 { - public enum AttachmentType { - Region, Boundingbox, Mesh, Linkedmesh, Path, Point, Clipping - } +namespace Spine3_6_39 +{ + public enum AttachmentType + { + Region, Boundingbox, Mesh, Linkedmesh, Path, Point, Clipping + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Attachments/BoundingBoxAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Attachments/BoundingBoxAttachment.cs index 8d6031c..bffa812 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Attachments/BoundingBoxAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Attachments/BoundingBoxAttachment.cs @@ -28,13 +28,14 @@ * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; - -namespace Spine3_6_39 { - /// Attachment that has a polygon for bounds checking. - public class BoundingBoxAttachment : VertexAttachment { - public BoundingBoxAttachment (string name) - : base(name) { - } - } +namespace Spine3_6_39 +{ + /// Attachment that has a polygon for bounds checking. + public class BoundingBoxAttachment : VertexAttachment + { + public BoundingBoxAttachment(string name) + : base(name) + { + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Attachments/ClippingAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Attachments/ClippingAttachment.cs index 132685c..df877c9 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Attachments/ClippingAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Attachments/ClippingAttachment.cs @@ -28,15 +28,16 @@ * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; - -namespace Spine3_6_39 { - public class ClippingAttachment : VertexAttachment { +namespace Spine3_6_39 +{ + public class ClippingAttachment : VertexAttachment + { internal SlotData endSlot; public SlotData EndSlot { get { return endSlot; } set { endSlot = value; } } - public ClippingAttachment(string name) : base(name) { + public ClippingAttachment(string name) : base(name) + { } } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Attachments/MeshAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Attachments/MeshAttachment.cs index 944e912..78d2830 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Attachments/MeshAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Attachments/MeshAttachment.cs @@ -28,93 +28,104 @@ * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; +namespace Spine3_6_39 +{ + /// Attachment that displays a texture region using a mesh. + public class MeshAttachment : VertexAttachment + { + internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; + private MeshAttachment parentMesh; + internal float[] uvs, regionUVs; + internal int[] triangles; + internal float r = 1, g = 1, b = 1, a = 1; + internal int hulllength; + internal bool inheritDeform; -namespace Spine3_6_39 { - /// Attachment that displays a texture region using a mesh. - public class MeshAttachment : VertexAttachment { - internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; - private MeshAttachment parentMesh; - internal float[] uvs, regionUVs; - internal int[] triangles; - internal float r = 1, g = 1, b = 1, a = 1; - internal int hulllength; - internal bool inheritDeform; + public int HullLength { get { return hulllength; } set { hulllength = value; } } + public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } } + /// The UV pair for each vertex, normalized within the entire texture. + public float[] UVs { get { return uvs; } set { uvs = value; } } + public int[] Triangles { get { return triangles; } set { triangles = value; } } - public int HullLength { get { return hulllength; } set { hulllength = value; } } - public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } } - /// The UV pair for each vertex, normalized within the entire texture. - public float[] UVs { get { return uvs; } set { uvs = value; } } - public int[] Triangles { get { return triangles; } set { triangles = value; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } + public string Path { get; set; } + public object RendererObject; //public Object RendererObject { get; set; } + public float RegionU { get; set; } + public float RegionV { get; set; } + public float RegionU2 { get; set; } + public float RegionV2 { get; set; } + public bool RegionRotate { get; set; } + public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } + public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. + public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } + public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. + public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } + public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. - public string Path { get; set; } - public object RendererObject; //public Object RendererObject { get; set; } - public float RegionU { get; set; } - public float RegionV { get; set; } - public float RegionU2 { get; set; } - public float RegionV2 { get; set; } - public bool RegionRotate { get; set; } - public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } - public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. - public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } - public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. - public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } - public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. + public bool InheritDeform { get { return inheritDeform; } set { inheritDeform = value; } } - public bool InheritDeform { get { return inheritDeform; } set { inheritDeform = value; } } + public MeshAttachment ParentMesh + { + get { return parentMesh; } + set + { + parentMesh = value; + if (value != null) + { + bones = value.bones; + vertices = value.vertices; + worldVerticesLength = value.worldVerticesLength; + regionUVs = value.regionUVs; + triangles = value.triangles; + HullLength = value.HullLength; + Edges = value.Edges; + Width = value.Width; + Height = value.Height; + } + } + } - public MeshAttachment ParentMesh { - get { return parentMesh; } - set { - parentMesh = value; - if (value != null) { - bones = value.bones; - vertices = value.vertices; - worldVerticesLength = value.worldVerticesLength; - regionUVs = value.regionUVs; - triangles = value.triangles; - HullLength = value.HullLength; - Edges = value.Edges; - Width = value.Width; - Height = value.Height; - } - } - } + // Nonessential. + public int[] Edges { get; set; } + public float Width { get; set; } + public float Height { get; set; } - // Nonessential. - public int[] Edges { get; set; } - public float Width { get; set; } - public float Height { get; set; } + public MeshAttachment(string name) + : base(name) + { + } - public MeshAttachment (string name) - : base(name) { - } + public void UpdateUVs() + { + float u = RegionU, v = RegionV, width = RegionU2 - RegionU, height = RegionV2 - RegionV; + float[] regionUVs = this.regionUVs; + if (this.uvs == null || this.uvs.Length != regionUVs.Length) this.uvs = new float[regionUVs.Length]; + float[] uvs = this.uvs; + if (RegionRotate) + { + for (int i = 0, n = uvs.Length; i < n; i += 2) + { + uvs[i] = u + regionUVs[i + 1] * width; + uvs[i + 1] = v + height - regionUVs[i] * height; + } + } + else + { + for (int i = 0, n = uvs.Length; i < n; i += 2) + { + uvs[i] = u + regionUVs[i] * width; + uvs[i + 1] = v + regionUVs[i + 1] * height; + } + } + } - public void UpdateUVs () { - float u = RegionU, v = RegionV, width = RegionU2 - RegionU, height = RegionV2 - RegionV; - float[] regionUVs = this.regionUVs; - if (this.uvs == null || this.uvs.Length != regionUVs.Length) this.uvs = new float[regionUVs.Length]; - float[] uvs = this.uvs; - if (RegionRotate) { - for (int i = 0, n = uvs.Length; i < n; i += 2) { - uvs[i] = u + regionUVs[i + 1] * width; - uvs[i + 1] = v + height - regionUVs[i] * height; - } - } else { - for (int i = 0, n = uvs.Length; i < n; i += 2) { - uvs[i] = u + regionUVs[i] * width; - uvs[i + 1] = v + regionUVs[i + 1] * height; - } - } - } - - override public bool ApplyDeform (VertexAttachment sourceAttachment) { - return this == sourceAttachment || (inheritDeform && parentMesh == sourceAttachment); - } - } + override public bool ApplyDeform(VertexAttachment sourceAttachment) + { + return this == sourceAttachment || (inheritDeform && parentMesh == sourceAttachment); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Attachments/PathAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Attachments/PathAttachment.cs index 2b50271..9112595 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Attachments/PathAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Attachments/PathAttachment.cs @@ -29,20 +29,22 @@ *****************************************************************************/ using System; -using System.Collections.Generic; -namespace Spine3_6_39 { - public class PathAttachment : VertexAttachment { - internal float[] lengths; - internal bool closed, constantSpeed; +namespace Spine3_6_39 +{ + public class PathAttachment : VertexAttachment + { + internal float[] lengths; + internal bool closed, constantSpeed; - /// The length in the setup pose from the start of the path to the end of each curve. - public float[] Lengths { get { return lengths; } set { lengths = value; } } - public bool Closed { get { return closed; } set { closed = value; } } - public bool ConstantSpeed { get { return constantSpeed; } set { constantSpeed = value; } } + /// The length in the setup pose from the start of the path to the end of each curve. + public float[] Lengths { get { return lengths; } set { lengths = value; } } + public bool Closed { get { return closed; } set { closed = value; } } + public bool ConstantSpeed { get { return constantSpeed; } set { constantSpeed = value; } } - public PathAttachment (String name) - : base(name) { - } - } + public PathAttachment(String name) + : base(name) + { + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Attachments/PointAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Attachments/PointAttachment.cs index 2e27c51..b937ae1 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Attachments/PointAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Attachments/PointAttachment.cs @@ -28,34 +28,39 @@ * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine3_6_39 { - /// - /// 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 Point Attachments in the Spine User Guide. - ///

- public class PointAttachment : Attachment { - internal float x, y, rotation; - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public float Rotation { get { return rotation; } set { rotation = value; } } +namespace Spine3_6_39 +{ + /// + /// 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 Point Attachments in the Spine User Guide. + ///

+ public class PointAttachment : Attachment + { + internal float x, y, rotation; + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public float Rotation { get { return rotation; } set { rotation = value; } } - public PointAttachment (string name) - : base(name) { - } + public PointAttachment(string name) + : base(name) + { + } - public void ComputeWorldPosition (Bone bone, out float ox, out float oy) { - bone.LocalToWorld(this.x, this.y, out ox, out oy); - } + public void ComputeWorldPosition(Bone bone, out float ox, out float oy) + { + bone.LocalToWorld(this.x, this.y, out ox, out oy); + } - public float ComputeWorldRotation (Bone bone) { - float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation); - float ix = cos * bone.a + sin * bone.b; - float iy = cos * bone.c + sin * bone.d; - return MathUtils.Atan2(iy, ix) * MathUtils.RadDeg; - } - } + public float ComputeWorldRotation(Bone bone) + { + float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation); + float ix = cos * bone.a + sin * bone.b; + float iy = cos * bone.c + sin * bone.d; + return MathUtils.Atan2(iy, ix) * MathUtils.RadDeg; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Attachments/RegionAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Attachments/RegionAttachment.cs index d00665d..e9ebd65 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Attachments/RegionAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Attachments/RegionAttachment.cs @@ -28,148 +28,155 @@ * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; +namespace Spine3_6_39 +{ + /// Attachment that displays a texture region. + public class RegionAttachment : Attachment + { + public const int BLX = 0; + public const int BLY = 1; + public const int ULX = 2; + public const int ULY = 3; + public const int URX = 4; + public const int URY = 5; + public const int BRX = 6; + public const int BRY = 7; -namespace Spine3_6_39 { - /// Attachment that displays a texture region. - public class RegionAttachment : Attachment { - public const int BLX = 0; - public const int BLY = 1; - public const int ULX = 2; - public const int ULY = 3; - public const int URX = 4; - public const int URY = 5; - public const int BRX = 6; - public const int BRY = 7; + internal float x, y, rotation, scaleX = 1, scaleY = 1, width, height; + internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; + internal float[] offset = new float[8], uvs = new float[8]; + internal float r = 1, g = 1, b = 1, a = 1; - internal float x, y, rotation, scaleX = 1, scaleY = 1, width, height; - internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; - internal float[] offset = new float[8], uvs = new float[8]; - internal float r = 1, g = 1, b = 1, a = 1; + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public float Rotation { get { return rotation; } set { rotation = value; } } + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + public float Width { get { return width; } set { width = value; } } + public float Height { get { return height; } set { height = value; } } - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public float Rotation { get { return rotation; } set { rotation = value; } } - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - public float ScaleY { get { return scaleY; } set { scaleY = value; } } - public float Width { get { return width; } set { width = value; } } - public float Height { get { return height; } set { height = value; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } + public string Path { get; set; } + public object RendererObject; //public object RendererObject { get; set; } + public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } + public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. + public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } + public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. + public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } + public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. - public string Path { get; set; } - public object RendererObject; //public object RendererObject { get; set; } - public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } - public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. - public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } - public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. - public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } - public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. + public float[] Offset { get { return offset; } } + public float[] UVs { get { return uvs; } } - public float[] Offset { get { return offset; } } - public float[] UVs { get { return uvs; } } + public RegionAttachment(string name) + : base(name) + { + } - public RegionAttachment (string name) - : base(name) { - } + public void UpdateOffset() + { + float width = this.width; + float height = this.height; + float scaleX = this.scaleX; + float scaleY = this.scaleY; + float regionScaleX = width / regionOriginalWidth * scaleX; + float regionScaleY = height / regionOriginalHeight * scaleY; + float localX = -width / 2 * scaleX + regionOffsetX * regionScaleX; + float localY = -height / 2 * scaleY + regionOffsetY * regionScaleY; + float localX2 = localX + regionWidth * regionScaleX; + float localY2 = localY + regionHeight * regionScaleY; + float rotation = this.rotation; + float cos = MathUtils.CosDeg(rotation); + float sin = MathUtils.SinDeg(rotation); + float x = this.x; + float y = this.y; + 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; + float[] offset = this.offset; + offset[BLX] = localXCos - localYSin; + offset[BLY] = localYCos + localXSin; + offset[ULX] = localXCos - localY2Sin; + offset[ULY] = localY2Cos + localXSin; + offset[URX] = localX2Cos - localY2Sin; + offset[URY] = localY2Cos + localX2Sin; + offset[BRX] = localX2Cos - localYSin; + offset[BRY] = localYCos + localX2Sin; + } - public void UpdateOffset () { - float width = this.width; - float height = this.height; - float scaleX = this.scaleX; - float scaleY = this.scaleY; - float regionScaleX = width / regionOriginalWidth * scaleX; - float regionScaleY = height / regionOriginalHeight * scaleY; - float localX = -width / 2 * scaleX + regionOffsetX * regionScaleX; - float localY = -height / 2 * scaleY + regionOffsetY * regionScaleY; - float localX2 = localX + regionWidth * regionScaleX; - float localY2 = localY + regionHeight * regionScaleY; - float rotation = this.rotation; - float cos = MathUtils.CosDeg(rotation); - float sin = MathUtils.SinDeg(rotation); - float x = this.x; - float y = this.y; - 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; - float[] offset = this.offset; - offset[BLX] = localXCos - localYSin; - offset[BLY] = localYCos + localXSin; - offset[ULX] = localXCos - localY2Sin; - offset[ULY] = localY2Cos + localXSin; - offset[URX] = localX2Cos - localY2Sin; - offset[URY] = localY2Cos + localX2Sin; - offset[BRX] = localX2Cos - localYSin; - offset[BRY] = localYCos + localX2Sin; - } + public void SetUVs(float u, float v, float u2, float v2, bool rotate) + { + float[] uvs = this.uvs; + // UV values differ from RegionAttachment.java + if (rotate) + { + uvs[URX] = u; + uvs[URY] = v2; + uvs[BRX] = u; + uvs[BRY] = v; + uvs[BLX] = u2; + uvs[BLY] = v; + uvs[ULX] = u2; + uvs[ULY] = v2; + } + else + { + uvs[ULX] = u; + uvs[ULY] = v2; + uvs[URX] = u; + uvs[URY] = v; + uvs[BRX] = u2; + uvs[BRY] = v; + uvs[BLX] = u2; + uvs[BLY] = v2; + } + } - public void SetUVs (float u, float v, float u2, float v2, bool rotate) { - float[] uvs = this.uvs; - // UV values differ from RegionAttachment.java - if (rotate) { - uvs[URX] = u; - uvs[URY] = v2; - uvs[BRX] = u; - uvs[BRY] = v; - uvs[BLX] = u2; - uvs[BLY] = v; - uvs[ULX] = u2; - uvs[ULY] = v2; - } else { - uvs[ULX] = u; - uvs[ULY] = v2; - uvs[URX] = u; - uvs[URY] = v; - uvs[BRX] = u2; - uvs[BRY] = v; - uvs[BLX] = u2; - uvs[BLY] = v2; - } - } + /// Transforms the attachment's four vertices to world coordinates. + /// The parent bone. + /// The output world vertices. Must have a length greater than or equal to offset + 8. + /// The worldVertices index to begin writing values. + /// The number of worldVertices entries between the value pairs written. + public void ComputeWorldVertices(Bone bone, float[] worldVertices, int offset, int stride = 2) + { + float[] vertexOffset = this.offset; + float bwx = bone.worldX, bwy = bone.worldY; + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + float offsetX, offsetY; - /// Transforms the attachment's four vertices to world coordinates. - /// The parent bone. - /// The output world vertices. Must have a length greater than or equal to offset + 8. - /// The worldVertices index to begin writing values. - /// The number of worldVertices entries between the value pairs written. - public void ComputeWorldVertices (Bone bone, float[] worldVertices, int offset, int stride = 2) { - float[] vertexOffset = this.offset; - float bwx = bone.worldX, bwy = bone.worldY; - float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - float offsetX, offsetY; + // Vertex order is different from RegionAttachment.java + offsetX = vertexOffset[BRX]; // 0 + offsetY = vertexOffset[BRY]; // 1 + worldVertices[offset] = offsetX * a + offsetY * b + bwx; // bl + worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; + offset += stride; - // Vertex order is different from RegionAttachment.java - offsetX = vertexOffset[BRX]; // 0 - offsetY = vertexOffset[BRY]; // 1 - worldVertices[offset] = offsetX * a + offsetY * b + bwx; // bl - worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; - offset += stride; + offsetX = vertexOffset[BLX]; // 2 + offsetY = vertexOffset[BLY]; // 3 + worldVertices[offset] = offsetX * a + offsetY * b + bwx; // ul + worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; + offset += stride; - offsetX = vertexOffset[BLX]; // 2 - offsetY = vertexOffset[BLY]; // 3 - worldVertices[offset] = offsetX * a + offsetY * b + bwx; // ul - worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; - offset += stride; + offsetX = vertexOffset[ULX]; // 4 + offsetY = vertexOffset[ULY]; // 5 + worldVertices[offset] = offsetX * a + offsetY * b + bwx; // ur + worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; + offset += stride; - offsetX = vertexOffset[ULX]; // 4 - offsetY = vertexOffset[ULY]; // 5 - worldVertices[offset] = offsetX * a + offsetY * b + bwx; // ur - worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; - offset += stride; - - offsetX = vertexOffset[URX]; // 6 - offsetY = vertexOffset[URY]; // 7 - worldVertices[offset] = offsetX * a + offsetY * b + bwx; // br - worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; - //offset += stride; - } - } + offsetX = vertexOffset[URX]; // 6 + offsetY = vertexOffset[URY]; // 7 + worldVertices[offset] = offsetX * a + offsetY * b + bwx; // br + worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; + //offset += stride; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Attachments/VertexAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Attachments/VertexAttachment.cs index 25bca69..40e95fc 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Attachments/VertexAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Attachments/VertexAttachment.cs @@ -30,101 +30,118 @@ using System; -namespace Spine3_6_39 { - /// >An attachment with vertices that are transformed by one or more bones and can be deformed by a slot's vertices. - public class VertexAttachment : Attachment { - static int nextID = 0; - static readonly Object nextIdLock = new Object(); +namespace Spine3_6_39 +{ + /// >An attachment with vertices that are transformed by one or more bones and can be deformed by a slot's vertices. + public class VertexAttachment : Attachment + { + static int nextID = 0; + static readonly Object nextIdLock = new Object(); - internal readonly int id; - internal int[] bones; - internal float[] vertices; - internal int worldVerticesLength; + internal readonly int id; + internal int[] bones; + internal float[] vertices; + internal int worldVerticesLength; - /// Gets a unique ID for this attachment. - public int Id { get { return id; } } - public int[] Bones { get { return bones; } set { bones = value; } } - public float[] Vertices { get { return vertices; } set { vertices = value; } } - public int WorldVerticesLength { get { return worldVerticesLength; } set { worldVerticesLength = value; } } + /// Gets a unique ID for this attachment. + public int Id { get { return id; } } + public int[] Bones { get { return bones; } set { bones = value; } } + public float[] Vertices { get { return vertices; } set { vertices = value; } } + public int WorldVerticesLength { get { return worldVerticesLength; } set { worldVerticesLength = value; } } - public VertexAttachment (string name) - : base(name) { + public VertexAttachment(string name) + : base(name) + { - lock (VertexAttachment.nextIdLock) { - id = (VertexAttachment.nextID++ & 65535) << 11; - } - } + lock (VertexAttachment.nextIdLock) + { + id = (VertexAttachment.nextID++ & 65535) << 11; + } + } - public void ComputeWorldVertices (Slot slot, float[] worldVertices) { - ComputeWorldVertices(slot, 0, worldVerticesLength, worldVertices, 0); - } + public void ComputeWorldVertices(Slot slot, float[] worldVertices) + { + ComputeWorldVertices(slot, 0, worldVerticesLength, worldVertices, 0); + } - /// Transforms local vertices to world coordinates. - /// The index of the first value to transform. Each vertex has 2 values, x and y. - /// The number of world vertex values to output. Must be less than or equal to - start. - /// The output world vertices. Must have a length greater than or equal to + . - /// The index to begin writing values. - /// The number of entries between the value pairs written. - public void ComputeWorldVertices (Slot slot, int start, int count, float[] worldVertices, int offset, int stride = 2) { - count = offset + (count >> 1) * stride; - Skeleton skeleton = slot.bone.skeleton; - var deformArray = slot.attachmentVertices; - float[] vertices = this.vertices; - int[] bones = this.bones; - if (bones == null) { - if (deformArray.Count > 0) vertices = deformArray.Items; - Bone bone = slot.bone; - float x = bone.worldX, y = bone.worldY; - float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - for (int vv = start, w = offset; w < count; vv += 2, w += stride) { - float vx = vertices[vv], 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 (int i = 0; i < start; i += 2) { - int n = bones[v]; - v += n + 1; - skip += n; - } - var skeletonBones = skeleton.bones.Items; - if (deformArray.Count == 0) { - for (int w = offset, b = skip * 3; w < count; w += stride) { - float wx = 0, wy = 0; - int n = bones[v++]; - n += v; - for (; v < n; v++, b += 3) { - Bone bone = skeletonBones[bones[v]]; - float vx = vertices[b], vy = vertices[b + 1], 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 { - float[] deform = deformArray.Items; - for (int w = offset, b = skip * 3, f = skip << 1; w < count; w += stride) { - float wx = 0, wy = 0; - int n = bones[v++]; - n += v; - for (; v < n; v++, b += 3, f += 2) { - Bone bone = skeletonBones[bones[v]]; - float vx = vertices[b] + deform[f], vy = vertices[b + 1] + deform[f + 1], 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; - } - } - } + /// Transforms local vertices to world coordinates. + /// The index of the first value to transform. Each vertex has 2 values, x and y. + /// The number of world vertex values to output. Must be less than or equal to - start. + /// The output world vertices. Must have a length greater than or equal to + . + /// The index to begin writing values. + /// The number of entries between the value pairs written. + public void ComputeWorldVertices(Slot slot, int start, int count, float[] worldVertices, int offset, int stride = 2) + { + count = offset + (count >> 1) * stride; + Skeleton skeleton = slot.bone.skeleton; + var deformArray = slot.attachmentVertices; + float[] vertices = this.vertices; + int[] bones = this.bones; + if (bones == null) + { + if (deformArray.Count > 0) vertices = deformArray.Items; + Bone bone = slot.bone; + float x = bone.worldX, y = bone.worldY; + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + for (int vv = start, w = offset; w < count; vv += 2, w += stride) + { + float vx = vertices[vv], 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 (int i = 0; i < start; i += 2) + { + int n = bones[v]; + v += n + 1; + skip += n; + } + var skeletonBones = skeleton.bones.Items; + if (deformArray.Count == 0) + { + for (int w = offset, b = skip * 3; w < count; w += stride) + { + float wx = 0, wy = 0; + int n = bones[v++]; + n += v; + for (; v < n; v++, b += 3) + { + Bone bone = skeletonBones[bones[v]]; + float vx = vertices[b], vy = vertices[b + 1], 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 + { + float[] deform = deformArray.Items; + for (int w = offset, b = skip * 3, f = skip << 1; w < count; w += stride) + { + float wx = 0, wy = 0; + int n = bones[v++]; + n += v; + for (; v < n; v++, b += 3, f += 2) + { + Bone bone = skeletonBones[bones[v]]; + float vx = vertices[b] + deform[f], vy = vertices[b + 1] + deform[f + 1], 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; + } + } + } - /// Returns true if a deform originally applied to the specified attachment should be applied to this attachment. - virtual public bool ApplyDeform (VertexAttachment sourceAttachment) { - return this == sourceAttachment; - } - } + /// Returns true if a deform originally applied to the specified attachment should be applied to this attachment. + virtual public bool ApplyDeform(VertexAttachment sourceAttachment) + { + return this == sourceAttachment; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/BlendMode.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/BlendMode.cs index f83ff14..efedacf 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/BlendMode.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/BlendMode.cs @@ -28,8 +28,10 @@ * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine3_6_39 { - public enum BlendMode { - Normal, Additive, Multiply, Screen - } +namespace Spine3_6_39 +{ + public enum BlendMode + { + Normal, Additive, Multiply, Screen + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Bone.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Bone.cs index cdd3067..79067cf 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Bone.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Bone.cs @@ -30,359 +30,395 @@ using System; -namespace Spine3_6_39 { - /// - /// 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. - /// - /// - public class Bone : IUpdatable { - static public bool yDown; - - internal BoneData data; - internal Skeleton skeleton; - internal Bone parent; - internal ExposedList children = new ExposedList(); - internal float x, y, rotation, scaleX, scaleY, shearX, shearY; - internal float ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY; - internal bool appliedValid; - - internal float a, b, worldX; - internal float c, d, worldY; - -// internal float worldSignX, worldSignY; -// public float WorldSignX { get { return worldSignX; } } -// public float WorldSignY { get { return worldSignY; } } - - internal bool sorted; - - public BoneData Data { get { return data; } } - public Skeleton Skeleton { get { return skeleton; } } - public Bone Parent { get { return parent; } } - public ExposedList Children { get { return children; } } - /// The local X translation. - public float X { get { return x; } set { x = value; } } - /// The local Y translation. - public float Y { get { return y; } set { y = value; } } - /// The local rotation. - public float Rotation { get { return rotation; } set { rotation = value; } } - - /// The local scaleX. - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - - /// The local scaleY. - public float ScaleY { get { return scaleY; } set { scaleY = value; } } - - /// The local shearX. - public float ShearX { get { return shearX; } set { shearX = value; } } - - /// The local shearY. - public float ShearY { get { return shearY; } set { shearY = value; } } - - /// The rotation, as calculated by any constraints. - public float AppliedRotation { get { return arotation; } set { arotation = value; } } - - /// The applied local x translation. - public float AX { get { return ax; } set { ax = value; } } - - /// The applied local y translation. - public float AY { get { return ay; } set { ay = value; } } - - /// The applied local scaleX. - public float AScaleX { get { return ascaleX; } set { ascaleX = value; } } - - /// The applied local scaleY. - public float AScaleY { get { return ascaleY; } set { ascaleY = value; } } - - /// The applied local shearX. - public float AShearX { get { return ashearX; } set { ashearX = value; } } - - /// The applied local shearY. - public float AShearY { get { return ashearY; } set { ashearY = value; } } - - public float A { get { return a; } } - public float B { get { return b; } } - public float C { get { return c; } } - public float D { get { return d; } } - - public float WorldX { get { return worldX; } } - public float WorldY { get { return worldY; } } - public float WorldRotationX { get { return MathUtils.Atan2(c, a) * MathUtils.RadDeg; } } - public float WorldRotationY { get { return MathUtils.Atan2(d, b) * MathUtils.RadDeg; } } - - /// Returns the magnitide (always positive) of the world scale X. - public float WorldScaleX { get { return (float)Math.Sqrt(a * a + c * c); } } - /// Returns the magnitide (always positive) of the world scale Y. - public float WorldScaleY { get { return (float)Math.Sqrt(b * b + d * d); } } - - /// May be null. - public Bone (BoneData data, Skeleton skeleton, Bone parent) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.data = data; - this.skeleton = skeleton; - this.parent = parent; - SetToSetupPose(); - } - - /// Same as . This method exists for Bone to implement . - public void Update () { - UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); - } - - /// Computes the world transform using the parent bone and this bone's local transform. - public void UpdateWorldTransform () { - UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); - } - - /// Computes the world transform using the parent bone and the specified local transform. - public void UpdateWorldTransform (float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY) { - ax = x; - ay = y; - arotation = rotation; - ascaleX = scaleX; - ascaleY = scaleY; - ashearX = shearX; - ashearY = shearY; - appliedValid = true; - Skeleton skeleton = this.skeleton; - - Bone parent = this.parent; - if (parent == null) { // Root bone. - float rotationY = rotation + 90 + shearY; - float la = MathUtils.CosDeg(rotation + shearX) * scaleX; - float lb = MathUtils.CosDeg(rotationY) * scaleY; - float lc = MathUtils.SinDeg(rotation + shearX) * scaleX; - float ld = MathUtils.SinDeg(rotationY) * scaleY; - if (skeleton.flipX) { - x = -x; - la = -la; - lb = -lb; - } - if (skeleton.flipY != yDown) { - y = -y; - lc = -lc; - ld = -ld; - } - a = la; - b = lb; - c = lc; - d = ld; - worldX = x + skeleton.x; - worldY = y + skeleton.y; -// worldSignX = Math.Sign(scaleX); -// worldSignY = Math.Sign(scaleY); - return; - } - - float 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; -// worldSignX = parent.worldSignX * Math.Sign(scaleX); -// worldSignY = parent.worldSignY * Math.Sign(scaleY); - - switch (data.transformMode) { - case TransformMode.Normal: { - float rotationY = rotation + 90 + shearY; - float la = MathUtils.CosDeg(rotation + shearX) * scaleX; - float lb = MathUtils.CosDeg(rotationY) * scaleY; - float lc = MathUtils.SinDeg(rotation + shearX) * scaleX; - float ld = MathUtils.SinDeg(rotationY) * scaleY; - a = pa * la + pb * lc; - b = pa * lb + pb * ld; - c = pc * la + pd * lc; - d = pc * lb + pd * ld; - return; - } - case TransformMode.OnlyTranslation: { - float rotationY = rotation + 90 + shearY; - a = MathUtils.CosDeg(rotation + shearX) * scaleX; - b = MathUtils.CosDeg(rotationY) * scaleY; - c = MathUtils.SinDeg(rotation + shearX) * scaleX; - d = MathUtils.SinDeg(rotationY) * scaleY; - break; - } - case TransformMode.NoRotationOrReflection: { - float s = pa * pa + pc * pc, prx; - if (s > 0.0001f) { - s = Math.Abs(pa * pd - pb * pc) / s; - pb = pc * s; - pd = pa * s; - prx = MathUtils.Atan2(pc, pa) * MathUtils.RadDeg; - } else { - pa = 0; - pc = 0; - prx = 90 - MathUtils.Atan2(pd, pb) * MathUtils.RadDeg; - } - float rx = rotation + shearX - prx; - float ry = rotation + shearY - prx + 90; - float la = MathUtils.CosDeg(rx) * scaleX; - float lb = MathUtils.CosDeg(ry) * scaleY; - float lc = MathUtils.SinDeg(rx) * scaleX; - float ld = MathUtils.SinDeg(ry) * scaleY; - a = pa * la - pb * lc; - b = pa * lb - pb * ld; - c = pc * la + pd * lc; - d = pc * lb + pd * ld; - break; - } - case TransformMode.NoScale: - case TransformMode.NoScaleOrReflection: { - float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation); - float za = pa * cos + pb * sin; - float zc = pc * cos + pd * sin; - float s = (float)Math.Sqrt(za * za + zc * zc); - if (s > 0.00001f) s = 1 / s; - za *= s; - zc *= s; - s = (float)Math.Sqrt(za * za + zc * zc); - float r = MathUtils.PI / 2 + MathUtils.Atan2(zc, za); - float zb = MathUtils.Cos(r) * s; - float zd = MathUtils.Sin(r) * s; - float la = MathUtils.CosDeg(shearX) * scaleX; - float lb = MathUtils.CosDeg(90 + shearY) * scaleY; - float lc = MathUtils.SinDeg(shearX) * scaleX; - float ld = MathUtils.SinDeg(90 + shearY) * scaleY; - if (data.transformMode != TransformMode.NoScaleOrReflection? pa * pd - pb* pc< 0 : skeleton.flipX != skeleton.flipY) { - zb = -zb; - zd = -zd; - } - a = za * la + zb * lc; - b = za * lb + zb * ld; - c = zc * la + zd * lc; - d = zc * lb + zd * ld; - return; - } - } - - if (skeleton.flipX) { - a = -a; - b = -b; - } - if (skeleton.flipY != Bone.yDown) { - c = -c; - d = -d; - } - } - - public void SetToSetupPose () { - BoneData data = this.data; - x = data.x; - y = data.y; - rotation = data.rotation; - scaleX = data.scaleX; - scaleY = data.scaleY; - shearX = data.shearX; - shearY = data.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. - /// - internal void UpdateAppliedTransform () { - appliedValid = true; - Bone parent = this.parent; - if (parent == null) { - ax = worldX; - ay = worldY; - arotation = MathUtils.Atan2(c, a) * MathUtils.RadDeg; - ascaleX = (float)Math.Sqrt(a * a + c * c); - ascaleY = (float)Math.Sqrt(b * b + d * d); - ashearX = 0; - ashearY = MathUtils.Atan2(a * b + c * d, a * d - b * c) * MathUtils.RadDeg; - return; - } - float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d; - float pid = 1 / (pa * pd - pb * pc); - float dx = worldX - parent.worldX, dy = worldY - parent.worldY; - ax = (dx * pd * pid - dy * pb * pid); - ay = (dy * pa * pid - dx * pc * pid); - float ia = pid * pd; - float id = pid * pa; - float ib = pid * pb; - float ic = pid * pc; - float ra = ia * a - ib * c; - float rb = ia * b - ib * d; - float rc = id * c - ic * a; - float rd = id * d - ic * b; - ashearX = 0; - ascaleX = (float)Math.Sqrt(ra * ra + rc * rc); - if (ascaleX > 0.0001f) { - float det = ra * rd - rb * rc; - ascaleY = det / ascaleX; - ashearY = MathUtils.Atan2(ra * rb + rc * rd, det) * MathUtils.RadDeg; - arotation = MathUtils.Atan2(rc, ra) * MathUtils.RadDeg; - } else { - ascaleX = 0; - ascaleY = (float)Math.Sqrt(rb * rb + rd * rd); - ashearY = 0; - arotation = 90 - MathUtils.Atan2(rd, rb) * MathUtils.RadDeg; - } - } - - public void WorldToLocal (float worldX, float worldY, out float localX, out float localY) { - float a = this.a, b = this.b, c = this.c, d = this.d; - float invDet = 1 / (a * d - b * c); - float x = worldX - this.worldX, y = worldY - this.worldY; - localX = (x * d * invDet - y * b * invDet); - localY = (y * a * invDet - x * c * invDet); - } - - public void LocalToWorld (float localX, float localY, out float worldX, out float worldY) { - worldX = localX * a + localY * b + this.worldX; - worldY = localX * c + localY * d + this.worldY; - } - - public float WorldToLocalRotationX { - get { - Bone parent = this.parent; - if (parent == null) return arotation; - float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, a = this.a, c = this.c; - return MathUtils.Atan2(pa * c - pc * a, pd * a - pb * c) * MathUtils.RadDeg; - } - } - - public float WorldToLocalRotationY { - get { - Bone parent = this.parent; - if (parent == null) return arotation; - float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, b = this.b, d = this.d; - return MathUtils.Atan2(pa * d - pc * b, pd * b - pb * d) * MathUtils.RadDeg; - } - } - - public float WorldToLocalRotation (float worldRotation) { - float sin = MathUtils.SinDeg(worldRotation), cos = MathUtils.CosDeg(worldRotation); - return MathUtils.Atan2(a * sin - c * cos, d * cos - b * sin) * MathUtils.RadDeg; - } - - public float LocalToWorldRotation (float localRotation) { - float sin = MathUtils.SinDeg(localRotation), cos = MathUtils.CosDeg(localRotation); - return MathUtils.Atan2(cos * c + sin * d, cos * a + sin * b) * MathUtils.RadDeg; - } - - /// - /// Rotates the world transform the specified amount and sets isAppliedValid to false. - /// - /// Degrees. - public void RotateWorld (float degrees) { - float a = this.a, b = this.b, c = this.c, d = this.d; - float cos = MathUtils.CosDeg(degrees), sin = MathUtils.SinDeg(degrees); - this.a = cos * a - sin * c; - this.b = cos * b - sin * d; - this.c = sin * a + cos * c; - this.d = sin * b + cos * d; - appliedValid = false; - } - - override public string ToString () { - return data.name; - } - } +namespace Spine3_6_39 +{ + /// + /// 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. + /// + /// + public class Bone : IUpdatable + { + static public bool yDown; + + internal BoneData data; + internal Skeleton skeleton; + internal Bone parent; + internal ExposedList children = new ExposedList(); + internal float x, y, rotation, scaleX, scaleY, shearX, shearY; + internal float ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY; + internal bool appliedValid; + + internal float a, b, worldX; + internal float c, d, worldY; + + // internal float worldSignX, worldSignY; + // public float WorldSignX { get { return worldSignX; } } + // public float WorldSignY { get { return worldSignY; } } + + internal bool sorted; + + public BoneData Data { get { return data; } } + public Skeleton Skeleton { get { return skeleton; } } + public Bone Parent { get { return parent; } } + public ExposedList Children { get { return children; } } + /// The local X translation. + public float X { get { return x; } set { x = value; } } + /// The local Y translation. + public float Y { get { return y; } set { y = value; } } + /// The local rotation. + public float Rotation { get { return rotation; } set { rotation = value; } } + + /// The local scaleX. + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + + /// The local scaleY. + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + + /// The local shearX. + public float ShearX { get { return shearX; } set { shearX = value; } } + + /// The local shearY. + public float ShearY { get { return shearY; } set { shearY = value; } } + + /// The rotation, as calculated by any constraints. + public float AppliedRotation { get { return arotation; } set { arotation = value; } } + + /// The applied local x translation. + public float AX { get { return ax; } set { ax = value; } } + + /// The applied local y translation. + public float AY { get { return ay; } set { ay = value; } } + + /// The applied local scaleX. + public float AScaleX { get { return ascaleX; } set { ascaleX = value; } } + + /// The applied local scaleY. + public float AScaleY { get { return ascaleY; } set { ascaleY = value; } } + + /// The applied local shearX. + public float AShearX { get { return ashearX; } set { ashearX = value; } } + + /// The applied local shearY. + public float AShearY { get { return ashearY; } set { ashearY = value; } } + + public float A { get { return a; } } + public float B { get { return b; } } + public float C { get { return c; } } + public float D { get { return d; } } + + public float WorldX { get { return worldX; } } + public float WorldY { get { return worldY; } } + public float WorldRotationX { get { return MathUtils.Atan2(c, a) * MathUtils.RadDeg; } } + public float WorldRotationY { get { return MathUtils.Atan2(d, b) * MathUtils.RadDeg; } } + + /// Returns the magnitide (always positive) of the world scale X. + public float WorldScaleX { get { return (float)Math.Sqrt(a * a + c * c); } } + /// Returns the magnitide (always positive) of the world scale Y. + public float WorldScaleY { get { return (float)Math.Sqrt(b * b + d * d); } } + + /// May be null. + public Bone(BoneData data, Skeleton skeleton, Bone parent) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + this.skeleton = skeleton; + this.parent = parent; + SetToSetupPose(); + } + + /// Same as . This method exists for Bone to implement . + public void Update() + { + UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); + } + + /// Computes the world transform using the parent bone and this bone's local transform. + public void UpdateWorldTransform() + { + UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); + } + + /// Computes the world transform using the parent bone and the specified local transform. + public void UpdateWorldTransform(float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY) + { + ax = x; + ay = y; + arotation = rotation; + ascaleX = scaleX; + ascaleY = scaleY; + ashearX = shearX; + ashearY = shearY; + appliedValid = true; + Skeleton skeleton = this.skeleton; + + Bone parent = this.parent; + if (parent == null) + { // Root bone. + float rotationY = rotation + 90 + shearY; + float la = MathUtils.CosDeg(rotation + shearX) * scaleX; + float lb = MathUtils.CosDeg(rotationY) * scaleY; + float lc = MathUtils.SinDeg(rotation + shearX) * scaleX; + float ld = MathUtils.SinDeg(rotationY) * scaleY; + if (skeleton.flipX) + { + x = -x; + la = -la; + lb = -lb; + } + if (skeleton.flipY != yDown) + { + y = -y; + lc = -lc; + ld = -ld; + } + a = la; + b = lb; + c = lc; + d = ld; + worldX = x + skeleton.x; + worldY = y + skeleton.y; + // worldSignX = Math.Sign(scaleX); + // worldSignY = Math.Sign(scaleY); + return; + } + + float 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; + // worldSignX = parent.worldSignX * Math.Sign(scaleX); + // worldSignY = parent.worldSignY * Math.Sign(scaleY); + + switch (data.transformMode) + { + case TransformMode.Normal: + { + float rotationY = rotation + 90 + shearY; + float la = MathUtils.CosDeg(rotation + shearX) * scaleX; + float lb = MathUtils.CosDeg(rotationY) * scaleY; + float lc = MathUtils.SinDeg(rotation + shearX) * scaleX; + float ld = MathUtils.SinDeg(rotationY) * scaleY; + a = pa * la + pb * lc; + b = pa * lb + pb * ld; + c = pc * la + pd * lc; + d = pc * lb + pd * ld; + return; + } + case TransformMode.OnlyTranslation: + { + float rotationY = rotation + 90 + shearY; + a = MathUtils.CosDeg(rotation + shearX) * scaleX; + b = MathUtils.CosDeg(rotationY) * scaleY; + c = MathUtils.SinDeg(rotation + shearX) * scaleX; + d = MathUtils.SinDeg(rotationY) * scaleY; + break; + } + case TransformMode.NoRotationOrReflection: + { + float s = pa * pa + pc * pc, prx; + if (s > 0.0001f) + { + s = Math.Abs(pa * pd - pb * pc) / s; + pb = pc * s; + pd = pa * s; + prx = MathUtils.Atan2(pc, pa) * MathUtils.RadDeg; + } + else + { + pa = 0; + pc = 0; + prx = 90 - MathUtils.Atan2(pd, pb) * MathUtils.RadDeg; + } + float rx = rotation + shearX - prx; + float ry = rotation + shearY - prx + 90; + float la = MathUtils.CosDeg(rx) * scaleX; + float lb = MathUtils.CosDeg(ry) * scaleY; + float lc = MathUtils.SinDeg(rx) * scaleX; + float ld = MathUtils.SinDeg(ry) * scaleY; + a = pa * la - pb * lc; + b = pa * lb - pb * ld; + c = pc * la + pd * lc; + d = pc * lb + pd * ld; + break; + } + case TransformMode.NoScale: + case TransformMode.NoScaleOrReflection: + { + float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation); + float za = pa * cos + pb * sin; + float zc = pc * cos + pd * sin; + float s = (float)Math.Sqrt(za * za + zc * zc); + if (s > 0.00001f) s = 1 / s; + za *= s; + zc *= s; + s = (float)Math.Sqrt(za * za + zc * zc); + float r = MathUtils.PI / 2 + MathUtils.Atan2(zc, za); + float zb = MathUtils.Cos(r) * s; + float zd = MathUtils.Sin(r) * s; + float la = MathUtils.CosDeg(shearX) * scaleX; + float lb = MathUtils.CosDeg(90 + shearY) * scaleY; + float lc = MathUtils.SinDeg(shearX) * scaleX; + float ld = MathUtils.SinDeg(90 + shearY) * scaleY; + if (data.transformMode != TransformMode.NoScaleOrReflection ? pa * pd - pb * pc < 0 : skeleton.flipX != skeleton.flipY) + { + zb = -zb; + zd = -zd; + } + a = za * la + zb * lc; + b = za * lb + zb * ld; + c = zc * la + zd * lc; + d = zc * lb + zd * ld; + return; + } + } + + if (skeleton.flipX) + { + a = -a; + b = -b; + } + if (skeleton.flipY != Bone.yDown) + { + c = -c; + d = -d; + } + } + + public void SetToSetupPose() + { + BoneData data = this.data; + x = data.x; + y = data.y; + rotation = data.rotation; + scaleX = data.scaleX; + scaleY = data.scaleY; + shearX = data.shearX; + shearY = data.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. + /// + internal void UpdateAppliedTransform() + { + appliedValid = true; + Bone parent = this.parent; + if (parent == null) + { + ax = worldX; + ay = worldY; + arotation = MathUtils.Atan2(c, a) * MathUtils.RadDeg; + ascaleX = (float)Math.Sqrt(a * a + c * c); + ascaleY = (float)Math.Sqrt(b * b + d * d); + ashearX = 0; + ashearY = MathUtils.Atan2(a * b + c * d, a * d - b * c) * MathUtils.RadDeg; + return; + } + float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d; + float pid = 1 / (pa * pd - pb * pc); + float dx = worldX - parent.worldX, dy = worldY - parent.worldY; + ax = (dx * pd * pid - dy * pb * pid); + ay = (dy * pa * pid - dx * pc * pid); + float ia = pid * pd; + float id = pid * pa; + float ib = pid * pb; + float ic = pid * pc; + float ra = ia * a - ib * c; + float rb = ia * b - ib * d; + float rc = id * c - ic * a; + float rd = id * d - ic * b; + ashearX = 0; + ascaleX = (float)Math.Sqrt(ra * ra + rc * rc); + if (ascaleX > 0.0001f) + { + float det = ra * rd - rb * rc; + ascaleY = det / ascaleX; + ashearY = MathUtils.Atan2(ra * rb + rc * rd, det) * MathUtils.RadDeg; + arotation = MathUtils.Atan2(rc, ra) * MathUtils.RadDeg; + } + else + { + ascaleX = 0; + ascaleY = (float)Math.Sqrt(rb * rb + rd * rd); + ashearY = 0; + arotation = 90 - MathUtils.Atan2(rd, rb) * MathUtils.RadDeg; + } + } + + public void WorldToLocal(float worldX, float worldY, out float localX, out float localY) + { + float a = this.a, b = this.b, c = this.c, d = this.d; + float invDet = 1 / (a * d - b * c); + float x = worldX - this.worldX, y = worldY - this.worldY; + localX = (x * d * invDet - y * b * invDet); + localY = (y * a * invDet - x * c * invDet); + } + + public void LocalToWorld(float localX, float localY, out float worldX, out float worldY) + { + worldX = localX * a + localY * b + this.worldX; + worldY = localX * c + localY * d + this.worldY; + } + + public float WorldToLocalRotationX + { + get + { + Bone parent = this.parent; + if (parent == null) return arotation; + float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, a = this.a, c = this.c; + return MathUtils.Atan2(pa * c - pc * a, pd * a - pb * c) * MathUtils.RadDeg; + } + } + + public float WorldToLocalRotationY + { + get + { + Bone parent = this.parent; + if (parent == null) return arotation; + float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, b = this.b, d = this.d; + return MathUtils.Atan2(pa * d - pc * b, pd * b - pb * d) * MathUtils.RadDeg; + } + } + + public float WorldToLocalRotation(float worldRotation) + { + float sin = MathUtils.SinDeg(worldRotation), cos = MathUtils.CosDeg(worldRotation); + return MathUtils.Atan2(a * sin - c * cos, d * cos - b * sin) * MathUtils.RadDeg; + } + + public float LocalToWorldRotation(float localRotation) + { + float sin = MathUtils.SinDeg(localRotation), cos = MathUtils.CosDeg(localRotation); + return MathUtils.Atan2(cos * c + sin * d, cos * a + sin * b) * MathUtils.RadDeg; + } + + /// + /// Rotates the world transform the specified amount and sets isAppliedValid to false. + /// + /// Degrees. + public void RotateWorld(float degrees) + { + float a = this.a, b = this.b, c = this.c, d = this.d; + float cos = MathUtils.CosDeg(degrees), sin = MathUtils.SinDeg(degrees); + this.a = cos * a - sin * c; + this.b = cos * b - sin * d; + this.c = sin * a + cos * c; + this.d = sin * b + cos * d; + appliedValid = false; + } + + override public string ToString() + { + return data.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/BoneData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/BoneData.cs index 4c982c8..fea92ba 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/BoneData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/BoneData.cs @@ -30,71 +30,76 @@ using System; -namespace Spine3_6_39 { - public class BoneData { - internal int index; - internal string name; - internal BoneData parent; - internal float length; - internal float x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY; - internal TransformMode transformMode = TransformMode.Normal; - - /// The index of the bone in Skeleton.Bones - public int Index { get { return index; } } - - /// The name of the bone, which is unique within the skeleton. - public string Name { get { return name; } } - - /// May be null. - public BoneData Parent { get { return parent; } } - - public float Length { get { return length; } set { length = value; } } - - /// Local X translation. - public float X { get { return x; } set { x = value; } } - - /// Local Y translation. - public float Y { get { return y; } set { y = value; } } - - /// Local rotation. - public float Rotation { get { return rotation; } set { rotation = value; } } - - /// Local scaleX. - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - - /// Local scaleY. - public float ScaleY { get { return scaleY; } set { scaleY = value; } } - - /// Local shearX. - public float ShearX { get { return shearX; } set { shearX = value; } } - - /// Local shearY. - public float ShearY { get { return shearY; } set { shearY = value; } } - - /// The transform mode for how parent world transforms affect this bone. - public TransformMode TransformMode { get { return transformMode; } set { transformMode = value; } } - - /// May be null. - public BoneData (int index, string name, BoneData parent) { - if (index < 0) throw new ArgumentException("index must be >= 0", "index"); - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.index = index; - this.name = name; - this.parent = parent; - } - - override public string ToString () { - return name; - } - } - - [Flags] - public enum TransformMode { - //0000 0 Flip Scale Rotation - Normal = 0, // 0000 - OnlyTranslation = 7, // 0111 - NoRotationOrReflection = 1, // 0001 - NoScale = 2, // 0010 - NoScaleOrReflection = 6, // 0110 - } +namespace Spine3_6_39 +{ + public class BoneData + { + internal int index; + internal string name; + internal BoneData parent; + internal float length; + internal float x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY; + internal TransformMode transformMode = TransformMode.Normal; + + /// The index of the bone in Skeleton.Bones + public int Index { get { return index; } } + + /// The name of the bone, which is unique within the skeleton. + public string Name { get { return name; } } + + /// May be null. + public BoneData Parent { get { return parent; } } + + public float Length { get { return length; } set { length = value; } } + + /// Local X translation. + public float X { get { return x; } set { x = value; } } + + /// Local Y translation. + public float Y { get { return y; } set { y = value; } } + + /// Local rotation. + public float Rotation { get { return rotation; } set { rotation = value; } } + + /// Local scaleX. + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + + /// Local scaleY. + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + + /// Local shearX. + public float ShearX { get { return shearX; } set { shearX = value; } } + + /// Local shearY. + public float ShearY { get { return shearY; } set { shearY = value; } } + + /// The transform mode for how parent world transforms affect this bone. + public TransformMode TransformMode { get { return transformMode; } set { transformMode = value; } } + + /// May be null. + public BoneData(int index, string name, BoneData parent) + { + if (index < 0) throw new ArgumentException("index must be >= 0", "index"); + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.index = index; + this.name = name; + this.parent = parent; + } + + override public string ToString() + { + return name; + } + } + + [Flags] + public enum TransformMode + { + //0000 0 Flip Scale Rotation + Normal = 0, // 0000 + OnlyTranslation = 7, // 0111 + NoRotationOrReflection = 1, // 0001 + NoScale = 2, // 0010 + NoScaleOrReflection = 6, // 0110 + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Event.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Event.cs index 55adc03..2d840b5 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Event.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Event.cs @@ -30,31 +30,35 @@ using System; -namespace Spine3_6_39 { - /// Stores the current pose values for an Event. - public class Event { - internal readonly EventData data; - internal readonly float time; - internal int intValue; - internal float floatValue; - internal string stringValue; +namespace Spine3_6_39 +{ + /// Stores the current pose values for an Event. + public class Event + { + internal readonly EventData data; + internal readonly float time; + internal int intValue; + internal float floatValue; + internal string stringValue; - public EventData Data { get { return data; } } - /// The animation time this event was keyed. - public float Time { get { return time; } } + public EventData Data { get { return data; } } + /// The animation time this event was keyed. + public float Time { get { return time; } } - public int Int { get { return intValue; } set { intValue = value; } } - public float Float { get { return floatValue; } set { floatValue = value; } } - public string String { get { return stringValue; } set { stringValue = value; } } + public int Int { get { return intValue; } set { intValue = value; } } + public float Float { get { return floatValue; } set { floatValue = value; } } + public string String { get { return stringValue; } set { stringValue = value; } } - public Event (float time, EventData data) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - this.time = time; - this.data = data; - } + public Event(float time, EventData data) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + this.time = time; + this.data = data; + } - override public string ToString () { - return this.data.Name; - } - } + override public string ToString() + { + return this.data.Name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/EventData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/EventData.cs index d30b765..62e6b32 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/EventData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/EventData.cs @@ -30,24 +30,28 @@ using System; -namespace Spine3_6_39 { - /// Stores the setup pose values for an Event. - public class EventData { - internal string name; +namespace Spine3_6_39 +{ + /// Stores the setup pose values for an Event. + public class EventData + { + internal string name; - /// The name of the event, which is unique within the skeleton. - public string Name { get { return name; } } - public int Int { get; set; } - public float Float { get; set; } - public string String { get; set; } + /// The name of the event, which is unique within the skeleton. + public string Name { get { return name; } } + public int Int { get; set; } + public float Float { get; set; } + public string String { get; set; } - public EventData (string name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; - } + public EventData(string name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + } - override public string ToString () { - return Name; - } - } + override public string ToString() + { + return Name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/ExposedList.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/ExposedList.cs index 481780a..21caa54 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/ExposedList.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/ExposedList.cs @@ -35,581 +35,674 @@ using System.Collections.Generic; using System.Diagnostics; -namespace Spine3_6_39 { - [Serializable] - [DebuggerDisplay("Count={Count}")] - public class ExposedList : IEnumerable { - public T[] Items; - public int Count; - private const int DefaultCapacity = 4; - private static readonly T[] EmptyArray = new T[0]; - private int version; - - public ExposedList () { - Items = EmptyArray; - } - - public ExposedList (IEnumerable collection) { - CheckCollection(collection); - - // initialize to needed size (if determinable) - ICollection c = collection as ICollection; - if (c == null) { - Items = EmptyArray; - AddEnumerable(collection); - } else { - Items = new T[c.Count]; - AddCollection(c); - } - } - - public ExposedList (int capacity) { - if (capacity < 0) - throw new ArgumentOutOfRangeException("capacity"); - Items = new T[capacity]; - } - - internal ExposedList (T[] data, int size) { - Items = data; - Count = size; - } - - public void Add (T item) { - // If we check to see if we need to grow before trying to grow - // we can speed things up by 25% - if (Count == Items.Length) - GrowIfNeeded(1); - Items[Count++] = item; - version++; - } - - public void GrowIfNeeded (int newCount) { - int minimumSize = Count + newCount; - if (minimumSize > Items.Length) - Capacity = Math.Max(Math.Max(Capacity * 2, DefaultCapacity), minimumSize); - } - - public ExposedList Resize (int newSize) { - if (newSize > Items.Length) Array.Resize(ref Items, newSize); - Count = newSize; - return this; - } - - public void EnsureCapacity (int min) { - if (Items.Length < min) { - int newCapacity = Items.Length == 0 ? DefaultCapacity : Items.Length * 2; - //if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength; - if (newCapacity < min) newCapacity = min; - Capacity = newCapacity; - } - } - - private void CheckRange (int index, int count) { - if (index < 0) - throw new ArgumentOutOfRangeException("index"); - - if (count < 0) - throw new ArgumentOutOfRangeException("count"); - - if ((uint)index + (uint)count > (uint)Count) - throw new ArgumentException("index and count exceed length of list"); - } - - private void AddCollection (ICollection collection) { - int collectionCount = collection.Count; - if (collectionCount == 0) - return; - - GrowIfNeeded(collectionCount); - collection.CopyTo(Items, Count); - Count += collectionCount; - } - - private void AddEnumerable (IEnumerable enumerable) { - foreach (T t in enumerable) { - Add(t); - } - } - - public void AddRange (IEnumerable collection) { - CheckCollection(collection); - - ICollection c = collection as ICollection; - if (c != null) - AddCollection(c); - else - AddEnumerable(collection); - version++; - } - - public int BinarySearch (T item) { - return Array.BinarySearch(Items, 0, Count, item); - } - - public int BinarySearch (T item, IComparer comparer) { - return Array.BinarySearch(Items, 0, Count, item, comparer); - } - - public int BinarySearch (int index, int count, T item, IComparer comparer) { - CheckRange(index, count); - return Array.BinarySearch(Items, index, count, item, comparer); - } - - public void Clear (bool clearArray = true) { - if (clearArray) - Array.Clear(Items, 0, Items.Length); - - Count = 0; - version++; - } - - public bool Contains (T item) { - return Array.IndexOf(Items, item, 0, Count) != -1; - } - - public ExposedList ConvertAll (Converter converter) { - if (converter == null) - throw new ArgumentNullException("converter"); - ExposedList u = new ExposedList(Count); - for (int i = 0; i < Count; i++) - u.Items[i] = converter(Items[i]); - - u.Count = Count; - return u; - } - - public void CopyTo (T[] array) { - Array.Copy(Items, 0, array, 0, Count); - } - - public void CopyTo (T[] array, int arrayIndex) { - Array.Copy(Items, 0, array, arrayIndex, Count); - } - - public void CopyTo (int index, T[] array, int arrayIndex, int count) { - CheckRange(index, count); - Array.Copy(Items, index, array, arrayIndex, count); - } - - - - public bool Exists (Predicate match) { - CheckMatch(match); - return GetIndex(0, Count, match) != -1; - } - - public T Find (Predicate match) { - CheckMatch(match); - int i = GetIndex(0, Count, match); - return (i != -1) ? Items[i] : default(T); - } - - private static void CheckMatch (Predicate match) { - if (match == null) - throw new ArgumentNullException("match"); - } - - public ExposedList FindAll (Predicate match) { - CheckMatch(match); - return FindAllList(match); - } - - private ExposedList FindAllList (Predicate match) { - ExposedList results = new ExposedList(); - for (int i = 0; i < Count; i++) - if (match(Items[i])) - results.Add(Items[i]); - - return results; - } - - public int FindIndex (Predicate match) { - CheckMatch(match); - return GetIndex(0, Count, match); - } - - public int FindIndex (int startIndex, Predicate match) { - CheckMatch(match); - CheckIndex(startIndex); - return GetIndex(startIndex, Count - startIndex, match); - } - - public int FindIndex (int startIndex, int count, Predicate match) { - CheckMatch(match); - CheckRange(startIndex, count); - return GetIndex(startIndex, count, match); - } - - private int GetIndex (int startIndex, int count, Predicate match) { - int end = startIndex + count; - for (int i = startIndex; i < end; i++) - if (match(Items[i])) - return i; - - return -1; - } - - public T FindLast (Predicate match) { - CheckMatch(match); - int i = GetLastIndex(0, Count, match); - return i == -1 ? default(T) : Items[i]; - } - - public int FindLastIndex (Predicate match) { - CheckMatch(match); - return GetLastIndex(0, Count, match); - } - - public int FindLastIndex (int startIndex, Predicate match) { - CheckMatch(match); - CheckIndex(startIndex); - return GetLastIndex(0, startIndex + 1, match); - } - - public int FindLastIndex (int startIndex, int count, Predicate match) { - CheckMatch(match); - int start = startIndex - count + 1; - CheckRange(start, count); - return GetLastIndex(start, count, match); - } - - private int GetLastIndex (int startIndex, int count, Predicate match) { - // unlike FindLastIndex, takes regular params for search range - for (int i = startIndex + count; i != startIndex; ) - if (match(Items[--i])) - return i; - return -1; - } - - public void ForEach (Action action) { - if (action == null) - throw new ArgumentNullException("action"); - for (int i = 0; i < Count; i++) - action(Items[i]); - } - - public Enumerator GetEnumerator () { - return new Enumerator(this); - } - - public ExposedList GetRange (int index, int count) { - CheckRange(index, count); - T[] tmpArray = new T[count]; - Array.Copy(Items, index, tmpArray, 0, count); - return new ExposedList(tmpArray, count); - } - - public int IndexOf (T item) { - return Array.IndexOf(Items, item, 0, Count); - } - - public int IndexOf (T item, int index) { - CheckIndex(index); - return Array.IndexOf(Items, item, index, Count - index); - } - - public int IndexOf (T item, int index, int count) { - if (index < 0) - throw new ArgumentOutOfRangeException("index"); - - if (count < 0) - throw new ArgumentOutOfRangeException("count"); - - if ((uint)index + (uint)count > (uint)Count) - throw new ArgumentOutOfRangeException("index and count exceed length of list"); - - return Array.IndexOf(Items, item, index, count); - } - - private void Shift (int start, int delta) { - if (delta < 0) - start -= delta; - - if (start < Count) - Array.Copy(Items, start, Items, start + delta, Count - start); - - Count += delta; - - if (delta < 0) - Array.Clear(Items, Count, -delta); - } - - private void CheckIndex (int index) { - if (index < 0 || (uint)index > (uint)Count) - throw new ArgumentOutOfRangeException("index"); - } - - public void Insert (int index, T item) { - CheckIndex(index); - if (Count == Items.Length) - GrowIfNeeded(1); - Shift(index, 1); - Items[index] = item; - version++; - } - - private void CheckCollection (IEnumerable collection) { - if (collection == null) - throw new ArgumentNullException("collection"); - } - - public void InsertRange (int index, IEnumerable collection) { - CheckCollection(collection); - CheckIndex(index); - if (collection == this) { - T[] buffer = new T[Count]; - CopyTo(buffer, 0); - GrowIfNeeded(Count); - Shift(index, buffer.Length); - Array.Copy(buffer, 0, Items, index, buffer.Length); - } else { - ICollection c = collection as ICollection; - if (c != null) - InsertCollection(index, c); - else - InsertEnumeration(index, collection); - } - version++; - } - - private void InsertCollection (int index, ICollection collection) { - int collectionCount = collection.Count; - GrowIfNeeded(collectionCount); - - Shift(index, collectionCount); - collection.CopyTo(Items, index); - } - - private void InsertEnumeration (int index, IEnumerable enumerable) { - foreach (T t in enumerable) - Insert(index++, t); - } - - public int LastIndexOf (T item) { - return Array.LastIndexOf(Items, item, Count - 1, Count); - } - - public int LastIndexOf (T item, int index) { - CheckIndex(index); - return Array.LastIndexOf(Items, item, index, index + 1); - } - - public int LastIndexOf (T item, int index, int count) { - if (index < 0) - throw new ArgumentOutOfRangeException("index", index, "index is negative"); - - if (count < 0) - throw new ArgumentOutOfRangeException("count", count, "count is negative"); - - if (index - count + 1 < 0) - throw new ArgumentOutOfRangeException("count", count, "count is too large"); - - return Array.LastIndexOf(Items, item, index, count); - } - - public bool Remove (T item) { - int loc = IndexOf(item); - if (loc != -1) - RemoveAt(loc); - - return loc != -1; - } - - public int RemoveAll (Predicate match) { - CheckMatch(match); - int i = 0; - int j = 0; - - // Find the first item to remove - for (i = 0; i < Count; i++) - if (match(Items[i])) - break; - - if (i == Count) - return 0; - - version++; - - // Remove any additional items - for (j = i + 1; j < Count; j++) { - if (!match(Items[j])) - Items[i++] = Items[j]; - } - if (j - i > 0) - Array.Clear(Items, i, j - i); - - Count = i; - return (j - i); - } - - public void RemoveAt (int index) { - if (index < 0 || (uint)index >= (uint)Count) - throw new ArgumentOutOfRangeException("index"); - Shift(index, -1); - Array.Clear(Items, Count, 1); - version++; - } - - // Spine Added Method - // Based on Stack.Pop(); https://referencesource.microsoft.com/#mscorlib/system/collections/stack.cs - /// Pops the last item of the list. If the list is empty, Pop throws an InvalidOperationException. - public T Pop () { - if (Count == 0) - throw new InvalidOperationException("List is empty. Nothing to pop."); - - int i = Count - 1; - T item = Items[i]; - Items[i] = default(T); - Count--; - version++; - return item; - } - - public void RemoveRange (int index, int count) { - CheckRange(index, count); - if (count > 0) { - Shift(index, -count); - Array.Clear(Items, Count, count); - version++; - } - } - - public void Reverse () { - Array.Reverse(Items, 0, Count); - version++; - } - - public void Reverse (int index, int count) { - CheckRange(index, count); - Array.Reverse(Items, index, count); - version++; - } - - public void Sort () { - Array.Sort(Items, 0, Count, Comparer.Default); - version++; - } - - public void Sort (IComparer comparer) { - Array.Sort(Items, 0, Count, comparer); - version++; - } - - public void Sort (Comparison comparison) { - Array.Sort(Items, comparison); - version++; - } - - public void Sort (int index, int count, IComparer comparer) { - CheckRange(index, count); - Array.Sort(Items, index, count, comparer); - version++; - } - - public T[] ToArray () { - T[] t = new T[Count]; - Array.Copy(Items, t, Count); - - return t; - } - - public void TrimExcess () { - Capacity = Count; - } - - public bool TrueForAll (Predicate match) { - CheckMatch(match); - - for (int i = 0; i < Count; i++) - if (!match(Items[i])) - return false; - - return true; - } - - public int Capacity { - get { - return Items.Length; - } - set { - if ((uint)value < (uint)Count) - throw new ArgumentOutOfRangeException(); - - Array.Resize(ref Items, value); - } - } - - #region Interface implementations. - - IEnumerator IEnumerable.GetEnumerator () { - return GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator () { - return GetEnumerator(); - } - - #endregion - - [Serializable] - public struct Enumerator : IEnumerator, IDisposable { - private ExposedList l; - private int next; - private int ver; - private T current; - - internal Enumerator (ExposedList l) - : this() { - this.l = l; - ver = l.version; - } - - public void Dispose () { - l = null; - } - - private void VerifyState () { - if (l == null) - throw new ObjectDisposedException(GetType().FullName); - if (ver != l.version) - throw new InvalidOperationException( - "Collection was modified; enumeration operation may not execute."); - } - - public bool MoveNext () { - VerifyState(); - - if (next < 0) - return false; - - if (next < l.Count) { - current = l.Items[next++]; - return true; - } - - next = -1; - return false; - } - - public T Current { - get { - return current; - } - } - - void IEnumerator.Reset () { - VerifyState(); - next = 0; - } - - object IEnumerator.Current { - get { - VerifyState(); - if (next <= 0) - throw new InvalidOperationException(); - return current; - } - } - } - } +namespace Spine3_6_39 +{ + [Serializable] + [DebuggerDisplay("Count={Count}")] + public class ExposedList : IEnumerable + { + public T[] Items; + public int Count; + private const int DefaultCapacity = 4; + private static readonly T[] EmptyArray = new T[0]; + private int version; + + public ExposedList() + { + Items = EmptyArray; + } + + public ExposedList(IEnumerable collection) + { + CheckCollection(collection); + + // initialize to needed size (if determinable) + ICollection c = collection as ICollection; + if (c == null) + { + Items = EmptyArray; + AddEnumerable(collection); + } + else + { + Items = new T[c.Count]; + AddCollection(c); + } + } + + public ExposedList(int capacity) + { + if (capacity < 0) + throw new ArgumentOutOfRangeException("capacity"); + Items = new T[capacity]; + } + + internal ExposedList(T[] data, int size) + { + Items = data; + Count = size; + } + + public void Add(T item) + { + // If we check to see if we need to grow before trying to grow + // we can speed things up by 25% + if (Count == Items.Length) + GrowIfNeeded(1); + Items[Count++] = item; + version++; + } + + public void GrowIfNeeded(int newCount) + { + int minimumSize = Count + newCount; + if (minimumSize > Items.Length) + Capacity = Math.Max(Math.Max(Capacity * 2, DefaultCapacity), minimumSize); + } + + public ExposedList Resize(int newSize) + { + if (newSize > Items.Length) Array.Resize(ref Items, newSize); + Count = newSize; + return this; + } + + public void EnsureCapacity(int min) + { + if (Items.Length < min) + { + int newCapacity = Items.Length == 0 ? DefaultCapacity : Items.Length * 2; + //if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength; + if (newCapacity < min) newCapacity = min; + Capacity = newCapacity; + } + } + + private void CheckRange(int index, int count) + { + if (index < 0) + throw new ArgumentOutOfRangeException("index"); + + if (count < 0) + throw new ArgumentOutOfRangeException("count"); + + if ((uint)index + (uint)count > (uint)Count) + throw new ArgumentException("index and count exceed length of list"); + } + + private void AddCollection(ICollection collection) + { + int collectionCount = collection.Count; + if (collectionCount == 0) + return; + + GrowIfNeeded(collectionCount); + collection.CopyTo(Items, Count); + Count += collectionCount; + } + + private void AddEnumerable(IEnumerable enumerable) + { + foreach (T t in enumerable) + { + Add(t); + } + } + + public void AddRange(IEnumerable collection) + { + CheckCollection(collection); + + ICollection c = collection as ICollection; + if (c != null) + AddCollection(c); + else + AddEnumerable(collection); + version++; + } + + public int BinarySearch(T item) + { + return Array.BinarySearch(Items, 0, Count, item); + } + + public int BinarySearch(T item, IComparer comparer) + { + return Array.BinarySearch(Items, 0, Count, item, comparer); + } + + public int BinarySearch(int index, int count, T item, IComparer comparer) + { + CheckRange(index, count); + return Array.BinarySearch(Items, index, count, item, comparer); + } + + public void Clear(bool clearArray = true) + { + if (clearArray) + Array.Clear(Items, 0, Items.Length); + + Count = 0; + version++; + } + + public bool Contains(T item) + { + return Array.IndexOf(Items, item, 0, Count) != -1; + } + + public ExposedList ConvertAll(Converter converter) + { + if (converter == null) + throw new ArgumentNullException("converter"); + ExposedList u = new ExposedList(Count); + for (int i = 0; i < Count; i++) + u.Items[i] = converter(Items[i]); + + u.Count = Count; + return u; + } + + public void CopyTo(T[] array) + { + Array.Copy(Items, 0, array, 0, Count); + } + + public void CopyTo(T[] array, int arrayIndex) + { + Array.Copy(Items, 0, array, arrayIndex, Count); + } + + public void CopyTo(int index, T[] array, int arrayIndex, int count) + { + CheckRange(index, count); + Array.Copy(Items, index, array, arrayIndex, count); + } + + + + public bool Exists(Predicate match) + { + CheckMatch(match); + return GetIndex(0, Count, match) != -1; + } + + public T Find(Predicate match) + { + CheckMatch(match); + int i = GetIndex(0, Count, match); + return (i != -1) ? Items[i] : default(T); + } + + private static void CheckMatch(Predicate match) + { + if (match == null) + throw new ArgumentNullException("match"); + } + + public ExposedList FindAll(Predicate match) + { + CheckMatch(match); + return FindAllList(match); + } + + private ExposedList FindAllList(Predicate match) + { + ExposedList results = new ExposedList(); + for (int i = 0; i < Count; i++) + if (match(Items[i])) + results.Add(Items[i]); + + return results; + } + + public int FindIndex(Predicate match) + { + CheckMatch(match); + return GetIndex(0, Count, match); + } + + public int FindIndex(int startIndex, Predicate match) + { + CheckMatch(match); + CheckIndex(startIndex); + return GetIndex(startIndex, Count - startIndex, match); + } + + public int FindIndex(int startIndex, int count, Predicate match) + { + CheckMatch(match); + CheckRange(startIndex, count); + return GetIndex(startIndex, count, match); + } + + private int GetIndex(int startIndex, int count, Predicate match) + { + int end = startIndex + count; + for (int i = startIndex; i < end; i++) + if (match(Items[i])) + return i; + + return -1; + } + + public T FindLast(Predicate match) + { + CheckMatch(match); + int i = GetLastIndex(0, Count, match); + return i == -1 ? default(T) : Items[i]; + } + + public int FindLastIndex(Predicate match) + { + CheckMatch(match); + return GetLastIndex(0, Count, match); + } + + public int FindLastIndex(int startIndex, Predicate match) + { + CheckMatch(match); + CheckIndex(startIndex); + return GetLastIndex(0, startIndex + 1, match); + } + + public int FindLastIndex(int startIndex, int count, Predicate match) + { + CheckMatch(match); + int start = startIndex - count + 1; + CheckRange(start, count); + return GetLastIndex(start, count, match); + } + + private int GetLastIndex(int startIndex, int count, Predicate match) + { + // unlike FindLastIndex, takes regular params for search range + for (int i = startIndex + count; i != startIndex;) + if (match(Items[--i])) + return i; + return -1; + } + + public void ForEach(Action action) + { + if (action == null) + throw new ArgumentNullException("action"); + for (int i = 0; i < Count; i++) + action(Items[i]); + } + + public Enumerator GetEnumerator() + { + return new Enumerator(this); + } + + public ExposedList GetRange(int index, int count) + { + CheckRange(index, count); + T[] tmpArray = new T[count]; + Array.Copy(Items, index, tmpArray, 0, count); + return new ExposedList(tmpArray, count); + } + + public int IndexOf(T item) + { + return Array.IndexOf(Items, item, 0, Count); + } + + public int IndexOf(T item, int index) + { + CheckIndex(index); + return Array.IndexOf(Items, item, index, Count - index); + } + + public int IndexOf(T item, int index, int count) + { + if (index < 0) + throw new ArgumentOutOfRangeException("index"); + + if (count < 0) + throw new ArgumentOutOfRangeException("count"); + + if ((uint)index + (uint)count > (uint)Count) + throw new ArgumentOutOfRangeException("index and count exceed length of list"); + + return Array.IndexOf(Items, item, index, count); + } + + private void Shift(int start, int delta) + { + if (delta < 0) + start -= delta; + + if (start < Count) + Array.Copy(Items, start, Items, start + delta, Count - start); + + Count += delta; + + if (delta < 0) + Array.Clear(Items, Count, -delta); + } + + private void CheckIndex(int index) + { + if (index < 0 || (uint)index > (uint)Count) + throw new ArgumentOutOfRangeException("index"); + } + + public void Insert(int index, T item) + { + CheckIndex(index); + if (Count == Items.Length) + GrowIfNeeded(1); + Shift(index, 1); + Items[index] = item; + version++; + } + + private void CheckCollection(IEnumerable collection) + { + if (collection == null) + throw new ArgumentNullException("collection"); + } + + public void InsertRange(int index, IEnumerable collection) + { + CheckCollection(collection); + CheckIndex(index); + if (collection == this) + { + T[] buffer = new T[Count]; + CopyTo(buffer, 0); + GrowIfNeeded(Count); + Shift(index, buffer.Length); + Array.Copy(buffer, 0, Items, index, buffer.Length); + } + else + { + ICollection c = collection as ICollection; + if (c != null) + InsertCollection(index, c); + else + InsertEnumeration(index, collection); + } + version++; + } + + private void InsertCollection(int index, ICollection collection) + { + int collectionCount = collection.Count; + GrowIfNeeded(collectionCount); + + Shift(index, collectionCount); + collection.CopyTo(Items, index); + } + + private void InsertEnumeration(int index, IEnumerable enumerable) + { + foreach (T t in enumerable) + Insert(index++, t); + } + + public int LastIndexOf(T item) + { + return Array.LastIndexOf(Items, item, Count - 1, Count); + } + + public int LastIndexOf(T item, int index) + { + CheckIndex(index); + return Array.LastIndexOf(Items, item, index, index + 1); + } + + public int LastIndexOf(T item, int index, int count) + { + if (index < 0) + throw new ArgumentOutOfRangeException("index", index, "index is negative"); + + if (count < 0) + throw new ArgumentOutOfRangeException("count", count, "count is negative"); + + if (index - count + 1 < 0) + throw new ArgumentOutOfRangeException("count", count, "count is too large"); + + return Array.LastIndexOf(Items, item, index, count); + } + + public bool Remove(T item) + { + int loc = IndexOf(item); + if (loc != -1) + RemoveAt(loc); + + return loc != -1; + } + + public int RemoveAll(Predicate match) + { + CheckMatch(match); + int i = 0; + int j = 0; + + // Find the first item to remove + for (i = 0; i < Count; i++) + if (match(Items[i])) + break; + + if (i == Count) + return 0; + + version++; + + // Remove any additional items + for (j = i + 1; j < Count; j++) + { + if (!match(Items[j])) + Items[i++] = Items[j]; + } + if (j - i > 0) + Array.Clear(Items, i, j - i); + + Count = i; + return (j - i); + } + + public void RemoveAt(int index) + { + if (index < 0 || (uint)index >= (uint)Count) + throw new ArgumentOutOfRangeException("index"); + Shift(index, -1); + Array.Clear(Items, Count, 1); + version++; + } + + // Spine Added Method + // Based on Stack.Pop(); https://referencesource.microsoft.com/#mscorlib/system/collections/stack.cs + /// Pops the last item of the list. If the list is empty, Pop throws an InvalidOperationException. + public T Pop() + { + if (Count == 0) + throw new InvalidOperationException("List is empty. Nothing to pop."); + + int i = Count - 1; + T item = Items[i]; + Items[i] = default(T); + Count--; + version++; + return item; + } + + public void RemoveRange(int index, int count) + { + CheckRange(index, count); + if (count > 0) + { + Shift(index, -count); + Array.Clear(Items, Count, count); + version++; + } + } + + public void Reverse() + { + Array.Reverse(Items, 0, Count); + version++; + } + + public void Reverse(int index, int count) + { + CheckRange(index, count); + Array.Reverse(Items, index, count); + version++; + } + + public void Sort() + { + Array.Sort(Items, 0, Count, Comparer.Default); + version++; + } + + public void Sort(IComparer comparer) + { + Array.Sort(Items, 0, Count, comparer); + version++; + } + + public void Sort(Comparison comparison) + { + Array.Sort(Items, comparison); + version++; + } + + public void Sort(int index, int count, IComparer comparer) + { + CheckRange(index, count); + Array.Sort(Items, index, count, comparer); + version++; + } + + public T[] ToArray() + { + T[] t = new T[Count]; + Array.Copy(Items, t, Count); + + return t; + } + + public void TrimExcess() + { + Capacity = Count; + } + + public bool TrueForAll(Predicate match) + { + CheckMatch(match); + + for (int i = 0; i < Count; i++) + if (!match(Items[i])) + return false; + + return true; + } + + public int Capacity + { + get + { + return Items.Length; + } + set + { + if ((uint)value < (uint)Count) + throw new ArgumentOutOfRangeException(); + + Array.Resize(ref Items, value); + } + } + + #region Interface implementations. + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion + + [Serializable] + public struct Enumerator : IEnumerator, IDisposable + { + private ExposedList l; + private int next; + private int ver; + private T current; + + internal Enumerator(ExposedList l) + : this() + { + this.l = l; + ver = l.version; + } + + public void Dispose() + { + l = null; + } + + private void VerifyState() + { + if (l == null) + throw new ObjectDisposedException(GetType().FullName); + if (ver != l.version) + throw new InvalidOperationException( + "Collection was modified; enumeration operation may not execute."); + } + + public bool MoveNext() + { + VerifyState(); + + if (next < 0) + return false; + + if (next < l.Count) + { + current = l.Items[next++]; + return true; + } + + next = -1; + return false; + } + + public T Current + { + get + { + return current; + } + } + + void IEnumerator.Reset() + { + VerifyState(); + next = 0; + } + + object IEnumerator.Current + { + get + { + VerifyState(); + if (next <= 0) + throw new InvalidOperationException(); + return current; + } + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/IConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/IConstraint.cs index 441ca37..3ddfa2e 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/IConstraint.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/IConstraint.cs @@ -28,13 +28,15 @@ * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine3_6_39 { - - /// The interface for all constraints. - public interface IConstraint : IUpdatable { - /// The ordinal for the order a skeleton's constraints will be applied. - int Order { get; } +namespace Spine3_6_39 +{ - } + /// The interface for all constraints. + public interface IConstraint : IUpdatable + { + /// The ordinal for the order a skeleton's constraints will be applied. + int Order { get; } + + } } \ No newline at end of file diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/IUpdatable.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/IUpdatable.cs index c29c174..7d4bb48 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/IUpdatable.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/IUpdatable.cs @@ -28,8 +28,10 @@ * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine3_6_39 { - public interface IUpdatable { - void Update (); - } +namespace Spine3_6_39 +{ + public interface IUpdatable + { + void Update(); + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/IkConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/IkConstraint.cs index 147c7af..1ba914b 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/IkConstraint.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/IkConstraint.cs @@ -30,198 +30,228 @@ using System; -namespace Spine3_6_39 { - public class IkConstraint : IConstraint { - internal IkConstraintData data; - internal ExposedList bones = new ExposedList(); - internal Bone target; - internal float mix; - internal int bendDirection; +namespace Spine3_6_39 +{ + public class IkConstraint : IConstraint + { + internal IkConstraintData data; + internal ExposedList bones = new ExposedList(); + internal Bone target; + internal float mix; + internal int bendDirection; - public IkConstraintData Data { get { return data; } } - public int Order { get { return data.order; } } - public ExposedList Bones { get { return bones; } } - public Bone Target { get { return target; } set { target = value; } } - public int BendDirection { get { return bendDirection; } set { bendDirection = value; } } - public float Mix { get { return mix; } set { mix = value; } } + public IkConstraintData Data { get { return data; } } + public int Order { get { return data.order; } } + public ExposedList Bones { get { return bones; } } + public Bone Target { get { return target; } set { target = value; } } + public int BendDirection { get { return bendDirection; } set { bendDirection = value; } } + public float Mix { get { return mix; } set { mix = value; } } - public IkConstraint (IkConstraintData data, Skeleton skeleton) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.data = data; - mix = data.mix; - bendDirection = data.bendDirection; + public IkConstraint(IkConstraintData data, Skeleton skeleton) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + mix = data.mix; + bendDirection = data.bendDirection; - bones = new ExposedList(data.bones.Count); - foreach (BoneData boneData in data.bones) - bones.Add(skeleton.FindBone(boneData.name)); - target = skeleton.FindBone(data.target.name); - } + bones = new ExposedList(data.bones.Count); + foreach (BoneData boneData in data.bones) + bones.Add(skeleton.FindBone(boneData.name)); + target = skeleton.FindBone(data.target.name); + } - /// Applies the constraint to the constrained bones. - public void Apply () { - Update(); - } + /// Applies the constraint to the constrained bones. + public void Apply() + { + Update(); + } - public void Update () { - Bone target = this.target; - ExposedList bones = this.bones; - switch (bones.Count) { - case 1: - Apply(bones.Items[0], target.worldX, target.worldY, mix); - break; - case 2: - Apply(bones.Items[0], bones.Items[1], target.worldX, target.worldY, bendDirection, mix); - break; - } - } + public void Update() + { + Bone target = this.target; + ExposedList bones = this.bones; + switch (bones.Count) + { + case 1: + Apply(bones.Items[0], target.worldX, target.worldY, mix); + break; + case 2: + Apply(bones.Items[0], bones.Items[1], target.worldX, target.worldY, bendDirection, mix); + break; + } + } - override public string ToString () { - return data.name; - } + override public string ToString() + { + return data.name; + } - /// 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 public void Apply (Bone bone, float targetX, float targetY, float alpha) { - if (!bone.appliedValid) bone.UpdateAppliedTransform(); - Bone p = bone.parent; - float id = 1 / (p.a * p.d - p.b * p.c); - float x = targetX - p.worldX, y = targetY - p.worldY; - float tx = (x * p.d - y * p.b) * id - bone.ax, ty = (y * p.a - x * p.c) * id - bone.ay; - float rotationIK = (float)Math.Atan2(ty, tx) * MathUtils.RadDeg - bone.ashearX - bone.arotation; - if (bone.ascaleX < 0) rotationIK += 180; - if (rotationIK > 180) - rotationIK -= 360; - else if (rotationIK < -180) rotationIK += 360; - bone.UpdateWorldTransform(bone.ax, bone.ay, bone.arotation + rotationIK * alpha, bone.ascaleX, bone.ascaleY, bone.ashearX, - bone.ashearY); - } + /// 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 public void Apply(Bone bone, float targetX, float targetY, float alpha) + { + if (!bone.appliedValid) bone.UpdateAppliedTransform(); + Bone p = bone.parent; + float id = 1 / (p.a * p.d - p.b * p.c); + float x = targetX - p.worldX, y = targetY - p.worldY; + float tx = (x * p.d - y * p.b) * id - bone.ax, ty = (y * p.a - x * p.c) * id - bone.ay; + float rotationIK = (float)Math.Atan2(ty, tx) * MathUtils.RadDeg - bone.ashearX - bone.arotation; + if (bone.ascaleX < 0) rotationIK += 180; + if (rotationIK > 180) + rotationIK -= 360; + else if (rotationIK < -180) rotationIK += 360; + bone.UpdateWorldTransform(bone.ax, bone.ay, bone.arotation + rotationIK * alpha, bone.ascaleX, bone.ascaleY, bone.ashearX, + bone.ashearY); + } - /// 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. - /// A direct descendant of the parent bone. - static public void Apply (Bone parent, Bone child, float targetX, float targetY, int bendDir, float alpha) { - if (alpha == 0) { - child.UpdateWorldTransform (); - return; - } - //float px = parent.x, py = parent.y, psx = parent.scaleX, psy = parent.scaleY, csx = child.scaleX; - if (!parent.appliedValid) parent.UpdateAppliedTransform(); - if (!child.appliedValid) child.UpdateAppliedTransform(); - float px = parent.ax, py = parent.ay, psx = parent.ascaleX, psy = parent.ascaleY, csx = child.ascaleX; - int os1, os2, s2; - if (psx < 0) { - psx = -psx; - os1 = 180; - s2 = -1; - } else { - os1 = 0; - s2 = 1; - } - if (psy < 0) { - psy = -psy; - s2 = -s2; - } - if (csx < 0) { - csx = -csx; - os2 = 180; - } else - os2 = 0; - float cx = child.ax, cy, cwx, cwy, a = parent.a, b = parent.b, c = parent.c, d = parent.d; - bool u = Math.Abs(psx - psy) <= 0.0001f; - if (!u) { - cy = 0; - cwx = a * cx + parent.worldX; - cwy = c * cx + parent.worldY; - } else { - cy = child.ay; - cwx = a * cx + b * cy + parent.worldX; - cwy = c * cx + d * cy + parent.worldY; - } - Bone pp = parent.parent; - a = pp.a; - b = pp.b; - c = pp.c; - d = pp.d; - float id = 1 / (a * d - b * c), x = targetX - pp.worldX, y = targetY - pp.worldY; - float tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py; - x = cwx - pp.worldX; - y = cwy - pp.worldY; - float dx = (x * d - y * b) * id - px, dy = (y * a - x * c) * id - py; - float l1 = (float)Math.Sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2; - if (u) { - l2 *= psx; - float cos = (tx * tx + ty * ty - l1 * l1 - l2 * l2) / (2 * l1 * l2); - if (cos < -1) - cos = -1; - else if (cos > 1) cos = 1; - a2 = (float)Math.Acos(cos) * bendDir; - a = l1 + l2 * cos; - b = l2 * (float)Math.Sin(a2); - a1 = (float)Math.Atan2(ty * a - tx * b, tx * a + ty * b); - } else { - a = psx * l2; - b = psy * l2; - float aa = a * a, bb = b * b, dd = tx * tx + ty * ty, ta = (float)Math.Atan2(ty, tx); - c = bb * l1 * l1 + aa * dd - aa * bb; - float c1 = -2 * bb * l1, c2 = bb - aa; - d = c1 * c1 - 4 * c2 * c; - if (d >= 0) { - float q = (float)Math.Sqrt(d); - if (c1 < 0) q = -q; - q = -(c1 + q) / 2; - float r0 = q / c2, r1 = c / q; - float r = Math.Abs(r0) < Math.Abs(r1) ? r0 : r1; - if (r * r <= dd) { - y = (float)Math.Sqrt(dd - r * r) * bendDir; - a1 = ta - (float)Math.Atan2(y, r); - a2 = (float)Math.Atan2(y / psy, (r - l1) / psx); - goto outer; // break outer; - } - } - float minAngle = MathUtils.PI, minX = l1 - a, minDist = minX * minX, minY = 0; - float maxAngle = 0, maxX = l1 + a, maxDist = maxX * maxX, maxY = 0; - c = -a * l1 / (aa - bb); - if (c >= -1 && c <= 1) { - c = (float)Math.Acos(c); - x = a * (float)Math.Cos(c) + l1; - y = b * (float)Math.Sin(c); - d = x * x + y * y; - if (d < minDist) { - minAngle = c; - minDist = d; - minX = x; - minY = y; - } - if (d > maxDist) { - maxAngle = c; - maxDist = d; - maxX = x; - maxY = y; - } - } - if (dd <= (minDist + maxDist) / 2) { - a1 = ta - (float)Math.Atan2(minY * bendDir, minX); - a2 = minAngle * bendDir; - } else { - a1 = ta - (float)Math.Atan2(maxY * bendDir, maxX); - a2 = maxAngle * bendDir; - } - } - outer: - float os = (float)Math.Atan2(cy, cx) * s2; - float rotation = parent.arotation; - a1 = (a1 - os) * MathUtils.RadDeg + os1 - rotation; - if (a1 > 180) - a1 -= 360; - else if (a1 < -180) a1 += 360; - parent.UpdateWorldTransform(px, py, rotation + a1 * alpha, parent.scaleX, parent.ascaleY, 0, 0); - rotation = child.arotation; - a2 = ((a2 + os) * MathUtils.RadDeg - child.ashearX) * s2 + os2 - rotation; - if (a2 > 180) - a2 -= 360; - else if (a2 < -180) a2 += 360; - child.UpdateWorldTransform(cx, cy, rotation + a2 * alpha, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY); - } - } + /// 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. + /// A direct descendant of the parent bone. + static public void Apply(Bone parent, Bone child, float targetX, float targetY, int bendDir, float alpha) + { + if (alpha == 0) + { + child.UpdateWorldTransform(); + return; + } + //float px = parent.x, py = parent.y, psx = parent.scaleX, psy = parent.scaleY, csx = child.scaleX; + if (!parent.appliedValid) parent.UpdateAppliedTransform(); + if (!child.appliedValid) child.UpdateAppliedTransform(); + float px = parent.ax, py = parent.ay, psx = parent.ascaleX, psy = parent.ascaleY, csx = child.ascaleX; + int os1, os2, s2; + if (psx < 0) + { + psx = -psx; + os1 = 180; + s2 = -1; + } + else + { + os1 = 0; + s2 = 1; + } + if (psy < 0) + { + psy = -psy; + s2 = -s2; + } + if (csx < 0) + { + csx = -csx; + os2 = 180; + } + else + os2 = 0; + float cx = child.ax, cy, cwx, cwy, a = parent.a, b = parent.b, c = parent.c, d = parent.d; + bool u = Math.Abs(psx - psy) <= 0.0001f; + if (!u) + { + cy = 0; + cwx = a * cx + parent.worldX; + cwy = c * cx + parent.worldY; + } + else + { + cy = child.ay; + cwx = a * cx + b * cy + parent.worldX; + cwy = c * cx + d * cy + parent.worldY; + } + Bone pp = parent.parent; + a = pp.a; + b = pp.b; + c = pp.c; + d = pp.d; + float id = 1 / (a * d - b * c), x = targetX - pp.worldX, y = targetY - pp.worldY; + float tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py; + x = cwx - pp.worldX; + y = cwy - pp.worldY; + float dx = (x * d - y * b) * id - px, dy = (y * a - x * c) * id - py; + float l1 = (float)Math.Sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2; + if (u) + { + l2 *= psx; + float cos = (tx * tx + ty * ty - l1 * l1 - l2 * l2) / (2 * l1 * l2); + if (cos < -1) + cos = -1; + else if (cos > 1) cos = 1; + a2 = (float)Math.Acos(cos) * bendDir; + a = l1 + l2 * cos; + b = l2 * (float)Math.Sin(a2); + a1 = (float)Math.Atan2(ty * a - tx * b, tx * a + ty * b); + } + else + { + a = psx * l2; + b = psy * l2; + float aa = a * a, bb = b * b, dd = tx * tx + ty * ty, ta = (float)Math.Atan2(ty, tx); + c = bb * l1 * l1 + aa * dd - aa * bb; + float c1 = -2 * bb * l1, c2 = bb - aa; + d = c1 * c1 - 4 * c2 * c; + if (d >= 0) + { + float q = (float)Math.Sqrt(d); + if (c1 < 0) q = -q; + q = -(c1 + q) / 2; + float r0 = q / c2, r1 = c / q; + float r = Math.Abs(r0) < Math.Abs(r1) ? r0 : r1; + if (r * r <= dd) + { + y = (float)Math.Sqrt(dd - r * r) * bendDir; + a1 = ta - (float)Math.Atan2(y, r); + a2 = (float)Math.Atan2(y / psy, (r - l1) / psx); + goto outer; // break outer; + } + } + float minAngle = MathUtils.PI, minX = l1 - a, minDist = minX * minX, minY = 0; + float maxAngle = 0, maxX = l1 + a, maxDist = maxX * maxX, maxY = 0; + c = -a * l1 / (aa - bb); + if (c >= -1 && c <= 1) + { + c = (float)Math.Acos(c); + x = a * (float)Math.Cos(c) + l1; + y = b * (float)Math.Sin(c); + d = x * x + y * y; + if (d < minDist) + { + minAngle = c; + minDist = d; + minX = x; + minY = y; + } + if (d > maxDist) + { + maxAngle = c; + maxDist = d; + maxX = x; + maxY = y; + } + } + if (dd <= (minDist + maxDist) / 2) + { + a1 = ta - (float)Math.Atan2(minY * bendDir, minX); + a2 = minAngle * bendDir; + } + else + { + a1 = ta - (float)Math.Atan2(maxY * bendDir, maxX); + a2 = maxAngle * bendDir; + } + } + outer: + float os = (float)Math.Atan2(cy, cx) * s2; + float rotation = parent.arotation; + a1 = (a1 - os) * MathUtils.RadDeg + os1 - rotation; + if (a1 > 180) + a1 -= 360; + else if (a1 < -180) a1 += 360; + parent.UpdateWorldTransform(px, py, rotation + a1 * alpha, parent.scaleX, parent.ascaleY, 0, 0); + rotation = child.arotation; + a2 = ((a2 + os) * MathUtils.RadDeg - child.ashearX) * s2 + os2 - rotation; + if (a2 > 180) + a2 -= 360; + else if (a2 < -180) a2 += 360; + child.UpdateWorldTransform(cx, cy, rotation + a2 * alpha, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/IkConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/IkConstraintData.cs index 2f4bc6b..e43fcb9 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/IkConstraintData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/IkConstraintData.cs @@ -31,52 +31,61 @@ using System; using System.Collections.Generic; -namespace Spine3_6_39 { - /// Stores the setup pose for an IkConstraint. - public class IkConstraintData { - internal string name; - internal int order; - internal List bones = new List(); - internal BoneData target; - internal int bendDirection = 1; - internal float mix = 1; +namespace Spine3_6_39 +{ + /// Stores the setup pose for an IkConstraint. + public class IkConstraintData + { + internal string name; + internal int order; + internal List bones = new List(); + internal BoneData target; + internal int bendDirection = 1; + internal float mix = 1; - /// The IK constraint's name, which is unique within the skeleton. - public string Name { - get { return name; } - } + /// The IK constraint's name, which is unique within the skeleton. + public string Name + { + get { return name; } + } - public int Order { - get { return order; } - set { order = value; } - } + public int Order + { + get { return order; } + set { order = value; } + } - /// The bones that are constrained by this IK Constraint. - public List Bones { - get { return bones; } - } + /// The bones that are constrained by this IK Constraint. + public List Bones + { + get { return bones; } + } - /// The bone that is the IK target. - public BoneData Target { - get { return target; } - set { target = value; } - } + /// The bone that is the IK target. + public BoneData Target + { + get { return target; } + set { target = value; } + } - /// Controls the bend direction of the IK bones, either 1 or -1. - public int BendDirection { - get { return bendDirection; } - set { bendDirection = value; } - } + /// Controls the bend direction of the IK bones, either 1 or -1. + public int BendDirection + { + get { return bendDirection; } + set { bendDirection = value; } + } - public float Mix { get { return mix; } set { mix = value; } } + public float Mix { get { return mix; } set { mix = value; } } - public IkConstraintData (string name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; - } + public IkConstraintData(string name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + } - override public string ToString () { - return name; - } - } + override public string ToString() + { + return name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Json.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Json.cs index 636f300..2206446 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Json.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Json.cs @@ -29,20 +29,22 @@ *****************************************************************************/ using System; +using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Text; -using System.Collections; -using System.Globalization; -using System.Collections.Generic; -namespace Spine3_6_39 { - public static class Json { - public static object Deserialize (TextReader text) { - var parser = new SharpJson3_6_39.JsonDecoder(); - parser.parseNumbersAsFloat = true; - return parser.Decode(text.ReadToEnd()); - } - } +namespace Spine3_6_39 +{ + public static class Json + { + public static object Deserialize(TextReader text) + { + var parser = new SharpJson3_6_39.JsonDecoder(); + parser.parseNumbersAsFloat = true; + return parser.Decode(text.ReadToEnd()); + } + } } /** @@ -76,460 +78,502 @@ public static object Deserialize (TextReader text) { */ namespace SharpJson3_6_39 { - class Lexer - { - public enum Token { - None, - Null, - True, - False, - Colon, - Comma, - String, - Number, - CurlyOpen, - CurlyClose, - SquaredOpen, - SquaredClose, - }; - - public bool hasError { - get { - return !success; - } - } - - public int lineNumber { - get; - private set; - } - - public bool parseNumbersAsFloat { - get; - set; - } - - char[] json; - int index = 0; - bool success = true; - char[] stringBuffer = new char[4096]; - - public Lexer(string text) - { - Reset(); - - json = text.ToCharArray(); - parseNumbersAsFloat = false; - } - - public void Reset() - { - index = 0; - lineNumber = 1; - success = true; - } - - public string ParseString() - { - int idx = 0; - StringBuilder builder = null; - - SkipWhiteSpaces(); - - // " - char c = json[index++]; - - bool failed = false; - bool complete = false; - - while (!complete && !failed) { - if (index == json.Length) - break; - - c = json[index++]; - if (c == '"') { - complete = true; - break; - } else if (c == '\\') { - if (index == json.Length) - break; - - c = json[index++]; - - switch (c) { - case '"': - stringBuffer[idx++] = '"'; - break; - case '\\': - stringBuffer[idx++] = '\\'; - break; - case '/': - stringBuffer[idx++] = '/'; - break; - case 'b': - stringBuffer[idx++] = '\b'; - break; - case'f': - stringBuffer[idx++] = '\f'; - break; - case 'n': - stringBuffer[idx++] = '\n'; - break; - case 'r': - stringBuffer[idx++] = '\r'; - break; - case 't': - stringBuffer[idx++] = '\t'; - break; - case 'u': - int remainingLength = json.Length - index; - if (remainingLength >= 4) { - var hex = new string(json, index, 4); - - // XXX: handle UTF - stringBuffer[idx++] = (char) Convert.ToInt32(hex, 16); - - // skip 4 chars - index += 4; - } else { - failed = true; - } - break; - } - } else { - stringBuffer[idx++] = c; - } - - if (idx >= stringBuffer.Length) { - if (builder == null) - builder = new StringBuilder(); - - builder.Append(stringBuffer, 0, idx); - idx = 0; - } - } - - if (!complete) { - success = false; - return null; - } - - if (builder != null) - return builder.ToString (); - else - return new string (stringBuffer, 0, idx); - } - - string GetNumberString() - { - SkipWhiteSpaces(); - - int lastIndex = GetLastIndexOfNumber(index); - int charLength = (lastIndex - index) + 1; - - var result = new string (json, index, charLength); - - index = lastIndex + 1; - - return result; - } - - public float ParseFloatNumber() - { - float number; - var str = GetNumberString (); - - if (!float.TryParse (str, NumberStyles.Float, CultureInfo.InvariantCulture, out number)) - return 0; - - return number; - } - - public double ParseDoubleNumber() - { - double number; - var str = GetNumberString (); - - if (!double.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out number)) - return 0; - - return number; - } - - int GetLastIndexOfNumber(int index) - { - int lastIndex; - - for (lastIndex = index; lastIndex < json.Length; lastIndex++) { - char ch = json[lastIndex]; - - if ((ch < '0' || ch > '9') && ch != '+' && ch != '-' - && ch != '.' && ch != 'e' && ch != 'E') - break; - } - - return lastIndex - 1; - } - - void SkipWhiteSpaces() - { - for (; index < json.Length; index++) { - char ch = json[index]; - - if (ch == '\n') - lineNumber++; - - if (!char.IsWhiteSpace(json[index])) - break; - } - } - - public Token LookAhead() - { - SkipWhiteSpaces(); - - int savedIndex = index; - return NextToken(json, ref savedIndex); - } - - public Token NextToken() - { - SkipWhiteSpaces(); - return NextToken(json, ref index); - } - - static Token NextToken(char[] json, ref int index) - { - if (index == json.Length) - return Token.None; - - char c = json[index++]; - - switch (c) { - case '{': - return Token.CurlyOpen; - case '}': - return Token.CurlyClose; - case '[': - return Token.SquaredOpen; - case ']': - return Token.SquaredClose; - case ',': - return Token.Comma; - case '"': - return Token.String; - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - case '-': - return Token.Number; - case ':': - return Token.Colon; - } - - index--; - - int remainingLength = json.Length - index; - - // false - if (remainingLength >= 5) { - if (json[index] == 'f' && - json[index + 1] == 'a' && - json[index + 2] == 'l' && - json[index + 3] == 's' && - json[index + 4] == 'e') { - index += 5; - return Token.False; - } - } - - // true - if (remainingLength >= 4) { - if (json[index] == 't' && - json[index + 1] == 'r' && - json[index + 2] == 'u' && - json[index + 3] == 'e') { - index += 4; - return Token.True; - } - } - - // null - if (remainingLength >= 4) { - if (json[index] == 'n' && - json[index + 1] == 'u' && - json[index + 2] == 'l' && - json[index + 3] == 'l') { - index += 4; - return Token.Null; - } - } - - return Token.None; - } - } - - public class JsonDecoder - { - public string errorMessage { - get; - private set; - } - - public bool parseNumbersAsFloat { - get; - set; - } - - Lexer lexer; - - public JsonDecoder() - { - errorMessage = null; - parseNumbersAsFloat = false; - } - - public object Decode(string text) - { - errorMessage = null; - - lexer = new Lexer(text); - lexer.parseNumbersAsFloat = parseNumbersAsFloat; - - return ParseValue(); - } - - public static object DecodeText(string text) - { - var builder = new JsonDecoder(); - return builder.Decode(text); - } - - IDictionary ParseObject() - { - var table = new Dictionary(); - - // { - lexer.NextToken(); - - while (true) { - var token = lexer.LookAhead(); - - switch (token) { - case Lexer.Token.None: - TriggerError("Invalid token"); - return null; - case Lexer.Token.Comma: - lexer.NextToken(); - break; - case Lexer.Token.CurlyClose: - lexer.NextToken(); - return table; - default: - // name - string name = EvalLexer(lexer.ParseString()); - - if (errorMessage != null) - return null; - - // : - token = lexer.NextToken(); - - if (token != Lexer.Token.Colon) { - TriggerError("Invalid token; expected ':'"); - return null; - } - - // value - object value = ParseValue(); - - if (errorMessage != null) - return null; - - table[name] = value; - break; - } - } - - //return null; // Unreachable code - } - - IList ParseArray() - { - var array = new List(); - - // [ - lexer.NextToken(); - - while (true) { - var token = lexer.LookAhead(); - - switch (token) { - case Lexer.Token.None: - TriggerError("Invalid token"); - return null; - case Lexer.Token.Comma: - lexer.NextToken(); - break; - case Lexer.Token.SquaredClose: - lexer.NextToken(); - return array; - default: - object value = ParseValue(); - - if (errorMessage != null) - return null; - - array.Add(value); - break; - } - } - - //return null; // Unreachable code - } - - object ParseValue() - { - switch (lexer.LookAhead()) { - case Lexer.Token.String: - return EvalLexer(lexer.ParseString()); - case Lexer.Token.Number: - if (parseNumbersAsFloat) - return EvalLexer(lexer.ParseFloatNumber()); - else - return EvalLexer(lexer.ParseDoubleNumber()); - case Lexer.Token.CurlyOpen: - return ParseObject(); - case Lexer.Token.SquaredOpen: - return ParseArray(); - case Lexer.Token.True: - lexer.NextToken(); - return true; - case Lexer.Token.False: - lexer.NextToken(); - return false; - case Lexer.Token.Null: - lexer.NextToken(); - return null; - case Lexer.Token.None: - break; - } - - TriggerError("Unable to parse value"); - return null; - } - - void TriggerError(string message) - { - errorMessage = string.Format("Error: '{0}' at line {1}", - message, lexer.lineNumber); - } - - T EvalLexer(T value) - { - if (lexer.hasError) - TriggerError("Lexical error ocurred"); - - return value; - } - } + class Lexer + { + public enum Token + { + None, + Null, + True, + False, + Colon, + Comma, + String, + Number, + CurlyOpen, + CurlyClose, + SquaredOpen, + SquaredClose, + }; + + public bool hasError + { + get + { + return !success; + } + } + + public int lineNumber + { + get; + private set; + } + + public bool parseNumbersAsFloat + { + get; + set; + } + + char[] json; + int index = 0; + bool success = true; + char[] stringBuffer = new char[4096]; + + public Lexer(string text) + { + Reset(); + + json = text.ToCharArray(); + parseNumbersAsFloat = false; + } + + public void Reset() + { + index = 0; + lineNumber = 1; + success = true; + } + + public string ParseString() + { + int idx = 0; + StringBuilder builder = null; + + SkipWhiteSpaces(); + + // " + char c = json[index++]; + + bool failed = false; + bool complete = false; + + while (!complete && !failed) + { + if (index == json.Length) + break; + + c = json[index++]; + if (c == '"') + { + complete = true; + break; + } + else if (c == '\\') + { + if (index == json.Length) + break; + + c = json[index++]; + + switch (c) + { + case '"': + stringBuffer[idx++] = '"'; + break; + case '\\': + stringBuffer[idx++] = '\\'; + break; + case '/': + stringBuffer[idx++] = '/'; + break; + case 'b': + stringBuffer[idx++] = '\b'; + break; + case 'f': + stringBuffer[idx++] = '\f'; + break; + case 'n': + stringBuffer[idx++] = '\n'; + break; + case 'r': + stringBuffer[idx++] = '\r'; + break; + case 't': + stringBuffer[idx++] = '\t'; + break; + case 'u': + int remainingLength = json.Length - index; + if (remainingLength >= 4) + { + var hex = new string(json, index, 4); + + // XXX: handle UTF + stringBuffer[idx++] = (char)Convert.ToInt32(hex, 16); + + // skip 4 chars + index += 4; + } + else + { + failed = true; + } + break; + } + } + else + { + stringBuffer[idx++] = c; + } + + if (idx >= stringBuffer.Length) + { + if (builder == null) + builder = new StringBuilder(); + + builder.Append(stringBuffer, 0, idx); + idx = 0; + } + } + + if (!complete) + { + success = false; + return null; + } + + if (builder != null) + return builder.ToString(); + else + return new string(stringBuffer, 0, idx); + } + + string GetNumberString() + { + SkipWhiteSpaces(); + + int lastIndex = GetLastIndexOfNumber(index); + int charLength = (lastIndex - index) + 1; + + var result = new string(json, index, charLength); + + index = lastIndex + 1; + + return result; + } + + public float ParseFloatNumber() + { + float number; + var str = GetNumberString(); + + if (!float.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out number)) + return 0; + + return number; + } + + public double ParseDoubleNumber() + { + double number; + var str = GetNumberString(); + + if (!double.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out number)) + return 0; + + return number; + } + + int GetLastIndexOfNumber(int index) + { + int lastIndex; + + for (lastIndex = index; lastIndex < json.Length; lastIndex++) + { + char ch = json[lastIndex]; + + if ((ch < '0' || ch > '9') && ch != '+' && ch != '-' + && ch != '.' && ch != 'e' && ch != 'E') + break; + } + + return lastIndex - 1; + } + + void SkipWhiteSpaces() + { + for (; index < json.Length; index++) + { + char ch = json[index]; + + if (ch == '\n') + lineNumber++; + + if (!char.IsWhiteSpace(json[index])) + break; + } + } + + public Token LookAhead() + { + SkipWhiteSpaces(); + + int savedIndex = index; + return NextToken(json, ref savedIndex); + } + + public Token NextToken() + { + SkipWhiteSpaces(); + return NextToken(json, ref index); + } + + static Token NextToken(char[] json, ref int index) + { + if (index == json.Length) + return Token.None; + + char c = json[index++]; + + switch (c) + { + case '{': + return Token.CurlyOpen; + case '}': + return Token.CurlyClose; + case '[': + return Token.SquaredOpen; + case ']': + return Token.SquaredClose; + case ',': + return Token.Comma; + case '"': + return Token.String; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + return Token.Number; + case ':': + return Token.Colon; + } + + index--; + + int remainingLength = json.Length - index; + + // false + if (remainingLength >= 5) + { + if (json[index] == 'f' && + json[index + 1] == 'a' && + json[index + 2] == 'l' && + json[index + 3] == 's' && + json[index + 4] == 'e') + { + index += 5; + return Token.False; + } + } + + // true + if (remainingLength >= 4) + { + if (json[index] == 't' && + json[index + 1] == 'r' && + json[index + 2] == 'u' && + json[index + 3] == 'e') + { + index += 4; + return Token.True; + } + } + + // null + if (remainingLength >= 4) + { + if (json[index] == 'n' && + json[index + 1] == 'u' && + json[index + 2] == 'l' && + json[index + 3] == 'l') + { + index += 4; + return Token.Null; + } + } + + return Token.None; + } + } + + public class JsonDecoder + { + public string errorMessage + { + get; + private set; + } + + public bool parseNumbersAsFloat + { + get; + set; + } + + Lexer lexer; + + public JsonDecoder() + { + errorMessage = null; + parseNumbersAsFloat = false; + } + + public object Decode(string text) + { + errorMessage = null; + + lexer = new Lexer(text); + lexer.parseNumbersAsFloat = parseNumbersAsFloat; + + return ParseValue(); + } + + public static object DecodeText(string text) + { + var builder = new JsonDecoder(); + return builder.Decode(text); + } + + IDictionary ParseObject() + { + var table = new Dictionary(); + + // { + lexer.NextToken(); + + while (true) + { + var token = lexer.LookAhead(); + + switch (token) + { + case Lexer.Token.None: + TriggerError("Invalid token"); + return null; + case Lexer.Token.Comma: + lexer.NextToken(); + break; + case Lexer.Token.CurlyClose: + lexer.NextToken(); + return table; + default: + // name + string name = EvalLexer(lexer.ParseString()); + + if (errorMessage != null) + return null; + + // : + token = lexer.NextToken(); + + if (token != Lexer.Token.Colon) + { + TriggerError("Invalid token; expected ':'"); + return null; + } + + // value + object value = ParseValue(); + + if (errorMessage != null) + return null; + + table[name] = value; + break; + } + } + + //return null; // Unreachable code + } + + IList ParseArray() + { + var array = new List(); + + // [ + lexer.NextToken(); + + while (true) + { + var token = lexer.LookAhead(); + + switch (token) + { + case Lexer.Token.None: + TriggerError("Invalid token"); + return null; + case Lexer.Token.Comma: + lexer.NextToken(); + break; + case Lexer.Token.SquaredClose: + lexer.NextToken(); + return array; + default: + object value = ParseValue(); + + if (errorMessage != null) + return null; + + array.Add(value); + break; + } + } + + //return null; // Unreachable code + } + + object ParseValue() + { + switch (lexer.LookAhead()) + { + case Lexer.Token.String: + return EvalLexer(lexer.ParseString()); + case Lexer.Token.Number: + if (parseNumbersAsFloat) + return EvalLexer(lexer.ParseFloatNumber()); + else + return EvalLexer(lexer.ParseDoubleNumber()); + case Lexer.Token.CurlyOpen: + return ParseObject(); + case Lexer.Token.SquaredOpen: + return ParseArray(); + case Lexer.Token.True: + lexer.NextToken(); + return true; + case Lexer.Token.False: + lexer.NextToken(); + return false; + case Lexer.Token.Null: + lexer.NextToken(); + return null; + case Lexer.Token.None: + break; + } + + TriggerError("Unable to parse value"); + return null; + } + + void TriggerError(string message) + { + errorMessage = string.Format("Error: '{0}' at line {1}", + message, lexer.lineNumber); + } + + T EvalLexer(T value) + { + if (lexer.hasError) + TriggerError("Lexical error ocurred"); + + return value; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/MathUtils.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/MathUtils.cs index 64ee9bc..62d37b5 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/MathUtils.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/MathUtils.cs @@ -30,71 +30,82 @@ using System; -namespace Spine3_6_39 { - public static class MathUtils { - public const float PI = 3.1415927f; - public const float PI2 = PI * 2; - public const float RadDeg = 180f / PI; - public const float DegRad = PI / 180; +namespace Spine3_6_39 +{ + public static class MathUtils + { + public const float PI = 3.1415927f; + public const float PI2 = PI * 2; + public const float RadDeg = 180f / PI; + public const float DegRad = PI / 180; - const int SIN_BITS = 14; // 16KB. Adjust for accuracy. - const int SIN_MASK = ~(-1 << SIN_BITS); - const int SIN_COUNT = SIN_MASK + 1; - const float RadFull = PI * 2; - const float DegFull = 360; - const float RadToIndex = SIN_COUNT / RadFull; - const float DegToIndex = SIN_COUNT / DegFull; - static float[] sin = new float[SIN_COUNT]; + const int SIN_BITS = 14; // 16KB. Adjust for accuracy. + const int SIN_MASK = ~(-1 << SIN_BITS); + const int SIN_COUNT = SIN_MASK + 1; + const float RadFull = PI * 2; + const float DegFull = 360; + const float RadToIndex = SIN_COUNT / RadFull; + const float DegToIndex = SIN_COUNT / DegFull; + static float[] sin = new float[SIN_COUNT]; - static MathUtils () { - for (int i = 0; i < SIN_COUNT; i++) - sin[i] = (float)Math.Sin((i + 0.5f) / SIN_COUNT * RadFull); - for (int i = 0; i < 360; i += 90) - sin[(int)(i * DegToIndex) & SIN_MASK] = (float)Math.Sin(i * DegRad); - } + static MathUtils() + { + for (int i = 0; i < SIN_COUNT; i++) + sin[i] = (float)Math.Sin((i + 0.5f) / SIN_COUNT * RadFull); + for (int i = 0; i < 360; i += 90) + sin[(int)(i * DegToIndex) & SIN_MASK] = (float)Math.Sin(i * DegRad); + } - /// Returns the sine in radians from a lookup table. - static public float Sin (float radians) { - return sin[(int)(radians * RadToIndex) & SIN_MASK]; - } + /// Returns the sine in radians from a lookup table. + static public float Sin(float radians) + { + return sin[(int)(radians * RadToIndex) & SIN_MASK]; + } - /// Returns the cosine in radians from a lookup table. - static public float Cos (float radians) { - return sin[(int)((radians + PI / 2) * RadToIndex) & SIN_MASK]; - } - - /// Returns the sine in radians from a lookup table. - static public float SinDeg (float degrees) { - return sin[(int)(degrees * DegToIndex) & SIN_MASK]; - } - - /// Returns the cosine in radians from a lookup table. - static public float CosDeg (float degrees) { - return sin[(int)((degrees + 90) * DegToIndex) & SIN_MASK]; - } + /// Returns the cosine in radians from a lookup table. + static public float Cos(float radians) + { + return sin[(int)((radians + PI / 2) * RadToIndex) & SIN_MASK]; + } - /// 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 public float Atan2 (float y, float x) { - if (x == 0f) { - if (y > 0f) return PI / 2; - if (y == 0f) return 0f; - return -PI / 2; - } - float atan, z = y / x; - if (Math.Abs(z) < 1f) { - atan = z / (1f + 0.28f * z * z); - if (x < 0f) return atan + (y < 0f ? -PI : PI); - return atan; - } - atan = PI / 2 - z / (z * z + 0.28f); - return y < 0f ? atan - PI : atan; - } + /// Returns the sine in radians from a lookup table. + static public float SinDeg(float degrees) + { + return sin[(int)(degrees * DegToIndex) & SIN_MASK]; + } - static public float Clamp (float value, float min, float max) { - if (value < min) return min; - if (value > max) return max; - return value; - } - } + /// Returns the cosine in radians from a lookup table. + static public float CosDeg(float degrees) + { + return sin[(int)((degrees + 90) * DegToIndex) & SIN_MASK]; + } + + /// 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 public float Atan2(float y, float x) + { + if (x == 0f) + { + if (y > 0f) return PI / 2; + if (y == 0f) return 0f; + return -PI / 2; + } + float atan, z = y / x; + if (Math.Abs(z) < 1f) + { + atan = z / (1f + 0.28f * z * z); + if (x < 0f) return atan + (y < 0f ? -PI : PI); + return atan; + } + atan = PI / 2 - z / (z * z + 0.28f); + return y < 0f ? atan - PI : atan; + } + + static public float Clamp(float value, float min, float max) + { + if (value < min) return min; + if (value > max) return max; + return value; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/PathConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/PathConstraint.cs index 0d088d4..8b7d5f7 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/PathConstraint.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/PathConstraint.cs @@ -30,381 +30,436 @@ using System; -namespace Spine3_6_39 { - public class PathConstraint : IConstraint { - const int NONE = -1, BEFORE = -2, AFTER = -3; +namespace Spine3_6_39 +{ + public class PathConstraint : IConstraint + { + const int NONE = -1, BEFORE = -2, AFTER = -3; - internal PathConstraintData data; - internal ExposedList bones; - internal Slot target; - internal float position, spacing, rotateMix, translateMix; + internal PathConstraintData data; + internal ExposedList bones; + internal Slot target; + internal float position, spacing, rotateMix, translateMix; - internal ExposedList spaces = new ExposedList(), positions = new ExposedList(); - internal ExposedList world = new ExposedList(), curves = new ExposedList(), lengths = new ExposedList(); - internal float[] segments = new float[10]; + internal ExposedList spaces = new ExposedList(), positions = new ExposedList(); + internal ExposedList world = new ExposedList(), curves = new ExposedList(), lengths = new ExposedList(); + internal float[] segments = new float[10]; - public int Order { get { return data.order; } } - public float Position { get { return position; } set { position = value; } } - public float Spacing { get { return spacing; } set { spacing = value; } } - public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } - public float TranslateMix { get { return translateMix; } set { translateMix = value; } } - public ExposedList Bones { get { return bones; } } - public Slot Target { get { return target; } set { target = value; } } - public PathConstraintData Data { get { return data; } } + public int Order { get { return data.order; } } + public float Position { get { return position; } set { position = value; } } + public float Spacing { get { return spacing; } set { spacing = value; } } + public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } + public float TranslateMix { get { return translateMix; } set { translateMix = value; } } + public ExposedList Bones { get { return bones; } } + public Slot Target { get { return target; } set { target = value; } } + public PathConstraintData Data { get { return data; } } - public PathConstraint (PathConstraintData data, Skeleton skeleton) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.data = data; - bones = new ExposedList(data.Bones.Count); - foreach (BoneData boneData in data.bones) - bones.Add(skeleton.FindBone(boneData.name)); - target = skeleton.FindSlot(data.target.name); - position = data.position; - spacing = data.spacing; - rotateMix = data.rotateMix; - translateMix = data.translateMix; - } + public PathConstraint(PathConstraintData data, Skeleton skeleton) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + bones = new ExposedList(data.Bones.Count); + foreach (BoneData boneData in data.bones) + bones.Add(skeleton.FindBone(boneData.name)); + target = skeleton.FindSlot(data.target.name); + position = data.position; + spacing = data.spacing; + rotateMix = data.rotateMix; + translateMix = data.translateMix; + } - public void Apply () { - Update(); - } - - public void Update () { - PathAttachment attachment = target.Attachment as PathAttachment; - if (attachment == null) return; + public void Apply() + { + Update(); + } - float rotateMix = this.rotateMix, translateMix = this.translateMix; - bool translate = translateMix > 0, rotate = rotateMix > 0; - if (!translate && !rotate) return; + public void Update() + { + PathAttachment attachment = target.Attachment as PathAttachment; + if (attachment == null) return; - PathConstraintData data = this.data; - SpacingMode spacingMode = data.spacingMode; - bool lengthSpacing = spacingMode == SpacingMode.Length; - RotateMode rotateMode = data.rotateMode; - bool tangents = rotateMode == RotateMode.Tangent, scale = rotateMode == RotateMode.ChainScale; - int boneCount = this.bones.Count, spacesCount = tangents ? boneCount : boneCount + 1; - Bone[] bonesItems = this.bones.Items; - ExposedList spaces = this.spaces.Resize(spacesCount), lengths = null; - float spacing = this.spacing; - if (scale || lengthSpacing) { - if (scale) lengths = this.lengths.Resize(boneCount); - for (int i = 0, n = spacesCount - 1; i < n;) { - Bone bone = bonesItems[i]; - float setupLength = bone.data.length; - if (setupLength == 0) setupLength = 0.000000001f; - float x = setupLength * bone.a, y = setupLength * bone.c; - float length = (float)Math.Sqrt(x * x + y * y); - if (scale) lengths.Items[i] = setupLength; - spaces.Items[++i] = (lengthSpacing ? Math.Max(0, setupLength + spacing) : spacing) * length / setupLength; - } - } else { - for (int i = 1; i < spacesCount; i++) - spaces.Items[i] = spacing; - } + float rotateMix = this.rotateMix, translateMix = this.translateMix; + bool translate = translateMix > 0, rotate = rotateMix > 0; + if (!translate && !rotate) return; - float[] positions = ComputeWorldPositions(attachment, spacesCount, tangents, - data.positionMode == PositionMode.Percent, spacingMode == SpacingMode.Percent); - float boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation; - bool tip; - if (offsetRotation == 0) { - tip = rotateMode == RotateMode.Chain; - } else { - tip = false; - Bone p = target.bone; - offsetRotation *= p.a * p.d - p.b * p.c > 0 ? MathUtils.DegRad : -MathUtils.DegRad; - } - for (int i = 0, p = 3; i < boneCount; i++, p += 3) { - Bone bone = bonesItems[i]; - bone.worldX += (boneX - bone.worldX) * translateMix; - bone.worldY += (boneY - bone.worldY) * translateMix; - float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY; - if (scale) { - float length = lengths.Items[i]; - if (length != 0) { - float s = ((float)Math.Sqrt(dx * dx + dy * dy) / length - 1) * rotateMix + 1; - bone.a *= s; - bone.c *= s; - } - } - boneX = x; - boneY = y; - if (rotate) { - 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.Items[i + 1] == 0) - r = positions[p + 2]; - else - r = MathUtils.Atan2(dy, dx); - r -= MathUtils.Atan2(c, a); - if (tip) { - cos = MathUtils.Cos(r); - sin = MathUtils.Sin(r); - float length = bone.data.length; - boneX += (length * (cos * a - sin * c) - dx) * rotateMix; - boneY += (length * (sin * a + cos * c) - dy) * rotateMix; - } else { - r += offsetRotation; - } - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) // - r += MathUtils.PI2; - r *= rotateMix; - cos = MathUtils.Cos(r); - sin = MathUtils.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.appliedValid = false; - } - } + PathConstraintData data = this.data; + SpacingMode spacingMode = data.spacingMode; + bool lengthSpacing = spacingMode == SpacingMode.Length; + RotateMode rotateMode = data.rotateMode; + bool tangents = rotateMode == RotateMode.Tangent, scale = rotateMode == RotateMode.ChainScale; + int boneCount = this.bones.Count, spacesCount = tangents ? boneCount : boneCount + 1; + Bone[] bonesItems = this.bones.Items; + ExposedList spaces = this.spaces.Resize(spacesCount), lengths = null; + float spacing = this.spacing; + if (scale || lengthSpacing) + { + if (scale) lengths = this.lengths.Resize(boneCount); + for (int i = 0, n = spacesCount - 1; i < n;) + { + Bone bone = bonesItems[i]; + float setupLength = bone.data.length; + if (setupLength == 0) setupLength = 0.000000001f; + float x = setupLength * bone.a, y = setupLength * bone.c; + float length = (float)Math.Sqrt(x * x + y * y); + if (scale) lengths.Items[i] = setupLength; + spaces.Items[++i] = (lengthSpacing ? Math.Max(0, setupLength + spacing) : spacing) * length / setupLength; + } + } + else + { + for (int i = 1; i < spacesCount; i++) + spaces.Items[i] = spacing; + } - float[] ComputeWorldPositions (PathAttachment path, int spacesCount, bool tangents, bool percentPosition, - bool percentSpacing) { + float[] positions = ComputeWorldPositions(attachment, spacesCount, tangents, + data.positionMode == PositionMode.Percent, spacingMode == SpacingMode.Percent); + float boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation; + bool tip; + if (offsetRotation == 0) + { + tip = rotateMode == RotateMode.Chain; + } + else + { + tip = false; + Bone p = target.bone; + offsetRotation *= p.a * p.d - p.b * p.c > 0 ? MathUtils.DegRad : -MathUtils.DegRad; + } + for (int i = 0, p = 3; i < boneCount; i++, p += 3) + { + Bone bone = bonesItems[i]; + bone.worldX += (boneX - bone.worldX) * translateMix; + bone.worldY += (boneY - bone.worldY) * translateMix; + float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY; + if (scale) + { + float length = lengths.Items[i]; + if (length != 0) + { + float s = ((float)Math.Sqrt(dx * dx + dy * dy) / length - 1) * rotateMix + 1; + bone.a *= s; + bone.c *= s; + } + } + boneX = x; + boneY = y; + if (rotate) + { + 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.Items[i + 1] == 0) + r = positions[p + 2]; + else + r = MathUtils.Atan2(dy, dx); + r -= MathUtils.Atan2(c, a); + if (tip) + { + cos = MathUtils.Cos(r); + sin = MathUtils.Sin(r); + float length = bone.data.length; + boneX += (length * (cos * a - sin * c) - dx) * rotateMix; + boneY += (length * (sin * a + cos * c) - dy) * rotateMix; + } + else + { + r += offsetRotation; + } + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) // + r += MathUtils.PI2; + r *= rotateMix; + cos = MathUtils.Cos(r); + sin = MathUtils.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.appliedValid = false; + } + } - Slot target = this.target; - float position = this.position; - float[] spacesItems = this.spaces.Items, output = this.positions.Resize(spacesCount * 3 + 2).Items, world; - bool closed = path.Closed; - int verticesLength = path.WorldVerticesLength, curveCount = verticesLength / 6, prevCurve = NONE; + float[] ComputeWorldPositions(PathAttachment path, int spacesCount, bool tangents, bool percentPosition, + bool percentSpacing) + { - float pathLength; - if (!path.ConstantSpeed) { - float[] lengths = path.Lengths; - curveCount -= closed ? 1 : 2; - pathLength = lengths[curveCount]; - if (percentPosition) position *= pathLength; - if (percentSpacing) { - for (int i = 0; i < spacesCount; i++) - spacesItems[i] *= pathLength; - } - world = this.world.Resize(8).Items; - for (int i = 0, o = 0, curve = 0; i < spacesCount; i++, o += 3) { - float space = spacesItems[i]; - position += space; - float p = position; + Slot target = this.target; + float position = this.position; + float[] spacesItems = this.spaces.Items, output = this.positions.Resize(spacesCount * 3 + 2).Items, world; + bool closed = path.Closed; + int verticesLength = path.WorldVerticesLength, curveCount = verticesLength / 6, prevCurve = NONE; - if (closed) { - 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, output, o); - continue; - } else if (p > pathLength) { - if (prevCurve != AFTER) { - prevCurve = AFTER; - path.ComputeWorldVertices(target, verticesLength - 6, 4, world, 0); - } - AddAfterPosition(p - pathLength, world, 0, output, o); - continue; - } + float pathLength; + if (!path.ConstantSpeed) + { + float[] lengths = path.Lengths; + curveCount -= closed ? 1 : 2; + pathLength = lengths[curveCount]; + if (percentPosition) position *= pathLength; + if (percentSpacing) + { + for (int i = 0; i < spacesCount; i++) + spacesItems[i] *= pathLength; + } + world = this.world.Resize(8).Items; + for (int i = 0, o = 0, curve = 0; i < spacesCount; i++, o += 3) + { + float space = spacesItems[i]; + position += space; + float p = position; - // 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], output, o, - tangents || (i > 0 && space == 0)); - } - return output; - } + if (closed) + { + 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, output, o); + continue; + } + else if (p > pathLength) + { + if (prevCurve != AFTER) + { + prevCurve = AFTER; + path.ComputeWorldVertices(target, verticesLength - 6, 4, world, 0); + } + AddAfterPosition(p - pathLength, world, 0, output, o); + continue; + } - // World vertices. - if (closed) { - verticesLength += 2; - world = this.world.Resize(verticesLength).Items; - 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 = this.world.Resize(verticesLength).Items; - path.ComputeWorldVertices(target, 2, verticesLength, world, 0); - } + // 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], output, o, + tangents || (i > 0 && space == 0)); + } + return output; + } - // Curve lengths. - float[] curves = this.curves.Resize(curveCount).Items; - 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 += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - dfx += ddfx; - dfy += ddfy; - ddfx += dddfx; - ddfy += dddfy; - pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - dfx += ddfx; - dfy += ddfy; - pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - dfx += ddfx + dddfx; - dfy += ddfy + dddfy; - pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - curves[i] = pathLength; - x1 = x2; - y1 = y2; - } - if (percentPosition) position *= pathLength; - if (percentSpacing) { - for (int i = 0; i < spacesCount; i++) - spacesItems[i] *= pathLength; - } + // World vertices. + if (closed) + { + verticesLength += 2; + world = this.world.Resize(verticesLength).Items; + 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 = this.world.Resize(verticesLength).Items; + path.ComputeWorldVertices(target, 2, verticesLength, world, 0); + } - float[] segments = this.segments; - float curveLength = 0; - for (int i = 0, o = 0, curve = 0, segment = 0; i < spacesCount; i++, o += 3) { - float space = spacesItems[i]; - position += space; - float p = position; + // Curve lengths. + float[] curves = this.curves.Resize(curveCount).Items; + 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 += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx; + dfy += ddfy; + pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx + dddfx; + dfy += ddfy + dddfy; + pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + curves[i] = pathLength; + x1 = x2; + y1 = y2; + } + if (percentPosition) position *= pathLength; + if (percentSpacing) + { + for (int i = 0; i < spacesCount; i++) + spacesItems[i] *= pathLength; + } - if (closed) { - p %= pathLength; - if (p < 0) p += pathLength; - curve = 0; - } else if (p < 0) { - AddBeforePosition(p, world, 0, output, o); - continue; - } else if (p > pathLength) { - AddAfterPosition(p - pathLength, world, verticesLength - 4, output, o); - continue; - } + float[] segments = this.segments; + float curveLength = 0; + for (int i = 0, o = 0, curve = 0, segment = 0; i < spacesCount; i++, o += 3) + { + float space = spacesItems[i]; + position += space; + float p = position; - // 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; - } + if (closed) + { + p %= pathLength; + if (p < 0) p += pathLength; + curve = 0; + } + else if (p < 0) + { + AddBeforePosition(p, world, 0, output, o); + continue; + } + else if (p > pathLength) + { + AddAfterPosition(p - pathLength, world, verticesLength - 4, output, o); + continue; + } - // 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 = (float)Math.Sqrt(dfx * dfx + dfy * dfy); - segments[0] = curveLength; - for (ii = 1; ii < 8; ii++) { - dfx += ddfx; - dfy += ddfy; - ddfx += dddfx; - ddfy += dddfy; - curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - segments[ii] = curveLength; - } - dfx += ddfx; - dfy += ddfy; - curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - segments[8] = curveLength; - dfx += ddfx + dddfx; - dfy += ddfy + dddfy; - curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - segments[9] = curveLength; - segment = 0; - } + // 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; + } - // 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, output, o, tangents || (i > 0 && space == 0)); - } - return output; - } + // 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 = (float)Math.Sqrt(dfx * dfx + dfy * dfy); + segments[0] = curveLength; + for (ii = 1; ii < 8; ii++) + { + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + segments[ii] = curveLength; + } + dfx += ddfx; + dfy += ddfy; + curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + segments[8] = curveLength; + dfx += ddfx + dddfx; + dfy += ddfy + dddfy; + curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + segments[9] = curveLength; + segment = 0; + } - static void AddBeforePosition (float p, float[] temp, int i, float[] output, int o) { - float x1 = temp[i], y1 = temp[i + 1], dx = temp[i + 2] - x1, dy = temp[i + 3] - y1, r = MathUtils.Atan2(dy, dx); - output[o] = x1 + p * MathUtils.Cos(r); - output[o + 1] = y1 + p * MathUtils.Sin(r); - output[o + 2] = r; - } + // 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, output, o, tangents || (i > 0 && space == 0)); + } + return output; + } - static void AddAfterPosition (float p, float[] temp, int i, float[] output, int o) { - float x1 = temp[i + 2], y1 = temp[i + 3], dx = x1 - temp[i], dy = y1 - temp[i + 1], r = MathUtils.Atan2(dy, dx); - output[o] = x1 + p * MathUtils.Cos(r); - output[o + 1] = y1 + p * MathUtils.Sin(r); - output[o + 2] = r; - } + static void AddBeforePosition(float p, float[] temp, int i, float[] output, int o) + { + float x1 = temp[i], y1 = temp[i + 1], dx = temp[i + 2] - x1, dy = temp[i + 3] - y1, r = MathUtils.Atan2(dy, dx); + output[o] = x1 + p * MathUtils.Cos(r); + output[o + 1] = y1 + p * MathUtils.Sin(r); + output[o + 2] = r; + } - static void AddCurvePosition (float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2, - float[] output, int o, bool tangents) { - if (p == 0 || float.IsNaN(p)) p = 0.0001f; - 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) output[o + 2] = (float)Math.Atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt)); - } - } + static void AddAfterPosition(float p, float[] temp, int i, float[] output, int o) + { + float x1 = temp[i + 2], y1 = temp[i + 3], dx = x1 - temp[i], dy = y1 - temp[i + 1], r = MathUtils.Atan2(dy, dx); + output[o] = x1 + p * MathUtils.Cos(r); + output[o + 1] = y1 + p * MathUtils.Sin(r); + output[o + 2] = r; + } + + static void AddCurvePosition(float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2, + float[] output, int o, bool tangents) + { + if (p == 0 || float.IsNaN(p)) p = 0.0001f; + 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) output[o + 2] = (float)Math.Atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt)); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/PathConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/PathConstraintData.cs index 8e6c7b0..22c0aaa 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/PathConstraintData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/PathConstraintData.cs @@ -30,50 +30,57 @@ using System; -namespace Spine3_6_39 { - public class PathConstraintData { - internal string name; - internal int order; - internal ExposedList bones = new ExposedList(); - internal SlotData target; - internal PositionMode positionMode; - internal SpacingMode spacingMode; - internal RotateMode rotateMode; - internal float offsetRotation; - internal float position, spacing, rotateMix, translateMix; +namespace Spine3_6_39 +{ + public class PathConstraintData + { + internal string name; + internal int order; + internal ExposedList bones = new ExposedList(); + internal SlotData target; + internal PositionMode positionMode; + internal SpacingMode spacingMode; + internal RotateMode rotateMode; + internal float offsetRotation; + internal float position, spacing, rotateMix, translateMix; - public string Name { get { return name; } } - public int Order { get { return order; } set { order = value; } } - public ExposedList Bones { get { return bones; } } - public SlotData Target { get { return target; } set { target = value; } } - public PositionMode PositionMode { get { return positionMode; } set { positionMode = value; } } - public SpacingMode SpacingMode { get { return spacingMode; } set { spacingMode = value; } } - public RotateMode RotateMode { get { return rotateMode; } set { rotateMode = value; } } - public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } - public float Position { get { return position; } set { position = value; } } - public float Spacing { get { return spacing; } set { spacing = value; } } - public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } - public float TranslateMix { get { return translateMix; } set { translateMix = value; } } + public string Name { get { return name; } } + public int Order { get { return order; } set { order = value; } } + public ExposedList Bones { get { return bones; } } + public SlotData Target { get { return target; } set { target = value; } } + public PositionMode PositionMode { get { return positionMode; } set { positionMode = value; } } + public SpacingMode SpacingMode { get { return spacingMode; } set { spacingMode = value; } } + public RotateMode RotateMode { get { return rotateMode; } set { rotateMode = value; } } + public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } + public float Position { get { return position; } set { position = value; } } + public float Spacing { get { return spacing; } set { spacing = value; } } + public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } + public float TranslateMix { get { return translateMix; } set { translateMix = value; } } - public PathConstraintData (String name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; - } + public PathConstraintData(String name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + } - public override string ToString () { - return name; - } - } - - public enum PositionMode { - Fixed, Percent - } + public override string ToString() + { + return name; + } + } - public enum SpacingMode { - Length, Fixed, Percent - } + public enum PositionMode + { + Fixed, Percent + } - public enum RotateMode { - Tangent, Chain, ChainScale - } + public enum SpacingMode + { + Length, Fixed, Percent + } + + public enum RotateMode + { + Tangent, Chain, ChainScale + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Skeleton.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Skeleton.cs index 959202b..337edeb 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Skeleton.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Skeleton.cs @@ -29,508 +29,581 @@ *****************************************************************************/ using System; -using System.Collections.Generic; - -namespace Spine3_6_39 { - public class Skeleton { - internal SkeletonData data; - internal ExposedList bones; - internal ExposedList slots; - internal ExposedList drawOrder; - internal ExposedList ikConstraints; - internal ExposedList transformConstraints; - internal ExposedList pathConstraints; - internal ExposedList updateCache = new ExposedList(); - internal ExposedList updateCacheReset = new ExposedList(); - internal Skin skin; - internal float r = 1, g = 1, b = 1, a = 1; - internal float time; - internal bool flipX, flipY; - internal float x, y; - - public SkeletonData Data { get { return data; } } - public ExposedList Bones { get { return bones; } } - public ExposedList UpdateCacheList { get { return updateCache; } } - public ExposedList Slots { get { return slots; } } - public ExposedList DrawOrder { get { return drawOrder; } } - public ExposedList IkConstraints { get { return ikConstraints; } } - public ExposedList PathConstraints { get { return pathConstraints; } } - public ExposedList TransformConstraints { get { return transformConstraints; } } - public Skin Skin { get { return skin; } set { skin = value; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } - public float Time { get { return time; } set { time = value; } } - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public bool FlipX { get { return flipX; } set { flipX = value; } } - public bool FlipY { get { return flipY; } set { flipY = value; } } - - public Bone RootBone { - get { return bones.Count == 0 ? null : bones.Items[0]; } - } - - public Skeleton (SkeletonData data) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - this.data = data; - - bones = new ExposedList(data.bones.Count); - foreach (BoneData boneData in data.bones) { - Bone bone; - if (boneData.parent == null) { - bone = new Bone(boneData, this, null); - } else { - Bone parent = bones.Items[boneData.parent.index]; - bone = new Bone(boneData, this, parent); - parent.children.Add(bone); - } - bones.Add(bone); - } - - slots = new ExposedList(data.slots.Count); - drawOrder = new ExposedList(data.slots.Count); - foreach (SlotData slotData in data.slots) { - Bone bone = bones.Items[slotData.boneData.index]; - Slot slot = new Slot(slotData, bone); - slots.Add(slot); - drawOrder.Add(slot); - } - - ikConstraints = new ExposedList(data.ikConstraints.Count); - foreach (IkConstraintData ikConstraintData in data.ikConstraints) - ikConstraints.Add(new IkConstraint(ikConstraintData, this)); - - transformConstraints = new ExposedList(data.transformConstraints.Count); - foreach (TransformConstraintData transformConstraintData in data.transformConstraints) - transformConstraints.Add(new TransformConstraint(transformConstraintData, this)); - - pathConstraints = new ExposedList (data.pathConstraints.Count); - foreach (PathConstraintData pathConstraintData in data.pathConstraints) - pathConstraints.Add(new PathConstraint(pathConstraintData, this)); - - UpdateCache(); - UpdateWorldTransform(); - } - - /// Caches information about bones and constraints. Must be called if bones, constraints or weighted path attachments are added - /// or removed. - public void UpdateCache () { - ExposedList updateCache = this.updateCache; - updateCache.Clear(); - this.updateCacheReset.Clear(); - - ExposedList bones = this.bones; - for (int i = 0, n = bones.Count; i < n; i++) - bones.Items[i].sorted = false; - - ExposedList ikConstraints = this.ikConstraints; - var transformConstraints = this.transformConstraints; - var pathConstraints = this.pathConstraints; - int ikCount = IkConstraints.Count, transformCount = transformConstraints.Count, pathCount = pathConstraints.Count; - int constraintCount = ikCount + transformCount + pathCount; - //outer: - for (int i = 0; i < constraintCount; i++) { - for (int ii = 0; ii < ikCount; ii++) { - IkConstraint constraint = ikConstraints.Items[ii]; - if (constraint.data.order == i) { - SortIkConstraint(constraint); - goto outer; //continue outer; - } - } - for (int ii = 0; ii < transformCount; ii++) { - TransformConstraint constraint = transformConstraints.Items[ii]; - if (constraint.data.order == i) { - SortTransformConstraint(constraint); - goto outer; //continue outer; - } - } - for (int ii = 0; ii < pathCount; ii++) { - PathConstraint constraint = pathConstraints.Items[ii]; - if (constraint.data.order == i) { - SortPathConstraint(constraint); - goto outer; //continue outer; - } - } - outer: {} - } - - for (int i = 0, n = bones.Count; i < n; i++) - SortBone(bones.Items[i]); - } - - private void SortIkConstraint (IkConstraint constraint) { - Bone target = constraint.target; - SortBone(target); - - var constrained = constraint.bones; - Bone parent = constrained.Items[0]; - SortBone(parent); - - if (constrained.Count > 1) { - Bone child = constrained.Items[constrained.Count - 1]; - if (!updateCache.Contains(child)) - updateCacheReset.Add(child); - } - - updateCache.Add(constraint); - - SortReset(parent.children); - constrained.Items[constrained.Count - 1].sorted = true; - } - - private void SortPathConstraint (PathConstraint constraint) { - Slot slot = constraint.target; - int slotIndex = slot.data.index; - Bone slotBone = slot.bone; - if (skin != null) SortPathConstraintAttachment(skin, slotIndex, slotBone); - if (data.defaultSkin != null && data.defaultSkin != skin) - SortPathConstraintAttachment(data.defaultSkin, slotIndex, slotBone); - for (int ii = 0, nn = data.skins.Count; ii < nn; ii++) - SortPathConstraintAttachment(data.skins.Items[ii], slotIndex, slotBone); - - Attachment attachment = slot.attachment; - if (attachment is PathAttachment) SortPathConstraintAttachment(attachment, slotBone); - - var constrained = constraint.bones; - int boneCount = constrained.Count; - for (int i = 0; i < boneCount; i++) - SortBone(constrained.Items[i]); - - updateCache.Add(constraint); - - for (int i = 0; i < boneCount; i++) - SortReset(constrained.Items[i].children); - for (int i = 0; i < boneCount; i++) - constrained.Items[i].sorted = true; - } - - private void SortTransformConstraint (TransformConstraint constraint) { - SortBone(constraint.target); - - var constrained = constraint.bones; - int boneCount = constrained.Count; - if (constraint.data.local) { - for (int i = 0; i < boneCount; i++) { - Bone child = constrained.Items[i]; - SortBone(child.parent); - if (!updateCache.Contains(child)) updateCacheReset.Add(child); - } - } else { - for (int i = 0; i < boneCount; i++) - SortBone(constrained.Items[i]); - } - - updateCache.Add(constraint); - - for (int i = 0; i < boneCount; i++) - SortReset(constrained.Items[i].children); - for (int i = 0; i < boneCount; i++) - constrained.Items[i].sorted = true; - } - - private void SortPathConstraintAttachment (Skin skin, int slotIndex, Bone slotBone) { - foreach (var entry in skin.Attachments) - if (entry.Key.slotIndex == slotIndex) SortPathConstraintAttachment(entry.Value, slotBone); - } - - private void SortPathConstraintAttachment (Attachment attachment, Bone slotBone) { - if (!(attachment is PathAttachment)) return; - int[] pathBones = ((PathAttachment)attachment).bones; - if (pathBones == null) - SortBone(slotBone); - else { - var bones = this.bones; - for (int i = 0, n = pathBones.Length; i < n;) { - int nn = pathBones[i++]; - nn += i; - while (i < nn) - SortBone(bones.Items[pathBones[i++]]); - } - } - } - - private void SortBone (Bone bone) { - if (bone.sorted) return; - Bone parent = bone.parent; - if (parent != null) SortBone(parent); - bone.sorted = true; - updateCache.Add(bone); - } - - private static void SortReset (ExposedList bones) { - var bonesItems = bones.Items; - for (int i = 0, n = bones.Count; i < n; i++) { - Bone bone = bonesItems[i]; - if (bone.sorted) SortReset(bone.children); - bone.sorted = false; - } - } - - /// Updates the world transform for each bone and applies constraints. - public void UpdateWorldTransform () { - var updateCacheReset = this.updateCacheReset; - var updateCacheResetItems = updateCacheReset.Items; - for (int i = 0, n = updateCacheReset.Count; i < n; i++) { - Bone bone = updateCacheResetItems[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; - bone.appliedValid = true; - } - var updateItems = this.updateCache.Items; - for (int i = 0, n = updateCache.Count; i < n; i++) - updateItems[i].Update(); - } - - /// Sets the bones, constraints, and slots to their setup pose values. - public void SetToSetupPose () { - SetBonesToSetupPose(); - SetSlotsToSetupPose(); - } - - /// Sets the bones and constraints to their setup pose values. - public void SetBonesToSetupPose () { - var bonesItems = this.bones.Items; - for (int i = 0, n = bones.Count; i < n; i++) - bonesItems[i].SetToSetupPose(); - - var ikConstraintsItems = this.ikConstraints.Items; - for (int i = 0, n = ikConstraints.Count; i < n; i++) { - IkConstraint constraint = ikConstraintsItems[i]; - constraint.bendDirection = constraint.data.bendDirection; - constraint.mix = constraint.data.mix; - } - - var transformConstraintsItems = this.transformConstraints.Items; - for (int i = 0, n = transformConstraints.Count; i < n; i++) { - TransformConstraint constraint = transformConstraintsItems[i]; - TransformConstraintData constraintData = constraint.data; - constraint.rotateMix = constraintData.rotateMix; - constraint.translateMix = constraintData.translateMix; - constraint.scaleMix = constraintData.scaleMix; - constraint.shearMix = constraintData.shearMix; - } - - var pathConstraintItems = this.pathConstraints.Items; - for (int i = 0, n = pathConstraints.Count; i < n; i++) { - PathConstraint constraint = pathConstraintItems[i]; - PathConstraintData constraintData = constraint.data; - constraint.position = constraintData.position; - constraint.spacing = constraintData.spacing; - constraint.rotateMix = constraintData.rotateMix; - constraint.translateMix = constraintData.translateMix; - } - } - - public void SetSlotsToSetupPose () { - var slots = this.slots; - var slotsItems = slots.Items; - drawOrder.Clear(); - for (int i = 0, n = slots.Count; i < n; i++) - drawOrder.Add(slotsItems[i]); - - for (int i = 0, n = slots.Count; i < n; i++) - slotsItems[i].SetToSetupPose(); - } - - /// May be null. - public Bone FindBone (string boneName) { - if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); - var bones = this.bones; - var bonesItems = bones.Items; - for (int i = 0, n = bones.Count; i < n; i++) { - Bone bone = bonesItems[i]; - if (bone.data.name == boneName) return bone; - } - return null; - } - - /// -1 if the bone was not found. - public int FindBoneIndex (string boneName) { - if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); - var bones = this.bones; - var bonesItems = bones.Items; - for (int i = 0, n = bones.Count; i < n; i++) - if (bonesItems[i].data.name == boneName) return i; - return -1; - } - - /// May be null. - public Slot FindSlot (string slotName) { - if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); - var slots = this.slots; - var slotsItems = slots.Items; - for (int i = 0, n = slots.Count; i < n; i++) { - Slot slot = slotsItems[i]; - if (slot.data.name == slotName) return slot; - } - return null; - } - - /// -1 if the bone was not found. - public int FindSlotIndex (string slotName) { - if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); - var slots = this.slots; - var slotsItems = slots.Items; - for (int i = 0, n = slots.Count; i < n; i++) - if (slotsItems[i].data.name.Equals(slotName)) return i; - return -1; - } - - /// Sets a skin by name (see SetSkin). - public void SetSkin (string skinName) { - Skin foundSkin = data.FindSkin(skinName); - if (foundSkin == null) throw new ArgumentException("Skin not found: " + skinName, "skinName"); - SetSkin(foundSkin); - } - - /// Sets the skin used to look up attachments before looking in the {@link SkeletonData#getDefaultSkin() default - /// skin}. Attachmentsfrom 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. - /// May be null. - public void SetSkin (Skin newSkin) { - if (newSkin != null) { - if (skin != null) - newSkin.AttachAll(this, skin); - else { - ExposedList slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) { - Slot slot = slots.Items[i]; - string name = slot.data.attachmentName; - if (name != null) { - Attachment attachment = newSkin.GetAttachment(i, name); - if (attachment != null) slot.Attachment = attachment; - } - } - } - } - skin = newSkin; - } - - /// May be null. - public Attachment GetAttachment (string slotName, string attachmentName) { - return GetAttachment(data.FindSlotIndex(slotName), attachmentName); - } - - /// May be null. - public Attachment GetAttachment (int slotIndex, string attachmentName) { - if (attachmentName == null) throw new ArgumentNullException("attachmentName", "attachmentName cannot be null."); - if (skin != null) { - Attachment attachment = skin.GetAttachment(slotIndex, attachmentName); - if (attachment != null) return attachment; - } - return data.defaultSkin != null ? data.defaultSkin.GetAttachment(slotIndex, attachmentName) : null; - } - - /// May be null. - public void SetAttachment (string slotName, string attachmentName) { - if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); - ExposedList slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) { - Slot slot = slots.Items[i]; - if (slot.data.name == slotName) { - Attachment attachment = null; - if (attachmentName != null) { - attachment = GetAttachment(i, attachmentName); - if (attachment == null) throw new Exception("Attachment not found: " + attachmentName + ", for slot: " + slotName); - } - slot.Attachment = attachment; - return; - } - } - throw new Exception("Slot not found: " + slotName); - } - - /// May be null. - public IkConstraint FindIkConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - ExposedList ikConstraints = this.ikConstraints; - for (int i = 0, n = ikConstraints.Count; i < n; i++) { - IkConstraint ikConstraint = ikConstraints.Items[i]; - if (ikConstraint.data.name == constraintName) return ikConstraint; - } - return null; - } - - /// May be null. - public TransformConstraint FindTransformConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - ExposedList transformConstraints = this.transformConstraints; - for (int i = 0, n = transformConstraints.Count; i < n; i++) { - TransformConstraint transformConstraint = transformConstraints.Items[i]; - if (transformConstraint.data.name == constraintName) return transformConstraint; - } - return null; - } - - /// May be null. - public PathConstraint FindPathConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - ExposedList pathConstraints = this.pathConstraints; - for (int i = 0, n = pathConstraints.Count; i < n; i++) { - PathConstraint constraint = pathConstraints.Items[i]; - if (constraint.data.name.Equals(constraintName)) return constraint; - } - return null; - } - - public void Update (float delta) { - time += delta; - } - - /// Returns the axis aligned bounding box (AABB) of the region and mesh attachments for the current pose. - /// The horizontal distance between the skeleton origin and the left side of the AABB. - /// The vertical distance between the skeleton origin and the bottom side of the AABB. - /// The width of the AABB - /// The height of the AABB. - /// Reference to hold a float[]. May be a null reference. This method will assign it a new float[] with the appropriate size as needed. - public void GetBounds (out float x, out float y, out float width, out float height, ref float[] vertexBuffer) { - float[] temp = vertexBuffer; - temp = temp ?? new float[8]; - var drawOrderItems = this.drawOrder.Items; - float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; - for (int i = 0, n = this.drawOrder.Count; i < n; i++) { - Slot slot = drawOrderItems[i]; - int verticesLength = 0; - float[] vertices = null; - Attachment attachment = slot.attachment; - var regionAttachment = attachment as RegionAttachment; - if (regionAttachment != null) { - verticesLength = 8; - vertices = temp; - if (vertices.Length < 8) vertices = temp = new float[8]; - regionAttachment.ComputeWorldVertices(slot.bone, temp, 0); - } else { - var meshAttachment = attachment as MeshAttachment; - if (meshAttachment != null) { - MeshAttachment mesh = meshAttachment; - verticesLength = mesh.WorldVerticesLength; - vertices = temp; - if (vertices.Length < verticesLength) vertices = temp = new float[verticesLength]; - mesh.ComputeWorldVertices(slot, 0, verticesLength, temp, 0); - } - } - - if (vertices != null) { - for (int ii = 0; ii < verticesLength; ii += 2) { - float vx = vertices[ii], vy = vertices[ii + 1]; - minX = Math.Min(minX, vx); - minY = Math.Min(minY, vy); - maxX = Math.Max(maxX, vx); - maxY = Math.Max(maxY, vy); - } - } - } - x = minX; - y = minY; - width = maxX - minX; - height = maxY - minY; - vertexBuffer = temp; - } - } + +namespace Spine3_6_39 +{ + public class Skeleton + { + internal SkeletonData data; + internal ExposedList bones; + internal ExposedList slots; + internal ExposedList drawOrder; + internal ExposedList ikConstraints; + internal ExposedList transformConstraints; + internal ExposedList pathConstraints; + internal ExposedList updateCache = new ExposedList(); + internal ExposedList updateCacheReset = new ExposedList(); + internal Skin skin; + internal float r = 1, g = 1, b = 1, a = 1; + internal float time; + internal bool flipX, flipY; + internal float x, y; + + public SkeletonData Data { get { return data; } } + public ExposedList Bones { get { return bones; } } + public ExposedList UpdateCacheList { get { return updateCache; } } + public ExposedList Slots { get { return slots; } } + public ExposedList DrawOrder { get { return drawOrder; } } + public ExposedList IkConstraints { get { return ikConstraints; } } + public ExposedList PathConstraints { get { return pathConstraints; } } + public ExposedList TransformConstraints { get { return transformConstraints; } } + public Skin Skin { get { return skin; } set { skin = value; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } + public float Time { get { return time; } set { time = value; } } + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public bool FlipX { get { return flipX; } set { flipX = value; } } + public bool FlipY { get { return flipY; } set { flipY = value; } } + + public Bone RootBone + { + get { return bones.Count == 0 ? null : bones.Items[0]; } + } + + public Skeleton(SkeletonData data) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + this.data = data; + + bones = new ExposedList(data.bones.Count); + foreach (BoneData boneData in data.bones) + { + Bone bone; + if (boneData.parent == null) + { + bone = new Bone(boneData, this, null); + } + else + { + Bone parent = bones.Items[boneData.parent.index]; + bone = new Bone(boneData, this, parent); + parent.children.Add(bone); + } + bones.Add(bone); + } + + slots = new ExposedList(data.slots.Count); + drawOrder = new ExposedList(data.slots.Count); + foreach (SlotData slotData in data.slots) + { + Bone bone = bones.Items[slotData.boneData.index]; + Slot slot = new Slot(slotData, bone); + slots.Add(slot); + drawOrder.Add(slot); + } + + ikConstraints = new ExposedList(data.ikConstraints.Count); + foreach (IkConstraintData ikConstraintData in data.ikConstraints) + ikConstraints.Add(new IkConstraint(ikConstraintData, this)); + + transformConstraints = new ExposedList(data.transformConstraints.Count); + foreach (TransformConstraintData transformConstraintData in data.transformConstraints) + transformConstraints.Add(new TransformConstraint(transformConstraintData, this)); + + pathConstraints = new ExposedList(data.pathConstraints.Count); + foreach (PathConstraintData pathConstraintData in data.pathConstraints) + pathConstraints.Add(new PathConstraint(pathConstraintData, this)); + + UpdateCache(); + UpdateWorldTransform(); + } + + /// Caches information about bones and constraints. Must be called if bones, constraints or weighted path attachments are added + /// or removed. + public void UpdateCache() + { + ExposedList updateCache = this.updateCache; + updateCache.Clear(); + this.updateCacheReset.Clear(); + + ExposedList bones = this.bones; + for (int i = 0, n = bones.Count; i < n; i++) + bones.Items[i].sorted = false; + + ExposedList ikConstraints = this.ikConstraints; + var transformConstraints = this.transformConstraints; + var pathConstraints = this.pathConstraints; + int ikCount = IkConstraints.Count, transformCount = transformConstraints.Count, pathCount = pathConstraints.Count; + int constraintCount = ikCount + transformCount + pathCount; + //outer: + for (int i = 0; i < constraintCount; i++) + { + for (int ii = 0; ii < ikCount; ii++) + { + IkConstraint constraint = ikConstraints.Items[ii]; + if (constraint.data.order == i) + { + SortIkConstraint(constraint); + goto outer; //continue outer; + } + } + for (int ii = 0; ii < transformCount; ii++) + { + TransformConstraint constraint = transformConstraints.Items[ii]; + if (constraint.data.order == i) + { + SortTransformConstraint(constraint); + goto outer; //continue outer; + } + } + for (int ii = 0; ii < pathCount; ii++) + { + PathConstraint constraint = pathConstraints.Items[ii]; + if (constraint.data.order == i) + { + SortPathConstraint(constraint); + goto outer; //continue outer; + } + } + outer: { } + } + + for (int i = 0, n = bones.Count; i < n; i++) + SortBone(bones.Items[i]); + } + + private void SortIkConstraint(IkConstraint constraint) + { + Bone target = constraint.target; + SortBone(target); + + var constrained = constraint.bones; + Bone parent = constrained.Items[0]; + SortBone(parent); + + if (constrained.Count > 1) + { + Bone child = constrained.Items[constrained.Count - 1]; + if (!updateCache.Contains(child)) + updateCacheReset.Add(child); + } + + updateCache.Add(constraint); + + SortReset(parent.children); + constrained.Items[constrained.Count - 1].sorted = true; + } + + private void SortPathConstraint(PathConstraint constraint) + { + Slot slot = constraint.target; + int slotIndex = slot.data.index; + Bone slotBone = slot.bone; + if (skin != null) SortPathConstraintAttachment(skin, slotIndex, slotBone); + if (data.defaultSkin != null && data.defaultSkin != skin) + SortPathConstraintAttachment(data.defaultSkin, slotIndex, slotBone); + for (int ii = 0, nn = data.skins.Count; ii < nn; ii++) + SortPathConstraintAttachment(data.skins.Items[ii], slotIndex, slotBone); + + Attachment attachment = slot.attachment; + if (attachment is PathAttachment) SortPathConstraintAttachment(attachment, slotBone); + + var constrained = constraint.bones; + int boneCount = constrained.Count; + for (int i = 0; i < boneCount; i++) + SortBone(constrained.Items[i]); + + updateCache.Add(constraint); + + for (int i = 0; i < boneCount; i++) + SortReset(constrained.Items[i].children); + for (int i = 0; i < boneCount; i++) + constrained.Items[i].sorted = true; + } + + private void SortTransformConstraint(TransformConstraint constraint) + { + SortBone(constraint.target); + + var constrained = constraint.bones; + int boneCount = constrained.Count; + if (constraint.data.local) + { + for (int i = 0; i < boneCount; i++) + { + Bone child = constrained.Items[i]; + SortBone(child.parent); + if (!updateCache.Contains(child)) updateCacheReset.Add(child); + } + } + else + { + for (int i = 0; i < boneCount; i++) + SortBone(constrained.Items[i]); + } + + updateCache.Add(constraint); + + for (int i = 0; i < boneCount; i++) + SortReset(constrained.Items[i].children); + for (int i = 0; i < boneCount; i++) + constrained.Items[i].sorted = true; + } + + private void SortPathConstraintAttachment(Skin skin, int slotIndex, Bone slotBone) + { + foreach (var entry in skin.Attachments) + if (entry.Key.slotIndex == slotIndex) SortPathConstraintAttachment(entry.Value, slotBone); + } + + private void SortPathConstraintAttachment(Attachment attachment, Bone slotBone) + { + if (!(attachment is PathAttachment)) return; + int[] pathBones = ((PathAttachment)attachment).bones; + if (pathBones == null) + SortBone(slotBone); + else + { + var bones = this.bones; + for (int i = 0, n = pathBones.Length; i < n;) + { + int nn = pathBones[i++]; + nn += i; + while (i < nn) + SortBone(bones.Items[pathBones[i++]]); + } + } + } + + private void SortBone(Bone bone) + { + if (bone.sorted) return; + Bone parent = bone.parent; + if (parent != null) SortBone(parent); + bone.sorted = true; + updateCache.Add(bone); + } + + private static void SortReset(ExposedList bones) + { + var bonesItems = bones.Items; + for (int i = 0, n = bones.Count; i < n; i++) + { + Bone bone = bonesItems[i]; + if (bone.sorted) SortReset(bone.children); + bone.sorted = false; + } + } + + /// Updates the world transform for each bone and applies constraints. + public void UpdateWorldTransform() + { + var updateCacheReset = this.updateCacheReset; + var updateCacheResetItems = updateCacheReset.Items; + for (int i = 0, n = updateCacheReset.Count; i < n; i++) + { + Bone bone = updateCacheResetItems[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; + bone.appliedValid = true; + } + var updateItems = this.updateCache.Items; + for (int i = 0, n = updateCache.Count; i < n; i++) + updateItems[i].Update(); + } + + /// Sets the bones, constraints, and slots to their setup pose values. + public void SetToSetupPose() + { + SetBonesToSetupPose(); + SetSlotsToSetupPose(); + } + + /// Sets the bones and constraints to their setup pose values. + public void SetBonesToSetupPose() + { + var bonesItems = this.bones.Items; + for (int i = 0, n = bones.Count; i < n; i++) + bonesItems[i].SetToSetupPose(); + + var ikConstraintsItems = this.ikConstraints.Items; + for (int i = 0, n = ikConstraints.Count; i < n; i++) + { + IkConstraint constraint = ikConstraintsItems[i]; + constraint.bendDirection = constraint.data.bendDirection; + constraint.mix = constraint.data.mix; + } + + var transformConstraintsItems = this.transformConstraints.Items; + for (int i = 0, n = transformConstraints.Count; i < n; i++) + { + TransformConstraint constraint = transformConstraintsItems[i]; + TransformConstraintData constraintData = constraint.data; + constraint.rotateMix = constraintData.rotateMix; + constraint.translateMix = constraintData.translateMix; + constraint.scaleMix = constraintData.scaleMix; + constraint.shearMix = constraintData.shearMix; + } + + var pathConstraintItems = this.pathConstraints.Items; + for (int i = 0, n = pathConstraints.Count; i < n; i++) + { + PathConstraint constraint = pathConstraintItems[i]; + PathConstraintData constraintData = constraint.data; + constraint.position = constraintData.position; + constraint.spacing = constraintData.spacing; + constraint.rotateMix = constraintData.rotateMix; + constraint.translateMix = constraintData.translateMix; + } + } + + public void SetSlotsToSetupPose() + { + var slots = this.slots; + var slotsItems = slots.Items; + drawOrder.Clear(); + for (int i = 0, n = slots.Count; i < n; i++) + drawOrder.Add(slotsItems[i]); + + for (int i = 0, n = slots.Count; i < n; i++) + slotsItems[i].SetToSetupPose(); + } + + /// May be null. + public Bone FindBone(string boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); + var bones = this.bones; + var bonesItems = bones.Items; + for (int i = 0, n = bones.Count; i < n; i++) + { + Bone bone = bonesItems[i]; + if (bone.data.name == boneName) return bone; + } + return null; + } + + /// -1 if the bone was not found. + public int FindBoneIndex(string boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); + var bones = this.bones; + var bonesItems = bones.Items; + for (int i = 0, n = bones.Count; i < n; i++) + if (bonesItems[i].data.name == boneName) return i; + return -1; + } + + /// May be null. + public Slot FindSlot(string slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + var slots = this.slots; + var slotsItems = slots.Items; + for (int i = 0, n = slots.Count; i < n; i++) + { + Slot slot = slotsItems[i]; + if (slot.data.name == slotName) return slot; + } + return null; + } + + /// -1 if the bone was not found. + public int FindSlotIndex(string slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + var slots = this.slots; + var slotsItems = slots.Items; + for (int i = 0, n = slots.Count; i < n; i++) + if (slotsItems[i].data.name.Equals(slotName)) return i; + return -1; + } + + /// Sets a skin by name (see SetSkin). + public void SetSkin(string skinName) + { + Skin foundSkin = data.FindSkin(skinName); + if (foundSkin == null) throw new ArgumentException("Skin not found: " + skinName, "skinName"); + SetSkin(foundSkin); + } + + /// Sets the skin used to look up attachments before looking in the {@link SkeletonData#getDefaultSkin() default + /// skin}. Attachmentsfrom 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. + /// May be null. + public void SetSkin(Skin newSkin) + { + if (newSkin != null) + { + if (skin != null) + newSkin.AttachAll(this, skin); + else + { + ExposedList slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + { + Slot slot = slots.Items[i]; + string name = slot.data.attachmentName; + if (name != null) + { + Attachment attachment = newSkin.GetAttachment(i, name); + if (attachment != null) slot.Attachment = attachment; + } + } + } + } + skin = newSkin; + } + + /// May be null. + public Attachment GetAttachment(string slotName, string attachmentName) + { + return GetAttachment(data.FindSlotIndex(slotName), attachmentName); + } + + /// May be null. + public Attachment GetAttachment(int slotIndex, string attachmentName) + { + if (attachmentName == null) throw new ArgumentNullException("attachmentName", "attachmentName cannot be null."); + if (skin != null) + { + Attachment attachment = skin.GetAttachment(slotIndex, attachmentName); + if (attachment != null) return attachment; + } + return data.defaultSkin != null ? data.defaultSkin.GetAttachment(slotIndex, attachmentName) : null; + } + + /// May be null. + public void SetAttachment(string slotName, string attachmentName) + { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + ExposedList slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + { + Slot slot = slots.Items[i]; + if (slot.data.name == slotName) + { + Attachment attachment = null; + if (attachmentName != null) + { + attachment = GetAttachment(i, attachmentName); + if (attachment == null) throw new Exception("Attachment not found: " + attachmentName + ", for slot: " + slotName); + } + slot.Attachment = attachment; + return; + } + } + throw new Exception("Slot not found: " + slotName); + } + + /// May be null. + public IkConstraint FindIkConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + ExposedList ikConstraints = this.ikConstraints; + for (int i = 0, n = ikConstraints.Count; i < n; i++) + { + IkConstraint ikConstraint = ikConstraints.Items[i]; + if (ikConstraint.data.name == constraintName) return ikConstraint; + } + return null; + } + + /// May be null. + public TransformConstraint FindTransformConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + ExposedList transformConstraints = this.transformConstraints; + for (int i = 0, n = transformConstraints.Count; i < n; i++) + { + TransformConstraint transformConstraint = transformConstraints.Items[i]; + if (transformConstraint.data.name == constraintName) return transformConstraint; + } + return null; + } + + /// May be null. + public PathConstraint FindPathConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + ExposedList pathConstraints = this.pathConstraints; + for (int i = 0, n = pathConstraints.Count; i < n; i++) + { + PathConstraint constraint = pathConstraints.Items[i]; + if (constraint.data.name.Equals(constraintName)) return constraint; + } + return null; + } + + public void Update(float delta) + { + time += delta; + } + + /// Returns the axis aligned bounding box (AABB) of the region and mesh attachments for the current pose. + /// The horizontal distance between the skeleton origin and the left side of the AABB. + /// The vertical distance between the skeleton origin and the bottom side of the AABB. + /// The width of the AABB + /// The height of the AABB. + /// Reference to hold a float[]. May be a null reference. This method will assign it a new float[] with the appropriate size as needed. + public void GetBounds(out float x, out float y, out float width, out float height, ref float[] vertexBuffer) + { + float[] temp = vertexBuffer; + temp = temp ?? new float[8]; + var drawOrderItems = this.drawOrder.Items; + float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; + for (int i = 0, n = this.drawOrder.Count; i < n; i++) + { + Slot slot = drawOrderItems[i]; + int verticesLength = 0; + float[] vertices = null; + Attachment attachment = slot.attachment; + var regionAttachment = attachment as RegionAttachment; + if (regionAttachment != null) + { + verticesLength = 8; + vertices = temp; + if (vertices.Length < 8) vertices = temp = new float[8]; + regionAttachment.ComputeWorldVertices(slot.bone, temp, 0); + } + else + { + var meshAttachment = attachment as MeshAttachment; + if (meshAttachment != null) + { + MeshAttachment mesh = meshAttachment; + verticesLength = mesh.WorldVerticesLength; + vertices = temp; + if (vertices.Length < verticesLength) vertices = temp = new float[verticesLength]; + mesh.ComputeWorldVertices(slot, 0, verticesLength, temp, 0); + } + } + + if (vertices != null) + { + for (int ii = 0; ii < verticesLength; ii += 2) + { + float vx = vertices[ii], vy = vertices[ii + 1]; + minX = Math.Min(minX, vx); + minY = Math.Min(minY, vy); + maxX = Math.Max(maxX, vx); + maxY = Math.Max(maxY, vy); + } + } + } + x = minX; + y = minY; + width = maxX - minX; + height = maxY - minY; + vertexBuffer = temp; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/SkeletonBinary.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/SkeletonBinary.cs index aba47a9..e0e771f 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/SkeletonBinary.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/SkeletonBinary.cs @@ -33,50 +33,54 @@ #endif using System; -using System.IO; using System.Collections.Generic; +using System.IO; #if WINDOWS_STOREAPP using System.Threading.Tasks; using Windows.Storage; #endif -namespace Spine3_6_39 { - public class SkeletonBinary { - public const int BONE_ROTATE = 0; - public const int BONE_TRANSLATE = 1; - public const int BONE_SCALE = 2; - public const int BONE_SHEAR = 3; - - public const int SLOT_ATTACHMENT = 0; - public const int SLOT_COLOR = 1; - public const int SLOT_TWO_COLOR = 2; - - public const int PATH_POSITION = 0; - public const int PATH_SPACING = 1; - public const int PATH_MIX = 2; - - public const int CURVE_LINEAR = 0; - public const int CURVE_STEPPED = 1; - public const int CURVE_BEZIER = 2; - - public float Scale { get; set; } - - private AttachmentLoader attachmentLoader; - private byte[] buffer = new byte[32]; - private List linkedMeshes = new List(); - - public SkeletonBinary (params Atlas[] atlasArray) - : this(new AtlasAttachmentLoader(atlasArray)) { - } - - public SkeletonBinary (AttachmentLoader attachmentLoader) { - if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader"); - this.attachmentLoader = attachmentLoader; - Scale = 1; - } - - #if !ISUNITY && WINDOWS_STOREAPP +namespace Spine3_6_39 +{ + public class SkeletonBinary + { + public const int BONE_ROTATE = 0; + public const int BONE_TRANSLATE = 1; + public const int BONE_SCALE = 2; + public const int BONE_SHEAR = 3; + + public const int SLOT_ATTACHMENT = 0; + public const int SLOT_COLOR = 1; + public const int SLOT_TWO_COLOR = 2; + + public const int PATH_POSITION = 0; + public const int PATH_SPACING = 1; + public const int PATH_MIX = 2; + + public const int CURVE_LINEAR = 0; + public const int CURVE_STEPPED = 1; + public const int CURVE_BEZIER = 2; + + public float Scale { get; set; } + + private AttachmentLoader attachmentLoader; + private byte[] buffer = new byte[32]; + private List linkedMeshes = new List(); + + public SkeletonBinary(params Atlas[] atlasArray) + : this(new AtlasAttachmentLoader(atlasArray)) + { + } + + public SkeletonBinary(AttachmentLoader attachmentLoader) + { + if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader"); + this.attachmentLoader = attachmentLoader; + Scale = 1; + } + +#if !ISUNITY && WINDOWS_STOREAPP private async Task ReadFile(string path) { var folder = Windows.ApplicationModel.Package.Current.InstalledLocation; using (var input = new BufferedStream(await folder.GetFileAsync(path).AsTask().ConfigureAwait(false))) { @@ -89,805 +93,908 @@ private async Task ReadFile(string path) { public SkeletonData ReadSkeletonData (String path) { return this.ReadFile(path).Result; } - #else - public SkeletonData ReadSkeletonData (String path) { - #if WINDOWS_PHONE +#else + public SkeletonData ReadSkeletonData(String path) + { +#if WINDOWS_PHONE using (var input = new BufferedStream(Microsoft.Xna.Framework.TitleContainer.OpenStream(path))) { - #else - using (var input = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) { - #endif - SkeletonData skeletonData = ReadSkeletonData(input); - skeletonData.name = Path.GetFileNameWithoutExtension(path); - return skeletonData; - } - } - #endif // WINDOWS_STOREAPP - - public static readonly TransformMode[] TransformModeValues = { - TransformMode.Normal, - TransformMode.OnlyTranslation, - TransformMode.NoRotationOrReflection, - TransformMode.NoScale, - TransformMode.NoScaleOrReflection - }; - - /// Returns the version string of binary skeleton data. - public static string GetVersionString (Stream input) { - if (input == null) throw new ArgumentNullException("input"); - - try { - // Hash. - int byteCount = ReadVarint(input, true); - if (byteCount > 1) input.Position += byteCount - 1; - - // Version. - byteCount = ReadVarint(input, true); - if (byteCount > 1) { - byteCount--; - var buffer = new byte[byteCount]; - ReadFully(input, buffer, 0, byteCount); - return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount); - } - - throw new ArgumentException("Stream does not contain a valid binary Skeleton Data.", "input"); - } catch (Exception e) { - throw new ArgumentException("Stream does not contain a valid binary Skeleton Data.\n" + e, "input"); - } - } - - public SkeletonData ReadSkeletonData (Stream input) { - if (input == null) throw new ArgumentNullException("input"); - float scale = Scale; - - var skeletonData = new SkeletonData(); - skeletonData.hash = ReadString(input); - if (skeletonData.hash.Length == 0) skeletonData.hash = null; - skeletonData.version = ReadString(input); - if (skeletonData.version.Length == 0) skeletonData.version = null; - skeletonData.width = ReadFloat(input); - skeletonData.height = ReadFloat(input); - - bool nonessential = ReadBoolean(input); - - if (nonessential) { - skeletonData.fps = ReadFloat(input); - skeletonData.imagesPath = ReadString(input); - if (skeletonData.imagesPath.Length == 0) skeletonData.imagesPath = null; - } - - // Bones. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - String name = ReadString(input); - BoneData parent = i == 0 ? null : skeletonData.bones.Items[ReadVarint(input, true)]; - BoneData data = new BoneData(i, name, 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.transformMode = TransformModeValues[ReadVarint(input, true)]; - if (nonessential) ReadInt(input); // Skip bone color. - skeletonData.bones.Add(data); - } - - // Slots. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - String slotName = ReadString(input); - BoneData boneData = skeletonData.bones.Items[ReadVarint(input, true)]; - SlotData slotData = new SlotData(i, slotName, boneData); - int color = ReadInt(input); - slotData.r = ((color & 0xff000000) >> 24) / 255f; - slotData.g = ((color & 0x00ff0000) >> 16) / 255f; - slotData.b = ((color & 0x0000ff00) >> 8) / 255f; - slotData.a = ((color & 0x000000ff)) / 255f; - - int darkColor = ReadInt(input); // 0x00rrggbb - if (darkColor != -1) { - slotData.hasSecondColor = true; - slotData.r2 = ((darkColor & 0x00ff0000) >> 16) / 255f; - slotData.g2 = ((darkColor & 0x0000ff00) >> 8) / 255f; - slotData.b2 = ((darkColor & 0x000000ff)) / 255f; - } - - slotData.attachmentName = ReadString(input); - slotData.blendMode = (BlendMode)ReadVarint(input, true); - skeletonData.slots.Add(slotData); - } - - // IK constraints. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - IkConstraintData data = new IkConstraintData(ReadString(input)); - data.order = ReadVarint(input, true); - for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) - data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]); - data.target = skeletonData.bones.Items[ReadVarint(input, true)]; - data.mix = ReadFloat(input); - data.bendDirection = ReadSByte(input); - skeletonData.ikConstraints.Add(data); - } - - // Transform constraints. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - TransformConstraintData data = new TransformConstraintData(ReadString(input)); - data.order = ReadVarint(input, true); - for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) - data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]); - data.target = skeletonData.bones.Items[ReadVarint(input, true)]; - data.local = ReadBoolean(input); - data.relative = ReadBoolean(input); - data.offsetRotation = ReadFloat(input); - data.offsetX = ReadFloat(input) * scale; - data.offsetY = ReadFloat(input) * scale; - data.offsetScaleX = ReadFloat(input); - data.offsetScaleY = ReadFloat(input); - data.offsetShearY = ReadFloat(input); - data.rotateMix = ReadFloat(input); - data.translateMix = ReadFloat(input); - data.scaleMix = ReadFloat(input); - data.shearMix = ReadFloat(input); - skeletonData.transformConstraints.Add(data); - } - - // Path constraints - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - PathConstraintData data = new PathConstraintData(ReadString(input)); - data.order = ReadVarint(input, true); - for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) - data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]); - data.target = skeletonData.slots.Items[ReadVarint(input, true)]; - data.positionMode = (PositionMode)Enum.GetValues(typeof(PositionMode)).GetValue(ReadVarint(input, true)); - data.spacingMode = (SpacingMode)Enum.GetValues(typeof(SpacingMode)).GetValue(ReadVarint(input, true)); - data.rotateMode = (RotateMode)Enum.GetValues(typeof(RotateMode)).GetValue(ReadVarint(input, true)); - 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.rotateMix = ReadFloat(input); - data.translateMix = ReadFloat(input); - skeletonData.pathConstraints.Add(data); - } - - // Default skin. - Skin defaultSkin = ReadSkin(input, skeletonData, "default", nonessential); - if (defaultSkin != null) { - skeletonData.defaultSkin = defaultSkin; - skeletonData.skins.Add(defaultSkin); - } - - // Skins. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) - skeletonData.skins.Add(ReadSkin(input, skeletonData, ReadString(input), nonessential)); - - // Linked meshes. - for (int i = 0, n = linkedMeshes.Count; i < n; i++) { - SkeletonJson.LinkedMesh linkedMesh = linkedMeshes[i]; - Skin skin = linkedMesh.skin == null ? skeletonData.DefaultSkin : skeletonData.FindSkin(linkedMesh.skin); - if (skin == null) throw new Exception("Skin not found: " + linkedMesh.skin); - Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); - if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); - linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; - linkedMesh.mesh.UpdateUVs(); - } - linkedMeshes.Clear(); - - // Events. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - EventData data = new EventData(ReadString(input)); - data.Int = ReadVarint(input, false); - data.Float = ReadFloat(input); - data.String = ReadString(input); - skeletonData.events.Add(data); - } - - // Animations. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) - ReadAnimation(ReadString(input), input, skeletonData); - - skeletonData.bones.TrimExcess(); - skeletonData.slots.TrimExcess(); - skeletonData.skins.TrimExcess(); - skeletonData.events.TrimExcess(); - skeletonData.animations.TrimExcess(); - skeletonData.ikConstraints.TrimExcess(); - skeletonData.pathConstraints.TrimExcess(); - return skeletonData; - } - - - /// May be null. - private Skin ReadSkin (Stream input, SkeletonData skeletonData, String skinName, bool nonessential) { - int slotCount = ReadVarint(input, true); - if (slotCount == 0) return null; - Skin skin = new Skin(skinName); - 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 = ReadString(input); - Attachment attachment = ReadAttachment(input, skeletonData, skin, slotIndex, name, nonessential); - if (attachment != null) skin.AddAttachment(slotIndex, name, attachment); - } - } - return skin; - } - - private Attachment ReadAttachment (Stream input, SkeletonData skeletonData, Skin skin, int slotIndex, String attachmentName, bool nonessential) { - float scale = Scale; - - String name = ReadString(input); - if (name == null) name = attachmentName; - - AttachmentType type = (AttachmentType)input.ReadByte(); - switch (type) { - case AttachmentType.Region: { - String path = ReadString(input); - float rotation = ReadFloat(input); - float x = ReadFloat(input); - float y = ReadFloat(input); - float scaleX = ReadFloat(input); - float scaleY = ReadFloat(input); - float width = ReadFloat(input); - float height = ReadFloat(input); - int color = ReadInt(input); - - if (path == null) path = name; - RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); - if (region == null) return null; - region.Path = path; - region.x = x * scale; - region.y = y * scale; - region.scaleX = scaleX; - region.scaleY = scaleY; - region.rotation = rotation; - region.width = width * scale; - region.height = height * scale; - region.r = ((color & 0xff000000) >> 24) / 255f; - region.g = ((color & 0x00ff0000) >> 16) / 255f; - region.b = ((color & 0x0000ff00) >> 8) / 255f; - region.a = ((color & 0x000000ff)) / 255f; - region.UpdateOffset(); - return region; - } - case AttachmentType.Boundingbox: { - int vertexCount = ReadVarint(input, true); - Vertices vertices = ReadVertices(input, vertexCount); - if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0; // Avoid unused local warning. - - BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); - if (box == null) return null; - box.worldVerticesLength = vertexCount << 1; - box.vertices = vertices.vertices; - box.bones = vertices.bones; - return box; - } - case AttachmentType.Mesh: { - String path = ReadString(input); - int color = ReadInt(input); - int vertexCount = ReadVarint(input, true); - float[] uvs = ReadFloatArray(input, vertexCount << 1, 1); - int[] triangles = ReadShortArray(input); - Vertices vertices = ReadVertices(input, vertexCount); - int hullLength = ReadVarint(input, true); - int[] edges = null; - float width = 0, height = 0; - if (nonessential) { - edges = ReadShortArray(input); - width = ReadFloat(input); - height = ReadFloat(input); - } - - if (path == null) path = name; - MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); - if (mesh == null) return null; - mesh.Path = path; - mesh.r = ((color & 0xff000000) >> 24) / 255f; - mesh.g = ((color & 0x00ff0000) >> 16) / 255f; - mesh.b = ((color & 0x0000ff00) >> 8) / 255f; - mesh.a = ((color & 0x000000ff)) / 255f; - mesh.bones = vertices.bones; - mesh.vertices = vertices.vertices; - mesh.WorldVerticesLength = vertexCount << 1; - mesh.triangles = triangles; - mesh.regionUVs = uvs; - mesh.UpdateUVs(); - mesh.HullLength = hullLength << 1; - if (nonessential) { - mesh.Edges = edges; - mesh.Width = width * scale; - mesh.Height = height * scale; - } - return mesh; - } - case AttachmentType.Linkedmesh: { - String path = ReadString(input); - int color = ReadInt(input); - String skinName = ReadString(input); - String parent = ReadString(input); - bool inheritDeform = ReadBoolean(input); - float width = 0, height = 0; - if (nonessential) { - width = ReadFloat(input); - height = ReadFloat(input); - } - - if (path == null) path = name; - MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); - if (mesh == null) return null; - mesh.Path = path; - mesh.r = ((color & 0xff000000) >> 24) / 255f; - mesh.g = ((color & 0x00ff0000) >> 16) / 255f; - mesh.b = ((color & 0x0000ff00) >> 8) / 255f; - mesh.a = ((color & 0x000000ff)) / 255f; - mesh.inheritDeform = inheritDeform; - if (nonessential) { - mesh.Width = width * scale; - mesh.Height = height * scale; - } - linkedMeshes.Add(new SkeletonJson.LinkedMesh(mesh, skinName, slotIndex, parent)); - return mesh; - } - case AttachmentType.Path: { - bool closed = ReadBoolean(input); - bool constantSpeed = ReadBoolean(input); - int vertexCount = ReadVarint(input, true); - Vertices vertices = ReadVertices(input, vertexCount); - float[] lengths = new float[vertexCount / 3]; - for (int i = 0, n = lengths.Length; i < n; i++) - lengths[i] = ReadFloat(input) * scale; - if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0; - - PathAttachment path = attachmentLoader.NewPathAttachment(skin, name); - if (path == null) return null; - path.closed = closed; - path.constantSpeed = constantSpeed; - path.worldVerticesLength = vertexCount << 1; - path.vertices = vertices.vertices; - path.bones = vertices.bones; - path.lengths = lengths; - return path; - } - case AttachmentType.Point: { - float rotation = ReadFloat(input); - float x = ReadFloat(input); - float y = ReadFloat(input); - if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0; - - PointAttachment point = attachmentLoader.NewPointAttachment(skin, name); - if (point == null) return null; - point.x = x * scale; - point.y = y * scale; - point.rotation = rotation; - //if (nonessential) point.color = color; - return point; - } - case AttachmentType.Clipping: { - int endSlotIndex = ReadVarint(input, true); - int vertexCount = ReadVarint(input, true); - Vertices vertices = ReadVertices(input, vertexCount); - if (nonessential) ReadInt(input); - - ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name); - if (clip == null) return null; - clip.EndSlot = skeletonData.slots.Items[endSlotIndex]; - clip.worldVerticesLength = vertexCount << 1; - clip.vertices = vertices.vertices; - clip.bones = vertices.bones; - return clip; - } - } - return null; - } - - private Vertices ReadVertices (Stream input, int vertexCount) { - float scale = Scale; - int verticesLength = vertexCount << 1; - Vertices vertices = new Vertices(); - if(!ReadBoolean(input)) { - vertices.vertices = ReadFloatArray(input, verticesLength, scale); - return vertices; - } - var weights = new ExposedList(verticesLength * 3 * 3); - var bonesArray = new ExposedList(verticesLength * 3); - for (int i = 0; i < vertexCount; i++) { - int boneCount = ReadVarint(input, true); - bonesArray.Add(boneCount); - for (int ii = 0; ii < boneCount; ii++) { - bonesArray.Add(ReadVarint(input, true)); - weights.Add(ReadFloat(input) * scale); - weights.Add(ReadFloat(input) * scale); - weights.Add(ReadFloat(input)); - } - } - - vertices.vertices = weights.ToArray(); - vertices.bones = bonesArray.ToArray(); - return vertices; - } - - private float[] ReadFloatArray (Stream input, int n, float scale) { - float[] array = new float[n]; - if (scale == 1) { - for (int i = 0; i < n; i++) - array[i] = ReadFloat(input); - } else { - for (int i = 0; i < n; i++) - array[i] = ReadFloat(input) * scale; - } - return array; - } - - private int[] ReadShortArray (Stream input) { - int n = ReadVarint(input, true); - int[] array = new int[n]; - for (int i = 0; i < n; i++) - array[i] = (input.ReadByte() << 8) | input.ReadByte(); - return array; - } - - private void ReadAnimation (String name, Stream input, SkeletonData skeletonData) { - var timelines = new ExposedList(); - float scale = Scale; - float duration = 0; - - // 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++) { - int timelineType = input.ReadByte(); - int frameCount = ReadVarint(input, true); - switch (timelineType) { - case SLOT_ATTACHMENT: { - AttachmentTimeline timeline = new AttachmentTimeline(frameCount); - timeline.slotIndex = slotIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) - timeline.SetFrame(frameIndex, ReadFloat(input), ReadString(input)); - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[frameCount - 1]); - break; - } - case SLOT_COLOR: { - ColorTimeline timeline = new ColorTimeline(frameCount); - timeline.slotIndex = slotIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - float time = ReadFloat(input); - int color = ReadInt(input); - float r = ((color & 0xff000000) >> 24) / 255f; - float g = ((color & 0x00ff0000) >> 16) / 255f; - float b = ((color & 0x0000ff00) >> 8) / 255f; - float a = ((color & 0x000000ff)) / 255f; - timeline.SetFrame(frameIndex, time, r, g, b, a); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * ColorTimeline.ENTRIES]); - break; - } - case SLOT_TWO_COLOR: { - TwoColorTimeline timeline = new TwoColorTimeline(frameCount); - timeline.slotIndex = slotIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - float time = ReadFloat(input); - int color = ReadInt(input); - float r = ((color & 0xff000000) >> 24) / 255f; - float g = ((color & 0x00ff0000) >> 16) / 255f; - float b = ((color & 0x0000ff00) >> 8) / 255f; - float a = ((color & 0x000000ff)) / 255f; - int color2 = ReadInt(input); // 0x00rrggbb - float r2 = ((color2 & 0x00ff0000) >> 16) / 255f; - float g2 = ((color2 & 0x0000ff00) >> 8) / 255f; - float b2 = ((color2 & 0x000000ff)) / 255f; - - timeline.SetFrame(frameIndex, time, r, g, b, a, r2, g2, b2); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TwoColorTimeline.ENTRIES]); - break; - } - } - } - } - - // 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++) { - int timelineType = input.ReadByte(); - int frameCount = ReadVarint(input, true); - switch (timelineType) { - case BONE_ROTATE: { - RotateTimeline timeline = new RotateTimeline(frameCount); - timeline.boneIndex = boneIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input)); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(frameCount - 1) * RotateTimeline.ENTRIES]); - break; - } - case BONE_TRANSLATE: - case BONE_SCALE: - case BONE_SHEAR: { - TranslateTimeline timeline; - float timelineScale = 1; - if (timelineType == BONE_SCALE) - timeline = new ScaleTimeline(frameCount); - else if (timelineType == BONE_SHEAR) - timeline = new ShearTimeline(frameCount); - else { - timeline = new TranslateTimeline(frameCount); - timelineScale = scale; - } - timeline.boneIndex = boneIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input) * timelineScale, ReadFloat(input) - * timelineScale); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TranslateTimeline.ENTRIES]); - break; - } - } - } - } - - // IK timelines. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - int index = ReadVarint(input, true); - int frameCount = ReadVarint(input, true); - IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount); - timeline.ikConstraintIndex = index; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadSByte(input)); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(frameCount - 1) * IkConstraintTimeline.ENTRIES]); - } - - // Transform constraint timelines. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - int index = ReadVarint(input, true); - int frameCount = ReadVarint(input, true); - TransformConstraintTimeline timeline = new TransformConstraintTimeline(frameCount); - timeline.transformConstraintIndex = index; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input)); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TransformConstraintTimeline.ENTRIES]); - } - - // Path constraint timelines. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - int index = ReadVarint(input, true); - PathConstraintData data = skeletonData.pathConstraints.Items[index]; - for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) { - int timelineType = ReadSByte(input); - int frameCount = ReadVarint(input, true); - switch(timelineType) { - case PATH_POSITION: - case PATH_SPACING: { - PathConstraintPositionTimeline timeline; - float timelineScale = 1; - if (timelineType == PATH_SPACING) { - timeline = new PathConstraintSpacingTimeline(frameCount); - if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale; - } else { - timeline = new PathConstraintPositionTimeline(frameCount); - if (data.positionMode == PositionMode.Fixed) timelineScale = scale; - } - timeline.pathConstraintIndex = index; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input) * timelineScale); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintPositionTimeline.ENTRIES]); - break; - } - case PATH_MIX: { - PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(frameCount); - timeline.pathConstraintIndex = index; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input)); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintMixTimeline.ENTRIES]); - break; - } - } - } - } - - // Deform timelines. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - Skin skin = skeletonData.skins.Items[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++) { - VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, ReadString(input)); - bool weighted = attachment.bones != null; - float[] vertices = attachment.vertices; - int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length; - - int frameCount = ReadVarint(input, true); - DeformTimeline timeline = new DeformTimeline(frameCount); - timeline.slotIndex = slotIndex; - timeline.attachment = attachment; - - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - float time = ReadFloat(input); - float[] deform; - int end = ReadVarint(input, true); - if (end == 0) - deform = weighted ? new float[deformLength] : vertices; - else { - deform = new float[deformLength]; - int start = ReadVarint(input, true); - end += start; - if (scale == 1) { - for (int v = start; v < end; v++) - deform[v] = ReadFloat(input); - } else { - for (int v = start; v < end; v++) - deform[v] = ReadFloat(input) * scale; - } - if (!weighted) { - for (int v = 0, vn = deform.Length; v < vn; v++) - deform[v] += vertices[v]; - } - } - - timeline.SetFrame(frameIndex, time, deform); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[frameCount - 1]); - } - } - } - - // Draw order timeline. - int drawOrderCount = ReadVarint(input, true); - if (drawOrderCount > 0) { - DrawOrderTimeline timeline = new DrawOrderTimeline(drawOrderCount); - int slotCount = skeletonData.slots.Count; - for (int i = 0; i < drawOrderCount; i++) { - float time = ReadFloat(input); - int offsetCount = ReadVarint(input, true); - int[] drawOrder = new int[slotCount]; - for (int ii = slotCount - 1; ii >= 0; ii--) - drawOrder[ii] = -1; - int[] unchanged = new int[slotCount - offsetCount]; - int originalIndex = 0, unchangedIndex = 0; - for (int ii = 0; ii < offsetCount; ii++) { - int slotIndex = ReadVarint(input, true); - // Collect unchanged items. - while (originalIndex != slotIndex) - unchanged[unchangedIndex++] = originalIndex++; - // Set changed items. - drawOrder[originalIndex + ReadVarint(input, true)] = originalIndex++; - } - // Collect remaining unchanged items. - while (originalIndex < slotCount) - unchanged[unchangedIndex++] = originalIndex++; - // Fill in unchanged items. - for (int ii = slotCount - 1; ii >= 0; ii--) - if (drawOrder[ii] == -1) drawOrder[ii] = unchanged[--unchangedIndex]; - timeline.SetFrame(i, time, drawOrder); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[drawOrderCount - 1]); - } - - // Event timeline. - int eventCount = ReadVarint(input, true); - if (eventCount > 0) { - EventTimeline timeline = new EventTimeline(eventCount); - for (int i = 0; i < eventCount; i++) { - float time = ReadFloat(input); - EventData eventData = skeletonData.events.Items[ReadVarint(input, true)]; - Event e = new Event(time, eventData); - e.Int = ReadVarint(input, false); - e.Float = ReadFloat(input); - e.String = ReadBoolean(input) ? ReadString(input) : eventData.String; - timeline.SetFrame(i, e); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[eventCount - 1]); - } - - timelines.TrimExcess(); - skeletonData.animations.Add(new Animation(name, timelines, duration)); - } - - private void ReadCurve (Stream input, int frameIndex, CurveTimeline timeline) { - switch (input.ReadByte()) { - case CURVE_STEPPED: - timeline.SetStepped(frameIndex); - break; - case CURVE_BEZIER: - timeline.SetCurve(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input)); - break; - } - } - - private static sbyte ReadSByte (Stream input) { - int value = input.ReadByte(); - if (value == -1) throw new EndOfStreamException(); - return (sbyte)value; - } - - private static bool ReadBoolean (Stream input) { - return input.ReadByte() != 0; - } - - private float ReadFloat (Stream input) { - buffer[3] = (byte)input.ReadByte(); - buffer[2] = (byte)input.ReadByte(); - buffer[1] = (byte)input.ReadByte(); - buffer[0] = (byte)input.ReadByte(); - return BitConverter.ToSingle(buffer, 0); - } - - private static int ReadInt (Stream input) { - return (input.ReadByte() << 24) + (input.ReadByte() << 16) + (input.ReadByte() << 8) + input.ReadByte(); - } - - private static int ReadVarint (Stream input, bool optimizePositive) { - int b = input.ReadByte(); - int result = b & 0x7F; - if ((b & 0x80) != 0) { - b = input.ReadByte(); - result |= (b & 0x7F) << 7; - if ((b & 0x80) != 0) { - b = input.ReadByte(); - result |= (b & 0x7F) << 14; - if ((b & 0x80) != 0) { - b = input.ReadByte(); - result |= (b & 0x7F) << 21; - if ((b & 0x80) != 0) result |= (input.ReadByte() & 0x7F) << 28; - } - } - } - return optimizePositive ? result : ((result >> 1) ^ -(result & 1)); - } - - private string ReadString (Stream input) { - int byteCount = ReadVarint(input, true); - switch (byteCount) { - case 0: - return null; - case 1: - return ""; - } - byteCount--; - byte[] buffer = this.buffer; - if (buffer.Length < byteCount) buffer = new byte[byteCount]; - ReadFully(input, buffer, 0, byteCount); - return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount); - } - - private static void ReadFully (Stream input, byte[] buffer, int offset, int length) { - while (length > 0) { - int count = input.Read(buffer, offset, length); - if (count <= 0) throw new EndOfStreamException(); - offset += count; - length -= count; - } - } - - internal class Vertices { - public int[] bones; - public float[] vertices; - } - } +#else + using (var input = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) + { +#endif + SkeletonData skeletonData = ReadSkeletonData(input); + skeletonData.name = Path.GetFileNameWithoutExtension(path); + return skeletonData; + } + } +#endif // WINDOWS_STOREAPP + + public static readonly TransformMode[] TransformModeValues = { + TransformMode.Normal, + TransformMode.OnlyTranslation, + TransformMode.NoRotationOrReflection, + TransformMode.NoScale, + TransformMode.NoScaleOrReflection + }; + + /// Returns the version string of binary skeleton data. + public static string GetVersionString(Stream input) + { + if (input == null) throw new ArgumentNullException("input"); + + try + { + // Hash. + int byteCount = ReadVarint(input, true); + if (byteCount > 1) input.Position += byteCount - 1; + + // Version. + byteCount = ReadVarint(input, true); + if (byteCount > 1) + { + byteCount--; + var buffer = new byte[byteCount]; + ReadFully(input, buffer, 0, byteCount); + return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount); + } + + throw new ArgumentException("Stream does not contain a valid binary Skeleton Data.", "input"); + } + catch (Exception e) + { + throw new ArgumentException("Stream does not contain a valid binary Skeleton Data.\n" + e, "input"); + } + } + + public SkeletonData ReadSkeletonData(Stream input) + { + if (input == null) throw new ArgumentNullException("input"); + float scale = Scale; + + var skeletonData = new SkeletonData(); + skeletonData.hash = ReadString(input); + if (skeletonData.hash.Length == 0) skeletonData.hash = null; + skeletonData.version = ReadString(input); + if (skeletonData.version.Length == 0) skeletonData.version = null; + skeletonData.width = ReadFloat(input); + skeletonData.height = ReadFloat(input); + + bool nonessential = ReadBoolean(input); + + if (nonessential) + { + skeletonData.fps = ReadFloat(input); + skeletonData.imagesPath = ReadString(input); + if (skeletonData.imagesPath.Length == 0) skeletonData.imagesPath = null; + } + + // Bones. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + String name = ReadString(input); + BoneData parent = i == 0 ? null : skeletonData.bones.Items[ReadVarint(input, true)]; + BoneData data = new BoneData(i, name, 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.transformMode = TransformModeValues[ReadVarint(input, true)]; + if (nonessential) ReadInt(input); // Skip bone color. + skeletonData.bones.Add(data); + } + + // Slots. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + String slotName = ReadString(input); + BoneData boneData = skeletonData.bones.Items[ReadVarint(input, true)]; + SlotData slotData = new SlotData(i, slotName, boneData); + int color = ReadInt(input); + slotData.r = ((color & 0xff000000) >> 24) / 255f; + slotData.g = ((color & 0x00ff0000) >> 16) / 255f; + slotData.b = ((color & 0x0000ff00) >> 8) / 255f; + slotData.a = ((color & 0x000000ff)) / 255f; + + int darkColor = ReadInt(input); // 0x00rrggbb + if (darkColor != -1) + { + slotData.hasSecondColor = true; + slotData.r2 = ((darkColor & 0x00ff0000) >> 16) / 255f; + slotData.g2 = ((darkColor & 0x0000ff00) >> 8) / 255f; + slotData.b2 = ((darkColor & 0x000000ff)) / 255f; + } + + slotData.attachmentName = ReadString(input); + slotData.blendMode = (BlendMode)ReadVarint(input, true); + skeletonData.slots.Add(slotData); + } + + // IK constraints. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + IkConstraintData data = new IkConstraintData(ReadString(input)); + data.order = ReadVarint(input, true); + for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) + data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]); + data.target = skeletonData.bones.Items[ReadVarint(input, true)]; + data.mix = ReadFloat(input); + data.bendDirection = ReadSByte(input); + skeletonData.ikConstraints.Add(data); + } + + // Transform constraints. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + TransformConstraintData data = new TransformConstraintData(ReadString(input)); + data.order = ReadVarint(input, true); + for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) + data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]); + data.target = skeletonData.bones.Items[ReadVarint(input, true)]; + data.local = ReadBoolean(input); + data.relative = ReadBoolean(input); + data.offsetRotation = ReadFloat(input); + data.offsetX = ReadFloat(input) * scale; + data.offsetY = ReadFloat(input) * scale; + data.offsetScaleX = ReadFloat(input); + data.offsetScaleY = ReadFloat(input); + data.offsetShearY = ReadFloat(input); + data.rotateMix = ReadFloat(input); + data.translateMix = ReadFloat(input); + data.scaleMix = ReadFloat(input); + data.shearMix = ReadFloat(input); + skeletonData.transformConstraints.Add(data); + } + + // Path constraints + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + PathConstraintData data = new PathConstraintData(ReadString(input)); + data.order = ReadVarint(input, true); + for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) + data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]); + data.target = skeletonData.slots.Items[ReadVarint(input, true)]; + data.positionMode = (PositionMode)Enum.GetValues(typeof(PositionMode)).GetValue(ReadVarint(input, true)); + data.spacingMode = (SpacingMode)Enum.GetValues(typeof(SpacingMode)).GetValue(ReadVarint(input, true)); + data.rotateMode = (RotateMode)Enum.GetValues(typeof(RotateMode)).GetValue(ReadVarint(input, true)); + 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.rotateMix = ReadFloat(input); + data.translateMix = ReadFloat(input); + skeletonData.pathConstraints.Add(data); + } + + // Default skin. + Skin defaultSkin = ReadSkin(input, skeletonData, "default", nonessential); + if (defaultSkin != null) + { + skeletonData.defaultSkin = defaultSkin; + skeletonData.skins.Add(defaultSkin); + } + + // Skins. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + skeletonData.skins.Add(ReadSkin(input, skeletonData, ReadString(input), nonessential)); + + // Linked meshes. + for (int i = 0, n = linkedMeshes.Count; i < n; i++) + { + SkeletonJson.LinkedMesh linkedMesh = linkedMeshes[i]; + Skin skin = linkedMesh.skin == null ? skeletonData.DefaultSkin : skeletonData.FindSkin(linkedMesh.skin); + if (skin == null) throw new Exception("Skin not found: " + linkedMesh.skin); + Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); + if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); + linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; + linkedMesh.mesh.UpdateUVs(); + } + linkedMeshes.Clear(); + + // Events. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + EventData data = new EventData(ReadString(input)); + data.Int = ReadVarint(input, false); + data.Float = ReadFloat(input); + data.String = ReadString(input); + skeletonData.events.Add(data); + } + + // Animations. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + ReadAnimation(ReadString(input), input, skeletonData); + + skeletonData.bones.TrimExcess(); + skeletonData.slots.TrimExcess(); + skeletonData.skins.TrimExcess(); + skeletonData.events.TrimExcess(); + skeletonData.animations.TrimExcess(); + skeletonData.ikConstraints.TrimExcess(); + skeletonData.pathConstraints.TrimExcess(); + return skeletonData; + } + + + /// May be null. + private Skin ReadSkin(Stream input, SkeletonData skeletonData, String skinName, bool nonessential) + { + int slotCount = ReadVarint(input, true); + if (slotCount == 0) return null; + Skin skin = new Skin(skinName); + 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 = ReadString(input); + Attachment attachment = ReadAttachment(input, skeletonData, skin, slotIndex, name, nonessential); + if (attachment != null) skin.AddAttachment(slotIndex, name, attachment); + } + } + return skin; + } + + private Attachment ReadAttachment(Stream input, SkeletonData skeletonData, Skin skin, int slotIndex, String attachmentName, bool nonessential) + { + float scale = Scale; + + String name = ReadString(input); + if (name == null) name = attachmentName; + + AttachmentType type = (AttachmentType)input.ReadByte(); + switch (type) + { + case AttachmentType.Region: + { + String path = ReadString(input); + float rotation = ReadFloat(input); + float x = ReadFloat(input); + float y = ReadFloat(input); + float scaleX = ReadFloat(input); + float scaleY = ReadFloat(input); + float width = ReadFloat(input); + float height = ReadFloat(input); + int color = ReadInt(input); + + if (path == null) path = name; + RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); + if (region == null) return null; + region.Path = path; + region.x = x * scale; + region.y = y * scale; + region.scaleX = scaleX; + region.scaleY = scaleY; + region.rotation = rotation; + region.width = width * scale; + region.height = height * scale; + region.r = ((color & 0xff000000) >> 24) / 255f; + region.g = ((color & 0x00ff0000) >> 16) / 255f; + region.b = ((color & 0x0000ff00) >> 8) / 255f; + region.a = ((color & 0x000000ff)) / 255f; + region.UpdateOffset(); + return region; + } + case AttachmentType.Boundingbox: + { + int vertexCount = ReadVarint(input, true); + Vertices vertices = ReadVertices(input, vertexCount); + if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0; // Avoid unused local warning. + + BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); + if (box == null) return null; + box.worldVerticesLength = vertexCount << 1; + box.vertices = vertices.vertices; + box.bones = vertices.bones; + return box; + } + case AttachmentType.Mesh: + { + String path = ReadString(input); + int color = ReadInt(input); + int vertexCount = ReadVarint(input, true); + float[] uvs = ReadFloatArray(input, vertexCount << 1, 1); + int[] triangles = ReadShortArray(input); + Vertices vertices = ReadVertices(input, vertexCount); + int hullLength = ReadVarint(input, true); + int[] edges = null; + float width = 0, height = 0; + if (nonessential) + { + edges = ReadShortArray(input); + width = ReadFloat(input); + height = ReadFloat(input); + } + + if (path == null) path = name; + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); + if (mesh == null) return null; + mesh.Path = path; + mesh.r = ((color & 0xff000000) >> 24) / 255f; + mesh.g = ((color & 0x00ff0000) >> 16) / 255f; + mesh.b = ((color & 0x0000ff00) >> 8) / 255f; + mesh.a = ((color & 0x000000ff)) / 255f; + mesh.bones = vertices.bones; + mesh.vertices = vertices.vertices; + mesh.WorldVerticesLength = vertexCount << 1; + mesh.triangles = triangles; + mesh.regionUVs = uvs; + mesh.UpdateUVs(); + mesh.HullLength = hullLength << 1; + if (nonessential) + { + mesh.Edges = edges; + mesh.Width = width * scale; + mesh.Height = height * scale; + } + return mesh; + } + case AttachmentType.Linkedmesh: + { + String path = ReadString(input); + int color = ReadInt(input); + String skinName = ReadString(input); + String parent = ReadString(input); + bool inheritDeform = ReadBoolean(input); + float width = 0, height = 0; + if (nonessential) + { + width = ReadFloat(input); + height = ReadFloat(input); + } + + if (path == null) path = name; + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); + if (mesh == null) return null; + mesh.Path = path; + mesh.r = ((color & 0xff000000) >> 24) / 255f; + mesh.g = ((color & 0x00ff0000) >> 16) / 255f; + mesh.b = ((color & 0x0000ff00) >> 8) / 255f; + mesh.a = ((color & 0x000000ff)) / 255f; + mesh.inheritDeform = inheritDeform; + if (nonessential) + { + mesh.Width = width * scale; + mesh.Height = height * scale; + } + linkedMeshes.Add(new SkeletonJson.LinkedMesh(mesh, skinName, slotIndex, parent)); + return mesh; + } + case AttachmentType.Path: + { + bool closed = ReadBoolean(input); + bool constantSpeed = ReadBoolean(input); + int vertexCount = ReadVarint(input, true); + Vertices vertices = ReadVertices(input, vertexCount); + float[] lengths = new float[vertexCount / 3]; + for (int i = 0, n = lengths.Length; i < n; i++) + lengths[i] = ReadFloat(input) * scale; + if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0; + + PathAttachment path = attachmentLoader.NewPathAttachment(skin, name); + if (path == null) return null; + path.closed = closed; + path.constantSpeed = constantSpeed; + path.worldVerticesLength = vertexCount << 1; + path.vertices = vertices.vertices; + path.bones = vertices.bones; + path.lengths = lengths; + return path; + } + case AttachmentType.Point: + { + float rotation = ReadFloat(input); + float x = ReadFloat(input); + float y = ReadFloat(input); + if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0; + + PointAttachment point = attachmentLoader.NewPointAttachment(skin, name); + if (point == null) return null; + point.x = x * scale; + point.y = y * scale; + point.rotation = rotation; + //if (nonessential) point.color = color; + return point; + } + case AttachmentType.Clipping: + { + int endSlotIndex = ReadVarint(input, true); + int vertexCount = ReadVarint(input, true); + Vertices vertices = ReadVertices(input, vertexCount); + if (nonessential) ReadInt(input); + + ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name); + if (clip == null) return null; + clip.EndSlot = skeletonData.slots.Items[endSlotIndex]; + clip.worldVerticesLength = vertexCount << 1; + clip.vertices = vertices.vertices; + clip.bones = vertices.bones; + return clip; + } + } + return null; + } + + private Vertices ReadVertices(Stream input, int vertexCount) + { + float scale = Scale; + int verticesLength = vertexCount << 1; + Vertices vertices = new Vertices(); + if (!ReadBoolean(input)) + { + vertices.vertices = ReadFloatArray(input, verticesLength, scale); + return vertices; + } + var weights = new ExposedList(verticesLength * 3 * 3); + var bonesArray = new ExposedList(verticesLength * 3); + for (int i = 0; i < vertexCount; i++) + { + int boneCount = ReadVarint(input, true); + bonesArray.Add(boneCount); + for (int ii = 0; ii < boneCount; ii++) + { + bonesArray.Add(ReadVarint(input, true)); + weights.Add(ReadFloat(input) * scale); + weights.Add(ReadFloat(input) * scale); + weights.Add(ReadFloat(input)); + } + } + + vertices.vertices = weights.ToArray(); + vertices.bones = bonesArray.ToArray(); + return vertices; + } + + private float[] ReadFloatArray(Stream input, int n, float scale) + { + float[] array = new float[n]; + if (scale == 1) + { + for (int i = 0; i < n; i++) + array[i] = ReadFloat(input); + } + else + { + for (int i = 0; i < n; i++) + array[i] = ReadFloat(input) * scale; + } + return array; + } + + private int[] ReadShortArray(Stream input) + { + int n = ReadVarint(input, true); + int[] array = new int[n]; + for (int i = 0; i < n; i++) + array[i] = (input.ReadByte() << 8) | input.ReadByte(); + return array; + } + + private void ReadAnimation(String name, Stream input, SkeletonData skeletonData) + { + var timelines = new ExposedList(); + float scale = Scale; + float duration = 0; + + // 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++) + { + int timelineType = input.ReadByte(); + int frameCount = ReadVarint(input, true); + switch (timelineType) + { + case SLOT_ATTACHMENT: + { + AttachmentTimeline timeline = new AttachmentTimeline(frameCount); + timeline.slotIndex = slotIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + timeline.SetFrame(frameIndex, ReadFloat(input), ReadString(input)); + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[frameCount - 1]); + break; + } + case SLOT_COLOR: + { + ColorTimeline timeline = new ColorTimeline(frameCount); + timeline.slotIndex = slotIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + float time = ReadFloat(input); + int color = ReadInt(input); + float r = ((color & 0xff000000) >> 24) / 255f; + float g = ((color & 0x00ff0000) >> 16) / 255f; + float b = ((color & 0x0000ff00) >> 8) / 255f; + float a = ((color & 0x000000ff)) / 255f; + timeline.SetFrame(frameIndex, time, r, g, b, a); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * ColorTimeline.ENTRIES]); + break; + } + case SLOT_TWO_COLOR: + { + TwoColorTimeline timeline = new TwoColorTimeline(frameCount); + timeline.slotIndex = slotIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + float time = ReadFloat(input); + int color = ReadInt(input); + float r = ((color & 0xff000000) >> 24) / 255f; + float g = ((color & 0x00ff0000) >> 16) / 255f; + float b = ((color & 0x0000ff00) >> 8) / 255f; + float a = ((color & 0x000000ff)) / 255f; + int color2 = ReadInt(input); // 0x00rrggbb + float r2 = ((color2 & 0x00ff0000) >> 16) / 255f; + float g2 = ((color2 & 0x0000ff00) >> 8) / 255f; + float b2 = ((color2 & 0x000000ff)) / 255f; + + timeline.SetFrame(frameIndex, time, r, g, b, a, r2, g2, b2); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TwoColorTimeline.ENTRIES]); + break; + } + } + } + } + + // 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++) + { + int timelineType = input.ReadByte(); + int frameCount = ReadVarint(input, true); + switch (timelineType) + { + case BONE_ROTATE: + { + RotateTimeline timeline = new RotateTimeline(frameCount); + timeline.boneIndex = boneIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input)); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(frameCount - 1) * RotateTimeline.ENTRIES]); + break; + } + case BONE_TRANSLATE: + case BONE_SCALE: + case BONE_SHEAR: + { + TranslateTimeline timeline; + float timelineScale = 1; + if (timelineType == BONE_SCALE) + timeline = new ScaleTimeline(frameCount); + else if (timelineType == BONE_SHEAR) + timeline = new ShearTimeline(frameCount); + else + { + timeline = new TranslateTimeline(frameCount); + timelineScale = scale; + } + timeline.boneIndex = boneIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input) * timelineScale, ReadFloat(input) + * timelineScale); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TranslateTimeline.ENTRIES]); + break; + } + } + } + } + + // IK timelines. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + int index = ReadVarint(input, true); + int frameCount = ReadVarint(input, true); + IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount); + timeline.ikConstraintIndex = index; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadSByte(input)); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(frameCount - 1) * IkConstraintTimeline.ENTRIES]); + } + + // Transform constraint timelines. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + int index = ReadVarint(input, true); + int frameCount = ReadVarint(input, true); + TransformConstraintTimeline timeline = new TransformConstraintTimeline(frameCount); + timeline.transformConstraintIndex = index; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input)); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TransformConstraintTimeline.ENTRIES]); + } + + // Path constraint timelines. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + int index = ReadVarint(input, true); + PathConstraintData data = skeletonData.pathConstraints.Items[index]; + for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) + { + int timelineType = ReadSByte(input); + int frameCount = ReadVarint(input, true); + switch (timelineType) + { + case PATH_POSITION: + case PATH_SPACING: + { + PathConstraintPositionTimeline timeline; + float timelineScale = 1; + if (timelineType == PATH_SPACING) + { + timeline = new PathConstraintSpacingTimeline(frameCount); + if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale; + } + else + { + timeline = new PathConstraintPositionTimeline(frameCount); + if (data.positionMode == PositionMode.Fixed) timelineScale = scale; + } + timeline.pathConstraintIndex = index; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input) * timelineScale); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintPositionTimeline.ENTRIES]); + break; + } + case PATH_MIX: + { + PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(frameCount); + timeline.pathConstraintIndex = index; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input)); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintMixTimeline.ENTRIES]); + break; + } + } + } + } + + // Deform timelines. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + Skin skin = skeletonData.skins.Items[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++) + { + VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, ReadString(input)); + bool weighted = attachment.bones != null; + float[] vertices = attachment.vertices; + int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length; + + int frameCount = ReadVarint(input, true); + DeformTimeline timeline = new DeformTimeline(frameCount); + timeline.slotIndex = slotIndex; + timeline.attachment = attachment; + + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + float time = ReadFloat(input); + float[] deform; + int end = ReadVarint(input, true); + if (end == 0) + deform = weighted ? new float[deformLength] : vertices; + else + { + deform = new float[deformLength]; + int start = ReadVarint(input, true); + end += start; + if (scale == 1) + { + for (int v = start; v < end; v++) + deform[v] = ReadFloat(input); + } + else + { + for (int v = start; v < end; v++) + deform[v] = ReadFloat(input) * scale; + } + if (!weighted) + { + for (int v = 0, vn = deform.Length; v < vn; v++) + deform[v] += vertices[v]; + } + } + + timeline.SetFrame(frameIndex, time, deform); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[frameCount - 1]); + } + } + } + + // Draw order timeline. + int drawOrderCount = ReadVarint(input, true); + if (drawOrderCount > 0) + { + DrawOrderTimeline timeline = new DrawOrderTimeline(drawOrderCount); + int slotCount = skeletonData.slots.Count; + for (int i = 0; i < drawOrderCount; i++) + { + float time = ReadFloat(input); + int offsetCount = ReadVarint(input, true); + int[] drawOrder = new int[slotCount]; + for (int ii = slotCount - 1; ii >= 0; ii--) + drawOrder[ii] = -1; + int[] unchanged = new int[slotCount - offsetCount]; + int originalIndex = 0, unchangedIndex = 0; + for (int ii = 0; ii < offsetCount; ii++) + { + int slotIndex = ReadVarint(input, true); + // Collect unchanged items. + while (originalIndex != slotIndex) + unchanged[unchangedIndex++] = originalIndex++; + // Set changed items. + drawOrder[originalIndex + ReadVarint(input, true)] = originalIndex++; + } + // Collect remaining unchanged items. + while (originalIndex < slotCount) + unchanged[unchangedIndex++] = originalIndex++; + // Fill in unchanged items. + for (int ii = slotCount - 1; ii >= 0; ii--) + if (drawOrder[ii] == -1) drawOrder[ii] = unchanged[--unchangedIndex]; + timeline.SetFrame(i, time, drawOrder); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[drawOrderCount - 1]); + } + + // Event timeline. + int eventCount = ReadVarint(input, true); + if (eventCount > 0) + { + EventTimeline timeline = new EventTimeline(eventCount); + for (int i = 0; i < eventCount; i++) + { + float time = ReadFloat(input); + EventData eventData = skeletonData.events.Items[ReadVarint(input, true)]; + Event e = new Event(time, eventData); + e.Int = ReadVarint(input, false); + e.Float = ReadFloat(input); + e.String = ReadBoolean(input) ? ReadString(input) : eventData.String; + timeline.SetFrame(i, e); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[eventCount - 1]); + } + + timelines.TrimExcess(); + skeletonData.animations.Add(new Animation(name, timelines, duration)); + } + + private void ReadCurve(Stream input, int frameIndex, CurveTimeline timeline) + { + switch (input.ReadByte()) + { + case CURVE_STEPPED: + timeline.SetStepped(frameIndex); + break; + case CURVE_BEZIER: + timeline.SetCurve(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input)); + break; + } + } + + private static sbyte ReadSByte(Stream input) + { + int value = input.ReadByte(); + if (value == -1) throw new EndOfStreamException(); + return (sbyte)value; + } + + private static bool ReadBoolean(Stream input) + { + return input.ReadByte() != 0; + } + + private float ReadFloat(Stream input) + { + buffer[3] = (byte)input.ReadByte(); + buffer[2] = (byte)input.ReadByte(); + buffer[1] = (byte)input.ReadByte(); + buffer[0] = (byte)input.ReadByte(); + return BitConverter.ToSingle(buffer, 0); + } + + private static int ReadInt(Stream input) + { + return (input.ReadByte() << 24) + (input.ReadByte() << 16) + (input.ReadByte() << 8) + input.ReadByte(); + } + + private static int ReadVarint(Stream input, bool optimizePositive) + { + int b = input.ReadByte(); + int result = b & 0x7F; + if ((b & 0x80) != 0) + { + b = input.ReadByte(); + result |= (b & 0x7F) << 7; + if ((b & 0x80) != 0) + { + b = input.ReadByte(); + result |= (b & 0x7F) << 14; + if ((b & 0x80) != 0) + { + b = input.ReadByte(); + result |= (b & 0x7F) << 21; + if ((b & 0x80) != 0) result |= (input.ReadByte() & 0x7F) << 28; + } + } + } + return optimizePositive ? result : ((result >> 1) ^ -(result & 1)); + } + + private string ReadString(Stream input) + { + int byteCount = ReadVarint(input, true); + switch (byteCount) + { + case 0: + return null; + case 1: + return ""; + } + byteCount--; + byte[] buffer = this.buffer; + if (buffer.Length < byteCount) buffer = new byte[byteCount]; + ReadFully(input, buffer, 0, byteCount); + return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount); + } + + private static void ReadFully(Stream input, byte[] buffer, int offset, int length) + { + while (length > 0) + { + int count = input.Read(buffer, offset, length); + if (count <= 0) throw new EndOfStreamException(); + offset += count; + length -= count; + } + } + + internal class Vertices + { + public int[] bones; + public float[] vertices; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/SkeletonBounds.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/SkeletonBounds.cs index 9e0e61d..5ebfadb 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/SkeletonBounds.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/SkeletonBounds.cs @@ -30,205 +30,232 @@ using System; -namespace Spine3_6_39 { - - /// - /// 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. - /// - public class SkeletonBounds { - private ExposedList polygonPool = new ExposedList(); - private float minX, minY, maxX, maxY; - - public ExposedList BoundingBoxes { get; private set; } - public ExposedList Polygons { get; private set; } - public float MinX { get { return minX; } set { minX = value; } } - public float MinY { get { return minY; } set { minY = value; } } - public float MaxX { get { return maxX; } set { maxX = value; } } - public float MaxY { get { return maxY; } set { maxY = value; } } - public float Width { get { return maxX - minX; } } - public float Height { get { return maxY - minY; } } - - public SkeletonBounds () { - BoundingBoxes = new ExposedList(); - Polygons = new ExposedList(); - } - - /// - /// Clears any previous polygons, finds all visible bounding box attachments, - /// and computes the world vertices for each bounding box's polygon. - /// The skeleton. - /// - /// If true, the axis aligned bounding box containing all the polygons is computed. - /// If false, the SkeletonBounds AABB methods will always return true. - /// - public void Update (Skeleton skeleton, bool updateAabb) { - ExposedList boundingBoxes = BoundingBoxes; - ExposedList polygons = Polygons; - ExposedList slots = skeleton.slots; - int slotCount = slots.Count; - - boundingBoxes.Clear(); - for (int i = 0, n = polygons.Count; i < n; i++) - polygonPool.Add(polygons.Items[i]); - polygons.Clear(); - - for (int i = 0; i < slotCount; i++) { - Slot slot = slots.Items[i]; - BoundingBoxAttachment boundingBox = slot.attachment as BoundingBoxAttachment; - if (boundingBox == null) continue; - boundingBoxes.Add(boundingBox); - - Polygon polygon = null; - int poolCount = polygonPool.Count; - if (poolCount > 0) { - polygon = polygonPool.Items[poolCount - 1]; - polygonPool.RemoveAt(poolCount - 1); - } else - polygon = new Polygon(); - polygons.Add(polygon); - - int count = boundingBox.worldVerticesLength; - polygon.Count = count; - if (polygon.Vertices.Length < count) polygon.Vertices = new float[count]; - boundingBox.ComputeWorldVertices(slot, polygon.Vertices); - } - - if (updateAabb) { - AabbCompute(); - } else { - minX = int.MinValue; - minY = int.MinValue; - maxX = int.MaxValue; - maxY = int.MaxValue; - } - } - - private void AabbCompute () { - float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; - ExposedList polygons = Polygons; - for (int i = 0, n = polygons.Count; i < n; i++) { - Polygon polygon = polygons.Items[i]; - float[] vertices = polygon.Vertices; - for (int ii = 0, nn = polygon.Count; ii < nn; ii += 2) { - float x = vertices[ii]; - float y = vertices[ii + 1]; - minX = Math.Min(minX, x); - minY = Math.Min(minY, y); - maxX = Math.Max(maxX, x); - maxY = Math.Max(maxY, y); - } - } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; - } - - - /// Returns true if the axis aligned bounding box contains the point. - public bool AabbContainsPoint (float x, float y) { - return x >= minX && x <= maxX && y >= minY && y <= maxY; - } - - /// Returns true if the axis aligned bounding box intersects the line segment. - public bool AabbIntersectsSegment (float x1, float y1, float x2, float y2) { - float minX = this.minX; - float minY = this.minY; - float maxX = this.maxX; - float maxY = this.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; - } - - /// Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds. - public bool AabbIntersectsSkeleton (SkeletonBounds bounds) { - return minX < bounds.maxX && maxX > bounds.minX && minY < bounds.maxY && maxY > bounds.minY; - } - - /// Returns true if the polygon contains the point. - public bool ContainsPoint (Polygon polygon, float x, float y) { - float[] 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; - } - - /// 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. - public BoundingBoxAttachment ContainsPoint (float x, float y) { - ExposedList polygons = Polygons; - for (int i = 0, n = polygons.Count; i < n; i++) - if (ContainsPoint(polygons.Items[i], x, y)) return BoundingBoxes.Items[i]; - return null; - } - - /// 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. - public BoundingBoxAttachment IntersectsSegment (float x1, float y1, float x2, float y2) { - ExposedList polygons = Polygons; - for (int i = 0, n = polygons.Count; i < n; i++) - if (IntersectsSegment(polygons.Items[i], x1, y1, x2, y2)) return BoundingBoxes.Items[i]; - return null; - } - - /// Returns true if the polygon contains the line segment. - public bool IntersectsSegment (Polygon polygon, float x1, float y1, float x2, float y2) { - float[] vertices = polygon.Vertices; - int 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 (int 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; - } - - public Polygon GetPolygon (BoundingBoxAttachment attachment) { - int index = BoundingBoxes.IndexOf(attachment); - return index == -1 ? null : Polygons.Items[index]; - } - } - - public class Polygon { - public float[] Vertices { get; set; } - public int Count { get; set; } - - public Polygon () { - Vertices = new float[16]; - } - } +namespace Spine3_6_39 +{ + + /// + /// 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. + /// + public class SkeletonBounds + { + private ExposedList polygonPool = new ExposedList(); + private float minX, minY, maxX, maxY; + + public ExposedList BoundingBoxes { get; private set; } + public ExposedList Polygons { get; private set; } + public float MinX { get { return minX; } set { minX = value; } } + public float MinY { get { return minY; } set { minY = value; } } + public float MaxX { get { return maxX; } set { maxX = value; } } + public float MaxY { get { return maxY; } set { maxY = value; } } + public float Width { get { return maxX - minX; } } + public float Height { get { return maxY - minY; } } + + public SkeletonBounds() + { + BoundingBoxes = new ExposedList(); + Polygons = new ExposedList(); + } + + /// + /// Clears any previous polygons, finds all visible bounding box attachments, + /// and computes the world vertices for each bounding box's polygon. + /// The skeleton. + /// + /// If true, the axis aligned bounding box containing all the polygons is computed. + /// If false, the SkeletonBounds AABB methods will always return true. + /// + public void Update(Skeleton skeleton, bool updateAabb) + { + ExposedList boundingBoxes = BoundingBoxes; + ExposedList polygons = Polygons; + ExposedList slots = skeleton.slots; + int slotCount = slots.Count; + + boundingBoxes.Clear(); + for (int i = 0, n = polygons.Count; i < n; i++) + polygonPool.Add(polygons.Items[i]); + polygons.Clear(); + + for (int i = 0; i < slotCount; i++) + { + Slot slot = slots.Items[i]; + BoundingBoxAttachment boundingBox = slot.attachment as BoundingBoxAttachment; + if (boundingBox == null) continue; + boundingBoxes.Add(boundingBox); + + Polygon polygon = null; + int poolCount = polygonPool.Count; + if (poolCount > 0) + { + polygon = polygonPool.Items[poolCount - 1]; + polygonPool.RemoveAt(poolCount - 1); + } + else + polygon = new Polygon(); + polygons.Add(polygon); + + int count = boundingBox.worldVerticesLength; + polygon.Count = count; + if (polygon.Vertices.Length < count) polygon.Vertices = new float[count]; + boundingBox.ComputeWorldVertices(slot, polygon.Vertices); + } + + if (updateAabb) + { + AabbCompute(); + } + else + { + minX = int.MinValue; + minY = int.MinValue; + maxX = int.MaxValue; + maxY = int.MaxValue; + } + } + + private void AabbCompute() + { + float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; + ExposedList polygons = Polygons; + for (int i = 0, n = polygons.Count; i < n; i++) + { + Polygon polygon = polygons.Items[i]; + float[] vertices = polygon.Vertices; + for (int ii = 0, nn = polygon.Count; ii < nn; ii += 2) + { + float x = vertices[ii]; + float y = vertices[ii + 1]; + minX = Math.Min(minX, x); + minY = Math.Min(minY, y); + maxX = Math.Max(maxX, x); + maxY = Math.Max(maxY, y); + } + } + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + + /// Returns true if the axis aligned bounding box contains the point. + public bool AabbContainsPoint(float x, float y) + { + return x >= minX && x <= maxX && y >= minY && y <= maxY; + } + + /// Returns true if the axis aligned bounding box intersects the line segment. + public bool AabbIntersectsSegment(float x1, float y1, float x2, float y2) + { + float minX = this.minX; + float minY = this.minY; + float maxX = this.maxX; + float maxY = this.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; + } + + /// Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds. + public bool AabbIntersectsSkeleton(SkeletonBounds bounds) + { + return minX < bounds.maxX && maxX > bounds.minX && minY < bounds.maxY && maxY > bounds.minY; + } + + /// Returns true if the polygon contains the point. + public bool ContainsPoint(Polygon polygon, float x, float y) + { + float[] 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; + } + + /// 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. + public BoundingBoxAttachment ContainsPoint(float x, float y) + { + ExposedList polygons = Polygons; + for (int i = 0, n = polygons.Count; i < n; i++) + if (ContainsPoint(polygons.Items[i], x, y)) return BoundingBoxes.Items[i]; + return null; + } + + /// 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. + public BoundingBoxAttachment IntersectsSegment(float x1, float y1, float x2, float y2) + { + ExposedList polygons = Polygons; + for (int i = 0, n = polygons.Count; i < n; i++) + if (IntersectsSegment(polygons.Items[i], x1, y1, x2, y2)) return BoundingBoxes.Items[i]; + return null; + } + + /// Returns true if the polygon contains the line segment. + public bool IntersectsSegment(Polygon polygon, float x1, float y1, float x2, float y2) + { + float[] vertices = polygon.Vertices; + int 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 (int 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; + } + + public Polygon GetPolygon(BoundingBoxAttachment attachment) + { + int index = BoundingBoxes.IndexOf(attachment); + return index == -1 ? null : Polygons.Items[index]; + } + } + + public class Polygon + { + public float[] Vertices { get; set; } + public int Count { get; set; } + + public Polygon() + { + Vertices = new float[16]; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/SkeletonClipping.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/SkeletonClipping.cs index 8191c74..7176610 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/SkeletonClipping.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/SkeletonClipping.cs @@ -28,258 +28,286 @@ * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; +namespace Spine3_6_39 +{ + public class SkeletonClipping + { + internal readonly Triangulator triangulator = new Triangulator(); + internal readonly ExposedList clippingPolygon = new ExposedList(); + internal readonly ExposedList clipOutput = new ExposedList(128); + internal readonly ExposedList clippedVertices = new ExposedList(128); + internal readonly ExposedList clippedTriangles = new ExposedList(128); + internal readonly ExposedList clippedUVs = new ExposedList(128); + internal readonly ExposedList scratch = new ExposedList(); -namespace Spine3_6_39 { - public class SkeletonClipping { - internal readonly Triangulator triangulator = new Triangulator(); - internal readonly ExposedList clippingPolygon = new ExposedList(); - internal readonly ExposedList clipOutput = new ExposedList(128); - internal readonly ExposedList clippedVertices = new ExposedList(128); - internal readonly ExposedList clippedTriangles = new ExposedList(128); - internal readonly ExposedList clippedUVs = new ExposedList(128); - internal readonly ExposedList scratch = new ExposedList(); + internal ClippingAttachment clipAttachment; + internal ExposedList> clippingPolygons; - internal ClippingAttachment clipAttachment; - internal ExposedList> clippingPolygons; + public ExposedList ClippedVertices { get { return clippedVertices; } } + public ExposedList ClippedTriangles { get { return clippedTriangles; } } + public ExposedList ClippedUVs { get { return clippedUVs; } } - public ExposedList ClippedVertices { get { return clippedVertices; } } - public ExposedList ClippedTriangles { get { return clippedTriangles; } } - public ExposedList ClippedUVs { get { return clippedUVs; } } + public bool IsClipping() { return clipAttachment != null; } - public bool IsClipping () { return clipAttachment != null; } + public int ClipStart(Slot slot, ClippingAttachment clip) + { + if (clipAttachment != null) return 0; + clipAttachment = clip; - public int ClipStart (Slot slot, ClippingAttachment clip) { - if (clipAttachment != null) return 0; - clipAttachment = clip; + int n = clip.worldVerticesLength; + float[] vertices = clippingPolygon.Resize(n).Items; + clip.ComputeWorldVertices(slot, 0, n, vertices, 0, 2); + MakeClockwise(clippingPolygon); + clippingPolygons = triangulator.Decompose(clippingPolygon, triangulator.Triangulate(clippingPolygon)); + foreach (var polygon in clippingPolygons) + { + MakeClockwise(polygon); + polygon.Add(polygon.Items[0]); + polygon.Add(polygon.Items[1]); + } + return clippingPolygons.Count; + } - int n = clip.worldVerticesLength; - float[] vertices = clippingPolygon.Resize(n).Items; - clip.ComputeWorldVertices(slot, 0, n, vertices, 0, 2); - MakeClockwise(clippingPolygon); - clippingPolygons = triangulator.Decompose(clippingPolygon, triangulator.Triangulate(clippingPolygon)); - foreach (var polygon in clippingPolygons) { - MakeClockwise(polygon); - polygon.Add(polygon.Items[0]); - polygon.Add(polygon.Items[1]); - } - return clippingPolygons.Count; - } + public void ClipEnd(Slot slot) + { + if (clipAttachment != null && clipAttachment.endSlot == slot.data) ClipEnd(); + } - public void ClipEnd (Slot slot) { - if (clipAttachment != null && clipAttachment.endSlot == slot.data) ClipEnd(); - } + public void ClipEnd() + { + if (clipAttachment == null) return; + clipAttachment = null; + clippingPolygons = null; + clippedVertices.Clear(); + clippedTriangles.Clear(); + clippingPolygon.Clear(); + } - public void ClipEnd () { - if (clipAttachment == null) return; - clipAttachment = null; - clippingPolygons = null; - clippedVertices.Clear(); - clippedTriangles.Clear(); - clippingPolygon.Clear(); - } - - public void ClipTriangles (float[] vertices, int verticesLength, int[] triangles, int trianglesLength, float[] uvs) { - ExposedList clipOutput = this.clipOutput, clippedVertices = this.clippedVertices; - var clippedTriangles = this.clippedTriangles; - var polygons = clippingPolygons.Items; - int polygonsCount = clippingPolygons.Count; + public void ClipTriangles(float[] vertices, int verticesLength, int[] triangles, int trianglesLength, float[] uvs) + { + ExposedList clipOutput = this.clipOutput, clippedVertices = this.clippedVertices; + var clippedTriangles = this.clippedTriangles; + var polygons = clippingPolygons.Items; + int polygonsCount = clippingPolygons.Count; - int index = 0; - clippedVertices.Clear(); - clippedUVs.Clear(); - clippedTriangles.Clear(); - //outer: // libgdx - for (int i = 0; i < trianglesLength; i += 3) { - int vertexOffset = triangles[i] << 1; - float x1 = vertices[vertexOffset], y1 = vertices[vertexOffset + 1]; - float u1 = uvs[vertexOffset], v1 = uvs[vertexOffset + 1]; + int index = 0; + clippedVertices.Clear(); + clippedUVs.Clear(); + clippedTriangles.Clear(); + //outer: // libgdx + for (int i = 0; i < trianglesLength; i += 3) + { + int vertexOffset = triangles[i] << 1; + float x1 = vertices[vertexOffset], y1 = vertices[vertexOffset + 1]; + float u1 = uvs[vertexOffset], v1 = uvs[vertexOffset + 1]; - vertexOffset = triangles[i + 1] << 1; - float x2 = vertices[vertexOffset], y2 = vertices[vertexOffset + 1]; - float u2 = uvs[vertexOffset], v2 = uvs[vertexOffset + 1]; + vertexOffset = triangles[i + 1] << 1; + float x2 = vertices[vertexOffset], y2 = vertices[vertexOffset + 1]; + float u2 = uvs[vertexOffset], v2 = uvs[vertexOffset + 1]; - vertexOffset = triangles[i + 2] << 1; - float x3 = vertices[vertexOffset], y3 = vertices[vertexOffset + 1]; - float u3 = uvs[vertexOffset], v3 = uvs[vertexOffset + 1]; + vertexOffset = triangles[i + 2] << 1; + float x3 = vertices[vertexOffset], y3 = vertices[vertexOffset + 1]; + float u3 = uvs[vertexOffset], v3 = uvs[vertexOffset + 1]; - for (int p = 0; p < polygonsCount; p++) { - int s = clippedVertices.Count; - if (Clip(x1, y1, x2, y2, x3, y3, polygons[p], clipOutput)) { - int clipOutputLength = clipOutput.Count; - 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)); + for (int p = 0; p < polygonsCount; p++) + { + int s = clippedVertices.Count; + if (Clip(x1, y1, x2, y2, x3, y3, polygons[p], clipOutput)) + { + int clipOutputLength = clipOutput.Count; + 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)); - int clipOutputCount = clipOutputLength >> 1; - float[] clipOutputItems = clipOutput.Items; - float[] clippedVerticesItems = clippedVertices.Resize(s + clipOutputCount * 2).Items; - float[] clippedUVsItems = clippedUVs.Resize(s + clipOutputCount * 2).Items; - for (int ii = 0; ii < clipOutputLength; ii += 2) { - float x = clipOutputItems[ii], y = clipOutputItems[ii + 1]; - clippedVerticesItems[s] = x; - clippedVerticesItems[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; - clippedUVsItems[s] = u1 * a + u2 * b + u3 * c; - clippedUVsItems[s + 1] = v1 * a + v2 * b + v3 * c; - s += 2; - } + int clipOutputCount = clipOutputLength >> 1; + float[] clipOutputItems = clipOutput.Items; + float[] clippedVerticesItems = clippedVertices.Resize(s + clipOutputCount * 2).Items; + float[] clippedUVsItems = clippedUVs.Resize(s + clipOutputCount * 2).Items; + for (int ii = 0; ii < clipOutputLength; ii += 2) + { + float x = clipOutputItems[ii], y = clipOutputItems[ii + 1]; + clippedVerticesItems[s] = x; + clippedVerticesItems[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; + clippedUVsItems[s] = u1 * a + u2 * b + u3 * c; + clippedUVsItems[s + 1] = v1 * a + v2 * b + v3 * c; + s += 2; + } - s = clippedTriangles.Count; - int[] clippedTrianglesItems = clippedTriangles.Resize(s + 3 * (clipOutputCount - 2)).Items; - clipOutputCount--; - for (int ii = 1; ii < clipOutputCount; ii++) { - clippedTrianglesItems[s] = index; - clippedTrianglesItems[s + 1] = index + ii; - clippedTrianglesItems[s + 2] = index + ii + 1; - s += 3; - } - index += clipOutputCount + 1; - } - else { - float[] clippedVerticesItems = clippedVertices.Resize(s + 3 * 2).Items; - float[] clippedUVsItems = clippedUVs.Resize(s + 3 * 2).Items; - clippedVerticesItems[s] = x1; - clippedVerticesItems[s + 1] = y1; - clippedVerticesItems[s + 2] = x2; - clippedVerticesItems[s + 3] = y2; - clippedVerticesItems[s + 4] = x3; - clippedVerticesItems[s + 5] = y3; + s = clippedTriangles.Count; + int[] clippedTrianglesItems = clippedTriangles.Resize(s + 3 * (clipOutputCount - 2)).Items; + clipOutputCount--; + for (int ii = 1; ii < clipOutputCount; ii++) + { + clippedTrianglesItems[s] = index; + clippedTrianglesItems[s + 1] = index + ii; + clippedTrianglesItems[s + 2] = index + ii + 1; + s += 3; + } + index += clipOutputCount + 1; + } + else + { + float[] clippedVerticesItems = clippedVertices.Resize(s + 3 * 2).Items; + float[] clippedUVsItems = clippedUVs.Resize(s + 3 * 2).Items; + clippedVerticesItems[s] = x1; + clippedVerticesItems[s + 1] = y1; + clippedVerticesItems[s + 2] = x2; + clippedVerticesItems[s + 3] = y2; + clippedVerticesItems[s + 4] = x3; + clippedVerticesItems[s + 5] = y3; - clippedUVsItems[s] = u1; - clippedUVsItems[s + 1] = v1; - clippedUVsItems[s + 2] = u2; - clippedUVsItems[s + 3] = v2; - clippedUVsItems[s + 4] = u3; - clippedUVsItems[s + 5] = v3; + clippedUVsItems[s] = u1; + clippedUVsItems[s + 1] = v1; + clippedUVsItems[s + 2] = u2; + clippedUVsItems[s + 3] = v2; + clippedUVsItems[s + 4] = u3; + clippedUVsItems[s + 5] = v3; - s = clippedTriangles.Count; - int[] clippedTrianglesItems = clippedTriangles.Resize(s + 3).Items; - clippedTrianglesItems[s] = index; - clippedTrianglesItems[s + 1] = index + 1; - clippedTrianglesItems[s + 2] = index + 2; - index += 3; - break; //continue outer; - } - } - } + s = clippedTriangles.Count; + int[] clippedTrianglesItems = clippedTriangles.Resize(s + 3).Items; + clippedTrianglesItems[s] = index; + clippedTrianglesItems[s + 1] = index + 1; + clippedTrianglesItems[s + 2] = index + 2; + index += 3; + break; //continue outer; + } + } + } - } + } - /** Clips the input triangle against the convex, clockwise clipping area. If the triangle lies entirely within the clipping + /** 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. */ - internal bool Clip (float x1, float y1, float x2, float y2, float x3, float y3, ExposedList clippingArea, ExposedList output) { - var originalOutput = output; - var clipped = false; + internal bool Clip(float x1, float y1, float x2, float y2, float x3, float y3, ExposedList clippingArea, ExposedList output) + { + var originalOutput = output; + var clipped = false; - // Avoid copy at the end. - ExposedList input = null; - if (clippingArea.Count % 4 >= 2) { - input = output; - output = scratch; - } else { - input = scratch; - } + // Avoid copy at the end. + ExposedList input = null; + if (clippingArea.Count % 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(); + 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(); - float[] clippingVertices = clippingArea.Items; - int clippingVerticesLast = clippingArea.Count - 4; - for (int i = 0; ; i += 2) { - float edgeX = clippingVertices[i], edgeY = clippingVertices[i + 1]; - float edgeX2 = clippingVertices[i + 2], edgeY2 = clippingVertices[i + 3]; - float deltaX = edgeX - edgeX2, deltaY = edgeY - edgeY2; + float[] clippingVertices = clippingArea.Items; + int clippingVerticesLast = clippingArea.Count - 4; + for (int i = 0; ; i += 2) + { + float edgeX = clippingVertices[i], edgeY = clippingVertices[i + 1]; + float edgeX2 = clippingVertices[i + 2], edgeY2 = clippingVertices[i + 3]; + float deltaX = edgeX - edgeX2, deltaY = edgeY - edgeY2; - float[] inputVertices = input.Items; - int inputVerticesLength = input.Count - 2, outputStart = output.Count; - for (int ii = 0; ii < inputVerticesLength; ii += 2) { - float inputX = inputVertices[ii], inputY = inputVertices[ii + 1]; - float inputX2 = inputVertices[ii + 2], inputY2 = inputVertices[ii + 3]; - bool side2 = deltaX * (inputY2 - edgeY2) - deltaY * (inputX2 - edgeX2) > 0; - if (deltaX * (inputY - edgeY2) - deltaY * (inputX - edgeX2) > 0) { - if (side2) { // v1 inside, v2 inside - output.Add(inputX2); - output.Add(inputY2); - continue; - } - // v1 inside, v2 outside - float c0 = inputY2 - inputY, c2 = inputX2 - inputX; - float ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / (c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY)); - output.Add(edgeX + (edgeX2 - edgeX) * ua); - output.Add(edgeY + (edgeY2 - edgeY) * ua); - } - else if (side2) { // v1 outside, v2 inside - float c0 = inputY2 - inputY, c2 = inputX2 - inputX; - float ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / (c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY)); - output.Add(edgeX + (edgeX2 - edgeX) * ua); - output.Add(edgeY + (edgeY2 - edgeY) * ua); - output.Add(inputX2); - output.Add(inputY2); - } - clipped = true; - } + float[] inputVertices = input.Items; + int inputVerticesLength = input.Count - 2, outputStart = output.Count; + for (int ii = 0; ii < inputVerticesLength; ii += 2) + { + float inputX = inputVertices[ii], inputY = inputVertices[ii + 1]; + float inputX2 = inputVertices[ii + 2], inputY2 = inputVertices[ii + 3]; + bool side2 = deltaX * (inputY2 - edgeY2) - deltaY * (inputX2 - edgeX2) > 0; + if (deltaX * (inputY - edgeY2) - deltaY * (inputX - edgeX2) > 0) + { + if (side2) + { // v1 inside, v2 inside + output.Add(inputX2); + output.Add(inputY2); + continue; + } + // v1 inside, v2 outside + float c0 = inputY2 - inputY, c2 = inputX2 - inputX; + float ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / (c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY)); + output.Add(edgeX + (edgeX2 - edgeX) * ua); + output.Add(edgeY + (edgeY2 - edgeY) * ua); + } + else if (side2) + { // v1 outside, v2 inside + float c0 = inputY2 - inputY, c2 = inputX2 - inputX; + float ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / (c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY)); + output.Add(edgeX + (edgeX2 - edgeX) * ua); + output.Add(edgeY + (edgeY2 - edgeY) * ua); + output.Add(inputX2); + output.Add(inputY2); + } + clipped = true; + } - if (outputStart == output.Count) { // All edges outside. - originalOutput.Clear(); - return true; - } + if (outputStart == output.Count) + { // All edges outside. + originalOutput.Clear(); + return true; + } - output.Add(output.Items[0]); - output.Add(output.Items[1]); + output.Add(output.Items[0]); + output.Add(output.Items[1]); - if (i == clippingVerticesLast) break; - var temp = output; - output = input; - output.Clear(); - input = temp; - } + if (i == clippingVerticesLast) break; + var temp = output; + output = input; + output.Clear(); + input = temp; + } - if (originalOutput != output) { - originalOutput.Clear(); - for (int i = 0, n = output.Count - 2; i < n; i++) { - originalOutput.Add(output.Items[i]); - } - } else { - originalOutput.Resize(originalOutput.Count - 2); - } + if (originalOutput != output) + { + originalOutput.Clear(); + for (int i = 0, n = output.Count - 2; i < n; i++) + { + originalOutput.Add(output.Items[i]); + } + } + else + { + originalOutput.Resize(originalOutput.Count - 2); + } - return clipped; - } + return clipped; + } - static void MakeClockwise (ExposedList polygon) { - float[] vertices = polygon.Items; - int verticeslength = polygon.Count; + static void MakeClockwise(ExposedList polygon) + { + float[] vertices = polygon.Items; + int verticeslength = polygon.Count; - float area = vertices[verticeslength - 2] * vertices[1] - vertices[0] * vertices[verticeslength - 1], p1x, p1y, p2x, p2y; - for (int i = 0, n = verticeslength - 3; i < n; i += 2) { - p1x = vertices[i]; - p1y = vertices[i + 1]; - p2x = vertices[i + 2]; - p2y = vertices[i + 3]; - area += p1x * p2y - p2x * p1y; - } - if (area < 0) return; + float area = vertices[verticeslength - 2] * vertices[1] - vertices[0] * vertices[verticeslength - 1], p1x, p1y, p2x, p2y; + for (int i = 0, n = verticeslength - 3; i < n; i += 2) + { + p1x = vertices[i]; + p1y = vertices[i + 1]; + p2x = vertices[i + 2]; + p2y = vertices[i + 3]; + area += p1x * p2y - p2x * p1y; + } + if (area < 0) return; - for (int i = 0, lastX = verticeslength - 2, n = verticeslength >> 1; i < n; i += 2) { - float x = vertices[i], y = vertices[i + 1]; - int other = lastX - i; - vertices[i] = vertices[other]; - vertices[i + 1] = vertices[other + 1]; - vertices[other] = x; - vertices[other + 1] = y; - } - } - } + for (int i = 0, lastX = verticeslength - 2, n = verticeslength >> 1; i < n; i += 2) + { + float x = vertices[i], y = vertices[i + 1]; + int other = lastX - i; + vertices[i] = vertices[other]; + vertices[i + 1] = vertices[other + 1]; + vertices[other] = x; + vertices[other + 1] = y; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/SkeletonData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/SkeletonData.cs index 1bcd67c..18faf70 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/SkeletonData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/SkeletonData.cs @@ -30,195 +30,215 @@ using System; -namespace Spine3_6_39 { - - /// Stores the setup pose and all of the stateless data for a skeleton. - public class SkeletonData { - internal string name; - internal ExposedList bones = new ExposedList(); // Ordered parents first - internal ExposedList slots = new ExposedList(); // Setup pose draw order. - internal ExposedList skins = new ExposedList(); - internal Skin defaultSkin; - internal ExposedList events = new ExposedList(); - internal ExposedList animations = new ExposedList(); - internal ExposedList ikConstraints = new ExposedList(); - internal ExposedList transformConstraints = new ExposedList(); - internal ExposedList pathConstraints = new ExposedList(); - internal float width, height; - internal string version, hash; - - // Nonessential. - internal float fps; - internal string imagesPath; - - public string Name { get { return name; } set { name = value; } } - - /// The skeleton's bones, sorted parent first. The root bone is always the first bone. - public ExposedList Bones { get { return bones; } } - - public ExposedList Slots { get { return slots; } } - - /// All skins, including the default skin. - public ExposedList Skins { get { return skins; } set { skins = value; } } - - /// - /// The skeleton's default skin. - /// By default this skin contains all attachments that were not in a skin in Spine. - /// - /// May be null. - public Skin DefaultSkin { get { return defaultSkin; } set { defaultSkin = value; } } - - public ExposedList Events { get { return events; } set { events = value; } } - public ExposedList Animations { get { return animations; } set { animations = value; } } - public ExposedList IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } } - public ExposedList TransformConstraints { get { return transformConstraints; } set { transformConstraints = value; } } - public ExposedList PathConstraints { get { return pathConstraints; } set { pathConstraints = value; } } - - public float Width { get { return width; } set { width = value; } } - public float Height { get { return height; } set { height = value; } } - /// The Spine version used to export this data, or null. - public string Version { get { return version; } set { version = value; } } - public string Hash { get { return hash; } set { hash = value; } } - public string ImagesPath { get { return imagesPath; } set { imagesPath = value; } } - - /// - /// The dopesheet FPS in Spine. Available only when nonessential data was exported. - public float Fps { get { return fps; } set { fps = value; } } - - // --- Bones. - - /// - /// 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. - /// May be null. - public BoneData FindBone (string boneName) { - if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); - var bones = this.bones; - var bonesItems = bones.Items; - for (int i = 0, n = bones.Count; i < n; i++) { - BoneData bone = bonesItems[i]; - if (bone.name == boneName) return bone; - } - return null; - } - - /// -1 if the bone was not found. - public int FindBoneIndex (string boneName) { - if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); - var bones = this.bones; - var bonesItems = bones.Items; - for (int i = 0, n = bones.Count; i < n; i++) - if (bonesItems[i].name == boneName) return i; - return -1; - } - - // --- Slots. - - /// May be null. - public SlotData FindSlot (string slotName) { - if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); - ExposedList slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) { - SlotData slot = slots.Items[i]; - if (slot.name == slotName) return slot; - } - return null; - } - - /// -1 if the slot was not found. - public int FindSlotIndex (string slotName) { - if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); - ExposedList slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) - if (slots.Items[i].name == slotName) return i; - return -1; - } - - // --- Skins. - - /// May be null. - public Skin FindSkin (string skinName) { - if (skinName == null) throw new ArgumentNullException("skinName", "skinName cannot be null."); - foreach (Skin skin in skins) - if (skin.name == skinName) return skin; - return null; - } - - // --- Events. - - /// May be null. - public EventData FindEvent (string eventDataName) { - if (eventDataName == null) throw new ArgumentNullException("eventDataName", "eventDataName cannot be null."); - foreach (EventData eventData in events) - if (eventData.name == eventDataName) return eventData; - return null; - } - - // --- Animations. - - /// May be null. - public Animation FindAnimation (string animationName) { - if (animationName == null) throw new ArgumentNullException("animationName", "animationName cannot be null."); - ExposedList animations = this.animations; - for (int i = 0, n = animations.Count; i < n; i++) { - Animation animation = animations.Items[i]; - if (animation.name == animationName) return animation; - } - return null; - } - - // --- IK constraints. - - /// May be null. - public IkConstraintData FindIkConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - ExposedList ikConstraints = this.ikConstraints; - for (int i = 0, n = ikConstraints.Count; i < n; i++) { - IkConstraintData ikConstraint = ikConstraints.Items[i]; - if (ikConstraint.name == constraintName) return ikConstraint; - } - return null; - } - - // --- Transform constraints. - - /// May be null. - public TransformConstraintData FindTransformConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - ExposedList transformConstraints = this.transformConstraints; - for (int i = 0, n = transformConstraints.Count; i < n; i++) { - TransformConstraintData transformConstraint = transformConstraints.Items[i]; - if (transformConstraint.name == constraintName) return transformConstraint; - } - return null; - } - - // --- Path constraints. - - /// May be null. - public PathConstraintData FindPathConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - ExposedList pathConstraints = this.pathConstraints; - for (int i = 0, n = pathConstraints.Count; i < n; i++) { - PathConstraintData constraint = pathConstraints.Items[i]; - if (constraint.name.Equals(constraintName)) return constraint; - } - return null; - } - - /// -1 if the path constraint was not found. - public int FindPathConstraintIndex (string pathConstraintName) { - if (pathConstraintName == null) throw new ArgumentNullException("pathConstraintName", "pathConstraintName cannot be null."); - ExposedList pathConstraints = this.pathConstraints; - for (int i = 0, n = pathConstraints.Count; i < n; i++) - if (pathConstraints.Items[i].name.Equals(pathConstraintName)) return i; - return -1; - } - - // --- - - override public string ToString () { - return name ?? base.ToString(); - } - } +namespace Spine3_6_39 +{ + + /// Stores the setup pose and all of the stateless data for a skeleton. + public class SkeletonData + { + internal string name; + internal ExposedList bones = new ExposedList(); // Ordered parents first + internal ExposedList slots = new ExposedList(); // Setup pose draw order. + internal ExposedList skins = new ExposedList(); + internal Skin defaultSkin; + internal ExposedList events = new ExposedList(); + internal ExposedList animations = new ExposedList(); + internal ExposedList ikConstraints = new ExposedList(); + internal ExposedList transformConstraints = new ExposedList(); + internal ExposedList pathConstraints = new ExposedList(); + internal float width, height; + internal string version, hash; + + // Nonessential. + internal float fps; + internal string imagesPath; + + public string Name { get { return name; } set { name = value; } } + + /// The skeleton's bones, sorted parent first. The root bone is always the first bone. + public ExposedList Bones { get { return bones; } } + + public ExposedList Slots { get { return slots; } } + + /// All skins, including the default skin. + public ExposedList Skins { get { return skins; } set { skins = value; } } + + /// + /// The skeleton's default skin. + /// By default this skin contains all attachments that were not in a skin in Spine. + /// + /// May be null. + public Skin DefaultSkin { get { return defaultSkin; } set { defaultSkin = value; } } + + public ExposedList Events { get { return events; } set { events = value; } } + public ExposedList Animations { get { return animations; } set { animations = value; } } + public ExposedList IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } } + public ExposedList TransformConstraints { get { return transformConstraints; } set { transformConstraints = value; } } + public ExposedList PathConstraints { get { return pathConstraints; } set { pathConstraints = value; } } + + public float Width { get { return width; } set { width = value; } } + public float Height { get { return height; } set { height = value; } } + /// The Spine version used to export this data, or null. + public string Version { get { return version; } set { version = value; } } + public string Hash { get { return hash; } set { hash = value; } } + public string ImagesPath { get { return imagesPath; } set { imagesPath = value; } } + + /// + /// The dopesheet FPS in Spine. Available only when nonessential data was exported. + public float Fps { get { return fps; } set { fps = value; } } + + // --- Bones. + + /// + /// 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. + /// May be null. + public BoneData FindBone(string boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); + var bones = this.bones; + var bonesItems = bones.Items; + for (int i = 0, n = bones.Count; i < n; i++) + { + BoneData bone = bonesItems[i]; + if (bone.name == boneName) return bone; + } + return null; + } + + /// -1 if the bone was not found. + public int FindBoneIndex(string boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); + var bones = this.bones; + var bonesItems = bones.Items; + for (int i = 0, n = bones.Count; i < n; i++) + if (bonesItems[i].name == boneName) return i; + return -1; + } + + // --- Slots. + + /// May be null. + public SlotData FindSlot(string slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + ExposedList slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + { + SlotData slot = slots.Items[i]; + if (slot.name == slotName) return slot; + } + return null; + } + + /// -1 if the slot was not found. + public int FindSlotIndex(string slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + ExposedList slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + if (slots.Items[i].name == slotName) return i; + return -1; + } + + // --- Skins. + + /// May be null. + public Skin FindSkin(string skinName) + { + if (skinName == null) throw new ArgumentNullException("skinName", "skinName cannot be null."); + foreach (Skin skin in skins) + if (skin.name == skinName) return skin; + return null; + } + + // --- Events. + + /// May be null. + public EventData FindEvent(string eventDataName) + { + if (eventDataName == null) throw new ArgumentNullException("eventDataName", "eventDataName cannot be null."); + foreach (EventData eventData in events) + if (eventData.name == eventDataName) return eventData; + return null; + } + + // --- Animations. + + /// May be null. + public Animation FindAnimation(string animationName) + { + if (animationName == null) throw new ArgumentNullException("animationName", "animationName cannot be null."); + ExposedList animations = this.animations; + for (int i = 0, n = animations.Count; i < n; i++) + { + Animation animation = animations.Items[i]; + if (animation.name == animationName) return animation; + } + return null; + } + + // --- IK constraints. + + /// May be null. + public IkConstraintData FindIkConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + ExposedList ikConstraints = this.ikConstraints; + for (int i = 0, n = ikConstraints.Count; i < n; i++) + { + IkConstraintData ikConstraint = ikConstraints.Items[i]; + if (ikConstraint.name == constraintName) return ikConstraint; + } + return null; + } + + // --- Transform constraints. + + /// May be null. + public TransformConstraintData FindTransformConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + ExposedList transformConstraints = this.transformConstraints; + for (int i = 0, n = transformConstraints.Count; i < n; i++) + { + TransformConstraintData transformConstraint = transformConstraints.Items[i]; + if (transformConstraint.name == constraintName) return transformConstraint; + } + return null; + } + + // --- Path constraints. + + /// May be null. + public PathConstraintData FindPathConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + ExposedList pathConstraints = this.pathConstraints; + for (int i = 0, n = pathConstraints.Count; i < n; i++) + { + PathConstraintData constraint = pathConstraints.Items[i]; + if (constraint.name.Equals(constraintName)) return constraint; + } + return null; + } + + /// -1 if the path constraint was not found. + public int FindPathConstraintIndex(string pathConstraintName) + { + if (pathConstraintName == null) throw new ArgumentNullException("pathConstraintName", "pathConstraintName cannot be null."); + ExposedList pathConstraints = this.pathConstraints; + for (int i = 0, n = pathConstraints.Count; i < n; i++) + if (pathConstraints.Items[i].name.Equals(pathConstraintName)) return i; + return -1; + } + + // --- + + override public string ToString() + { + return name ?? base.ToString(); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/SkeletonJson.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/SkeletonJson.cs index 661aaa2..c228894 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/SkeletonJson.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/SkeletonJson.cs @@ -33,32 +33,36 @@ #endif using System; -using System.IO; using System.Collections.Generic; +using System.IO; #if WINDOWS_STOREAPP using System.Threading.Tasks; using Windows.Storage; #endif -namespace Spine3_6_39 { - public class SkeletonJson { - public float Scale { get; set; } +namespace Spine3_6_39 +{ + public class SkeletonJson + { + public float Scale { get; set; } - private AttachmentLoader attachmentLoader; - private List linkedMeshes = new List(); + private AttachmentLoader attachmentLoader; + private List linkedMeshes = new List(); - public SkeletonJson (params Atlas[] atlasArray) - : this(new AtlasAttachmentLoader(atlasArray)) { - } + public SkeletonJson(params Atlas[] atlasArray) + : this(new AtlasAttachmentLoader(atlasArray)) + { + } - public SkeletonJson (AttachmentLoader attachmentLoader) { - if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader", "attachmentLoader cannot be null."); - this.attachmentLoader = attachmentLoader; - Scale = 1; - } + public SkeletonJson(AttachmentLoader attachmentLoader) + { + if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader", "attachmentLoader cannot be null."); + this.attachmentLoader = attachmentLoader; + Scale = 1; + } - #if !IS_UNITY && WINDOWS_STOREAPP +#if !IS_UNITY && WINDOWS_STOREAPP private async Task ReadFile(string path) { var folder = Windows.ApplicationModel.Package.Current.InstalledLocation; var file = await folder.GetFileAsync(path).AsTask().ConfigureAwait(false); @@ -72,795 +76,913 @@ private async Task ReadFile(string path) { public SkeletonData ReadSkeletonData (String path) { return this.ReadFile(path).Result; } - #else - public SkeletonData ReadSkeletonData (String path) { - #if WINDOWS_PHONE +#else + public SkeletonData ReadSkeletonData(String path) + { +#if WINDOWS_PHONE using (var reader = new StreamReader(Microsoft.Xna.Framework.TitleContainer.OpenStream(path))) { - #else - using (var reader = new StreamReader(new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))) { - #endif - SkeletonData skeletonData = ReadSkeletonData(reader); - skeletonData.name = Path.GetFileNameWithoutExtension(path); - return skeletonData; - } - } - #endif - - public SkeletonData ReadSkeletonData (TextReader reader) { - if (reader == null) throw new ArgumentNullException("reader", "reader cannot be null."); - - var scale = this.Scale; - var skeletonData = new SkeletonData(); - - var root = Json.Deserialize(reader) as Dictionary; - if (root == null) throw new Exception("Invalid JSON."); - - // Skeleton. - if (root.ContainsKey("skeleton")) { - var skeletonMap = (Dictionary)root["skeleton"]; - skeletonData.hash = (String)skeletonMap["hash"]; - skeletonData.version = (String)skeletonMap["spine"]; - skeletonData.width = GetFloat(skeletonMap, "width", 0); - skeletonData.height = GetFloat(skeletonMap, "height", 0); - skeletonData.fps = GetFloat(skeletonMap, "fps", 0); - skeletonData.imagesPath = GetString(skeletonMap, "images", null); - } - - // Bones. - foreach (Dictionary boneMap in (List)root["bones"]) { - BoneData parent = null; - if (boneMap.ContainsKey("parent")) { - parent = skeletonData.FindBone((String)boneMap["parent"]); - if (parent == null) - throw new Exception("Parent bone not found: " + boneMap["parent"]); - } - var data = new BoneData(skeletonData.Bones.Count, (String)boneMap["name"], parent); - data.length = GetFloat(boneMap, "length", 0) * scale; - data.x = GetFloat(boneMap, "x", 0) * scale; - data.y = GetFloat(boneMap, "y", 0) * scale; - data.rotation = GetFloat(boneMap, "rotation", 0); - data.scaleX = GetFloat(boneMap, "scaleX", 1); - data.scaleY = GetFloat(boneMap, "scaleY", 1); - data.shearX = GetFloat(boneMap, "shearX", 0); - data.shearY = GetFloat(boneMap, "shearY", 0); - - string tm = GetString(boneMap, "transform", TransformMode.Normal.ToString()); - data.transformMode = (TransformMode)Enum.Parse(typeof(TransformMode), tm, true); - - skeletonData.bones.Add(data); - } - - // Slots. - if (root.ContainsKey("slots")) { - foreach (Dictionary slotMap in (List)root["slots"]) { - var slotName = (String)slotMap["name"]; - var boneName = (String)slotMap["bone"]; - BoneData boneData = skeletonData.FindBone(boneName); - if (boneData == null) throw new Exception("Slot bone not found: " + boneName); - var data = new SlotData(skeletonData.Slots.Count, slotName, boneData); - - if (slotMap.ContainsKey("color")) { - var color = (String)slotMap["color"]; - data.r = ToColor(color, 0); - data.g = ToColor(color, 1); - data.b = ToColor(color, 2); - data.a = ToColor(color, 3); - } - - if (slotMap.ContainsKey("dark")) { - var color2 = (String)slotMap["dark"]; - data.r2 = ToColor(color2, 0, 6); // expectedLength = 6. ie. "RRGGBB" - data.g2 = ToColor(color2, 1, 6); - data.b2 = ToColor(color2, 2, 6); - data.hasSecondColor = true; - } - - data.attachmentName = GetString(slotMap, "attachment", null); - if (slotMap.ContainsKey("blend")) - data.blendMode = (BlendMode)Enum.Parse(typeof(BlendMode), (String)slotMap["blend"], true); - else - data.blendMode = BlendMode.Normal; - skeletonData.slots.Add(data); - } - } - - // IK constraints. - if (root.ContainsKey("ik")) { - foreach (Dictionary constraintMap in (List)root["ik"]) { - IkConstraintData data = new IkConstraintData((String)constraintMap["name"]); - data.order = GetInt(constraintMap, "order", 0); - - foreach (String boneName in (List)constraintMap["bones"]) { - BoneData bone = skeletonData.FindBone(boneName); - if (bone == null) throw new Exception("IK constraint bone not found: " + boneName); - data.bones.Add(bone); - } - - String targetName = (String)constraintMap["target"]; - data.target = skeletonData.FindBone(targetName); - if (data.target == null) throw new Exception("Target bone not found: " + targetName); - - data.bendDirection = GetBoolean(constraintMap, "bendPositive", true) ? 1 : -1; - data.mix = GetFloat(constraintMap, "mix", 1); - - skeletonData.ikConstraints.Add(data); - } - } - - // Transform constraints. - if (root.ContainsKey("transform")) { - foreach (Dictionary constraintMap in (List)root["transform"]) { - TransformConstraintData data = new TransformConstraintData((String)constraintMap["name"]); - data.order = GetInt(constraintMap, "order", 0); - - foreach (String boneName in (List)constraintMap["bones"]) { - BoneData bone = skeletonData.FindBone(boneName); - if (bone == null) throw new Exception("Transform constraint bone not found: " + boneName); - data.bones.Add(bone); - } - - String targetName = (String)constraintMap["target"]; - data.target = skeletonData.FindBone(targetName); - if (data.target == null) throw new Exception("Target bone not found: " + targetName); - - data.local = GetBoolean(constraintMap, "local", false); - data.relative = GetBoolean(constraintMap, "relative", false); - - data.offsetRotation = GetFloat(constraintMap, "rotation", 0); - data.offsetX = GetFloat(constraintMap, "x", 0) * scale; - data.offsetY = GetFloat(constraintMap, "y", 0) * scale; - data.offsetScaleX = GetFloat(constraintMap, "scaleX", 0); - data.offsetScaleY = GetFloat(constraintMap, "scaleY", 0); - data.offsetShearY = GetFloat(constraintMap, "shearY", 0); - - data.rotateMix = GetFloat(constraintMap, "rotateMix", 1); - data.translateMix = GetFloat(constraintMap, "translateMix", 1); - data.scaleMix = GetFloat(constraintMap, "scaleMix", 1); - data.shearMix = GetFloat(constraintMap, "shearMix", 1); - - skeletonData.transformConstraints.Add(data); - } - } - - // Path constraints. - if(root.ContainsKey("path")) { - foreach (Dictionary constraintMap in (List)root["path"]) { - PathConstraintData data = new PathConstraintData((String)constraintMap["name"]); - data.order = GetInt(constraintMap, "order", 0); - - foreach (String boneName in (List)constraintMap["bones"]) { - BoneData bone = skeletonData.FindBone(boneName); - if (bone == null) throw new Exception("Path bone not found: " + boneName); - data.bones.Add(bone); - } - - String targetName = (String)constraintMap["target"]; - data.target = skeletonData.FindSlot(targetName); - if (data.target == null) throw new Exception("Target slot not found: " + targetName); - - data.positionMode = (PositionMode)Enum.Parse(typeof(PositionMode), GetString(constraintMap, "positionMode", "percent"), true); - data.spacingMode = (SpacingMode)Enum.Parse(typeof(SpacingMode), GetString(constraintMap, "spacingMode", "length"), true); - data.rotateMode = (RotateMode)Enum.Parse(typeof(RotateMode), GetString(constraintMap, "rotateMode", "tangent"), true); - data.offsetRotation = GetFloat(constraintMap, "rotation", 0); - data.position = GetFloat(constraintMap, "position", 0); - if (data.positionMode == PositionMode.Fixed) data.position *= scale; - data.spacing = GetFloat(constraintMap, "spacing", 0); - if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale; - data.rotateMix = GetFloat(constraintMap, "rotateMix", 1); - data.translateMix = GetFloat(constraintMap, "translateMix", 1); - - skeletonData.pathConstraints.Add(data); - } - } - - // Skins. - if (root.ContainsKey("skins")) { - foreach (KeyValuePair skinMap in (Dictionary)root["skins"]) { - var skin = new Skin(skinMap.Key); - foreach (KeyValuePair slotEntry in (Dictionary)skinMap.Value) { - int slotIndex = skeletonData.FindSlotIndex(slotEntry.Key); - foreach (KeyValuePair entry in ((Dictionary)slotEntry.Value)) { - try { - Attachment attachment = ReadAttachment((Dictionary)entry.Value, skin, slotIndex, entry.Key, skeletonData); - if (attachment != null) skin.AddAttachment(slotIndex, entry.Key, attachment); - } catch (Exception e) { - throw new Exception("Error reading attachment: " + entry.Key + ", skin: " + skin, e); - } - } - } - skeletonData.skins.Add(skin); - if (skin.name == "default") skeletonData.defaultSkin = skin; - } - } - - // Linked meshes. - for (int i = 0, n = linkedMeshes.Count; i < n; i++) { - LinkedMesh linkedMesh = linkedMeshes[i]; - Skin skin = linkedMesh.skin == null ? skeletonData.defaultSkin : skeletonData.FindSkin(linkedMesh.skin); - if (skin == null) throw new Exception("Slot not found: " + linkedMesh.skin); - Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); - if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); - linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; - linkedMesh.mesh.UpdateUVs(); - } - linkedMeshes.Clear(); - - // Events. - if (root.ContainsKey("events")) { - foreach (KeyValuePair entry in (Dictionary)root["events"]) { - var entryMap = (Dictionary)entry.Value; - var data = new EventData(entry.Key); - data.Int = GetInt(entryMap, "int", 0); - data.Float = GetFloat(entryMap, "float", 0); - data.String = GetString(entryMap, "string", string.Empty); - skeletonData.events.Add(data); - } - } - - // Animations. - if (root.ContainsKey("animations")) { - foreach (KeyValuePair entry in (Dictionary)root["animations"]) { - try { - ReadAnimation((Dictionary)entry.Value, entry.Key, skeletonData); - } catch (Exception e) { - throw new Exception("Error reading animation: " + entry.Key, e); - } - } - } - - skeletonData.bones.TrimExcess(); - skeletonData.slots.TrimExcess(); - skeletonData.skins.TrimExcess(); - skeletonData.events.TrimExcess(); - skeletonData.animations.TrimExcess(); - skeletonData.ikConstraints.TrimExcess(); - return skeletonData; - } - - private Attachment ReadAttachment (Dictionary map, Skin skin, int slotIndex, String name, SkeletonData skeletonData) { - var scale = this.Scale; - name = GetString(map, "name", name); - - var typeName = GetString(map, "type", "region"); - if (typeName == "skinnedmesh") typeName = "weightedmesh"; - if (typeName == "weightedmesh") typeName = "mesh"; - if (typeName == "weightedlinkedmesh") typeName = "linkedmesh"; - var type = (AttachmentType)Enum.Parse(typeof(AttachmentType), typeName, true); - - String path = GetString(map, "path", name); - - switch (type) { - case AttachmentType.Region: - RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); - if (region == null) return null; - region.Path = path; - region.x = GetFloat(map, "x", 0) * scale; - region.y = GetFloat(map, "y", 0) * scale; - region.scaleX = GetFloat(map, "scaleX", 1); - region.scaleY = GetFloat(map, "scaleY", 1); - region.rotation = GetFloat(map, "rotation", 0); - region.width = GetFloat(map, "width", 32) * scale; - region.height = GetFloat(map, "height", 32) * scale; - region.UpdateOffset(); - - if (map.ContainsKey("color")) { - var color = (String)map["color"]; - region.r = ToColor(color, 0); - region.g = ToColor(color, 1); - region.b = ToColor(color, 2); - region.a = ToColor(color, 3); - } - - region.UpdateOffset(); - return region; - case AttachmentType.Boundingbox: - BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); - if (box == null) return null; - ReadVertices(map, box, GetInt(map, "vertexCount", 0) << 1); - return box; - case AttachmentType.Mesh: - case AttachmentType.Linkedmesh: { - MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); - if (mesh == null) return null; - mesh.Path = path; - - if (map.ContainsKey("color")) { - var color = (String)map["color"]; - mesh.r = ToColor(color, 0); - mesh.g = ToColor(color, 1); - mesh.b = ToColor(color, 2); - mesh.a = ToColor(color, 3); - } - - mesh.Width = GetFloat(map, "width", 0) * scale; - mesh.Height = GetFloat(map, "height", 0) * scale; - - String parent = GetString(map, "parent", null); - if (parent != null) { - mesh.InheritDeform = GetBoolean(map, "deform", true); - linkedMeshes.Add(new LinkedMesh(mesh, GetString(map, "skin", null), slotIndex, parent)); - return mesh; - } - - float[] uvs = GetFloatArray(map, "uvs", 1); - ReadVertices(map, mesh, uvs.Length); - mesh.triangles = GetIntArray(map, "triangles"); - mesh.regionUVs = uvs; - mesh.UpdateUVs(); - - if (map.ContainsKey("hull")) mesh.HullLength = GetInt(map, "hull", 0) * 2; - if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges"); - return mesh; - } - case AttachmentType.Path: { - PathAttachment pathAttachment = attachmentLoader.NewPathAttachment(skin, name); - if (pathAttachment == null) return null; - pathAttachment.closed = GetBoolean(map, "closed", false); - pathAttachment.constantSpeed = GetBoolean(map, "constantSpeed", true); - - int vertexCount = GetInt(map, "vertexCount", 0); - ReadVertices(map, pathAttachment, vertexCount << 1); - - // potential BOZO see Java impl - pathAttachment.lengths = GetFloatArray(map, "lengths", scale); - return pathAttachment; - } - case AttachmentType.Point: { - PointAttachment point = attachmentLoader.NewPointAttachment(skin, name); - if (point == null) return null; - point.x = GetFloat(map, "x", 0) * scale; - point.y = GetFloat(map, "y", 0) * scale; - point.rotation = GetFloat(map, "rotation", 0); - - //string color = GetString(map, "color", null); - //if (color != null) point.color = color; - return point; - } - case AttachmentType.Clipping: { - ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name); - if (clip == null) return null; - - string end = GetString(map, "end", null); - if (end != null) { - SlotData slot = skeletonData.FindSlot(end); - if (slot == null) throw new Exception("Clipping end slot not found: " + end); - clip.EndSlot = slot; - } - - ReadVertices(map, clip, GetInt(map, "vertexCount", 0) << 1); - - //string color = GetString(map, "color", null); - // if (color != null) clip.color = color; - return clip; - } - } - return null; - } - - private void ReadVertices (Dictionary map, VertexAttachment attachment, int verticesLength) { - attachment.WorldVerticesLength = verticesLength; - float[] vertices = GetFloatArray(map, "vertices", 1); - float scale = Scale; - if (verticesLength == vertices.Length) { - if (scale != 1) { - for (int i = 0; i < vertices.Length; i++) { - vertices[i] *= scale; - } - } - attachment.vertices = vertices; - return; - } - ExposedList weights = new ExposedList(verticesLength * 3 * 3); - ExposedList bones = new ExposedList(verticesLength * 3); - for (int i = 0, n = vertices.Length; i < n;) { - int boneCount = (int)vertices[i++]; - bones.Add(boneCount); - for (int nn = i + boneCount * 4; i < nn; i += 4) { - bones.Add((int)vertices[i]); - weights.Add(vertices[i + 1] * this.Scale); - weights.Add(vertices[i + 2] * this.Scale); - weights.Add(vertices[i + 3]); - } - } - attachment.bones = bones.ToArray(); - attachment.vertices = weights.ToArray(); - } - - private void ReadAnimation (Dictionary map, String name, SkeletonData skeletonData) { - var scale = this.Scale; - var timelines = new ExposedList(); - float duration = 0; - - // Slot timelines. - if (map.ContainsKey("slots")) { - foreach (KeyValuePair entry in (Dictionary)map["slots"]) { - String slotName = entry.Key; - int slotIndex = skeletonData.FindSlotIndex(slotName); - var timelineMap = (Dictionary)entry.Value; - foreach (KeyValuePair timelineEntry in timelineMap) { - var values = (List)timelineEntry.Value; - var timelineName = (String)timelineEntry.Key; - if (timelineName == "attachment") { - var timeline = new AttachmentTimeline(values.Count); - timeline.slotIndex = slotIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - timeline.SetFrame(frameIndex++, time, (String)valueMap["name"]); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); - - } else if (timelineName == "color") { - var timeline = new ColorTimeline(values.Count); - timeline.slotIndex = slotIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - string c = (string)valueMap["color"]; - timeline.SetFrame(frameIndex, time, ToColor(c, 0), ToColor(c, 1), ToColor(c, 2), ToColor(c, 3)); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * ColorTimeline.ENTRIES]); - - } else if (timelineName == "twoColor") { - var timeline = new TwoColorTimeline(values.Count); - timeline.slotIndex = slotIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - string light = (string)valueMap["light"]; - string dark = (string)valueMap["dark"]; - timeline.SetFrame(frameIndex, time, ToColor(light, 0), ToColor(light, 1), ToColor(light, 2), ToColor(light, 3), - ToColor(dark, 0, 6), ToColor(dark, 1, 6), ToColor(dark, 2, 6)); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TwoColorTimeline.ENTRIES]); - - } else - throw new Exception("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")"); - } - } - } - - // Bone timelines. - if (map.ContainsKey("bones")) { - foreach (KeyValuePair entry in (Dictionary)map["bones"]) { - String boneName = entry.Key; - int boneIndex = skeletonData.FindBoneIndex(boneName); - if (boneIndex == -1) throw new Exception("Bone not found: " + boneName); - var timelineMap = (Dictionary)entry.Value; - foreach (KeyValuePair timelineEntry in timelineMap) { - var values = (List)timelineEntry.Value; - var timelineName = (String)timelineEntry.Key; - if (timelineName == "rotate") { - var timeline = new RotateTimeline(values.Count); - timeline.boneIndex = boneIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - timeline.SetFrame(frameIndex, (float)valueMap["time"], (float)valueMap["angle"]); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * RotateTimeline.ENTRIES]); - - } else if (timelineName == "translate" || timelineName == "scale" || timelineName == "shear") { - TranslateTimeline timeline; - float timelineScale = 1; - if (timelineName == "scale") - timeline = new ScaleTimeline(values.Count); - else if (timelineName == "shear") - timeline = new ShearTimeline(values.Count); - else { - timeline = new TranslateTimeline(values.Count); - timelineScale = scale; - } - timeline.boneIndex = boneIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - float x = GetFloat(valueMap, "x", 0); - float y = GetFloat(valueMap, "y", 0); - timeline.SetFrame(frameIndex, time, x * timelineScale, y * timelineScale); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TranslateTimeline.ENTRIES]); - - } else - throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")"); - } - } - } - - // IK constraint timelines. - if (map.ContainsKey("ik")) { - foreach (KeyValuePair constraintMap in (Dictionary)map["ik"]) { - IkConstraintData constraint = skeletonData.FindIkConstraint(constraintMap.Key); - var values = (List)constraintMap.Value; - var timeline = new IkConstraintTimeline(values.Count); - timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(constraint); - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - float mix = GetFloat(valueMap, "mix", 1); - bool bendPositive = GetBoolean(valueMap, "bendPositive", true); - timeline.SetFrame(frameIndex, time, mix, bendPositive ? 1 : -1); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * IkConstraintTimeline.ENTRIES]); - } - } - - // Transform constraint timelines. - if (map.ContainsKey("transform")) { - foreach (KeyValuePair constraintMap in (Dictionary)map["transform"]) { - TransformConstraintData constraint = skeletonData.FindTransformConstraint(constraintMap.Key); - var values = (List)constraintMap.Value; - var timeline = new TransformConstraintTimeline(values.Count); - timeline.transformConstraintIndex = skeletonData.transformConstraints.IndexOf(constraint); - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - float rotateMix = GetFloat(valueMap, "rotateMix", 1); - float translateMix = GetFloat(valueMap, "translateMix", 1); - float scaleMix = GetFloat(valueMap, "scaleMix", 1); - float shearMix = GetFloat(valueMap, "shearMix", 1); - timeline.SetFrame(frameIndex, time, rotateMix, translateMix, scaleMix, shearMix); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TransformConstraintTimeline.ENTRIES]); - } - } - - // Path constraint timelines. - if (map.ContainsKey("paths")) { - foreach (KeyValuePair constraintMap in (Dictionary)map["paths"]) { - int index = skeletonData.FindPathConstraintIndex(constraintMap.Key); - if (index == -1) throw new Exception("Path constraint not found: " + constraintMap.Key); - PathConstraintData data = skeletonData.pathConstraints.Items[index]; - var timelineMap = (Dictionary)constraintMap.Value; - foreach (KeyValuePair timelineEntry in timelineMap) { - var values = (List)timelineEntry.Value; - var timelineName = (String)timelineEntry.Key; - if (timelineName == "position" || timelineName == "spacing") { - PathConstraintPositionTimeline timeline; - float timelineScale = 1; - if (timelineName == "spacing") { - timeline = new PathConstraintSpacingTimeline(values.Count); - if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale; - } - else { - timeline = new PathConstraintPositionTimeline(values.Count); - if (data.positionMode == PositionMode.Fixed) timelineScale = scale; - } - timeline.pathConstraintIndex = index; - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - timeline.SetFrame(frameIndex, (float)valueMap["time"], GetFloat(valueMap, timelineName, 0) * timelineScale); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintPositionTimeline.ENTRIES]); - } - else if (timelineName == "mix") { - PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(values.Count); - timeline.pathConstraintIndex = index; - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - timeline.SetFrame(frameIndex, (float)valueMap["time"], GetFloat(valueMap, "rotateMix", 1), GetFloat(valueMap, "translateMix", 1)); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintMixTimeline.ENTRIES]); - } - } - } - } - - // Deform timelines. - if (map.ContainsKey("deform")) { - foreach (KeyValuePair deformMap in (Dictionary)map["deform"]) { - Skin skin = skeletonData.FindSkin(deformMap.Key); - foreach (KeyValuePair slotMap in (Dictionary)deformMap.Value) { - int slotIndex = skeletonData.FindSlotIndex(slotMap.Key); - if (slotIndex == -1) throw new Exception("Slot not found: " + slotMap.Key); - foreach (KeyValuePair timelineMap in (Dictionary)slotMap.Value) { - var values = (List)timelineMap.Value; - VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, timelineMap.Key); - if (attachment == null) throw new Exception("Deform attachment not found: " + timelineMap.Key); - bool weighted = attachment.bones != null; - float[] vertices = attachment.vertices; - int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length; - - var timeline = new DeformTimeline(values.Count); - timeline.slotIndex = slotIndex; - timeline.attachment = attachment; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float[] deform; - if (!valueMap.ContainsKey("vertices")) { - deform = weighted ? new float[deformLength] : vertices; - } else { - deform = new float[deformLength]; - int start = GetInt(valueMap, "offset", 0); - float[] verticesValue = GetFloatArray(valueMap, "vertices", 1); - Array.Copy(verticesValue, 0, deform, start, verticesValue.Length); - if (scale != 1) { - for (int i = start, n = i + verticesValue.Length; i < n; i++) - deform[i] *= scale; - } - - if (!weighted) { - for (int i = 0; i < deformLength; i++) - deform[i] += vertices[i]; - } - } - - timeline.SetFrame(frameIndex, (float)valueMap["time"], deform); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); - } - } - } - } - - // Draw order timeline. - if (map.ContainsKey("drawOrder") || map.ContainsKey("draworder")) { - var values = (List)map[map.ContainsKey("drawOrder") ? "drawOrder" : "draworder"]; - var timeline = new DrawOrderTimeline(values.Count); - int slotCount = skeletonData.slots.Count; - int frameIndex = 0; - foreach (Dictionary drawOrderMap in values) { - int[] drawOrder = null; - if (drawOrderMap.ContainsKey("offsets")) { - drawOrder = new int[slotCount]; - for (int i = slotCount - 1; i >= 0; i--) - drawOrder[i] = -1; - var offsets = (List)drawOrderMap["offsets"]; - int[] unchanged = new int[slotCount - offsets.Count]; - int originalIndex = 0, unchangedIndex = 0; - foreach (Dictionary offsetMap in offsets) { - int slotIndex = skeletonData.FindSlotIndex((String)offsetMap["slot"]); - if (slotIndex == -1) throw new Exception("Slot not found: " + offsetMap["slot"]); - // Collect unchanged items. - while (originalIndex != slotIndex) - unchanged[unchangedIndex++] = originalIndex++; - // Set changed items. - int index = originalIndex + (int)(float)offsetMap["offset"]; - drawOrder[index] = originalIndex++; - } - // Collect remaining unchanged items. - while (originalIndex < slotCount) - unchanged[unchangedIndex++] = originalIndex++; - // Fill in unchanged items. - for (int i = slotCount - 1; i >= 0; i--) - if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex]; - } - timeline.SetFrame(frameIndex++, (float)drawOrderMap["time"], drawOrder); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); - } - - // Event timeline. - if (map.ContainsKey("events")) { - var eventsMap = (List)map["events"]; - var timeline = new EventTimeline(eventsMap.Count); - int frameIndex = 0; - foreach (Dictionary eventMap in eventsMap) { - EventData eventData = skeletonData.FindEvent((String)eventMap["name"]); - if (eventData == null) throw new Exception("Event not found: " + eventMap["name"]); - var e = new Event((float)eventMap["time"], eventData); - e.Int = GetInt(eventMap, "int", eventData.Int); - e.Float = GetFloat(eventMap, "float", eventData.Float); - e.String = GetString(eventMap, "string", eventData.String); - timeline.SetFrame(frameIndex++, e); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); - } - - timelines.TrimExcess(); - skeletonData.animations.Add(new Animation(name, timelines, duration)); - } - - static void ReadCurve (Dictionary valueMap, CurveTimeline timeline, int frameIndex) { - if (!valueMap.ContainsKey("curve")) - return; - Object curveObject = valueMap["curve"]; - if (curveObject.Equals("stepped")) - timeline.SetStepped(frameIndex); - else { - var curve = curveObject as List; - if (curve != null) - timeline.SetCurve(frameIndex, (float)curve[0], (float)curve[1], (float)curve[2], (float)curve[3]); - } - } - - internal class LinkedMesh { - internal String parent, skin; - internal int slotIndex; - internal MeshAttachment mesh; - - public LinkedMesh (MeshAttachment mesh, String skin, int slotIndex, String parent) { - this.mesh = mesh; - this.skin = skin; - this.slotIndex = slotIndex; - this.parent = parent; - } - } - - static float[] GetFloatArray(Dictionary map, String name, float scale) { - var list = (List)map[name]; - var values = new float[list.Count]; - if (scale == 1) { - for (int i = 0, n = list.Count; i < n; i++) - values[i] = (float)list[i]; - } else { - for (int i = 0, n = list.Count; i < n; i++) - values[i] = (float)list[i] * scale; - } - return values; - } - - static int[] GetIntArray(Dictionary map, String name) { - var list = (List)map[name]; - var values = new int[list.Count]; - for (int i = 0, n = list.Count; i < n; i++) - values[i] = (int)(float)list[i]; - return values; - } - - static float GetFloat(Dictionary map, String name, float defaultValue) { - if (!map.ContainsKey(name)) - return defaultValue; - return (float)map[name]; - } - - static int GetInt(Dictionary map, String name, int defaultValue) { - if (!map.ContainsKey(name)) - return defaultValue; - return (int)(float)map[name]; - } - - static bool GetBoolean(Dictionary map, String name, bool defaultValue) { - if (!map.ContainsKey(name)) - return defaultValue; - return (bool)map[name]; - } - - static String GetString(Dictionary map, String name, String defaultValue) { - if (!map.ContainsKey(name)) - return defaultValue; - return (String)map[name]; - } +#else + using (var reader = new StreamReader(new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))) + { +#endif + SkeletonData skeletonData = ReadSkeletonData(reader); + skeletonData.name = Path.GetFileNameWithoutExtension(path); + return skeletonData; + } + } +#endif - static float ToColor(String hexString, int colorIndex, int expectedLength = 8) { - if (hexString.Length != expectedLength) - throw new ArgumentException("Color hexidecimal length must be " + expectedLength + ", recieved: " + hexString, "hexString"); - return Convert.ToInt32(hexString.Substring(colorIndex * 2, 2), 16) / (float)255; - } - } + public SkeletonData ReadSkeletonData(TextReader reader) + { + if (reader == null) throw new ArgumentNullException("reader", "reader cannot be null."); + + var scale = this.Scale; + var skeletonData = new SkeletonData(); + + var root = Json.Deserialize(reader) as Dictionary; + if (root == null) throw new Exception("Invalid JSON."); + + // Skeleton. + if (root.ContainsKey("skeleton")) + { + var skeletonMap = (Dictionary)root["skeleton"]; + skeletonData.hash = (String)skeletonMap["hash"]; + skeletonData.version = (String)skeletonMap["spine"]; + skeletonData.width = GetFloat(skeletonMap, "width", 0); + skeletonData.height = GetFloat(skeletonMap, "height", 0); + skeletonData.fps = GetFloat(skeletonMap, "fps", 0); + skeletonData.imagesPath = GetString(skeletonMap, "images", null); + } + + // Bones. + foreach (Dictionary boneMap in (List)root["bones"]) + { + BoneData parent = null; + if (boneMap.ContainsKey("parent")) + { + parent = skeletonData.FindBone((String)boneMap["parent"]); + if (parent == null) + throw new Exception("Parent bone not found: " + boneMap["parent"]); + } + var data = new BoneData(skeletonData.Bones.Count, (String)boneMap["name"], parent); + data.length = GetFloat(boneMap, "length", 0) * scale; + data.x = GetFloat(boneMap, "x", 0) * scale; + data.y = GetFloat(boneMap, "y", 0) * scale; + data.rotation = GetFloat(boneMap, "rotation", 0); + data.scaleX = GetFloat(boneMap, "scaleX", 1); + data.scaleY = GetFloat(boneMap, "scaleY", 1); + data.shearX = GetFloat(boneMap, "shearX", 0); + data.shearY = GetFloat(boneMap, "shearY", 0); + + string tm = GetString(boneMap, "transform", TransformMode.Normal.ToString()); + data.transformMode = (TransformMode)Enum.Parse(typeof(TransformMode), tm, true); + + skeletonData.bones.Add(data); + } + + // Slots. + if (root.ContainsKey("slots")) + { + foreach (Dictionary slotMap in (List)root["slots"]) + { + var slotName = (String)slotMap["name"]; + var boneName = (String)slotMap["bone"]; + BoneData boneData = skeletonData.FindBone(boneName); + if (boneData == null) throw new Exception("Slot bone not found: " + boneName); + var data = new SlotData(skeletonData.Slots.Count, slotName, boneData); + + if (slotMap.ContainsKey("color")) + { + var color = (String)slotMap["color"]; + data.r = ToColor(color, 0); + data.g = ToColor(color, 1); + data.b = ToColor(color, 2); + data.a = ToColor(color, 3); + } + + if (slotMap.ContainsKey("dark")) + { + var color2 = (String)slotMap["dark"]; + data.r2 = ToColor(color2, 0, 6); // expectedLength = 6. ie. "RRGGBB" + data.g2 = ToColor(color2, 1, 6); + data.b2 = ToColor(color2, 2, 6); + data.hasSecondColor = true; + } + + data.attachmentName = GetString(slotMap, "attachment", null); + if (slotMap.ContainsKey("blend")) + data.blendMode = (BlendMode)Enum.Parse(typeof(BlendMode), (String)slotMap["blend"], true); + else + data.blendMode = BlendMode.Normal; + skeletonData.slots.Add(data); + } + } + + // IK constraints. + if (root.ContainsKey("ik")) + { + foreach (Dictionary constraintMap in (List)root["ik"]) + { + IkConstraintData data = new IkConstraintData((String)constraintMap["name"]); + data.order = GetInt(constraintMap, "order", 0); + + foreach (String boneName in (List)constraintMap["bones"]) + { + BoneData bone = skeletonData.FindBone(boneName); + if (bone == null) throw new Exception("IK constraint bone not found: " + boneName); + data.bones.Add(bone); + } + + String targetName = (String)constraintMap["target"]; + data.target = skeletonData.FindBone(targetName); + if (data.target == null) throw new Exception("Target bone not found: " + targetName); + + data.bendDirection = GetBoolean(constraintMap, "bendPositive", true) ? 1 : -1; + data.mix = GetFloat(constraintMap, "mix", 1); + + skeletonData.ikConstraints.Add(data); + } + } + + // Transform constraints. + if (root.ContainsKey("transform")) + { + foreach (Dictionary constraintMap in (List)root["transform"]) + { + TransformConstraintData data = new TransformConstraintData((String)constraintMap["name"]); + data.order = GetInt(constraintMap, "order", 0); + + foreach (String boneName in (List)constraintMap["bones"]) + { + BoneData bone = skeletonData.FindBone(boneName); + if (bone == null) throw new Exception("Transform constraint bone not found: " + boneName); + data.bones.Add(bone); + } + + String targetName = (String)constraintMap["target"]; + data.target = skeletonData.FindBone(targetName); + if (data.target == null) throw new Exception("Target bone not found: " + targetName); + + data.local = GetBoolean(constraintMap, "local", false); + data.relative = GetBoolean(constraintMap, "relative", false); + + data.offsetRotation = GetFloat(constraintMap, "rotation", 0); + data.offsetX = GetFloat(constraintMap, "x", 0) * scale; + data.offsetY = GetFloat(constraintMap, "y", 0) * scale; + data.offsetScaleX = GetFloat(constraintMap, "scaleX", 0); + data.offsetScaleY = GetFloat(constraintMap, "scaleY", 0); + data.offsetShearY = GetFloat(constraintMap, "shearY", 0); + + data.rotateMix = GetFloat(constraintMap, "rotateMix", 1); + data.translateMix = GetFloat(constraintMap, "translateMix", 1); + data.scaleMix = GetFloat(constraintMap, "scaleMix", 1); + data.shearMix = GetFloat(constraintMap, "shearMix", 1); + + skeletonData.transformConstraints.Add(data); + } + } + + // Path constraints. + if (root.ContainsKey("path")) + { + foreach (Dictionary constraintMap in (List)root["path"]) + { + PathConstraintData data = new PathConstraintData((String)constraintMap["name"]); + data.order = GetInt(constraintMap, "order", 0); + + foreach (String boneName in (List)constraintMap["bones"]) + { + BoneData bone = skeletonData.FindBone(boneName); + if (bone == null) throw new Exception("Path bone not found: " + boneName); + data.bones.Add(bone); + } + + String targetName = (String)constraintMap["target"]; + data.target = skeletonData.FindSlot(targetName); + if (data.target == null) throw new Exception("Target slot not found: " + targetName); + + data.positionMode = (PositionMode)Enum.Parse(typeof(PositionMode), GetString(constraintMap, "positionMode", "percent"), true); + data.spacingMode = (SpacingMode)Enum.Parse(typeof(SpacingMode), GetString(constraintMap, "spacingMode", "length"), true); + data.rotateMode = (RotateMode)Enum.Parse(typeof(RotateMode), GetString(constraintMap, "rotateMode", "tangent"), true); + data.offsetRotation = GetFloat(constraintMap, "rotation", 0); + data.position = GetFloat(constraintMap, "position", 0); + if (data.positionMode == PositionMode.Fixed) data.position *= scale; + data.spacing = GetFloat(constraintMap, "spacing", 0); + if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale; + data.rotateMix = GetFloat(constraintMap, "rotateMix", 1); + data.translateMix = GetFloat(constraintMap, "translateMix", 1); + + skeletonData.pathConstraints.Add(data); + } + } + + // Skins. + if (root.ContainsKey("skins")) + { + foreach (KeyValuePair skinMap in (Dictionary)root["skins"]) + { + var skin = new Skin(skinMap.Key); + foreach (KeyValuePair slotEntry in (Dictionary)skinMap.Value) + { + int slotIndex = skeletonData.FindSlotIndex(slotEntry.Key); + foreach (KeyValuePair entry in ((Dictionary)slotEntry.Value)) + { + try + { + Attachment attachment = ReadAttachment((Dictionary)entry.Value, skin, slotIndex, entry.Key, skeletonData); + if (attachment != null) skin.AddAttachment(slotIndex, entry.Key, attachment); + } + catch (Exception e) + { + throw new Exception("Error reading attachment: " + entry.Key + ", skin: " + skin, e); + } + } + } + skeletonData.skins.Add(skin); + if (skin.name == "default") skeletonData.defaultSkin = skin; + } + } + + // Linked meshes. + for (int i = 0, n = linkedMeshes.Count; i < n; i++) + { + LinkedMesh linkedMesh = linkedMeshes[i]; + Skin skin = linkedMesh.skin == null ? skeletonData.defaultSkin : skeletonData.FindSkin(linkedMesh.skin); + if (skin == null) throw new Exception("Slot not found: " + linkedMesh.skin); + Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); + if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); + linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; + linkedMesh.mesh.UpdateUVs(); + } + linkedMeshes.Clear(); + + // Events. + if (root.ContainsKey("events")) + { + foreach (KeyValuePair entry in (Dictionary)root["events"]) + { + var entryMap = (Dictionary)entry.Value; + var data = new EventData(entry.Key); + data.Int = GetInt(entryMap, "int", 0); + data.Float = GetFloat(entryMap, "float", 0); + data.String = GetString(entryMap, "string", string.Empty); + skeletonData.events.Add(data); + } + } + + // Animations. + if (root.ContainsKey("animations")) + { + foreach (KeyValuePair entry in (Dictionary)root["animations"]) + { + try + { + ReadAnimation((Dictionary)entry.Value, entry.Key, skeletonData); + } + catch (Exception e) + { + throw new Exception("Error reading animation: " + entry.Key, e); + } + } + } + + skeletonData.bones.TrimExcess(); + skeletonData.slots.TrimExcess(); + skeletonData.skins.TrimExcess(); + skeletonData.events.TrimExcess(); + skeletonData.animations.TrimExcess(); + skeletonData.ikConstraints.TrimExcess(); + return skeletonData; + } + + private Attachment ReadAttachment(Dictionary map, Skin skin, int slotIndex, String name, SkeletonData skeletonData) + { + var scale = this.Scale; + name = GetString(map, "name", name); + + var typeName = GetString(map, "type", "region"); + if (typeName == "skinnedmesh") typeName = "weightedmesh"; + if (typeName == "weightedmesh") typeName = "mesh"; + if (typeName == "weightedlinkedmesh") typeName = "linkedmesh"; + var type = (AttachmentType)Enum.Parse(typeof(AttachmentType), typeName, true); + + String path = GetString(map, "path", name); + + switch (type) + { + case AttachmentType.Region: + RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); + if (region == null) return null; + region.Path = path; + region.x = GetFloat(map, "x", 0) * scale; + region.y = GetFloat(map, "y", 0) * scale; + region.scaleX = GetFloat(map, "scaleX", 1); + region.scaleY = GetFloat(map, "scaleY", 1); + region.rotation = GetFloat(map, "rotation", 0); + region.width = GetFloat(map, "width", 32) * scale; + region.height = GetFloat(map, "height", 32) * scale; + region.UpdateOffset(); + + if (map.ContainsKey("color")) + { + var color = (String)map["color"]; + region.r = ToColor(color, 0); + region.g = ToColor(color, 1); + region.b = ToColor(color, 2); + region.a = ToColor(color, 3); + } + + region.UpdateOffset(); + return region; + case AttachmentType.Boundingbox: + BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); + if (box == null) return null; + ReadVertices(map, box, GetInt(map, "vertexCount", 0) << 1); + return box; + case AttachmentType.Mesh: + case AttachmentType.Linkedmesh: + { + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); + if (mesh == null) return null; + mesh.Path = path; + + if (map.ContainsKey("color")) + { + var color = (String)map["color"]; + mesh.r = ToColor(color, 0); + mesh.g = ToColor(color, 1); + mesh.b = ToColor(color, 2); + mesh.a = ToColor(color, 3); + } + + mesh.Width = GetFloat(map, "width", 0) * scale; + mesh.Height = GetFloat(map, "height", 0) * scale; + + String parent = GetString(map, "parent", null); + if (parent != null) + { + mesh.InheritDeform = GetBoolean(map, "deform", true); + linkedMeshes.Add(new LinkedMesh(mesh, GetString(map, "skin", null), slotIndex, parent)); + return mesh; + } + + float[] uvs = GetFloatArray(map, "uvs", 1); + ReadVertices(map, mesh, uvs.Length); + mesh.triangles = GetIntArray(map, "triangles"); + mesh.regionUVs = uvs; + mesh.UpdateUVs(); + + if (map.ContainsKey("hull")) mesh.HullLength = GetInt(map, "hull", 0) * 2; + if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges"); + return mesh; + } + case AttachmentType.Path: + { + PathAttachment pathAttachment = attachmentLoader.NewPathAttachment(skin, name); + if (pathAttachment == null) return null; + pathAttachment.closed = GetBoolean(map, "closed", false); + pathAttachment.constantSpeed = GetBoolean(map, "constantSpeed", true); + + int vertexCount = GetInt(map, "vertexCount", 0); + ReadVertices(map, pathAttachment, vertexCount << 1); + + // potential BOZO see Java impl + pathAttachment.lengths = GetFloatArray(map, "lengths", scale); + return pathAttachment; + } + case AttachmentType.Point: + { + PointAttachment point = attachmentLoader.NewPointAttachment(skin, name); + if (point == null) return null; + point.x = GetFloat(map, "x", 0) * scale; + point.y = GetFloat(map, "y", 0) * scale; + point.rotation = GetFloat(map, "rotation", 0); + + //string color = GetString(map, "color", null); + //if (color != null) point.color = color; + return point; + } + case AttachmentType.Clipping: + { + ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name); + if (clip == null) return null; + + string end = GetString(map, "end", null); + if (end != null) + { + SlotData slot = skeletonData.FindSlot(end); + if (slot == null) throw new Exception("Clipping end slot not found: " + end); + clip.EndSlot = slot; + } + + ReadVertices(map, clip, GetInt(map, "vertexCount", 0) << 1); + + //string color = GetString(map, "color", null); + // if (color != null) clip.color = color; + return clip; + } + } + return null; + } + + private void ReadVertices(Dictionary map, VertexAttachment attachment, int verticesLength) + { + attachment.WorldVerticesLength = verticesLength; + float[] vertices = GetFloatArray(map, "vertices", 1); + float scale = Scale; + if (verticesLength == vertices.Length) + { + if (scale != 1) + { + for (int i = 0; i < vertices.Length; i++) + { + vertices[i] *= scale; + } + } + attachment.vertices = vertices; + return; + } + ExposedList weights = new ExposedList(verticesLength * 3 * 3); + ExposedList bones = new ExposedList(verticesLength * 3); + for (int i = 0, n = vertices.Length; i < n;) + { + int boneCount = (int)vertices[i++]; + bones.Add(boneCount); + for (int nn = i + boneCount * 4; i < nn; i += 4) + { + bones.Add((int)vertices[i]); + weights.Add(vertices[i + 1] * this.Scale); + weights.Add(vertices[i + 2] * this.Scale); + weights.Add(vertices[i + 3]); + } + } + attachment.bones = bones.ToArray(); + attachment.vertices = weights.ToArray(); + } + + private void ReadAnimation(Dictionary map, String name, SkeletonData skeletonData) + { + var scale = this.Scale; + var timelines = new ExposedList(); + float duration = 0; + + // Slot timelines. + if (map.ContainsKey("slots")) + { + foreach (KeyValuePair entry in (Dictionary)map["slots"]) + { + String slotName = entry.Key; + int slotIndex = skeletonData.FindSlotIndex(slotName); + var timelineMap = (Dictionary)entry.Value; + foreach (KeyValuePair timelineEntry in timelineMap) + { + var values = (List)timelineEntry.Value; + var timelineName = timelineEntry.Key; + if (timelineName == "attachment") + { + var timeline = new AttachmentTimeline(values.Count); + timeline.slotIndex = slotIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + timeline.SetFrame(frameIndex++, time, (String)valueMap["name"]); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); + + } + else if (timelineName == "color") + { + var timeline = new ColorTimeline(values.Count); + timeline.slotIndex = slotIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + string c = (string)valueMap["color"]; + timeline.SetFrame(frameIndex, time, ToColor(c, 0), ToColor(c, 1), ToColor(c, 2), ToColor(c, 3)); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * ColorTimeline.ENTRIES]); + + } + else if (timelineName == "twoColor") + { + var timeline = new TwoColorTimeline(values.Count); + timeline.slotIndex = slotIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + string light = (string)valueMap["light"]; + string dark = (string)valueMap["dark"]; + timeline.SetFrame(frameIndex, time, ToColor(light, 0), ToColor(light, 1), ToColor(light, 2), ToColor(light, 3), + ToColor(dark, 0, 6), ToColor(dark, 1, 6), ToColor(dark, 2, 6)); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TwoColorTimeline.ENTRIES]); + + } + else + throw new Exception("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")"); + } + } + } + + // Bone timelines. + if (map.ContainsKey("bones")) + { + foreach (KeyValuePair entry in (Dictionary)map["bones"]) + { + String boneName = entry.Key; + int boneIndex = skeletonData.FindBoneIndex(boneName); + if (boneIndex == -1) throw new Exception("Bone not found: " + boneName); + var timelineMap = (Dictionary)entry.Value; + foreach (KeyValuePair timelineEntry in timelineMap) + { + var values = (List)timelineEntry.Value; + var timelineName = timelineEntry.Key; + if (timelineName == "rotate") + { + var timeline = new RotateTimeline(values.Count); + timeline.boneIndex = boneIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + timeline.SetFrame(frameIndex, (float)valueMap["time"], (float)valueMap["angle"]); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * RotateTimeline.ENTRIES]); + + } + else if (timelineName == "translate" || timelineName == "scale" || timelineName == "shear") + { + TranslateTimeline timeline; + float timelineScale = 1; + if (timelineName == "scale") + timeline = new ScaleTimeline(values.Count); + else if (timelineName == "shear") + timeline = new ShearTimeline(values.Count); + else + { + timeline = new TranslateTimeline(values.Count); + timelineScale = scale; + } + timeline.boneIndex = boneIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + float x = GetFloat(valueMap, "x", 0); + float y = GetFloat(valueMap, "y", 0); + timeline.SetFrame(frameIndex, time, x * timelineScale, y * timelineScale); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TranslateTimeline.ENTRIES]); + + } + else + throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")"); + } + } + } + + // IK constraint timelines. + if (map.ContainsKey("ik")) + { + foreach (KeyValuePair constraintMap in (Dictionary)map["ik"]) + { + IkConstraintData constraint = skeletonData.FindIkConstraint(constraintMap.Key); + var values = (List)constraintMap.Value; + var timeline = new IkConstraintTimeline(values.Count); + timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(constraint); + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + float mix = GetFloat(valueMap, "mix", 1); + bool bendPositive = GetBoolean(valueMap, "bendPositive", true); + timeline.SetFrame(frameIndex, time, mix, bendPositive ? 1 : -1); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * IkConstraintTimeline.ENTRIES]); + } + } + + // Transform constraint timelines. + if (map.ContainsKey("transform")) + { + foreach (KeyValuePair constraintMap in (Dictionary)map["transform"]) + { + TransformConstraintData constraint = skeletonData.FindTransformConstraint(constraintMap.Key); + var values = (List)constraintMap.Value; + var timeline = new TransformConstraintTimeline(values.Count); + timeline.transformConstraintIndex = skeletonData.transformConstraints.IndexOf(constraint); + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + float rotateMix = GetFloat(valueMap, "rotateMix", 1); + float translateMix = GetFloat(valueMap, "translateMix", 1); + float scaleMix = GetFloat(valueMap, "scaleMix", 1); + float shearMix = GetFloat(valueMap, "shearMix", 1); + timeline.SetFrame(frameIndex, time, rotateMix, translateMix, scaleMix, shearMix); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TransformConstraintTimeline.ENTRIES]); + } + } + + // Path constraint timelines. + if (map.ContainsKey("paths")) + { + foreach (KeyValuePair constraintMap in (Dictionary)map["paths"]) + { + int index = skeletonData.FindPathConstraintIndex(constraintMap.Key); + if (index == -1) throw new Exception("Path constraint not found: " + constraintMap.Key); + PathConstraintData data = skeletonData.pathConstraints.Items[index]; + var timelineMap = (Dictionary)constraintMap.Value; + foreach (KeyValuePair timelineEntry in timelineMap) + { + var values = (List)timelineEntry.Value; + var timelineName = timelineEntry.Key; + if (timelineName == "position" || timelineName == "spacing") + { + PathConstraintPositionTimeline timeline; + float timelineScale = 1; + if (timelineName == "spacing") + { + timeline = new PathConstraintSpacingTimeline(values.Count); + if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale; + } + else + { + timeline = new PathConstraintPositionTimeline(values.Count); + if (data.positionMode == PositionMode.Fixed) timelineScale = scale; + } + timeline.pathConstraintIndex = index; + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + timeline.SetFrame(frameIndex, (float)valueMap["time"], GetFloat(valueMap, timelineName, 0) * timelineScale); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintPositionTimeline.ENTRIES]); + } + else if (timelineName == "mix") + { + PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(values.Count); + timeline.pathConstraintIndex = index; + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + timeline.SetFrame(frameIndex, (float)valueMap["time"], GetFloat(valueMap, "rotateMix", 1), GetFloat(valueMap, "translateMix", 1)); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintMixTimeline.ENTRIES]); + } + } + } + } + + // Deform timelines. + if (map.ContainsKey("deform")) + { + foreach (KeyValuePair deformMap in (Dictionary)map["deform"]) + { + Skin skin = skeletonData.FindSkin(deformMap.Key); + foreach (KeyValuePair slotMap in (Dictionary)deformMap.Value) + { + int slotIndex = skeletonData.FindSlotIndex(slotMap.Key); + if (slotIndex == -1) throw new Exception("Slot not found: " + slotMap.Key); + foreach (KeyValuePair timelineMap in (Dictionary)slotMap.Value) + { + var values = (List)timelineMap.Value; + VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, timelineMap.Key); + if (attachment == null) throw new Exception("Deform attachment not found: " + timelineMap.Key); + bool weighted = attachment.bones != null; + float[] vertices = attachment.vertices; + int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length; + + var timeline = new DeformTimeline(values.Count); + timeline.slotIndex = slotIndex; + timeline.attachment = attachment; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float[] deform; + if (!valueMap.ContainsKey("vertices")) + { + deform = weighted ? new float[deformLength] : vertices; + } + else + { + deform = new float[deformLength]; + int start = GetInt(valueMap, "offset", 0); + float[] verticesValue = GetFloatArray(valueMap, "vertices", 1); + Array.Copy(verticesValue, 0, deform, start, verticesValue.Length); + if (scale != 1) + { + for (int i = start, n = i + verticesValue.Length; i < n; i++) + deform[i] *= scale; + } + + if (!weighted) + { + for (int i = 0; i < deformLength; i++) + deform[i] += vertices[i]; + } + } + + timeline.SetFrame(frameIndex, (float)valueMap["time"], deform); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); + } + } + } + } + + // Draw order timeline. + if (map.ContainsKey("drawOrder") || map.ContainsKey("draworder")) + { + var values = (List)map[map.ContainsKey("drawOrder") ? "drawOrder" : "draworder"]; + var timeline = new DrawOrderTimeline(values.Count); + int slotCount = skeletonData.slots.Count; + int frameIndex = 0; + foreach (Dictionary drawOrderMap in values) + { + int[] drawOrder = null; + if (drawOrderMap.ContainsKey("offsets")) + { + drawOrder = new int[slotCount]; + for (int i = slotCount - 1; i >= 0; i--) + drawOrder[i] = -1; + var offsets = (List)drawOrderMap["offsets"]; + int[] unchanged = new int[slotCount - offsets.Count]; + int originalIndex = 0, unchangedIndex = 0; + foreach (Dictionary offsetMap in offsets) + { + int slotIndex = skeletonData.FindSlotIndex((String)offsetMap["slot"]); + if (slotIndex == -1) throw new Exception("Slot not found: " + offsetMap["slot"]); + // Collect unchanged items. + while (originalIndex != slotIndex) + unchanged[unchangedIndex++] = originalIndex++; + // Set changed items. + int index = originalIndex + (int)(float)offsetMap["offset"]; + drawOrder[index] = originalIndex++; + } + // Collect remaining unchanged items. + while (originalIndex < slotCount) + unchanged[unchangedIndex++] = originalIndex++; + // Fill in unchanged items. + for (int i = slotCount - 1; i >= 0; i--) + if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex]; + } + timeline.SetFrame(frameIndex++, (float)drawOrderMap["time"], drawOrder); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); + } + + // Event timeline. + if (map.ContainsKey("events")) + { + var eventsMap = (List)map["events"]; + var timeline = new EventTimeline(eventsMap.Count); + int frameIndex = 0; + foreach (Dictionary eventMap in eventsMap) + { + EventData eventData = skeletonData.FindEvent((String)eventMap["name"]); + if (eventData == null) throw new Exception("Event not found: " + eventMap["name"]); + var e = new Event((float)eventMap["time"], eventData); + e.Int = GetInt(eventMap, "int", eventData.Int); + e.Float = GetFloat(eventMap, "float", eventData.Float); + e.String = GetString(eventMap, "string", eventData.String); + timeline.SetFrame(frameIndex++, e); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); + } + + timelines.TrimExcess(); + skeletonData.animations.Add(new Animation(name, timelines, duration)); + } + + static void ReadCurve(Dictionary valueMap, CurveTimeline timeline, int frameIndex) + { + if (!valueMap.ContainsKey("curve")) + return; + Object curveObject = valueMap["curve"]; + if (curveObject.Equals("stepped")) + timeline.SetStepped(frameIndex); + else + { + var curve = curveObject as List; + if (curve != null) + timeline.SetCurve(frameIndex, (float)curve[0], (float)curve[1], (float)curve[2], (float)curve[3]); + } + } + + internal class LinkedMesh + { + internal String parent, skin; + internal int slotIndex; + internal MeshAttachment mesh; + + public LinkedMesh(MeshAttachment mesh, String skin, int slotIndex, String parent) + { + this.mesh = mesh; + this.skin = skin; + this.slotIndex = slotIndex; + this.parent = parent; + } + } + + static float[] GetFloatArray(Dictionary map, String name, float scale) + { + var list = (List)map[name]; + var values = new float[list.Count]; + if (scale == 1) + { + for (int i = 0, n = list.Count; i < n; i++) + values[i] = (float)list[i]; + } + else + { + for (int i = 0, n = list.Count; i < n; i++) + values[i] = (float)list[i] * scale; + } + return values; + } + + static int[] GetIntArray(Dictionary map, String name) + { + var list = (List)map[name]; + var values = new int[list.Count]; + for (int i = 0, n = list.Count; i < n; i++) + values[i] = (int)(float)list[i]; + return values; + } + + static float GetFloat(Dictionary map, String name, float defaultValue) + { + if (!map.ContainsKey(name)) + return defaultValue; + return (float)map[name]; + } + + static int GetInt(Dictionary map, String name, int defaultValue) + { + if (!map.ContainsKey(name)) + return defaultValue; + return (int)(float)map[name]; + } + + static bool GetBoolean(Dictionary map, String name, bool defaultValue) + { + if (!map.ContainsKey(name)) + return defaultValue; + return (bool)map[name]; + } + + static String GetString(Dictionary map, String name, String defaultValue) + { + if (!map.ContainsKey(name)) + return defaultValue; + return (String)map[name]; + } + + static float ToColor(String hexString, int colorIndex, int expectedLength = 8) + { + if (hexString.Length != expectedLength) + throw new ArgumentException("Color hexidecimal length must be " + expectedLength + ", recieved: " + hexString, "hexString"); + return Convert.ToInt32(hexString.Substring(colorIndex * 2, 2), 16) / (float)255; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Skin.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Skin.cs index e6dc3cb..685a07b 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Skin.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Skin.cs @@ -31,95 +31,111 @@ using System; using System.Collections.Generic; -namespace Spine3_6_39 { - /// Stores attachments by slot index and attachment name. - /// See SkeletonData , Skeleton , and - /// Runtime skins in the Spine Runtimes Guide. - /// - public class Skin { - internal string name; - private Dictionary attachments = - new Dictionary(AttachmentKeyTupleComparer.Instance); +namespace Spine3_6_39 +{ + /// Stores attachments by slot index and attachment name. + /// See SkeletonData , Skeleton , and + /// Runtime skins in the Spine Runtimes Guide. + /// + public class Skin + { + internal string name; + private Dictionary attachments = + new Dictionary(AttachmentKeyTupleComparer.Instance); - public string Name { get { return name; } } - public Dictionary Attachments { get { return attachments; } } + public string Name { get { return name; } } + public Dictionary Attachments { get { return attachments; } } - public Skin (string name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; - } + public Skin(string name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + } - /// 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. - public void AddAttachment (int slotIndex, string name, Attachment attachment) { - if (attachment == null) throw new ArgumentNullException("attachment", "attachment cannot be null."); - attachments[new AttachmentKeyTuple(slotIndex, name)] = attachment; - } + /// 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. + public void AddAttachment(int slotIndex, string name, Attachment attachment) + { + if (attachment == null) throw new ArgumentNullException("attachment", "attachment cannot be null."); + attachments[new AttachmentKeyTuple(slotIndex, name)] = attachment; + } - /// Returns the attachment for the specified slot index and name, or null. - /// May be null. - public Attachment GetAttachment (int slotIndex, string name) { - Attachment attachment; - attachments.TryGetValue(new AttachmentKeyTuple(slotIndex, name), out attachment); - return attachment; - } + /// Returns the attachment for the specified slot index and name, or null. + /// May be null. + public Attachment GetAttachment(int slotIndex, string name) + { + Attachment attachment; + attachments.TryGetValue(new AttachmentKeyTuple(slotIndex, name), out attachment); + return attachment; + } - /// Finds the skin keys for a given slot. The results are added to the passed List(names). - /// The target slotIndex. To find the slot index, use or - /// Found skin key names will be added to this list. - public void FindNamesForSlot (int slotIndex, List names) { - if (names == null) throw new ArgumentNullException("names", "names cannot be null."); - foreach (AttachmentKeyTuple key in attachments.Keys) - if (key.slotIndex == slotIndex) names.Add(key.name); - } + /// Finds the skin keys for a given slot. The results are added to the passed List(names). + /// The target slotIndex. To find the slot index, use or + /// Found skin key names will be added to this list. + public void FindNamesForSlot(int slotIndex, List names) + { + if (names == null) throw new ArgumentNullException("names", "names cannot be null."); + foreach (AttachmentKeyTuple key in attachments.Keys) + if (key.slotIndex == slotIndex) names.Add(key.name); + } - /// Finds the attachments for a given slot. The results are added to the passed List(Attachment). - /// The target slotIndex. To find the slot index, use or - /// Found Attachments will be added to this list. - public void FindAttachmentsForSlot (int slotIndex, List attachments) { - if (attachments == null) throw new ArgumentNullException("attachments", "attachments cannot be null."); - foreach (KeyValuePair entry in this.attachments) - if (entry.Key.slotIndex == slotIndex) attachments.Add(entry.Value); - } + /// Finds the attachments for a given slot. The results are added to the passed List(Attachment). + /// The target slotIndex. To find the slot index, use or + /// Found Attachments will be added to this list. + public void FindAttachmentsForSlot(int slotIndex, List attachments) + { + if (attachments == null) throw new ArgumentNullException("attachments", "attachments cannot be null."); + foreach (KeyValuePair entry in this.attachments) + if (entry.Key.slotIndex == slotIndex) attachments.Add(entry.Value); + } - override public string ToString () { - return name; - } + override public string ToString() + { + return name; + } - /// Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached. - internal void AttachAll (Skeleton skeleton, Skin oldSkin) { - foreach (KeyValuePair entry in oldSkin.attachments) { - int slotIndex = entry.Key.slotIndex; - Slot slot = skeleton.slots.Items[slotIndex]; - if (slot.Attachment == entry.Value) { - Attachment attachment = GetAttachment(slotIndex, entry.Key.name); - if (attachment != null) slot.Attachment = attachment; - } - } - } + /// Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached. + internal void AttachAll(Skeleton skeleton, Skin oldSkin) + { + foreach (KeyValuePair entry in oldSkin.attachments) + { + int slotIndex = entry.Key.slotIndex; + Slot slot = skeleton.slots.Items[slotIndex]; + if (slot.Attachment == entry.Value) + { + Attachment attachment = GetAttachment(slotIndex, entry.Key.name); + if (attachment != null) slot.Attachment = attachment; + } + } + } - public struct AttachmentKeyTuple { - public readonly int slotIndex; - public readonly string name; - internal readonly int nameHashCode; + public struct AttachmentKeyTuple + { + public readonly int slotIndex; + public readonly string name; + internal readonly int nameHashCode; - public AttachmentKeyTuple (int slotIndex, string name) { - this.slotIndex = slotIndex; - this.name = name; - nameHashCode = this.name.GetHashCode(); - } - } + public AttachmentKeyTuple(int slotIndex, string name) + { + this.slotIndex = slotIndex; + this.name = name; + nameHashCode = this.name.GetHashCode(); + } + } - // Avoids boxing in the dictionary. - class AttachmentKeyTupleComparer : IEqualityComparer { - internal static readonly AttachmentKeyTupleComparer Instance = new AttachmentKeyTupleComparer(); + // Avoids boxing in the dictionary. + class AttachmentKeyTupleComparer : IEqualityComparer + { + internal static readonly AttachmentKeyTupleComparer Instance = new AttachmentKeyTupleComparer(); - bool IEqualityComparer.Equals (AttachmentKeyTuple o1, AttachmentKeyTuple o2) { - return o1.slotIndex == o2.slotIndex && o1.nameHashCode == o2.nameHashCode && o1.name == o2.name; - } + bool IEqualityComparer.Equals(AttachmentKeyTuple o1, AttachmentKeyTuple o2) + { + return o1.slotIndex == o2.slotIndex && o1.nameHashCode == o2.nameHashCode && o1.name == o2.name; + } - int IEqualityComparer.GetHashCode (AttachmentKeyTuple o) { - return o.slotIndex; - } - } - } + int IEqualityComparer.GetHashCode(AttachmentKeyTuple o) + { + return o.slotIndex; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Slot.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Slot.cs index e88bcfd..3c57c8c 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Slot.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Slot.cs @@ -30,71 +30,80 @@ using System; -namespace Spine3_6_39 { - public class Slot { - internal SlotData data; - internal Bone bone; - internal float r, g, b, a; - internal float r2, g2, b2; - internal bool hasSecondColor; - internal Attachment attachment; - internal float attachmentTime; - internal ExposedList attachmentVertices = new ExposedList(); +namespace Spine3_6_39 +{ + public class Slot + { + internal SlotData data; + internal Bone bone; + internal float r, g, b, a; + internal float r2, g2, b2; + internal bool hasSecondColor; + internal Attachment attachment; + internal float attachmentTime; + internal ExposedList attachmentVertices = new ExposedList(); - public SlotData Data { get { return data; } } - public Bone Bone { get { return bone; } } - public Skeleton Skeleton { get { return bone.skeleton; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } + public SlotData Data { get { return data; } } + public Bone Bone { get { return bone; } } + public Skeleton Skeleton { get { return bone.skeleton; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } - public float R2 { get { return r2; } set { r2 = value; } } - public float G2 { get { return g2; } set { g2 = value; } } - public float B2 { get { return b2; } set { b2 = value; } } - public bool HasSecondColor { get { return data.hasSecondColor; } set { data.hasSecondColor = value; } } + public float R2 { get { return r2; } set { r2 = value; } } + public float G2 { get { return g2; } set { g2 = value; } } + public float B2 { get { return b2; } set { b2 = value; } } + public bool HasSecondColor { get { return data.hasSecondColor; } set { data.hasSecondColor = value; } } - /// May be null. - public Attachment Attachment { - get { return attachment; } - set { - if (attachment == value) return; - attachment = value; - attachmentTime = bone.skeleton.time; - attachmentVertices.Clear(false); - } - } + /// May be null. + public Attachment Attachment + { + get { return attachment; } + set + { + if (attachment == value) return; + attachment = value; + attachmentTime = bone.skeleton.time; + attachmentVertices.Clear(false); + } + } - public float AttachmentTime { - get { return bone.skeleton.time - attachmentTime; } - set { attachmentTime = bone.skeleton.time - value; } - } + public float AttachmentTime + { + get { return bone.skeleton.time - attachmentTime; } + set { attachmentTime = bone.skeleton.time - value; } + } - public ExposedList AttachmentVertices { get { return attachmentVertices; } set { attachmentVertices = value; } } + public ExposedList AttachmentVertices { get { return attachmentVertices; } set { attachmentVertices = value; } } - public Slot (SlotData data, Bone bone) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); - this.data = data; - this.bone = bone; - SetToSetupPose(); - } + public Slot(SlotData data, Bone bone) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); + this.data = data; + this.bone = bone; + SetToSetupPose(); + } - public void SetToSetupPose () { - r = data.r; - g = data.g; - b = data.b; - a = data.a; - if (data.attachmentName == null) - Attachment = null; - else { - attachment = null; - Attachment = bone.skeleton.GetAttachment(data.index, data.attachmentName); - } - } + public void SetToSetupPose() + { + r = data.r; + g = data.g; + b = data.b; + a = data.a; + if (data.attachmentName == null) + Attachment = null; + else + { + attachment = null; + Attachment = bone.skeleton.GetAttachment(data.index, data.attachmentName); + } + } - override public string ToString () { - return data.name; - } - } + override public string ToString() + { + return data.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/SlotData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/SlotData.cs index 7a4fc97..2c7ff9b 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/SlotData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/SlotData.cs @@ -30,45 +30,49 @@ using System; -namespace Spine3_6_39 { - public class SlotData { - internal int index; - internal string name; - internal BoneData boneData; - internal float r = 1, g = 1, b = 1, a = 1; - internal float r2 = 0, g2 = 0, b2 = 0; - internal bool hasSecondColor = false; - internal string attachmentName; - internal BlendMode blendMode; +namespace Spine3_6_39 +{ + public class SlotData + { + internal int index; + internal string name; + internal BoneData boneData; + internal float r = 1, g = 1, b = 1, a = 1; + internal float r2 = 0, g2 = 0, b2 = 0; + internal bool hasSecondColor = false; + internal string attachmentName; + internal BlendMode blendMode; - public int Index { get { return index; } } - public string Name { get { return name; } } - public BoneData BoneData { get { return boneData; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } + public int Index { get { return index; } } + public string Name { get { return name; } } + public BoneData BoneData { get { return boneData; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } - public float R2 { get { return r2; } set { r2 = value; } } - public float G2 { get { return g2; } set { g2 = value; } } - public float B2 { get { return b2; } set { b2 = value; } } - public bool HasSecondColor { get { return hasSecondColor; } set { hasSecondColor = value; } } + public float R2 { get { return r2; } set { r2 = value; } } + public float G2 { get { return g2; } set { g2 = value; } } + public float B2 { get { return b2; } set { b2 = value; } } + public bool HasSecondColor { get { return hasSecondColor; } set { hasSecondColor = value; } } - /// May be null. - public String AttachmentName { get { return attachmentName; } set { attachmentName = value; } } - public BlendMode BlendMode { get { return blendMode; } set { blendMode = value; } } + /// May be null. + public String AttachmentName { get { return attachmentName; } set { attachmentName = value; } } + public BlendMode BlendMode { get { return blendMode; } set { blendMode = value; } } - public SlotData (int index, String name, BoneData boneData) { - if (index < 0) throw new ArgumentException ("index must be >= 0.", "index"); - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - if (boneData == null) throw new ArgumentNullException("boneData", "boneData cannot be null."); - this.index = index; - this.name = name; - this.boneData = boneData; - } + public SlotData(int index, String name, BoneData boneData) + { + if (index < 0) throw new ArgumentException("index must be >= 0.", "index"); + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + if (boneData == null) throw new ArgumentNullException("boneData", "boneData cannot be null."); + this.index = index; + this.name = name; + this.boneData = boneData; + } - override public string ToString () { - return name; - } - } + override public string ToString() + { + return name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/TransformConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/TransformConstraint.cs index 9b8fcfb..6aed155 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/TransformConstraint.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/TransformConstraint.cs @@ -30,255 +30,286 @@ using System; -namespace Spine3_6_39 { - public class TransformConstraint : IConstraint { - internal TransformConstraintData data; - internal ExposedList bones; - internal Bone target; - internal float rotateMix, translateMix, scaleMix, shearMix; - - public TransformConstraintData Data { get { return data; } } - public int Order { get { return data.order; } } - public ExposedList Bones { get { return bones; } } - public Bone Target { get { return target; } set { target = value; } } - public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } - public float TranslateMix { get { return translateMix; } set { translateMix = value; } } - public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } } - public float ShearMix { get { return shearMix; } set { shearMix = value; } } - - public TransformConstraint (TransformConstraintData data, Skeleton skeleton) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.data = data; - rotateMix = data.rotateMix; - translateMix = data.translateMix; - scaleMix = data.scaleMix; - shearMix = data.shearMix; - - bones = new ExposedList(); - foreach (BoneData boneData in data.bones) - bones.Add (skeleton.FindBone (boneData.name)); - - target = skeleton.FindBone(data.target.name); - } - - public void Apply () { - Update(); - } - - public void Update () { - if (data.local) { - if (data.relative) - ApplyRelativeLocal(); - else - ApplyAbsoluteLocal(); - } else { - if (data.relative) - ApplyRelativeWorld(); - else - ApplyAbsoluteWorld(); - } - } - - void ApplyAbsoluteWorld () { - float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; - Bone target = this.target; - float ta = target.a, tb = target.b, tc = target.c, td = target.d; - float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad; - float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect; - var bones = this.bones; - for (int i = 0, n = bones.Count; i < n; i++) { - Bone bone = bones.Items[i]; - bool modified = false; - - if (rotateMix != 0) { - float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - float r = MathUtils.Atan2(tc, ta) - MathUtils.Atan2(c, a) + offsetRotation; - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) r += MathUtils.PI2; - r *= rotateMix; - float cos = MathUtils.Cos(r), sin = MathUtils.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; - modified = true; - } - - if (translateMix != 0) { - float tx, ty; //Vector2 temp = this.temp; - target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY)); - bone.worldX += (tx - bone.worldX) * translateMix; - bone.worldY += (ty - bone.worldY) * translateMix; - modified = true; - } - - if (scaleMix > 0) { - float s = (float)Math.Sqrt(bone.a * bone.a + bone.c * bone.c); - //float ts = (float)Math.sqrt(ta * ta + tc * tc); - if (s > 0.00001f) s = (s + ((float)Math.Sqrt(ta * ta + tc * tc) - s + data.offsetScaleX) * scaleMix) / s; - bone.a *= s; - bone.c *= s; - s = (float)Math.Sqrt(bone.b * bone.b + bone.d * bone.d); - //ts = (float)Math.Sqrt(tb * tb + td * td); - if (s > 0.00001f) s = (s + ((float)Math.Sqrt(tb * tb + td * td) - s + data.offsetScaleY) * scaleMix) / s; - bone.b *= s; - bone.d *= s; - modified = true; - } - - if (shearMix > 0) { - float b = bone.b, d = bone.d; - float by = MathUtils.Atan2(d, b); - float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta) - (by - MathUtils.Atan2(bone.c, bone.a)); - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) r += MathUtils.PI2; - r = by + (r + offsetShearY) * shearMix; - float s = (float)Math.Sqrt(b * b + d * d); - bone.b = MathUtils.Cos(r) * s; - bone.d = MathUtils.Sin(r) * s; - modified = true; - } - - if (modified) bone.appliedValid = false; - } - } - - void ApplyRelativeWorld () { - float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; - Bone target = this.target; - float ta = target.a, tb = target.b, tc = target.c, td = target.d; - float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad; - float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect; - var bones = this.bones; - for (int i = 0, n = bones.Count; i < n; i++) { - Bone bone = bones.Items[i]; - bool modified = false; - - if (rotateMix != 0) { - float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - float r = MathUtils.Atan2(tc, ta) + offsetRotation; - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) r += MathUtils.PI2; - r *= rotateMix; - float cos = MathUtils.Cos(r), sin = MathUtils.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; - modified = true; - } - - if (translateMix != 0) { - float tx, ty; //Vector2 temp = this.temp; - target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY)); - bone.worldX += tx * translateMix; - bone.worldY += ty * translateMix; - modified = true; - } - - if (scaleMix > 0) { - float s = ((float)Math.Sqrt(ta * ta + tc * tc) - 1 + data.offsetScaleX) * scaleMix + 1; - bone.a *= s; - bone.c *= s; - s = ((float)Math.Sqrt(tb * tb + td * td) - 1 + data.offsetScaleY) * scaleMix + 1; - bone.b *= s; - bone.d *= s; - modified = true; - } - - if (shearMix > 0) { - float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta); - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) r += MathUtils.PI2; - float b = bone.b, d = bone.d; - r = MathUtils.Atan2(d, b) + (r - MathUtils.PI / 2 + offsetShearY) * shearMix; - float s = (float)Math.Sqrt(b * b + d * d); - bone.b = MathUtils.Cos(r) * s; - bone.d = MathUtils.Sin(r) * s; - modified = true; - } - - if (modified) bone.appliedValid = false; - } - } - - void ApplyAbsoluteLocal () { - float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; - Bone target = this.target; - if (!target.appliedValid) target.UpdateAppliedTransform(); - var bonesItems = this.bones.Items; - for (int i = 0, n = this.bones.Count; i < n; i++) { - Bone bone = bonesItems[i]; - if (!bone.appliedValid) bone.UpdateAppliedTransform(); - - float rotation = bone.arotation; - if (rotateMix != 0) { - float r = target.arotation - rotation + data.offsetRotation; - r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; - rotation += r * rotateMix; - } - - float x = bone.ax, y = bone.ay; - if (translateMix != 0) { - x += (target.ax - x + data.offsetX) * translateMix; - y += (target.ay - y + data.offsetY) * translateMix; - } - - float scaleX = bone.ascaleX, scaleY = bone.ascaleY; - if (scaleMix > 0) { - if (scaleX > 0.00001f) scaleX = (scaleX + (target.ascaleX - scaleX + data.offsetScaleX) * scaleMix) / scaleX; - if (scaleY > 0.00001f) scaleY = (scaleY + (target.ascaleY - scaleY + data.offsetScaleY) * scaleMix) / scaleY; - } - - float shearY = bone.ashearY; - if (shearMix > 0) { - float r = target.ashearY - shearY + data.offsetShearY; - r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; - bone.shearY += r * shearMix; - } - - bone.UpdateWorldTransform(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY); - } - } - - void ApplyRelativeLocal () { - float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; - Bone target = this.target; - if (!target.appliedValid) target.UpdateAppliedTransform(); - var bonesItems = this.bones.Items; - for (int i = 0, n = this.bones.Count; i < n; i++) { - Bone bone = bonesItems[i]; - if (!bone.appliedValid) bone.UpdateAppliedTransform(); - - float rotation = bone.arotation; - if (rotateMix != 0) rotation += (target.arotation + data.offsetRotation) * rotateMix; - - float x = bone.ax, y = bone.ay; - if (translateMix != 0) { - x += (target.ax + data.offsetX) * translateMix; - y += (target.ay + data.offsetY) * translateMix; - } - - float scaleX = bone.ascaleX, scaleY = bone.ascaleY; - if (scaleMix > 0) { - if (scaleX > 0.00001f) scaleX *= ((target.ascaleX - 1 + data.offsetScaleX) * scaleMix) + 1; - if (scaleY > 0.00001f) scaleY *= ((target.ascaleY - 1 + data.offsetScaleY) * scaleMix) + 1; - } - - float shearY = bone.ashearY; - if (shearMix > 0) shearY += (target.ashearY + data.offsetShearY) * shearMix; - - bone.UpdateWorldTransform(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY); - } - } - - override public string ToString () { - return data.name; - } - } +namespace Spine3_6_39 +{ + public class TransformConstraint : IConstraint + { + internal TransformConstraintData data; + internal ExposedList bones; + internal Bone target; + internal float rotateMix, translateMix, scaleMix, shearMix; + + public TransformConstraintData Data { get { return data; } } + public int Order { get { return data.order; } } + public ExposedList Bones { get { return bones; } } + public Bone Target { get { return target; } set { target = value; } } + public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } + public float TranslateMix { get { return translateMix; } set { translateMix = value; } } + public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } } + public float ShearMix { get { return shearMix; } set { shearMix = value; } } + + public TransformConstraint(TransformConstraintData data, Skeleton skeleton) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + rotateMix = data.rotateMix; + translateMix = data.translateMix; + scaleMix = data.scaleMix; + shearMix = data.shearMix; + + bones = new ExposedList(); + foreach (BoneData boneData in data.bones) + bones.Add(skeleton.FindBone(boneData.name)); + + target = skeleton.FindBone(data.target.name); + } + + public void Apply() + { + Update(); + } + + public void Update() + { + if (data.local) + { + if (data.relative) + ApplyRelativeLocal(); + else + ApplyAbsoluteLocal(); + } + else + { + if (data.relative) + ApplyRelativeWorld(); + else + ApplyAbsoluteWorld(); + } + } + + void ApplyAbsoluteWorld() + { + float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; + Bone target = this.target; + float ta = target.a, tb = target.b, tc = target.c, td = target.d; + float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad; + float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect; + var bones = this.bones; + for (int i = 0, n = bones.Count; i < n; i++) + { + Bone bone = bones.Items[i]; + bool modified = false; + + if (rotateMix != 0) + { + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + float r = MathUtils.Atan2(tc, ta) - MathUtils.Atan2(c, a) + offsetRotation; + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) r += MathUtils.PI2; + r *= rotateMix; + float cos = MathUtils.Cos(r), sin = MathUtils.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; + modified = true; + } + + if (translateMix != 0) + { + float tx, ty; //Vector2 temp = this.temp; + target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY)); + bone.worldX += (tx - bone.worldX) * translateMix; + bone.worldY += (ty - bone.worldY) * translateMix; + modified = true; + } + + if (scaleMix > 0) + { + float s = (float)Math.Sqrt(bone.a * bone.a + bone.c * bone.c); + //float ts = (float)Math.sqrt(ta * ta + tc * tc); + if (s > 0.00001f) s = (s + ((float)Math.Sqrt(ta * ta + tc * tc) - s + data.offsetScaleX) * scaleMix) / s; + bone.a *= s; + bone.c *= s; + s = (float)Math.Sqrt(bone.b * bone.b + bone.d * bone.d); + //ts = (float)Math.Sqrt(tb * tb + td * td); + if (s > 0.00001f) s = (s + ((float)Math.Sqrt(tb * tb + td * td) - s + data.offsetScaleY) * scaleMix) / s; + bone.b *= s; + bone.d *= s; + modified = true; + } + + if (shearMix > 0) + { + float b = bone.b, d = bone.d; + float by = MathUtils.Atan2(d, b); + float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta) - (by - MathUtils.Atan2(bone.c, bone.a)); + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) r += MathUtils.PI2; + r = by + (r + offsetShearY) * shearMix; + float s = (float)Math.Sqrt(b * b + d * d); + bone.b = MathUtils.Cos(r) * s; + bone.d = MathUtils.Sin(r) * s; + modified = true; + } + + if (modified) bone.appliedValid = false; + } + } + + void ApplyRelativeWorld() + { + float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; + Bone target = this.target; + float ta = target.a, tb = target.b, tc = target.c, td = target.d; + float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad; + float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect; + var bones = this.bones; + for (int i = 0, n = bones.Count; i < n; i++) + { + Bone bone = bones.Items[i]; + bool modified = false; + + if (rotateMix != 0) + { + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + float r = MathUtils.Atan2(tc, ta) + offsetRotation; + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) r += MathUtils.PI2; + r *= rotateMix; + float cos = MathUtils.Cos(r), sin = MathUtils.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; + modified = true; + } + + if (translateMix != 0) + { + float tx, ty; //Vector2 temp = this.temp; + target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY)); + bone.worldX += tx * translateMix; + bone.worldY += ty * translateMix; + modified = true; + } + + if (scaleMix > 0) + { + float s = ((float)Math.Sqrt(ta * ta + tc * tc) - 1 + data.offsetScaleX) * scaleMix + 1; + bone.a *= s; + bone.c *= s; + s = ((float)Math.Sqrt(tb * tb + td * td) - 1 + data.offsetScaleY) * scaleMix + 1; + bone.b *= s; + bone.d *= s; + modified = true; + } + + if (shearMix > 0) + { + float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta); + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) r += MathUtils.PI2; + float b = bone.b, d = bone.d; + r = MathUtils.Atan2(d, b) + (r - MathUtils.PI / 2 + offsetShearY) * shearMix; + float s = (float)Math.Sqrt(b * b + d * d); + bone.b = MathUtils.Cos(r) * s; + bone.d = MathUtils.Sin(r) * s; + modified = true; + } + + if (modified) bone.appliedValid = false; + } + } + + void ApplyAbsoluteLocal() + { + float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; + Bone target = this.target; + if (!target.appliedValid) target.UpdateAppliedTransform(); + var bonesItems = this.bones.Items; + for (int i = 0, n = this.bones.Count; i < n; i++) + { + Bone bone = bonesItems[i]; + if (!bone.appliedValid) bone.UpdateAppliedTransform(); + + float rotation = bone.arotation; + if (rotateMix != 0) + { + float r = target.arotation - rotation + data.offsetRotation; + r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; + rotation += r * rotateMix; + } + + float x = bone.ax, y = bone.ay; + if (translateMix != 0) + { + x += (target.ax - x + data.offsetX) * translateMix; + y += (target.ay - y + data.offsetY) * translateMix; + } + + float scaleX = bone.ascaleX, scaleY = bone.ascaleY; + if (scaleMix > 0) + { + if (scaleX > 0.00001f) scaleX = (scaleX + (target.ascaleX - scaleX + data.offsetScaleX) * scaleMix) / scaleX; + if (scaleY > 0.00001f) scaleY = (scaleY + (target.ascaleY - scaleY + data.offsetScaleY) * scaleMix) / scaleY; + } + + float shearY = bone.ashearY; + if (shearMix > 0) + { + float r = target.ashearY - shearY + data.offsetShearY; + r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; + bone.shearY += r * shearMix; + } + + bone.UpdateWorldTransform(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY); + } + } + + void ApplyRelativeLocal() + { + float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; + Bone target = this.target; + if (!target.appliedValid) target.UpdateAppliedTransform(); + var bonesItems = this.bones.Items; + for (int i = 0, n = this.bones.Count; i < n; i++) + { + Bone bone = bonesItems[i]; + if (!bone.appliedValid) bone.UpdateAppliedTransform(); + + float rotation = bone.arotation; + if (rotateMix != 0) rotation += (target.arotation + data.offsetRotation) * rotateMix; + + float x = bone.ax, y = bone.ay; + if (translateMix != 0) + { + x += (target.ax + data.offsetX) * translateMix; + y += (target.ay + data.offsetY) * translateMix; + } + + float scaleX = bone.ascaleX, scaleY = bone.ascaleY; + if (scaleMix > 0) + { + if (scaleX > 0.00001f) scaleX *= ((target.ascaleX - 1 + data.offsetScaleX) * scaleMix) + 1; + if (scaleY > 0.00001f) scaleY *= ((target.ascaleY - 1 + data.offsetScaleY) * scaleMix) + 1; + } + + float shearY = bone.ashearY; + if (shearMix > 0) shearY += (target.ashearY + data.offsetShearY) * shearMix; + + bone.UpdateWorldTransform(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY); + } + } + + override public string ToString() + { + return data.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/TransformConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/TransformConstraintData.cs index d2d8e02..b547143 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/TransformConstraintData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/TransformConstraintData.cs @@ -30,42 +30,46 @@ using System; -namespace Spine3_6_39 { - public class TransformConstraintData { - internal string name; - internal int order; - internal ExposedList bones = new ExposedList(); - internal BoneData target; - internal float rotateMix, translateMix, scaleMix, shearMix; - internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY; - internal bool relative, local; +namespace Spine3_6_39 +{ + public class TransformConstraintData + { + internal string name; + internal int order; + internal ExposedList bones = new ExposedList(); + internal BoneData target; + internal float rotateMix, translateMix, scaleMix, shearMix; + internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY; + internal bool relative, local; - public string Name { get { return name; } } - public int Order { get { return order; } set { order = value; } } - public ExposedList Bones { get { return bones; } } - public BoneData Target { get { return target; } set { target = value; } } - public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } - public float TranslateMix { get { return translateMix; } set { translateMix = value; } } - public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } } - public float ShearMix { get { return shearMix; } set { shearMix = value; } } + public string Name { get { return name; } } + public int Order { get { return order; } set { order = value; } } + public ExposedList Bones { get { return bones; } } + public BoneData Target { get { return target; } set { target = value; } } + public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } + public float TranslateMix { get { return translateMix; } set { translateMix = value; } } + public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } } + public float ShearMix { get { return shearMix; } set { shearMix = value; } } - public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } - public float OffsetX { get { return offsetX; } set { offsetX = value; } } - public float OffsetY { get { return offsetY; } set { offsetY = value; } } - public float OffsetScaleX { get { return offsetScaleX; } set { offsetScaleX = value; } } - public float OffsetScaleY { get { return offsetScaleY; } set { offsetScaleY = value; } } - public float OffsetShearY { get { return offsetShearY; } set { offsetShearY = value; } } + public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } + public float OffsetX { get { return offsetX; } set { offsetX = value; } } + public float OffsetY { get { return offsetY; } set { offsetY = value; } } + public float OffsetScaleX { get { return offsetScaleX; } set { offsetScaleX = value; } } + public float OffsetScaleY { get { return offsetScaleY; } set { offsetScaleY = value; } } + public float OffsetShearY { get { return offsetShearY; } set { offsetShearY = value; } } - public bool Relative { get { return relative; } set { relative = value; } } - public bool Local { get { return local; } set { local = value; } } + public bool Relative { get { return relative; } set { relative = value; } } + public bool Local { get { return local; } set { local = value; } } - public TransformConstraintData (string name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; - } + public TransformConstraintData(string name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + } - override public string ToString () { - return name; - } - } + override public string ToString() + { + return name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Triangulator.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Triangulator.cs index b590555..3879a12 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Triangulator.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/Triangulator.cs @@ -30,249 +30,280 @@ using System; -namespace Spine3_6_39 { - internal class Triangulator { - private readonly ExposedList> convexPolygons = new ExposedList>(); - private readonly ExposedList> convexPolygonsIndices = new ExposedList>(); - - private readonly ExposedList indicesArray = new ExposedList(); - private readonly ExposedList isConcaveArray = new ExposedList(); - private readonly ExposedList triangles = new ExposedList(); - - private readonly Pool> polygonPool = new Pool>(); - private readonly Pool> polygonIndicesPool = new Pool>(); - - public ExposedList Triangulate (ExposedList verticesArray) { - var vertices = verticesArray.Items; - int vertexCount = verticesArray.Count >> 1; - - var indicesArray = this.indicesArray; - indicesArray.Clear(); - int[] indices = indicesArray.Resize(vertexCount).Items; - for (int i = 0; i < vertexCount; i++) - indices[i] = i; - - var isConcaveArray = this.isConcaveArray; - bool[] isConcave = isConcaveArray.Resize(vertexCount).Items; - for (int i = 0, n = vertexCount; i < n; ++i) - isConcave[i] = IsConcave(i, vertexCount, vertices, indices); - - var triangles = this.triangles; - triangles.Clear(); - triangles.EnsureCapacity(Math.Max(0, vertexCount - 2) << 2); - - while (vertexCount > 3) { - // Find ear tip. - int previous = vertexCount - 1, i = 0, next = 1; - - // outer: - while (true) { - if (!isConcave[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 (int ii = (next + 1) % vertexCount; ii != previous; ii = (ii + 1) % vertexCount) { - if (!isConcave[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 outer; // break outer; - } - } - } - break; - } - outer: - - if (next == 0) { - do { - if (!isConcave[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]); - indicesArray.RemoveAt(i); - isConcaveArray.RemoveAt(i); - vertexCount--; - - int previousIndex = (vertexCount + i - 1) % vertexCount; - int nextIndex = i == vertexCount ? 0 : i; - isConcave[previousIndex] = IsConcave(previousIndex, vertexCount, vertices, indices); - isConcave[nextIndex] = IsConcave(nextIndex, vertexCount, vertices, indices); - } - - if (vertexCount == 3) { - triangles.Add(indices[2]); - triangles.Add(indices[0]); - triangles.Add(indices[1]); - } - - return triangles; - } - - public ExposedList> Decompose (ExposedList verticesArray, ExposedList triangles) { - var vertices = verticesArray.Items; - var convexPolygons = this.convexPolygons; - for (int i = 0, n = convexPolygons.Count; i < n; i++) { - polygonPool.Free(convexPolygons.Items[i]); - } - convexPolygons.Clear(); - - var convexPolygonsIndices = this.convexPolygonsIndices; - for (int i = 0, n = convexPolygonsIndices.Count; i < n; i++) { - polygonIndicesPool.Free(convexPolygonsIndices.Items[i]); - } - convexPolygonsIndices.Clear(); - - var polygonIndices = polygonIndicesPool.Obtain(); - polygonIndices.Clear(); - - var polygon = polygonPool.Obtain(); - polygon.Clear(); - - // Merge subsequent triangles if they form a triangle fan. - int fanBaseIndex = -1, lastWinding = 0; - int[] trianglesItems = triangles.Items; - for (int i = 0, n = triangles.Count; i < n; i += 3) { - int t1 = trianglesItems[i] << 1, t2 = trianglesItems[i + 1] << 1, t3 = trianglesItems[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). - var merged = false; - if (fanBaseIndex == t1) { - int o = polygon.Count - 4; - float[] p = polygon.Items; - 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.Count > 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.Count > 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 (int i = 0, n = convexPolygons.Count; i < n; i++) { - polygonIndices = convexPolygonsIndices.Items[i]; - if (polygonIndices.Count == 0) continue; - int firstIndex = polygonIndices.Items[0]; - int lastIndex = polygonIndices.Items[polygonIndices.Count - 1]; - - polygon = convexPolygons.Items[i]; - int o = polygon.Count - 4; - float[] p = polygon.Items; - 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 winding = Winding(prevPrevX, prevPrevY, prevX, prevY, firstX, firstY); - - for (int ii = 0; ii < n; ii++) { - if (ii == i) continue; - var otherIndices = convexPolygonsIndices.Items[ii]; - if (otherIndices.Count != 3) continue; - int otherFirstIndex = otherIndices.Items[0]; - int otherSecondIndex = otherIndices.Items[1]; - int otherLastIndex = otherIndices.Items[2]; - - var otherPoly = convexPolygons.Items[ii]; - float x3 = otherPoly.Items[otherPoly.Count - 2], y3 = otherPoly.Items[otherPoly.Count - 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 == winding && winding2 == winding) { - 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 = convexPolygons.Count - 1; i >= 0; i--) { - polygon = convexPolygons.Items[i]; - if (polygon.Count == 0) { - convexPolygons.RemoveAt(i); - polygonPool.Free(polygon); - polygonIndices = convexPolygonsIndices.Items[i]; - convexPolygonsIndices.RemoveAt(i); - polygonIndicesPool.Free(polygonIndices); - } - } - - return convexPolygons; - } - - static private bool IsConcave (int index, int vertexCount, float[] vertices, int[] 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]); - } - - static private bool PositiveArea (float p1x, float p1y, float p2x, float p2y, float p3x, float p3y) { - return p1x * (p3y - p2y) + p2x * (p1y - p3y) + p3x * (p2y - p1y) >= 0; - } - - static private int 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; - } - } +namespace Spine3_6_39 +{ + internal class Triangulator + { + private readonly ExposedList> convexPolygons = new ExposedList>(); + private readonly ExposedList> convexPolygonsIndices = new ExposedList>(); + + private readonly ExposedList indicesArray = new ExposedList(); + private readonly ExposedList isConcaveArray = new ExposedList(); + private readonly ExposedList triangles = new ExposedList(); + + private readonly Pool> polygonPool = new Pool>(); + private readonly Pool> polygonIndicesPool = new Pool>(); + + public ExposedList Triangulate(ExposedList verticesArray) + { + var vertices = verticesArray.Items; + int vertexCount = verticesArray.Count >> 1; + + var indicesArray = this.indicesArray; + indicesArray.Clear(); + int[] indices = indicesArray.Resize(vertexCount).Items; + for (int i = 0; i < vertexCount; i++) + indices[i] = i; + + var isConcaveArray = this.isConcaveArray; + bool[] isConcave = isConcaveArray.Resize(vertexCount).Items; + for (int i = 0, n = vertexCount; i < n; ++i) + isConcave[i] = IsConcave(i, vertexCount, vertices, indices); + + var triangles = this.triangles; + triangles.Clear(); + triangles.EnsureCapacity(Math.Max(0, vertexCount - 2) << 2); + + while (vertexCount > 3) + { + // Find ear tip. + int previous = vertexCount - 1, i = 0, next = 1; + + // outer: + while (true) + { + if (!isConcave[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 (int ii = (next + 1) % vertexCount; ii != previous; ii = (ii + 1) % vertexCount) + { + if (!isConcave[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 outer; // break outer; + } + } + } + break; + } + outer: + + if (next == 0) + { + do + { + if (!isConcave[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]); + indicesArray.RemoveAt(i); + isConcaveArray.RemoveAt(i); + vertexCount--; + + int previousIndex = (vertexCount + i - 1) % vertexCount; + int nextIndex = i == vertexCount ? 0 : i; + isConcave[previousIndex] = IsConcave(previousIndex, vertexCount, vertices, indices); + isConcave[nextIndex] = IsConcave(nextIndex, vertexCount, vertices, indices); + } + + if (vertexCount == 3) + { + triangles.Add(indices[2]); + triangles.Add(indices[0]); + triangles.Add(indices[1]); + } + + return triangles; + } + + public ExposedList> Decompose(ExposedList verticesArray, ExposedList triangles) + { + var vertices = verticesArray.Items; + var convexPolygons = this.convexPolygons; + for (int i = 0, n = convexPolygons.Count; i < n; i++) + { + polygonPool.Free(convexPolygons.Items[i]); + } + convexPolygons.Clear(); + + var convexPolygonsIndices = this.convexPolygonsIndices; + for (int i = 0, n = convexPolygonsIndices.Count; i < n; i++) + { + polygonIndicesPool.Free(convexPolygonsIndices.Items[i]); + } + convexPolygonsIndices.Clear(); + + var polygonIndices = polygonIndicesPool.Obtain(); + polygonIndices.Clear(); + + var polygon = polygonPool.Obtain(); + polygon.Clear(); + + // Merge subsequent triangles if they form a triangle fan. + int fanBaseIndex = -1, lastWinding = 0; + int[] trianglesItems = triangles.Items; + for (int i = 0, n = triangles.Count; i < n; i += 3) + { + int t1 = trianglesItems[i] << 1, t2 = trianglesItems[i + 1] << 1, t3 = trianglesItems[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). + var merged = false; + if (fanBaseIndex == t1) + { + int o = polygon.Count - 4; + float[] p = polygon.Items; + 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.Count > 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.Count > 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 (int i = 0, n = convexPolygons.Count; i < n; i++) + { + polygonIndices = convexPolygonsIndices.Items[i]; + if (polygonIndices.Count == 0) continue; + int firstIndex = polygonIndices.Items[0]; + int lastIndex = polygonIndices.Items[polygonIndices.Count - 1]; + + polygon = convexPolygons.Items[i]; + int o = polygon.Count - 4; + float[] p = polygon.Items; + 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 winding = Winding(prevPrevX, prevPrevY, prevX, prevY, firstX, firstY); + + for (int ii = 0; ii < n; ii++) + { + if (ii == i) continue; + var otherIndices = convexPolygonsIndices.Items[ii]; + if (otherIndices.Count != 3) continue; + int otherFirstIndex = otherIndices.Items[0]; + int otherSecondIndex = otherIndices.Items[1]; + int otherLastIndex = otherIndices.Items[2]; + + var otherPoly = convexPolygons.Items[ii]; + float x3 = otherPoly.Items[otherPoly.Count - 2], y3 = otherPoly.Items[otherPoly.Count - 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 == winding && winding2 == winding) + { + 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 = convexPolygons.Count - 1; i >= 0; i--) + { + polygon = convexPolygons.Items[i]; + if (polygon.Count == 0) + { + convexPolygons.RemoveAt(i); + polygonPool.Free(polygon); + polygonIndices = convexPolygonsIndices.Items[i]; + convexPolygonsIndices.RemoveAt(i); + polygonIndicesPool.Free(polygonIndices); + } + } + + return convexPolygons; + } + + static private bool IsConcave(int index, int vertexCount, float[] vertices, int[] 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]); + } + + static private bool PositiveArea(float p1x, float p1y, float p2x, float p2y, float p3x, float p3y) + { + return p1x * (p3y - p2y) + p2x * (p1y - p3y) + p3x * (p2y - p1y) >= 0; + } + + static private int 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/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/XnaLoader/MeshBatcher.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/XnaLoader/MeshBatcher.cs index ec9a595..74e3997 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/XnaLoader/MeshBatcher.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/XnaLoader/MeshBatcher.cs @@ -31,156 +31,169 @@ using System; using System.Collections.Generic; -using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; -namespace Spine3_6_39 { - public struct VertexPositionColorTextureColor : IVertexType { - public Vector3 Position; - public Color Color; - public Vector2 TextureCoordinate; - public Color Color2; - - public readonly static VertexDeclaration VertexDeclaration = new VertexDeclaration - ( - new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0), - new VertexElement(12, VertexElementFormat.Color, VertexElementUsage.Color, 0), - new VertexElement(16, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0), - new VertexElement(24, VertexElementFormat.Color, VertexElementUsage.Color, 1) - ); - - VertexDeclaration IVertexType.VertexDeclaration { - get { return VertexDeclaration; } - } - } - - // #region License - // /* - // Microsoft Public License (Ms-PL) - // MonoGame - Copyright ?2009 The MonoGame Team - // - // All rights reserved. - // - // This license governs use of the accompanying software. If you use the software, you accept this license. If you do not - // accept the license, do not use the software. - // - // 1. Definitions - // The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under - // U.S. copyright law. - // - // A "contribution" is the original software, or any additions or changes to the software. - // A "contributor" is any person that distributes its contribution under this license. - // "Licensed patents" are a contributor's patent claims that read directly on its contribution. - // - // 2. Grant of Rights - // (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, - // each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. - // (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, - // each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. - // - // 3. Conditions and Limitations - // (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. - // (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, - // your patent license from such contributor to the software ends automatically. - // (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution - // notices that are present in the software. - // (D) If you distribute any portion of the software in source code form, you may do so only under this license by including - // a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object - // code form, you may only do so under a license that complies with this license. - // (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees - // or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent - // permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular - // purpose and non-infringement. - // */ - // #endregion License - // - - /// Draws batched meshes. - public class MeshBatcher { - private readonly List items; - private readonly Queue freeItems; - private VertexPositionColorTextureColor[] vertexArray = { }; - private short[] triangles = { }; - - public MeshBatcher () { - items = new List(256); - freeItems = new Queue(256); - EnsureCapacity(256, 512); - } - - /// Returns a pooled MeshItem. - public MeshItem NextItem (int vertexCount, int triangleCount) { - MeshItem item = freeItems.Count > 0 ? freeItems.Dequeue() : new MeshItem(); - if (item.vertices.Length < vertexCount) item.vertices = new VertexPositionColorTextureColor[vertexCount]; - if (item.triangles.Length < triangleCount) item.triangles = new int[triangleCount]; - item.vertexCount = vertexCount; - item.triangleCount = triangleCount; - items.Add(item); - return item; - } - - private void EnsureCapacity (int vertexCount, int triangleCount) { - if (vertexArray.Length < vertexCount) vertexArray = new VertexPositionColorTextureColor[vertexCount]; - if (triangles.Length < triangleCount) triangles = new short[triangleCount]; - } - - public void Draw (GraphicsDevice device) { - if (items.Count == 0) return; - - int itemCount = items.Count; - int vertexCount = 0, triangleCount = 0; - for (int i = 0; i < itemCount; i++) { - MeshItem item = items[i]; - vertexCount += item.vertexCount; - triangleCount += item.triangleCount; - } - EnsureCapacity(vertexCount, triangleCount); - - vertexCount = 0; - triangleCount = 0; - Texture2D lastTexture = null; - for (int i = 0; i < itemCount; i++) { - MeshItem item = items[i]; - int itemVertexCount = item.vertexCount; - - if (item.texture != lastTexture || vertexCount + itemVertexCount > short.MaxValue) { - FlushVertexArray(device, vertexCount, triangleCount); - vertexCount = 0; - triangleCount = 0; - lastTexture = item.texture; - device.Textures[0] = lastTexture; - } - - int[] itemTriangles = item.triangles; - int itemTriangleCount = item.triangleCount; - for (int ii = 0, t = triangleCount; ii < itemTriangleCount; ii++, t++) - triangles[t] = (short)(itemTriangles[ii] + vertexCount); - triangleCount += itemTriangleCount; - - Array.Copy(item.vertices, 0, vertexArray, vertexCount, itemVertexCount); - vertexCount += itemVertexCount; - - item.texture = null; - freeItems.Enqueue(item); - } - FlushVertexArray(device, vertexCount, triangleCount); - items.Clear(); - } - - private void FlushVertexArray (GraphicsDevice device, int vertexCount, int triangleCount) { - if (vertexCount == 0) return; - device.DrawUserIndexedPrimitives( - PrimitiveType.TriangleList, - vertexArray, 0, vertexCount, - triangles, 0, triangleCount / 3, - VertexPositionColorTextureColor.VertexDeclaration); - } - } - - public class MeshItem { - public Texture2D texture; - public int vertexCount, triangleCount; - public VertexPositionColorTextureColor[] vertices = { }; - public int[] triangles = { }; - } +namespace Spine3_6_39 +{ + public struct VertexPositionColorTextureColor : IVertexType + { + public Vector3 Position; + public Color Color; + public Vector2 TextureCoordinate; + public Color Color2; + + public readonly static VertexDeclaration VertexDeclaration = new VertexDeclaration + ( + new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0), + new VertexElement(12, VertexElementFormat.Color, VertexElementUsage.Color, 0), + new VertexElement(16, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0), + new VertexElement(24, VertexElementFormat.Color, VertexElementUsage.Color, 1) + ); + + VertexDeclaration IVertexType.VertexDeclaration + { + get { return VertexDeclaration; } + } + } + + // #region License + // /* + // Microsoft Public License (Ms-PL) + // MonoGame - Copyright ?2009 The MonoGame Team + // + // All rights reserved. + // + // This license governs use of the accompanying software. If you use the software, you accept this license. If you do not + // accept the license, do not use the software. + // + // 1. Definitions + // The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under + // U.S. copyright law. + // + // A "contribution" is the original software, or any additions or changes to the software. + // A "contributor" is any person that distributes its contribution under this license. + // "Licensed patents" are a contributor's patent claims that read directly on its contribution. + // + // 2. Grant of Rights + // (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, + // each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. + // (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, + // each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. + // + // 3. Conditions and Limitations + // (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. + // (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, + // your patent license from such contributor to the software ends automatically. + // (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution + // notices that are present in the software. + // (D) If you distribute any portion of the software in source code form, you may do so only under this license by including + // a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object + // code form, you may only do so under a license that complies with this license. + // (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees + // or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent + // permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular + // purpose and non-infringement. + // */ + // #endregion License + // + + /// Draws batched meshes. + public class MeshBatcher + { + private readonly List items; + private readonly Queue freeItems; + private VertexPositionColorTextureColor[] vertexArray = { }; + private short[] triangles = { }; + + public MeshBatcher() + { + items = new List(256); + freeItems = new Queue(256); + EnsureCapacity(256, 512); + } + + /// Returns a pooled MeshItem. + public MeshItem NextItem(int vertexCount, int triangleCount) + { + MeshItem item = freeItems.Count > 0 ? freeItems.Dequeue() : new MeshItem(); + if (item.vertices.Length < vertexCount) item.vertices = new VertexPositionColorTextureColor[vertexCount]; + if (item.triangles.Length < triangleCount) item.triangles = new int[triangleCount]; + item.vertexCount = vertexCount; + item.triangleCount = triangleCount; + items.Add(item); + return item; + } + + private void EnsureCapacity(int vertexCount, int triangleCount) + { + if (vertexArray.Length < vertexCount) vertexArray = new VertexPositionColorTextureColor[vertexCount]; + if (triangles.Length < triangleCount) triangles = new short[triangleCount]; + } + + public void Draw(GraphicsDevice device) + { + if (items.Count == 0) return; + + int itemCount = items.Count; + int vertexCount = 0, triangleCount = 0; + for (int i = 0; i < itemCount; i++) + { + MeshItem item = items[i]; + vertexCount += item.vertexCount; + triangleCount += item.triangleCount; + } + EnsureCapacity(vertexCount, triangleCount); + + vertexCount = 0; + triangleCount = 0; + Texture2D lastTexture = null; + for (int i = 0; i < itemCount; i++) + { + MeshItem item = items[i]; + int itemVertexCount = item.vertexCount; + + if (item.texture != lastTexture || vertexCount + itemVertexCount > short.MaxValue) + { + FlushVertexArray(device, vertexCount, triangleCount); + vertexCount = 0; + triangleCount = 0; + lastTexture = item.texture; + device.Textures[0] = lastTexture; + } + + int[] itemTriangles = item.triangles; + int itemTriangleCount = item.triangleCount; + for (int ii = 0, t = triangleCount; ii < itemTriangleCount; ii++, t++) + triangles[t] = (short)(itemTriangles[ii] + vertexCount); + triangleCount += itemTriangleCount; + + Array.Copy(item.vertices, 0, vertexArray, vertexCount, itemVertexCount); + vertexCount += itemVertexCount; + + item.texture = null; + freeItems.Enqueue(item); + } + FlushVertexArray(device, vertexCount, triangleCount); + items.Clear(); + } + + private void FlushVertexArray(GraphicsDevice device, int vertexCount, int triangleCount) + { + if (vertexCount == 0) return; + device.DrawUserIndexedPrimitives( + PrimitiveType.TriangleList, + vertexArray, 0, vertexCount, + triangles, 0, triangleCount / 3, + VertexPositionColorTextureColor.VertexDeclaration); + } + } + + public class MeshItem + { + public Texture2D texture; + public int vertexCount, triangleCount; + public VertexPositionColorTextureColor[] vertices = { }; + public int[] triangles = { }; + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/XnaLoader/ShapeRenderer.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/XnaLoader/ShapeRenderer.cs index d38d82c..66cbc81 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/XnaLoader/ShapeRenderer.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/XnaLoader/ShapeRenderer.cs @@ -29,140 +29,157 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Spine3_6_39 { - /// - /// Batch drawing of lines and shapes that can be derrived from lines. - /// - /// Call drawing methods in between Begin()/End() - /// - public class ShapeRenderer { - GraphicsDevice device; - List vertices = new List(); - Color color = Color.White; - BasicEffect effect; - public BasicEffect Effect { get { return effect; } set { effect = value; } } - - public ShapeRenderer(GraphicsDevice device) { - this.device = device; - this.effect = new BasicEffect(device); - effect.World = Matrix.Identity; - effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); - effect.TextureEnabled = false; - effect.VertexColorEnabled = true; - } - - public void SetColor(Color color) { - this.color = color; - } - - public void Begin() { - device.RasterizerState = new RasterizerState(); - device.BlendState = BlendState.AlphaBlend; - } - - public void Line(float x1, float y1, float x2, float y2) { - vertices.Add(new VertexPositionColor(new Vector3(x1, y1, 0), color)); - vertices.Add(new VertexPositionColor(new Vector3(x2, y2, 0), color)); - } - - /** Calls {@link #circle(float, float, float, int)} by estimating the number of segments needed for a smooth circle. */ - public void Circle(float x, float y, float radius) { - Circle(x, y, radius, Math.Max(1, (int)(6 * (float)Math.Pow(radius, 1.0f / 3.0f)))); - } - - /** Draws a circle using {@link ShapeType#Line} or {@link ShapeType#Filled}. */ - public void Circle(float x, float y, float radius, int segments) { - if (segments <= 0) throw new ArgumentException("segments must be > 0."); - float angle = 2 * MathUtils.PI / segments; - float cos = MathUtils.Cos(angle); - float sin = MathUtils.Sin(angle); - float cx = radius, cy = 0; - float temp = 0; - - for (int i = 0; i < segments; i++) { - vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); - temp = cx; - cx = cos * cx - sin * cy; - cy = sin * temp + cos * cy; - vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); - } - vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); - - temp = cx; - cx = radius; - cy = 0; - vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); - } - - public void Triangle(float x1, float y1, float x2, float y2, float x3, float y3) { - vertices.Add(new VertexPositionColor(new Vector3(x1, y1, 0), color)); - vertices.Add(new VertexPositionColor(new Vector3(x2, y2, 0), color)); - - vertices.Add(new VertexPositionColor(new Vector3(x2, y2, 0), color)); - vertices.Add(new VertexPositionColor(new Vector3(x3, y3, 0), color)); - - vertices.Add(new VertexPositionColor(new Vector3(x3, y3, 0), color)); - vertices.Add(new VertexPositionColor(new Vector3(x1, y1, 0), color)); - } - - public void X(float x, float y, float len) { - Line(x + len, y + len, x - len, y - len); - Line(x - len, y + len, x + len, y - len); - } - - public void Polygon(float[] polygonVertices, int offset, int count) { - if (count< 3) throw new ArgumentException("Polygon must contain at least 3 vertices"); - - offset <<= 1; - count <<= 1; - - var firstX = polygonVertices[offset]; - var firstY = polygonVertices[offset + 1]; - var last = offset + count; - - for (int i = offset, n = offset + count - 2; i= last) { - x2 = firstX; - y2 = firstY; - } else { - x2 = polygonVertices[i + 2]; - y2 = polygonVertices[i + 3]; - } - - Line(x1, y1, x2, y2); - } - } - - public void Rect(float x, float y, float width, float height) { - Line(x, y, x + width, y); - Line(x + width, y, x + width, y + height); - Line(x + width, y + height, x, y + height); - Line(x, y + height, x, y); - } - - public void End() { - if (vertices.Count == 0) return; - var verticesArray = vertices.ToArray(); - - foreach (EffectPass pass in effect.CurrentTechnique.Passes) { - pass.Apply(); - device.DrawUserPrimitives(PrimitiveType.LineList, verticesArray, 0, verticesArray.Length / 2); - } - - vertices.Clear(); - } - } +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Spine3_6_39 +{ + /// + /// Batch drawing of lines and shapes that can be derrived from lines. + /// + /// Call drawing methods in between Begin()/End() + /// + public class ShapeRenderer + { + GraphicsDevice device; + List vertices = new List(); + Color color = Color.White; + BasicEffect effect; + public BasicEffect Effect { get { return effect; } set { effect = value; } } + + public ShapeRenderer(GraphicsDevice device) + { + this.device = device; + this.effect = new BasicEffect(device); + effect.World = Matrix.Identity; + effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); + effect.TextureEnabled = false; + effect.VertexColorEnabled = true; + } + + public void SetColor(Color color) + { + this.color = color; + } + + public void Begin() + { + device.RasterizerState = new RasterizerState(); + device.BlendState = BlendState.AlphaBlend; + } + + public void Line(float x1, float y1, float x2, float y2) + { + vertices.Add(new VertexPositionColor(new Vector3(x1, y1, 0), color)); + vertices.Add(new VertexPositionColor(new Vector3(x2, y2, 0), color)); + } + + /** Calls {@link #circle(float, float, float, int)} by estimating the number of segments needed for a smooth circle. */ + public void Circle(float x, float y, float radius) + { + Circle(x, y, radius, Math.Max(1, (int)(6 * (float)Math.Pow(radius, 1.0f / 3.0f)))); + } + + /** Draws a circle using {@link ShapeType#Line} or {@link ShapeType#Filled}. */ + public void Circle(float x, float y, float radius, int segments) + { + if (segments <= 0) throw new ArgumentException("segments must be > 0."); + float angle = 2 * MathUtils.PI / segments; + float cos = MathUtils.Cos(angle); + float sin = MathUtils.Sin(angle); + float cx = radius, cy = 0; + float temp = 0; + + for (int i = 0; i < segments; i++) + { + vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); + temp = cx; + cx = cos * cx - sin * cy; + cy = sin * temp + cos * cy; + vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); + } + vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); + + temp = cx; + cx = radius; + cy = 0; + vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); + } + + public void Triangle(float x1, float y1, float x2, float y2, float x3, float y3) + { + vertices.Add(new VertexPositionColor(new Vector3(x1, y1, 0), color)); + vertices.Add(new VertexPositionColor(new Vector3(x2, y2, 0), color)); + + vertices.Add(new VertexPositionColor(new Vector3(x2, y2, 0), color)); + vertices.Add(new VertexPositionColor(new Vector3(x3, y3, 0), color)); + + vertices.Add(new VertexPositionColor(new Vector3(x3, y3, 0), color)); + vertices.Add(new VertexPositionColor(new Vector3(x1, y1, 0), color)); + } + + public void X(float x, float y, float len) + { + Line(x + len, y + len, x - len, y - len); + Line(x - len, y + len, x + len, y - len); + } + + public void Polygon(float[] polygonVertices, int offset, int count) + { + if (count < 3) throw new ArgumentException("Polygon must contain at least 3 vertices"); + + offset <<= 1; + count <<= 1; + + var firstX = polygonVertices[offset]; + var firstY = polygonVertices[offset + 1]; + var last = offset + count; + + for (int i = offset, n = offset + count - 2; i < n; i += 2) + { + var x1 = polygonVertices[i]; + var y1 = polygonVertices[i + 1]; + + var x2 = 0f; + var y2 = 0f; + + if (i + 2 >= last) + { + x2 = firstX; + y2 = firstY; + } + else + { + x2 = polygonVertices[i + 2]; + y2 = polygonVertices[i + 3]; + } + + Line(x1, y1, x2, y2); + } + } + + public void Rect(float x, float y, float width, float height) + { + Line(x, y, x + width, y); + Line(x + width, y, x + width, y + height); + Line(x + width, y + height, x, y + height); + Line(x, y + height, x, y); + } + + public void End() + { + if (vertices.Count == 0) return; + var verticesArray = vertices.ToArray(); + + foreach (EffectPass pass in effect.CurrentTechnique.Passes) + { + pass.Apply(); + device.DrawUserPrimitives(PrimitiveType.LineList, verticesArray, 0, verticesArray.Length / 2); + } + + vertices.Clear(); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/XnaLoader/SkeletonRenderer.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/XnaLoader/SkeletonRenderer.cs index 0cfee2a..7c272ec 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/XnaLoader/SkeletonRenderer.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/XnaLoader/SkeletonRenderer.cs @@ -29,115 +29,125 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; -using System.Collections.Generic; -using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Spine3_6_39 +{ + /// Draws region and mesh attachments. + public class SkeletonRenderer + { + private const int TL = 0; + private const int TR = 1; + private const int BL = 2; + private const int BR = 3; + + SkeletonClipping clipper = new SkeletonClipping(); + GraphicsDevice device; + MeshBatcher batcher; + RasterizerState rasterizerState; + float[] vertices = new float[8]; + int[] quadTriangles = { 0, 1, 2, 2, 3, 0 }; + BlendState defaultBlendState; + + Effect effect; + public Effect Effect { get { return effect; } set { effect = value; } } + + private bool premultipliedAlpha; + public bool PremultipliedAlpha { get { return premultipliedAlpha; } set { premultipliedAlpha = value; } } + + public SkeletonRenderer(GraphicsDevice device) + { + this.device = device; + + batcher = new MeshBatcher(); + + var basicEffect = new BasicEffect(device); + basicEffect.World = Matrix.Identity; + basicEffect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); + basicEffect.TextureEnabled = true; + basicEffect.VertexColorEnabled = true; + effect = basicEffect; + + rasterizerState = new RasterizerState(); + rasterizerState.CullMode = CullMode.None; + + Bone.yDown = true; + } + + public void Begin() + { + defaultBlendState = premultipliedAlpha ? BlendState.AlphaBlend : BlendState.NonPremultiplied; -namespace Spine3_6_39 { - /// Draws region and mesh attachments. - public class SkeletonRenderer { - private const int TL = 0; - private const int TR = 1; - private const int BL = 2; - private const int BR = 3; - - SkeletonClipping clipper = new SkeletonClipping(); - GraphicsDevice device; - MeshBatcher batcher; - RasterizerState rasterizerState; - float[] vertices = new float[8]; - int[] quadTriangles = { 0, 1, 2, 2, 3, 0 }; - BlendState defaultBlendState; - - Effect effect; - public Effect Effect { get { return effect; } set { effect = value; } } - - private bool premultipliedAlpha; - public bool PremultipliedAlpha { get { return premultipliedAlpha; } set { premultipliedAlpha = value; } } - - public SkeletonRenderer (GraphicsDevice device) { - this.device = device; - - batcher = new MeshBatcher(); - - var basicEffect = new BasicEffect(device); - basicEffect.World = Matrix.Identity; - basicEffect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); - basicEffect.TextureEnabled = true; - basicEffect.VertexColorEnabled = true; - effect = basicEffect; - - rasterizerState = new RasterizerState(); - rasterizerState.CullMode = CullMode.None; - - Bone.yDown = true; - } - - public void Begin () { - defaultBlendState = premultipliedAlpha ? BlendState.AlphaBlend : BlendState.NonPremultiplied; - - device.RasterizerState = rasterizerState; - device.BlendState = defaultBlendState; - } - - public void End () { - foreach (EffectPass pass in effect.CurrentTechnique.Passes) { - pass.Apply(); - batcher.Draw(device); - } - } - - public void Draw(Skeleton skeleton) { - var drawOrder = skeleton.DrawOrder; - var drawOrderItems = skeleton.DrawOrder.Items; - float skeletonR = skeleton.R, skeletonG = skeleton.G, skeletonB = skeleton.B, skeletonA = skeleton.A; - Color color = new Color(); - - for (int i = 0, n = drawOrder.Count; i < n; i++) { - Slot slot = drawOrderItems[i]; - Attachment attachment = slot.Attachment; - - float attachmentColorR, attachmentColorG, attachmentColorB, attachmentColorA; - Texture2D texture = null; - int verticesCount = 0; - float[] vertices = this.vertices; - int indicesCount = 0; - int[] indices = null; - float[] uvs = null; - - if (attachment is RegionAttachment) { - RegionAttachment regionAttachment = (RegionAttachment)attachment; - attachmentColorR = regionAttachment.R; attachmentColorG = regionAttachment.G; attachmentColorB = regionAttachment.B; attachmentColorA = regionAttachment.A; - AtlasRegion region = (AtlasRegion)regionAttachment.RendererObject; - texture = (Texture2D)region.page.rendererObject; - verticesCount = 4; - regionAttachment.ComputeWorldVertices(slot.Bone, vertices, 0, 2); - indicesCount = 6; - indices = quadTriangles; - uvs = regionAttachment.UVs; - } - else if (attachment is MeshAttachment) { - MeshAttachment mesh = (MeshAttachment)attachment; - attachmentColorR = mesh.R; attachmentColorG = mesh.G; attachmentColorB = mesh.B; attachmentColorA = mesh.A; - AtlasRegion region = (AtlasRegion)mesh.RendererObject; - texture = (Texture2D)region.page.rendererObject; - int vertexCount = mesh.WorldVerticesLength; - if (vertices.Length < vertexCount) vertices = new float[vertexCount]; - verticesCount = vertexCount >> 1; - mesh.ComputeWorldVertices(slot, vertices); - indicesCount = mesh.Triangles.Length; - indices = mesh.Triangles; - uvs = mesh.UVs; - } - else if (attachment is ClippingAttachment) { - ClippingAttachment clip = (ClippingAttachment)attachment; - clipper.ClipStart(slot, clip); - continue; - } - else { - continue; - } + device.RasterizerState = rasterizerState; + device.BlendState = defaultBlendState; + } + + public void End() + { + foreach (EffectPass pass in effect.CurrentTechnique.Passes) + { + pass.Apply(); + batcher.Draw(device); + } + } + + public void Draw(Skeleton skeleton) + { + var drawOrder = skeleton.DrawOrder; + var drawOrderItems = skeleton.DrawOrder.Items; + float skeletonR = skeleton.R, skeletonG = skeleton.G, skeletonB = skeleton.B, skeletonA = skeleton.A; + Color color = new Color(); + + for (int i = 0, n = drawOrder.Count; i < n; i++) + { + Slot slot = drawOrderItems[i]; + Attachment attachment = slot.Attachment; + + float attachmentColorR, attachmentColorG, attachmentColorB, attachmentColorA; + Texture2D texture = null; + int verticesCount = 0; + float[] vertices = this.vertices; + int indicesCount = 0; + int[] indices = null; + float[] uvs = null; + + if (attachment is RegionAttachment) + { + RegionAttachment regionAttachment = (RegionAttachment)attachment; + attachmentColorR = regionAttachment.R; attachmentColorG = regionAttachment.G; attachmentColorB = regionAttachment.B; attachmentColorA = regionAttachment.A; + AtlasRegion region = (AtlasRegion)regionAttachment.RendererObject; + texture = (Texture2D)region.page.rendererObject; + verticesCount = 4; + regionAttachment.ComputeWorldVertices(slot.Bone, vertices, 0, 2); + indicesCount = 6; + indices = quadTriangles; + uvs = regionAttachment.UVs; + } + else if (attachment is MeshAttachment) + { + MeshAttachment mesh = (MeshAttachment)attachment; + attachmentColorR = mesh.R; attachmentColorG = mesh.G; attachmentColorB = mesh.B; attachmentColorA = mesh.A; + AtlasRegion region = (AtlasRegion)mesh.RendererObject; + texture = (Texture2D)region.page.rendererObject; + int vertexCount = mesh.WorldVerticesLength; + if (vertices.Length < vertexCount) vertices = new float[vertexCount]; + verticesCount = vertexCount >> 1; + mesh.ComputeWorldVertices(slot, vertices); + indicesCount = mesh.Triangles.Length; + indices = mesh.Triangles; + uvs = mesh.UVs; + } + else if (attachment is ClippingAttachment) + { + ClippingAttachment clip = (ClippingAttachment)attachment; + clipper.ClipStart(slot, clip); + continue; + } + else + { + continue; + } // set blend state BlendState blendState = new BlendState(); @@ -199,57 +209,63 @@ public void Draw(Skeleton skeleton) { // calculate color float a = skeletonA * slot.A * attachmentColorA; - if (premultipliedAlpha) { - color = new Color( - skeletonR * slot.R * attachmentColorR * a, - skeletonG * slot.G * attachmentColorG * a, - skeletonB * slot.B * attachmentColorB * a, a); - } - else { - color = new Color( - skeletonR * slot.R * attachmentColorR, - skeletonG * slot.G * attachmentColorG, - skeletonB * slot.B * attachmentColorB, a); - } - - Color darkColor = new Color(); - if (slot.HasSecondColor) { - darkColor = new Color(slot.R2, slot.G2, slot.B2); - } - - // clip - if (clipper.IsClipping()) { - clipper.ClipTriangles(vertices, verticesCount << 1, indices, indicesCount, uvs); - vertices = clipper.ClippedVertices.Items; - verticesCount = clipper.ClippedVertices.Count >> 1; - indices = clipper.ClippedTriangles.Items; - indicesCount = clipper.ClippedTriangles.Count; - uvs = clipper.ClippedUVs.Items; - } - - if (verticesCount == 0 || indicesCount == 0) - continue; - - // submit to batch - MeshItem item = batcher.NextItem(verticesCount, indicesCount); - item.texture = texture; - for (int ii = 0, nn = indicesCount; ii < nn; ii++) { - item.triangles[ii] = indices[ii]; - } - VertexPositionColorTextureColor[] itemVertices = item.vertices; - for (int ii = 0, v = 0, nn = verticesCount << 1; v < nn; ii++, v += 2) { - itemVertices[ii].Color = color; - itemVertices[ii].Color2 = darkColor; - itemVertices[ii].Position.X = vertices[v]; - itemVertices[ii].Position.Y = vertices[v + 1]; - itemVertices[ii].Position.Z = 0; - itemVertices[ii].TextureCoordinate.X = uvs[v]; - itemVertices[ii].TextureCoordinate.Y = uvs[v + 1]; - } - - clipper.ClipEnd(slot); - } - clipper.ClipEnd(); - } - } + if (premultipliedAlpha) + { + color = new Color( + skeletonR * slot.R * attachmentColorR * a, + skeletonG * slot.G * attachmentColorG * a, + skeletonB * slot.B * attachmentColorB * a, a); + } + else + { + color = new Color( + skeletonR * slot.R * attachmentColorR, + skeletonG * slot.G * attachmentColorG, + skeletonB * slot.B * attachmentColorB, a); + } + + Color darkColor = new Color(); + if (slot.HasSecondColor) + { + darkColor = new Color(slot.R2, slot.G2, slot.B2); + } + + // clip + if (clipper.IsClipping()) + { + clipper.ClipTriangles(vertices, verticesCount << 1, indices, indicesCount, uvs); + vertices = clipper.ClippedVertices.Items; + verticesCount = clipper.ClippedVertices.Count >> 1; + indices = clipper.ClippedTriangles.Items; + indicesCount = clipper.ClippedTriangles.Count; + uvs = clipper.ClippedUVs.Items; + } + + if (verticesCount == 0 || indicesCount == 0) + continue; + + // submit to batch + MeshItem item = batcher.NextItem(verticesCount, indicesCount); + item.texture = texture; + for (int ii = 0, nn = indicesCount; ii < nn; ii++) + { + item.triangles[ii] = indices[ii]; + } + VertexPositionColorTextureColor[] itemVertices = item.vertices; + for (int ii = 0, v = 0, nn = verticesCount << 1; v < nn; ii++, v += 2) + { + itemVertices[ii].Color = color; + itemVertices[ii].Color2 = darkColor; + itemVertices[ii].Position.X = vertices[v]; + itemVertices[ii].Position.Y = vertices[v + 1]; + itemVertices[ii].Position.Z = 0; + itemVertices[ii].TextureCoordinate.X = uvs[v]; + itemVertices[ii].TextureCoordinate.Y = uvs[v + 1]; + } + + clipper.ClipEnd(slot); + } + clipper.ClipEnd(); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/XnaLoader/XnaTextureLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/XnaLoader/XnaTextureLoader.cs index 928075e..6a6884a 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/XnaLoader/XnaTextureLoader.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.39/XnaLoader/XnaTextureLoader.cs @@ -30,21 +30,23 @@ *****************************************************************************/ using System; -using System.IO; -using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; -namespace Spine3_6_39 { - public class XnaTextureLoader : TextureLoader { - GraphicsDevice device; +namespace Spine3_6_39 +{ + public class XnaTextureLoader : TextureLoader + { + GraphicsDevice device; - public XnaTextureLoader (GraphicsDevice device) { - this.device = device; - } + public XnaTextureLoader(GraphicsDevice device) + { + this.device = device; + } - public void Load (AtlasPage page, String path) { - Texture2D texture = Util.LoadTexture(device, path); - page.rendererObject = texture; + public void Load(AtlasPage page, String path) + { + Texture2D texture = Util.LoadTexture(device, path); + page.rendererObject = texture; if (page.width == 0 || page.height == 0) { page.width = texture.Width; @@ -52,8 +54,9 @@ public void Load (AtlasPage page, String path) { } } - public void Unload (Object texture) { - ((Texture2D)texture).Dispose(); - } - } + public void Unload(Object texture) + { + ((Texture2D)texture).Dispose(); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Animation.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Animation.cs index 7a4ef78..98dc268 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Animation.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Animation.cs @@ -29,1367 +29,1599 @@ *****************************************************************************/ using System; -using System.Collections.Generic; - -namespace Spine3_6_53 { - public class Animation { - internal ExposedList timelines; - internal float duration; - internal String name; - - public string Name { get { return name; } } - public ExposedList Timelines { get { return timelines; } set { timelines = value; } } - public float Duration { get { return duration; } set { duration = value; } } - - public Animation (string name, ExposedList timelines, float duration) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - if (timelines == null) throw new ArgumentNullException("timelines", "timelines cannot be null."); - this.name = name; - this.timelines = timelines; - this.duration = duration; - } - - /// Applies all the animation's timelines to the specified skeleton. - /// - public void Apply (Skeleton skeleton, float lastTime, float time, bool loop, ExposedList events, float alpha, MixPose pose, MixDirection direction) { - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - - if (loop && duration != 0) { - time %= duration; - if (lastTime > 0) lastTime %= duration; - } - - ExposedList timelines = this.timelines; - for (int i = 0, n = timelines.Count; i < n; i++) - timelines.Items[i].Apply(skeleton, lastTime, time, events, alpha, pose, direction); - } - - /// After the first and before the last entry. - internal static int BinarySearch (float[] values, float target, int step) { - int low = 0; - int high = values.Length / step - 2; - if (high == 0) return step; - int current = (int)((uint)high >> 1); - while (true) { - if (values[(current + 1) * step] <= target) - low = current + 1; - else - high = current; - if (low == high) return (low + 1) * step; - current = (int)((uint)(low + high) >> 1); - } - } - - /// After the first and before the last entry. - internal static int BinarySearch (float[] values, float target) { - int low = 0; - int high = values.Length - 2; - if (high == 0) return 1; - int current = (int)((uint)high >> 1); - while (true) { - if (values[(current + 1)] <= target) - low = current + 1; - else - high = current; - if (low == high) return (low + 1); - current = (int)((uint)(low + high) >> 1); - } - } - - internal static int LinearSearch (float[] values, float target, int step) { - for (int i = 0, last = values.Length - step; i <= last; i += step) - if (values[i] > target) return i; - return -1; - } - } - - public interface Timeline { - /// Sets the value(s) for the specified time. - /// The skeleton the timeline is being applied to. This provides access to the bones, slots, and other skeleton components the timeline may change. - /// 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). - /// The time within the animation. Most timelines find the key before and the key after this time so they can interpolate between the keys. - /// If any events are fired, they are added to this list. Can be null to ignore firing events or if the timeline does not fire events. May be null. - /// 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). - /// Controls how mixing is applied when alpha is than 1. - /// Indicates whether the timeline is mixing in or out. Used by timelines which perform instant transitions such as DrawOrderTimeline and AttachmentTimeline. - void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, MixPose pose, MixDirection direction); - int PropertyId { get; } - } - - /// - /// Controls how a timeline is mixed with the setup or current pose. - /// - public enum MixPose { - /// The timeline value is mixed with the setup pose (the current pose is not used). - Setup, - /// The timeline value is mixed with the current pose. The setup pose is used as the timeline value before the first key, - /// except for timelines which perform instant transitions, such as DrawOrderTimeline or AttachmentTimeline. - Current, - /// The timeline value is mixed with the current pose. No change is made before the first key (the current pose is kept until the first key). - CurrentLayered - } - - /// - /// 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). - /// - public enum MixDirection { - In, - Out - } - - internal enum TimelineType { - Rotate = 0, Translate, Scale, Shear, // - Attachment, Color, Deform, // - Event, DrawOrder, // - IkConstraint, TransformConstraint, // - PathConstraintPosition, PathConstraintSpacing, PathConstraintMix, // - TwoColor - } - - /// Base class for frames that use an interpolation bezier curve. - abstract public class CurveTimeline : Timeline { - protected const float LINEAR = 0, STEPPED = 1, BEZIER = 2; - protected const int BEZIER_SIZE = 10 * 2 - 1; - - internal float[] curves; // type, x, y, ... - public int FrameCount { get { return curves.Length / BEZIER_SIZE + 1; } } - - public CurveTimeline (int frameCount) { - if (frameCount <= 0) throw new ArgumentException("frameCount must be > 0: " + frameCount, "frameCount"); - curves = new float[(frameCount - 1) * BEZIER_SIZE]; - } - - abstract public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction); - - abstract public int PropertyId { get; } - - public void SetLinear (int frameIndex) { - curves[frameIndex * BEZIER_SIZE] = LINEAR; - } - - public void SetStepped (int frameIndex) { - curves[frameIndex * BEZIER_SIZE] = STEPPED; - } - - /// Sets the control handle positions for an interpolation bezier curve used to transition from this keyframe to the next. - /// cx1 and cx2 are from 0 to 1, representing the percent of time between the two keyframes. cy1 and cy2 are the percent of - /// the difference between the keyframe's values. - public void SetCurve (int frameIndex, float cx1, float cy1, float cx2, float cy2) { - float tmpx = (-cx1 * 2 + cx2) * 0.03f, tmpy = (-cy1 * 2 + cy2) * 0.03f; - float dddfx = ((cx1 - cx2) * 3 + 1) * 0.006f, dddfy = ((cy1 - cy2) * 3 + 1) * 0.006f; - float ddfx = tmpx * 2 + dddfx, ddfy = tmpy * 2 + dddfy; - float dfx = cx1 * 0.3f + tmpx + dddfx * 0.16666667f, dfy = cy1 * 0.3f + tmpy + dddfy * 0.16666667f; - - int i = frameIndex * BEZIER_SIZE; - float[] curves = this.curves; - curves[i++] = BEZIER; - - float x = dfx, y = dfy; - for (int n = i + BEZIER_SIZE - 1; i < n; i += 2) { - curves[i] = x; - curves[i + 1] = y; - dfx += ddfx; - dfy += ddfy; - ddfx += dddfx; - ddfy += dddfy; - x += dfx; - y += dfy; - } - } - - public float GetCurvePercent (int frameIndex, float percent) { - percent = MathUtils.Clamp (percent, 0, 1); - float[] curves = this.curves; - int i = frameIndex * BEZIER_SIZE; - float type = curves[i]; - if (type == LINEAR) return percent; - if (type == STEPPED) return 0; - i++; - float x = 0; - for (int start = i, n = i + BEZIER_SIZE - 1; i < n; i += 2) { - x = curves[i]; - if (x >= percent) { - float prevX, prevY; - if (i == start) { - prevX = 0; - prevY = 0; - } else { - prevX = curves[i - 2]; - prevY = curves[i - 1]; - } - return prevY + (curves[i + 1] - prevY) * (percent - prevX) / (x - prevX); - } - } - float y = curves[i - 1]; - return y + (1 - y) * (percent - x) / (1 - x); // Last point is 1,1. - } - public float GetCurveType (int frameIndex) { - return curves[frameIndex * BEZIER_SIZE]; - } - } - - public class RotateTimeline : CurveTimeline { - public const int ENTRIES = 2; - internal const int PREV_TIME = -2, PREV_ROTATION = -1; - internal const int ROTATION = 1; - - internal int boneIndex; - internal float[] frames; - - public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, angle, ... - - override public int PropertyId { - get { return ((int)TimelineType.Rotate << 24) + boneIndex; } - } - - public RotateTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount << 1]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, float degrees) { - frameIndex <<= 1; - frames[frameIndex] = time; - frames[frameIndex + ROTATION] = degrees; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - - float[] frames = this.frames; - if (time < frames[0]) { - switch (pose) { - case MixPose.Setup: - bone.rotation = bone.data.rotation; - return; - case MixPose.Current: - float rr = bone.data.rotation - bone.rotation; - rr -= (16384 - (int)(16384.499999999996 - rr / 360)) * 360; - bone.rotation += rr * alpha; - return; - } - return; - } - - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - if (pose == MixPose.Setup) { - bone.rotation = bone.data.rotation + frames[frames.Length + PREV_ROTATION] * alpha; - } else { - float rr = bone.data.rotation + frames[frames.Length + PREV_ROTATION] - bone.rotation; - rr -= (16384 - (int)(16384.499999999996 - rr / 360)) * 360; // Wrap within -180 and 180. - bone.rotation += rr * alpha; - } - return; - } - - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - float prevRotation = frames[frame + PREV_ROTATION]; - float frameTime = frames[frame]; - float percent = GetCurvePercent((frame >> 1) - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - float r = frames[frame + ROTATION] - prevRotation; - r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; - r = prevRotation + r * percent; - if (pose == MixPose.Setup) { - r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; - bone.rotation = bone.data.rotation + r * alpha; - } else { - r = bone.data.rotation + r - bone.rotation; - r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; - bone.rotation += r * alpha; - } - } - } - - public class TranslateTimeline : CurveTimeline { - public const int ENTRIES = 3; - protected const int PREV_TIME = -3, PREV_X = -2, PREV_Y = -1; - protected const int X = 1, Y = 2; - - internal int boneIndex; - internal float[] frames; - - public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, value, value, ... - - override public int PropertyId { - get { return ((int)TimelineType.Translate << 24) + boneIndex; } - } - - public TranslateTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, float x, float y) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + X] = x; - frames[frameIndex + Y] = y; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - - float[] frames = this.frames; - if (time < frames[0]) { - switch (pose) { - case MixPose.Setup: - bone.x = bone.data.x; - bone.y = bone.data.y; - return; - case MixPose.Current: - bone.x += (bone.data.x - bone.x) * alpha; - bone.y += (bone.data.y - bone.y) * alpha; - return; - } - return; - } - - float x, y; - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - x = frames[frames.Length + PREV_X]; - y = frames[frames.Length + PREV_Y]; - } else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - x = frames[frame + PREV_X]; - y = frames[frame + PREV_Y]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - x += (frames[frame + X] - x) * percent; - y += (frames[frame + Y] - y) * percent; - } - if (pose == MixPose.Setup) { - bone.x = bone.data.x + x * alpha; - bone.y = bone.data.y + y * alpha; - } else { - bone.x += (bone.data.x + x - bone.x) * alpha; - bone.y += (bone.data.y + y - bone.y) * alpha; - } - } - } - - public class ScaleTimeline : TranslateTimeline { - override public int PropertyId { - get { return ((int)TimelineType.Scale << 24) + boneIndex; } - } - - public ScaleTimeline (int frameCount) - : base(frameCount) { - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - - float[] frames = this.frames; - if (time < frames[0]) { - switch (pose) { - case MixPose.Setup: - bone.scaleX = bone.data.scaleX; - bone.scaleY = bone.data.scaleY; - return; - case MixPose.Current: - bone.scaleX += (bone.data.scaleX - bone.scaleX) * alpha; - bone.scaleY += (bone.data.scaleY - bone.scaleY) * alpha; - return; - } - return; - } - - float x, y; - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - x = frames[frames.Length + PREV_X] * bone.data.scaleX; - y = frames[frames.Length + PREV_Y] * bone.data.scaleY; - } else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - x = frames[frame + PREV_X]; - y = frames[frame + PREV_Y]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - x = (x + (frames[frame + X] - x) * percent) * bone.data.scaleX; - y = (y + (frames[frame + Y] - y) * percent) * bone.data.scaleY; - } - if (alpha == 1) { - bone.scaleX = x; - bone.scaleY = y; - } else { - float bx, by; - if (pose == MixPose.Setup) { - bx = bone.data.scaleX; - by = bone.data.scaleY; - } else { - bx = bone.scaleX; - by = bone.scaleY; - } - // Mixing out uses sign of setup or current pose, else use sign of key. - if (direction == MixDirection.Out) { - x = (x >= 0 ? x : -x) * (bx >= 0 ? 1 : -1); - y = (y >= 0 ? y : -y) * (by >= 0 ? 1 : -1); - } else { - bx = (bx >= 0 ? bx : -bx) * (x >= 0 ? 1 : -1); - by = (by >= 0 ? by : -by) * (y >= 0 ? 1 : -1); - } - bone.scaleX = bx + (x - bx) * alpha; - bone.scaleY = by + (y - by) * alpha; - } - } - } - - public class ShearTimeline : TranslateTimeline { - override public int PropertyId { - get { return ((int)TimelineType.Shear << 24) + boneIndex; } - } - - public ShearTimeline (int frameCount) - : base(frameCount) { - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - float[] frames = this.frames; - if (time < frames[0]) { - switch (pose) { - case MixPose.Setup: - bone.shearX = bone.data.shearX; - bone.shearY = bone.data.shearY; - return; - case MixPose.Current: - bone.shearX += (bone.data.shearX - bone.shearX) * alpha; - bone.shearY += (bone.data.shearY - bone.shearY) * alpha; - return; - } - return; - } - - float x, y; - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - x = frames[frames.Length + PREV_X]; - y = frames[frames.Length + PREV_Y]; - } else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - x = frames[frame + PREV_X]; - y = frames[frame + PREV_Y]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - x = x + (frames[frame + X] - x) * percent; - y = y + (frames[frame + Y] - y) * percent; - } - if (pose == MixPose.Setup) { - bone.shearX = bone.data.shearX + x * alpha; - bone.shearY = bone.data.shearY + y * alpha; - } else { - bone.shearX += (bone.data.shearX + x - bone.shearX) * alpha; - bone.shearY += (bone.data.shearY + y - bone.shearY) * alpha; - } - } - } - - public class ColorTimeline : CurveTimeline { - public const int ENTRIES = 5; - protected const int PREV_TIME = -5, PREV_R = -4, PREV_G = -3, PREV_B = -2, PREV_A = -1; - protected const int R = 1, G = 2, B = 3, A = 4; - - internal int slotIndex; - internal float[] frames; - - public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, r, g, b, a, ... - - override public int PropertyId { - get { return ((int)TimelineType.Color << 24) + slotIndex; } - } - - public ColorTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, float r, float g, float b, float a) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + R] = r; - frames[frameIndex + G] = g; - frames[frameIndex + B] = b; - frames[frameIndex + A] = a; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { - Slot slot = skeleton.slots.Items[slotIndex]; - float[] frames = this.frames; - if (time < frames[0]) { - var slotData = slot.data; - switch (pose) { - case MixPose.Setup: - slot.r = slotData.r; - slot.g = slotData.g; - slot.b = slotData.b; - slot.a = slotData.a; - return; - case MixPose.Current: - slot.r += (slot.r - slotData.r) * alpha; - slot.g += (slot.g - slotData.g) * alpha; - slot.b += (slot.b - slotData.b) * alpha; - slot.a += (slot.a - slotData.a) * alpha; - return; - } - return; - } - - float r, g, b, a; - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - int i = frames.Length; - r = frames[i + PREV_R]; - g = frames[i + PREV_G]; - b = frames[i + PREV_B]; - a = frames[i + PREV_A]; - } else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - r = frames[frame + PREV_R]; - g = frames[frame + PREV_G]; - b = frames[frame + PREV_B]; - a = frames[frame + PREV_A]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - r += (frames[frame + R] - r) * percent; - g += (frames[frame + G] - g) * percent; - b += (frames[frame + B] - b) * percent; - a += (frames[frame + A] - a) * percent; - } - if (alpha == 1) { - slot.r = r; - slot.g = g; - slot.b = b; - slot.a = a; - } else { - float br, bg, bb, ba; - if (pose == MixPose.Setup) { - br = slot.data.r; - bg = slot.data.g; - bb = slot.data.b; - ba = slot.data.a; - } else { - br = slot.r; - bg = slot.g; - bb = slot.b; - ba = slot.a; - } - slot.r = br + ((r - br) * alpha); - slot.g = bg + ((g - bg) * alpha); - slot.b = bb + ((b - bb) * alpha); - slot.a = ba + ((a - ba) * alpha); - } - } - } - - public class TwoColorTimeline : CurveTimeline { - public const int ENTRIES = 8; - protected const int PREV_TIME = -8, PREV_R = -7, PREV_G = -6, PREV_B = -5, PREV_A = -4; - protected const int PREV_R2 = -3, PREV_G2 = -2, PREV_B2 = -1; - protected const int R = 1, G = 2, B = 3, A = 4, R2 = 5, G2 = 6, B2 = 7; - - internal int slotIndex; - internal float[] frames; // time, r, g, b, a, r2, g2, b2, ... - - public int SlotIndex { - get { return slotIndex; } - set { - if (value < 0) - throw new ArgumentOutOfRangeException("index must be >= 0."); - slotIndex = value; - } - } - - public float[] Frames { get { return frames; } } - - override public int PropertyId { - get { return ((int)TimelineType.TwoColor << 24) + slotIndex; } - } - - public TwoColorTimeline (int frameCount) : - base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, float r, float g, float b, float a, float r2, float g2, float b2) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + R] = r; - frames[frameIndex + G] = g; - frames[frameIndex + B] = b; - frames[frameIndex + A] = a; - frames[frameIndex + R2] = r2; - frames[frameIndex + G2] = g2; - frames[frameIndex + B2] = b2; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { - Slot slot = skeleton.slots.Items[slotIndex]; - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - var slotData = slot.data; - switch (pose) { - case MixPose.Setup: - // slot.color.set(slot.data.color); - // slot.darkColor.set(slot.data.darkColor); - slot.r = slotData.r; - slot.g = slotData.g; - slot.b = slotData.b; - slot.a = slotData.a; - slot.r2 = slotData.r2; - slot.g2 = slotData.g2; - slot.b2 = slotData.b2; - return; - case MixPose.Current: - slot.r += (slot.r - slotData.r) * alpha; - slot.g += (slot.g - slotData.g) * alpha; - slot.b += (slot.b - slotData.b) * alpha; - slot.a += (slot.a - slotData.a) * alpha; - slot.r2 += (slot.r2 - slotData.r2) * alpha; - slot.g2 += (slot.g2 - slotData.g2) * alpha; - slot.b2 += (slot.b2 - slotData.b2) * alpha; - return; - } - return; - } - - float r, g, b, a, r2, g2, b2; - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - int i = frames.Length; - r = frames[i + PREV_R]; - g = frames[i + PREV_G]; - b = frames[i + PREV_B]; - a = frames[i + PREV_A]; - r2 = frames[i + PREV_R2]; - g2 = frames[i + PREV_G2]; - b2 = frames[i + PREV_B2]; - } else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - r = frames[frame + PREV_R]; - g = frames[frame + PREV_G]; - b = frames[frame + PREV_B]; - a = frames[frame + PREV_A]; - r2 = frames[frame + PREV_R2]; - g2 = frames[frame + PREV_G2]; - b2 = frames[frame + PREV_B2]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - r += (frames[frame + R] - r) * percent; - g += (frames[frame + G] - g) * percent; - b += (frames[frame + B] - b) * percent; - a += (frames[frame + A] - a) * percent; - r2 += (frames[frame + R2] - r2) * percent; - g2 += (frames[frame + G2] - g2) * percent; - b2 += (frames[frame + B2] - b2) * percent; - } - if (alpha == 1) { - slot.r = r; - slot.g = g; - slot.b = b; - slot.a = a; - slot.r2 = r2; - slot.g2 = g2; - slot.b2 = b2; - } else { - float br, bg, bb, ba, br2, bg2, bb2; - if (pose == MixPose.Setup) { - br = slot.data.r; - bg = slot.data.g; - bb = slot.data.b; - ba = slot.data.a; - br2 = slot.data.r2; - bg2 = slot.data.g2; - bb2 = slot.data.b2; - } else { - br = slot.r; - bg = slot.g; - bb = slot.b; - ba = slot.a; - br2 = slot.r2; - bg2 = slot.g2; - bb2 = slot.b2; - } - slot.r = br + ((r - br) * alpha); - slot.g = bg + ((g - bg) * alpha); - slot.b = bb + ((b - bb) * alpha); - slot.a = ba + ((a - ba) * alpha); - slot.r2 = br2 + ((r2 - br2) * alpha); - slot.g2 = bg2 + ((g2 - bg2) * alpha); - slot.b2 = bb2 + ((b2 - bb2) * alpha); - } - } - - } - - public class AttachmentTimeline : Timeline { - internal int slotIndex; - internal float[] frames; - internal string[] attachmentNames; - - public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, ... - public string[] AttachmentNames { get { return attachmentNames; } set { attachmentNames = value; } } - public int FrameCount { get { return frames.Length; } } - - public int PropertyId { - get { return ((int)TimelineType.Attachment << 24) + slotIndex; } - } - - public AttachmentTimeline (int frameCount) { - frames = new float[frameCount]; - attachmentNames = new String[frameCount]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, String attachmentName) { - frames[frameIndex] = time; - attachmentNames[frameIndex] = attachmentName; - } - - public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { - string attachmentName; - Slot slot = skeleton.slots.Items[slotIndex]; - if (direction == MixDirection.Out && pose == MixPose.Setup) { - attachmentName = slot.data.attachmentName; - slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); - return; - } - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - if (pose == MixPose.Setup) { - attachmentName = slot.data.attachmentName; - slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); - } - return; - } - - int frameIndex; - if (time >= frames[frames.Length - 1]) // Time is after last frame. - frameIndex = frames.Length - 1; - else - frameIndex = Animation.BinarySearch(frames, time, 1) - 1; - - attachmentName = attachmentNames[frameIndex]; - slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); - } - } - - public class DeformTimeline : CurveTimeline { - internal int slotIndex; - internal float[] frames; - internal float[][] frameVertices; - internal VertexAttachment attachment; - - public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, ... - public float[][] Vertices { get { return frameVertices; } set { frameVertices = value; } } - public VertexAttachment Attachment { get { return attachment; } set { attachment = value; } } - - override public int PropertyId { - get { return ((int)TimelineType.Deform << 24) + attachment.id + slotIndex; } - } - - public DeformTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount]; - frameVertices = new float[frameCount][]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, float[] vertices) { - frames[frameIndex] = time; - frameVertices[frameIndex] = vertices; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { - Slot slot = skeleton.slots.Items[slotIndex]; - VertexAttachment vertexAttachment = slot.attachment as VertexAttachment; - if (vertexAttachment == null || !vertexAttachment.ApplyDeform(attachment)) return; - - var verticesArray = slot.attachmentVertices; - if (verticesArray.Count == 0) alpha = 1; - - float[][] frameVertices = this.frameVertices; - int vertexCount = frameVertices[0].Length; - float[] frames = this.frames; - float[] vertices; - - if (time < frames[0]) { - - switch (pose) { - case MixPose.Setup: - verticesArray.Clear(); - return; - case MixPose.Current: - if (alpha == 1) { - verticesArray.Clear(); - return; - } - - // verticesArray.SetSize(vertexCount) // Ensure size and preemptively set count. - if (verticesArray.Capacity < vertexCount) verticesArray.Capacity = vertexCount; - verticesArray.Count = vertexCount; - vertices = verticesArray.Items; - - if (vertexAttachment.bones == null) { - // Unweighted vertex positions. - float[] setupVertices = vertexAttachment.vertices; - for (int i = 0; i < vertexCount; i++) - vertices[i] += (setupVertices[i] - vertices[i]) * alpha; - } else { - // Weighted deform offsets. - alpha = 1 - alpha; - for (int i = 0; i < vertexCount; i++) - vertices[i] *= alpha; - } - return; - default: - return; - } - - } - - // verticesArray.SetSize(vertexCount) // Ensure size and preemptively set count. - if (verticesArray.Capacity < vertexCount) verticesArray.Capacity = vertexCount; - verticesArray.Count = vertexCount; - vertices = verticesArray.Items; - - if (time >= frames[frames.Length - 1]) { // Time is after last frame. - float[] lastVertices = frameVertices[frames.Length - 1]; - if (alpha == 1) { - // Vertex positions or deform offsets, no alpha. - Array.Copy(lastVertices, 0, vertices, 0, vertexCount); - } else if (pose == MixPose.Setup) { - if (vertexAttachment.bones == null) { - // Unweighted vertex positions, with alpha. - float[] setupVertices = vertexAttachment.vertices; - for (int i = 0; i < vertexCount; i++) { - float setup = setupVertices[i]; - vertices[i] = setup + (lastVertices[i] - setup) * alpha; - } - } else { - // Weighted deform offsets, with alpha. - for (int i = 0; i < vertexCount; i++) - vertices[i] = lastVertices[i] * alpha; - } - } else { - // Vertex positions or deform offsets, with alpha. - for (int i = 0; i < vertexCount; i++) - vertices[i] += (lastVertices[i] - vertices[i]) * alpha; - } - return; - } - - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time); - float[] prevVertices = frameVertices[frame - 1]; - float[] nextVertices = frameVertices[frame]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame - 1, 1 - (time - frameTime) / (frames[frame - 1] - frameTime)); - - if (alpha == 1) { - // Vertex positions or deform offsets, no alpha. - for (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - vertices[i] = prev + (nextVertices[i] - prev) * percent; - } - } else if (pose == MixPose.Setup) { - if (vertexAttachment.bones == null) { - // Unweighted vertex positions, with alpha. - var setupVertices = vertexAttachment.vertices; - for (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i], setup = setupVertices[i]; - vertices[i] = setup + (prev + (nextVertices[i] - prev) * percent - setup) * alpha; - } - } else { - // Weighted deform offsets, with alpha. - for (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - vertices[i] = (prev + (nextVertices[i] - prev) * percent) * alpha; - } - } - } else { - // Vertex positions or deform offsets, with alpha. - for (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - vertices[i] += (prev + (nextVertices[i] - prev) * percent - vertices[i]) * alpha; - } - } - } - } - - public class EventTimeline : Timeline { - internal float[] frames; - private Event[] events; - - public float[] Frames { get { return frames; } set { frames = value; } } // time, ... - public Event[] Events { get { return events; } set { events = value; } } - public int FrameCount { get { return frames.Length; } } - - public int PropertyId { - get { return ((int)TimelineType.Event << 24); } - } - - public EventTimeline (int frameCount) { - frames = new float[frameCount]; - events = new Event[frameCount]; - } - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, Event e) { - frames[frameIndex] = e.Time; - events[frameIndex] = e; - } - - /// Fires events for frames > lastTime and <= time. - public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { - if (firedEvents == null) return; - float[] frames = this.frames; - int frameCount = frames.Length; - - if (lastTime > time) { // Fire events after last time for looped animations. - Apply(skeleton, lastTime, int.MaxValue, firedEvents, alpha, pose, direction); - lastTime = -1f; - } else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame. - return; - if (time < frames[0]) return; // Time is before first frame. - - int frame; - if (lastTime < frames[0]) - frame = 0; - else { - frame = Animation.BinarySearch(frames, lastTime); - float frameTime = frames[frame]; - while (frame > 0) { // Fire multiple events with the same frame. - if (frames[frame - 1] != frameTime) break; - frame--; - } - } - for (; frame < frameCount && time >= frames[frame]; frame++) - firedEvents.Add(events[frame]); - } - } - - public class DrawOrderTimeline : Timeline { - internal float[] frames; - private int[][] drawOrders; - - public float[] Frames { get { return frames; } set { frames = value; } } // time, ... - public int[][] DrawOrders { get { return drawOrders; } set { drawOrders = value; } } - public int FrameCount { get { return frames.Length; } } - - public int PropertyId { - get { return ((int)TimelineType.DrawOrder << 24); } - } - - public DrawOrderTimeline (int frameCount) { - frames = new float[frameCount]; - drawOrders = new int[frameCount][]; - } - - /// Sets the time and value of the specified keyframe. - /// May be null to use bind pose draw order. - public void SetFrame (int frameIndex, float time, int[] drawOrder) { - frames[frameIndex] = time; - drawOrders[frameIndex] = drawOrder; - } - - public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { - ExposedList drawOrder = skeleton.drawOrder; - ExposedList slots = skeleton.slots; - if (direction == MixDirection.Out && pose == MixPose.Setup) { - Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count); - return; - } - - float[] frames = this.frames; - if (time < frames[0]) { - if (pose == MixPose.Setup) Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count); - return; - } - - int frame; - if (time >= frames[frames.Length - 1]) // Time is after last frame. - frame = frames.Length - 1; - else - frame = Animation.BinarySearch(frames, time) - 1; - - int[] drawOrderToSetupIndex = drawOrders[frame]; - if (drawOrderToSetupIndex == null) { - drawOrder.Clear(); - for (int i = 0, n = slots.Count; i < n; i++) - drawOrder.Add(slots.Items[i]); - } else { - var drawOrderItems = drawOrder.Items; - var slotsItems = slots.Items; - for (int i = 0, n = drawOrderToSetupIndex.Length; i < n; i++) - drawOrderItems[i] = slotsItems[drawOrderToSetupIndex[i]]; - } - } - } - - public class IkConstraintTimeline : CurveTimeline { - public const int ENTRIES = 3; - private const int PREV_TIME = -3, PREV_MIX = -2, PREV_BEND_DIRECTION = -1; - private const int MIX = 1, BEND_DIRECTION = 2; - - internal int ikConstraintIndex; - internal float[] frames; - - public int IkConstraintIndex { get { return ikConstraintIndex; } set { ikConstraintIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, mix, bendDirection, ... - - override public int PropertyId { - get { return ((int)TimelineType.IkConstraint << 24) + ikConstraintIndex; } - } - - public IkConstraintTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - /// Sets the time, mix and bend direction of the specified keyframe. - public void SetFrame (int frameIndex, float time, float mix, int bendDirection) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + MIX] = mix; - frames[frameIndex + BEND_DIRECTION] = bendDirection; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { - IkConstraint constraint = skeleton.ikConstraints.Items[ikConstraintIndex]; - float[] frames = this.frames; - if (time < frames[0]) { - switch (pose) { - case MixPose.Setup: - constraint.mix = constraint.data.mix; - constraint.bendDirection = constraint.data.bendDirection; - return; - case MixPose.Current: - constraint.mix += (constraint.data.mix - constraint.mix) * alpha; - constraint.bendDirection = constraint.data.bendDirection; - return; - } - return; - } - - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - if (pose == MixPose.Setup) { - constraint.mix = constraint.data.mix + (frames[frames.Length + PREV_MIX] - constraint.data.mix) * alpha; - constraint.bendDirection = direction == MixDirection.Out ? constraint.data.bendDirection - : (int)frames[frames.Length + PREV_BEND_DIRECTION]; - } else { - constraint.mix += (frames[frames.Length + PREV_MIX] - constraint.mix) * alpha; - if (direction == MixDirection.In) constraint.bendDirection = (int)frames[frames.Length + PREV_BEND_DIRECTION]; - } - return; - } - - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - float mix = frames[frame + PREV_MIX]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - if (pose == MixPose.Setup) { - constraint.mix = constraint.data.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.data.mix) * alpha; - constraint.bendDirection = direction == MixDirection.Out ? constraint.data.bendDirection : (int)frames[frame + PREV_BEND_DIRECTION]; - } else { - constraint.mix += (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha; - if (direction == MixDirection.In) constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION]; - } - } - } - - public class TransformConstraintTimeline : CurveTimeline { - public const int ENTRIES = 5; - private const int PREV_TIME = -5, PREV_ROTATE = -4, PREV_TRANSLATE = -3, PREV_SCALE = -2, PREV_SHEAR = -1; - private const int ROTATE = 1, TRANSLATE = 2, SCALE = 3, SHEAR = 4; - - internal int transformConstraintIndex; - internal float[] frames; - - public int TransformConstraintIndex { get { return transformConstraintIndex; } set { transformConstraintIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, scale mix, shear mix, ... - - override public int PropertyId { - get { return ((int)TimelineType.TransformConstraint << 24) + transformConstraintIndex; } - } - - public TransformConstraintTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - public void SetFrame (int frameIndex, float time, float rotateMix, float translateMix, float scaleMix, float shearMix) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + ROTATE] = rotateMix; - frames[frameIndex + TRANSLATE] = translateMix; - frames[frameIndex + SCALE] = scaleMix; - frames[frameIndex + SHEAR] = shearMix; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { - TransformConstraint constraint = skeleton.transformConstraints.Items[transformConstraintIndex]; - float[] frames = this.frames; - if (time < frames[0]) { - var data = constraint.data; - switch (pose) { - case MixPose.Setup: - constraint.rotateMix = data.rotateMix; - constraint.translateMix = data.translateMix; - constraint.scaleMix = data.scaleMix; - constraint.shearMix = data.shearMix; - return; - case MixPose.Current: - constraint.rotateMix += (data.rotateMix - constraint.rotateMix) * alpha; - constraint.translateMix += (data.translateMix - constraint.translateMix) * alpha; - constraint.scaleMix += (data.scaleMix - constraint.scaleMix) * alpha; - constraint.shearMix += (data.shearMix - constraint.shearMix) * alpha; - return; - } - return; - } - - float rotate, translate, scale, shear; - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - int i = frames.Length; - rotate = frames[i + PREV_ROTATE]; - translate = frames[i + PREV_TRANSLATE]; - scale = frames[i + PREV_SCALE]; - shear = frames[i + PREV_SHEAR]; - } else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - rotate = frames[frame + PREV_ROTATE]; - translate = frames[frame + PREV_TRANSLATE]; - scale = frames[frame + PREV_SCALE]; - shear = frames[frame + PREV_SHEAR]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - rotate += (frames[frame + ROTATE] - rotate) * percent; - translate += (frames[frame + TRANSLATE] - translate) * percent; - scale += (frames[frame + SCALE] - scale) * percent; - shear += (frames[frame + SHEAR] - shear) * percent; - } - if (pose == MixPose.Setup) { - TransformConstraintData data = constraint.data; - constraint.rotateMix = data.rotateMix + (rotate - data.rotateMix) * alpha; - constraint.translateMix = data.translateMix + (translate - data.translateMix) * alpha; - constraint.scaleMix = data.scaleMix + (scale - data.scaleMix) * alpha; - constraint.shearMix = data.shearMix + (shear - data.shearMix) * alpha; - } else { - constraint.rotateMix += (rotate - constraint.rotateMix) * alpha; - constraint.translateMix += (translate - constraint.translateMix) * alpha; - constraint.scaleMix += (scale - constraint.scaleMix) * alpha; - constraint.shearMix += (shear - constraint.shearMix) * alpha; - } - } - } - - public class PathConstraintPositionTimeline : CurveTimeline { - public const int ENTRIES = 2; - protected const int PREV_TIME = -2, PREV_VALUE = -1; - protected const int VALUE = 1; - - internal int pathConstraintIndex; - internal float[] frames; - - override public int PropertyId { - get { return ((int)TimelineType.PathConstraintPosition << 24) + pathConstraintIndex; } - } - - public PathConstraintPositionTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - public int PathConstraintIndex { get { return pathConstraintIndex; } set { pathConstraintIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, position, ... - - /// Sets the time and value of the specified keyframe. - public void SetFrame (int frameIndex, float time, float value) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + VALUE] = value; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { - PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; - float[] frames = this.frames; - if (time < frames[0]) { - switch (pose) { - case MixPose.Setup: - constraint.position = constraint.data.position; - return; - case MixPose.Current: - constraint.position += (constraint.data.position - constraint.position) * alpha; - return; - } - return; - } - - float position; - if (time >= frames[frames.Length - ENTRIES]) // Time is after last frame. - position = frames[frames.Length + PREV_VALUE]; - else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - position = frames[frame + PREV_VALUE]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - position += (frames[frame + VALUE] - position) * percent; - } - if (pose == MixPose.Setup) - constraint.position = constraint.data.position + (position - constraint.data.position) * alpha; - else - constraint.position += (position - constraint.position) * alpha; - } - } - - public class PathConstraintSpacingTimeline : PathConstraintPositionTimeline { - override public int PropertyId { - get { return ((int)TimelineType.PathConstraintSpacing << 24) + pathConstraintIndex; } - } - - public PathConstraintSpacingTimeline (int frameCount) - : base(frameCount) { - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { - PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; - float[] frames = this.frames; - if (time < frames[0]) { - switch (pose) { - case MixPose.Setup: - constraint.spacing = constraint.data.spacing; - return; - case MixPose.Current: - constraint.spacing += (constraint.data.spacing - constraint.spacing) * alpha; - return; - } - return; - } - - float spacing; - if (time >= frames[frames.Length - ENTRIES]) // Time is after last frame. - spacing = frames[frames.Length + PREV_VALUE]; - else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - spacing = frames[frame + PREV_VALUE]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - spacing += (frames[frame + VALUE] - spacing) * percent; - } - - if (pose == MixPose.Setup) - constraint.spacing = constraint.data.spacing + (spacing - constraint.data.spacing) * alpha; - else - constraint.spacing += (spacing - constraint.spacing) * alpha; - } - } - - public class PathConstraintMixTimeline : CurveTimeline { - public const int ENTRIES = 3; - private const int PREV_TIME = -3, PREV_ROTATE = -2, PREV_TRANSLATE = -1; - private const int ROTATE = 1, TRANSLATE = 2; - - internal int pathConstraintIndex; - internal float[] frames; - - public int PathConstraintIndex { get { return pathConstraintIndex; } set { pathConstraintIndex = value; } } - public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, ... - - override public int PropertyId { - get { return ((int)TimelineType.PathConstraintMix << 24) + pathConstraintIndex; } - } - - public PathConstraintMixTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - /// Sets the time and mixes of the specified keyframe. - public void SetFrame (int frameIndex, float time, float rotateMix, float translateMix) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + ROTATE] = rotateMix; - frames[frameIndex + TRANSLATE] = translateMix; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) { - PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; - float[] frames = this.frames; - if (time < frames[0]) { - switch (pose) { - case MixPose.Setup: - constraint.rotateMix = constraint.data.rotateMix; - constraint.translateMix = constraint.data.translateMix; - return; - case MixPose.Current: - constraint.rotateMix += (constraint.data.rotateMix - constraint.rotateMix) * alpha; - constraint.translateMix += (constraint.data.translateMix - constraint.translateMix) * alpha; - return; - } - return; - } - - float rotate, translate; - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - rotate = frames[frames.Length + PREV_ROTATE]; - translate = frames[frames.Length + PREV_TRANSLATE]; - } else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - rotate = frames[frame + PREV_ROTATE]; - translate = frames[frame + PREV_TRANSLATE]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - rotate += (frames[frame + ROTATE] - rotate) * percent; - translate += (frames[frame + TRANSLATE] - translate) * percent; - } - - if (pose == MixPose.Setup) { - constraint.rotateMix = constraint.data.rotateMix + (rotate - constraint.data.rotateMix) * alpha; - constraint.translateMix = constraint.data.translateMix + (translate - constraint.data.translateMix) * alpha; - } else { - constraint.rotateMix += (rotate - constraint.rotateMix) * alpha; - constraint.translateMix += (translate - constraint.translateMix) * alpha; - } - } - } + +namespace Spine3_6_53 +{ + public class Animation + { + internal ExposedList timelines; + internal float duration; + internal String name; + + public string Name { get { return name; } } + public ExposedList Timelines { get { return timelines; } set { timelines = value; } } + public float Duration { get { return duration; } set { duration = value; } } + + public Animation(string name, ExposedList timelines, float duration) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + if (timelines == null) throw new ArgumentNullException("timelines", "timelines cannot be null."); + this.name = name; + this.timelines = timelines; + this.duration = duration; + } + + /// Applies all the animation's timelines to the specified skeleton. + /// + public void Apply(Skeleton skeleton, float lastTime, float time, bool loop, ExposedList events, float alpha, MixPose pose, MixDirection direction) + { + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + + if (loop && duration != 0) + { + time %= duration; + if (lastTime > 0) lastTime %= duration; + } + + ExposedList timelines = this.timelines; + for (int i = 0, n = timelines.Count; i < n; i++) + timelines.Items[i].Apply(skeleton, lastTime, time, events, alpha, pose, direction); + } + + /// After the first and before the last entry. + internal static int BinarySearch(float[] values, float target, int step) + { + int low = 0; + int high = values.Length / step - 2; + if (high == 0) return step; + int current = (int)((uint)high >> 1); + while (true) + { + if (values[(current + 1) * step] <= target) + low = current + 1; + else + high = current; + if (low == high) return (low + 1) * step; + current = (int)((uint)(low + high) >> 1); + } + } + + /// After the first and before the last entry. + internal static int BinarySearch(float[] values, float target) + { + int low = 0; + int high = values.Length - 2; + if (high == 0) return 1; + int current = (int)((uint)high >> 1); + while (true) + { + if (values[(current + 1)] <= target) + low = current + 1; + else + high = current; + if (low == high) return (low + 1); + current = (int)((uint)(low + high) >> 1); + } + } + + internal static int LinearSearch(float[] values, float target, int step) + { + for (int i = 0, last = values.Length - step; i <= last; i += step) + if (values[i] > target) return i; + return -1; + } + } + + public interface Timeline + { + /// Sets the value(s) for the specified time. + /// The skeleton the timeline is being applied to. This provides access to the bones, slots, and other skeleton components the timeline may change. + /// 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). + /// The time within the animation. Most timelines find the key before and the key after this time so they can interpolate between the keys. + /// If any events are fired, they are added to this list. Can be null to ignore firing events or if the timeline does not fire events. May be null. + /// 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). + /// Controls how mixing is applied when alpha is than 1. + /// Indicates whether the timeline is mixing in or out. Used by timelines which perform instant transitions such as DrawOrderTimeline and AttachmentTimeline. + void Apply(Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, MixPose pose, MixDirection direction); + int PropertyId { get; } + } + + /// + /// Controls how a timeline is mixed with the setup or current pose. + /// + public enum MixPose + { + /// The timeline value is mixed with the setup pose (the current pose is not used). + Setup, + /// The timeline value is mixed with the current pose. The setup pose is used as the timeline value before the first key, + /// except for timelines which perform instant transitions, such as DrawOrderTimeline or AttachmentTimeline. + Current, + /// The timeline value is mixed with the current pose. No change is made before the first key (the current pose is kept until the first key). + CurrentLayered + } + + /// + /// 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). + /// + public enum MixDirection + { + In, + Out + } + + internal enum TimelineType + { + Rotate = 0, Translate, Scale, Shear, // + Attachment, Color, Deform, // + Event, DrawOrder, // + IkConstraint, TransformConstraint, // + PathConstraintPosition, PathConstraintSpacing, PathConstraintMix, // + TwoColor + } + + /// Base class for frames that use an interpolation bezier curve. + abstract public class CurveTimeline : Timeline + { + protected const float LINEAR = 0, STEPPED = 1, BEZIER = 2; + protected const int BEZIER_SIZE = 10 * 2 - 1; + + internal float[] curves; // type, x, y, ... + public int FrameCount { get { return curves.Length / BEZIER_SIZE + 1; } } + + public CurveTimeline(int frameCount) + { + if (frameCount <= 0) throw new ArgumentException("frameCount must be > 0: " + frameCount, "frameCount"); + curves = new float[(frameCount - 1) * BEZIER_SIZE]; + } + + abstract public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction); + + abstract public int PropertyId { get; } + + public void SetLinear(int frameIndex) + { + curves[frameIndex * BEZIER_SIZE] = LINEAR; + } + + public void SetStepped(int frameIndex) + { + curves[frameIndex * BEZIER_SIZE] = STEPPED; + } + + /// Sets the control handle positions for an interpolation bezier curve used to transition from this keyframe to the next. + /// cx1 and cx2 are from 0 to 1, representing the percent of time between the two keyframes. cy1 and cy2 are the percent of + /// the difference between the keyframe's values. + public void SetCurve(int frameIndex, float cx1, float cy1, float cx2, float cy2) + { + float tmpx = (-cx1 * 2 + cx2) * 0.03f, tmpy = (-cy1 * 2 + cy2) * 0.03f; + float dddfx = ((cx1 - cx2) * 3 + 1) * 0.006f, dddfy = ((cy1 - cy2) * 3 + 1) * 0.006f; + float ddfx = tmpx * 2 + dddfx, ddfy = tmpy * 2 + dddfy; + float dfx = cx1 * 0.3f + tmpx + dddfx * 0.16666667f, dfy = cy1 * 0.3f + tmpy + dddfy * 0.16666667f; + + int i = frameIndex * BEZIER_SIZE; + float[] curves = this.curves; + curves[i++] = BEZIER; + + float x = dfx, y = dfy; + for (int n = i + BEZIER_SIZE - 1; i < n; i += 2) + { + curves[i] = x; + curves[i + 1] = y; + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + x += dfx; + y += dfy; + } + } + + public float GetCurvePercent(int frameIndex, float percent) + { + percent = MathUtils.Clamp(percent, 0, 1); + float[] curves = this.curves; + int i = frameIndex * BEZIER_SIZE; + float type = curves[i]; + if (type == LINEAR) return percent; + if (type == STEPPED) return 0; + i++; + float x = 0; + for (int start = i, n = i + BEZIER_SIZE - 1; i < n; i += 2) + { + x = curves[i]; + if (x >= percent) + { + float prevX, prevY; + if (i == start) + { + prevX = 0; + prevY = 0; + } + else + { + prevX = curves[i - 2]; + prevY = curves[i - 1]; + } + return prevY + (curves[i + 1] - prevY) * (percent - prevX) / (x - prevX); + } + } + float y = curves[i - 1]; + return y + (1 - y) * (percent - x) / (1 - x); // Last point is 1,1. + } + public float GetCurveType(int frameIndex) + { + return curves[frameIndex * BEZIER_SIZE]; + } + } + + public class RotateTimeline : CurveTimeline + { + public const int ENTRIES = 2; + internal const int PREV_TIME = -2, PREV_ROTATION = -1; + internal const int ROTATION = 1; + + internal int boneIndex; + internal float[] frames; + + public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, angle, ... + + override public int PropertyId + { + get { return ((int)TimelineType.Rotate << 24) + boneIndex; } + } + + public RotateTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount << 1]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, float degrees) + { + frameIndex <<= 1; + frames[frameIndex] = time; + frames[frameIndex + ROTATION] = degrees; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + + float[] frames = this.frames; + if (time < frames[0]) + { + switch (pose) + { + case MixPose.Setup: + bone.rotation = bone.data.rotation; + return; + case MixPose.Current: + float rr = bone.data.rotation - bone.rotation; + rr -= (16384 - (int)(16384.499999999996 - rr / 360)) * 360; + bone.rotation += rr * alpha; + return; + } + return; + } + + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + if (pose == MixPose.Setup) + { + bone.rotation = bone.data.rotation + frames[frames.Length + PREV_ROTATION] * alpha; + } + else + { + float rr = bone.data.rotation + frames[frames.Length + PREV_ROTATION] - bone.rotation; + rr -= (16384 - (int)(16384.499999999996 - rr / 360)) * 360; // Wrap within -180 and 180. + bone.rotation += rr * alpha; + } + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + float prevRotation = frames[frame + PREV_ROTATION]; + float frameTime = frames[frame]; + float percent = GetCurvePercent((frame >> 1) - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + float r = frames[frame + ROTATION] - prevRotation; + r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; + r = prevRotation + r * percent; + if (pose == MixPose.Setup) + { + r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; + bone.rotation = bone.data.rotation + r * alpha; + } + else + { + r = bone.data.rotation + r - bone.rotation; + r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; + bone.rotation += r * alpha; + } + } + } + + public class TranslateTimeline : CurveTimeline + { + public const int ENTRIES = 3; + protected const int PREV_TIME = -3, PREV_X = -2, PREV_Y = -1; + protected const int X = 1, Y = 2; + + internal int boneIndex; + internal float[] frames; + + public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, value, value, ... + + override public int PropertyId + { + get { return ((int)TimelineType.Translate << 24) + boneIndex; } + } + + public TranslateTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, float x, float y) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + X] = x; + frames[frameIndex + Y] = y; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + + float[] frames = this.frames; + if (time < frames[0]) + { + switch (pose) + { + case MixPose.Setup: + bone.x = bone.data.x; + bone.y = bone.data.y; + return; + case MixPose.Current: + bone.x += (bone.data.x - bone.x) * alpha; + bone.y += (bone.data.y - bone.y) * alpha; + return; + } + return; + } + + float x, y; + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + x = frames[frames.Length + PREV_X]; + y = frames[frames.Length + PREV_Y]; + } + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + x = frames[frame + PREV_X]; + y = frames[frame + PREV_Y]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + x += (frames[frame + X] - x) * percent; + y += (frames[frame + Y] - y) * percent; + } + if (pose == MixPose.Setup) + { + bone.x = bone.data.x + x * alpha; + bone.y = bone.data.y + y * alpha; + } + else + { + bone.x += (bone.data.x + x - bone.x) * alpha; + bone.y += (bone.data.y + y - bone.y) * alpha; + } + } + } + + public class ScaleTimeline : TranslateTimeline + { + override public int PropertyId + { + get { return ((int)TimelineType.Scale << 24) + boneIndex; } + } + + public ScaleTimeline(int frameCount) + : base(frameCount) + { + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + + float[] frames = this.frames; + if (time < frames[0]) + { + switch (pose) + { + case MixPose.Setup: + bone.scaleX = bone.data.scaleX; + bone.scaleY = bone.data.scaleY; + return; + case MixPose.Current: + bone.scaleX += (bone.data.scaleX - bone.scaleX) * alpha; + bone.scaleY += (bone.data.scaleY - bone.scaleY) * alpha; + return; + } + return; + } + + float x, y; + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + x = frames[frames.Length + PREV_X] * bone.data.scaleX; + y = frames[frames.Length + PREV_Y] * bone.data.scaleY; + } + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + x = frames[frame + PREV_X]; + y = frames[frame + PREV_Y]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + x = (x + (frames[frame + X] - x) * percent) * bone.data.scaleX; + y = (y + (frames[frame + Y] - y) * percent) * bone.data.scaleY; + } + if (alpha == 1) + { + bone.scaleX = x; + bone.scaleY = y; + } + else + { + float bx, by; + if (pose == MixPose.Setup) + { + bx = bone.data.scaleX; + by = bone.data.scaleY; + } + else + { + bx = bone.scaleX; + by = bone.scaleY; + } + // Mixing out uses sign of setup or current pose, else use sign of key. + if (direction == MixDirection.Out) + { + x = (x >= 0 ? x : -x) * (bx >= 0 ? 1 : -1); + y = (y >= 0 ? y : -y) * (by >= 0 ? 1 : -1); + } + else + { + bx = (bx >= 0 ? bx : -bx) * (x >= 0 ? 1 : -1); + by = (by >= 0 ? by : -by) * (y >= 0 ? 1 : -1); + } + bone.scaleX = bx + (x - bx) * alpha; + bone.scaleY = by + (y - by) * alpha; + } + } + } + + public class ShearTimeline : TranslateTimeline + { + override public int PropertyId + { + get { return ((int)TimelineType.Shear << 24) + boneIndex; } + } + + public ShearTimeline(int frameCount) + : base(frameCount) + { + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + float[] frames = this.frames; + if (time < frames[0]) + { + switch (pose) + { + case MixPose.Setup: + bone.shearX = bone.data.shearX; + bone.shearY = bone.data.shearY; + return; + case MixPose.Current: + bone.shearX += (bone.data.shearX - bone.shearX) * alpha; + bone.shearY += (bone.data.shearY - bone.shearY) * alpha; + return; + } + return; + } + + float x, y; + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + x = frames[frames.Length + PREV_X]; + y = frames[frames.Length + PREV_Y]; + } + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + x = frames[frame + PREV_X]; + y = frames[frame + PREV_Y]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + x = x + (frames[frame + X] - x) * percent; + y = y + (frames[frame + Y] - y) * percent; + } + if (pose == MixPose.Setup) + { + bone.shearX = bone.data.shearX + x * alpha; + bone.shearY = bone.data.shearY + y * alpha; + } + else + { + bone.shearX += (bone.data.shearX + x - bone.shearX) * alpha; + bone.shearY += (bone.data.shearY + y - bone.shearY) * alpha; + } + } + } + + public class ColorTimeline : CurveTimeline + { + public const int ENTRIES = 5; + protected const int PREV_TIME = -5, PREV_R = -4, PREV_G = -3, PREV_B = -2, PREV_A = -1; + protected const int R = 1, G = 2, B = 3, A = 4; + + internal int slotIndex; + internal float[] frames; + + public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, r, g, b, a, ... + + override public int PropertyId + { + get { return ((int)TimelineType.Color << 24) + slotIndex; } + } + + public ColorTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, float r, float g, float b, float a) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + R] = r; + frames[frameIndex + G] = g; + frames[frameIndex + B] = b; + frames[frameIndex + A] = a; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) + { + Slot slot = skeleton.slots.Items[slotIndex]; + float[] frames = this.frames; + if (time < frames[0]) + { + var slotData = slot.data; + switch (pose) + { + case MixPose.Setup: + slot.r = slotData.r; + slot.g = slotData.g; + slot.b = slotData.b; + slot.a = slotData.a; + return; + case MixPose.Current: + slot.r += (slot.r - slotData.r) * alpha; + slot.g += (slot.g - slotData.g) * alpha; + slot.b += (slot.b - slotData.b) * alpha; + slot.a += (slot.a - slotData.a) * alpha; + return; + } + return; + } + + float r, g, b, a; + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + int i = frames.Length; + r = frames[i + PREV_R]; + g = frames[i + PREV_G]; + b = frames[i + PREV_B]; + a = frames[i + PREV_A]; + } + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + r = frames[frame + PREV_R]; + g = frames[frame + PREV_G]; + b = frames[frame + PREV_B]; + a = frames[frame + PREV_A]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + r += (frames[frame + R] - r) * percent; + g += (frames[frame + G] - g) * percent; + b += (frames[frame + B] - b) * percent; + a += (frames[frame + A] - a) * percent; + } + if (alpha == 1) + { + slot.r = r; + slot.g = g; + slot.b = b; + slot.a = a; + } + else + { + float br, bg, bb, ba; + if (pose == MixPose.Setup) + { + br = slot.data.r; + bg = slot.data.g; + bb = slot.data.b; + ba = slot.data.a; + } + else + { + br = slot.r; + bg = slot.g; + bb = slot.b; + ba = slot.a; + } + slot.r = br + ((r - br) * alpha); + slot.g = bg + ((g - bg) * alpha); + slot.b = bb + ((b - bb) * alpha); + slot.a = ba + ((a - ba) * alpha); + } + } + } + + public class TwoColorTimeline : CurveTimeline + { + public const int ENTRIES = 8; + protected const int PREV_TIME = -8, PREV_R = -7, PREV_G = -6, PREV_B = -5, PREV_A = -4; + protected const int PREV_R2 = -3, PREV_G2 = -2, PREV_B2 = -1; + protected const int R = 1, G = 2, B = 3, A = 4, R2 = 5, G2 = 6, B2 = 7; + + internal int slotIndex; + internal float[] frames; // time, r, g, b, a, r2, g2, b2, ... + + public int SlotIndex + { + get { return slotIndex; } + set + { + if (value < 0) + throw new ArgumentOutOfRangeException("index must be >= 0."); + slotIndex = value; + } + } + + public float[] Frames { get { return frames; } } + + override public int PropertyId + { + get { return ((int)TimelineType.TwoColor << 24) + slotIndex; } + } + + public TwoColorTimeline(int frameCount) : + base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, float r, float g, float b, float a, float r2, float g2, float b2) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + R] = r; + frames[frameIndex + G] = g; + frames[frameIndex + B] = b; + frames[frameIndex + A] = a; + frames[frameIndex + R2] = r2; + frames[frameIndex + G2] = g2; + frames[frameIndex + B2] = b2; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) + { + Slot slot = skeleton.slots.Items[slotIndex]; + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + var slotData = slot.data; + switch (pose) + { + case MixPose.Setup: + // slot.color.set(slot.data.color); + // slot.darkColor.set(slot.data.darkColor); + slot.r = slotData.r; + slot.g = slotData.g; + slot.b = slotData.b; + slot.a = slotData.a; + slot.r2 = slotData.r2; + slot.g2 = slotData.g2; + slot.b2 = slotData.b2; + return; + case MixPose.Current: + slot.r += (slot.r - slotData.r) * alpha; + slot.g += (slot.g - slotData.g) * alpha; + slot.b += (slot.b - slotData.b) * alpha; + slot.a += (slot.a - slotData.a) * alpha; + slot.r2 += (slot.r2 - slotData.r2) * alpha; + slot.g2 += (slot.g2 - slotData.g2) * alpha; + slot.b2 += (slot.b2 - slotData.b2) * alpha; + return; + } + return; + } + + float r, g, b, a, r2, g2, b2; + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + int i = frames.Length; + r = frames[i + PREV_R]; + g = frames[i + PREV_G]; + b = frames[i + PREV_B]; + a = frames[i + PREV_A]; + r2 = frames[i + PREV_R2]; + g2 = frames[i + PREV_G2]; + b2 = frames[i + PREV_B2]; + } + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + r = frames[frame + PREV_R]; + g = frames[frame + PREV_G]; + b = frames[frame + PREV_B]; + a = frames[frame + PREV_A]; + r2 = frames[frame + PREV_R2]; + g2 = frames[frame + PREV_G2]; + b2 = frames[frame + PREV_B2]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + r += (frames[frame + R] - r) * percent; + g += (frames[frame + G] - g) * percent; + b += (frames[frame + B] - b) * percent; + a += (frames[frame + A] - a) * percent; + r2 += (frames[frame + R2] - r2) * percent; + g2 += (frames[frame + G2] - g2) * percent; + b2 += (frames[frame + B2] - b2) * percent; + } + if (alpha == 1) + { + slot.r = r; + slot.g = g; + slot.b = b; + slot.a = a; + slot.r2 = r2; + slot.g2 = g2; + slot.b2 = b2; + } + else + { + float br, bg, bb, ba, br2, bg2, bb2; + if (pose == MixPose.Setup) + { + br = slot.data.r; + bg = slot.data.g; + bb = slot.data.b; + ba = slot.data.a; + br2 = slot.data.r2; + bg2 = slot.data.g2; + bb2 = slot.data.b2; + } + else + { + br = slot.r; + bg = slot.g; + bb = slot.b; + ba = slot.a; + br2 = slot.r2; + bg2 = slot.g2; + bb2 = slot.b2; + } + slot.r = br + ((r - br) * alpha); + slot.g = bg + ((g - bg) * alpha); + slot.b = bb + ((b - bb) * alpha); + slot.a = ba + ((a - ba) * alpha); + slot.r2 = br2 + ((r2 - br2) * alpha); + slot.g2 = bg2 + ((g2 - bg2) * alpha); + slot.b2 = bb2 + ((b2 - bb2) * alpha); + } + } + + } + + public class AttachmentTimeline : Timeline + { + internal int slotIndex; + internal float[] frames; + internal string[] attachmentNames; + + public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, ... + public string[] AttachmentNames { get { return attachmentNames; } set { attachmentNames = value; } } + public int FrameCount { get { return frames.Length; } } + + public int PropertyId + { + get { return ((int)TimelineType.Attachment << 24) + slotIndex; } + } + + public AttachmentTimeline(int frameCount) + { + frames = new float[frameCount]; + attachmentNames = new String[frameCount]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, String attachmentName) + { + frames[frameIndex] = time; + attachmentNames[frameIndex] = attachmentName; + } + + public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) + { + string attachmentName; + Slot slot = skeleton.slots.Items[slotIndex]; + if (direction == MixDirection.Out && pose == MixPose.Setup) + { + attachmentName = slot.data.attachmentName; + slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); + return; + } + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + if (pose == MixPose.Setup) + { + attachmentName = slot.data.attachmentName; + slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); + } + return; + } + + int frameIndex; + if (time >= frames[frames.Length - 1]) // Time is after last frame. + frameIndex = frames.Length - 1; + else + frameIndex = Animation.BinarySearch(frames, time, 1) - 1; + + attachmentName = attachmentNames[frameIndex]; + slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); + } + } + + public class DeformTimeline : CurveTimeline + { + internal int slotIndex; + internal float[] frames; + internal float[][] frameVertices; + internal VertexAttachment attachment; + + public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, ... + public float[][] Vertices { get { return frameVertices; } set { frameVertices = value; } } + public VertexAttachment Attachment { get { return attachment; } set { attachment = value; } } + + override public int PropertyId + { + get { return ((int)TimelineType.Deform << 24) + attachment.id + slotIndex; } + } + + public DeformTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount]; + frameVertices = new float[frameCount][]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, float[] vertices) + { + frames[frameIndex] = time; + frameVertices[frameIndex] = vertices; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) + { + Slot slot = skeleton.slots.Items[slotIndex]; + VertexAttachment vertexAttachment = slot.attachment as VertexAttachment; + if (vertexAttachment == null || !vertexAttachment.ApplyDeform(attachment)) return; + + var verticesArray = slot.attachmentVertices; + if (verticesArray.Count == 0) alpha = 1; + + float[][] frameVertices = this.frameVertices; + int vertexCount = frameVertices[0].Length; + float[] frames = this.frames; + float[] vertices; + + if (time < frames[0]) + { + + switch (pose) + { + case MixPose.Setup: + verticesArray.Clear(); + return; + case MixPose.Current: + if (alpha == 1) + { + verticesArray.Clear(); + return; + } + + // verticesArray.SetSize(vertexCount) // Ensure size and preemptively set count. + if (verticesArray.Capacity < vertexCount) verticesArray.Capacity = vertexCount; + verticesArray.Count = vertexCount; + vertices = verticesArray.Items; + + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions. + float[] setupVertices = vertexAttachment.vertices; + for (int i = 0; i < vertexCount; i++) + vertices[i] += (setupVertices[i] - vertices[i]) * alpha; + } + else + { + // Weighted deform offsets. + alpha = 1 - alpha; + for (int i = 0; i < vertexCount; i++) + vertices[i] *= alpha; + } + return; + default: + return; + } + + } + + // verticesArray.SetSize(vertexCount) // Ensure size and preemptively set count. + if (verticesArray.Capacity < vertexCount) verticesArray.Capacity = vertexCount; + verticesArray.Count = vertexCount; + vertices = verticesArray.Items; + + if (time >= frames[frames.Length - 1]) + { // Time is after last frame. + float[] lastVertices = frameVertices[frames.Length - 1]; + if (alpha == 1) + { + // Vertex positions or deform offsets, no alpha. + Array.Copy(lastVertices, 0, vertices, 0, vertexCount); + } + else if (pose == MixPose.Setup) + { + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions, with alpha. + float[] setupVertices = vertexAttachment.vertices; + for (int i = 0; i < vertexCount; i++) + { + float setup = setupVertices[i]; + vertices[i] = setup + (lastVertices[i] - setup) * alpha; + } + } + else + { + // Weighted deform offsets, with alpha. + for (int i = 0; i < vertexCount; i++) + vertices[i] = lastVertices[i] * alpha; + } + } + else + { + // Vertex positions or deform offsets, with alpha. + for (int i = 0; i < vertexCount; i++) + vertices[i] += (lastVertices[i] - vertices[i]) * alpha; + } + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time); + float[] prevVertices = frameVertices[frame - 1]; + float[] nextVertices = frameVertices[frame]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame - 1, 1 - (time - frameTime) / (frames[frame - 1] - frameTime)); + + if (alpha == 1) + { + // Vertex positions or deform offsets, no alpha. + for (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i]; + vertices[i] = prev + (nextVertices[i] - prev) * percent; + } + } + else if (pose == MixPose.Setup) + { + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions, with alpha. + var setupVertices = vertexAttachment.vertices; + for (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i], setup = setupVertices[i]; + vertices[i] = setup + (prev + (nextVertices[i] - prev) * percent - setup) * alpha; + } + } + else + { + // Weighted deform offsets, with alpha. + for (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i]; + vertices[i] = (prev + (nextVertices[i] - prev) * percent) * alpha; + } + } + } + else + { + // Vertex positions or deform offsets, with alpha. + for (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i]; + vertices[i] += (prev + (nextVertices[i] - prev) * percent - vertices[i]) * alpha; + } + } + } + } + + public class EventTimeline : Timeline + { + internal float[] frames; + private Event[] events; + + public float[] Frames { get { return frames; } set { frames = value; } } // time, ... + public Event[] Events { get { return events; } set { events = value; } } + public int FrameCount { get { return frames.Length; } } + + public int PropertyId + { + get { return ((int)TimelineType.Event << 24); } + } + + public EventTimeline(int frameCount) + { + frames = new float[frameCount]; + events = new Event[frameCount]; + } + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, Event e) + { + frames[frameIndex] = e.Time; + events[frameIndex] = e; + } + + /// Fires events for frames > lastTime and <= time. + public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) + { + if (firedEvents == null) return; + float[] frames = this.frames; + int frameCount = frames.Length; + + if (lastTime > time) + { // Fire events after last time for looped animations. + Apply(skeleton, lastTime, int.MaxValue, firedEvents, alpha, pose, direction); + lastTime = -1f; + } + else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame. + return; + if (time < frames[0]) return; // Time is before first frame. + + int frame; + if (lastTime < frames[0]) + frame = 0; + else + { + frame = Animation.BinarySearch(frames, lastTime); + float frameTime = frames[frame]; + while (frame > 0) + { // Fire multiple events with the same frame. + if (frames[frame - 1] != frameTime) break; + frame--; + } + } + for (; frame < frameCount && time >= frames[frame]; frame++) + firedEvents.Add(events[frame]); + } + } + + public class DrawOrderTimeline : Timeline + { + internal float[] frames; + private int[][] drawOrders; + + public float[] Frames { get { return frames; } set { frames = value; } } // time, ... + public int[][] DrawOrders { get { return drawOrders; } set { drawOrders = value; } } + public int FrameCount { get { return frames.Length; } } + + public int PropertyId + { + get { return ((int)TimelineType.DrawOrder << 24); } + } + + public DrawOrderTimeline(int frameCount) + { + frames = new float[frameCount]; + drawOrders = new int[frameCount][]; + } + + /// Sets the time and value of the specified keyframe. + /// May be null to use bind pose draw order. + public void SetFrame(int frameIndex, float time, int[] drawOrder) + { + frames[frameIndex] = time; + drawOrders[frameIndex] = drawOrder; + } + + public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) + { + ExposedList drawOrder = skeleton.drawOrder; + ExposedList slots = skeleton.slots; + if (direction == MixDirection.Out && pose == MixPose.Setup) + { + Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count); + return; + } + + float[] frames = this.frames; + if (time < frames[0]) + { + if (pose == MixPose.Setup) Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count); + return; + } + + int frame; + if (time >= frames[frames.Length - 1]) // Time is after last frame. + frame = frames.Length - 1; + else + frame = Animation.BinarySearch(frames, time) - 1; + + int[] drawOrderToSetupIndex = drawOrders[frame]; + if (drawOrderToSetupIndex == null) + { + drawOrder.Clear(); + for (int i = 0, n = slots.Count; i < n; i++) + drawOrder.Add(slots.Items[i]); + } + else + { + var drawOrderItems = drawOrder.Items; + var slotsItems = slots.Items; + for (int i = 0, n = drawOrderToSetupIndex.Length; i < n; i++) + drawOrderItems[i] = slotsItems[drawOrderToSetupIndex[i]]; + } + } + } + + public class IkConstraintTimeline : CurveTimeline + { + public const int ENTRIES = 3; + private const int PREV_TIME = -3, PREV_MIX = -2, PREV_BEND_DIRECTION = -1; + private const int MIX = 1, BEND_DIRECTION = 2; + + internal int ikConstraintIndex; + internal float[] frames; + + public int IkConstraintIndex { get { return ikConstraintIndex; } set { ikConstraintIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, mix, bendDirection, ... + + override public int PropertyId + { + get { return ((int)TimelineType.IkConstraint << 24) + ikConstraintIndex; } + } + + public IkConstraintTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + /// Sets the time, mix and bend direction of the specified keyframe. + public void SetFrame(int frameIndex, float time, float mix, int bendDirection) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + MIX] = mix; + frames[frameIndex + BEND_DIRECTION] = bendDirection; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) + { + IkConstraint constraint = skeleton.ikConstraints.Items[ikConstraintIndex]; + float[] frames = this.frames; + if (time < frames[0]) + { + switch (pose) + { + case MixPose.Setup: + constraint.mix = constraint.data.mix; + constraint.bendDirection = constraint.data.bendDirection; + return; + case MixPose.Current: + constraint.mix += (constraint.data.mix - constraint.mix) * alpha; + constraint.bendDirection = constraint.data.bendDirection; + return; + } + return; + } + + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + if (pose == MixPose.Setup) + { + constraint.mix = constraint.data.mix + (frames[frames.Length + PREV_MIX] - constraint.data.mix) * alpha; + constraint.bendDirection = direction == MixDirection.Out ? constraint.data.bendDirection + : (int)frames[frames.Length + PREV_BEND_DIRECTION]; + } + else + { + constraint.mix += (frames[frames.Length + PREV_MIX] - constraint.mix) * alpha; + if (direction == MixDirection.In) constraint.bendDirection = (int)frames[frames.Length + PREV_BEND_DIRECTION]; + } + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + float mix = frames[frame + PREV_MIX]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + if (pose == MixPose.Setup) + { + constraint.mix = constraint.data.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.data.mix) * alpha; + constraint.bendDirection = direction == MixDirection.Out ? constraint.data.bendDirection : (int)frames[frame + PREV_BEND_DIRECTION]; + } + else + { + constraint.mix += (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha; + if (direction == MixDirection.In) constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION]; + } + } + } + + public class TransformConstraintTimeline : CurveTimeline + { + public const int ENTRIES = 5; + private const int PREV_TIME = -5, PREV_ROTATE = -4, PREV_TRANSLATE = -3, PREV_SCALE = -2, PREV_SHEAR = -1; + private const int ROTATE = 1, TRANSLATE = 2, SCALE = 3, SHEAR = 4; + + internal int transformConstraintIndex; + internal float[] frames; + + public int TransformConstraintIndex { get { return transformConstraintIndex; } set { transformConstraintIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, scale mix, shear mix, ... + + override public int PropertyId + { + get { return ((int)TimelineType.TransformConstraint << 24) + transformConstraintIndex; } + } + + public TransformConstraintTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + public void SetFrame(int frameIndex, float time, float rotateMix, float translateMix, float scaleMix, float shearMix) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + ROTATE] = rotateMix; + frames[frameIndex + TRANSLATE] = translateMix; + frames[frameIndex + SCALE] = scaleMix; + frames[frameIndex + SHEAR] = shearMix; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) + { + TransformConstraint constraint = skeleton.transformConstraints.Items[transformConstraintIndex]; + float[] frames = this.frames; + if (time < frames[0]) + { + var data = constraint.data; + switch (pose) + { + case MixPose.Setup: + constraint.rotateMix = data.rotateMix; + constraint.translateMix = data.translateMix; + constraint.scaleMix = data.scaleMix; + constraint.shearMix = data.shearMix; + return; + case MixPose.Current: + constraint.rotateMix += (data.rotateMix - constraint.rotateMix) * alpha; + constraint.translateMix += (data.translateMix - constraint.translateMix) * alpha; + constraint.scaleMix += (data.scaleMix - constraint.scaleMix) * alpha; + constraint.shearMix += (data.shearMix - constraint.shearMix) * alpha; + return; + } + return; + } + + float rotate, translate, scale, shear; + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + int i = frames.Length; + rotate = frames[i + PREV_ROTATE]; + translate = frames[i + PREV_TRANSLATE]; + scale = frames[i + PREV_SCALE]; + shear = frames[i + PREV_SHEAR]; + } + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + rotate = frames[frame + PREV_ROTATE]; + translate = frames[frame + PREV_TRANSLATE]; + scale = frames[frame + PREV_SCALE]; + shear = frames[frame + PREV_SHEAR]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + rotate += (frames[frame + ROTATE] - rotate) * percent; + translate += (frames[frame + TRANSLATE] - translate) * percent; + scale += (frames[frame + SCALE] - scale) * percent; + shear += (frames[frame + SHEAR] - shear) * percent; + } + if (pose == MixPose.Setup) + { + TransformConstraintData data = constraint.data; + constraint.rotateMix = data.rotateMix + (rotate - data.rotateMix) * alpha; + constraint.translateMix = data.translateMix + (translate - data.translateMix) * alpha; + constraint.scaleMix = data.scaleMix + (scale - data.scaleMix) * alpha; + constraint.shearMix = data.shearMix + (shear - data.shearMix) * alpha; + } + else + { + constraint.rotateMix += (rotate - constraint.rotateMix) * alpha; + constraint.translateMix += (translate - constraint.translateMix) * alpha; + constraint.scaleMix += (scale - constraint.scaleMix) * alpha; + constraint.shearMix += (shear - constraint.shearMix) * alpha; + } + } + } + + public class PathConstraintPositionTimeline : CurveTimeline + { + public const int ENTRIES = 2; + protected const int PREV_TIME = -2, PREV_VALUE = -1; + protected const int VALUE = 1; + + internal int pathConstraintIndex; + internal float[] frames; + + override public int PropertyId + { + get { return ((int)TimelineType.PathConstraintPosition << 24) + pathConstraintIndex; } + } + + public PathConstraintPositionTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + public int PathConstraintIndex { get { return pathConstraintIndex; } set { pathConstraintIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, position, ... + + /// Sets the time and value of the specified keyframe. + public void SetFrame(int frameIndex, float time, float value) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + VALUE] = value; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) + { + PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; + float[] frames = this.frames; + if (time < frames[0]) + { + switch (pose) + { + case MixPose.Setup: + constraint.position = constraint.data.position; + return; + case MixPose.Current: + constraint.position += (constraint.data.position - constraint.position) * alpha; + return; + } + return; + } + + float position; + if (time >= frames[frames.Length - ENTRIES]) // Time is after last frame. + position = frames[frames.Length + PREV_VALUE]; + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + position = frames[frame + PREV_VALUE]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + position += (frames[frame + VALUE] - position) * percent; + } + if (pose == MixPose.Setup) + constraint.position = constraint.data.position + (position - constraint.data.position) * alpha; + else + constraint.position += (position - constraint.position) * alpha; + } + } + + public class PathConstraintSpacingTimeline : PathConstraintPositionTimeline + { + override public int PropertyId + { + get { return ((int)TimelineType.PathConstraintSpacing << 24) + pathConstraintIndex; } + } + + public PathConstraintSpacingTimeline(int frameCount) + : base(frameCount) + { + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) + { + PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; + float[] frames = this.frames; + if (time < frames[0]) + { + switch (pose) + { + case MixPose.Setup: + constraint.spacing = constraint.data.spacing; + return; + case MixPose.Current: + constraint.spacing += (constraint.data.spacing - constraint.spacing) * alpha; + return; + } + return; + } + + float spacing; + if (time >= frames[frames.Length - ENTRIES]) // Time is after last frame. + spacing = frames[frames.Length + PREV_VALUE]; + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + spacing = frames[frame + PREV_VALUE]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + spacing += (frames[frame + VALUE] - spacing) * percent; + } + + if (pose == MixPose.Setup) + constraint.spacing = constraint.data.spacing + (spacing - constraint.data.spacing) * alpha; + else + constraint.spacing += (spacing - constraint.spacing) * alpha; + } + } + + public class PathConstraintMixTimeline : CurveTimeline + { + public const int ENTRIES = 3; + private const int PREV_TIME = -3, PREV_ROTATE = -2, PREV_TRANSLATE = -1; + private const int ROTATE = 1, TRANSLATE = 2; + + internal int pathConstraintIndex; + internal float[] frames; + + public int PathConstraintIndex { get { return pathConstraintIndex; } set { pathConstraintIndex = value; } } + public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, ... + + override public int PropertyId + { + get { return ((int)TimelineType.PathConstraintMix << 24) + pathConstraintIndex; } + } + + public PathConstraintMixTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + /// Sets the time and mixes of the specified keyframe. + public void SetFrame(int frameIndex, float time, float rotateMix, float translateMix) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + ROTATE] = rotateMix; + frames[frameIndex + TRANSLATE] = translateMix; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixPose pose, MixDirection direction) + { + PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; + float[] frames = this.frames; + if (time < frames[0]) + { + switch (pose) + { + case MixPose.Setup: + constraint.rotateMix = constraint.data.rotateMix; + constraint.translateMix = constraint.data.translateMix; + return; + case MixPose.Current: + constraint.rotateMix += (constraint.data.rotateMix - constraint.rotateMix) * alpha; + constraint.translateMix += (constraint.data.translateMix - constraint.translateMix) * alpha; + return; + } + return; + } + + float rotate, translate; + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + rotate = frames[frames.Length + PREV_ROTATE]; + translate = frames[frames.Length + PREV_TRANSLATE]; + } + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + rotate = frames[frame + PREV_ROTATE]; + translate = frames[frame + PREV_TRANSLATE]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + rotate += (frames[frame + ROTATE] - rotate) * percent; + translate += (frames[frame + TRANSLATE] - translate) * percent; + } + + if (pose == MixPose.Setup) + { + constraint.rotateMix = constraint.data.rotateMix + (rotate - constraint.data.rotateMix) * alpha; + constraint.translateMix = constraint.data.translateMix + (translate - constraint.data.translateMix) * alpha; + } + else + { + constraint.rotateMix += (rotate - constraint.rotateMix) * alpha; + constraint.translateMix += (translate - constraint.translateMix) * alpha; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/AnimationState.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/AnimationState.cs index a62cb25..1db9773 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/AnimationState.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/AnimationState.cs @@ -31,1036 +31,1162 @@ using System; using System.Collections.Generic; -namespace Spine3_6_53 { - public class AnimationState { - static readonly Animation EmptyAnimation = new Animation("", new ExposedList(), 0); - internal const int Subsequent = 0, First = 1, Dip = 2, DipMix = 3; - - private AnimationStateData data; - - Pool trackEntryPool = new Pool(); - private readonly ExposedList tracks = new ExposedList(); - private readonly ExposedList events = new ExposedList(); - private readonly EventQueue queue; // Initialized by constructor. - - private readonly HashSet propertyIDs = new HashSet(); - private readonly ExposedList mixingTo = new ExposedList(); - private bool animationsChanged; - - private float timeScale = 1; - - public AnimationStateData Data { get { return data; } } - /// A list of tracks that have animations, which may contain nulls. - public ExposedList Tracks { get { return tracks; } } - public float TimeScale { get { return timeScale; } set { timeScale = value; } } - - public delegate void TrackEntryDelegate (TrackEntry trackEntry); - public event TrackEntryDelegate Start, Interrupt, End, Dispose, Complete; - - public delegate void TrackEntryEventDelegate (TrackEntry trackEntry, Event e); - public event TrackEntryEventDelegate Event; - - public AnimationState (AnimationStateData data) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - this.data = data; - this.queue = new EventQueue( - this, - delegate { this.animationsChanged = true; }, - trackEntryPool - ); - } - - /// - /// Increments the track entry times, setting queued animations as current if needed - /// delta time - public void Update (float delta) { - delta *= timeScale; - var tracksItems = tracks.Items; - for (int i = 0, n = tracks.Count; i < n; i++) { - TrackEntry current = tracksItems[i]; - if (current == null) continue; - - 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 = nextTime + (delta * next.timeScale); - current.trackTime += currentDelta; - SetCurrent(i, next, true); - while (next.mixingFrom != null) { - next.mixTime += currentDelta; - 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. - tracksItems[i] = null; - - queue.End(current); - DisposeNext(current); - continue; - } - if (current.mixingFrom != null && UpdateMixingFrom(current, delta)) { - // End mixing from entries once all have completed. - var from = current.mixingFrom; - current.mixingFrom = null; - while (from != null) { - queue.End(from); - from = from.mixingFrom; - } - } - - current.trackTime += currentDelta; - } - - queue.Drain(); - } - - /// Returns true when all mixing from entries are complete. - private bool 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; - - // Require mixTime > 0 to ensure the mixing from entry was applied at least once. - if (to.mixTime > 0 && (to.mixTime >= to.mixDuration || to.timeScale == 0)) { - // Require totalAlpha == 0 to ensure mixing is complete, unless mixDuration == 0 (the transition is a single frame). - if (from.totalAlpha == 0 || to.mixDuration == 0) { - to.mixingFrom = from.mixingFrom; - to.interruptAlpha = from.interruptAlpha; - queue.End(from); - } - return finished; - } - - from.trackTime += delta * from.timeScale; - to.mixTime += delta * to.timeScale; - return false; - } - - /// - /// 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. - public bool Apply (Skeleton skeleton) { - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - if (animationsChanged) AnimationsChanged(); - - var events = this.events; - - bool applied = false; - var tracksItems = tracks.Items; - for (int i = 0, m = tracks.Count; i < m; i++) { - TrackEntry current = tracksItems[i]; - if (current == null || current.delay > 0) continue; - applied = true; - MixPose currentPose = i == 0 ? MixPose.Current : MixPose.CurrentLayered; - - // Apply mixing from entries first. - float mix = current.alpha; - if (current.mixingFrom != null) - mix *= ApplyMixingFrom(current, skeleton, currentPose); - else if (current.trackTime >= current.trackEnd && current.next == null) // - mix = 0; // Set to setup pose the last time the entry will be applied. - - // Apply current entry. - float animationLast = current.animationLast, animationTime = current.AnimationTime; - int timelineCount = current.animation.timelines.Count; - var timelines = current.animation.timelines; - var timelinesItems = timelines.Items; - if (mix == 1) { - for (int ii = 0; ii < timelineCount; ii++) - timelinesItems[ii].Apply(skeleton, animationLast, animationTime, events, 1, MixPose.Setup, MixDirection.In); - } else { - var timelineData = current.timelineData.Items; - - bool firstFrame = current.timelinesRotation.Count == 0; - if (firstFrame) current.timelinesRotation.EnsureCapacity(timelines.Count << 1); - var timelinesRotation = current.timelinesRotation.Items; - - for (int ii = 0; ii < timelineCount; ii++) { - Timeline timeline = timelinesItems[ii]; - MixPose pose = timelineData[ii] >= AnimationState.First ? MixPose.Setup : currentPose; - var rotateTimeline = timeline as RotateTimeline; - if (rotateTimeline != null) - ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, mix, pose, timelinesRotation, ii << 1, firstFrame); - else - timeline.Apply(skeleton, animationLast, animationTime, events, mix, pose, MixDirection.In); - } - } - QueueEvents(current, animationTime); - events.Clear(false); - current.nextAnimationLast = animationTime; - current.nextTrackLast = current.trackTime; - } - - queue.Drain(); - return applied; - } - - private float ApplyMixingFrom (TrackEntry to, Skeleton skeleton, MixPose currentPose) { - TrackEntry from = to.mixingFrom; - if (from.mixingFrom != null) ApplyMixingFrom(from, skeleton, currentPose); - - float mix; - if (to.mixDuration == 0) { // Single frame mix to undo mixingFrom changes. - mix = 1; - currentPose = MixPose.Setup; - } else { - mix = to.mixTime / to.mixDuration; - if (mix > 1) mix = 1; - } - - var eventBuffer = mix < from.eventThreshold ? this.events : null; - bool attachments = mix < from.attachmentThreshold, drawOrder = mix < from.drawOrderThreshold; - float animationLast = from.animationLast, animationTime = from.AnimationTime; - var timelines = from.animation.timelines; - int timelineCount = timelines.Count; - var timelinesItems = timelines.Items; - var timelineData = from.timelineData.Items; - var timelineDipMix = from.timelineDipMix.Items; - - bool firstFrame = from.timelinesRotation.Count == 0; - if (firstFrame) from.timelinesRotation.Resize(timelines.Count << 1); // from.timelinesRotation.setSize - var timelinesRotation = from.timelinesRotation.Items; - - MixPose pose; - float alphaDip = from.alpha * to.interruptAlpha, alphaMix = alphaDip * (1 - mix), alpha; - from.totalAlpha = 0; - for (int i = 0; i < timelineCount; i++) { - Timeline timeline = timelinesItems[i]; - switch (timelineData[i]) { - case Subsequent: - if (!attachments && timeline is AttachmentTimeline) continue; - if (!drawOrder && timeline is DrawOrderTimeline) continue; - pose = currentPose; - alpha = alphaMix; - break; - case First: - pose = MixPose.Setup; - alpha = alphaMix; - break; - case Dip: - pose = MixPose.Setup; - alpha = alphaDip; - break; - default: - pose = MixPose.Setup; - TrackEntry dipMix = timelineDipMix[i]; - alpha = alphaDip * Math.Max(0, 1 - dipMix.mixTime / dipMix.mixDuration); - break; - } - from.totalAlpha += alpha; - var rotateTimeline = timeline as RotateTimeline; - if (rotateTimeline != null) { - ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, alpha, pose, timelinesRotation, i << 1, firstFrame); - } else { - timeline.Apply(skeleton, animationLast, animationTime, eventBuffer, alpha, pose, MixDirection.Out); - } - } - - if (to.mixDuration > 0) QueueEvents(from, animationTime); - this.events.Clear(false); - from.nextAnimationLast = animationTime; - from.nextTrackLast = from.trackTime; - - return mix; - } - - static private void ApplyRotateTimeline (RotateTimeline rotateTimeline, Skeleton skeleton, float time, float alpha, MixPose pose, - float[] timelinesRotation, int i, bool firstFrame) { - - if (firstFrame) timelinesRotation[i] = 0; - - if (alpha == 1) { - rotateTimeline.Apply(skeleton, 0, time, null, 1, pose, MixDirection.In); - return; - } - - Bone bone = skeleton.bones.Items[rotateTimeline.boneIndex]; - float[] frames = rotateTimeline.frames; - if (time < frames[0]) { - if (pose == MixPose.Setup) bone.rotation = bone.data.rotation; - return; - } - - float r2; - if (time >= frames[frames.Length - RotateTimeline.ENTRIES]) // Time is after last frame. - r2 = bone.data.rotation + frames[frames.Length + RotateTimeline.PREV_ROTATION]; - else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, RotateTimeline.ENTRIES); - float prevRotation = frames[frame + RotateTimeline.PREV_ROTATION]; - float frameTime = frames[frame]; - float percent = rotateTimeline.GetCurvePercent((frame >> 1) - 1, - 1 - (time - frameTime) / (frames[frame + RotateTimeline.PREV_TIME] - frameTime)); - - r2 = frames[frame + RotateTimeline.ROTATION] - prevRotation; - r2 -= (16384 - (int)(16384.499999999996 - r2 / 360)) * 360; - r2 = prevRotation + r2 * percent + bone.data.rotation; - r2 -= (16384 - (int)(16384.499999999996 - r2 / 360)) * 360; - } - - // Mix between rotations using the direction of the shortest route on the first frame while detecting crosses. - float r1 = pose == MixPose.Setup ? bone.data.rotation : bone.rotation; - float total, diff = r2 - r1; - if (diff == 0) { - total = timelinesRotation[i]; - } else { - diff -= (16384 - (int)(16384.499999999996 - diff / 360)) * 360; - float lastTotal, lastDiff; - if (firstFrame) { - lastTotal = 0; - lastDiff = diff; - } else { - lastTotal = timelinesRotation[i]; // Angle and direction of mix, including loops. - lastDiff = timelinesRotation[i + 1]; // Difference between bones. - } - bool current = diff > 0, dir = lastTotal >= 0; - // Detect cross at 0 (not 180). - if (Math.Sign(lastDiff) != Math.Sign(diff) && Math.Abs(lastDiff) <= 90) { - // A cross after a 360 rotation is a loop. - if (Math.Abs(lastTotal) > 180) lastTotal += 360 * Math.Sign(lastTotal); - dir = current; - } - total = diff + lastTotal - lastTotal % 360; // Store loops as part of lastTotal. - if (dir != current) total += 360 * Math.Sign(lastTotal); - timelinesRotation[i] = total; - } - timelinesRotation[i + 1] = diff; - r1 += total * alpha; - bone.rotation = r1 - (16384 - (int)(16384.499999999996 - r1 / 360)) * 360; - } - - private void QueueEvents (TrackEntry entry, float animationTime) { - float animationStart = entry.animationStart, animationEnd = entry.animationEnd; - float duration = animationEnd - animationStart; - float trackLastWrapped = entry.trackLast % duration; - - // Queue events before complete. - var events = this.events; - var eventsItems = events.Items; - int i = 0, n = events.Count; - for (; i < n; i++) { - var e = eventsItems[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) - complete = duration == 0 || (trackLastWrapped > entry.trackTime % duration); - else - complete = animationTime >= animationEnd && entry.animationLast < animationEnd; - if (complete) queue.Complete(entry); - - // Queue events after complete. - for (; i < n; i++) { - Event e = eventsItems[i]; - if (e.time < animationStart) continue; // Discard events outside animation start/end. - queue.Event(entry, eventsItems[i]); - } - } - - /// - /// Removes all animations from all tracks, leaving skeletons in their previous pose. - /// It may be desired to use to mix the skeletons back to the setup pose, - /// rather than leaving them in their previous pose. - public void ClearTracks () { - bool oldDrainDisabled = queue.drainDisabled; - queue.drainDisabled = true; - for (int i = 0, n = tracks.Count; i < n; i++) { - ClearTrack(i); - } - tracks.Clear(); - queue.drainDisabled = oldDrainDisabled; - queue.Drain(); - } - - /// - /// Removes all animations from the tracks, leaving skeletons in their previous pose. - /// It may be desired to use to mix the skeletons back to the setup pose, - /// rather than leaving them in their previous pose. - public void ClearTrack (int trackIndex) { - if (trackIndex >= tracks.Count) return; - TrackEntry current = tracks.Items[trackIndex]; - if (current == null) return; - - queue.End(current); - - DisposeNext(current); - - TrackEntry entry = current; - while (true) { - TrackEntry from = entry.mixingFrom; - if (from == null) break; - queue.End(from); - entry.mixingFrom = null; - entry = from; - } - - tracks.Items[current.trackIndex] = null; - - queue.Drain(); - } - - /// Sets the active TrackEntry for a given track number. - private void SetCurrent (int index, TrackEntry current, bool interrupt) { - TrackEntry from = ExpandToIndex(index); - tracks.Items[index] = current; - - if (from != null) { - if (interrupt) queue.Interrupt(from); - current.mixingFrom = from; - current.mixTime = 0; - - // Store interrupted mix percentage. - if (from.mixingFrom != null && from.mixDuration > 0) - current.interruptAlpha *= Math.Min(1, from.mixTime / from.mixDuration); - - from.timelinesRotation.Clear(); // Reset rotation for mixing out, in case entry was mixed in. - } - - queue.Start(current); // triggers AnimationsChanged - } - - - /// Sets an animation by name. - public TrackEntry SetAnimation (int trackIndex, string animationName, bool loop) { - Animation animation = data.skeletonData.FindAnimation(animationName); - if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName"); - return SetAnimation(trackIndex, animation, loop); - } - - /// Sets the current animation for a track, discarding any queued animations. - /// 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 determines when the track is cleared. - /// - /// A track entry to allow further customization of animation playback. References to the track entry must not be kept - /// after . - public TrackEntry SetAnimation (int trackIndex, Animation animation, bool loop) { - if (animation == null) throw new ArgumentNullException("animation", "animation cannot be 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.Items[trackIndex] = current.mixingFrom; - queue.Interrupt(current); - queue.End(current); - DisposeNext(current); - current = current.mixingFrom; - interrupt = false; - } else { - DisposeNext(current); - } - } - TrackEntry entry = NewTrackEntry(trackIndex, animation, loop, current); - SetCurrent(trackIndex, entry, interrupt); - queue.Drain(); - return entry; - } - - /// Queues an animation by name. - /// - public TrackEntry AddAnimation (int trackIndex, string animationName, bool loop, float delay) { - Animation animation = data.skeletonData.FindAnimation(animationName); - if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName"); - return AddAnimation(trackIndex, animation, loop, 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 . - /// - /// 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. - /// - /// A track entry to allow further customization of animation playback. References to the track entry must not be kept - /// after - public TrackEntry AddAnimation (int trackIndex, Animation animation, bool loop, float delay) { - if (animation == null) throw new ArgumentNullException("animation", "animation cannot be 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(); - } else { - last.next = entry; - if (delay <= 0) { - float duration = last.animationEnd - last.animationStart; - if (duration != 0) { - if (last.loop) { - delay += duration * (1 + (int)(last.trackTime / duration)); - } else { - delay += duration; - } - delay -= data.GetMix(last.animation, animation); - } else - delay = 0; - } - } - - entry.delay = delay; - return entry; - } - - /// - /// Sets an empty animation for a track, discarding any queued animations, and mixes to it over the specified mix duration. - public TrackEntry SetEmptyAnimation (int trackIndex, float mixDuration) { - TrackEntry entry = SetAnimation(trackIndex, AnimationState.EmptyAnimation, false); - entry.mixDuration = mixDuration; - entry.trackEnd = mixDuration; - return entry; - } - - /// - /// 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. - /// - /// A track entry to allow further customization of animation playback. References to the track entry must not be kept after . - /// - /// Track number. - /// Mix duration. - /// 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. - public TrackEntry AddEmptyAnimation (int trackIndex, float mixDuration, float delay) { - if (delay <= 0) delay -= mixDuration; - TrackEntry entry = AddAnimation(trackIndex, AnimationState.EmptyAnimation, false, delay); - entry.mixDuration = mixDuration; - entry.trackEnd = mixDuration; - return entry; - } - - /// - /// Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix duration. - public void SetEmptyAnimations (float mixDuration) { - bool oldDrainDisabled = queue.drainDisabled; - queue.drainDisabled = true; - for (int i = 0, n = tracks.Count; i < n; i++) { - TrackEntry current = tracks.Items[i]; - if (current != null) SetEmptyAnimation(i, mixDuration); - } - queue.drainDisabled = oldDrainDisabled; - queue.Drain(); - } - - private TrackEntry ExpandToIndex (int index) { - if (index < tracks.Count) return tracks.Items[index]; - while (index >= tracks.Count) - tracks.Add(null); - return null; - } - - /// Object-pooling version of new TrackEntry. Obtain an unused TrackEntry from the pool and clear/initialize its values. - /// May be null. - private TrackEntry NewTrackEntry (int trackIndex, Animation animation, bool loop, TrackEntry last) { - TrackEntry entry = trackEntryPool.Obtain(); // Pooling - entry.trackIndex = trackIndex; - entry.animation = animation; - entry.loop = loop; - - entry.eventThreshold = 0; - entry.attachmentThreshold = 0; - entry.drawOrderThreshold = 0; - - entry.animationStart = 0; - entry.animationEnd = animation.Duration; - 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 = float.MaxValue; // loop ? float.MaxValue : animation.Duration; - entry.timeScale = 1; - - entry.alpha = 1; - entry.interruptAlpha = 1; - entry.mixTime = 0; - entry.mixDuration = (last == null) ? 0 : data.GetMix(last.animation, animation); - return entry; - } - - /// Dispose all track entries queued after the given TrackEntry. - private void DisposeNext (TrackEntry entry) { - TrackEntry next = entry.next; - while (next != null) { - queue.Dispose(next); - next = next.next; - } - entry.next = null; - } - - private void AnimationsChanged () { - animationsChanged = false; - - var propertyIDs = this.propertyIDs; - propertyIDs.Clear(); - var mixingTo = this.mixingTo; - - var tracksItems = tracks.Items; - for (int i = 0, n = tracks.Count; i < n; i++) { - var entry = tracksItems[i]; - if (entry != null) entry.SetTimelineData(null, mixingTo, propertyIDs); - } - } - - /// The track entry for the animation currently playing on the track, or null if no animation is currently playing. - public TrackEntry GetCurrent (int trackIndex) { - return (trackIndex >= tracks.Count) ? null : tracks.Items[trackIndex]; - } - - override public string ToString () { - var buffer = new System.Text.StringBuilder(); - for (int i = 0, n = tracks.Count; i < n; i++) { - TrackEntry entry = tracks.Items[i]; - if (entry == null) continue; - if (buffer.Length > 0) buffer.Append(", "); - buffer.Append(entry.ToString()); - } - return buffer.Length == 0 ? "" : buffer.ToString(); - } - - internal void OnStart (TrackEntry entry) { if (Start != null) Start(entry); } - internal void OnInterrupt (TrackEntry entry) { if (Interrupt != null) Interrupt(entry); } - internal void OnEnd (TrackEntry entry) { if (End != null) End(entry); } - internal void OnDispose (TrackEntry entry) { if (Dispose != null) Dispose(entry); } - internal void OnComplete (TrackEntry entry) { if (Complete != null) Complete(entry); } - internal void OnEvent (TrackEntry entry, Event e) { if (Event != null) Event(entry, e); } - } - - /// State for the playback of an animation. - public class TrackEntry : Pool.IPoolable { - internal Animation animation; - - internal TrackEntry next, mixingFrom; - internal int trackIndex; - - internal bool loop; - internal float eventThreshold, attachmentThreshold, drawOrderThreshold; - internal float animationStart, animationEnd, animationLast, nextAnimationLast; - internal float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale = 1f; - internal float alpha, mixTime, mixDuration, interruptAlpha, totalAlpha; - internal readonly ExposedList timelineData = new ExposedList(); - internal readonly ExposedList timelineDipMix = new ExposedList(); - internal readonly ExposedList timelinesRotation = new ExposedList(); - - // IPoolable.Reset() - public void Reset () { - next = null; - mixingFrom = null; - animation = null; - timelineData.Clear(); - timelineDipMix.Clear(); - timelinesRotation.Clear(); - - Start = null; - Interrupt = null; - End = null; - Dispose = null; - Complete = null; - Event = null; - } - - /// Sets the timeline data. - /// May be null. - internal TrackEntry SetTimelineData (TrackEntry to, ExposedList mixingToArray, HashSet propertyIDs) { - if (to != null) mixingToArray.Add(to); - var lastEntry = mixingFrom != null ? mixingFrom.SetTimelineData(this, mixingToArray, propertyIDs) : this; - if (to != null) mixingToArray.Pop(); - - var mixingTo = mixingToArray.Items; - int mixingToLast = mixingToArray.Count - 1; - var timelines = animation.timelines.Items; - int timelinesCount = animation.timelines.Count; - var timelineDataItems = timelineData.Resize(timelinesCount).Items; // timelineData.setSize(timelinesCount); - timelineDipMix.Clear(); - var timelineDipMixItems = timelineDipMix.Resize(timelinesCount).Items; //timelineDipMix.setSize(timelinesCount); - - // outer: - for (int i = 0; i < timelinesCount; i++) { - int id = timelines[i].PropertyId; - if (!propertyIDs.Add(id)) { - timelineDataItems[i] = AnimationState.Subsequent; - } else if (to == null || !to.HasTimeline(id)) { - timelineDataItems[i] = AnimationState.First; - } else { - for (int ii = mixingToLast; ii >= 0; ii--) { - var entry = mixingTo[ii]; - if (!entry.HasTimeline(id)) { - if (entry.mixDuration > 0) { - timelineDataItems[i] = AnimationState.DipMix; - timelineDipMixItems[i] = entry; - goto continue_outer; // continue outer; - } - break; - } - } - timelineDataItems[i] = AnimationState.Dip; - } - continue_outer: {} - } - return lastEntry; - } - - bool HasTimeline (int id) { - var timelines = animation.timelines.Items; - for (int i = 0, n = animation.timelines.Count; i < n; i++) - if (timelines[i].PropertyId == id) return true; - return false; - } - - /// The index of the track where this entry is either current or queued. - public int TrackIndex { get { return trackIndex; } } - - /// The animation to apply for this track entry. - public Animation Animation { get { return animation; } } - - /// - /// If true, the animation will repeat. If false, it will not, instead its last frame is applied if played beyond its duration. - public bool Loop { get { return loop; } set { loop = value; } } - - /// - /// 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. - public float Delay { get { return delay; } set { delay = value; } } - - /// - /// Current time in seconds this track entry has been the current track entry. The track time determines - /// . The track time can be set to start the animation at a time other than 0, without affecting looping. - public float TrackTime { get { return trackTime; } set { trackTime = value; } } - - /// - /// 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 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 to mix the properties back to the - /// setup pose over time, rather than have it happen instantly. - /// - public float TrackEnd { get { return trackEnd; } set { trackEnd = value; } } - - /// - /// 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 to the same value to - /// prevent timeline keys before the start time from triggering. - /// - public float AnimationStart { get { return animationStart; } set { animationStart = value; } } - - /// - /// Seconds for the last frame of this animation. Non-looping animations won't play past this time. Looping animations will - /// loop back to at this time. Defaults to the animation duration. - public float AnimationEnd { get { return animationEnd; } set { animationEnd = value; } } - - /// - /// 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. - public float AnimationLast { - get { return animationLast; } - set { - animationLast = value; - nextAnimationLast = value; - } - } - - /// - /// Uses to compute the animation time between . and - /// . When the track time is 0, the animation time is equal to the animation start time. - /// - public float AnimationTime { - get { - if (loop) { - float duration = animationEnd - animationStart; - if (duration == 0) return animationStart; - return (trackTime % duration) + animationStart; - } - return Math.Min(trackTime + animationStart, animationEnd); - } - } - - /// - /// Multiplier for the delta time when the animation state is updated, causing time for this animation to play slower or - /// faster. Defaults to 1. - /// - public float TimeScale { get { return timeScale; } set { timeScale = value; } } - - /// - /// 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. - /// - public float Alpha { get { return alpha; } set { alpha = value; } } - - /// - /// 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. - public float EventThreshold { get { return eventThreshold; } set { eventThreshold = value; } } - - /// - /// 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. - public float AttachmentThreshold { get { return attachmentThreshold; } set { attachmentThreshold = value; } } - - /// - /// 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. - /// - public float DrawOrderThreshold { get { return drawOrderThreshold; } set { drawOrderThreshold = value; } } - - /// - /// The animation queued to start after this animation, or null. - public TrackEntry Next { get { return next; } } - - /// - /// Returns true if at least one loop has been completed. - public bool IsComplete { - get { return trackTime >= animationEnd - animationStart; } - } - - /// - /// Seconds from 0 to the mix duration when mixing from the previous animation to this animation. May be slightly more than - /// when the mix is complete. - public float MixTime { get { return mixTime; } set { mixTime = value; } } - - /// - /// Seconds for mixing from the previous animation to this animation. Defaults to the value provided by - /// 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 is next called. - /// - /// When using with a - /// delay less than or equal to 0, note the is set using the mix duration from the - /// - /// - /// - public float MixDuration { get { return mixDuration; } set { mixDuration = value; } } - - /// - /// 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 linked list. - public TrackEntry MixingFrom { get { return mixingFrom; } } - - public event AnimationState.TrackEntryDelegate Start, Interrupt, End, Dispose, Complete; - public event AnimationState.TrackEntryEventDelegate Event; - internal void OnStart () { if (Start != null) Start(this); } - internal void OnInterrupt () { if (Interrupt != null) Interrupt(this); } - internal void OnEnd () { if (End != null) End(this); } - internal void OnDispose () { if (Dispose != null) Dispose(this); } - internal void OnComplete () { if (Complete != null) Complete(this); } - internal void OnEvent (Event e) { if (Event != null) Event(this, e); } - - /// - /// 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 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. - public void ResetRotationDirections () { - timelinesRotation.Clear(); - } - - override public string ToString () { - return animation == null ? "" : animation.name; - } - } - - class EventQueue { - private readonly List eventQueueEntries = new List(); - internal bool drainDisabled; - - private readonly AnimationState state; - private readonly Pool trackEntryPool; - internal event Action AnimationsChanged; - - internal EventQueue (AnimationState state, Action HandleAnimationsChanged, Pool trackEntryPool) { - this.state = state; - this.AnimationsChanged += HandleAnimationsChanged; - this.trackEntryPool = trackEntryPool; - } - - struct EventQueueEntry { - public EventType type; - public TrackEntry entry; - public Event e; - - public EventQueueEntry (EventType eventType, TrackEntry trackEntry, Event e = null) { - this.type = eventType; - this.entry = trackEntry; - this.e = e; - } - } - - enum EventType { - Start, Interrupt, End, Dispose, Complete, Event - } - - internal void Start (TrackEntry entry) { - eventQueueEntries.Add(new EventQueueEntry(EventType.Start, entry)); - if (AnimationsChanged != null) AnimationsChanged(); - } - - internal void Interrupt (TrackEntry entry) { - eventQueueEntries.Add(new EventQueueEntry(EventType.Interrupt, entry)); - } - - internal void End (TrackEntry entry) { - eventQueueEntries.Add(new EventQueueEntry(EventType.End, entry)); - if (AnimationsChanged != null) AnimationsChanged(); - } - - internal void Dispose (TrackEntry entry) { - eventQueueEntries.Add(new EventQueueEntry(EventType.Dispose, entry)); - } - - internal void Complete (TrackEntry entry) { - eventQueueEntries.Add(new EventQueueEntry(EventType.Complete, entry)); - } - - internal void Event (TrackEntry entry, Event e) { - eventQueueEntries.Add(new EventQueueEntry(EventType.Event, entry, e)); - } - - /// Raises all events in the queue and drains the queue. - internal void Drain () { - if (drainDisabled) return; - drainDisabled = true; - - var entries = this.eventQueueEntries; - AnimationState state = this.state; - - // Don't cache entries.Count so callbacks can queue their own events (eg, call SetAnimation in AnimationState_Complete). - for (int i = 0; i < entries.Count; i++) { - var queueEntry = entries[i]; - TrackEntry trackEntry = queueEntry.entry; - - switch (queueEntry.type) { - case EventType.Start: - trackEntry.OnStart(); - state.OnStart(trackEntry); - break; - case EventType.Interrupt: - trackEntry.OnInterrupt(); - state.OnInterrupt(trackEntry); - break; - case EventType.End: - trackEntry.OnEnd(); - state.OnEnd(trackEntry); - goto case EventType.Dispose; // Fall through. (C#) - case EventType.Dispose: - trackEntry.OnDispose(); - state.OnDispose(trackEntry); - trackEntryPool.Free(trackEntry); // Pooling - break; - case EventType.Complete: - trackEntry.OnComplete(); - state.OnComplete(trackEntry); - break; - case EventType.Event: - trackEntry.OnEvent(queueEntry.e); - state.OnEvent(trackEntry, queueEntry.e); - break; - } - } - eventQueueEntries.Clear(); - - drainDisabled = false; - } - - internal void Clear () { - eventQueueEntries.Clear(); - } - } - - public class Pool where T : class, new() { - public readonly int max; - readonly Stack freeObjects; - - public int Count { get { return freeObjects.Count; } } - public int Peak { get; private set; } - - public Pool (int initialCapacity = 16, int max = int.MaxValue) { - freeObjects = new Stack(initialCapacity); - this.max = max; - } - - public T Obtain () { - return freeObjects.Count == 0 ? new T() : freeObjects.Pop(); - } - - public void Free (T obj) { - if (obj == null) throw new ArgumentNullException("obj", "obj cannot be null"); - if (freeObjects.Count < max) { - freeObjects.Push(obj); - Peak = Math.Max(Peak, freeObjects.Count); - } - Reset(obj); - } - -// protected void FreeAll (List objects) { -// if (objects == null) throw new ArgumentNullException("objects", "objects cannot be null."); -// var freeObjects = this.freeObjects; -// int max = this.max; -// for (int i = 0; i < objects.Count; i++) { -// T obj = objects[i]; -// if (obj == null) continue; -// if (freeObjects.Count < max) freeObjects.Push(obj); -// Reset(obj); -// } -// Peak = Math.Max(Peak, freeObjects.Count); -// } - - public void Clear () { - freeObjects.Clear(); - } - - protected void Reset (T obj) { - var poolable = obj as IPoolable; - if (poolable != null) poolable.Reset(); - } - - public interface IPoolable { - void Reset (); - } - } +namespace Spine3_6_53 +{ + public class AnimationState + { + static readonly Animation EmptyAnimation = new Animation("", new ExposedList(), 0); + internal const int Subsequent = 0, First = 1, Dip = 2, DipMix = 3; + + private AnimationStateData data; + + Pool trackEntryPool = new Pool(); + private readonly ExposedList tracks = new ExposedList(); + private readonly ExposedList events = new ExposedList(); + private readonly EventQueue queue; // Initialized by constructor. + + private readonly HashSet propertyIDs = new HashSet(); + private readonly ExposedList mixingTo = new ExposedList(); + private bool animationsChanged; + + private float timeScale = 1; + + public AnimationStateData Data { get { return data; } } + /// A list of tracks that have animations, which may contain nulls. + public ExposedList Tracks { get { return tracks; } } + public float TimeScale { get { return timeScale; } set { timeScale = value; } } + + public delegate void TrackEntryDelegate(TrackEntry trackEntry); + public event TrackEntryDelegate Start, Interrupt, End, Dispose, Complete; + + public delegate void TrackEntryEventDelegate(TrackEntry trackEntry, Event e); + public event TrackEntryEventDelegate Event; + + public AnimationState(AnimationStateData data) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + this.data = data; + this.queue = new EventQueue( + this, + delegate { this.animationsChanged = true; }, + trackEntryPool + ); + } + + /// + /// Increments the track entry times, setting queued animations as current if needed + /// delta time + public void Update(float delta) + { + delta *= timeScale; + var tracksItems = tracks.Items; + for (int i = 0, n = tracks.Count; i < n; i++) + { + TrackEntry current = tracksItems[i]; + if (current == null) continue; + + 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 = nextTime + (delta * next.timeScale); + current.trackTime += currentDelta; + SetCurrent(i, next, true); + while (next.mixingFrom != null) + { + next.mixTime += currentDelta; + 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. + tracksItems[i] = null; + + queue.End(current); + DisposeNext(current); + continue; + } + if (current.mixingFrom != null && UpdateMixingFrom(current, delta)) + { + // End mixing from entries once all have completed. + var from = current.mixingFrom; + current.mixingFrom = null; + while (from != null) + { + queue.End(from); + from = from.mixingFrom; + } + } + + current.trackTime += currentDelta; + } + + queue.Drain(); + } + + /// Returns true when all mixing from entries are complete. + private bool 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; + + // Require mixTime > 0 to ensure the mixing from entry was applied at least once. + if (to.mixTime > 0 && (to.mixTime >= to.mixDuration || to.timeScale == 0)) + { + // Require totalAlpha == 0 to ensure mixing is complete, unless mixDuration == 0 (the transition is a single frame). + if (from.totalAlpha == 0 || to.mixDuration == 0) + { + to.mixingFrom = from.mixingFrom; + to.interruptAlpha = from.interruptAlpha; + queue.End(from); + } + return finished; + } + + from.trackTime += delta * from.timeScale; + to.mixTime += delta * to.timeScale; + return false; + } + + /// + /// 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. + public bool Apply(Skeleton skeleton) + { + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + if (animationsChanged) AnimationsChanged(); + + var events = this.events; + + bool applied = false; + var tracksItems = tracks.Items; + for (int i = 0, m = tracks.Count; i < m; i++) + { + TrackEntry current = tracksItems[i]; + if (current == null || current.delay > 0) continue; + applied = true; + MixPose currentPose = i == 0 ? MixPose.Current : MixPose.CurrentLayered; + + // Apply mixing from entries first. + float mix = current.alpha; + if (current.mixingFrom != null) + mix *= ApplyMixingFrom(current, skeleton, currentPose); + else if (current.trackTime >= current.trackEnd && current.next == null) // + mix = 0; // Set to setup pose the last time the entry will be applied. + + // Apply current entry. + float animationLast = current.animationLast, animationTime = current.AnimationTime; + int timelineCount = current.animation.timelines.Count; + var timelines = current.animation.timelines; + var timelinesItems = timelines.Items; + if (mix == 1) + { + for (int ii = 0; ii < timelineCount; ii++) + timelinesItems[ii].Apply(skeleton, animationLast, animationTime, events, 1, MixPose.Setup, MixDirection.In); + } + else + { + var timelineData = current.timelineData.Items; + + bool firstFrame = current.timelinesRotation.Count == 0; + if (firstFrame) current.timelinesRotation.EnsureCapacity(timelines.Count << 1); + var timelinesRotation = current.timelinesRotation.Items; + + for (int ii = 0; ii < timelineCount; ii++) + { + Timeline timeline = timelinesItems[ii]; + MixPose pose = timelineData[ii] >= AnimationState.First ? MixPose.Setup : currentPose; + var rotateTimeline = timeline as RotateTimeline; + if (rotateTimeline != null) + ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, mix, pose, timelinesRotation, ii << 1, firstFrame); + else + timeline.Apply(skeleton, animationLast, animationTime, events, mix, pose, MixDirection.In); + } + } + QueueEvents(current, animationTime); + events.Clear(false); + current.nextAnimationLast = animationTime; + current.nextTrackLast = current.trackTime; + } + + queue.Drain(); + return applied; + } + + private float ApplyMixingFrom(TrackEntry to, Skeleton skeleton, MixPose currentPose) + { + TrackEntry from = to.mixingFrom; + if (from.mixingFrom != null) ApplyMixingFrom(from, skeleton, currentPose); + + float mix; + if (to.mixDuration == 0) + { // Single frame mix to undo mixingFrom changes. + mix = 1; + currentPose = MixPose.Setup; + } + else + { + mix = to.mixTime / to.mixDuration; + if (mix > 1) mix = 1; + } + + var eventBuffer = mix < from.eventThreshold ? this.events : null; + bool attachments = mix < from.attachmentThreshold, drawOrder = mix < from.drawOrderThreshold; + float animationLast = from.animationLast, animationTime = from.AnimationTime; + var timelines = from.animation.timelines; + int timelineCount = timelines.Count; + var timelinesItems = timelines.Items; + var timelineData = from.timelineData.Items; + var timelineDipMix = from.timelineDipMix.Items; + + bool firstFrame = from.timelinesRotation.Count == 0; + if (firstFrame) from.timelinesRotation.Resize(timelines.Count << 1); // from.timelinesRotation.setSize + var timelinesRotation = from.timelinesRotation.Items; + + MixPose pose; + float alphaDip = from.alpha * to.interruptAlpha, alphaMix = alphaDip * (1 - mix), alpha; + from.totalAlpha = 0; + for (int i = 0; i < timelineCount; i++) + { + Timeline timeline = timelinesItems[i]; + switch (timelineData[i]) + { + case Subsequent: + if (!attachments && timeline is AttachmentTimeline) continue; + if (!drawOrder && timeline is DrawOrderTimeline) continue; + pose = currentPose; + alpha = alphaMix; + break; + case First: + pose = MixPose.Setup; + alpha = alphaMix; + break; + case Dip: + pose = MixPose.Setup; + alpha = alphaDip; + break; + default: + pose = MixPose.Setup; + TrackEntry dipMix = timelineDipMix[i]; + alpha = alphaDip * Math.Max(0, 1 - dipMix.mixTime / dipMix.mixDuration); + break; + } + from.totalAlpha += alpha; + var rotateTimeline = timeline as RotateTimeline; + if (rotateTimeline != null) + { + ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, alpha, pose, timelinesRotation, i << 1, firstFrame); + } + else + { + timeline.Apply(skeleton, animationLast, animationTime, eventBuffer, alpha, pose, MixDirection.Out); + } + } + + if (to.mixDuration > 0) QueueEvents(from, animationTime); + this.events.Clear(false); + from.nextAnimationLast = animationTime; + from.nextTrackLast = from.trackTime; + + return mix; + } + + static private void ApplyRotateTimeline(RotateTimeline rotateTimeline, Skeleton skeleton, float time, float alpha, MixPose pose, + float[] timelinesRotation, int i, bool firstFrame) + { + + if (firstFrame) timelinesRotation[i] = 0; + + if (alpha == 1) + { + rotateTimeline.Apply(skeleton, 0, time, null, 1, pose, MixDirection.In); + return; + } + + Bone bone = skeleton.bones.Items[rotateTimeline.boneIndex]; + float[] frames = rotateTimeline.frames; + if (time < frames[0]) + { + if (pose == MixPose.Setup) bone.rotation = bone.data.rotation; + return; + } + + float r2; + if (time >= frames[frames.Length - RotateTimeline.ENTRIES]) // Time is after last frame. + r2 = bone.data.rotation + frames[frames.Length + RotateTimeline.PREV_ROTATION]; + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, RotateTimeline.ENTRIES); + float prevRotation = frames[frame + RotateTimeline.PREV_ROTATION]; + float frameTime = frames[frame]; + float percent = rotateTimeline.GetCurvePercent((frame >> 1) - 1, + 1 - (time - frameTime) / (frames[frame + RotateTimeline.PREV_TIME] - frameTime)); + + r2 = frames[frame + RotateTimeline.ROTATION] - prevRotation; + r2 -= (16384 - (int)(16384.499999999996 - r2 / 360)) * 360; + r2 = prevRotation + r2 * percent + bone.data.rotation; + r2 -= (16384 - (int)(16384.499999999996 - r2 / 360)) * 360; + } + + // Mix between rotations using the direction of the shortest route on the first frame while detecting crosses. + float r1 = pose == MixPose.Setup ? bone.data.rotation : bone.rotation; + float total, diff = r2 - r1; + if (diff == 0) + { + total = timelinesRotation[i]; + } + else + { + diff -= (16384 - (int)(16384.499999999996 - diff / 360)) * 360; + float lastTotal, lastDiff; + if (firstFrame) + { + lastTotal = 0; + lastDiff = diff; + } + else + { + lastTotal = timelinesRotation[i]; // Angle and direction of mix, including loops. + lastDiff = timelinesRotation[i + 1]; // Difference between bones. + } + bool current = diff > 0, dir = lastTotal >= 0; + // Detect cross at 0 (not 180). + if (Math.Sign(lastDiff) != Math.Sign(diff) && Math.Abs(lastDiff) <= 90) + { + // A cross after a 360 rotation is a loop. + if (Math.Abs(lastTotal) > 180) lastTotal += 360 * Math.Sign(lastTotal); + dir = current; + } + total = diff + lastTotal - lastTotal % 360; // Store loops as part of lastTotal. + if (dir != current) total += 360 * Math.Sign(lastTotal); + timelinesRotation[i] = total; + } + timelinesRotation[i + 1] = diff; + r1 += total * alpha; + bone.rotation = r1 - (16384 - (int)(16384.499999999996 - r1 / 360)) * 360; + } + + private void QueueEvents(TrackEntry entry, float animationTime) + { + float animationStart = entry.animationStart, animationEnd = entry.animationEnd; + float duration = animationEnd - animationStart; + float trackLastWrapped = entry.trackLast % duration; + + // Queue events before complete. + var events = this.events; + var eventsItems = events.Items; + int i = 0, n = events.Count; + for (; i < n; i++) + { + var e = eventsItems[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) + complete = duration == 0 || (trackLastWrapped > entry.trackTime % duration); + else + complete = animationTime >= animationEnd && entry.animationLast < animationEnd; + if (complete) queue.Complete(entry); + + // Queue events after complete. + for (; i < n; i++) + { + Event e = eventsItems[i]; + if (e.time < animationStart) continue; // Discard events outside animation start/end. + queue.Event(entry, eventsItems[i]); + } + } + + /// + /// Removes all animations from all tracks, leaving skeletons in their previous pose. + /// It may be desired to use to mix the skeletons back to the setup pose, + /// rather than leaving them in their previous pose. + public void ClearTracks() + { + bool oldDrainDisabled = queue.drainDisabled; + queue.drainDisabled = true; + for (int i = 0, n = tracks.Count; i < n; i++) + { + ClearTrack(i); + } + tracks.Clear(); + queue.drainDisabled = oldDrainDisabled; + queue.Drain(); + } + + /// + /// Removes all animations from the tracks, leaving skeletons in their previous pose. + /// It may be desired to use to mix the skeletons back to the setup pose, + /// rather than leaving them in their previous pose. + public void ClearTrack(int trackIndex) + { + if (trackIndex >= tracks.Count) return; + TrackEntry current = tracks.Items[trackIndex]; + if (current == null) return; + + queue.End(current); + + DisposeNext(current); + + TrackEntry entry = current; + while (true) + { + TrackEntry from = entry.mixingFrom; + if (from == null) break; + queue.End(from); + entry.mixingFrom = null; + entry = from; + } + + tracks.Items[current.trackIndex] = null; + + queue.Drain(); + } + + /// Sets the active TrackEntry for a given track number. + private void SetCurrent(int index, TrackEntry current, bool interrupt) + { + TrackEntry from = ExpandToIndex(index); + tracks.Items[index] = current; + + if (from != null) + { + if (interrupt) queue.Interrupt(from); + current.mixingFrom = from; + current.mixTime = 0; + + // Store interrupted mix percentage. + if (from.mixingFrom != null && from.mixDuration > 0) + current.interruptAlpha *= Math.Min(1, from.mixTime / from.mixDuration); + + from.timelinesRotation.Clear(); // Reset rotation for mixing out, in case entry was mixed in. + } + + queue.Start(current); // triggers AnimationsChanged + } + + + /// Sets an animation by name. + public TrackEntry SetAnimation(int trackIndex, string animationName, bool loop) + { + Animation animation = data.skeletonData.FindAnimation(animationName); + if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName"); + return SetAnimation(trackIndex, animation, loop); + } + + /// Sets the current animation for a track, discarding any queued animations. + /// 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 determines when the track is cleared. + /// + /// A track entry to allow further customization of animation playback. References to the track entry must not be kept + /// after . + public TrackEntry SetAnimation(int trackIndex, Animation animation, bool loop) + { + if (animation == null) throw new ArgumentNullException("animation", "animation cannot be 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.Items[trackIndex] = current.mixingFrom; + queue.Interrupt(current); + queue.End(current); + DisposeNext(current); + current = current.mixingFrom; + interrupt = false; + } + else + { + DisposeNext(current); + } + } + TrackEntry entry = NewTrackEntry(trackIndex, animation, loop, current); + SetCurrent(trackIndex, entry, interrupt); + queue.Drain(); + return entry; + } + + /// Queues an animation by name. + /// + public TrackEntry AddAnimation(int trackIndex, string animationName, bool loop, float delay) + { + Animation animation = data.skeletonData.FindAnimation(animationName); + if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName"); + return AddAnimation(trackIndex, animation, loop, 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 . + /// + /// 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. + /// + /// A track entry to allow further customization of animation playback. References to the track entry must not be kept + /// after + public TrackEntry AddAnimation(int trackIndex, Animation animation, bool loop, float delay) + { + if (animation == null) throw new ArgumentNullException("animation", "animation cannot be 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(); + } + else + { + last.next = entry; + if (delay <= 0) + { + float duration = last.animationEnd - last.animationStart; + if (duration != 0) + { + if (last.loop) + { + delay += duration * (1 + (int)(last.trackTime / duration)); + } + else + { + delay += duration; + } + delay -= data.GetMix(last.animation, animation); + } + else + delay = 0; + } + } + + entry.delay = delay; + return entry; + } + + /// + /// Sets an empty animation for a track, discarding any queued animations, and mixes to it over the specified mix duration. + public TrackEntry SetEmptyAnimation(int trackIndex, float mixDuration) + { + TrackEntry entry = SetAnimation(trackIndex, AnimationState.EmptyAnimation, false); + entry.mixDuration = mixDuration; + entry.trackEnd = mixDuration; + return entry; + } + + /// + /// 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. + /// + /// A track entry to allow further customization of animation playback. References to the track entry must not be kept after . + /// + /// Track number. + /// Mix duration. + /// 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. + public TrackEntry AddEmptyAnimation(int trackIndex, float mixDuration, float delay) + { + if (delay <= 0) delay -= mixDuration; + TrackEntry entry = AddAnimation(trackIndex, AnimationState.EmptyAnimation, false, delay); + entry.mixDuration = mixDuration; + entry.trackEnd = mixDuration; + return entry; + } + + /// + /// Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix duration. + public void SetEmptyAnimations(float mixDuration) + { + bool oldDrainDisabled = queue.drainDisabled; + queue.drainDisabled = true; + for (int i = 0, n = tracks.Count; i < n; i++) + { + TrackEntry current = tracks.Items[i]; + if (current != null) SetEmptyAnimation(i, mixDuration); + } + queue.drainDisabled = oldDrainDisabled; + queue.Drain(); + } + + private TrackEntry ExpandToIndex(int index) + { + if (index < tracks.Count) return tracks.Items[index]; + while (index >= tracks.Count) + tracks.Add(null); + return null; + } + + /// Object-pooling version of new TrackEntry. Obtain an unused TrackEntry from the pool and clear/initialize its values. + /// May be null. + private TrackEntry NewTrackEntry(int trackIndex, Animation animation, bool loop, TrackEntry last) + { + TrackEntry entry = trackEntryPool.Obtain(); // Pooling + entry.trackIndex = trackIndex; + entry.animation = animation; + entry.loop = loop; + + entry.eventThreshold = 0; + entry.attachmentThreshold = 0; + entry.drawOrderThreshold = 0; + + entry.animationStart = 0; + entry.animationEnd = animation.Duration; + 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 = float.MaxValue; // loop ? float.MaxValue : animation.Duration; + entry.timeScale = 1; + + entry.alpha = 1; + entry.interruptAlpha = 1; + entry.mixTime = 0; + entry.mixDuration = (last == null) ? 0 : data.GetMix(last.animation, animation); + return entry; + } + + /// Dispose all track entries queued after the given TrackEntry. + private void DisposeNext(TrackEntry entry) + { + TrackEntry next = entry.next; + while (next != null) + { + queue.Dispose(next); + next = next.next; + } + entry.next = null; + } + + private void AnimationsChanged() + { + animationsChanged = false; + + var propertyIDs = this.propertyIDs; + propertyIDs.Clear(); + var mixingTo = this.mixingTo; + + var tracksItems = tracks.Items; + for (int i = 0, n = tracks.Count; i < n; i++) + { + var entry = tracksItems[i]; + if (entry != null) entry.SetTimelineData(null, mixingTo, propertyIDs); + } + } + + /// The track entry for the animation currently playing on the track, or null if no animation is currently playing. + public TrackEntry GetCurrent(int trackIndex) + { + return (trackIndex >= tracks.Count) ? null : tracks.Items[trackIndex]; + } + + override public string ToString() + { + var buffer = new System.Text.StringBuilder(); + for (int i = 0, n = tracks.Count; i < n; i++) + { + TrackEntry entry = tracks.Items[i]; + if (entry == null) continue; + if (buffer.Length > 0) buffer.Append(", "); + buffer.Append(entry.ToString()); + } + return buffer.Length == 0 ? "" : buffer.ToString(); + } + + internal void OnStart(TrackEntry entry) { if (Start != null) Start(entry); } + internal void OnInterrupt(TrackEntry entry) { if (Interrupt != null) Interrupt(entry); } + internal void OnEnd(TrackEntry entry) { if (End != null) End(entry); } + internal void OnDispose(TrackEntry entry) { if (Dispose != null) Dispose(entry); } + internal void OnComplete(TrackEntry entry) { if (Complete != null) Complete(entry); } + internal void OnEvent(TrackEntry entry, Event e) { if (Event != null) Event(entry, e); } + } + + /// State for the playback of an animation. + public class TrackEntry : Pool.IPoolable + { + internal Animation animation; + + internal TrackEntry next, mixingFrom; + internal int trackIndex; + + internal bool loop; + internal float eventThreshold, attachmentThreshold, drawOrderThreshold; + internal float animationStart, animationEnd, animationLast, nextAnimationLast; + internal float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale = 1f; + internal float alpha, mixTime, mixDuration, interruptAlpha, totalAlpha; + internal readonly ExposedList timelineData = new ExposedList(); + internal readonly ExposedList timelineDipMix = new ExposedList(); + internal readonly ExposedList timelinesRotation = new ExposedList(); + + // IPoolable.Reset() + public void Reset() + { + next = null; + mixingFrom = null; + animation = null; + timelineData.Clear(); + timelineDipMix.Clear(); + timelinesRotation.Clear(); + + Start = null; + Interrupt = null; + End = null; + Dispose = null; + Complete = null; + Event = null; + } + + /// Sets the timeline data. + /// May be null. + internal TrackEntry SetTimelineData(TrackEntry to, ExposedList mixingToArray, HashSet propertyIDs) + { + if (to != null) mixingToArray.Add(to); + var lastEntry = mixingFrom != null ? mixingFrom.SetTimelineData(this, mixingToArray, propertyIDs) : this; + if (to != null) mixingToArray.Pop(); + + var mixingTo = mixingToArray.Items; + int mixingToLast = mixingToArray.Count - 1; + var timelines = animation.timelines.Items; + int timelinesCount = animation.timelines.Count; + var timelineDataItems = timelineData.Resize(timelinesCount).Items; // timelineData.setSize(timelinesCount); + timelineDipMix.Clear(); + var timelineDipMixItems = timelineDipMix.Resize(timelinesCount).Items; //timelineDipMix.setSize(timelinesCount); + + // outer: + for (int i = 0; i < timelinesCount; i++) + { + int id = timelines[i].PropertyId; + if (!propertyIDs.Add(id)) + { + timelineDataItems[i] = AnimationState.Subsequent; + } + else if (to == null || !to.HasTimeline(id)) + { + timelineDataItems[i] = AnimationState.First; + } + else + { + for (int ii = mixingToLast; ii >= 0; ii--) + { + var entry = mixingTo[ii]; + if (!entry.HasTimeline(id)) + { + if (entry.mixDuration > 0) + { + timelineDataItems[i] = AnimationState.DipMix; + timelineDipMixItems[i] = entry; + goto continue_outer; // continue outer; + } + break; + } + } + timelineDataItems[i] = AnimationState.Dip; + } + continue_outer: { } + } + return lastEntry; + } + + bool HasTimeline(int id) + { + var timelines = animation.timelines.Items; + for (int i = 0, n = animation.timelines.Count; i < n; i++) + if (timelines[i].PropertyId == id) return true; + return false; + } + + /// The index of the track where this entry is either current or queued. + public int TrackIndex { get { return trackIndex; } } + + /// The animation to apply for this track entry. + public Animation Animation { get { return animation; } } + + /// + /// If true, the animation will repeat. If false, it will not, instead its last frame is applied if played beyond its duration. + public bool Loop { get { return loop; } set { loop = value; } } + + /// + /// 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. + public float Delay { get { return delay; } set { delay = value; } } + + /// + /// Current time in seconds this track entry has been the current track entry. The track time determines + /// . The track time can be set to start the animation at a time other than 0, without affecting looping. + public float TrackTime { get { return trackTime; } set { trackTime = value; } } + + /// + /// 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 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 to mix the properties back to the + /// setup pose over time, rather than have it happen instantly. + /// + public float TrackEnd { get { return trackEnd; } set { trackEnd = value; } } + + /// + /// 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 to the same value to + /// prevent timeline keys before the start time from triggering. + /// + public float AnimationStart { get { return animationStart; } set { animationStart = value; } } + + /// + /// Seconds for the last frame of this animation. Non-looping animations won't play past this time. Looping animations will + /// loop back to at this time. Defaults to the animation duration. + public float AnimationEnd { get { return animationEnd; } set { animationEnd = value; } } + + /// + /// 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. + public float AnimationLast + { + get { return animationLast; } + set + { + animationLast = value; + nextAnimationLast = value; + } + } + + /// + /// Uses to compute the animation time between . and + /// . When the track time is 0, the animation time is equal to the animation start time. + /// + public float AnimationTime + { + get + { + if (loop) + { + float duration = animationEnd - animationStart; + if (duration == 0) return animationStart; + return (trackTime % duration) + animationStart; + } + return Math.Min(trackTime + animationStart, animationEnd); + } + } + + /// + /// Multiplier for the delta time when the animation state is updated, causing time for this animation to play slower or + /// faster. Defaults to 1. + /// + public float TimeScale { get { return timeScale; } set { timeScale = value; } } + + /// + /// 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. + /// + public float Alpha { get { return alpha; } set { alpha = value; } } + + /// + /// 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. + public float EventThreshold { get { return eventThreshold; } set { eventThreshold = value; } } + + /// + /// 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. + public float AttachmentThreshold { get { return attachmentThreshold; } set { attachmentThreshold = value; } } + + /// + /// 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. + /// + public float DrawOrderThreshold { get { return drawOrderThreshold; } set { drawOrderThreshold = value; } } + + /// + /// The animation queued to start after this animation, or null. + public TrackEntry Next { get { return next; } } + + /// + /// Returns true if at least one loop has been completed. + public bool IsComplete + { + get { return trackTime >= animationEnd - animationStart; } + } + + /// + /// Seconds from 0 to the mix duration when mixing from the previous animation to this animation. May be slightly more than + /// when the mix is complete. + public float MixTime { get { return mixTime; } set { mixTime = value; } } + + /// + /// Seconds for mixing from the previous animation to this animation. Defaults to the value provided by + /// 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 is next called. + /// + /// When using with a + /// delay less than or equal to 0, note the is set using the mix duration from the + /// + /// + /// + public float MixDuration { get { return mixDuration; } set { mixDuration = value; } } + + /// + /// 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 linked list. + public TrackEntry MixingFrom { get { return mixingFrom; } } + + public event AnimationState.TrackEntryDelegate Start, Interrupt, End, Dispose, Complete; + public event AnimationState.TrackEntryEventDelegate Event; + internal void OnStart() { if (Start != null) Start(this); } + internal void OnInterrupt() { if (Interrupt != null) Interrupt(this); } + internal void OnEnd() { if (End != null) End(this); } + internal void OnDispose() { if (Dispose != null) Dispose(this); } + internal void OnComplete() { if (Complete != null) Complete(this); } + internal void OnEvent(Event e) { if (Event != null) Event(this, e); } + + /// + /// 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 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. + public void ResetRotationDirections() + { + timelinesRotation.Clear(); + } + + override public string ToString() + { + return animation == null ? "" : animation.name; + } + } + + class EventQueue + { + private readonly List eventQueueEntries = new List(); + internal bool drainDisabled; + + private readonly AnimationState state; + private readonly Pool trackEntryPool; + internal event Action AnimationsChanged; + + internal EventQueue(AnimationState state, Action HandleAnimationsChanged, Pool trackEntryPool) + { + this.state = state; + this.AnimationsChanged += HandleAnimationsChanged; + this.trackEntryPool = trackEntryPool; + } + + struct EventQueueEntry + { + public EventType type; + public TrackEntry entry; + public Event e; + + public EventQueueEntry(EventType eventType, TrackEntry trackEntry, Event e = null) + { + this.type = eventType; + this.entry = trackEntry; + this.e = e; + } + } + + enum EventType + { + Start, Interrupt, End, Dispose, Complete, Event + } + + internal void Start(TrackEntry entry) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.Start, entry)); + if (AnimationsChanged != null) AnimationsChanged(); + } + + internal void Interrupt(TrackEntry entry) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.Interrupt, entry)); + } + + internal void End(TrackEntry entry) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.End, entry)); + if (AnimationsChanged != null) AnimationsChanged(); + } + + internal void Dispose(TrackEntry entry) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.Dispose, entry)); + } + + internal void Complete(TrackEntry entry) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.Complete, entry)); + } + + internal void Event(TrackEntry entry, Event e) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.Event, entry, e)); + } + + /// Raises all events in the queue and drains the queue. + internal void Drain() + { + if (drainDisabled) return; + drainDisabled = true; + + var entries = this.eventQueueEntries; + AnimationState state = this.state; + + // Don't cache entries.Count so callbacks can queue their own events (eg, call SetAnimation in AnimationState_Complete). + for (int i = 0; i < entries.Count; i++) + { + var queueEntry = entries[i]; + TrackEntry trackEntry = queueEntry.entry; + + switch (queueEntry.type) + { + case EventType.Start: + trackEntry.OnStart(); + state.OnStart(trackEntry); + break; + case EventType.Interrupt: + trackEntry.OnInterrupt(); + state.OnInterrupt(trackEntry); + break; + case EventType.End: + trackEntry.OnEnd(); + state.OnEnd(trackEntry); + goto case EventType.Dispose; // Fall through. (C#) + case EventType.Dispose: + trackEntry.OnDispose(); + state.OnDispose(trackEntry); + trackEntryPool.Free(trackEntry); // Pooling + break; + case EventType.Complete: + trackEntry.OnComplete(); + state.OnComplete(trackEntry); + break; + case EventType.Event: + trackEntry.OnEvent(queueEntry.e); + state.OnEvent(trackEntry, queueEntry.e); + break; + } + } + eventQueueEntries.Clear(); + + drainDisabled = false; + } + + internal void Clear() + { + eventQueueEntries.Clear(); + } + } + + public class Pool where T : class, new() + { + public readonly int max; + readonly Stack freeObjects; + + public int Count { get { return freeObjects.Count; } } + public int Peak { get; private set; } + + public Pool(int initialCapacity = 16, int max = int.MaxValue) + { + freeObjects = new Stack(initialCapacity); + this.max = max; + } + + public T Obtain() + { + return freeObjects.Count == 0 ? new T() : freeObjects.Pop(); + } + + public void Free(T obj) + { + if (obj == null) throw new ArgumentNullException("obj", "obj cannot be null"); + if (freeObjects.Count < max) + { + freeObjects.Push(obj); + Peak = Math.Max(Peak, freeObjects.Count); + } + Reset(obj); + } + + // protected void FreeAll (List objects) { + // if (objects == null) throw new ArgumentNullException("objects", "objects cannot be null."); + // var freeObjects = this.freeObjects; + // int max = this.max; + // for (int i = 0; i < objects.Count; i++) { + // T obj = objects[i]; + // if (obj == null) continue; + // if (freeObjects.Count < max) freeObjects.Push(obj); + // Reset(obj); + // } + // Peak = Math.Max(Peak, freeObjects.Count); + // } + + public void Clear() + { + freeObjects.Clear(); + } + + protected void Reset(T obj) + { + var poolable = obj as IPoolable; + if (poolable != null) poolable.Reset(); + } + + public interface IPoolable + { + void Reset(); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/AnimationStateData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/AnimationStateData.cs index d26df1b..317d924 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/AnimationStateData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/AnimationStateData.cs @@ -31,85 +31,97 @@ using System; using System.Collections.Generic; -namespace Spine3_6_53 { +namespace Spine3_6_53 +{ - /// Stores mix (crossfade) durations to be applied when AnimationState animations are changed. - public class AnimationStateData { - internal SkeletonData skeletonData; - readonly Dictionary animationToMixTime = new Dictionary(AnimationPairComparer.Instance); - internal float defaultMix; + /// Stores mix (crossfade) durations to be applied when AnimationState animations are changed. + public class AnimationStateData + { + internal SkeletonData skeletonData; + readonly Dictionary animationToMixTime = new Dictionary(AnimationPairComparer.Instance); + internal float defaultMix; - /// The SkeletonData to look up animations when they are specified by name. - public SkeletonData SkeletonData { get { return skeletonData; } } + /// The SkeletonData to look up animations when they are specified by name. + public SkeletonData SkeletonData { get { return skeletonData; } } - /// - /// The mix duration to use when no mix duration has been specifically defined between two animations. - public float DefaultMix { get { return defaultMix; } set { defaultMix = value; } } + /// + /// The mix duration to use when no mix duration has been specifically defined between two animations. + public float DefaultMix { get { return defaultMix; } set { defaultMix = value; } } - public AnimationStateData (SkeletonData skeletonData) { - if (skeletonData == null) throw new ArgumentException("skeletonData cannot be null.", "skeletonData"); - this.skeletonData = skeletonData; - } + public AnimationStateData(SkeletonData skeletonData) + { + if (skeletonData == null) throw new ArgumentException("skeletonData cannot be null.", "skeletonData"); + this.skeletonData = skeletonData; + } - /// Sets a mix duration by animation names. - public void SetMix (string fromName, string toName, float duration) { - Animation from = skeletonData.FindAnimation(fromName); - if (from == null) throw new ArgumentException("Animation not found: " + fromName, "fromName"); - Animation to = skeletonData.FindAnimation(toName); - if (to == null) throw new ArgumentException("Animation not found: " + toName, "toName"); - SetMix(from, to, duration); - } + /// Sets a mix duration by animation names. + public void SetMix(string fromName, string toName, float duration) + { + Animation from = skeletonData.FindAnimation(fromName); + if (from == null) throw new ArgumentException("Animation not found: " + fromName, "fromName"); + Animation to = skeletonData.FindAnimation(toName); + if (to == null) throw new ArgumentException("Animation not found: " + toName, "toName"); + SetMix(from, to, duration); + } - /// Sets a mix duration when changing from the specified animation to the other. - /// See TrackEntry.MixDuration. - public void SetMix (Animation from, Animation to, float duration) { - if (from == null) throw new ArgumentNullException("from", "from cannot be null."); - if (to == null) throw new ArgumentNullException("to", "to cannot be null."); - AnimationPair key = new AnimationPair(from, to); - animationToMixTime.Remove(key); - animationToMixTime.Add(key, duration); - } + /// Sets a mix duration when changing from the specified animation to the other. + /// See TrackEntry.MixDuration. + public void SetMix(Animation from, Animation to, float duration) + { + if (from == null) throw new ArgumentNullException("from", "from cannot be null."); + if (to == null) throw new ArgumentNullException("to", "to cannot be null."); + AnimationPair key = new AnimationPair(from, to); + animationToMixTime.Remove(key); + animationToMixTime.Add(key, 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. - /// - public float GetMix (Animation from, Animation to) { - if (from == null) throw new ArgumentNullException("from", "from cannot be null."); - if (to == null) throw new ArgumentNullException("to", "to cannot be null."); - AnimationPair key = new AnimationPair(from, to); - float duration; - if (animationToMixTime.TryGetValue(key, out duration)) return duration; - return defaultMix; - } + /// + /// The mix duration to use when changing from the specified animation to the other, + /// or the DefaultMix if no mix duration has been set. + /// + public float GetMix(Animation from, Animation to) + { + if (from == null) throw new ArgumentNullException("from", "from cannot be null."); + if (to == null) throw new ArgumentNullException("to", "to cannot be null."); + AnimationPair key = new AnimationPair(from, to); + float duration; + if (animationToMixTime.TryGetValue(key, out duration)) return duration; + return defaultMix; + } - public struct AnimationPair { - public readonly Animation a1; - public readonly Animation a2; + public struct AnimationPair + { + public readonly Animation a1; + public readonly Animation a2; - public AnimationPair (Animation a1, Animation a2) { - this.a1 = a1; - this.a2 = a2; - } + public AnimationPair(Animation a1, Animation a2) + { + this.a1 = a1; + this.a2 = a2; + } - public override string ToString () { - return a1.name + "->" + a2.name; - } - } + public override string ToString() + { + return a1.name + "->" + a2.name; + } + } - // Avoids boxing in the dictionary. - public class AnimationPairComparer : IEqualityComparer { - public static readonly AnimationPairComparer Instance = new AnimationPairComparer(); + // Avoids boxing in the dictionary. + public class AnimationPairComparer : IEqualityComparer + { + public static readonly AnimationPairComparer Instance = new AnimationPairComparer(); - bool IEqualityComparer.Equals (AnimationPair x, AnimationPair y) { - return ReferenceEquals(x.a1, y.a1) && ReferenceEquals(x.a2, y.a2); - } + bool IEqualityComparer.Equals(AnimationPair x, AnimationPair y) + { + return ReferenceEquals(x.a1, y.a1) && ReferenceEquals(x.a2, y.a2); + } - int IEqualityComparer.GetHashCode (AnimationPair obj) { - // from Tuple.CombineHashCodes // return (((h1 << 5) + h1) ^ h2); - int h1 = obj.a1.GetHashCode(); - return (((h1 << 5) + h1) ^ obj.a2.GetHashCode()); - } - } - } + int IEqualityComparer.GetHashCode(AnimationPair obj) + { + // from Tuple.CombineHashCodes // return (((h1 << 5) + h1) ^ h2); + int h1 = obj.a1.GetHashCode(); + return (((h1 << 5) + h1) ^ obj.a2.GetHashCode()); + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Atlas.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Atlas.cs index eb9446f..5dbe385 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Atlas.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Atlas.cs @@ -35,31 +35,34 @@ using System; using System.Collections.Generic; using System.IO; -using System.Reflection; #if WINDOWS_STOREAPP using System.Threading.Tasks; using Windows.Storage; #endif -namespace Spine3_6_53 { - public class Atlas : IEnumerable { - readonly List pages = new List(); - List regions = new List(); - TextureLoader textureLoader; - - #region IEnumerable implementation - public IEnumerator GetEnumerator () { - return regions.GetEnumerator(); - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () { - return regions.GetEnumerator(); - } - #endregion - - #if !(IS_UNITY) - #if WINDOWS_STOREAPP +namespace Spine3_6_53 +{ + public class Atlas : IEnumerable + { + readonly List pages = new List(); + List regions = new List(); + TextureLoader textureLoader; + + #region IEnumerable implementation + public IEnumerator GetEnumerator() + { + return regions.GetEnumerator(); + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return regions.GetEnumerator(); + } + #endregion + +#if !(IS_UNITY) +#if WINDOWS_STOREAPP private async Task ReadFile(string path, TextureLoader textureLoader) { var folder = Windows.ApplicationModel.Package.Current.InstalledLocation; var file = await folder.GetFileAsync(path).AsTask().ConfigureAwait(false); @@ -75,233 +78,264 @@ private async Task ReadFile(string path, TextureLoader textureLoader) { public Atlas(string path, TextureLoader textureLoader) { this.ReadFile(path, textureLoader).Wait(); } - #else +#else - public Atlas (string path, TextureLoader textureLoader) { + public Atlas(string path, TextureLoader textureLoader) + { - #if WINDOWS_PHONE +#if WINDOWS_PHONE Stream stream = Microsoft.Xna.Framework.TitleContainer.OpenStream(path); using (StreamReader reader = new StreamReader(stream)) { - #else - using (StreamReader reader = new StreamReader(path)) { - #endif // WINDOWS_PHONE - - try { - Load(reader, Path.GetDirectoryName(path), textureLoader); - } catch (Exception ex) { - throw new Exception("Error reading atlas file: " + path, ex); - } - - } - } - #endif // WINDOWS_STOREAPP - - #endif - - public Atlas (TextReader reader, string dir, TextureLoader textureLoader) { - Load(reader, dir, textureLoader); - } +#else + using (StreamReader reader = new StreamReader(path)) + { +#endif // WINDOWS_PHONE + + try + { + Load(reader, Path.GetDirectoryName(path), textureLoader); + } + catch (Exception ex) + { + throw new Exception("Error reading atlas file: " + path, ex); + } + + } + } +#endif // WINDOWS_STOREAPP - public Atlas (List pages, List regions) { - this.pages = pages; - this.regions = regions; - this.textureLoader = null; - } - - private void Load (TextReader reader, string imagesDir, TextureLoader textureLoader) { - if (textureLoader == null) throw new ArgumentNullException("textureLoader", "textureLoader cannot be null."); - this.textureLoader = textureLoader; - - string[] tuple = new string[4]; - AtlasPage page = null; - while (true) { - string line = reader.ReadLine(); - if (line == null) break; - if (line.Trim().Length == 0) - page = null; - else if (page == null) { - page = new AtlasPage(); - page.name = line; - - if (ReadTuple(reader, tuple) == 2) { // size is only optional for an atlas packed with an old TexturePacker. - page.width = int.Parse(tuple[0]); - page.height = int.Parse(tuple[1]); - ReadTuple(reader, tuple); - } - page.format = (Format)Enum.Parse(typeof(Format), tuple[0], false); - - ReadTuple(reader, tuple); - page.minFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[0], false); - page.magFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[1], false); - - string direction = ReadValue(reader); - page.uWrap = TextureWrap.ClampToEdge; - page.vWrap = TextureWrap.ClampToEdge; - if (direction == "x") - page.uWrap = TextureWrap.Repeat; - else if (direction == "y") - page.vWrap = TextureWrap.Repeat; - else if (direction == "xy") - page.uWrap = page.vWrap = TextureWrap.Repeat; - - textureLoader.Load(page, Path.Combine(imagesDir, line)); - - pages.Add(page); - - } else { - AtlasRegion region = new AtlasRegion(); - region.name = line; - region.page = page; - - region.rotate = Boolean.Parse(ReadValue(reader)); - - ReadTuple(reader, tuple); - int x = int.Parse(tuple[0]); - int y = int.Parse(tuple[1]); - - ReadTuple(reader, tuple); - int width = int.Parse(tuple[0]); - int height = int.Parse(tuple[1]); - - region.u = x / (float)page.width; - region.v = y / (float)page.height; - if (region.rotate) { - region.u2 = (x + height) / (float)page.width; - region.v2 = (y + width) / (float)page.height; - } else { - region.u2 = (x + width) / (float)page.width; - region.v2 = (y + height) / (float)page.height; - } - region.x = x; - region.y = y; - region.width = Math.Abs(width); - region.height = Math.Abs(height); - - if (ReadTuple(reader, tuple) == 4) { // split is optional - region.splits = new [] {int.Parse(tuple[0]), int.Parse(tuple[1]), - int.Parse(tuple[2]), int.Parse(tuple[3])}; - - if (ReadTuple(reader, tuple) == 4) { // pad is optional, but only present with splits - region.pads = new [] {int.Parse(tuple[0]), int.Parse(tuple[1]), - int.Parse(tuple[2]), int.Parse(tuple[3])}; - - ReadTuple(reader, tuple); - } - } - - region.originalWidth = int.Parse(tuple[0]); - region.originalHeight = int.Parse(tuple[1]); - - ReadTuple(reader, tuple); - region.offsetX = int.Parse(tuple[0]); - region.offsetY = int.Parse(tuple[1]); - - region.index = int.Parse(ReadValue(reader)); - - regions.Add(region); - } - } - } - - static string ReadValue (TextReader reader) { - string line = reader.ReadLine(); - int colon = line.IndexOf(':'); - if (colon == -1) throw new Exception("Invalid line: " + line); - return line.Substring(colon + 1).Trim(); - } - - /// Returns the number of tuple values read (1, 2 or 4). - static int ReadTuple (TextReader reader, string[] tuple) { - string line = reader.ReadLine(); - int colon = line.IndexOf(':'); - if (colon == -1) throw new Exception("Invalid line: " + line); - int i = 0, lastMatch = colon + 1; - for (; i < 3; i++) { - int comma = line.IndexOf(',', lastMatch); - if (comma == -1) break; - tuple[i] = line.Substring(lastMatch, comma - lastMatch).Trim(); - lastMatch = comma + 1; - } - tuple[i] = line.Substring(lastMatch).Trim(); - return i + 1; - } - - public void FlipV () { - for (int i = 0, n = regions.Count; i < n; i++) { - AtlasRegion region = regions[i]; - region.v = 1 - region.v; - region.v2 = 1 - region.v2; - } - } - - /// 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. - /// The region, or null. - public AtlasRegion FindRegion (string name) { - for (int i = 0, n = regions.Count; i < n; i++) - if (regions[i].name == name) return regions[i]; - return null; - } +#endif - public void Dispose () { - if (textureLoader == null) return; - for (int i = 0, n = pages.Count; i < n; i++) - textureLoader.Unload(pages[i].rendererObject); - } - } - - public enum Format { - Alpha, - Intensity, - LuminanceAlpha, - RGB565, - RGBA4444, - RGB888, - RGBA8888 - } - - public enum TextureFilter { - Nearest, - Linear, - MipMap, - MipMapNearestNearest, - MipMapLinearNearest, - MipMapNearestLinear, - MipMapLinearLinear - } - - public enum TextureWrap { - MirroredRepeat, - ClampToEdge, - Repeat - } - - public class AtlasPage { - public string name; - public Format format; - public TextureFilter minFilter; - public TextureFilter magFilter; - public TextureWrap uWrap; - public TextureWrap vWrap; - public Object rendererObject; - public int width, height; - } - - public class AtlasRegion { - public AtlasPage page; - public string name; - public int x, y, width, height; - public float u, v, u2, v2; - public float offsetX, offsetY; - public int originalWidth, originalHeight; - public int index; - public bool rotate; - public int[] splits; - public int[] pads; - } - - public interface TextureLoader { - void Load (AtlasPage page, string path); - void Unload (Object texture); - } + public Atlas(TextReader reader, string dir, TextureLoader textureLoader) + { + Load(reader, dir, textureLoader); + } + + public Atlas(List pages, List regions) + { + this.pages = pages; + this.regions = regions; + this.textureLoader = null; + } + + private void Load(TextReader reader, string imagesDir, TextureLoader textureLoader) + { + if (textureLoader == null) throw new ArgumentNullException("textureLoader", "textureLoader cannot be null."); + this.textureLoader = textureLoader; + + string[] tuple = new string[4]; + AtlasPage page = null; + while (true) + { + string line = reader.ReadLine(); + if (line == null) break; + if (line.Trim().Length == 0) + page = null; + else if (page == null) + { + page = new AtlasPage(); + page.name = line; + + if (ReadTuple(reader, tuple) == 2) + { // size is only optional for an atlas packed with an old TexturePacker. + page.width = int.Parse(tuple[0]); + page.height = int.Parse(tuple[1]); + ReadTuple(reader, tuple); + } + page.format = (Format)Enum.Parse(typeof(Format), tuple[0], false); + + ReadTuple(reader, tuple); + page.minFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[0], false); + page.magFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[1], false); + + string direction = ReadValue(reader); + page.uWrap = TextureWrap.ClampToEdge; + page.vWrap = TextureWrap.ClampToEdge; + if (direction == "x") + page.uWrap = TextureWrap.Repeat; + else if (direction == "y") + page.vWrap = TextureWrap.Repeat; + else if (direction == "xy") + page.uWrap = page.vWrap = TextureWrap.Repeat; + + textureLoader.Load(page, Path.Combine(imagesDir, line)); + + pages.Add(page); + + } + else + { + AtlasRegion region = new AtlasRegion(); + region.name = line; + region.page = page; + + region.rotate = Boolean.Parse(ReadValue(reader)); + + ReadTuple(reader, tuple); + int x = int.Parse(tuple[0]); + int y = int.Parse(tuple[1]); + + ReadTuple(reader, tuple); + int width = int.Parse(tuple[0]); + int height = int.Parse(tuple[1]); + + region.u = x / (float)page.width; + region.v = y / (float)page.height; + if (region.rotate) + { + region.u2 = (x + height) / (float)page.width; + region.v2 = (y + width) / (float)page.height; + } + else + { + region.u2 = (x + width) / (float)page.width; + region.v2 = (y + height) / (float)page.height; + } + region.x = x; + region.y = y; + region.width = Math.Abs(width); + region.height = Math.Abs(height); + + if (ReadTuple(reader, tuple) == 4) + { // split is optional + region.splits = new[] {int.Parse(tuple[0]), int.Parse(tuple[1]), + int.Parse(tuple[2]), int.Parse(tuple[3])}; + + if (ReadTuple(reader, tuple) == 4) + { // pad is optional, but only present with splits + region.pads = new[] {int.Parse(tuple[0]), int.Parse(tuple[1]), + int.Parse(tuple[2]), int.Parse(tuple[3])}; + + ReadTuple(reader, tuple); + } + } + + region.originalWidth = int.Parse(tuple[0]); + region.originalHeight = int.Parse(tuple[1]); + + ReadTuple(reader, tuple); + region.offsetX = int.Parse(tuple[0]); + region.offsetY = int.Parse(tuple[1]); + + region.index = int.Parse(ReadValue(reader)); + + regions.Add(region); + } + } + } + + static string ReadValue(TextReader reader) + { + string line = reader.ReadLine(); + int colon = line.IndexOf(':'); + if (colon == -1) throw new Exception("Invalid line: " + line); + return line.Substring(colon + 1).Trim(); + } + + /// Returns the number of tuple values read (1, 2 or 4). + static int ReadTuple(TextReader reader, string[] tuple) + { + string line = reader.ReadLine(); + int colon = line.IndexOf(':'); + if (colon == -1) throw new Exception("Invalid line: " + line); + int i = 0, lastMatch = colon + 1; + for (; i < 3; i++) + { + int comma = line.IndexOf(',', lastMatch); + if (comma == -1) break; + tuple[i] = line.Substring(lastMatch, comma - lastMatch).Trim(); + lastMatch = comma + 1; + } + tuple[i] = line.Substring(lastMatch).Trim(); + return i + 1; + } + + public void FlipV() + { + for (int i = 0, n = regions.Count; i < n; i++) + { + AtlasRegion region = regions[i]; + region.v = 1 - region.v; + region.v2 = 1 - region.v2; + } + } + + /// 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. + /// The region, or null. + public AtlasRegion FindRegion(string name) + { + for (int i = 0, n = regions.Count; i < n; i++) + if (regions[i].name == name) return regions[i]; + return null; + } + + public void Dispose() + { + if (textureLoader == null) return; + for (int i = 0, n = pages.Count; i < n; i++) + textureLoader.Unload(pages[i].rendererObject); + } + } + + public enum Format + { + Alpha, + Intensity, + LuminanceAlpha, + RGB565, + RGBA4444, + RGB888, + RGBA8888 + } + + public enum TextureFilter + { + Nearest, + Linear, + MipMap, + MipMapNearestNearest, + MipMapLinearNearest, + MipMapNearestLinear, + MipMapLinearLinear + } + + public enum TextureWrap + { + MirroredRepeat, + ClampToEdge, + Repeat + } + + public class AtlasPage + { + public string name; + public Format format; + public TextureFilter minFilter; + public TextureFilter magFilter; + public TextureWrap uWrap; + public TextureWrap vWrap; + public Object rendererObject; + public int width, height; + } + + public class AtlasRegion + { + public AtlasPage page; + public string name; + public int x, y, width, height; + public float u, v, u2, v2; + public float offsetX, offsetY; + public int originalWidth, originalHeight; + public int index; + public bool rotate; + public int[] splits; + public int[] pads; + } + + public interface TextureLoader + { + void Load(AtlasPage page, string path); + void Unload(Object texture); + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Attachments/AtlasAttachmentLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Attachments/AtlasAttachmentLoader.cs index d7a3dd0..003578f 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Attachments/AtlasAttachmentLoader.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Attachments/AtlasAttachmentLoader.cs @@ -30,80 +30,91 @@ using System; -namespace Spine3_6_53 { +namespace Spine3_6_53 +{ - /// - /// An AttachmentLoader that configures attachments using texture regions from an Atlas. - /// See Loading Skeleton Data in the Spine Runtimes Guide. - /// - public class AtlasAttachmentLoader : AttachmentLoader { - private Atlas[] atlasArray; + /// + /// An AttachmentLoader that configures attachments using texture regions from an Atlas. + /// See Loading Skeleton Data in the Spine Runtimes Guide. + /// + public class AtlasAttachmentLoader : AttachmentLoader + { + private Atlas[] atlasArray; - public AtlasAttachmentLoader (params Atlas[] atlasArray) { - if (atlasArray == null) throw new ArgumentNullException("atlas array cannot be null."); - this.atlasArray = atlasArray; - } + public AtlasAttachmentLoader(params Atlas[] atlasArray) + { + if (atlasArray == null) throw new ArgumentNullException("atlas array cannot be null."); + this.atlasArray = atlasArray; + } - public RegionAttachment NewRegionAttachment (Skin skin, string name, string path) { - AtlasRegion region = FindRegion(path); - if (region == null) throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name)); - RegionAttachment attachment = new RegionAttachment(name); - attachment.RendererObject = region; - attachment.SetUVs(region.u, region.v, region.u2, region.v2, region.rotate); - attachment.regionOffsetX = region.offsetX; - attachment.regionOffsetY = region.offsetY; - attachment.regionWidth = region.width; - attachment.regionHeight = region.height; - attachment.regionOriginalWidth = region.originalWidth; - attachment.regionOriginalHeight = region.originalHeight; - return attachment; - } + public RegionAttachment NewRegionAttachment(Skin skin, string name, string path) + { + AtlasRegion region = FindRegion(path); + if (region == null) throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name)); + RegionAttachment attachment = new RegionAttachment(name); + attachment.RendererObject = region; + attachment.SetUVs(region.u, region.v, region.u2, region.v2, region.rotate); + attachment.regionOffsetX = region.offsetX; + attachment.regionOffsetY = region.offsetY; + attachment.regionWidth = region.width; + attachment.regionHeight = region.height; + attachment.regionOriginalWidth = region.originalWidth; + attachment.regionOriginalHeight = region.originalHeight; + return attachment; + } - public MeshAttachment NewMeshAttachment (Skin skin, string name, string path) { - AtlasRegion region = FindRegion(path); - if (region == null) throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name)); - MeshAttachment attachment = new MeshAttachment(name); - attachment.RendererObject = region; - attachment.RegionU = region.u; - attachment.RegionV = region.v; - attachment.RegionU2 = region.u2; - attachment.RegionV2 = region.v2; - attachment.RegionRotate = region.rotate; - attachment.regionOffsetX = region.offsetX; - attachment.regionOffsetY = region.offsetY; - attachment.regionWidth = region.width; - attachment.regionHeight = region.height; - attachment.regionOriginalWidth = region.originalWidth; - attachment.regionOriginalHeight = region.originalHeight; - return attachment; - } + public MeshAttachment NewMeshAttachment(Skin skin, string name, string path) + { + AtlasRegion region = FindRegion(path); + if (region == null) throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name)); + MeshAttachment attachment = new MeshAttachment(name); + attachment.RendererObject = region; + attachment.RegionU = region.u; + attachment.RegionV = region.v; + attachment.RegionU2 = region.u2; + attachment.RegionV2 = region.v2; + attachment.RegionRotate = region.rotate; + attachment.regionOffsetX = region.offsetX; + attachment.regionOffsetY = region.offsetY; + attachment.regionWidth = region.width; + attachment.regionHeight = region.height; + attachment.regionOriginalWidth = region.originalWidth; + attachment.regionOriginalHeight = region.originalHeight; + return attachment; + } - public BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, string name) { - return new BoundingBoxAttachment(name); - } + public BoundingBoxAttachment NewBoundingBoxAttachment(Skin skin, string name) + { + return new BoundingBoxAttachment(name); + } - public PathAttachment NewPathAttachment (Skin skin, string name) { - return new PathAttachment(name); - } + public PathAttachment NewPathAttachment(Skin skin, string name) + { + return new PathAttachment(name); + } - public PointAttachment NewPointAttachment (Skin skin, string name) { - return new PointAttachment(name); - } + public PointAttachment NewPointAttachment(Skin skin, string name) + { + return new PointAttachment(name); + } - public ClippingAttachment NewClippingAttachment(Skin skin, string name) { - return new ClippingAttachment(name); - } + public ClippingAttachment NewClippingAttachment(Skin skin, string name) + { + return new ClippingAttachment(name); + } - public AtlasRegion FindRegion (string name) { - AtlasRegion region; + public AtlasRegion FindRegion(string name) + { + AtlasRegion region; - for (int i = 0; i < atlasArray.Length; i++) { - region = atlasArray[i].FindRegion(name); - if (region != null) - return region; - } + for (int i = 0; i < atlasArray.Length; i++) + { + region = atlasArray[i].FindRegion(name); + if (region != null) + return region; + } - return null; - } - } + return null; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Attachments/Attachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Attachments/Attachment.cs index 23a5c3e..42d3189 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Attachments/Attachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Attachments/Attachment.cs @@ -30,21 +30,26 @@ using System; -namespace Spine3_6_53 { - abstract public class Attachment { - public string Name { get; private set; } +namespace Spine3_6_53 +{ + abstract public class Attachment + { + public string Name { get; private set; } - protected Attachment (string name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null"); - Name = name; - } + protected Attachment(string name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null"); + Name = name; + } - override public string ToString () { - return Name; - } - } + override public string ToString() + { + return Name; + } + } - public interface IHasRendererObject { - object RendererObject { get; } - } + public interface IHasRendererObject + { + object RendererObject { get; } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Attachments/AttachmentLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Attachments/AttachmentLoader.cs index 2449549..b72a483 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Attachments/AttachmentLoader.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Attachments/AttachmentLoader.cs @@ -28,22 +28,24 @@ * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine3_6_53 { - public interface AttachmentLoader { - /// May be null to not load any attachment. - RegionAttachment NewRegionAttachment (Skin skin, string name, string path); +namespace Spine3_6_53 +{ + public interface AttachmentLoader + { + /// May be null to not load any attachment. + RegionAttachment NewRegionAttachment(Skin skin, string name, string path); - /// May be null to not load any attachment. - MeshAttachment NewMeshAttachment (Skin skin, string name, string path); + /// May be null to not load any attachment. + MeshAttachment NewMeshAttachment(Skin skin, string name, string path); - /// May be null to not load any attachment. - BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, string name); + /// May be null to not load any attachment. + BoundingBoxAttachment NewBoundingBoxAttachment(Skin skin, string name); - /// May be null to not load any attachment - PathAttachment NewPathAttachment (Skin skin, string name); + /// May be null to not load any attachment + PathAttachment NewPathAttachment(Skin skin, string name); - PointAttachment NewPointAttachment (Skin skin, string name); + PointAttachment NewPointAttachment(Skin skin, string name); - ClippingAttachment NewClippingAttachment (Skin skin, string name); - } + ClippingAttachment NewClippingAttachment(Skin skin, string name); + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Attachments/AttachmentType.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Attachments/AttachmentType.cs index 2129410..330f4e5 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Attachments/AttachmentType.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Attachments/AttachmentType.cs @@ -28,8 +28,10 @@ * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine3_6_53 { - public enum AttachmentType { - Region, Boundingbox, Mesh, Linkedmesh, Path, Point, Clipping - } +namespace Spine3_6_53 +{ + public enum AttachmentType + { + Region, Boundingbox, Mesh, Linkedmesh, Path, Point, Clipping + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Attachments/BoundingBoxAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Attachments/BoundingBoxAttachment.cs index b97c56f..5c4fd91 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Attachments/BoundingBoxAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Attachments/BoundingBoxAttachment.cs @@ -28,13 +28,14 @@ * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; - -namespace Spine3_6_53 { - /// Attachment that has a polygon for bounds checking. - public class BoundingBoxAttachment : VertexAttachment { - public BoundingBoxAttachment (string name) - : base(name) { - } - } +namespace Spine3_6_53 +{ + /// Attachment that has a polygon for bounds checking. + public class BoundingBoxAttachment : VertexAttachment + { + public BoundingBoxAttachment(string name) + : base(name) + { + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Attachments/ClippingAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Attachments/ClippingAttachment.cs index 9af6c41..cd642d2 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Attachments/ClippingAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Attachments/ClippingAttachment.cs @@ -28,15 +28,16 @@ * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; - -namespace Spine3_6_53 { - public class ClippingAttachment : VertexAttachment { +namespace Spine3_6_53 +{ + public class ClippingAttachment : VertexAttachment + { internal SlotData endSlot; public SlotData EndSlot { get { return endSlot; } set { endSlot = value; } } - public ClippingAttachment(string name) : base(name) { + public ClippingAttachment(string name) : base(name) + { } } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Attachments/MeshAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Attachments/MeshAttachment.cs index 2ae4b53..4ddb783 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Attachments/MeshAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Attachments/MeshAttachment.cs @@ -28,93 +28,104 @@ * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; +namespace Spine3_6_53 +{ + /// Attachment that displays a texture region using a mesh. + public class MeshAttachment : VertexAttachment, IHasRendererObject + { + internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; + private MeshAttachment parentMesh; + internal float[] uvs, regionUVs; + internal int[] triangles; + internal float r = 1, g = 1, b = 1, a = 1; + internal int hulllength; + internal bool inheritDeform; -namespace Spine3_6_53 { - /// Attachment that displays a texture region using a mesh. - public class MeshAttachment : VertexAttachment, IHasRendererObject { - internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; - private MeshAttachment parentMesh; - internal float[] uvs, regionUVs; - internal int[] triangles; - internal float r = 1, g = 1, b = 1, a = 1; - internal int hulllength; - internal bool inheritDeform; + public int HullLength { get { return hulllength; } set { hulllength = value; } } + public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } } + /// The UV pair for each vertex, normalized within the entire texture. + public float[] UVs { get { return uvs; } set { uvs = value; } } + public int[] Triangles { get { return triangles; } set { triangles = value; } } - public int HullLength { get { return hulllength; } set { hulllength = value; } } - public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } } - /// The UV pair for each vertex, normalized within the entire texture. - public float[] UVs { get { return uvs; } set { uvs = value; } } - public int[] Triangles { get { return triangles; } set { triangles = value; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } + public string Path { get; set; } + public object RendererObject { get; set; } + public float RegionU { get; set; } + public float RegionV { get; set; } + public float RegionU2 { get; set; } + public float RegionV2 { get; set; } + public bool RegionRotate { get; set; } + public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } + public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. + public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } + public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. + public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } + public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. - public string Path { get; set; } - public object RendererObject { get; set; } - public float RegionU { get; set; } - public float RegionV { get; set; } - public float RegionU2 { get; set; } - public float RegionV2 { get; set; } - public bool RegionRotate { get; set; } - public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } - public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. - public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } - public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. - public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } - public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. + public bool InheritDeform { get { return inheritDeform; } set { inheritDeform = value; } } - public bool InheritDeform { get { return inheritDeform; } set { inheritDeform = value; } } + public MeshAttachment ParentMesh + { + get { return parentMesh; } + set + { + parentMesh = value; + if (value != null) + { + bones = value.bones; + vertices = value.vertices; + worldVerticesLength = value.worldVerticesLength; + regionUVs = value.regionUVs; + triangles = value.triangles; + HullLength = value.HullLength; + Edges = value.Edges; + Width = value.Width; + Height = value.Height; + } + } + } - public MeshAttachment ParentMesh { - get { return parentMesh; } - set { - parentMesh = value; - if (value != null) { - bones = value.bones; - vertices = value.vertices; - worldVerticesLength = value.worldVerticesLength; - regionUVs = value.regionUVs; - triangles = value.triangles; - HullLength = value.HullLength; - Edges = value.Edges; - Width = value.Width; - Height = value.Height; - } - } - } + // Nonessential. + public int[] Edges { get; set; } + public float Width { get; set; } + public float Height { get; set; } - // Nonessential. - public int[] Edges { get; set; } - public float Width { get; set; } - public float Height { get; set; } + public MeshAttachment(string name) + : base(name) + { + } - public MeshAttachment (string name) - : base(name) { - } + public void UpdateUVs() + { + float u = RegionU, v = RegionV, width = RegionU2 - RegionU, height = RegionV2 - RegionV; + float[] regionUVs = this.regionUVs; + if (this.uvs == null || this.uvs.Length != regionUVs.Length) this.uvs = new float[regionUVs.Length]; + float[] uvs = this.uvs; + if (RegionRotate) + { + for (int i = 0, n = uvs.Length; i < n; i += 2) + { + uvs[i] = u + regionUVs[i + 1] * width; + uvs[i + 1] = v + height - regionUVs[i] * height; + } + } + else + { + for (int i = 0, n = uvs.Length; i < n; i += 2) + { + uvs[i] = u + regionUVs[i] * width; + uvs[i + 1] = v + regionUVs[i + 1] * height; + } + } + } - public void UpdateUVs () { - float u = RegionU, v = RegionV, width = RegionU2 - RegionU, height = RegionV2 - RegionV; - float[] regionUVs = this.regionUVs; - if (this.uvs == null || this.uvs.Length != regionUVs.Length) this.uvs = new float[regionUVs.Length]; - float[] uvs = this.uvs; - if (RegionRotate) { - for (int i = 0, n = uvs.Length; i < n; i += 2) { - uvs[i] = u + regionUVs[i + 1] * width; - uvs[i + 1] = v + height - regionUVs[i] * height; - } - } else { - for (int i = 0, n = uvs.Length; i < n; i += 2) { - uvs[i] = u + regionUVs[i] * width; - uvs[i + 1] = v + regionUVs[i + 1] * height; - } - } - } - - override public bool ApplyDeform (VertexAttachment sourceAttachment) { - return this == sourceAttachment || (inheritDeform && parentMesh == sourceAttachment); - } - } + override public bool ApplyDeform(VertexAttachment sourceAttachment) + { + return this == sourceAttachment || (inheritDeform && parentMesh == sourceAttachment); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Attachments/PathAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Attachments/PathAttachment.cs index e36fd90..c6453d9 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Attachments/PathAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Attachments/PathAttachment.cs @@ -29,20 +29,22 @@ *****************************************************************************/ using System; -using System.Collections.Generic; -namespace Spine3_6_53 { - public class PathAttachment : VertexAttachment { - internal float[] lengths; - internal bool closed, constantSpeed; +namespace Spine3_6_53 +{ + public class PathAttachment : VertexAttachment + { + internal float[] lengths; + internal bool closed, constantSpeed; - /// The length in the setup pose from the start of the path to the end of each curve. - public float[] Lengths { get { return lengths; } set { lengths = value; } } - public bool Closed { get { return closed; } set { closed = value; } } - public bool ConstantSpeed { get { return constantSpeed; } set { constantSpeed = value; } } + /// The length in the setup pose from the start of the path to the end of each curve. + public float[] Lengths { get { return lengths; } set { lengths = value; } } + public bool Closed { get { return closed; } set { closed = value; } } + public bool ConstantSpeed { get { return constantSpeed; } set { constantSpeed = value; } } - public PathAttachment (String name) - : base(name) { - } - } + public PathAttachment(String name) + : base(name) + { + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Attachments/PointAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Attachments/PointAttachment.cs index 1eb054b..a831d21 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Attachments/PointAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Attachments/PointAttachment.cs @@ -28,34 +28,39 @@ * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine3_6_53 { - /// - /// 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 Point Attachments in the Spine User Guide. - ///

- public class PointAttachment : Attachment { - internal float x, y, rotation; - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public float Rotation { get { return rotation; } set { rotation = value; } } +namespace Spine3_6_53 +{ + /// + /// 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 Point Attachments in the Spine User Guide. + ///

+ public class PointAttachment : Attachment + { + internal float x, y, rotation; + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public float Rotation { get { return rotation; } set { rotation = value; } } - public PointAttachment (string name) - : base(name) { - } + public PointAttachment(string name) + : base(name) + { + } - public void ComputeWorldPosition (Bone bone, out float ox, out float oy) { - bone.LocalToWorld(this.x, this.y, out ox, out oy); - } + public void ComputeWorldPosition(Bone bone, out float ox, out float oy) + { + bone.LocalToWorld(this.x, this.y, out ox, out oy); + } - public float ComputeWorldRotation (Bone bone) { - float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation); - float ix = cos * bone.a + sin * bone.b; - float iy = cos * bone.c + sin * bone.d; - return MathUtils.Atan2(iy, ix) * MathUtils.RadDeg; - } - } + public float ComputeWorldRotation(Bone bone) + { + float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation); + float ix = cos * bone.a + sin * bone.b; + float iy = cos * bone.c + sin * bone.d; + return MathUtils.Atan2(iy, ix) * MathUtils.RadDeg; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Attachments/RegionAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Attachments/RegionAttachment.cs index ef461fc..64f111c 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Attachments/RegionAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Attachments/RegionAttachment.cs @@ -28,156 +28,164 @@ * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; +namespace Spine3_6_53 +{ + /// Attachment that displays a texture region. + public class RegionAttachment : Attachment, IHasRendererObject + { + public const int BLX = 0; + public const int BLY = 1; + public const int ULX = 2; + public const int ULY = 3; + public const int URX = 4; + public const int URY = 5; + public const int BRX = 6; + public const int BRY = 7; -namespace Spine3_6_53 { - /// Attachment that displays a texture region. - public class RegionAttachment : Attachment, IHasRendererObject { - public const int BLX = 0; - public const int BLY = 1; - public const int ULX = 2; - public const int ULY = 3; - public const int URX = 4; - public const int URY = 5; - public const int BRX = 6; - public const int BRY = 7; + internal float x, y, rotation, scaleX = 1, scaleY = 1, width, height; + internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; + internal float[] offset = new float[8], uvs = new float[8]; + internal float r = 1, g = 1, b = 1, a = 1; - internal float x, y, rotation, scaleX = 1, scaleY = 1, width, height; - internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; - internal float[] offset = new float[8], uvs = new float[8]; - internal float r = 1, g = 1, b = 1, a = 1; + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public float Rotation { get { return rotation; } set { rotation = value; } } + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + public float Width { get { return width; } set { width = value; } } + public float Height { get { return height; } set { height = value; } } - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public float Rotation { get { return rotation; } set { rotation = value; } } - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - public float ScaleY { get { return scaleY; } set { scaleY = value; } } - public float Width { get { return width; } set { width = value; } } - public float Height { get { return height; } set { height = value; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } + public string Path { get; set; } + public object RendererObject { get; set; } + public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } + public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. + public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } + public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. + public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } + public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. - public string Path { get; set; } - public object RendererObject { get; set; } - public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } - public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. - public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } - public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. - public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } - public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. + public float[] Offset { get { return offset; } } + public float[] UVs { get { return uvs; } } - public float[] Offset { get { return offset; } } - public float[] UVs { get { return uvs; } } + public RegionAttachment(string name) + : base(name) + { + } - public RegionAttachment (string name) - : base(name) { - } + public void UpdateOffset() + { + float width = this.width; + float height = this.height; + float localX2 = width * 0.5f; + float localY2 = height * 0.5f; + float localX = -localX2; + float localY = -localY2; + if (regionOriginalWidth != 0) + { // if (region != null) + localX += regionOffsetX / regionOriginalWidth * width; + localY += regionOffsetY / regionOriginalHeight * height; + localX2 -= (regionOriginalWidth - regionOffsetX - regionWidth) / regionOriginalWidth * width; + localY2 -= (regionOriginalHeight - regionOffsetY - regionHeight) / regionOriginalHeight * height; + } + float scaleX = this.scaleX; + float scaleY = this.scaleY; + localX *= scaleX; + localY *= scaleY; + localX2 *= scaleX; + localY2 *= scaleY; + float rotation = this.rotation; + float cos = MathUtils.CosDeg(rotation); + float sin = MathUtils.SinDeg(rotation); + float x = this.x; + float y = this.y; + 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; + float[] offset = this.offset; + offset[BLX] = localXCos - localYSin; + offset[BLY] = localYCos + localXSin; + offset[ULX] = localXCos - localY2Sin; + offset[ULY] = localY2Cos + localXSin; + offset[URX] = localX2Cos - localY2Sin; + offset[URY] = localY2Cos + localX2Sin; + offset[BRX] = localX2Cos - localYSin; + offset[BRY] = localYCos + localX2Sin; + } - public void UpdateOffset () { - float width = this.width; - float height = this.height; - float localX2 = width * 0.5f; - float localY2 = height * 0.5f; - float localX = -localX2; - float localY = -localY2; - if (regionOriginalWidth != 0) { // if (region != null) - localX += regionOffsetX / regionOriginalWidth * width; - localY += regionOffsetY / regionOriginalHeight * height; - localX2 -= (regionOriginalWidth - regionOffsetX - regionWidth) / regionOriginalWidth * width; - localY2 -= (regionOriginalHeight - regionOffsetY - regionHeight) / regionOriginalHeight * height; - } - float scaleX = this.scaleX; - float scaleY = this.scaleY; - localX *= scaleX; - localY *= scaleY; - localX2 *= scaleX; - localY2 *= scaleY; - float rotation = this.rotation; - float cos = MathUtils.CosDeg(rotation); - float sin = MathUtils.SinDeg(rotation); - float x = this.x; - float y = this.y; - 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; - float[] offset = this.offset; - offset[BLX] = localXCos - localYSin; - offset[BLY] = localYCos + localXSin; - offset[ULX] = localXCos - localY2Sin; - offset[ULY] = localY2Cos + localXSin; - offset[URX] = localX2Cos - localY2Sin; - offset[URY] = localY2Cos + localX2Sin; - offset[BRX] = localX2Cos - localYSin; - offset[BRY] = localYCos + localX2Sin; - } + public void SetUVs(float u, float v, float u2, float v2, bool rotate) + { + float[] uvs = this.uvs; + // UV values differ from RegionAttachment.java + if (rotate) + { + uvs[URX] = u; + uvs[URY] = v2; + uvs[BRX] = u; + uvs[BRY] = v; + uvs[BLX] = u2; + uvs[BLY] = v; + uvs[ULX] = u2; + uvs[ULY] = v2; + } + else + { + uvs[ULX] = u; + uvs[ULY] = v2; + uvs[URX] = u; + uvs[URY] = v; + uvs[BRX] = u2; + uvs[BRY] = v; + uvs[BLX] = u2; + uvs[BLY] = v2; + } + } - public void SetUVs (float u, float v, float u2, float v2, bool rotate) { - float[] uvs = this.uvs; - // UV values differ from RegionAttachment.java - if (rotate) { - uvs[URX] = u; - uvs[URY] = v2; - uvs[BRX] = u; - uvs[BRY] = v; - uvs[BLX] = u2; - uvs[BLY] = v; - uvs[ULX] = u2; - uvs[ULY] = v2; - } else { - uvs[ULX] = u; - uvs[ULY] = v2; - uvs[URX] = u; - uvs[URY] = v; - uvs[BRX] = u2; - uvs[BRY] = v; - uvs[BLX] = u2; - uvs[BLY] = v2; - } - } + /// Transforms the attachment's four vertices to world coordinates. + /// The parent bone. + /// The output world vertices. Must have a length greater than or equal to offset + 8. + /// The worldVertices index to begin writing values. + /// The number of worldVertices entries between the value pairs written. + public void ComputeWorldVertices(Bone bone, float[] worldVertices, int offset, int stride = 2) + { + float[] vertexOffset = this.offset; + float bwx = bone.worldX, bwy = bone.worldY; + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + float offsetX, offsetY; - /// Transforms the attachment's four vertices to world coordinates. - /// The parent bone. - /// The output world vertices. Must have a length greater than or equal to offset + 8. - /// The worldVertices index to begin writing values. - /// The number of worldVertices entries between the value pairs written. - public void ComputeWorldVertices (Bone bone, float[] worldVertices, int offset, int stride = 2) { - float[] vertexOffset = this.offset; - float bwx = bone.worldX, bwy = bone.worldY; - float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - float offsetX, offsetY; + // Vertex order is different from RegionAttachment.java + offsetX = vertexOffset[BRX]; // 0 + offsetY = vertexOffset[BRY]; // 1 + worldVertices[offset] = offsetX * a + offsetY * b + bwx; // bl + worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; + offset += stride; - // Vertex order is different from RegionAttachment.java - offsetX = vertexOffset[BRX]; // 0 - offsetY = vertexOffset[BRY]; // 1 - worldVertices[offset] = offsetX * a + offsetY * b + bwx; // bl - worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; - offset += stride; + offsetX = vertexOffset[BLX]; // 2 + offsetY = vertexOffset[BLY]; // 3 + worldVertices[offset] = offsetX * a + offsetY * b + bwx; // ul + worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; + offset += stride; - offsetX = vertexOffset[BLX]; // 2 - offsetY = vertexOffset[BLY]; // 3 - worldVertices[offset] = offsetX * a + offsetY * b + bwx; // ul - worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; - offset += stride; + offsetX = vertexOffset[ULX]; // 4 + offsetY = vertexOffset[ULY]; // 5 + worldVertices[offset] = offsetX * a + offsetY * b + bwx; // ur + worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; + offset += stride; - offsetX = vertexOffset[ULX]; // 4 - offsetY = vertexOffset[ULY]; // 5 - worldVertices[offset] = offsetX * a + offsetY * b + bwx; // ur - worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; - offset += stride; - - offsetX = vertexOffset[URX]; // 6 - offsetY = vertexOffset[URY]; // 7 - worldVertices[offset] = offsetX * a + offsetY * b + bwx; // br - worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; - //offset += stride; - } - } + offsetX = vertexOffset[URX]; // 6 + offsetY = vertexOffset[URY]; // 7 + worldVertices[offset] = offsetX * a + offsetY * b + bwx; // br + worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; + //offset += stride; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Attachments/VertexAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Attachments/VertexAttachment.cs index 5914932..d0e2617 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Attachments/VertexAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Attachments/VertexAttachment.cs @@ -30,101 +30,118 @@ using System; -namespace Spine3_6_53 { - /// >An attachment with vertices that are transformed by one or more bones and can be deformed by a slot's vertices. - public class VertexAttachment : Attachment { - static int nextID = 0; - static readonly Object nextIdLock = new Object(); +namespace Spine3_6_53 +{ + /// >An attachment with vertices that are transformed by one or more bones and can be deformed by a slot's vertices. + public class VertexAttachment : Attachment + { + static int nextID = 0; + static readonly Object nextIdLock = new Object(); - internal readonly int id; - internal int[] bones; - internal float[] vertices; - internal int worldVerticesLength; + internal readonly int id; + internal int[] bones; + internal float[] vertices; + internal int worldVerticesLength; - /// Gets a unique ID for this attachment. - public int Id { get { return id; } } - public int[] Bones { get { return bones; } set { bones = value; } } - public float[] Vertices { get { return vertices; } set { vertices = value; } } - public int WorldVerticesLength { get { return worldVerticesLength; } set { worldVerticesLength = value; } } + /// Gets a unique ID for this attachment. + public int Id { get { return id; } } + public int[] Bones { get { return bones; } set { bones = value; } } + public float[] Vertices { get { return vertices; } set { vertices = value; } } + public int WorldVerticesLength { get { return worldVerticesLength; } set { worldVerticesLength = value; } } - public VertexAttachment (string name) - : base(name) { + public VertexAttachment(string name) + : base(name) + { - lock (VertexAttachment.nextIdLock) { - id = (VertexAttachment.nextID++ & 65535) << 11; - } - } + lock (VertexAttachment.nextIdLock) + { + id = (VertexAttachment.nextID++ & 65535) << 11; + } + } - public void ComputeWorldVertices (Slot slot, float[] worldVertices) { - ComputeWorldVertices(slot, 0, worldVerticesLength, worldVertices, 0); - } + public void ComputeWorldVertices(Slot slot, float[] worldVertices) + { + ComputeWorldVertices(slot, 0, worldVerticesLength, worldVertices, 0); + } - /// Transforms local vertices to world coordinates. - /// The index of the first value to transform. Each vertex has 2 values, x and y. - /// The number of world vertex values to output. Must be less than or equal to - start. - /// The output world vertices. Must have a length greater than or equal to + . - /// The index to begin writing values. - /// The number of entries between the value pairs written. - public void ComputeWorldVertices (Slot slot, int start, int count, float[] worldVertices, int offset, int stride = 2) { - count = offset + (count >> 1) * stride; - Skeleton skeleton = slot.bone.skeleton; - var deformArray = slot.attachmentVertices; - float[] vertices = this.vertices; - int[] bones = this.bones; - if (bones == null) { - if (deformArray.Count > 0) vertices = deformArray.Items; - Bone bone = slot.bone; - float x = bone.worldX, y = bone.worldY; - float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - for (int vv = start, w = offset; w < count; vv += 2, w += stride) { - float vx = vertices[vv], 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 (int i = 0; i < start; i += 2) { - int n = bones[v]; - v += n + 1; - skip += n; - } - var skeletonBones = skeleton.bones.Items; - if (deformArray.Count == 0) { - for (int w = offset, b = skip * 3; w < count; w += stride) { - float wx = 0, wy = 0; - int n = bones[v++]; - n += v; - for (; v < n; v++, b += 3) { - Bone bone = skeletonBones[bones[v]]; - float vx = vertices[b], vy = vertices[b + 1], 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 { - float[] deform = deformArray.Items; - for (int w = offset, b = skip * 3, f = skip << 1; w < count; w += stride) { - float wx = 0, wy = 0; - int n = bones[v++]; - n += v; - for (; v < n; v++, b += 3, f += 2) { - Bone bone = skeletonBones[bones[v]]; - float vx = vertices[b] + deform[f], vy = vertices[b + 1] + deform[f + 1], 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; - } - } - } + /// Transforms local vertices to world coordinates. + /// The index of the first value to transform. Each vertex has 2 values, x and y. + /// The number of world vertex values to output. Must be less than or equal to - start. + /// The output world vertices. Must have a length greater than or equal to + . + /// The index to begin writing values. + /// The number of entries between the value pairs written. + public void ComputeWorldVertices(Slot slot, int start, int count, float[] worldVertices, int offset, int stride = 2) + { + count = offset + (count >> 1) * stride; + Skeleton skeleton = slot.bone.skeleton; + var deformArray = slot.attachmentVertices; + float[] vertices = this.vertices; + int[] bones = this.bones; + if (bones == null) + { + if (deformArray.Count > 0) vertices = deformArray.Items; + Bone bone = slot.bone; + float x = bone.worldX, y = bone.worldY; + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + for (int vv = start, w = offset; w < count; vv += 2, w += stride) + { + float vx = vertices[vv], 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 (int i = 0; i < start; i += 2) + { + int n = bones[v]; + v += n + 1; + skip += n; + } + var skeletonBones = skeleton.bones.Items; + if (deformArray.Count == 0) + { + for (int w = offset, b = skip * 3; w < count; w += stride) + { + float wx = 0, wy = 0; + int n = bones[v++]; + n += v; + for (; v < n; v++, b += 3) + { + Bone bone = skeletonBones[bones[v]]; + float vx = vertices[b], vy = vertices[b + 1], 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 + { + float[] deform = deformArray.Items; + for (int w = offset, b = skip * 3, f = skip << 1; w < count; w += stride) + { + float wx = 0, wy = 0; + int n = bones[v++]; + n += v; + for (; v < n; v++, b += 3, f += 2) + { + Bone bone = skeletonBones[bones[v]]; + float vx = vertices[b] + deform[f], vy = vertices[b + 1] + deform[f + 1], 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; + } + } + } - /// Returns true if a deform originally applied to the specified attachment should be applied to this attachment. - virtual public bool ApplyDeform (VertexAttachment sourceAttachment) { - return this == sourceAttachment; - } - } + /// Returns true if a deform originally applied to the specified attachment should be applied to this attachment. + virtual public bool ApplyDeform(VertexAttachment sourceAttachment) + { + return this == sourceAttachment; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/BlendMode.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/BlendMode.cs index be4f763..b983448 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/BlendMode.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/BlendMode.cs @@ -28,8 +28,10 @@ * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine3_6_53 { - public enum BlendMode { - Normal, Additive, Multiply, Screen - } +namespace Spine3_6_53 +{ + public enum BlendMode + { + Normal, Additive, Multiply, Screen + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Bone.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Bone.cs index 2ffc818..b3c2d70 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Bone.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Bone.cs @@ -30,355 +30,391 @@ using System; -namespace Spine3_6_53 { - /// - /// 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. - /// - /// - public class Bone : IUpdatable { - static public bool yDown; - - internal BoneData data; - internal Skeleton skeleton; - internal Bone parent; - internal ExposedList children = new ExposedList(); - internal float x, y, rotation, scaleX, scaleY, shearX, shearY; - internal float ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY; - internal bool appliedValid; - - internal float a, b, worldX; - internal float c, d, worldY; - -// internal float worldSignX, worldSignY; -// public float WorldSignX { get { return worldSignX; } } -// public float WorldSignY { get { return worldSignY; } } - - internal bool sorted; - - public BoneData Data { get { return data; } } - public Skeleton Skeleton { get { return skeleton; } } - public Bone Parent { get { return parent; } } - public ExposedList Children { get { return children; } } - /// The local X translation. - public float X { get { return x; } set { x = value; } } - /// The local Y translation. - public float Y { get { return y; } set { y = value; } } - /// The local rotation. - public float Rotation { get { return rotation; } set { rotation = value; } } - - /// The local scaleX. - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - - /// The local scaleY. - public float ScaleY { get { return scaleY; } set { scaleY = value; } } - - /// The local shearX. - public float ShearX { get { return shearX; } set { shearX = value; } } - - /// The local shearY. - public float ShearY { get { return shearY; } set { shearY = value; } } - - /// The rotation, as calculated by any constraints. - public float AppliedRotation { get { return arotation; } set { arotation = value; } } - - /// The applied local x translation. - public float AX { get { return ax; } set { ax = value; } } - - /// The applied local y translation. - public float AY { get { return ay; } set { ay = value; } } - - /// The applied local scaleX. - public float AScaleX { get { return ascaleX; } set { ascaleX = value; } } - - /// The applied local scaleY. - public float AScaleY { get { return ascaleY; } set { ascaleY = value; } } - - /// The applied local shearX. - public float AShearX { get { return ashearX; } set { ashearX = value; } } - - /// The applied local shearY. - public float AShearY { get { return ashearY; } set { ashearY = value; } } - - public float A { get { return a; } } - public float B { get { return b; } } - public float C { get { return c; } } - public float D { get { return d; } } - - public float WorldX { get { return worldX; } } - public float WorldY { get { return worldY; } } - public float WorldRotationX { get { return MathUtils.Atan2(c, a) * MathUtils.RadDeg; } } - public float WorldRotationY { get { return MathUtils.Atan2(d, b) * MathUtils.RadDeg; } } - - /// Returns the magnitide (always positive) of the world scale X. - public float WorldScaleX { get { return (float)Math.Sqrt(a * a + c * c); } } - /// Returns the magnitide (always positive) of the world scale Y. - public float WorldScaleY { get { return (float)Math.Sqrt(b * b + d * d); } } - - /// May be null. - public Bone (BoneData data, Skeleton skeleton, Bone parent) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.data = data; - this.skeleton = skeleton; - this.parent = parent; - SetToSetupPose(); - } - - /// Same as . This method exists for Bone to implement . - public void Update () { - UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); - } - - /// Computes the world transform using the parent bone and this bone's local transform. - public void UpdateWorldTransform () { - UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); - } - - /// Computes the world transform using the parent bone and the specified local transform. - public void UpdateWorldTransform (float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY) { - ax = x; - ay = y; - arotation = rotation; - ascaleX = scaleX; - ascaleY = scaleY; - ashearX = shearX; - ashearY = shearY; - appliedValid = true; - Skeleton skeleton = this.skeleton; - - Bone parent = this.parent; - if (parent == null) { // Root bone. - float rotationY = rotation + 90 + shearY; - float la = MathUtils.CosDeg(rotation + shearX) * scaleX; - float lb = MathUtils.CosDeg(rotationY) * scaleY; - float lc = MathUtils.SinDeg(rotation + shearX) * scaleX; - float ld = MathUtils.SinDeg(rotationY) * scaleY; - if (skeleton.flipX) { - x = -x; - la = -la; - lb = -lb; - } - if (skeleton.flipY != yDown) { - y = -y; - lc = -lc; - ld = -ld; - } - a = la; - b = lb; - c = lc; - d = ld; - worldX = x + skeleton.x; - worldY = y + skeleton.y; - return; - } - - float 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 (data.transformMode) { - case TransformMode.Normal: { - float rotationY = rotation + 90 + shearY; - float la = MathUtils.CosDeg(rotation + shearX) * scaleX; - float lb = MathUtils.CosDeg(rotationY) * scaleY; - float lc = MathUtils.SinDeg(rotation + shearX) * scaleX; - float ld = MathUtils.SinDeg(rotationY) * scaleY; - a = pa * la + pb * lc; - b = pa * lb + pb * ld; - c = pc * la + pd * lc; - d = pc * lb + pd * ld; - return; - } - case TransformMode.OnlyTranslation: { - float rotationY = rotation + 90 + shearY; - a = MathUtils.CosDeg(rotation + shearX) * scaleX; - b = MathUtils.CosDeg(rotationY) * scaleY; - c = MathUtils.SinDeg(rotation + shearX) * scaleX; - d = MathUtils.SinDeg(rotationY) * scaleY; - break; - } - case TransformMode.NoRotationOrReflection: { - float s = pa * pa + pc * pc, prx; - if (s > 0.0001f) { - s = Math.Abs(pa * pd - pb * pc) / s; - pb = pc * s; - pd = pa * s; - prx = MathUtils.Atan2(pc, pa) * MathUtils.RadDeg; - } else { - pa = 0; - pc = 0; - prx = 90 - MathUtils.Atan2(pd, pb) * MathUtils.RadDeg; - } - float rx = rotation + shearX - prx; - float ry = rotation + shearY - prx + 90; - float la = MathUtils.CosDeg(rx) * scaleX; - float lb = MathUtils.CosDeg(ry) * scaleY; - float lc = MathUtils.SinDeg(rx) * scaleX; - float ld = MathUtils.SinDeg(ry) * scaleY; - a = pa * la - pb * lc; - b = pa * lb - pb * ld; - c = pc * la + pd * lc; - d = pc * lb + pd * ld; - break; - } - case TransformMode.NoScale: - case TransformMode.NoScaleOrReflection: { - float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation); - float za = pa * cos + pb * sin; - float zc = pc * cos + pd * sin; - float s = (float)Math.Sqrt(za * za + zc * zc); - if (s > 0.00001f) s = 1 / s; - za *= s; - zc *= s; - s = (float)Math.Sqrt(za * za + zc * zc); - float r = MathUtils.PI / 2 + MathUtils.Atan2(zc, za); - float zb = MathUtils.Cos(r) * s; - float zd = MathUtils.Sin(r) * s; - float la = MathUtils.CosDeg(shearX) * scaleX; - float lb = MathUtils.CosDeg(90 + shearY) * scaleY; - float lc = MathUtils.SinDeg(shearX) * scaleX; - float ld = MathUtils.SinDeg(90 + shearY) * scaleY; - if (data.transformMode != TransformMode.NoScaleOrReflection? pa * pd - pb* pc< 0 : skeleton.flipX != skeleton.flipY) { - zb = -zb; - zd = -zd; - } - a = za * la + zb * lc; - b = za * lb + zb * ld; - c = zc * la + zd * lc; - d = zc * lb + zd * ld; - return; - } - } - - if (skeleton.flipX) { - a = -a; - b = -b; - } - if (skeleton.flipY != Bone.yDown) { - c = -c; - d = -d; - } - } - - public void SetToSetupPose () { - BoneData data = this.data; - x = data.x; - y = data.y; - rotation = data.rotation; - scaleX = data.scaleX; - scaleY = data.scaleY; - shearX = data.shearX; - shearY = data.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. - /// - internal void UpdateAppliedTransform () { - appliedValid = true; - Bone parent = this.parent; - if (parent == null) { - ax = worldX; - ay = worldY; - arotation = MathUtils.Atan2(c, a) * MathUtils.RadDeg; - ascaleX = (float)Math.Sqrt(a * a + c * c); - ascaleY = (float)Math.Sqrt(b * b + d * d); - ashearX = 0; - ashearY = MathUtils.Atan2(a * b + c * d, a * d - b * c) * MathUtils.RadDeg; - return; - } - float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d; - float pid = 1 / (pa * pd - pb * pc); - float dx = worldX - parent.worldX, dy = worldY - parent.worldY; - ax = (dx * pd * pid - dy * pb * pid); - ay = (dy * pa * pid - dx * pc * pid); - float ia = pid * pd; - float id = pid * pa; - float ib = pid * pb; - float ic = pid * pc; - float ra = ia * a - ib * c; - float rb = ia * b - ib * d; - float rc = id * c - ic * a; - float rd = id * d - ic * b; - ashearX = 0; - ascaleX = (float)Math.Sqrt(ra * ra + rc * rc); - if (ascaleX > 0.0001f) { - float det = ra * rd - rb * rc; - ascaleY = det / ascaleX; - ashearY = MathUtils.Atan2(ra * rb + rc * rd, det) * MathUtils.RadDeg; - arotation = MathUtils.Atan2(rc, ra) * MathUtils.RadDeg; - } else { - ascaleX = 0; - ascaleY = (float)Math.Sqrt(rb * rb + rd * rd); - ashearY = 0; - arotation = 90 - MathUtils.Atan2(rd, rb) * MathUtils.RadDeg; - } - } - - public void WorldToLocal (float worldX, float worldY, out float localX, out float localY) { - float a = this.a, b = this.b, c = this.c, d = this.d; - float invDet = 1 / (a * d - b * c); - float x = worldX - this.worldX, y = worldY - this.worldY; - localX = (x * d * invDet - y * b * invDet); - localY = (y * a * invDet - x * c * invDet); - } - - public void LocalToWorld (float localX, float localY, out float worldX, out float worldY) { - worldX = localX * a + localY * b + this.worldX; - worldY = localX * c + localY * d + this.worldY; - } - - public float WorldToLocalRotationX { - get { - Bone parent = this.parent; - if (parent == null) return arotation; - float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, a = this.a, c = this.c; - return MathUtils.Atan2(pa * c - pc * a, pd * a - pb * c) * MathUtils.RadDeg; - } - } - - public float WorldToLocalRotationY { - get { - Bone parent = this.parent; - if (parent == null) return arotation; - float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, b = this.b, d = this.d; - return MathUtils.Atan2(pa * d - pc * b, pd * b - pb * d) * MathUtils.RadDeg; - } - } - - public float WorldToLocalRotation (float worldRotation) { - float sin = MathUtils.SinDeg(worldRotation), cos = MathUtils.CosDeg(worldRotation); - return MathUtils.Atan2(a * sin - c * cos, d * cos - b * sin) * MathUtils.RadDeg; - } - - public float LocalToWorldRotation (float localRotation) { - float sin = MathUtils.SinDeg(localRotation), cos = MathUtils.CosDeg(localRotation); - return MathUtils.Atan2(cos * c + sin * d, cos * a + sin * b) * MathUtils.RadDeg; - } - - /// - /// Rotates the world transform the specified amount and sets isAppliedValid to false. - /// - /// Degrees. - public void RotateWorld (float degrees) { - float a = this.a, b = this.b, c = this.c, d = this.d; - float cos = MathUtils.CosDeg(degrees), sin = MathUtils.SinDeg(degrees); - this.a = cos * a - sin * c; - this.b = cos * b - sin * d; - this.c = sin * a + cos * c; - this.d = sin * b + cos * d; - appliedValid = false; - } - - override public string ToString () { - return data.name; - } - } +namespace Spine3_6_53 +{ + /// + /// 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. + /// + /// + public class Bone : IUpdatable + { + static public bool yDown; + + internal BoneData data; + internal Skeleton skeleton; + internal Bone parent; + internal ExposedList children = new ExposedList(); + internal float x, y, rotation, scaleX, scaleY, shearX, shearY; + internal float ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY; + internal bool appliedValid; + + internal float a, b, worldX; + internal float c, d, worldY; + + // internal float worldSignX, worldSignY; + // public float WorldSignX { get { return worldSignX; } } + // public float WorldSignY { get { return worldSignY; } } + + internal bool sorted; + + public BoneData Data { get { return data; } } + public Skeleton Skeleton { get { return skeleton; } } + public Bone Parent { get { return parent; } } + public ExposedList Children { get { return children; } } + /// The local X translation. + public float X { get { return x; } set { x = value; } } + /// The local Y translation. + public float Y { get { return y; } set { y = value; } } + /// The local rotation. + public float Rotation { get { return rotation; } set { rotation = value; } } + + /// The local scaleX. + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + + /// The local scaleY. + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + + /// The local shearX. + public float ShearX { get { return shearX; } set { shearX = value; } } + + /// The local shearY. + public float ShearY { get { return shearY; } set { shearY = value; } } + + /// The rotation, as calculated by any constraints. + public float AppliedRotation { get { return arotation; } set { arotation = value; } } + + /// The applied local x translation. + public float AX { get { return ax; } set { ax = value; } } + + /// The applied local y translation. + public float AY { get { return ay; } set { ay = value; } } + + /// The applied local scaleX. + public float AScaleX { get { return ascaleX; } set { ascaleX = value; } } + + /// The applied local scaleY. + public float AScaleY { get { return ascaleY; } set { ascaleY = value; } } + + /// The applied local shearX. + public float AShearX { get { return ashearX; } set { ashearX = value; } } + + /// The applied local shearY. + public float AShearY { get { return ashearY; } set { ashearY = value; } } + + public float A { get { return a; } } + public float B { get { return b; } } + public float C { get { return c; } } + public float D { get { return d; } } + + public float WorldX { get { return worldX; } } + public float WorldY { get { return worldY; } } + public float WorldRotationX { get { return MathUtils.Atan2(c, a) * MathUtils.RadDeg; } } + public float WorldRotationY { get { return MathUtils.Atan2(d, b) * MathUtils.RadDeg; } } + + /// Returns the magnitide (always positive) of the world scale X. + public float WorldScaleX { get { return (float)Math.Sqrt(a * a + c * c); } } + /// Returns the magnitide (always positive) of the world scale Y. + public float WorldScaleY { get { return (float)Math.Sqrt(b * b + d * d); } } + + /// May be null. + public Bone(BoneData data, Skeleton skeleton, Bone parent) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + this.skeleton = skeleton; + this.parent = parent; + SetToSetupPose(); + } + + /// Same as . This method exists for Bone to implement . + public void Update() + { + UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); + } + + /// Computes the world transform using the parent bone and this bone's local transform. + public void UpdateWorldTransform() + { + UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); + } + + /// Computes the world transform using the parent bone and the specified local transform. + public void UpdateWorldTransform(float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY) + { + ax = x; + ay = y; + arotation = rotation; + ascaleX = scaleX; + ascaleY = scaleY; + ashearX = shearX; + ashearY = shearY; + appliedValid = true; + Skeleton skeleton = this.skeleton; + + Bone parent = this.parent; + if (parent == null) + { // Root bone. + float rotationY = rotation + 90 + shearY; + float la = MathUtils.CosDeg(rotation + shearX) * scaleX; + float lb = MathUtils.CosDeg(rotationY) * scaleY; + float lc = MathUtils.SinDeg(rotation + shearX) * scaleX; + float ld = MathUtils.SinDeg(rotationY) * scaleY; + if (skeleton.flipX) + { + x = -x; + la = -la; + lb = -lb; + } + if (skeleton.flipY != yDown) + { + y = -y; + lc = -lc; + ld = -ld; + } + a = la; + b = lb; + c = lc; + d = ld; + worldX = x + skeleton.x; + worldY = y + skeleton.y; + return; + } + + float 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 (data.transformMode) + { + case TransformMode.Normal: + { + float rotationY = rotation + 90 + shearY; + float la = MathUtils.CosDeg(rotation + shearX) * scaleX; + float lb = MathUtils.CosDeg(rotationY) * scaleY; + float lc = MathUtils.SinDeg(rotation + shearX) * scaleX; + float ld = MathUtils.SinDeg(rotationY) * scaleY; + a = pa * la + pb * lc; + b = pa * lb + pb * ld; + c = pc * la + pd * lc; + d = pc * lb + pd * ld; + return; + } + case TransformMode.OnlyTranslation: + { + float rotationY = rotation + 90 + shearY; + a = MathUtils.CosDeg(rotation + shearX) * scaleX; + b = MathUtils.CosDeg(rotationY) * scaleY; + c = MathUtils.SinDeg(rotation + shearX) * scaleX; + d = MathUtils.SinDeg(rotationY) * scaleY; + break; + } + case TransformMode.NoRotationOrReflection: + { + float s = pa * pa + pc * pc, prx; + if (s > 0.0001f) + { + s = Math.Abs(pa * pd - pb * pc) / s; + pb = pc * s; + pd = pa * s; + prx = MathUtils.Atan2(pc, pa) * MathUtils.RadDeg; + } + else + { + pa = 0; + pc = 0; + prx = 90 - MathUtils.Atan2(pd, pb) * MathUtils.RadDeg; + } + float rx = rotation + shearX - prx; + float ry = rotation + shearY - prx + 90; + float la = MathUtils.CosDeg(rx) * scaleX; + float lb = MathUtils.CosDeg(ry) * scaleY; + float lc = MathUtils.SinDeg(rx) * scaleX; + float ld = MathUtils.SinDeg(ry) * scaleY; + a = pa * la - pb * lc; + b = pa * lb - pb * ld; + c = pc * la + pd * lc; + d = pc * lb + pd * ld; + break; + } + case TransformMode.NoScale: + case TransformMode.NoScaleOrReflection: + { + float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation); + float za = pa * cos + pb * sin; + float zc = pc * cos + pd * sin; + float s = (float)Math.Sqrt(za * za + zc * zc); + if (s > 0.00001f) s = 1 / s; + za *= s; + zc *= s; + s = (float)Math.Sqrt(za * za + zc * zc); + float r = MathUtils.PI / 2 + MathUtils.Atan2(zc, za); + float zb = MathUtils.Cos(r) * s; + float zd = MathUtils.Sin(r) * s; + float la = MathUtils.CosDeg(shearX) * scaleX; + float lb = MathUtils.CosDeg(90 + shearY) * scaleY; + float lc = MathUtils.SinDeg(shearX) * scaleX; + float ld = MathUtils.SinDeg(90 + shearY) * scaleY; + if (data.transformMode != TransformMode.NoScaleOrReflection ? pa * pd - pb * pc < 0 : skeleton.flipX != skeleton.flipY) + { + zb = -zb; + zd = -zd; + } + a = za * la + zb * lc; + b = za * lb + zb * ld; + c = zc * la + zd * lc; + d = zc * lb + zd * ld; + return; + } + } + + if (skeleton.flipX) + { + a = -a; + b = -b; + } + if (skeleton.flipY != Bone.yDown) + { + c = -c; + d = -d; + } + } + + public void SetToSetupPose() + { + BoneData data = this.data; + x = data.x; + y = data.y; + rotation = data.rotation; + scaleX = data.scaleX; + scaleY = data.scaleY; + shearX = data.shearX; + shearY = data.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. + /// + internal void UpdateAppliedTransform() + { + appliedValid = true; + Bone parent = this.parent; + if (parent == null) + { + ax = worldX; + ay = worldY; + arotation = MathUtils.Atan2(c, a) * MathUtils.RadDeg; + ascaleX = (float)Math.Sqrt(a * a + c * c); + ascaleY = (float)Math.Sqrt(b * b + d * d); + ashearX = 0; + ashearY = MathUtils.Atan2(a * b + c * d, a * d - b * c) * MathUtils.RadDeg; + return; + } + float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d; + float pid = 1 / (pa * pd - pb * pc); + float dx = worldX - parent.worldX, dy = worldY - parent.worldY; + ax = (dx * pd * pid - dy * pb * pid); + ay = (dy * pa * pid - dx * pc * pid); + float ia = pid * pd; + float id = pid * pa; + float ib = pid * pb; + float ic = pid * pc; + float ra = ia * a - ib * c; + float rb = ia * b - ib * d; + float rc = id * c - ic * a; + float rd = id * d - ic * b; + ashearX = 0; + ascaleX = (float)Math.Sqrt(ra * ra + rc * rc); + if (ascaleX > 0.0001f) + { + float det = ra * rd - rb * rc; + ascaleY = det / ascaleX; + ashearY = MathUtils.Atan2(ra * rb + rc * rd, det) * MathUtils.RadDeg; + arotation = MathUtils.Atan2(rc, ra) * MathUtils.RadDeg; + } + else + { + ascaleX = 0; + ascaleY = (float)Math.Sqrt(rb * rb + rd * rd); + ashearY = 0; + arotation = 90 - MathUtils.Atan2(rd, rb) * MathUtils.RadDeg; + } + } + + public void WorldToLocal(float worldX, float worldY, out float localX, out float localY) + { + float a = this.a, b = this.b, c = this.c, d = this.d; + float invDet = 1 / (a * d - b * c); + float x = worldX - this.worldX, y = worldY - this.worldY; + localX = (x * d * invDet - y * b * invDet); + localY = (y * a * invDet - x * c * invDet); + } + + public void LocalToWorld(float localX, float localY, out float worldX, out float worldY) + { + worldX = localX * a + localY * b + this.worldX; + worldY = localX * c + localY * d + this.worldY; + } + + public float WorldToLocalRotationX + { + get + { + Bone parent = this.parent; + if (parent == null) return arotation; + float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, a = this.a, c = this.c; + return MathUtils.Atan2(pa * c - pc * a, pd * a - pb * c) * MathUtils.RadDeg; + } + } + + public float WorldToLocalRotationY + { + get + { + Bone parent = this.parent; + if (parent == null) return arotation; + float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, b = this.b, d = this.d; + return MathUtils.Atan2(pa * d - pc * b, pd * b - pb * d) * MathUtils.RadDeg; + } + } + + public float WorldToLocalRotation(float worldRotation) + { + float sin = MathUtils.SinDeg(worldRotation), cos = MathUtils.CosDeg(worldRotation); + return MathUtils.Atan2(a * sin - c * cos, d * cos - b * sin) * MathUtils.RadDeg; + } + + public float LocalToWorldRotation(float localRotation) + { + float sin = MathUtils.SinDeg(localRotation), cos = MathUtils.CosDeg(localRotation); + return MathUtils.Atan2(cos * c + sin * d, cos * a + sin * b) * MathUtils.RadDeg; + } + + /// + /// Rotates the world transform the specified amount and sets isAppliedValid to false. + /// + /// Degrees. + public void RotateWorld(float degrees) + { + float a = this.a, b = this.b, c = this.c, d = this.d; + float cos = MathUtils.CosDeg(degrees), sin = MathUtils.SinDeg(degrees); + this.a = cos * a - sin * c; + this.b = cos * b - sin * d; + this.c = sin * a + cos * c; + this.d = sin * b + cos * d; + appliedValid = false; + } + + override public string ToString() + { + return data.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/BoneData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/BoneData.cs index 69b8fd9..1481a0b 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/BoneData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/BoneData.cs @@ -30,71 +30,76 @@ using System; -namespace Spine3_6_53 { - public class BoneData { - internal int index; - internal string name; - internal BoneData parent; - internal float length; - internal float x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY; - internal TransformMode transformMode = TransformMode.Normal; - - /// The index of the bone in Skeleton.Bones - public int Index { get { return index; } } - - /// The name of the bone, which is unique within the skeleton. - public string Name { get { return name; } } - - /// May be null. - public BoneData Parent { get { return parent; } } - - public float Length { get { return length; } set { length = value; } } - - /// Local X translation. - public float X { get { return x; } set { x = value; } } - - /// Local Y translation. - public float Y { get { return y; } set { y = value; } } - - /// Local rotation. - public float Rotation { get { return rotation; } set { rotation = value; } } - - /// Local scaleX. - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - - /// Local scaleY. - public float ScaleY { get { return scaleY; } set { scaleY = value; } } - - /// Local shearX. - public float ShearX { get { return shearX; } set { shearX = value; } } - - /// Local shearY. - public float ShearY { get { return shearY; } set { shearY = value; } } - - /// The transform mode for how parent world transforms affect this bone. - public TransformMode TransformMode { get { return transformMode; } set { transformMode = value; } } - - /// May be null. - public BoneData (int index, string name, BoneData parent) { - if (index < 0) throw new ArgumentException("index must be >= 0", "index"); - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.index = index; - this.name = name; - this.parent = parent; - } - - override public string ToString () { - return name; - } - } - - [Flags] - public enum TransformMode { - //0000 0 Flip Scale Rotation - Normal = 0, // 0000 - OnlyTranslation = 7, // 0111 - NoRotationOrReflection = 1, // 0001 - NoScale = 2, // 0010 - NoScaleOrReflection = 6, // 0110 - } +namespace Spine3_6_53 +{ + public class BoneData + { + internal int index; + internal string name; + internal BoneData parent; + internal float length; + internal float x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY; + internal TransformMode transformMode = TransformMode.Normal; + + /// The index of the bone in Skeleton.Bones + public int Index { get { return index; } } + + /// The name of the bone, which is unique within the skeleton. + public string Name { get { return name; } } + + /// May be null. + public BoneData Parent { get { return parent; } } + + public float Length { get { return length; } set { length = value; } } + + /// Local X translation. + public float X { get { return x; } set { x = value; } } + + /// Local Y translation. + public float Y { get { return y; } set { y = value; } } + + /// Local rotation. + public float Rotation { get { return rotation; } set { rotation = value; } } + + /// Local scaleX. + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + + /// Local scaleY. + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + + /// Local shearX. + public float ShearX { get { return shearX; } set { shearX = value; } } + + /// Local shearY. + public float ShearY { get { return shearY; } set { shearY = value; } } + + /// The transform mode for how parent world transforms affect this bone. + public TransformMode TransformMode { get { return transformMode; } set { transformMode = value; } } + + /// May be null. + public BoneData(int index, string name, BoneData parent) + { + if (index < 0) throw new ArgumentException("index must be >= 0", "index"); + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.index = index; + this.name = name; + this.parent = parent; + } + + override public string ToString() + { + return name; + } + } + + [Flags] + public enum TransformMode + { + //0000 0 Flip Scale Rotation + Normal = 0, // 0000 + OnlyTranslation = 7, // 0111 + NoRotationOrReflection = 1, // 0001 + NoScale = 2, // 0010 + NoScaleOrReflection = 6, // 0110 + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Event.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Event.cs index 25dd795..c51ad9a 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Event.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Event.cs @@ -30,31 +30,35 @@ using System; -namespace Spine3_6_53 { - /// Stores the current pose values for an Event. - public class Event { - internal readonly EventData data; - internal readonly float time; - internal int intValue; - internal float floatValue; - internal string stringValue; +namespace Spine3_6_53 +{ + /// Stores the current pose values for an Event. + public class Event + { + internal readonly EventData data; + internal readonly float time; + internal int intValue; + internal float floatValue; + internal string stringValue; - public EventData Data { get { return data; } } - /// The animation time this event was keyed. - public float Time { get { return time; } } + public EventData Data { get { return data; } } + /// The animation time this event was keyed. + public float Time { get { return time; } } - public int Int { get { return intValue; } set { intValue = value; } } - public float Float { get { return floatValue; } set { floatValue = value; } } - public string String { get { return stringValue; } set { stringValue = value; } } + public int Int { get { return intValue; } set { intValue = value; } } + public float Float { get { return floatValue; } set { floatValue = value; } } + public string String { get { return stringValue; } set { stringValue = value; } } - public Event (float time, EventData data) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - this.time = time; - this.data = data; - } + public Event(float time, EventData data) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + this.time = time; + this.data = data; + } - override public string ToString () { - return this.data.Name; - } - } + override public string ToString() + { + return this.data.Name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/EventData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/EventData.cs index 1e15c31..e378aec 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/EventData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/EventData.cs @@ -30,24 +30,28 @@ using System; -namespace Spine3_6_53 { - /// Stores the setup pose values for an Event. - public class EventData { - internal string name; +namespace Spine3_6_53 +{ + /// Stores the setup pose values for an Event. + public class EventData + { + internal string name; - /// The name of the event, which is unique within the skeleton. - public string Name { get { return name; } } - public int Int { get; set; } - public float Float { get; set; } - public string String { get; set; } + /// The name of the event, which is unique within the skeleton. + public string Name { get { return name; } } + public int Int { get; set; } + public float Float { get; set; } + public string String { get; set; } - public EventData (string name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; - } + public EventData(string name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + } - override public string ToString () { - return Name; - } - } + override public string ToString() + { + return Name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/ExposedList.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/ExposedList.cs index 4f5c82d..76c0a2c 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/ExposedList.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/ExposedList.cs @@ -35,590 +35,686 @@ using System.Collections.Generic; using System.Diagnostics; -namespace Spine3_6_53 { - [DebuggerDisplay("Count={Count}")] - public class ExposedList : IEnumerable { - public T[] Items; - public int Count; - private const int DefaultCapacity = 4; - private static readonly T[] EmptyArray = new T[0]; - private int version; - - public ExposedList () { - Items = EmptyArray; - } - - public ExposedList (IEnumerable collection) { - CheckCollection(collection); - - // initialize to needed size (if determinable) - ICollection c = collection as ICollection; - if (c == null) { - Items = EmptyArray; - AddEnumerable(collection); - } else { - Items = new T[c.Count]; - AddCollection(c); - } - } - - public ExposedList (int capacity) { - if (capacity < 0) - throw new ArgumentOutOfRangeException("capacity"); - Items = new T[capacity]; - } - - internal ExposedList (T[] data, int size) { - Items = data; - Count = size; - } - - public void Add (T item) { - // If we check to see if we need to grow before trying to grow - // we can speed things up by 25% - if (Count == Items.Length) - GrowIfNeeded(1); - Items[Count++] = item; - version++; - } - - public void GrowIfNeeded (int newCount) { - int minimumSize = Count + newCount; - if (minimumSize > Items.Length) - Capacity = Math.Max(Math.Max(Capacity * 2, DefaultCapacity), minimumSize); - } - - public ExposedList Resize (int newSize) { - int itemsLength = Items.Length; - var oldItems = Items; - if (newSize > itemsLength) { - Array.Resize(ref Items, newSize); -// var newItems = new T[newSize]; -// Array.Copy(oldItems, newItems, Count); -// Items = newItems; - } else if (newSize < itemsLength) { - // Allow nulling of T reference type to allow GC. - for (int i = newSize; i < itemsLength; i++) - oldItems[i] = default(T); - } - Count = newSize; - return this; - } - - public void EnsureCapacity (int min) { - if (Items.Length < min) { - int newCapacity = Items.Length == 0 ? DefaultCapacity : Items.Length * 2; - //if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength; - if (newCapacity < min) newCapacity = min; - Capacity = newCapacity; - } - } - - private void CheckRange (int index, int count) { - if (index < 0) - throw new ArgumentOutOfRangeException("index"); - - if (count < 0) - throw new ArgumentOutOfRangeException("count"); - - if ((uint)index + (uint)count > (uint)Count) - throw new ArgumentException("index and count exceed length of list"); - } - - private void AddCollection (ICollection collection) { - int collectionCount = collection.Count; - if (collectionCount == 0) - return; - - GrowIfNeeded(collectionCount); - collection.CopyTo(Items, Count); - Count += collectionCount; - } - - private void AddEnumerable (IEnumerable enumerable) { - foreach (T t in enumerable) { - Add(t); - } - } - - public void AddRange (IEnumerable collection) { - CheckCollection(collection); - - ICollection c = collection as ICollection; - if (c != null) - AddCollection(c); - else - AddEnumerable(collection); - version++; - } - - public int BinarySearch (T item) { - return Array.BinarySearch(Items, 0, Count, item); - } - - public int BinarySearch (T item, IComparer comparer) { - return Array.BinarySearch(Items, 0, Count, item, comparer); - } - - public int BinarySearch (int index, int count, T item, IComparer comparer) { - CheckRange(index, count); - return Array.BinarySearch(Items, index, count, item, comparer); - } - - public void Clear (bool clearArray = true) { - if (clearArray) - Array.Clear(Items, 0, Items.Length); - - Count = 0; - version++; - } - - public bool Contains (T item) { - return Array.IndexOf(Items, item, 0, Count) != -1; - } - - public ExposedList ConvertAll (Converter converter) { - if (converter == null) - throw new ArgumentNullException("converter"); - ExposedList u = new ExposedList(Count); - for (int i = 0; i < Count; i++) - u.Items[i] = converter(Items[i]); - - u.Count = Count; - return u; - } - - public void CopyTo (T[] array) { - Array.Copy(Items, 0, array, 0, Count); - } - - public void CopyTo (T[] array, int arrayIndex) { - Array.Copy(Items, 0, array, arrayIndex, Count); - } - - public void CopyTo (int index, T[] array, int arrayIndex, int count) { - CheckRange(index, count); - Array.Copy(Items, index, array, arrayIndex, count); - } - - - - public bool Exists (Predicate match) { - CheckMatch(match); - return GetIndex(0, Count, match) != -1; - } - - public T Find (Predicate match) { - CheckMatch(match); - int i = GetIndex(0, Count, match); - return (i != -1) ? Items[i] : default(T); - } - - private static void CheckMatch (Predicate match) { - if (match == null) - throw new ArgumentNullException("match"); - } - - public ExposedList FindAll (Predicate match) { - CheckMatch(match); - return FindAllList(match); - } - - private ExposedList FindAllList (Predicate match) { - ExposedList results = new ExposedList(); - for (int i = 0; i < Count; i++) - if (match(Items[i])) - results.Add(Items[i]); - - return results; - } - - public int FindIndex (Predicate match) { - CheckMatch(match); - return GetIndex(0, Count, match); - } - - public int FindIndex (int startIndex, Predicate match) { - CheckMatch(match); - CheckIndex(startIndex); - return GetIndex(startIndex, Count - startIndex, match); - } - - public int FindIndex (int startIndex, int count, Predicate match) { - CheckMatch(match); - CheckRange(startIndex, count); - return GetIndex(startIndex, count, match); - } - - private int GetIndex (int startIndex, int count, Predicate match) { - int end = startIndex + count; - for (int i = startIndex; i < end; i++) - if (match(Items[i])) - return i; - - return -1; - } - - public T FindLast (Predicate match) { - CheckMatch(match); - int i = GetLastIndex(0, Count, match); - return i == -1 ? default(T) : Items[i]; - } - - public int FindLastIndex (Predicate match) { - CheckMatch(match); - return GetLastIndex(0, Count, match); - } - - public int FindLastIndex (int startIndex, Predicate match) { - CheckMatch(match); - CheckIndex(startIndex); - return GetLastIndex(0, startIndex + 1, match); - } - - public int FindLastIndex (int startIndex, int count, Predicate match) { - CheckMatch(match); - int start = startIndex - count + 1; - CheckRange(start, count); - return GetLastIndex(start, count, match); - } - - private int GetLastIndex (int startIndex, int count, Predicate match) { - // unlike FindLastIndex, takes regular params for search range - for (int i = startIndex + count; i != startIndex; ) - if (match(Items[--i])) - return i; - return -1; - } - - public void ForEach (Action action) { - if (action == null) - throw new ArgumentNullException("action"); - for (int i = 0; i < Count; i++) - action(Items[i]); - } - - public Enumerator GetEnumerator () { - return new Enumerator(this); - } - - public ExposedList GetRange (int index, int count) { - CheckRange(index, count); - T[] tmpArray = new T[count]; - Array.Copy(Items, index, tmpArray, 0, count); - return new ExposedList(tmpArray, count); - } - - public int IndexOf (T item) { - return Array.IndexOf(Items, item, 0, Count); - } - - public int IndexOf (T item, int index) { - CheckIndex(index); - return Array.IndexOf(Items, item, index, Count - index); - } - - public int IndexOf (T item, int index, int count) { - if (index < 0) - throw new ArgumentOutOfRangeException("index"); - - if (count < 0) - throw new ArgumentOutOfRangeException("count"); - - if ((uint)index + (uint)count > (uint)Count) - throw new ArgumentOutOfRangeException("index and count exceed length of list"); - - return Array.IndexOf(Items, item, index, count); - } - - private void Shift (int start, int delta) { - if (delta < 0) - start -= delta; - - if (start < Count) - Array.Copy(Items, start, Items, start + delta, Count - start); - - Count += delta; - - if (delta < 0) - Array.Clear(Items, Count, -delta); - } - - private void CheckIndex (int index) { - if (index < 0 || (uint)index > (uint)Count) - throw new ArgumentOutOfRangeException("index"); - } - - public void Insert (int index, T item) { - CheckIndex(index); - if (Count == Items.Length) - GrowIfNeeded(1); - Shift(index, 1); - Items[index] = item; - version++; - } - - private void CheckCollection (IEnumerable collection) { - if (collection == null) - throw new ArgumentNullException("collection"); - } - - public void InsertRange (int index, IEnumerable collection) { - CheckCollection(collection); - CheckIndex(index); - if (collection == this) { - T[] buffer = new T[Count]; - CopyTo(buffer, 0); - GrowIfNeeded(Count); - Shift(index, buffer.Length); - Array.Copy(buffer, 0, Items, index, buffer.Length); - } else { - ICollection c = collection as ICollection; - if (c != null) - InsertCollection(index, c); - else - InsertEnumeration(index, collection); - } - version++; - } - - private void InsertCollection (int index, ICollection collection) { - int collectionCount = collection.Count; - GrowIfNeeded(collectionCount); - - Shift(index, collectionCount); - collection.CopyTo(Items, index); - } - - private void InsertEnumeration (int index, IEnumerable enumerable) { - foreach (T t in enumerable) - Insert(index++, t); - } - - public int LastIndexOf (T item) { - return Array.LastIndexOf(Items, item, Count - 1, Count); - } - - public int LastIndexOf (T item, int index) { - CheckIndex(index); - return Array.LastIndexOf(Items, item, index, index + 1); - } - - public int LastIndexOf (T item, int index, int count) { - if (index < 0) - throw new ArgumentOutOfRangeException("index", index, "index is negative"); - - if (count < 0) - throw new ArgumentOutOfRangeException("count", count, "count is negative"); - - if (index - count + 1 < 0) - throw new ArgumentOutOfRangeException("count", count, "count is too large"); - - return Array.LastIndexOf(Items, item, index, count); - } - - public bool Remove (T item) { - int loc = IndexOf(item); - if (loc != -1) - RemoveAt(loc); - - return loc != -1; - } - - public int RemoveAll (Predicate match) { - CheckMatch(match); - int i = 0; - int j = 0; - - // Find the first item to remove - for (i = 0; i < Count; i++) - if (match(Items[i])) - break; - - if (i == Count) - return 0; - - version++; - - // Remove any additional items - for (j = i + 1; j < Count; j++) { - if (!match(Items[j])) - Items[i++] = Items[j]; - } - if (j - i > 0) - Array.Clear(Items, i, j - i); - - Count = i; - return (j - i); - } - - public void RemoveAt (int index) { - if (index < 0 || (uint)index >= (uint)Count) - throw new ArgumentOutOfRangeException("index"); - Shift(index, -1); - Array.Clear(Items, Count, 1); - version++; - } - - // Spine Added Method - // Based on Stack.Pop(); https://referencesource.microsoft.com/#mscorlib/system/collections/stack.cs - /// Pops the last item of the list. If the list is empty, Pop throws an InvalidOperationException. - public T Pop () { - if (Count == 0) - throw new InvalidOperationException("List is empty. Nothing to pop."); - - int i = Count - 1; - T item = Items[i]; - Items[i] = default(T); - Count--; - version++; - return item; - } - - public void RemoveRange (int index, int count) { - CheckRange(index, count); - if (count > 0) { - Shift(index, -count); - Array.Clear(Items, Count, count); - version++; - } - } - - public void Reverse () { - Array.Reverse(Items, 0, Count); - version++; - } - - public void Reverse (int index, int count) { - CheckRange(index, count); - Array.Reverse(Items, index, count); - version++; - } - - public void Sort () { - Array.Sort(Items, 0, Count, Comparer.Default); - version++; - } - - public void Sort (IComparer comparer) { - Array.Sort(Items, 0, Count, comparer); - version++; - } - - public void Sort (Comparison comparison) { - Array.Sort(Items, comparison); - version++; - } - - public void Sort (int index, int count, IComparer comparer) { - CheckRange(index, count); - Array.Sort(Items, index, count, comparer); - version++; - } - - public T[] ToArray () { - T[] t = new T[Count]; - Array.Copy(Items, t, Count); - - return t; - } - - public void TrimExcess () { - Capacity = Count; - } - - public bool TrueForAll (Predicate match) { - CheckMatch(match); - - for (int i = 0; i < Count; i++) - if (!match(Items[i])) - return false; - - return true; - } - - public int Capacity { - get { - return Items.Length; - } - set { - if ((uint)value < (uint)Count) - throw new ArgumentOutOfRangeException(); - - Array.Resize(ref Items, value); - } - } - - #region Interface implementations. - - IEnumerator IEnumerable.GetEnumerator () { - return GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator () { - return GetEnumerator(); - } - - #endregion - - public struct Enumerator : IEnumerator, IDisposable { - private ExposedList l; - private int next; - private int ver; - private T current; - - internal Enumerator (ExposedList l) - : this() { - this.l = l; - ver = l.version; - } - - public void Dispose () { - l = null; - } - - private void VerifyState () { - if (l == null) - throw new ObjectDisposedException(GetType().FullName); - if (ver != l.version) - throw new InvalidOperationException( - "Collection was modified; enumeration operation may not execute."); - } - - public bool MoveNext () { - VerifyState(); - - if (next < 0) - return false; - - if (next < l.Count) { - current = l.Items[next++]; - return true; - } - - next = -1; - return false; - } - - public T Current { - get { - return current; - } - } - - void IEnumerator.Reset () { - VerifyState(); - next = 0; - } - - object IEnumerator.Current { - get { - VerifyState(); - if (next <= 0) - throw new InvalidOperationException(); - return current; - } - } - } - } +namespace Spine3_6_53 +{ + [DebuggerDisplay("Count={Count}")] + public class ExposedList : IEnumerable + { + public T[] Items; + public int Count; + private const int DefaultCapacity = 4; + private static readonly T[] EmptyArray = new T[0]; + private int version; + + public ExposedList() + { + Items = EmptyArray; + } + + public ExposedList(IEnumerable collection) + { + CheckCollection(collection); + + // initialize to needed size (if determinable) + ICollection c = collection as ICollection; + if (c == null) + { + Items = EmptyArray; + AddEnumerable(collection); + } + else + { + Items = new T[c.Count]; + AddCollection(c); + } + } + + public ExposedList(int capacity) + { + if (capacity < 0) + throw new ArgumentOutOfRangeException("capacity"); + Items = new T[capacity]; + } + + internal ExposedList(T[] data, int size) + { + Items = data; + Count = size; + } + + public void Add(T item) + { + // If we check to see if we need to grow before trying to grow + // we can speed things up by 25% + if (Count == Items.Length) + GrowIfNeeded(1); + Items[Count++] = item; + version++; + } + + public void GrowIfNeeded(int newCount) + { + int minimumSize = Count + newCount; + if (minimumSize > Items.Length) + Capacity = Math.Max(Math.Max(Capacity * 2, DefaultCapacity), minimumSize); + } + + public ExposedList Resize(int newSize) + { + int itemsLength = Items.Length; + var oldItems = Items; + if (newSize > itemsLength) + { + Array.Resize(ref Items, newSize); + // var newItems = new T[newSize]; + // Array.Copy(oldItems, newItems, Count); + // Items = newItems; + } + else if (newSize < itemsLength) + { + // Allow nulling of T reference type to allow GC. + for (int i = newSize; i < itemsLength; i++) + oldItems[i] = default(T); + } + Count = newSize; + return this; + } + + public void EnsureCapacity(int min) + { + if (Items.Length < min) + { + int newCapacity = Items.Length == 0 ? DefaultCapacity : Items.Length * 2; + //if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength; + if (newCapacity < min) newCapacity = min; + Capacity = newCapacity; + } + } + + private void CheckRange(int index, int count) + { + if (index < 0) + throw new ArgumentOutOfRangeException("index"); + + if (count < 0) + throw new ArgumentOutOfRangeException("count"); + + if ((uint)index + (uint)count > (uint)Count) + throw new ArgumentException("index and count exceed length of list"); + } + + private void AddCollection(ICollection collection) + { + int collectionCount = collection.Count; + if (collectionCount == 0) + return; + + GrowIfNeeded(collectionCount); + collection.CopyTo(Items, Count); + Count += collectionCount; + } + + private void AddEnumerable(IEnumerable enumerable) + { + foreach (T t in enumerable) + { + Add(t); + } + } + + public void AddRange(IEnumerable collection) + { + CheckCollection(collection); + + ICollection c = collection as ICollection; + if (c != null) + AddCollection(c); + else + AddEnumerable(collection); + version++; + } + + public int BinarySearch(T item) + { + return Array.BinarySearch(Items, 0, Count, item); + } + + public int BinarySearch(T item, IComparer comparer) + { + return Array.BinarySearch(Items, 0, Count, item, comparer); + } + + public int BinarySearch(int index, int count, T item, IComparer comparer) + { + CheckRange(index, count); + return Array.BinarySearch(Items, index, count, item, comparer); + } + + public void Clear(bool clearArray = true) + { + if (clearArray) + Array.Clear(Items, 0, Items.Length); + + Count = 0; + version++; + } + + public bool Contains(T item) + { + return Array.IndexOf(Items, item, 0, Count) != -1; + } + + public ExposedList ConvertAll(Converter converter) + { + if (converter == null) + throw new ArgumentNullException("converter"); + ExposedList u = new ExposedList(Count); + for (int i = 0; i < Count; i++) + u.Items[i] = converter(Items[i]); + + u.Count = Count; + return u; + } + + public void CopyTo(T[] array) + { + Array.Copy(Items, 0, array, 0, Count); + } + + public void CopyTo(T[] array, int arrayIndex) + { + Array.Copy(Items, 0, array, arrayIndex, Count); + } + + public void CopyTo(int index, T[] array, int arrayIndex, int count) + { + CheckRange(index, count); + Array.Copy(Items, index, array, arrayIndex, count); + } + + + + public bool Exists(Predicate match) + { + CheckMatch(match); + return GetIndex(0, Count, match) != -1; + } + + public T Find(Predicate match) + { + CheckMatch(match); + int i = GetIndex(0, Count, match); + return (i != -1) ? Items[i] : default(T); + } + + private static void CheckMatch(Predicate match) + { + if (match == null) + throw new ArgumentNullException("match"); + } + + public ExposedList FindAll(Predicate match) + { + CheckMatch(match); + return FindAllList(match); + } + + private ExposedList FindAllList(Predicate match) + { + ExposedList results = new ExposedList(); + for (int i = 0; i < Count; i++) + if (match(Items[i])) + results.Add(Items[i]); + + return results; + } + + public int FindIndex(Predicate match) + { + CheckMatch(match); + return GetIndex(0, Count, match); + } + + public int FindIndex(int startIndex, Predicate match) + { + CheckMatch(match); + CheckIndex(startIndex); + return GetIndex(startIndex, Count - startIndex, match); + } + + public int FindIndex(int startIndex, int count, Predicate match) + { + CheckMatch(match); + CheckRange(startIndex, count); + return GetIndex(startIndex, count, match); + } + + private int GetIndex(int startIndex, int count, Predicate match) + { + int end = startIndex + count; + for (int i = startIndex; i < end; i++) + if (match(Items[i])) + return i; + + return -1; + } + + public T FindLast(Predicate match) + { + CheckMatch(match); + int i = GetLastIndex(0, Count, match); + return i == -1 ? default(T) : Items[i]; + } + + public int FindLastIndex(Predicate match) + { + CheckMatch(match); + return GetLastIndex(0, Count, match); + } + + public int FindLastIndex(int startIndex, Predicate match) + { + CheckMatch(match); + CheckIndex(startIndex); + return GetLastIndex(0, startIndex + 1, match); + } + + public int FindLastIndex(int startIndex, int count, Predicate match) + { + CheckMatch(match); + int start = startIndex - count + 1; + CheckRange(start, count); + return GetLastIndex(start, count, match); + } + + private int GetLastIndex(int startIndex, int count, Predicate match) + { + // unlike FindLastIndex, takes regular params for search range + for (int i = startIndex + count; i != startIndex;) + if (match(Items[--i])) + return i; + return -1; + } + + public void ForEach(Action action) + { + if (action == null) + throw new ArgumentNullException("action"); + for (int i = 0; i < Count; i++) + action(Items[i]); + } + + public Enumerator GetEnumerator() + { + return new Enumerator(this); + } + + public ExposedList GetRange(int index, int count) + { + CheckRange(index, count); + T[] tmpArray = new T[count]; + Array.Copy(Items, index, tmpArray, 0, count); + return new ExposedList(tmpArray, count); + } + + public int IndexOf(T item) + { + return Array.IndexOf(Items, item, 0, Count); + } + + public int IndexOf(T item, int index) + { + CheckIndex(index); + return Array.IndexOf(Items, item, index, Count - index); + } + + public int IndexOf(T item, int index, int count) + { + if (index < 0) + throw new ArgumentOutOfRangeException("index"); + + if (count < 0) + throw new ArgumentOutOfRangeException("count"); + + if ((uint)index + (uint)count > (uint)Count) + throw new ArgumentOutOfRangeException("index and count exceed length of list"); + + return Array.IndexOf(Items, item, index, count); + } + + private void Shift(int start, int delta) + { + if (delta < 0) + start -= delta; + + if (start < Count) + Array.Copy(Items, start, Items, start + delta, Count - start); + + Count += delta; + + if (delta < 0) + Array.Clear(Items, Count, -delta); + } + + private void CheckIndex(int index) + { + if (index < 0 || (uint)index > (uint)Count) + throw new ArgumentOutOfRangeException("index"); + } + + public void Insert(int index, T item) + { + CheckIndex(index); + if (Count == Items.Length) + GrowIfNeeded(1); + Shift(index, 1); + Items[index] = item; + version++; + } + + private void CheckCollection(IEnumerable collection) + { + if (collection == null) + throw new ArgumentNullException("collection"); + } + + public void InsertRange(int index, IEnumerable collection) + { + CheckCollection(collection); + CheckIndex(index); + if (collection == this) + { + T[] buffer = new T[Count]; + CopyTo(buffer, 0); + GrowIfNeeded(Count); + Shift(index, buffer.Length); + Array.Copy(buffer, 0, Items, index, buffer.Length); + } + else + { + ICollection c = collection as ICollection; + if (c != null) + InsertCollection(index, c); + else + InsertEnumeration(index, collection); + } + version++; + } + + private void InsertCollection(int index, ICollection collection) + { + int collectionCount = collection.Count; + GrowIfNeeded(collectionCount); + + Shift(index, collectionCount); + collection.CopyTo(Items, index); + } + + private void InsertEnumeration(int index, IEnumerable enumerable) + { + foreach (T t in enumerable) + Insert(index++, t); + } + + public int LastIndexOf(T item) + { + return Array.LastIndexOf(Items, item, Count - 1, Count); + } + + public int LastIndexOf(T item, int index) + { + CheckIndex(index); + return Array.LastIndexOf(Items, item, index, index + 1); + } + + public int LastIndexOf(T item, int index, int count) + { + if (index < 0) + throw new ArgumentOutOfRangeException("index", index, "index is negative"); + + if (count < 0) + throw new ArgumentOutOfRangeException("count", count, "count is negative"); + + if (index - count + 1 < 0) + throw new ArgumentOutOfRangeException("count", count, "count is too large"); + + return Array.LastIndexOf(Items, item, index, count); + } + + public bool Remove(T item) + { + int loc = IndexOf(item); + if (loc != -1) + RemoveAt(loc); + + return loc != -1; + } + + public int RemoveAll(Predicate match) + { + CheckMatch(match); + int i = 0; + int j = 0; + + // Find the first item to remove + for (i = 0; i < Count; i++) + if (match(Items[i])) + break; + + if (i == Count) + return 0; + + version++; + + // Remove any additional items + for (j = i + 1; j < Count; j++) + { + if (!match(Items[j])) + Items[i++] = Items[j]; + } + if (j - i > 0) + Array.Clear(Items, i, j - i); + + Count = i; + return (j - i); + } + + public void RemoveAt(int index) + { + if (index < 0 || (uint)index >= (uint)Count) + throw new ArgumentOutOfRangeException("index"); + Shift(index, -1); + Array.Clear(Items, Count, 1); + version++; + } + + // Spine Added Method + // Based on Stack.Pop(); https://referencesource.microsoft.com/#mscorlib/system/collections/stack.cs + /// Pops the last item of the list. If the list is empty, Pop throws an InvalidOperationException. + public T Pop() + { + if (Count == 0) + throw new InvalidOperationException("List is empty. Nothing to pop."); + + int i = Count - 1; + T item = Items[i]; + Items[i] = default(T); + Count--; + version++; + return item; + } + + public void RemoveRange(int index, int count) + { + CheckRange(index, count); + if (count > 0) + { + Shift(index, -count); + Array.Clear(Items, Count, count); + version++; + } + } + + public void Reverse() + { + Array.Reverse(Items, 0, Count); + version++; + } + + public void Reverse(int index, int count) + { + CheckRange(index, count); + Array.Reverse(Items, index, count); + version++; + } + + public void Sort() + { + Array.Sort(Items, 0, Count, Comparer.Default); + version++; + } + + public void Sort(IComparer comparer) + { + Array.Sort(Items, 0, Count, comparer); + version++; + } + + public void Sort(Comparison comparison) + { + Array.Sort(Items, comparison); + version++; + } + + public void Sort(int index, int count, IComparer comparer) + { + CheckRange(index, count); + Array.Sort(Items, index, count, comparer); + version++; + } + + public T[] ToArray() + { + T[] t = new T[Count]; + Array.Copy(Items, t, Count); + + return t; + } + + public void TrimExcess() + { + Capacity = Count; + } + + public bool TrueForAll(Predicate match) + { + CheckMatch(match); + + for (int i = 0; i < Count; i++) + if (!match(Items[i])) + return false; + + return true; + } + + public int Capacity + { + get + { + return Items.Length; + } + set + { + if ((uint)value < (uint)Count) + throw new ArgumentOutOfRangeException(); + + Array.Resize(ref Items, value); + } + } + + #region Interface implementations. + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion + + public struct Enumerator : IEnumerator, IDisposable + { + private ExposedList l; + private int next; + private int ver; + private T current; + + internal Enumerator(ExposedList l) + : this() + { + this.l = l; + ver = l.version; + } + + public void Dispose() + { + l = null; + } + + private void VerifyState() + { + if (l == null) + throw new ObjectDisposedException(GetType().FullName); + if (ver != l.version) + throw new InvalidOperationException( + "Collection was modified; enumeration operation may not execute."); + } + + public bool MoveNext() + { + VerifyState(); + + if (next < 0) + return false; + + if (next < l.Count) + { + current = l.Items[next++]; + return true; + } + + next = -1; + return false; + } + + public T Current + { + get + { + return current; + } + } + + void IEnumerator.Reset() + { + VerifyState(); + next = 0; + } + + object IEnumerator.Current + { + get + { + VerifyState(); + if (next <= 0) + throw new InvalidOperationException(); + return current; + } + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/IConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/IConstraint.cs index bab9de8..e1be5aa 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/IConstraint.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/IConstraint.cs @@ -28,13 +28,15 @@ * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine3_6_53 { - - /// The interface for all constraints. - public interface IConstraint : IUpdatable { - /// The ordinal for the order a skeleton's constraints will be applied. - int Order { get; } +namespace Spine3_6_53 +{ - } + /// The interface for all constraints. + public interface IConstraint : IUpdatable + { + /// The ordinal for the order a skeleton's constraints will be applied. + int Order { get; } + + } } \ No newline at end of file diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/IUpdatable.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/IUpdatable.cs index d9a5ccb..5642c59 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/IUpdatable.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/IUpdatable.cs @@ -28,8 +28,10 @@ * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine3_6_53 { - public interface IUpdatable { - void Update (); - } +namespace Spine3_6_53 +{ + public interface IUpdatable + { + void Update(); + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/IkConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/IkConstraint.cs index 2d248bb..3653238 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/IkConstraint.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/IkConstraint.cs @@ -30,198 +30,228 @@ using System; -namespace Spine3_6_53 { - public class IkConstraint : IConstraint { - internal IkConstraintData data; - internal ExposedList bones = new ExposedList(); - internal Bone target; - internal float mix; - internal int bendDirection; +namespace Spine3_6_53 +{ + public class IkConstraint : IConstraint + { + internal IkConstraintData data; + internal ExposedList bones = new ExposedList(); + internal Bone target; + internal float mix; + internal int bendDirection; - public IkConstraintData Data { get { return data; } } - public int Order { get { return data.order; } } - public ExposedList Bones { get { return bones; } } - public Bone Target { get { return target; } set { target = value; } } - public int BendDirection { get { return bendDirection; } set { bendDirection = value; } } - public float Mix { get { return mix; } set { mix = value; } } + public IkConstraintData Data { get { return data; } } + public int Order { get { return data.order; } } + public ExposedList Bones { get { return bones; } } + public Bone Target { get { return target; } set { target = value; } } + public int BendDirection { get { return bendDirection; } set { bendDirection = value; } } + public float Mix { get { return mix; } set { mix = value; } } - public IkConstraint (IkConstraintData data, Skeleton skeleton) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.data = data; - mix = data.mix; - bendDirection = data.bendDirection; + public IkConstraint(IkConstraintData data, Skeleton skeleton) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + mix = data.mix; + bendDirection = data.bendDirection; - bones = new ExposedList(data.bones.Count); - foreach (BoneData boneData in data.bones) - bones.Add(skeleton.FindBone(boneData.name)); - target = skeleton.FindBone(data.target.name); - } + bones = new ExposedList(data.bones.Count); + foreach (BoneData boneData in data.bones) + bones.Add(skeleton.FindBone(boneData.name)); + target = skeleton.FindBone(data.target.name); + } - /// Applies the constraint to the constrained bones. - public void Apply () { - Update(); - } + /// Applies the constraint to the constrained bones. + public void Apply() + { + Update(); + } - public void Update () { - Bone target = this.target; - ExposedList bones = this.bones; - switch (bones.Count) { - case 1: - Apply(bones.Items[0], target.worldX, target.worldY, mix); - break; - case 2: - Apply(bones.Items[0], bones.Items[1], target.worldX, target.worldY, bendDirection, mix); - break; - } - } + public void Update() + { + Bone target = this.target; + ExposedList bones = this.bones; + switch (bones.Count) + { + case 1: + Apply(bones.Items[0], target.worldX, target.worldY, mix); + break; + case 2: + Apply(bones.Items[0], bones.Items[1], target.worldX, target.worldY, bendDirection, mix); + break; + } + } - override public string ToString () { - return data.name; - } + override public string ToString() + { + return data.name; + } - /// 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 public void Apply (Bone bone, float targetX, float targetY, float alpha) { - if (!bone.appliedValid) bone.UpdateAppliedTransform(); - Bone p = bone.parent; - float id = 1 / (p.a * p.d - p.b * p.c); - float x = targetX - p.worldX, y = targetY - p.worldY; - float tx = (x * p.d - y * p.b) * id - bone.ax, ty = (y * p.a - x * p.c) * id - bone.ay; - float rotationIK = (float)Math.Atan2(ty, tx) * MathUtils.RadDeg - bone.ashearX - bone.arotation; - if (bone.ascaleX < 0) rotationIK += 180; - if (rotationIK > 180) - rotationIK -= 360; - else if (rotationIK < -180) rotationIK += 360; - bone.UpdateWorldTransform(bone.ax, bone.ay, bone.arotation + rotationIK * alpha, bone.ascaleX, bone.ascaleY, bone.ashearX, - bone.ashearY); - } + /// 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 public void Apply(Bone bone, float targetX, float targetY, float alpha) + { + if (!bone.appliedValid) bone.UpdateAppliedTransform(); + Bone p = bone.parent; + float id = 1 / (p.a * p.d - p.b * p.c); + float x = targetX - p.worldX, y = targetY - p.worldY; + float tx = (x * p.d - y * p.b) * id - bone.ax, ty = (y * p.a - x * p.c) * id - bone.ay; + float rotationIK = (float)Math.Atan2(ty, tx) * MathUtils.RadDeg - bone.ashearX - bone.arotation; + if (bone.ascaleX < 0) rotationIK += 180; + if (rotationIK > 180) + rotationIK -= 360; + else if (rotationIK < -180) rotationIK += 360; + bone.UpdateWorldTransform(bone.ax, bone.ay, bone.arotation + rotationIK * alpha, bone.ascaleX, bone.ascaleY, bone.ashearX, + bone.ashearY); + } - /// 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. - /// A direct descendant of the parent bone. - static public void Apply (Bone parent, Bone child, float targetX, float targetY, int bendDir, float alpha) { - if (alpha == 0) { - child.UpdateWorldTransform (); - return; - } - //float px = parent.x, py = parent.y, psx = parent.scaleX, psy = parent.scaleY, csx = child.scaleX; - if (!parent.appliedValid) parent.UpdateAppliedTransform(); - if (!child.appliedValid) child.UpdateAppliedTransform(); - float px = parent.ax, py = parent.ay, psx = parent.ascaleX, psy = parent.ascaleY, csx = child.ascaleX; - int os1, os2, s2; - if (psx < 0) { - psx = -psx; - os1 = 180; - s2 = -1; - } else { - os1 = 0; - s2 = 1; - } - if (psy < 0) { - psy = -psy; - s2 = -s2; - } - if (csx < 0) { - csx = -csx; - os2 = 180; - } else - os2 = 0; - float cx = child.ax, cy, cwx, cwy, a = parent.a, b = parent.b, c = parent.c, d = parent.d; - bool u = Math.Abs(psx - psy) <= 0.0001f; - if (!u) { - cy = 0; - cwx = a * cx + parent.worldX; - cwy = c * cx + parent.worldY; - } else { - cy = child.ay; - cwx = a * cx + b * cy + parent.worldX; - cwy = c * cx + d * cy + parent.worldY; - } - Bone pp = parent.parent; - a = pp.a; - b = pp.b; - c = pp.c; - d = pp.d; - float id = 1 / (a * d - b * c), x = targetX - pp.worldX, y = targetY - pp.worldY; - float tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py; - x = cwx - pp.worldX; - y = cwy - pp.worldY; - float dx = (x * d - y * b) * id - px, dy = (y * a - x * c) * id - py; - float l1 = (float)Math.Sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2; - if (u) { - l2 *= psx; - float cos = (tx * tx + ty * ty - l1 * l1 - l2 * l2) / (2 * l1 * l2); - if (cos < -1) - cos = -1; - else if (cos > 1) cos = 1; - a2 = (float)Math.Acos(cos) * bendDir; - a = l1 + l2 * cos; - b = l2 * (float)Math.Sin(a2); - a1 = (float)Math.Atan2(ty * a - tx * b, tx * a + ty * b); - } else { - a = psx * l2; - b = psy * l2; - float aa = a * a, bb = b * b, dd = tx * tx + ty * ty, ta = (float)Math.Atan2(ty, tx); - c = bb * l1 * l1 + aa * dd - aa * bb; - float c1 = -2 * bb * l1, c2 = bb - aa; - d = c1 * c1 - 4 * c2 * c; - if (d >= 0) { - float q = (float)Math.Sqrt(d); - if (c1 < 0) q = -q; - q = -(c1 + q) / 2; - float r0 = q / c2, r1 = c / q; - float r = Math.Abs(r0) < Math.Abs(r1) ? r0 : r1; - if (r * r <= dd) { - y = (float)Math.Sqrt(dd - r * r) * bendDir; - a1 = ta - (float)Math.Atan2(y, r); - a2 = (float)Math.Atan2(y / psy, (r - l1) / psx); - goto break_outer; // break outer; - } - } - float minAngle = MathUtils.PI, minX = l1 - a, minDist = minX * minX, minY = 0; - float maxAngle = 0, maxX = l1 + a, maxDist = maxX * maxX, maxY = 0; - c = -a * l1 / (aa - bb); - if (c >= -1 && c <= 1) { - c = (float)Math.Acos(c); - x = a * (float)Math.Cos(c) + l1; - y = b * (float)Math.Sin(c); - d = x * x + y * y; - if (d < minDist) { - minAngle = c; - minDist = d; - minX = x; - minY = y; - } - if (d > maxDist) { - maxAngle = c; - maxDist = d; - maxX = x; - maxY = y; - } - } - if (dd <= (minDist + maxDist) / 2) { - a1 = ta - (float)Math.Atan2(minY * bendDir, minX); - a2 = minAngle * bendDir; - } else { - a1 = ta - (float)Math.Atan2(maxY * bendDir, maxX); - a2 = maxAngle * bendDir; - } - } - break_outer: - float os = (float)Math.Atan2(cy, cx) * s2; - float rotation = parent.arotation; - a1 = (a1 - os) * MathUtils.RadDeg + os1 - rotation; - if (a1 > 180) - a1 -= 360; - else if (a1 < -180) a1 += 360; - parent.UpdateWorldTransform(px, py, rotation + a1 * alpha, parent.scaleX, parent.ascaleY, 0, 0); - rotation = child.arotation; - a2 = ((a2 + os) * MathUtils.RadDeg - child.ashearX) * s2 + os2 - rotation; - if (a2 > 180) - a2 -= 360; - else if (a2 < -180) a2 += 360; - child.UpdateWorldTransform(cx, cy, rotation + a2 * alpha, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY); - } - } + /// 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. + /// A direct descendant of the parent bone. + static public void Apply(Bone parent, Bone child, float targetX, float targetY, int bendDir, float alpha) + { + if (alpha == 0) + { + child.UpdateWorldTransform(); + return; + } + //float px = parent.x, py = parent.y, psx = parent.scaleX, psy = parent.scaleY, csx = child.scaleX; + if (!parent.appliedValid) parent.UpdateAppliedTransform(); + if (!child.appliedValid) child.UpdateAppliedTransform(); + float px = parent.ax, py = parent.ay, psx = parent.ascaleX, psy = parent.ascaleY, csx = child.ascaleX; + int os1, os2, s2; + if (psx < 0) + { + psx = -psx; + os1 = 180; + s2 = -1; + } + else + { + os1 = 0; + s2 = 1; + } + if (psy < 0) + { + psy = -psy; + s2 = -s2; + } + if (csx < 0) + { + csx = -csx; + os2 = 180; + } + else + os2 = 0; + float cx = child.ax, cy, cwx, cwy, a = parent.a, b = parent.b, c = parent.c, d = parent.d; + bool u = Math.Abs(psx - psy) <= 0.0001f; + if (!u) + { + cy = 0; + cwx = a * cx + parent.worldX; + cwy = c * cx + parent.worldY; + } + else + { + cy = child.ay; + cwx = a * cx + b * cy + parent.worldX; + cwy = c * cx + d * cy + parent.worldY; + } + Bone pp = parent.parent; + a = pp.a; + b = pp.b; + c = pp.c; + d = pp.d; + float id = 1 / (a * d - b * c), x = targetX - pp.worldX, y = targetY - pp.worldY; + float tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py; + x = cwx - pp.worldX; + y = cwy - pp.worldY; + float dx = (x * d - y * b) * id - px, dy = (y * a - x * c) * id - py; + float l1 = (float)Math.Sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2; + if (u) + { + l2 *= psx; + float cos = (tx * tx + ty * ty - l1 * l1 - l2 * l2) / (2 * l1 * l2); + if (cos < -1) + cos = -1; + else if (cos > 1) cos = 1; + a2 = (float)Math.Acos(cos) * bendDir; + a = l1 + l2 * cos; + b = l2 * (float)Math.Sin(a2); + a1 = (float)Math.Atan2(ty * a - tx * b, tx * a + ty * b); + } + else + { + a = psx * l2; + b = psy * l2; + float aa = a * a, bb = b * b, dd = tx * tx + ty * ty, ta = (float)Math.Atan2(ty, tx); + c = bb * l1 * l1 + aa * dd - aa * bb; + float c1 = -2 * bb * l1, c2 = bb - aa; + d = c1 * c1 - 4 * c2 * c; + if (d >= 0) + { + float q = (float)Math.Sqrt(d); + if (c1 < 0) q = -q; + q = -(c1 + q) / 2; + float r0 = q / c2, r1 = c / q; + float r = Math.Abs(r0) < Math.Abs(r1) ? r0 : r1; + if (r * r <= dd) + { + y = (float)Math.Sqrt(dd - r * r) * bendDir; + a1 = ta - (float)Math.Atan2(y, r); + a2 = (float)Math.Atan2(y / psy, (r - l1) / psx); + goto break_outer; // break outer; + } + } + float minAngle = MathUtils.PI, minX = l1 - a, minDist = minX * minX, minY = 0; + float maxAngle = 0, maxX = l1 + a, maxDist = maxX * maxX, maxY = 0; + c = -a * l1 / (aa - bb); + if (c >= -1 && c <= 1) + { + c = (float)Math.Acos(c); + x = a * (float)Math.Cos(c) + l1; + y = b * (float)Math.Sin(c); + d = x * x + y * y; + if (d < minDist) + { + minAngle = c; + minDist = d; + minX = x; + minY = y; + } + if (d > maxDist) + { + maxAngle = c; + maxDist = d; + maxX = x; + maxY = y; + } + } + if (dd <= (minDist + maxDist) / 2) + { + a1 = ta - (float)Math.Atan2(minY * bendDir, minX); + a2 = minAngle * bendDir; + } + else + { + a1 = ta - (float)Math.Atan2(maxY * bendDir, maxX); + a2 = maxAngle * bendDir; + } + } + break_outer: + float os = (float)Math.Atan2(cy, cx) * s2; + float rotation = parent.arotation; + a1 = (a1 - os) * MathUtils.RadDeg + os1 - rotation; + if (a1 > 180) + a1 -= 360; + else if (a1 < -180) a1 += 360; + parent.UpdateWorldTransform(px, py, rotation + a1 * alpha, parent.scaleX, parent.ascaleY, 0, 0); + rotation = child.arotation; + a2 = ((a2 + os) * MathUtils.RadDeg - child.ashearX) * s2 + os2 - rotation; + if (a2 > 180) + a2 -= 360; + else if (a2 < -180) a2 += 360; + child.UpdateWorldTransform(cx, cy, rotation + a2 * alpha, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/IkConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/IkConstraintData.cs index 0271bae..a398ae0 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/IkConstraintData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/IkConstraintData.cs @@ -31,52 +31,61 @@ using System; using System.Collections.Generic; -namespace Spine3_6_53 { - /// Stores the setup pose for an IkConstraint. - public class IkConstraintData { - internal string name; - internal int order; - internal List bones = new List(); - internal BoneData target; - internal int bendDirection = 1; - internal float mix = 1; +namespace Spine3_6_53 +{ + /// Stores the setup pose for an IkConstraint. + public class IkConstraintData + { + internal string name; + internal int order; + internal List bones = new List(); + internal BoneData target; + internal int bendDirection = 1; + internal float mix = 1; - /// The IK constraint's name, which is unique within the skeleton. - public string Name { - get { return name; } - } + /// The IK constraint's name, which is unique within the skeleton. + public string Name + { + get { return name; } + } - public int Order { - get { return order; } - set { order = value; } - } + public int Order + { + get { return order; } + set { order = value; } + } - /// The bones that are constrained by this IK Constraint. - public List Bones { - get { return bones; } - } + /// The bones that are constrained by this IK Constraint. + public List Bones + { + get { return bones; } + } - /// The bone that is the IK target. - public BoneData Target { - get { return target; } - set { target = value; } - } + /// The bone that is the IK target. + public BoneData Target + { + get { return target; } + set { target = value; } + } - /// Controls the bend direction of the IK bones, either 1 or -1. - public int BendDirection { - get { return bendDirection; } - set { bendDirection = value; } - } + /// Controls the bend direction of the IK bones, either 1 or -1. + public int BendDirection + { + get { return bendDirection; } + set { bendDirection = value; } + } - public float Mix { get { return mix; } set { mix = value; } } + public float Mix { get { return mix; } set { mix = value; } } - public IkConstraintData (string name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; - } + public IkConstraintData(string name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + } - override public string ToString () { - return name; - } - } + override public string ToString() + { + return name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Json.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Json.cs index e167c41..93507e0 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Json.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Json.cs @@ -29,20 +29,22 @@ *****************************************************************************/ using System; +using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Text; -using System.Collections; -using System.Globalization; -using System.Collections.Generic; -namespace Spine3_6_53 { - public static class Json { - public static object Deserialize (TextReader text) { - var parser = new SharpJson.JsonDecoder(); - parser.parseNumbersAsFloat = true; - return parser.Decode(text.ReadToEnd()); - } - } +namespace Spine3_6_53 +{ + public static class Json + { + public static object Deserialize(TextReader text) + { + var parser = new SharpJson.JsonDecoder(); + parser.parseNumbersAsFloat = true; + return parser.Decode(text.ReadToEnd()); + } + } } /** @@ -76,460 +78,502 @@ public static object Deserialize (TextReader text) { */ namespace SharpJson { - class Lexer - { - public enum Token { - None, - Null, - True, - False, - Colon, - Comma, - String, - Number, - CurlyOpen, - CurlyClose, - SquaredOpen, - SquaredClose, - }; - - public bool hasError { - get { - return !success; - } - } - - public int lineNumber { - get; - private set; - } - - public bool parseNumbersAsFloat { - get; - set; - } - - char[] json; - int index = 0; - bool success = true; - char[] stringBuffer = new char[4096]; - - public Lexer(string text) - { - Reset(); - - json = text.ToCharArray(); - parseNumbersAsFloat = false; - } - - public void Reset() - { - index = 0; - lineNumber = 1; - success = true; - } - - public string ParseString() - { - int idx = 0; - StringBuilder builder = null; - - SkipWhiteSpaces(); - - // " - char c = json[index++]; - - bool failed = false; - bool complete = false; - - while (!complete && !failed) { - if (index == json.Length) - break; - - c = json[index++]; - if (c == '"') { - complete = true; - break; - } else if (c == '\\') { - if (index == json.Length) - break; - - c = json[index++]; - - switch (c) { - case '"': - stringBuffer[idx++] = '"'; - break; - case '\\': - stringBuffer[idx++] = '\\'; - break; - case '/': - stringBuffer[idx++] = '/'; - break; - case 'b': - stringBuffer[idx++] = '\b'; - break; - case'f': - stringBuffer[idx++] = '\f'; - break; - case 'n': - stringBuffer[idx++] = '\n'; - break; - case 'r': - stringBuffer[idx++] = '\r'; - break; - case 't': - stringBuffer[idx++] = '\t'; - break; - case 'u': - int remainingLength = json.Length - index; - if (remainingLength >= 4) { - var hex = new string(json, index, 4); - - // XXX: handle UTF - stringBuffer[idx++] = (char) Convert.ToInt32(hex, 16); - - // skip 4 chars - index += 4; - } else { - failed = true; - } - break; - } - } else { - stringBuffer[idx++] = c; - } - - if (idx >= stringBuffer.Length) { - if (builder == null) - builder = new StringBuilder(); - - builder.Append(stringBuffer, 0, idx); - idx = 0; - } - } - - if (!complete) { - success = false; - return null; - } - - if (builder != null) - return builder.ToString (); - else - return new string (stringBuffer, 0, idx); - } - - string GetNumberString() - { - SkipWhiteSpaces(); - - int lastIndex = GetLastIndexOfNumber(index); - int charLength = (lastIndex - index) + 1; - - var result = new string (json, index, charLength); - - index = lastIndex + 1; - - return result; - } - - public float ParseFloatNumber() - { - float number; - var str = GetNumberString (); - - if (!float.TryParse (str, NumberStyles.Float, CultureInfo.InvariantCulture, out number)) - return 0; - - return number; - } - - public double ParseDoubleNumber() - { - double number; - var str = GetNumberString (); - - if (!double.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out number)) - return 0; - - return number; - } - - int GetLastIndexOfNumber(int index) - { - int lastIndex; - - for (lastIndex = index; lastIndex < json.Length; lastIndex++) { - char ch = json[lastIndex]; - - if ((ch < '0' || ch > '9') && ch != '+' && ch != '-' - && ch != '.' && ch != 'e' && ch != 'E') - break; - } - - return lastIndex - 1; - } - - void SkipWhiteSpaces() - { - for (; index < json.Length; index++) { - char ch = json[index]; - - if (ch == '\n') - lineNumber++; - - if (!char.IsWhiteSpace(json[index])) - break; - } - } - - public Token LookAhead() - { - SkipWhiteSpaces(); - - int savedIndex = index; - return NextToken(json, ref savedIndex); - } - - public Token NextToken() - { - SkipWhiteSpaces(); - return NextToken(json, ref index); - } - - static Token NextToken(char[] json, ref int index) - { - if (index == json.Length) - return Token.None; - - char c = json[index++]; - - switch (c) { - case '{': - return Token.CurlyOpen; - case '}': - return Token.CurlyClose; - case '[': - return Token.SquaredOpen; - case ']': - return Token.SquaredClose; - case ',': - return Token.Comma; - case '"': - return Token.String; - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - case '-': - return Token.Number; - case ':': - return Token.Colon; - } - - index--; - - int remainingLength = json.Length - index; - - // false - if (remainingLength >= 5) { - if (json[index] == 'f' && - json[index + 1] == 'a' && - json[index + 2] == 'l' && - json[index + 3] == 's' && - json[index + 4] == 'e') { - index += 5; - return Token.False; - } - } - - // true - if (remainingLength >= 4) { - if (json[index] == 't' && - json[index + 1] == 'r' && - json[index + 2] == 'u' && - json[index + 3] == 'e') { - index += 4; - return Token.True; - } - } - - // null - if (remainingLength >= 4) { - if (json[index] == 'n' && - json[index + 1] == 'u' && - json[index + 2] == 'l' && - json[index + 3] == 'l') { - index += 4; - return Token.Null; - } - } - - return Token.None; - } - } - - public class JsonDecoder - { - public string errorMessage { - get; - private set; - } - - public bool parseNumbersAsFloat { - get; - set; - } - - Lexer lexer; - - public JsonDecoder() - { - errorMessage = null; - parseNumbersAsFloat = false; - } - - public object Decode(string text) - { - errorMessage = null; - - lexer = new Lexer(text); - lexer.parseNumbersAsFloat = parseNumbersAsFloat; - - return ParseValue(); - } - - public static object DecodeText(string text) - { - var builder = new JsonDecoder(); - return builder.Decode(text); - } - - IDictionary ParseObject() - { - var table = new Dictionary(); - - // { - lexer.NextToken(); - - while (true) { - var token = lexer.LookAhead(); - - switch (token) { - case Lexer.Token.None: - TriggerError("Invalid token"); - return null; - case Lexer.Token.Comma: - lexer.NextToken(); - break; - case Lexer.Token.CurlyClose: - lexer.NextToken(); - return table; - default: - // name - string name = EvalLexer(lexer.ParseString()); - - if (errorMessage != null) - return null; - - // : - token = lexer.NextToken(); - - if (token != Lexer.Token.Colon) { - TriggerError("Invalid token; expected ':'"); - return null; - } - - // value - object value = ParseValue(); - - if (errorMessage != null) - return null; - - table[name] = value; - break; - } - } - - //return null; // Unreachable code - } - - IList ParseArray() - { - var array = new List(); - - // [ - lexer.NextToken(); - - while (true) { - var token = lexer.LookAhead(); - - switch (token) { - case Lexer.Token.None: - TriggerError("Invalid token"); - return null; - case Lexer.Token.Comma: - lexer.NextToken(); - break; - case Lexer.Token.SquaredClose: - lexer.NextToken(); - return array; - default: - object value = ParseValue(); - - if (errorMessage != null) - return null; - - array.Add(value); - break; - } - } - - //return null; // Unreachable code - } - - object ParseValue() - { - switch (lexer.LookAhead()) { - case Lexer.Token.String: - return EvalLexer(lexer.ParseString()); - case Lexer.Token.Number: - if (parseNumbersAsFloat) - return EvalLexer(lexer.ParseFloatNumber()); - else - return EvalLexer(lexer.ParseDoubleNumber()); - case Lexer.Token.CurlyOpen: - return ParseObject(); - case Lexer.Token.SquaredOpen: - return ParseArray(); - case Lexer.Token.True: - lexer.NextToken(); - return true; - case Lexer.Token.False: - lexer.NextToken(); - return false; - case Lexer.Token.Null: - lexer.NextToken(); - return null; - case Lexer.Token.None: - break; - } - - TriggerError("Unable to parse value"); - return null; - } - - void TriggerError(string message) - { - errorMessage = string.Format("Error: '{0}' at line {1}", - message, lexer.lineNumber); - } - - T EvalLexer(T value) - { - if (lexer.hasError) - TriggerError("Lexical error ocurred"); - - return value; - } - } + class Lexer + { + public enum Token + { + None, + Null, + True, + False, + Colon, + Comma, + String, + Number, + CurlyOpen, + CurlyClose, + SquaredOpen, + SquaredClose, + }; + + public bool hasError + { + get + { + return !success; + } + } + + public int lineNumber + { + get; + private set; + } + + public bool parseNumbersAsFloat + { + get; + set; + } + + char[] json; + int index = 0; + bool success = true; + char[] stringBuffer = new char[4096]; + + public Lexer(string text) + { + Reset(); + + json = text.ToCharArray(); + parseNumbersAsFloat = false; + } + + public void Reset() + { + index = 0; + lineNumber = 1; + success = true; + } + + public string ParseString() + { + int idx = 0; + StringBuilder builder = null; + + SkipWhiteSpaces(); + + // " + char c = json[index++]; + + bool failed = false; + bool complete = false; + + while (!complete && !failed) + { + if (index == json.Length) + break; + + c = json[index++]; + if (c == '"') + { + complete = true; + break; + } + else if (c == '\\') + { + if (index == json.Length) + break; + + c = json[index++]; + + switch (c) + { + case '"': + stringBuffer[idx++] = '"'; + break; + case '\\': + stringBuffer[idx++] = '\\'; + break; + case '/': + stringBuffer[idx++] = '/'; + break; + case 'b': + stringBuffer[idx++] = '\b'; + break; + case 'f': + stringBuffer[idx++] = '\f'; + break; + case 'n': + stringBuffer[idx++] = '\n'; + break; + case 'r': + stringBuffer[idx++] = '\r'; + break; + case 't': + stringBuffer[idx++] = '\t'; + break; + case 'u': + int remainingLength = json.Length - index; + if (remainingLength >= 4) + { + var hex = new string(json, index, 4); + + // XXX: handle UTF + stringBuffer[idx++] = (char)Convert.ToInt32(hex, 16); + + // skip 4 chars + index += 4; + } + else + { + failed = true; + } + break; + } + } + else + { + stringBuffer[idx++] = c; + } + + if (idx >= stringBuffer.Length) + { + if (builder == null) + builder = new StringBuilder(); + + builder.Append(stringBuffer, 0, idx); + idx = 0; + } + } + + if (!complete) + { + success = false; + return null; + } + + if (builder != null) + return builder.ToString(); + else + return new string(stringBuffer, 0, idx); + } + + string GetNumberString() + { + SkipWhiteSpaces(); + + int lastIndex = GetLastIndexOfNumber(index); + int charLength = (lastIndex - index) + 1; + + var result = new string(json, index, charLength); + + index = lastIndex + 1; + + return result; + } + + public float ParseFloatNumber() + { + float number; + var str = GetNumberString(); + + if (!float.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out number)) + return 0; + + return number; + } + + public double ParseDoubleNumber() + { + double number; + var str = GetNumberString(); + + if (!double.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out number)) + return 0; + + return number; + } + + int GetLastIndexOfNumber(int index) + { + int lastIndex; + + for (lastIndex = index; lastIndex < json.Length; lastIndex++) + { + char ch = json[lastIndex]; + + if ((ch < '0' || ch > '9') && ch != '+' && ch != '-' + && ch != '.' && ch != 'e' && ch != 'E') + break; + } + + return lastIndex - 1; + } + + void SkipWhiteSpaces() + { + for (; index < json.Length; index++) + { + char ch = json[index]; + + if (ch == '\n') + lineNumber++; + + if (!char.IsWhiteSpace(json[index])) + break; + } + } + + public Token LookAhead() + { + SkipWhiteSpaces(); + + int savedIndex = index; + return NextToken(json, ref savedIndex); + } + + public Token NextToken() + { + SkipWhiteSpaces(); + return NextToken(json, ref index); + } + + static Token NextToken(char[] json, ref int index) + { + if (index == json.Length) + return Token.None; + + char c = json[index++]; + + switch (c) + { + case '{': + return Token.CurlyOpen; + case '}': + return Token.CurlyClose; + case '[': + return Token.SquaredOpen; + case ']': + return Token.SquaredClose; + case ',': + return Token.Comma; + case '"': + return Token.String; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + return Token.Number; + case ':': + return Token.Colon; + } + + index--; + + int remainingLength = json.Length - index; + + // false + if (remainingLength >= 5) + { + if (json[index] == 'f' && + json[index + 1] == 'a' && + json[index + 2] == 'l' && + json[index + 3] == 's' && + json[index + 4] == 'e') + { + index += 5; + return Token.False; + } + } + + // true + if (remainingLength >= 4) + { + if (json[index] == 't' && + json[index + 1] == 'r' && + json[index + 2] == 'u' && + json[index + 3] == 'e') + { + index += 4; + return Token.True; + } + } + + // null + if (remainingLength >= 4) + { + if (json[index] == 'n' && + json[index + 1] == 'u' && + json[index + 2] == 'l' && + json[index + 3] == 'l') + { + index += 4; + return Token.Null; + } + } + + return Token.None; + } + } + + public class JsonDecoder + { + public string errorMessage + { + get; + private set; + } + + public bool parseNumbersAsFloat + { + get; + set; + } + + Lexer lexer; + + public JsonDecoder() + { + errorMessage = null; + parseNumbersAsFloat = false; + } + + public object Decode(string text) + { + errorMessage = null; + + lexer = new Lexer(text); + lexer.parseNumbersAsFloat = parseNumbersAsFloat; + + return ParseValue(); + } + + public static object DecodeText(string text) + { + var builder = new JsonDecoder(); + return builder.Decode(text); + } + + IDictionary ParseObject() + { + var table = new Dictionary(); + + // { + lexer.NextToken(); + + while (true) + { + var token = lexer.LookAhead(); + + switch (token) + { + case Lexer.Token.None: + TriggerError("Invalid token"); + return null; + case Lexer.Token.Comma: + lexer.NextToken(); + break; + case Lexer.Token.CurlyClose: + lexer.NextToken(); + return table; + default: + // name + string name = EvalLexer(lexer.ParseString()); + + if (errorMessage != null) + return null; + + // : + token = lexer.NextToken(); + + if (token != Lexer.Token.Colon) + { + TriggerError("Invalid token; expected ':'"); + return null; + } + + // value + object value = ParseValue(); + + if (errorMessage != null) + return null; + + table[name] = value; + break; + } + } + + //return null; // Unreachable code + } + + IList ParseArray() + { + var array = new List(); + + // [ + lexer.NextToken(); + + while (true) + { + var token = lexer.LookAhead(); + + switch (token) + { + case Lexer.Token.None: + TriggerError("Invalid token"); + return null; + case Lexer.Token.Comma: + lexer.NextToken(); + break; + case Lexer.Token.SquaredClose: + lexer.NextToken(); + return array; + default: + object value = ParseValue(); + + if (errorMessage != null) + return null; + + array.Add(value); + break; + } + } + + //return null; // Unreachable code + } + + object ParseValue() + { + switch (lexer.LookAhead()) + { + case Lexer.Token.String: + return EvalLexer(lexer.ParseString()); + case Lexer.Token.Number: + if (parseNumbersAsFloat) + return EvalLexer(lexer.ParseFloatNumber()); + else + return EvalLexer(lexer.ParseDoubleNumber()); + case Lexer.Token.CurlyOpen: + return ParseObject(); + case Lexer.Token.SquaredOpen: + return ParseArray(); + case Lexer.Token.True: + lexer.NextToken(); + return true; + case Lexer.Token.False: + lexer.NextToken(); + return false; + case Lexer.Token.Null: + lexer.NextToken(); + return null; + case Lexer.Token.None: + break; + } + + TriggerError("Unable to parse value"); + return null; + } + + void TriggerError(string message) + { + errorMessage = string.Format("Error: '{0}' at line {1}", + message, lexer.lineNumber); + } + + T EvalLexer(T value) + { + if (lexer.hasError) + TriggerError("Lexical error ocurred"); + + return value; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/MathUtils.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/MathUtils.cs index d291c93..9cb81b5 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/MathUtils.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/MathUtils.cs @@ -30,71 +30,82 @@ using System; -namespace Spine3_6_53 { - public static class MathUtils { - public const float PI = 3.1415927f; - public const float PI2 = PI * 2; - public const float RadDeg = 180f / PI; - public const float DegRad = PI / 180; +namespace Spine3_6_53 +{ + public static class MathUtils + { + public const float PI = 3.1415927f; + public const float PI2 = PI * 2; + public const float RadDeg = 180f / PI; + public const float DegRad = PI / 180; - const int SIN_BITS = 14; // 16KB. Adjust for accuracy. - const int SIN_MASK = ~(-1 << SIN_BITS); - const int SIN_COUNT = SIN_MASK + 1; - const float RadFull = PI * 2; - const float DegFull = 360; - const float RadToIndex = SIN_COUNT / RadFull; - const float DegToIndex = SIN_COUNT / DegFull; - static float[] sin = new float[SIN_COUNT]; + const int SIN_BITS = 14; // 16KB. Adjust for accuracy. + const int SIN_MASK = ~(-1 << SIN_BITS); + const int SIN_COUNT = SIN_MASK + 1; + const float RadFull = PI * 2; + const float DegFull = 360; + const float RadToIndex = SIN_COUNT / RadFull; + const float DegToIndex = SIN_COUNT / DegFull; + static float[] sin = new float[SIN_COUNT]; - static MathUtils () { - for (int i = 0; i < SIN_COUNT; i++) - sin[i] = (float)Math.Sin((i + 0.5f) / SIN_COUNT * RadFull); - for (int i = 0; i < 360; i += 90) - sin[(int)(i * DegToIndex) & SIN_MASK] = (float)Math.Sin(i * DegRad); - } + static MathUtils() + { + for (int i = 0; i < SIN_COUNT; i++) + sin[i] = (float)Math.Sin((i + 0.5f) / SIN_COUNT * RadFull); + for (int i = 0; i < 360; i += 90) + sin[(int)(i * DegToIndex) & SIN_MASK] = (float)Math.Sin(i * DegRad); + } - /// Returns the sine in radians from a lookup table. - static public float Sin (float radians) { - return sin[(int)(radians * RadToIndex) & SIN_MASK]; - } + /// Returns the sine in radians from a lookup table. + static public float Sin(float radians) + { + return sin[(int)(radians * RadToIndex) & SIN_MASK]; + } - /// Returns the cosine in radians from a lookup table. - static public float Cos (float radians) { - return sin[(int)((radians + PI / 2) * RadToIndex) & SIN_MASK]; - } - - /// Returns the sine in radians from a lookup table. - static public float SinDeg (float degrees) { - return sin[(int)(degrees * DegToIndex) & SIN_MASK]; - } - - /// Returns the cosine in radians from a lookup table. - static public float CosDeg (float degrees) { - return sin[(int)((degrees + 90) * DegToIndex) & SIN_MASK]; - } + /// Returns the cosine in radians from a lookup table. + static public float Cos(float radians) + { + return sin[(int)((radians + PI / 2) * RadToIndex) & SIN_MASK]; + } - /// 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 public float Atan2 (float y, float x) { - if (x == 0f) { - if (y > 0f) return PI / 2; - if (y == 0f) return 0f; - return -PI / 2; - } - float atan, z = y / x; - if (Math.Abs(z) < 1f) { - atan = z / (1f + 0.28f * z * z); - if (x < 0f) return atan + (y < 0f ? -PI : PI); - return atan; - } - atan = PI / 2 - z / (z * z + 0.28f); - return y < 0f ? atan - PI : atan; - } + /// Returns the sine in radians from a lookup table. + static public float SinDeg(float degrees) + { + return sin[(int)(degrees * DegToIndex) & SIN_MASK]; + } - static public float Clamp (float value, float min, float max) { - if (value < min) return min; - if (value > max) return max; - return value; - } - } + /// Returns the cosine in radians from a lookup table. + static public float CosDeg(float degrees) + { + return sin[(int)((degrees + 90) * DegToIndex) & SIN_MASK]; + } + + /// 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 public float Atan2(float y, float x) + { + if (x == 0f) + { + if (y > 0f) return PI / 2; + if (y == 0f) return 0f; + return -PI / 2; + } + float atan, z = y / x; + if (Math.Abs(z) < 1f) + { + atan = z / (1f + 0.28f * z * z); + if (x < 0f) return atan + (y < 0f ? -PI : PI); + return atan; + } + atan = PI / 2 - z / (z * z + 0.28f); + return y < 0f ? atan - PI : atan; + } + + static public float Clamp(float value, float min, float max) + { + if (value < min) return min; + if (value > max) return max; + return value; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/PathConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/PathConstraint.cs index ac2222e..0a79ff2 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/PathConstraint.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/PathConstraint.cs @@ -30,388 +30,446 @@ using System; -namespace Spine3_6_53 { - public class PathConstraint : IConstraint { - const int NONE = -1, BEFORE = -2, AFTER = -3; - const float Epsilon = 0.00001f; +namespace Spine3_6_53 +{ + public class PathConstraint : IConstraint + { + const int NONE = -1, BEFORE = -2, AFTER = -3; + const float Epsilon = 0.00001f; - internal PathConstraintData data; - internal ExposedList bones; - internal Slot target; - internal float position, spacing, rotateMix, translateMix; + internal PathConstraintData data; + internal ExposedList bones; + internal Slot target; + internal float position, spacing, rotateMix, translateMix; - internal ExposedList spaces = new ExposedList(), positions = new ExposedList(); - internal ExposedList world = new ExposedList(), curves = new ExposedList(), lengths = new ExposedList(); - internal float[] segments = new float[10]; + internal ExposedList spaces = new ExposedList(), positions = new ExposedList(); + internal ExposedList world = new ExposedList(), curves = new ExposedList(), lengths = new ExposedList(); + internal float[] segments = new float[10]; - public int Order { get { return data.order; } } - public float Position { get { return position; } set { position = value; } } - public float Spacing { get { return spacing; } set { spacing = value; } } - public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } - public float TranslateMix { get { return translateMix; } set { translateMix = value; } } - public ExposedList Bones { get { return bones; } } - public Slot Target { get { return target; } set { target = value; } } - public PathConstraintData Data { get { return data; } } + public int Order { get { return data.order; } } + public float Position { get { return position; } set { position = value; } } + public float Spacing { get { return spacing; } set { spacing = value; } } + public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } + public float TranslateMix { get { return translateMix; } set { translateMix = value; } } + public ExposedList Bones { get { return bones; } } + public Slot Target { get { return target; } set { target = value; } } + public PathConstraintData Data { get { return data; } } - public PathConstraint (PathConstraintData data, Skeleton skeleton) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.data = data; - bones = new ExposedList(data.Bones.Count); - foreach (BoneData boneData in data.bones) - bones.Add(skeleton.FindBone(boneData.name)); - target = skeleton.FindSlot(data.target.name); - position = data.position; - spacing = data.spacing; - rotateMix = data.rotateMix; - translateMix = data.translateMix; - } + public PathConstraint(PathConstraintData data, Skeleton skeleton) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + bones = new ExposedList(data.Bones.Count); + foreach (BoneData boneData in data.bones) + bones.Add(skeleton.FindBone(boneData.name)); + target = skeleton.FindSlot(data.target.name); + position = data.position; + spacing = data.spacing; + rotateMix = data.rotateMix; + translateMix = data.translateMix; + } - /// Applies the constraint to the constrained bones. - public void Apply () { - Update(); - } - - public void Update () { - PathAttachment attachment = target.Attachment as PathAttachment; - if (attachment == null) return; + /// Applies the constraint to the constrained bones. + public void Apply() + { + Update(); + } - float rotateMix = this.rotateMix, translateMix = this.translateMix; - bool translate = translateMix > 0, rotate = rotateMix > 0; - if (!translate && !rotate) return; + public void Update() + { + PathAttachment attachment = target.Attachment as PathAttachment; + if (attachment == null) return; - PathConstraintData data = this.data; - SpacingMode spacingMode = data.spacingMode; - bool lengthSpacing = spacingMode == SpacingMode.Length; - RotateMode rotateMode = data.rotateMode; - bool tangents = rotateMode == RotateMode.Tangent, scale = rotateMode == RotateMode.ChainScale; - int boneCount = this.bones.Count, spacesCount = tangents ? boneCount : boneCount + 1; - Bone[] bonesItems = this.bones.Items; - ExposedList spaces = this.spaces.Resize(spacesCount), lengths = null; - float spacing = this.spacing; - if (scale || lengthSpacing) { - if (scale) lengths = this.lengths.Resize(boneCount); - for (int i = 0, n = spacesCount - 1; i < n;) { - Bone bone = bonesItems[i]; - float setupLength = bone.data.length; - if (setupLength < PathConstraint.Epsilon) { - if (scale) lengths.Items[i] = 0; - spaces.Items[++i] = 0; - } else { - float x = setupLength * bone.a, y = setupLength * bone.c; - float length = (float)Math.Sqrt(x * x + y * y); - if (scale) lengths.Items[i] = length; - spaces.Items[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length / setupLength; - } - } - } else { - for (int i = 1; i < spacesCount; i++) - spaces.Items[i] = spacing; - } + float rotateMix = this.rotateMix, translateMix = this.translateMix; + bool translate = translateMix > 0, rotate = rotateMix > 0; + if (!translate && !rotate) return; - float[] positions = ComputeWorldPositions(attachment, spacesCount, tangents, - data.positionMode == PositionMode.Percent, spacingMode == SpacingMode.Percent); - float boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation; - bool tip; - if (offsetRotation == 0) { - tip = rotateMode == RotateMode.Chain; - } else { - tip = false; - Bone p = target.bone; - offsetRotation *= p.a * p.d - p.b * p.c > 0 ? MathUtils.DegRad : -MathUtils.DegRad; - } - for (int i = 0, p = 3; i < boneCount; i++, p += 3) { - Bone bone = bonesItems[i]; - bone.worldX += (boneX - bone.worldX) * translateMix; - bone.worldY += (boneY - bone.worldY) * translateMix; - float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY; - if (scale) { - float length = lengths.Items[i]; - if (length >= PathConstraint.Epsilon) { - float s = ((float)Math.Sqrt(dx * dx + dy * dy) / length - 1) * rotateMix + 1; - bone.a *= s; - bone.c *= s; - } - } - boneX = x; - boneY = y; - if (rotate) { - 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.Items[i + 1] < PathConstraint.Epsilon) - r = positions[p + 2]; - else - r = MathUtils.Atan2(dy, dx); - r -= MathUtils.Atan2(c, a); - if (tip) { - cos = MathUtils.Cos(r); - sin = MathUtils.Sin(r); - float length = bone.data.length; - boneX += (length * (cos * a - sin * c) - dx) * rotateMix; - boneY += (length * (sin * a + cos * c) - dy) * rotateMix; - } else { - r += offsetRotation; - } - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) // - r += MathUtils.PI2; - r *= rotateMix; - cos = MathUtils.Cos(r); - sin = MathUtils.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.appliedValid = false; - } - } + PathConstraintData data = this.data; + SpacingMode spacingMode = data.spacingMode; + bool lengthSpacing = spacingMode == SpacingMode.Length; + RotateMode rotateMode = data.rotateMode; + bool tangents = rotateMode == RotateMode.Tangent, scale = rotateMode == RotateMode.ChainScale; + int boneCount = this.bones.Count, spacesCount = tangents ? boneCount : boneCount + 1; + Bone[] bonesItems = this.bones.Items; + ExposedList spaces = this.spaces.Resize(spacesCount), lengths = null; + float spacing = this.spacing; + if (scale || lengthSpacing) + { + if (scale) lengths = this.lengths.Resize(boneCount); + for (int i = 0, n = spacesCount - 1; i < n;) + { + Bone bone = bonesItems[i]; + float setupLength = bone.data.length; + if (setupLength < PathConstraint.Epsilon) + { + if (scale) lengths.Items[i] = 0; + spaces.Items[++i] = 0; + } + else + { + float x = setupLength * bone.a, y = setupLength * bone.c; + float length = (float)Math.Sqrt(x * x + y * y); + if (scale) lengths.Items[i] = length; + spaces.Items[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length / setupLength; + } + } + } + else + { + for (int i = 1; i < spacesCount; i++) + spaces.Items[i] = spacing; + } - float[] ComputeWorldPositions (PathAttachment path, int spacesCount, bool tangents, bool percentPosition, - bool percentSpacing) { + float[] positions = ComputeWorldPositions(attachment, spacesCount, tangents, + data.positionMode == PositionMode.Percent, spacingMode == SpacingMode.Percent); + float boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation; + bool tip; + if (offsetRotation == 0) + { + tip = rotateMode == RotateMode.Chain; + } + else + { + tip = false; + Bone p = target.bone; + offsetRotation *= p.a * p.d - p.b * p.c > 0 ? MathUtils.DegRad : -MathUtils.DegRad; + } + for (int i = 0, p = 3; i < boneCount; i++, p += 3) + { + Bone bone = bonesItems[i]; + bone.worldX += (boneX - bone.worldX) * translateMix; + bone.worldY += (boneY - bone.worldY) * translateMix; + float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY; + if (scale) + { + float length = lengths.Items[i]; + if (length >= PathConstraint.Epsilon) + { + float s = ((float)Math.Sqrt(dx * dx + dy * dy) / length - 1) * rotateMix + 1; + bone.a *= s; + bone.c *= s; + } + } + boneX = x; + boneY = y; + if (rotate) + { + 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.Items[i + 1] < PathConstraint.Epsilon) + r = positions[p + 2]; + else + r = MathUtils.Atan2(dy, dx); + r -= MathUtils.Atan2(c, a); + if (tip) + { + cos = MathUtils.Cos(r); + sin = MathUtils.Sin(r); + float length = bone.data.length; + boneX += (length * (cos * a - sin * c) - dx) * rotateMix; + boneY += (length * (sin * a + cos * c) - dy) * rotateMix; + } + else + { + r += offsetRotation; + } + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) // + r += MathUtils.PI2; + r *= rotateMix; + cos = MathUtils.Cos(r); + sin = MathUtils.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.appliedValid = false; + } + } - Slot target = this.target; - float position = this.position; - float[] spacesItems = this.spaces.Items, output = this.positions.Resize(spacesCount * 3 + 2).Items, world; - bool closed = path.Closed; - int verticesLength = path.WorldVerticesLength, curveCount = verticesLength / 6, prevCurve = NONE; + float[] ComputeWorldPositions(PathAttachment path, int spacesCount, bool tangents, bool percentPosition, + bool percentSpacing) + { - float pathLength; - if (!path.ConstantSpeed) { - float[] lengths = path.Lengths; - curveCount -= closed ? 1 : 2; - pathLength = lengths[curveCount]; - if (percentPosition) position *= pathLength; - if (percentSpacing) { - for (int i = 0; i < spacesCount; i++) - spacesItems[i] *= pathLength; - } - world = this.world.Resize(8).Items; - for (int i = 0, o = 0, curve = 0; i < spacesCount; i++, o += 3) { - float space = spacesItems[i]; - position += space; - float p = position; + Slot target = this.target; + float position = this.position; + float[] spacesItems = this.spaces.Items, output = this.positions.Resize(spacesCount * 3 + 2).Items, world; + bool closed = path.Closed; + int verticesLength = path.WorldVerticesLength, curveCount = verticesLength / 6, prevCurve = NONE; - if (closed) { - 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, output, o); - continue; - } else if (p > pathLength) { - if (prevCurve != AFTER) { - prevCurve = AFTER; - path.ComputeWorldVertices(target, verticesLength - 6, 4, world, 0); - } - AddAfterPosition(p - pathLength, world, 0, output, o); - continue; - } + float pathLength; + if (!path.ConstantSpeed) + { + float[] lengths = path.Lengths; + curveCount -= closed ? 1 : 2; + pathLength = lengths[curveCount]; + if (percentPosition) position *= pathLength; + if (percentSpacing) + { + for (int i = 0; i < spacesCount; i++) + spacesItems[i] *= pathLength; + } + world = this.world.Resize(8).Items; + for (int i = 0, o = 0, curve = 0; i < spacesCount; i++, o += 3) + { + float space = spacesItems[i]; + position += space; + float p = position; - // 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], output, o, - tangents || (i > 0 && space < PathConstraint.Epsilon)); - } - return output; - } + if (closed) + { + 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, output, o); + continue; + } + else if (p > pathLength) + { + if (prevCurve != AFTER) + { + prevCurve = AFTER; + path.ComputeWorldVertices(target, verticesLength - 6, 4, world, 0); + } + AddAfterPosition(p - pathLength, world, 0, output, o); + continue; + } - // World vertices. - if (closed) { - verticesLength += 2; - world = this.world.Resize(verticesLength).Items; - 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 = this.world.Resize(verticesLength).Items; - path.ComputeWorldVertices(target, 2, verticesLength, world, 0); - } + // 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], output, o, + tangents || (i > 0 && space < PathConstraint.Epsilon)); + } + return output; + } - // Curve lengths. - float[] curves = this.curves.Resize(curveCount).Items; - 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 += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - dfx += ddfx; - dfy += ddfy; - ddfx += dddfx; - ddfy += dddfy; - pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - dfx += ddfx; - dfy += ddfy; - pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - dfx += ddfx + dddfx; - dfy += ddfy + dddfy; - pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - curves[i] = pathLength; - x1 = x2; - y1 = y2; - } - if (percentPosition) position *= pathLength; - if (percentSpacing) { - for (int i = 0; i < spacesCount; i++) - spacesItems[i] *= pathLength; - } + // World vertices. + if (closed) + { + verticesLength += 2; + world = this.world.Resize(verticesLength).Items; + 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 = this.world.Resize(verticesLength).Items; + path.ComputeWorldVertices(target, 2, verticesLength, world, 0); + } - float[] segments = this.segments; - float curveLength = 0; - for (int i = 0, o = 0, curve = 0, segment = 0; i < spacesCount; i++, o += 3) { - float space = spacesItems[i]; - position += space; - float p = position; + // Curve lengths. + float[] curves = this.curves.Resize(curveCount).Items; + 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 += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx; + dfy += ddfy; + pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx + dddfx; + dfy += ddfy + dddfy; + pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + curves[i] = pathLength; + x1 = x2; + y1 = y2; + } + if (percentPosition) position *= pathLength; + if (percentSpacing) + { + for (int i = 0; i < spacesCount; i++) + spacesItems[i] *= pathLength; + } - if (closed) { - p %= pathLength; - if (p < 0) p += pathLength; - curve = 0; - } else if (p < 0) { - AddBeforePosition(p, world, 0, output, o); - continue; - } else if (p > pathLength) { - AddAfterPosition(p - pathLength, world, verticesLength - 4, output, o); - continue; - } + float[] segments = this.segments; + float curveLength = 0; + for (int i = 0, o = 0, curve = 0, segment = 0; i < spacesCount; i++, o += 3) + { + float space = spacesItems[i]; + position += space; + float p = position; - // 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; - } + if (closed) + { + p %= pathLength; + if (p < 0) p += pathLength; + curve = 0; + } + else if (p < 0) + { + AddBeforePosition(p, world, 0, output, o); + continue; + } + else if (p > pathLength) + { + AddAfterPosition(p - pathLength, world, verticesLength - 4, output, o); + continue; + } - // 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 = (float)Math.Sqrt(dfx * dfx + dfy * dfy); - segments[0] = curveLength; - for (ii = 1; ii < 8; ii++) { - dfx += ddfx; - dfy += ddfy; - ddfx += dddfx; - ddfy += dddfy; - curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - segments[ii] = curveLength; - } - dfx += ddfx; - dfy += ddfy; - curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - segments[8] = curveLength; - dfx += ddfx + dddfx; - dfy += ddfy + dddfy; - curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - segments[9] = curveLength; - segment = 0; - } + // 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; + } - // 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, output, o, tangents || (i > 0 && space < PathConstraint.Epsilon)); - } - return output; - } + // 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 = (float)Math.Sqrt(dfx * dfx + dfy * dfy); + segments[0] = curveLength; + for (ii = 1; ii < 8; ii++) + { + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + segments[ii] = curveLength; + } + dfx += ddfx; + dfy += ddfy; + curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + segments[8] = curveLength; + dfx += ddfx + dddfx; + dfy += ddfy + dddfy; + curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + segments[9] = curveLength; + segment = 0; + } - static void AddBeforePosition (float p, float[] temp, int i, float[] output, int o) { - float x1 = temp[i], y1 = temp[i + 1], dx = temp[i + 2] - x1, dy = temp[i + 3] - y1, r = MathUtils.Atan2(dy, dx); - output[o] = x1 + p * MathUtils.Cos(r); - output[o + 1] = y1 + p * MathUtils.Sin(r); - output[o + 2] = r; - } + // 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, output, o, tangents || (i > 0 && space < PathConstraint.Epsilon)); + } + return output; + } - static void AddAfterPosition (float p, float[] temp, int i, float[] output, int o) { - float x1 = temp[i + 2], y1 = temp[i + 3], dx = x1 - temp[i], dy = y1 - temp[i + 1], r = MathUtils.Atan2(dy, dx); - output[o] = x1 + p * MathUtils.Cos(r); - output[o + 1] = y1 + p * MathUtils.Sin(r); - output[o + 2] = r; - } + static void AddBeforePosition(float p, float[] temp, int i, float[] output, int o) + { + float x1 = temp[i], y1 = temp[i + 1], dx = temp[i + 2] - x1, dy = temp[i + 3] - y1, r = MathUtils.Atan2(dy, dx); + output[o] = x1 + p * MathUtils.Cos(r); + output[o + 1] = y1 + p * MathUtils.Sin(r); + output[o + 2] = r; + } - static void AddCurvePosition (float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2, - float[] output, int o, bool tangents) { - if (p < PathConstraint.Epsilon || float.IsNaN(p)) p = PathConstraint.Epsilon; - 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) - output[o + 2] = (float)Math.Atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt)); - } - } + static void AddAfterPosition(float p, float[] temp, int i, float[] output, int o) + { + float x1 = temp[i + 2], y1 = temp[i + 3], dx = x1 - temp[i], dy = y1 - temp[i + 1], r = MathUtils.Atan2(dy, dx); + output[o] = x1 + p * MathUtils.Cos(r); + output[o + 1] = y1 + p * MathUtils.Sin(r); + output[o + 2] = r; + } + + static void AddCurvePosition(float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2, + float[] output, int o, bool tangents) + { + if (p < PathConstraint.Epsilon || float.IsNaN(p)) p = PathConstraint.Epsilon; + 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) + output[o + 2] = (float)Math.Atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt)); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/PathConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/PathConstraintData.cs index 839545f..c48ec34 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/PathConstraintData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/PathConstraintData.cs @@ -30,50 +30,57 @@ using System; -namespace Spine3_6_53 { - public class PathConstraintData { - internal string name; - internal int order; - internal ExposedList bones = new ExposedList(); - internal SlotData target; - internal PositionMode positionMode; - internal SpacingMode spacingMode; - internal RotateMode rotateMode; - internal float offsetRotation; - internal float position, spacing, rotateMix, translateMix; +namespace Spine3_6_53 +{ + public class PathConstraintData + { + internal string name; + internal int order; + internal ExposedList bones = new ExposedList(); + internal SlotData target; + internal PositionMode positionMode; + internal SpacingMode spacingMode; + internal RotateMode rotateMode; + internal float offsetRotation; + internal float position, spacing, rotateMix, translateMix; - public string Name { get { return name; } } - public int Order { get { return order; } set { order = value; } } - public ExposedList Bones { get { return bones; } } - public SlotData Target { get { return target; } set { target = value; } } - public PositionMode PositionMode { get { return positionMode; } set { positionMode = value; } } - public SpacingMode SpacingMode { get { return spacingMode; } set { spacingMode = value; } } - public RotateMode RotateMode { get { return rotateMode; } set { rotateMode = value; } } - public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } - public float Position { get { return position; } set { position = value; } } - public float Spacing { get { return spacing; } set { spacing = value; } } - public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } - public float TranslateMix { get { return translateMix; } set { translateMix = value; } } + public string Name { get { return name; } } + public int Order { get { return order; } set { order = value; } } + public ExposedList Bones { get { return bones; } } + public SlotData Target { get { return target; } set { target = value; } } + public PositionMode PositionMode { get { return positionMode; } set { positionMode = value; } } + public SpacingMode SpacingMode { get { return spacingMode; } set { spacingMode = value; } } + public RotateMode RotateMode { get { return rotateMode; } set { rotateMode = value; } } + public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } + public float Position { get { return position; } set { position = value; } } + public float Spacing { get { return spacing; } set { spacing = value; } } + public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } + public float TranslateMix { get { return translateMix; } set { translateMix = value; } } - public PathConstraintData (String name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; - } + public PathConstraintData(String name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + } - public override string ToString () { - return name; - } - } - - public enum PositionMode { - Fixed, Percent - } + public override string ToString() + { + return name; + } + } - public enum SpacingMode { - Length, Fixed, Percent - } + public enum PositionMode + { + Fixed, Percent + } - public enum RotateMode { - Tangent, Chain, ChainScale - } + public enum SpacingMode + { + Length, Fixed, Percent + } + + public enum RotateMode + { + Tangent, Chain, ChainScale + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Skeleton.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Skeleton.cs index 0383e99..93dc114 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Skeleton.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Skeleton.cs @@ -29,513 +29,586 @@ *****************************************************************************/ using System; -using System.Collections.Generic; - -namespace Spine3_6_53 { - public class Skeleton { - internal SkeletonData data; - internal ExposedList bones; - internal ExposedList slots; - internal ExposedList drawOrder; - internal ExposedList ikConstraints; - internal ExposedList transformConstraints; - internal ExposedList pathConstraints; - internal ExposedList updateCache = new ExposedList(); - internal ExposedList updateCacheReset = new ExposedList(); - internal Skin skin; - internal float r = 1, g = 1, b = 1, a = 1; - internal float time; - internal bool flipX, flipY; - internal float x, y; - - public SkeletonData Data { get { return data; } } - public ExposedList Bones { get { return bones; } } - public ExposedList UpdateCacheList { get { return updateCache; } } - public ExposedList Slots { get { return slots; } } - public ExposedList DrawOrder { get { return drawOrder; } } - public ExposedList IkConstraints { get { return ikConstraints; } } - public ExposedList PathConstraints { get { return pathConstraints; } } - public ExposedList TransformConstraints { get { return transformConstraints; } } - public Skin Skin { get { return skin; } set { skin = value; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } - public float Time { get { return time; } set { time = value; } } - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public bool FlipX { get { return flipX; } set { flipX = value; } } - public bool FlipY { get { return flipY; } set { flipY = value; } } - - public Bone RootBone { - get { return bones.Count == 0 ? null : bones.Items[0]; } - } - - public Skeleton (SkeletonData data) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - this.data = data; - - bones = new ExposedList(data.bones.Count); - foreach (BoneData boneData in data.bones) { - Bone bone; - if (boneData.parent == null) { - bone = new Bone(boneData, this, null); - } else { - Bone parent = bones.Items[boneData.parent.index]; - bone = new Bone(boneData, this, parent); - parent.children.Add(bone); - } - bones.Add(bone); - } - - slots = new ExposedList(data.slots.Count); - drawOrder = new ExposedList(data.slots.Count); - foreach (SlotData slotData in data.slots) { - Bone bone = bones.Items[slotData.boneData.index]; - Slot slot = new Slot(slotData, bone); - slots.Add(slot); - drawOrder.Add(slot); - } - - ikConstraints = new ExposedList(data.ikConstraints.Count); - foreach (IkConstraintData ikConstraintData in data.ikConstraints) - ikConstraints.Add(new IkConstraint(ikConstraintData, this)); - - transformConstraints = new ExposedList(data.transformConstraints.Count); - foreach (TransformConstraintData transformConstraintData in data.transformConstraints) - transformConstraints.Add(new TransformConstraint(transformConstraintData, this)); - - pathConstraints = new ExposedList (data.pathConstraints.Count); - foreach (PathConstraintData pathConstraintData in data.pathConstraints) - pathConstraints.Add(new PathConstraint(pathConstraintData, this)); - - UpdateCache(); - UpdateWorldTransform(); - } - - /// Caches information about bones and constraints. Must be called if bones, constraints or weighted path attachments are added - /// or removed. - public void UpdateCache () { - ExposedList updateCache = this.updateCache; - updateCache.Clear(); - this.updateCacheReset.Clear(); - - ExposedList bones = this.bones; - for (int i = 0, n = bones.Count; i < n; i++) - bones.Items[i].sorted = false; - - ExposedList ikConstraints = this.ikConstraints; - var transformConstraints = this.transformConstraints; - var pathConstraints = this.pathConstraints; - int ikCount = IkConstraints.Count, transformCount = transformConstraints.Count, pathCount = pathConstraints.Count; - int constraintCount = ikCount + transformCount + pathCount; - //outer: - for (int i = 0; i < constraintCount; i++) { - for (int ii = 0; ii < ikCount; ii++) { - IkConstraint constraint = ikConstraints.Items[ii]; - if (constraint.data.order == i) { - SortIkConstraint(constraint); - goto continue_outer; //continue outer; - } - } - for (int ii = 0; ii < transformCount; ii++) { - TransformConstraint constraint = transformConstraints.Items[ii]; - if (constraint.data.order == i) { - SortTransformConstraint(constraint); - goto continue_outer; //continue outer; - } - } - for (int ii = 0; ii < pathCount; ii++) { - PathConstraint constraint = pathConstraints.Items[ii]; - if (constraint.data.order == i) { - SortPathConstraint(constraint); - goto continue_outer; //continue outer; - } - } - continue_outer: {} - } - - for (int i = 0, n = bones.Count; i < n; i++) - SortBone(bones.Items[i]); - } - - private void SortIkConstraint (IkConstraint constraint) { - Bone target = constraint.target; - SortBone(target); - - var constrained = constraint.bones; - Bone parent = constrained.Items[0]; - SortBone(parent); - - if (constrained.Count > 1) { - Bone child = constrained.Items[constrained.Count - 1]; - if (!updateCache.Contains(child)) - updateCacheReset.Add(child); - } - - updateCache.Add(constraint); - - SortReset(parent.children); - constrained.Items[constrained.Count - 1].sorted = true; - } - - private void SortPathConstraint (PathConstraint constraint) { - Slot slot = constraint.target; - int slotIndex = slot.data.index; - Bone slotBone = slot.bone; - if (skin != null) SortPathConstraintAttachment(skin, slotIndex, slotBone); - if (data.defaultSkin != null && data.defaultSkin != skin) - SortPathConstraintAttachment(data.defaultSkin, slotIndex, slotBone); - for (int ii = 0, nn = data.skins.Count; ii < nn; ii++) - SortPathConstraintAttachment(data.skins.Items[ii], slotIndex, slotBone); - - Attachment attachment = slot.attachment; - if (attachment is PathAttachment) SortPathConstraintAttachment(attachment, slotBone); - - var constrained = constraint.bones; - int boneCount = constrained.Count; - for (int i = 0; i < boneCount; i++) - SortBone(constrained.Items[i]); - - updateCache.Add(constraint); - - for (int i = 0; i < boneCount; i++) - SortReset(constrained.Items[i].children); - for (int i = 0; i < boneCount; i++) - constrained.Items[i].sorted = true; - } - - private void SortTransformConstraint (TransformConstraint constraint) { - SortBone(constraint.target); - - var constrained = constraint.bones; - int boneCount = constrained.Count; - if (constraint.data.local) { - for (int i = 0; i < boneCount; i++) { - Bone child = constrained.Items[i]; - SortBone(child.parent); - if (!updateCache.Contains(child)) updateCacheReset.Add(child); - } - } else { - for (int i = 0; i < boneCount; i++) - SortBone(constrained.Items[i]); - } - - updateCache.Add(constraint); - - for (int i = 0; i < boneCount; i++) - SortReset(constrained.Items[i].children); - for (int i = 0; i < boneCount; i++) - constrained.Items[i].sorted = true; - } - - private void SortPathConstraintAttachment (Skin skin, int slotIndex, Bone slotBone) { - foreach (var entry in skin.Attachments) - if (entry.Key.slotIndex == slotIndex) SortPathConstraintAttachment(entry.Value, slotBone); - } - - private void SortPathConstraintAttachment (Attachment attachment, Bone slotBone) { - if (!(attachment is PathAttachment)) return; - int[] pathBones = ((PathAttachment)attachment).bones; - if (pathBones == null) - SortBone(slotBone); - else { - var bones = this.bones; - for (int i = 0, n = pathBones.Length; i < n;) { - int nn = pathBones[i++]; - nn += i; - while (i < nn) - SortBone(bones.Items[pathBones[i++]]); - } - } - } - - private void SortBone (Bone bone) { - if (bone.sorted) return; - Bone parent = bone.parent; - if (parent != null) SortBone(parent); - bone.sorted = true; - updateCache.Add(bone); - } - - private static void SortReset (ExposedList bones) { - var bonesItems = bones.Items; - for (int i = 0, n = bones.Count; i < n; i++) { - Bone bone = bonesItems[i]; - if (bone.sorted) SortReset(bone.children); - bone.sorted = false; - } - } - - /// Updates the world transform for each bone and applies constraints. - public void UpdateWorldTransform () { - var updateCacheReset = this.updateCacheReset; - var updateCacheResetItems = updateCacheReset.Items; - for (int i = 0, n = updateCacheReset.Count; i < n; i++) { - Bone bone = updateCacheResetItems[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; - bone.appliedValid = true; - } - var updateItems = this.updateCache.Items; - for (int i = 0, n = updateCache.Count; i < n; i++) - updateItems[i].Update(); - } - - /// Sets the bones, constraints, and slots to their setup pose values. - public void SetToSetupPose () { - SetBonesToSetupPose(); - SetSlotsToSetupPose(); - } - - /// Sets the bones and constraints to their setup pose values. - public void SetBonesToSetupPose () { - var bonesItems = this.bones.Items; - for (int i = 0, n = bones.Count; i < n; i++) - bonesItems[i].SetToSetupPose(); - - var ikConstraintsItems = this.ikConstraints.Items; - for (int i = 0, n = ikConstraints.Count; i < n; i++) { - IkConstraint constraint = ikConstraintsItems[i]; - constraint.bendDirection = constraint.data.bendDirection; - constraint.mix = constraint.data.mix; - } - - var transformConstraintsItems = this.transformConstraints.Items; - for (int i = 0, n = transformConstraints.Count; i < n; i++) { - TransformConstraint constraint = transformConstraintsItems[i]; - TransformConstraintData constraintData = constraint.data; - constraint.rotateMix = constraintData.rotateMix; - constraint.translateMix = constraintData.translateMix; - constraint.scaleMix = constraintData.scaleMix; - constraint.shearMix = constraintData.shearMix; - } - - var pathConstraintItems = this.pathConstraints.Items; - for (int i = 0, n = pathConstraints.Count; i < n; i++) { - PathConstraint constraint = pathConstraintItems[i]; - PathConstraintData constraintData = constraint.data; - constraint.position = constraintData.position; - constraint.spacing = constraintData.spacing; - constraint.rotateMix = constraintData.rotateMix; - constraint.translateMix = constraintData.translateMix; - } - } - - public void SetSlotsToSetupPose () { - var slots = this.slots; - var slotsItems = slots.Items; - drawOrder.Clear(); - for (int i = 0, n = slots.Count; i < n; i++) - drawOrder.Add(slotsItems[i]); - - for (int i = 0, n = slots.Count; i < n; i++) - slotsItems[i].SetToSetupPose(); - } - - /// May be null. - public Bone FindBone (string boneName) { - if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); - var bones = this.bones; - var bonesItems = bones.Items; - for (int i = 0, n = bones.Count; i < n; i++) { - Bone bone = bonesItems[i]; - if (bone.data.name == boneName) return bone; - } - return null; - } - - /// -1 if the bone was not found. - public int FindBoneIndex (string boneName) { - if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); - var bones = this.bones; - var bonesItems = bones.Items; - for (int i = 0, n = bones.Count; i < n; i++) - if (bonesItems[i].data.name == boneName) return i; - return -1; - } - - /// May be null. - public Slot FindSlot (string slotName) { - if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); - var slots = this.slots; - var slotsItems = slots.Items; - for (int i = 0, n = slots.Count; i < n; i++) { - Slot slot = slotsItems[i]; - if (slot.data.name == slotName) return slot; - } - return null; - } - - /// -1 if the bone was not found. - public int FindSlotIndex (string slotName) { - if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); - var slots = this.slots; - var slotsItems = slots.Items; - for (int i = 0, n = slots.Count; i < n; i++) - if (slotsItems[i].data.name.Equals(slotName)) return i; - return -1; - } - - /// Sets a skin by name (see SetSkin). - public void SetSkin (string skinName) { - Skin foundSkin = data.FindSkin(skinName); - if (foundSkin == null) throw new ArgumentException("Skin not found: " + skinName, "skinName"); - SetSkin(foundSkin); - } - - /// - /// 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 - /// . - /// Also, often 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. - /// - /// May be null. - public void SetSkin (Skin newSkin) { - if (newSkin != null) { - if (skin != null) - newSkin.AttachAll(this, skin); - else { - ExposedList slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) { - Slot slot = slots.Items[i]; - string name = slot.data.attachmentName; - if (name != null) { - Attachment attachment = newSkin.GetAttachment(i, name); - if (attachment != null) slot.Attachment = attachment; - } - } - } - } - skin = newSkin; - } - - /// May be null. - public Attachment GetAttachment (string slotName, string attachmentName) { - return GetAttachment(data.FindSlotIndex(slotName), attachmentName); - } - - /// May be null. - public Attachment GetAttachment (int slotIndex, string attachmentName) { - if (attachmentName == null) throw new ArgumentNullException("attachmentName", "attachmentName cannot be null."); - if (skin != null) { - Attachment attachment = skin.GetAttachment(slotIndex, attachmentName); - if (attachment != null) return attachment; - } - return data.defaultSkin != null ? data.defaultSkin.GetAttachment(slotIndex, attachmentName) : null; - } - - /// May be null. - public void SetAttachment (string slotName, string attachmentName) { - if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); - ExposedList slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) { - Slot slot = slots.Items[i]; - if (slot.data.name == slotName) { - Attachment attachment = null; - if (attachmentName != null) { - attachment = GetAttachment(i, attachmentName); - if (attachment == null) throw new Exception("Attachment not found: " + attachmentName + ", for slot: " + slotName); - } - slot.Attachment = attachment; - return; - } - } - throw new Exception("Slot not found: " + slotName); - } - - /// May be null. - public IkConstraint FindIkConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - ExposedList ikConstraints = this.ikConstraints; - for (int i = 0, n = ikConstraints.Count; i < n; i++) { - IkConstraint ikConstraint = ikConstraints.Items[i]; - if (ikConstraint.data.name == constraintName) return ikConstraint; - } - return null; - } - - /// May be null. - public TransformConstraint FindTransformConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - ExposedList transformConstraints = this.transformConstraints; - for (int i = 0, n = transformConstraints.Count; i < n; i++) { - TransformConstraint transformConstraint = transformConstraints.Items[i]; - if (transformConstraint.data.name == constraintName) return transformConstraint; - } - return null; - } - - /// May be null. - public PathConstraint FindPathConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - ExposedList pathConstraints = this.pathConstraints; - for (int i = 0, n = pathConstraints.Count; i < n; i++) { - PathConstraint constraint = pathConstraints.Items[i]; - if (constraint.data.name.Equals(constraintName)) return constraint; - } - return null; - } - - public void Update (float delta) { - time += delta; - } - - /// Returns the axis aligned bounding box (AABB) of the region and mesh attachments for the current pose. - /// The horizontal distance between the skeleton origin and the left side of the AABB. - /// The vertical distance between the skeleton origin and the bottom side of the AABB. - /// The width of the AABB - /// The height of the AABB. - /// Reference to hold a float[]. May be a null reference. This method will assign it a new float[] with the appropriate size as needed. - public void GetBounds (out float x, out float y, out float width, out float height, ref float[] vertexBuffer) { - float[] temp = vertexBuffer; - temp = temp ?? new float[8]; - var drawOrderItems = this.drawOrder.Items; - float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; - for (int i = 0, n = this.drawOrder.Count; i < n; i++) { - Slot slot = drawOrderItems[i]; - int verticesLength = 0; - float[] vertices = null; - Attachment attachment = slot.attachment; - var regionAttachment = attachment as RegionAttachment; - if (regionAttachment != null) { - verticesLength = 8; - vertices = temp; - if (vertices.Length < 8) vertices = temp = new float[8]; - regionAttachment.ComputeWorldVertices(slot.bone, temp, 0); - } else { - var meshAttachment = attachment as MeshAttachment; - if (meshAttachment != null) { - MeshAttachment mesh = meshAttachment; - verticesLength = mesh.WorldVerticesLength; - vertices = temp; - if (vertices.Length < verticesLength) vertices = temp = new float[verticesLength]; - mesh.ComputeWorldVertices(slot, 0, verticesLength, temp, 0); - } - } - - if (vertices != null) { - for (int ii = 0; ii < verticesLength; ii += 2) { - float vx = vertices[ii], vy = vertices[ii + 1]; - minX = Math.Min(minX, vx); - minY = Math.Min(minY, vy); - maxX = Math.Max(maxX, vx); - maxY = Math.Max(maxY, vy); - } - } - } - x = minX; - y = minY; - width = maxX - minX; - height = maxY - minY; - vertexBuffer = temp; - } - } + +namespace Spine3_6_53 +{ + public class Skeleton + { + internal SkeletonData data; + internal ExposedList bones; + internal ExposedList slots; + internal ExposedList drawOrder; + internal ExposedList ikConstraints; + internal ExposedList transformConstraints; + internal ExposedList pathConstraints; + internal ExposedList updateCache = new ExposedList(); + internal ExposedList updateCacheReset = new ExposedList(); + internal Skin skin; + internal float r = 1, g = 1, b = 1, a = 1; + internal float time; + internal bool flipX, flipY; + internal float x, y; + + public SkeletonData Data { get { return data; } } + public ExposedList Bones { get { return bones; } } + public ExposedList UpdateCacheList { get { return updateCache; } } + public ExposedList Slots { get { return slots; } } + public ExposedList DrawOrder { get { return drawOrder; } } + public ExposedList IkConstraints { get { return ikConstraints; } } + public ExposedList PathConstraints { get { return pathConstraints; } } + public ExposedList TransformConstraints { get { return transformConstraints; } } + public Skin Skin { get { return skin; } set { skin = value; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } + public float Time { get { return time; } set { time = value; } } + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public bool FlipX { get { return flipX; } set { flipX = value; } } + public bool FlipY { get { return flipY; } set { flipY = value; } } + + public Bone RootBone + { + get { return bones.Count == 0 ? null : bones.Items[0]; } + } + + public Skeleton(SkeletonData data) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + this.data = data; + + bones = new ExposedList(data.bones.Count); + foreach (BoneData boneData in data.bones) + { + Bone bone; + if (boneData.parent == null) + { + bone = new Bone(boneData, this, null); + } + else + { + Bone parent = bones.Items[boneData.parent.index]; + bone = new Bone(boneData, this, parent); + parent.children.Add(bone); + } + bones.Add(bone); + } + + slots = new ExposedList(data.slots.Count); + drawOrder = new ExposedList(data.slots.Count); + foreach (SlotData slotData in data.slots) + { + Bone bone = bones.Items[slotData.boneData.index]; + Slot slot = new Slot(slotData, bone); + slots.Add(slot); + drawOrder.Add(slot); + } + + ikConstraints = new ExposedList(data.ikConstraints.Count); + foreach (IkConstraintData ikConstraintData in data.ikConstraints) + ikConstraints.Add(new IkConstraint(ikConstraintData, this)); + + transformConstraints = new ExposedList(data.transformConstraints.Count); + foreach (TransformConstraintData transformConstraintData in data.transformConstraints) + transformConstraints.Add(new TransformConstraint(transformConstraintData, this)); + + pathConstraints = new ExposedList(data.pathConstraints.Count); + foreach (PathConstraintData pathConstraintData in data.pathConstraints) + pathConstraints.Add(new PathConstraint(pathConstraintData, this)); + + UpdateCache(); + UpdateWorldTransform(); + } + + /// Caches information about bones and constraints. Must be called if bones, constraints or weighted path attachments are added + /// or removed. + public void UpdateCache() + { + ExposedList updateCache = this.updateCache; + updateCache.Clear(); + this.updateCacheReset.Clear(); + + ExposedList bones = this.bones; + for (int i = 0, n = bones.Count; i < n; i++) + bones.Items[i].sorted = false; + + ExposedList ikConstraints = this.ikConstraints; + var transformConstraints = this.transformConstraints; + var pathConstraints = this.pathConstraints; + int ikCount = IkConstraints.Count, transformCount = transformConstraints.Count, pathCount = pathConstraints.Count; + int constraintCount = ikCount + transformCount + pathCount; + //outer: + for (int i = 0; i < constraintCount; i++) + { + for (int ii = 0; ii < ikCount; ii++) + { + IkConstraint constraint = ikConstraints.Items[ii]; + if (constraint.data.order == i) + { + SortIkConstraint(constraint); + goto continue_outer; //continue outer; + } + } + for (int ii = 0; ii < transformCount; ii++) + { + TransformConstraint constraint = transformConstraints.Items[ii]; + if (constraint.data.order == i) + { + SortTransformConstraint(constraint); + goto continue_outer; //continue outer; + } + } + for (int ii = 0; ii < pathCount; ii++) + { + PathConstraint constraint = pathConstraints.Items[ii]; + if (constraint.data.order == i) + { + SortPathConstraint(constraint); + goto continue_outer; //continue outer; + } + } + continue_outer: { } + } + + for (int i = 0, n = bones.Count; i < n; i++) + SortBone(bones.Items[i]); + } + + private void SortIkConstraint(IkConstraint constraint) + { + Bone target = constraint.target; + SortBone(target); + + var constrained = constraint.bones; + Bone parent = constrained.Items[0]; + SortBone(parent); + + if (constrained.Count > 1) + { + Bone child = constrained.Items[constrained.Count - 1]; + if (!updateCache.Contains(child)) + updateCacheReset.Add(child); + } + + updateCache.Add(constraint); + + SortReset(parent.children); + constrained.Items[constrained.Count - 1].sorted = true; + } + + private void SortPathConstraint(PathConstraint constraint) + { + Slot slot = constraint.target; + int slotIndex = slot.data.index; + Bone slotBone = slot.bone; + if (skin != null) SortPathConstraintAttachment(skin, slotIndex, slotBone); + if (data.defaultSkin != null && data.defaultSkin != skin) + SortPathConstraintAttachment(data.defaultSkin, slotIndex, slotBone); + for (int ii = 0, nn = data.skins.Count; ii < nn; ii++) + SortPathConstraintAttachment(data.skins.Items[ii], slotIndex, slotBone); + + Attachment attachment = slot.attachment; + if (attachment is PathAttachment) SortPathConstraintAttachment(attachment, slotBone); + + var constrained = constraint.bones; + int boneCount = constrained.Count; + for (int i = 0; i < boneCount; i++) + SortBone(constrained.Items[i]); + + updateCache.Add(constraint); + + for (int i = 0; i < boneCount; i++) + SortReset(constrained.Items[i].children); + for (int i = 0; i < boneCount; i++) + constrained.Items[i].sorted = true; + } + + private void SortTransformConstraint(TransformConstraint constraint) + { + SortBone(constraint.target); + + var constrained = constraint.bones; + int boneCount = constrained.Count; + if (constraint.data.local) + { + for (int i = 0; i < boneCount; i++) + { + Bone child = constrained.Items[i]; + SortBone(child.parent); + if (!updateCache.Contains(child)) updateCacheReset.Add(child); + } + } + else + { + for (int i = 0; i < boneCount; i++) + SortBone(constrained.Items[i]); + } + + updateCache.Add(constraint); + + for (int i = 0; i < boneCount; i++) + SortReset(constrained.Items[i].children); + for (int i = 0; i < boneCount; i++) + constrained.Items[i].sorted = true; + } + + private void SortPathConstraintAttachment(Skin skin, int slotIndex, Bone slotBone) + { + foreach (var entry in skin.Attachments) + if (entry.Key.slotIndex == slotIndex) SortPathConstraintAttachment(entry.Value, slotBone); + } + + private void SortPathConstraintAttachment(Attachment attachment, Bone slotBone) + { + if (!(attachment is PathAttachment)) return; + int[] pathBones = ((PathAttachment)attachment).bones; + if (pathBones == null) + SortBone(slotBone); + else + { + var bones = this.bones; + for (int i = 0, n = pathBones.Length; i < n;) + { + int nn = pathBones[i++]; + nn += i; + while (i < nn) + SortBone(bones.Items[pathBones[i++]]); + } + } + } + + private void SortBone(Bone bone) + { + if (bone.sorted) return; + Bone parent = bone.parent; + if (parent != null) SortBone(parent); + bone.sorted = true; + updateCache.Add(bone); + } + + private static void SortReset(ExposedList bones) + { + var bonesItems = bones.Items; + for (int i = 0, n = bones.Count; i < n; i++) + { + Bone bone = bonesItems[i]; + if (bone.sorted) SortReset(bone.children); + bone.sorted = false; + } + } + + /// Updates the world transform for each bone and applies constraints. + public void UpdateWorldTransform() + { + var updateCacheReset = this.updateCacheReset; + var updateCacheResetItems = updateCacheReset.Items; + for (int i = 0, n = updateCacheReset.Count; i < n; i++) + { + Bone bone = updateCacheResetItems[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; + bone.appliedValid = true; + } + var updateItems = this.updateCache.Items; + for (int i = 0, n = updateCache.Count; i < n; i++) + updateItems[i].Update(); + } + + /// Sets the bones, constraints, and slots to their setup pose values. + public void SetToSetupPose() + { + SetBonesToSetupPose(); + SetSlotsToSetupPose(); + } + + /// Sets the bones and constraints to their setup pose values. + public void SetBonesToSetupPose() + { + var bonesItems = this.bones.Items; + for (int i = 0, n = bones.Count; i < n; i++) + bonesItems[i].SetToSetupPose(); + + var ikConstraintsItems = this.ikConstraints.Items; + for (int i = 0, n = ikConstraints.Count; i < n; i++) + { + IkConstraint constraint = ikConstraintsItems[i]; + constraint.bendDirection = constraint.data.bendDirection; + constraint.mix = constraint.data.mix; + } + + var transformConstraintsItems = this.transformConstraints.Items; + for (int i = 0, n = transformConstraints.Count; i < n; i++) + { + TransformConstraint constraint = transformConstraintsItems[i]; + TransformConstraintData constraintData = constraint.data; + constraint.rotateMix = constraintData.rotateMix; + constraint.translateMix = constraintData.translateMix; + constraint.scaleMix = constraintData.scaleMix; + constraint.shearMix = constraintData.shearMix; + } + + var pathConstraintItems = this.pathConstraints.Items; + for (int i = 0, n = pathConstraints.Count; i < n; i++) + { + PathConstraint constraint = pathConstraintItems[i]; + PathConstraintData constraintData = constraint.data; + constraint.position = constraintData.position; + constraint.spacing = constraintData.spacing; + constraint.rotateMix = constraintData.rotateMix; + constraint.translateMix = constraintData.translateMix; + } + } + + public void SetSlotsToSetupPose() + { + var slots = this.slots; + var slotsItems = slots.Items; + drawOrder.Clear(); + for (int i = 0, n = slots.Count; i < n; i++) + drawOrder.Add(slotsItems[i]); + + for (int i = 0, n = slots.Count; i < n; i++) + slotsItems[i].SetToSetupPose(); + } + + /// May be null. + public Bone FindBone(string boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); + var bones = this.bones; + var bonesItems = bones.Items; + for (int i = 0, n = bones.Count; i < n; i++) + { + Bone bone = bonesItems[i]; + if (bone.data.name == boneName) return bone; + } + return null; + } + + /// -1 if the bone was not found. + public int FindBoneIndex(string boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); + var bones = this.bones; + var bonesItems = bones.Items; + for (int i = 0, n = bones.Count; i < n; i++) + if (bonesItems[i].data.name == boneName) return i; + return -1; + } + + /// May be null. + public Slot FindSlot(string slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + var slots = this.slots; + var slotsItems = slots.Items; + for (int i = 0, n = slots.Count; i < n; i++) + { + Slot slot = slotsItems[i]; + if (slot.data.name == slotName) return slot; + } + return null; + } + + /// -1 if the bone was not found. + public int FindSlotIndex(string slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + var slots = this.slots; + var slotsItems = slots.Items; + for (int i = 0, n = slots.Count; i < n; i++) + if (slotsItems[i].data.name.Equals(slotName)) return i; + return -1; + } + + /// Sets a skin by name (see SetSkin). + public void SetSkin(string skinName) + { + Skin foundSkin = data.FindSkin(skinName); + if (foundSkin == null) throw new ArgumentException("Skin not found: " + skinName, "skinName"); + SetSkin(foundSkin); + } + + /// + /// 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 + /// . + /// Also, often 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. + /// + /// May be null. + public void SetSkin(Skin newSkin) + { + if (newSkin != null) + { + if (skin != null) + newSkin.AttachAll(this, skin); + else + { + ExposedList slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + { + Slot slot = slots.Items[i]; + string name = slot.data.attachmentName; + if (name != null) + { + Attachment attachment = newSkin.GetAttachment(i, name); + if (attachment != null) slot.Attachment = attachment; + } + } + } + } + skin = newSkin; + } + + /// May be null. + public Attachment GetAttachment(string slotName, string attachmentName) + { + return GetAttachment(data.FindSlotIndex(slotName), attachmentName); + } + + /// May be null. + public Attachment GetAttachment(int slotIndex, string attachmentName) + { + if (attachmentName == null) throw new ArgumentNullException("attachmentName", "attachmentName cannot be null."); + if (skin != null) + { + Attachment attachment = skin.GetAttachment(slotIndex, attachmentName); + if (attachment != null) return attachment; + } + return data.defaultSkin != null ? data.defaultSkin.GetAttachment(slotIndex, attachmentName) : null; + } + + /// May be null. + public void SetAttachment(string slotName, string attachmentName) + { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + ExposedList slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + { + Slot slot = slots.Items[i]; + if (slot.data.name == slotName) + { + Attachment attachment = null; + if (attachmentName != null) + { + attachment = GetAttachment(i, attachmentName); + if (attachment == null) throw new Exception("Attachment not found: " + attachmentName + ", for slot: " + slotName); + } + slot.Attachment = attachment; + return; + } + } + throw new Exception("Slot not found: " + slotName); + } + + /// May be null. + public IkConstraint FindIkConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + ExposedList ikConstraints = this.ikConstraints; + for (int i = 0, n = ikConstraints.Count; i < n; i++) + { + IkConstraint ikConstraint = ikConstraints.Items[i]; + if (ikConstraint.data.name == constraintName) return ikConstraint; + } + return null; + } + + /// May be null. + public TransformConstraint FindTransformConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + ExposedList transformConstraints = this.transformConstraints; + for (int i = 0, n = transformConstraints.Count; i < n; i++) + { + TransformConstraint transformConstraint = transformConstraints.Items[i]; + if (transformConstraint.data.name == constraintName) return transformConstraint; + } + return null; + } + + /// May be null. + public PathConstraint FindPathConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + ExposedList pathConstraints = this.pathConstraints; + for (int i = 0, n = pathConstraints.Count; i < n; i++) + { + PathConstraint constraint = pathConstraints.Items[i]; + if (constraint.data.name.Equals(constraintName)) return constraint; + } + return null; + } + + public void Update(float delta) + { + time += delta; + } + + /// Returns the axis aligned bounding box (AABB) of the region and mesh attachments for the current pose. + /// The horizontal distance between the skeleton origin and the left side of the AABB. + /// The vertical distance between the skeleton origin and the bottom side of the AABB. + /// The width of the AABB + /// The height of the AABB. + /// Reference to hold a float[]. May be a null reference. This method will assign it a new float[] with the appropriate size as needed. + public void GetBounds(out float x, out float y, out float width, out float height, ref float[] vertexBuffer) + { + float[] temp = vertexBuffer; + temp = temp ?? new float[8]; + var drawOrderItems = this.drawOrder.Items; + float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; + for (int i = 0, n = this.drawOrder.Count; i < n; i++) + { + Slot slot = drawOrderItems[i]; + int verticesLength = 0; + float[] vertices = null; + Attachment attachment = slot.attachment; + var regionAttachment = attachment as RegionAttachment; + if (regionAttachment != null) + { + verticesLength = 8; + vertices = temp; + if (vertices.Length < 8) vertices = temp = new float[8]; + regionAttachment.ComputeWorldVertices(slot.bone, temp, 0); + } + else + { + var meshAttachment = attachment as MeshAttachment; + if (meshAttachment != null) + { + MeshAttachment mesh = meshAttachment; + verticesLength = mesh.WorldVerticesLength; + vertices = temp; + if (vertices.Length < verticesLength) vertices = temp = new float[verticesLength]; + mesh.ComputeWorldVertices(slot, 0, verticesLength, temp, 0); + } + } + + if (vertices != null) + { + for (int ii = 0; ii < verticesLength; ii += 2) + { + float vx = vertices[ii], vy = vertices[ii + 1]; + minX = Math.Min(minX, vx); + minY = Math.Min(minY, vy); + maxX = Math.Max(maxX, vx); + maxY = Math.Max(maxY, vy); + } + } + } + x = minX; + y = minY; + width = maxX - minX; + height = maxY - minY; + vertexBuffer = temp; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/SkeletonBinary.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/SkeletonBinary.cs index 742e000..608e451 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/SkeletonBinary.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/SkeletonBinary.cs @@ -33,50 +33,54 @@ #endif using System; -using System.IO; using System.Collections.Generic; +using System.IO; #if WINDOWS_STOREAPP using System.Threading.Tasks; using Windows.Storage; #endif -namespace Spine3_6_53 { - public class SkeletonBinary { - public const int BONE_ROTATE = 0; - public const int BONE_TRANSLATE = 1; - public const int BONE_SCALE = 2; - public const int BONE_SHEAR = 3; - - public const int SLOT_ATTACHMENT = 0; - public const int SLOT_COLOR = 1; - public const int SLOT_TWO_COLOR = 2; - - public const int PATH_POSITION = 0; - public const int PATH_SPACING = 1; - public const int PATH_MIX = 2; - - public const int CURVE_LINEAR = 0; - public const int CURVE_STEPPED = 1; - public const int CURVE_BEZIER = 2; - - public float Scale { get; set; } - - private AttachmentLoader attachmentLoader; - private byte[] buffer = new byte[32]; - private List linkedMeshes = new List(); - - public SkeletonBinary (params Atlas[] atlasArray) - : this(new AtlasAttachmentLoader(atlasArray)) { - } - - public SkeletonBinary (AttachmentLoader attachmentLoader) { - if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader"); - this.attachmentLoader = attachmentLoader; - Scale = 1; - } - - #if !ISUNITY && WINDOWS_STOREAPP +namespace Spine3_6_53 +{ + public class SkeletonBinary + { + public const int BONE_ROTATE = 0; + public const int BONE_TRANSLATE = 1; + public const int BONE_SCALE = 2; + public const int BONE_SHEAR = 3; + + public const int SLOT_ATTACHMENT = 0; + public const int SLOT_COLOR = 1; + public const int SLOT_TWO_COLOR = 2; + + public const int PATH_POSITION = 0; + public const int PATH_SPACING = 1; + public const int PATH_MIX = 2; + + public const int CURVE_LINEAR = 0; + public const int CURVE_STEPPED = 1; + public const int CURVE_BEZIER = 2; + + public float Scale { get; set; } + + private AttachmentLoader attachmentLoader; + private byte[] buffer = new byte[32]; + private List linkedMeshes = new List(); + + public SkeletonBinary(params Atlas[] atlasArray) + : this(new AtlasAttachmentLoader(atlasArray)) + { + } + + public SkeletonBinary(AttachmentLoader attachmentLoader) + { + if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader"); + this.attachmentLoader = attachmentLoader; + Scale = 1; + } + +#if !ISUNITY && WINDOWS_STOREAPP private async Task ReadFile(string path) { var folder = Windows.ApplicationModel.Package.Current.InstalledLocation; using (var input = new BufferedStream(await folder.GetFileAsync(path).AsTask().ConfigureAwait(false))) { @@ -89,805 +93,908 @@ private async Task ReadFile(string path) { public SkeletonData ReadSkeletonData (String path) { return this.ReadFile(path).Result; } - #else - public SkeletonData ReadSkeletonData (String path) { - #if WINDOWS_PHONE +#else + public SkeletonData ReadSkeletonData(String path) + { +#if WINDOWS_PHONE using (var input = new BufferedStream(Microsoft.Xna.Framework.TitleContainer.OpenStream(path))) { - #else - using (var input = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) { - #endif - SkeletonData skeletonData = ReadSkeletonData(input); - skeletonData.name = Path.GetFileNameWithoutExtension(path); - return skeletonData; - } - } - #endif // WINDOWS_STOREAPP - - public static readonly TransformMode[] TransformModeValues = { - TransformMode.Normal, - TransformMode.OnlyTranslation, - TransformMode.NoRotationOrReflection, - TransformMode.NoScale, - TransformMode.NoScaleOrReflection - }; - - /// Returns the version string of binary skeleton data. - public static string GetVersionString (Stream input) { - if (input == null) throw new ArgumentNullException("input"); - - try { - // Hash. - int byteCount = ReadVarint(input, true); - if (byteCount > 1) input.Position += byteCount - 1; - - // Version. - byteCount = ReadVarint(input, true); - if (byteCount > 1) { - byteCount--; - var buffer = new byte[byteCount]; - ReadFully(input, buffer, 0, byteCount); - return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount); - } - - throw new ArgumentException("Stream does not contain a valid binary Skeleton Data.", "input"); - } catch (Exception e) { - throw new ArgumentException("Stream does not contain a valid binary Skeleton Data.\n" + e, "input"); - } - } - - public SkeletonData ReadSkeletonData (Stream input) { - if (input == null) throw new ArgumentNullException("input"); - float scale = Scale; - - var skeletonData = new SkeletonData(); - skeletonData.hash = ReadString(input); - if (skeletonData.hash.Length == 0) skeletonData.hash = null; - skeletonData.version = ReadString(input); - if (skeletonData.version.Length == 0) skeletonData.version = null; - skeletonData.width = ReadFloat(input); - skeletonData.height = ReadFloat(input); - - bool nonessential = ReadBoolean(input); - - if (nonessential) { - skeletonData.fps = ReadFloat(input); - skeletonData.imagesPath = ReadString(input); - if (skeletonData.imagesPath.Length == 0) skeletonData.imagesPath = null; - } - - // Bones. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - String name = ReadString(input); - BoneData parent = i == 0 ? null : skeletonData.bones.Items[ReadVarint(input, true)]; - BoneData data = new BoneData(i, name, 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.transformMode = TransformModeValues[ReadVarint(input, true)]; - if (nonessential) ReadInt(input); // Skip bone color. - skeletonData.bones.Add(data); - } - - // Slots. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - String slotName = ReadString(input); - BoneData boneData = skeletonData.bones.Items[ReadVarint(input, true)]; - SlotData slotData = new SlotData(i, slotName, boneData); - int color = ReadInt(input); - slotData.r = ((color & 0xff000000) >> 24) / 255f; - slotData.g = ((color & 0x00ff0000) >> 16) / 255f; - slotData.b = ((color & 0x0000ff00) >> 8) / 255f; - slotData.a = ((color & 0x000000ff)) / 255f; - - int darkColor = ReadInt(input); // 0x00rrggbb - if (darkColor != -1) { - slotData.hasSecondColor = true; - slotData.r2 = ((darkColor & 0x00ff0000) >> 16) / 255f; - slotData.g2 = ((darkColor & 0x0000ff00) >> 8) / 255f; - slotData.b2 = ((darkColor & 0x000000ff)) / 255f; - } - - slotData.attachmentName = ReadString(input); - slotData.blendMode = (BlendMode)ReadVarint(input, true); - skeletonData.slots.Add(slotData); - } - - // IK constraints. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - IkConstraintData data = new IkConstraintData(ReadString(input)); - data.order = ReadVarint(input, true); - for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) - data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]); - data.target = skeletonData.bones.Items[ReadVarint(input, true)]; - data.mix = ReadFloat(input); - data.bendDirection = ReadSByte(input); - skeletonData.ikConstraints.Add(data); - } - - // Transform constraints. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - TransformConstraintData data = new TransformConstraintData(ReadString(input)); - data.order = ReadVarint(input, true); - for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) - data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]); - data.target = skeletonData.bones.Items[ReadVarint(input, true)]; - data.local = ReadBoolean(input); - data.relative = ReadBoolean(input); - data.offsetRotation = ReadFloat(input); - data.offsetX = ReadFloat(input) * scale; - data.offsetY = ReadFloat(input) * scale; - data.offsetScaleX = ReadFloat(input); - data.offsetScaleY = ReadFloat(input); - data.offsetShearY = ReadFloat(input); - data.rotateMix = ReadFloat(input); - data.translateMix = ReadFloat(input); - data.scaleMix = ReadFloat(input); - data.shearMix = ReadFloat(input); - skeletonData.transformConstraints.Add(data); - } - - // Path constraints - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - PathConstraintData data = new PathConstraintData(ReadString(input)); - data.order = ReadVarint(input, true); - for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) - data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]); - data.target = skeletonData.slots.Items[ReadVarint(input, true)]; - data.positionMode = (PositionMode)Enum.GetValues(typeof(PositionMode)).GetValue(ReadVarint(input, true)); - data.spacingMode = (SpacingMode)Enum.GetValues(typeof(SpacingMode)).GetValue(ReadVarint(input, true)); - data.rotateMode = (RotateMode)Enum.GetValues(typeof(RotateMode)).GetValue(ReadVarint(input, true)); - 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.rotateMix = ReadFloat(input); - data.translateMix = ReadFloat(input); - skeletonData.pathConstraints.Add(data); - } - - // Default skin. - Skin defaultSkin = ReadSkin(input, skeletonData, "default", nonessential); - if (defaultSkin != null) { - skeletonData.defaultSkin = defaultSkin; - skeletonData.skins.Add(defaultSkin); - } - - // Skins. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) - skeletonData.skins.Add(ReadSkin(input, skeletonData, ReadString(input), nonessential)); - - // Linked meshes. - for (int i = 0, n = linkedMeshes.Count; i < n; i++) { - SkeletonJson.LinkedMesh linkedMesh = linkedMeshes[i]; - Skin skin = linkedMesh.skin == null ? skeletonData.DefaultSkin : skeletonData.FindSkin(linkedMesh.skin); - if (skin == null) throw new Exception("Skin not found: " + linkedMesh.skin); - Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); - if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); - linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; - linkedMesh.mesh.UpdateUVs(); - } - linkedMeshes.Clear(); - - // Events. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - EventData data = new EventData(ReadString(input)); - data.Int = ReadVarint(input, false); - data.Float = ReadFloat(input); - data.String = ReadString(input); - skeletonData.events.Add(data); - } - - // Animations. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) - ReadAnimation(ReadString(input), input, skeletonData); - - skeletonData.bones.TrimExcess(); - skeletonData.slots.TrimExcess(); - skeletonData.skins.TrimExcess(); - skeletonData.events.TrimExcess(); - skeletonData.animations.TrimExcess(); - skeletonData.ikConstraints.TrimExcess(); - skeletonData.pathConstraints.TrimExcess(); - return skeletonData; - } - - - /// May be null. - private Skin ReadSkin (Stream input, SkeletonData skeletonData, String skinName, bool nonessential) { - int slotCount = ReadVarint(input, true); - if (slotCount == 0) return null; - Skin skin = new Skin(skinName); - 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 = ReadString(input); - Attachment attachment = ReadAttachment(input, skeletonData, skin, slotIndex, name, nonessential); - if (attachment != null) skin.AddAttachment(slotIndex, name, attachment); - } - } - return skin; - } - - private Attachment ReadAttachment (Stream input, SkeletonData skeletonData, Skin skin, int slotIndex, String attachmentName, bool nonessential) { - float scale = Scale; - - String name = ReadString(input); - if (name == null) name = attachmentName; - - AttachmentType type = (AttachmentType)input.ReadByte(); - switch (type) { - case AttachmentType.Region: { - String path = ReadString(input); - float rotation = ReadFloat(input); - float x = ReadFloat(input); - float y = ReadFloat(input); - float scaleX = ReadFloat(input); - float scaleY = ReadFloat(input); - float width = ReadFloat(input); - float height = ReadFloat(input); - int color = ReadInt(input); - - if (path == null) path = name; - RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); - if (region == null) return null; - region.Path = path; - region.x = x * scale; - region.y = y * scale; - region.scaleX = scaleX; - region.scaleY = scaleY; - region.rotation = rotation; - region.width = width * scale; - region.height = height * scale; - region.r = ((color & 0xff000000) >> 24) / 255f; - region.g = ((color & 0x00ff0000) >> 16) / 255f; - region.b = ((color & 0x0000ff00) >> 8) / 255f; - region.a = ((color & 0x000000ff)) / 255f; - region.UpdateOffset(); - return region; - } - case AttachmentType.Boundingbox: { - int vertexCount = ReadVarint(input, true); - Vertices vertices = ReadVertices(input, vertexCount); - if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0; // Avoid unused local warning. - - BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); - if (box == null) return null; - box.worldVerticesLength = vertexCount << 1; - box.vertices = vertices.vertices; - box.bones = vertices.bones; - return box; - } - case AttachmentType.Mesh: { - String path = ReadString(input); - int color = ReadInt(input); - int vertexCount = ReadVarint(input, true); - float[] uvs = ReadFloatArray(input, vertexCount << 1, 1); - int[] triangles = ReadShortArray(input); - Vertices vertices = ReadVertices(input, vertexCount); - int hullLength = ReadVarint(input, true); - int[] edges = null; - float width = 0, height = 0; - if (nonessential) { - edges = ReadShortArray(input); - width = ReadFloat(input); - height = ReadFloat(input); - } - - if (path == null) path = name; - MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); - if (mesh == null) return null; - mesh.Path = path; - mesh.r = ((color & 0xff000000) >> 24) / 255f; - mesh.g = ((color & 0x00ff0000) >> 16) / 255f; - mesh.b = ((color & 0x0000ff00) >> 8) / 255f; - mesh.a = ((color & 0x000000ff)) / 255f; - mesh.bones = vertices.bones; - mesh.vertices = vertices.vertices; - mesh.WorldVerticesLength = vertexCount << 1; - mesh.triangles = triangles; - mesh.regionUVs = uvs; - mesh.UpdateUVs(); - mesh.HullLength = hullLength << 1; - if (nonessential) { - mesh.Edges = edges; - mesh.Width = width * scale; - mesh.Height = height * scale; - } - return mesh; - } - case AttachmentType.Linkedmesh: { - String path = ReadString(input); - int color = ReadInt(input); - String skinName = ReadString(input); - String parent = ReadString(input); - bool inheritDeform = ReadBoolean(input); - float width = 0, height = 0; - if (nonessential) { - width = ReadFloat(input); - height = ReadFloat(input); - } - - if (path == null) path = name; - MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); - if (mesh == null) return null; - mesh.Path = path; - mesh.r = ((color & 0xff000000) >> 24) / 255f; - mesh.g = ((color & 0x00ff0000) >> 16) / 255f; - mesh.b = ((color & 0x0000ff00) >> 8) / 255f; - mesh.a = ((color & 0x000000ff)) / 255f; - mesh.inheritDeform = inheritDeform; - if (nonessential) { - mesh.Width = width * scale; - mesh.Height = height * scale; - } - linkedMeshes.Add(new SkeletonJson.LinkedMesh(mesh, skinName, slotIndex, parent)); - return mesh; - } - case AttachmentType.Path: { - bool closed = ReadBoolean(input); - bool constantSpeed = ReadBoolean(input); - int vertexCount = ReadVarint(input, true); - Vertices vertices = ReadVertices(input, vertexCount); - float[] lengths = new float[vertexCount / 3]; - for (int i = 0, n = lengths.Length; i < n; i++) - lengths[i] = ReadFloat(input) * scale; - if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0; - - PathAttachment path = attachmentLoader.NewPathAttachment(skin, name); - if (path == null) return null; - path.closed = closed; - path.constantSpeed = constantSpeed; - path.worldVerticesLength = vertexCount << 1; - path.vertices = vertices.vertices; - path.bones = vertices.bones; - path.lengths = lengths; - return path; - } - case AttachmentType.Point: { - float rotation = ReadFloat(input); - float x = ReadFloat(input); - float y = ReadFloat(input); - if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0; - - PointAttachment point = attachmentLoader.NewPointAttachment(skin, name); - if (point == null) return null; - point.x = x * scale; - point.y = y * scale; - point.rotation = rotation; - //if (nonessential) point.color = color; - return point; - } - case AttachmentType.Clipping: { - int endSlotIndex = ReadVarint(input, true); - int vertexCount = ReadVarint(input, true); - Vertices vertices = ReadVertices(input, vertexCount); - if (nonessential) ReadInt(input); - - ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name); - if (clip == null) return null; - clip.EndSlot = skeletonData.slots.Items[endSlotIndex]; - clip.worldVerticesLength = vertexCount << 1; - clip.vertices = vertices.vertices; - clip.bones = vertices.bones; - return clip; - } - } - return null; - } - - private Vertices ReadVertices (Stream input, int vertexCount) { - float scale = Scale; - int verticesLength = vertexCount << 1; - Vertices vertices = new Vertices(); - if(!ReadBoolean(input)) { - vertices.vertices = ReadFloatArray(input, verticesLength, scale); - return vertices; - } - var weights = new ExposedList(verticesLength * 3 * 3); - var bonesArray = new ExposedList(verticesLength * 3); - for (int i = 0; i < vertexCount; i++) { - int boneCount = ReadVarint(input, true); - bonesArray.Add(boneCount); - for (int ii = 0; ii < boneCount; ii++) { - bonesArray.Add(ReadVarint(input, true)); - weights.Add(ReadFloat(input) * scale); - weights.Add(ReadFloat(input) * scale); - weights.Add(ReadFloat(input)); - } - } - - vertices.vertices = weights.ToArray(); - vertices.bones = bonesArray.ToArray(); - return vertices; - } - - private float[] ReadFloatArray (Stream input, int n, float scale) { - float[] array = new float[n]; - if (scale == 1) { - for (int i = 0; i < n; i++) - array[i] = ReadFloat(input); - } else { - for (int i = 0; i < n; i++) - array[i] = ReadFloat(input) * scale; - } - return array; - } - - private int[] ReadShortArray (Stream input) { - int n = ReadVarint(input, true); - int[] array = new int[n]; - for (int i = 0; i < n; i++) - array[i] = (input.ReadByte() << 8) | input.ReadByte(); - return array; - } - - private void ReadAnimation (String name, Stream input, SkeletonData skeletonData) { - var timelines = new ExposedList(); - float scale = Scale; - float duration = 0; - - // 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++) { - int timelineType = input.ReadByte(); - int frameCount = ReadVarint(input, true); - switch (timelineType) { - case SLOT_ATTACHMENT: { - AttachmentTimeline timeline = new AttachmentTimeline(frameCount); - timeline.slotIndex = slotIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) - timeline.SetFrame(frameIndex, ReadFloat(input), ReadString(input)); - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[frameCount - 1]); - break; - } - case SLOT_COLOR: { - ColorTimeline timeline = new ColorTimeline(frameCount); - timeline.slotIndex = slotIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - float time = ReadFloat(input); - int color = ReadInt(input); - float r = ((color & 0xff000000) >> 24) / 255f; - float g = ((color & 0x00ff0000) >> 16) / 255f; - float b = ((color & 0x0000ff00) >> 8) / 255f; - float a = ((color & 0x000000ff)) / 255f; - timeline.SetFrame(frameIndex, time, r, g, b, a); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * ColorTimeline.ENTRIES]); - break; - } - case SLOT_TWO_COLOR: { - TwoColorTimeline timeline = new TwoColorTimeline(frameCount); - timeline.slotIndex = slotIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - float time = ReadFloat(input); - int color = ReadInt(input); - float r = ((color & 0xff000000) >> 24) / 255f; - float g = ((color & 0x00ff0000) >> 16) / 255f; - float b = ((color & 0x0000ff00) >> 8) / 255f; - float a = ((color & 0x000000ff)) / 255f; - int color2 = ReadInt(input); // 0x00rrggbb - float r2 = ((color2 & 0x00ff0000) >> 16) / 255f; - float g2 = ((color2 & 0x0000ff00) >> 8) / 255f; - float b2 = ((color2 & 0x000000ff)) / 255f; - - timeline.SetFrame(frameIndex, time, r, g, b, a, r2, g2, b2); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TwoColorTimeline.ENTRIES]); - break; - } - } - } - } - - // 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++) { - int timelineType = input.ReadByte(); - int frameCount = ReadVarint(input, true); - switch (timelineType) { - case BONE_ROTATE: { - RotateTimeline timeline = new RotateTimeline(frameCount); - timeline.boneIndex = boneIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input)); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(frameCount - 1) * RotateTimeline.ENTRIES]); - break; - } - case BONE_TRANSLATE: - case BONE_SCALE: - case BONE_SHEAR: { - TranslateTimeline timeline; - float timelineScale = 1; - if (timelineType == BONE_SCALE) - timeline = new ScaleTimeline(frameCount); - else if (timelineType == BONE_SHEAR) - timeline = new ShearTimeline(frameCount); - else { - timeline = new TranslateTimeline(frameCount); - timelineScale = scale; - } - timeline.boneIndex = boneIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input) * timelineScale, ReadFloat(input) - * timelineScale); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TranslateTimeline.ENTRIES]); - break; - } - } - } - } - - // IK timelines. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - int index = ReadVarint(input, true); - int frameCount = ReadVarint(input, true); - IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount); - timeline.ikConstraintIndex = index; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadSByte(input)); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(frameCount - 1) * IkConstraintTimeline.ENTRIES]); - } - - // Transform constraint timelines. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - int index = ReadVarint(input, true); - int frameCount = ReadVarint(input, true); - TransformConstraintTimeline timeline = new TransformConstraintTimeline(frameCount); - timeline.transformConstraintIndex = index; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input)); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TransformConstraintTimeline.ENTRIES]); - } - - // Path constraint timelines. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - int index = ReadVarint(input, true); - PathConstraintData data = skeletonData.pathConstraints.Items[index]; - for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) { - int timelineType = ReadSByte(input); - int frameCount = ReadVarint(input, true); - switch(timelineType) { - case PATH_POSITION: - case PATH_SPACING: { - PathConstraintPositionTimeline timeline; - float timelineScale = 1; - if (timelineType == PATH_SPACING) { - timeline = new PathConstraintSpacingTimeline(frameCount); - if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale; - } else { - timeline = new PathConstraintPositionTimeline(frameCount); - if (data.positionMode == PositionMode.Fixed) timelineScale = scale; - } - timeline.pathConstraintIndex = index; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input) * timelineScale); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintPositionTimeline.ENTRIES]); - break; - } - case PATH_MIX: { - PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(frameCount); - timeline.pathConstraintIndex = index; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input)); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintMixTimeline.ENTRIES]); - break; - } - } - } - } - - // Deform timelines. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - Skin skin = skeletonData.skins.Items[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++) { - VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, ReadString(input)); - bool weighted = attachment.bones != null; - float[] vertices = attachment.vertices; - int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length; - - int frameCount = ReadVarint(input, true); - DeformTimeline timeline = new DeformTimeline(frameCount); - timeline.slotIndex = slotIndex; - timeline.attachment = attachment; - - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - float time = ReadFloat(input); - float[] deform; - int end = ReadVarint(input, true); - if (end == 0) - deform = weighted ? new float[deformLength] : vertices; - else { - deform = new float[deformLength]; - int start = ReadVarint(input, true); - end += start; - if (scale == 1) { - for (int v = start; v < end; v++) - deform[v] = ReadFloat(input); - } else { - for (int v = start; v < end; v++) - deform[v] = ReadFloat(input) * scale; - } - if (!weighted) { - for (int v = 0, vn = deform.Length; v < vn; v++) - deform[v] += vertices[v]; - } - } - - timeline.SetFrame(frameIndex, time, deform); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[frameCount - 1]); - } - } - } - - // Draw order timeline. - int drawOrderCount = ReadVarint(input, true); - if (drawOrderCount > 0) { - DrawOrderTimeline timeline = new DrawOrderTimeline(drawOrderCount); - int slotCount = skeletonData.slots.Count; - for (int i = 0; i < drawOrderCount; i++) { - float time = ReadFloat(input); - int offsetCount = ReadVarint(input, true); - int[] drawOrder = new int[slotCount]; - for (int ii = slotCount - 1; ii >= 0; ii--) - drawOrder[ii] = -1; - int[] unchanged = new int[slotCount - offsetCount]; - int originalIndex = 0, unchangedIndex = 0; - for (int ii = 0; ii < offsetCount; ii++) { - int slotIndex = ReadVarint(input, true); - // Collect unchanged items. - while (originalIndex != slotIndex) - unchanged[unchangedIndex++] = originalIndex++; - // Set changed items. - drawOrder[originalIndex + ReadVarint(input, true)] = originalIndex++; - } - // Collect remaining unchanged items. - while (originalIndex < slotCount) - unchanged[unchangedIndex++] = originalIndex++; - // Fill in unchanged items. - for (int ii = slotCount - 1; ii >= 0; ii--) - if (drawOrder[ii] == -1) drawOrder[ii] = unchanged[--unchangedIndex]; - timeline.SetFrame(i, time, drawOrder); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[drawOrderCount - 1]); - } - - // Event timeline. - int eventCount = ReadVarint(input, true); - if (eventCount > 0) { - EventTimeline timeline = new EventTimeline(eventCount); - for (int i = 0; i < eventCount; i++) { - float time = ReadFloat(input); - EventData eventData = skeletonData.events.Items[ReadVarint(input, true)]; - Event e = new Event(time, eventData); - e.Int = ReadVarint(input, false); - e.Float = ReadFloat(input); - e.String = ReadBoolean(input) ? ReadString(input) : eventData.String; - timeline.SetFrame(i, e); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[eventCount - 1]); - } - - timelines.TrimExcess(); - skeletonData.animations.Add(new Animation(name, timelines, duration)); - } - - private void ReadCurve (Stream input, int frameIndex, CurveTimeline timeline) { - switch (input.ReadByte()) { - case CURVE_STEPPED: - timeline.SetStepped(frameIndex); - break; - case CURVE_BEZIER: - timeline.SetCurve(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input)); - break; - } - } - - private static sbyte ReadSByte (Stream input) { - int value = input.ReadByte(); - if (value == -1) throw new EndOfStreamException(); - return (sbyte)value; - } - - private static bool ReadBoolean (Stream input) { - return input.ReadByte() != 0; - } - - private float ReadFloat (Stream input) { - buffer[3] = (byte)input.ReadByte(); - buffer[2] = (byte)input.ReadByte(); - buffer[1] = (byte)input.ReadByte(); - buffer[0] = (byte)input.ReadByte(); - return BitConverter.ToSingle(buffer, 0); - } - - private static int ReadInt (Stream input) { - return (input.ReadByte() << 24) + (input.ReadByte() << 16) + (input.ReadByte() << 8) + input.ReadByte(); - } - - private static int ReadVarint (Stream input, bool optimizePositive) { - int b = input.ReadByte(); - int result = b & 0x7F; - if ((b & 0x80) != 0) { - b = input.ReadByte(); - result |= (b & 0x7F) << 7; - if ((b & 0x80) != 0) { - b = input.ReadByte(); - result |= (b & 0x7F) << 14; - if ((b & 0x80) != 0) { - b = input.ReadByte(); - result |= (b & 0x7F) << 21; - if ((b & 0x80) != 0) result |= (input.ReadByte() & 0x7F) << 28; - } - } - } - return optimizePositive ? result : ((result >> 1) ^ -(result & 1)); - } - - private string ReadString (Stream input) { - int byteCount = ReadVarint(input, true); - switch (byteCount) { - case 0: - return null; - case 1: - return ""; - } - byteCount--; - byte[] buffer = this.buffer; - if (buffer.Length < byteCount) buffer = new byte[byteCount]; - ReadFully(input, buffer, 0, byteCount); - return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount); - } - - private static void ReadFully (Stream input, byte[] buffer, int offset, int length) { - while (length > 0) { - int count = input.Read(buffer, offset, length); - if (count <= 0) throw new EndOfStreamException(); - offset += count; - length -= count; - } - } - - internal class Vertices { - public int[] bones; - public float[] vertices; - } - } +#else + using (var input = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) + { +#endif + SkeletonData skeletonData = ReadSkeletonData(input); + skeletonData.name = Path.GetFileNameWithoutExtension(path); + return skeletonData; + } + } +#endif // WINDOWS_STOREAPP + + public static readonly TransformMode[] TransformModeValues = { + TransformMode.Normal, + TransformMode.OnlyTranslation, + TransformMode.NoRotationOrReflection, + TransformMode.NoScale, + TransformMode.NoScaleOrReflection + }; + + /// Returns the version string of binary skeleton data. + public static string GetVersionString(Stream input) + { + if (input == null) throw new ArgumentNullException("input"); + + try + { + // Hash. + int byteCount = ReadVarint(input, true); + if (byteCount > 1) input.Position += byteCount - 1; + + // Version. + byteCount = ReadVarint(input, true); + if (byteCount > 1) + { + byteCount--; + var buffer = new byte[byteCount]; + ReadFully(input, buffer, 0, byteCount); + return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount); + } + + throw new ArgumentException("Stream does not contain a valid binary Skeleton Data.", "input"); + } + catch (Exception e) + { + throw new ArgumentException("Stream does not contain a valid binary Skeleton Data.\n" + e, "input"); + } + } + + public SkeletonData ReadSkeletonData(Stream input) + { + if (input == null) throw new ArgumentNullException("input"); + float scale = Scale; + + var skeletonData = new SkeletonData(); + skeletonData.hash = ReadString(input); + if (skeletonData.hash.Length == 0) skeletonData.hash = null; + skeletonData.version = ReadString(input); + if (skeletonData.version.Length == 0) skeletonData.version = null; + skeletonData.width = ReadFloat(input); + skeletonData.height = ReadFloat(input); + + bool nonessential = ReadBoolean(input); + + if (nonessential) + { + skeletonData.fps = ReadFloat(input); + skeletonData.imagesPath = ReadString(input); + if (skeletonData.imagesPath.Length == 0) skeletonData.imagesPath = null; + } + + // Bones. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + String name = ReadString(input); + BoneData parent = i == 0 ? null : skeletonData.bones.Items[ReadVarint(input, true)]; + BoneData data = new BoneData(i, name, 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.transformMode = TransformModeValues[ReadVarint(input, true)]; + if (nonessential) ReadInt(input); // Skip bone color. + skeletonData.bones.Add(data); + } + + // Slots. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + String slotName = ReadString(input); + BoneData boneData = skeletonData.bones.Items[ReadVarint(input, true)]; + SlotData slotData = new SlotData(i, slotName, boneData); + int color = ReadInt(input); + slotData.r = ((color & 0xff000000) >> 24) / 255f; + slotData.g = ((color & 0x00ff0000) >> 16) / 255f; + slotData.b = ((color & 0x0000ff00) >> 8) / 255f; + slotData.a = ((color & 0x000000ff)) / 255f; + + int darkColor = ReadInt(input); // 0x00rrggbb + if (darkColor != -1) + { + slotData.hasSecondColor = true; + slotData.r2 = ((darkColor & 0x00ff0000) >> 16) / 255f; + slotData.g2 = ((darkColor & 0x0000ff00) >> 8) / 255f; + slotData.b2 = ((darkColor & 0x000000ff)) / 255f; + } + + slotData.attachmentName = ReadString(input); + slotData.blendMode = (BlendMode)ReadVarint(input, true); + skeletonData.slots.Add(slotData); + } + + // IK constraints. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + IkConstraintData data = new IkConstraintData(ReadString(input)); + data.order = ReadVarint(input, true); + for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) + data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]); + data.target = skeletonData.bones.Items[ReadVarint(input, true)]; + data.mix = ReadFloat(input); + data.bendDirection = ReadSByte(input); + skeletonData.ikConstraints.Add(data); + } + + // Transform constraints. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + TransformConstraintData data = new TransformConstraintData(ReadString(input)); + data.order = ReadVarint(input, true); + for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) + data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]); + data.target = skeletonData.bones.Items[ReadVarint(input, true)]; + data.local = ReadBoolean(input); + data.relative = ReadBoolean(input); + data.offsetRotation = ReadFloat(input); + data.offsetX = ReadFloat(input) * scale; + data.offsetY = ReadFloat(input) * scale; + data.offsetScaleX = ReadFloat(input); + data.offsetScaleY = ReadFloat(input); + data.offsetShearY = ReadFloat(input); + data.rotateMix = ReadFloat(input); + data.translateMix = ReadFloat(input); + data.scaleMix = ReadFloat(input); + data.shearMix = ReadFloat(input); + skeletonData.transformConstraints.Add(data); + } + + // Path constraints + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + PathConstraintData data = new PathConstraintData(ReadString(input)); + data.order = ReadVarint(input, true); + for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) + data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]); + data.target = skeletonData.slots.Items[ReadVarint(input, true)]; + data.positionMode = (PositionMode)Enum.GetValues(typeof(PositionMode)).GetValue(ReadVarint(input, true)); + data.spacingMode = (SpacingMode)Enum.GetValues(typeof(SpacingMode)).GetValue(ReadVarint(input, true)); + data.rotateMode = (RotateMode)Enum.GetValues(typeof(RotateMode)).GetValue(ReadVarint(input, true)); + 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.rotateMix = ReadFloat(input); + data.translateMix = ReadFloat(input); + skeletonData.pathConstraints.Add(data); + } + + // Default skin. + Skin defaultSkin = ReadSkin(input, skeletonData, "default", nonessential); + if (defaultSkin != null) + { + skeletonData.defaultSkin = defaultSkin; + skeletonData.skins.Add(defaultSkin); + } + + // Skins. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + skeletonData.skins.Add(ReadSkin(input, skeletonData, ReadString(input), nonessential)); + + // Linked meshes. + for (int i = 0, n = linkedMeshes.Count; i < n; i++) + { + SkeletonJson.LinkedMesh linkedMesh = linkedMeshes[i]; + Skin skin = linkedMesh.skin == null ? skeletonData.DefaultSkin : skeletonData.FindSkin(linkedMesh.skin); + if (skin == null) throw new Exception("Skin not found: " + linkedMesh.skin); + Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); + if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); + linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; + linkedMesh.mesh.UpdateUVs(); + } + linkedMeshes.Clear(); + + // Events. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + EventData data = new EventData(ReadString(input)); + data.Int = ReadVarint(input, false); + data.Float = ReadFloat(input); + data.String = ReadString(input); + skeletonData.events.Add(data); + } + + // Animations. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + ReadAnimation(ReadString(input), input, skeletonData); + + skeletonData.bones.TrimExcess(); + skeletonData.slots.TrimExcess(); + skeletonData.skins.TrimExcess(); + skeletonData.events.TrimExcess(); + skeletonData.animations.TrimExcess(); + skeletonData.ikConstraints.TrimExcess(); + skeletonData.pathConstraints.TrimExcess(); + return skeletonData; + } + + + /// May be null. + private Skin ReadSkin(Stream input, SkeletonData skeletonData, String skinName, bool nonessential) + { + int slotCount = ReadVarint(input, true); + if (slotCount == 0) return null; + Skin skin = new Skin(skinName); + 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 = ReadString(input); + Attachment attachment = ReadAttachment(input, skeletonData, skin, slotIndex, name, nonessential); + if (attachment != null) skin.AddAttachment(slotIndex, name, attachment); + } + } + return skin; + } + + private Attachment ReadAttachment(Stream input, SkeletonData skeletonData, Skin skin, int slotIndex, String attachmentName, bool nonessential) + { + float scale = Scale; + + String name = ReadString(input); + if (name == null) name = attachmentName; + + AttachmentType type = (AttachmentType)input.ReadByte(); + switch (type) + { + case AttachmentType.Region: + { + String path = ReadString(input); + float rotation = ReadFloat(input); + float x = ReadFloat(input); + float y = ReadFloat(input); + float scaleX = ReadFloat(input); + float scaleY = ReadFloat(input); + float width = ReadFloat(input); + float height = ReadFloat(input); + int color = ReadInt(input); + + if (path == null) path = name; + RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); + if (region == null) return null; + region.Path = path; + region.x = x * scale; + region.y = y * scale; + region.scaleX = scaleX; + region.scaleY = scaleY; + region.rotation = rotation; + region.width = width * scale; + region.height = height * scale; + region.r = ((color & 0xff000000) >> 24) / 255f; + region.g = ((color & 0x00ff0000) >> 16) / 255f; + region.b = ((color & 0x0000ff00) >> 8) / 255f; + region.a = ((color & 0x000000ff)) / 255f; + region.UpdateOffset(); + return region; + } + case AttachmentType.Boundingbox: + { + int vertexCount = ReadVarint(input, true); + Vertices vertices = ReadVertices(input, vertexCount); + if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0; // Avoid unused local warning. + + BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); + if (box == null) return null; + box.worldVerticesLength = vertexCount << 1; + box.vertices = vertices.vertices; + box.bones = vertices.bones; + return box; + } + case AttachmentType.Mesh: + { + String path = ReadString(input); + int color = ReadInt(input); + int vertexCount = ReadVarint(input, true); + float[] uvs = ReadFloatArray(input, vertexCount << 1, 1); + int[] triangles = ReadShortArray(input); + Vertices vertices = ReadVertices(input, vertexCount); + int hullLength = ReadVarint(input, true); + int[] edges = null; + float width = 0, height = 0; + if (nonessential) + { + edges = ReadShortArray(input); + width = ReadFloat(input); + height = ReadFloat(input); + } + + if (path == null) path = name; + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); + if (mesh == null) return null; + mesh.Path = path; + mesh.r = ((color & 0xff000000) >> 24) / 255f; + mesh.g = ((color & 0x00ff0000) >> 16) / 255f; + mesh.b = ((color & 0x0000ff00) >> 8) / 255f; + mesh.a = ((color & 0x000000ff)) / 255f; + mesh.bones = vertices.bones; + mesh.vertices = vertices.vertices; + mesh.WorldVerticesLength = vertexCount << 1; + mesh.triangles = triangles; + mesh.regionUVs = uvs; + mesh.UpdateUVs(); + mesh.HullLength = hullLength << 1; + if (nonessential) + { + mesh.Edges = edges; + mesh.Width = width * scale; + mesh.Height = height * scale; + } + return mesh; + } + case AttachmentType.Linkedmesh: + { + String path = ReadString(input); + int color = ReadInt(input); + String skinName = ReadString(input); + String parent = ReadString(input); + bool inheritDeform = ReadBoolean(input); + float width = 0, height = 0; + if (nonessential) + { + width = ReadFloat(input); + height = ReadFloat(input); + } + + if (path == null) path = name; + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); + if (mesh == null) return null; + mesh.Path = path; + mesh.r = ((color & 0xff000000) >> 24) / 255f; + mesh.g = ((color & 0x00ff0000) >> 16) / 255f; + mesh.b = ((color & 0x0000ff00) >> 8) / 255f; + mesh.a = ((color & 0x000000ff)) / 255f; + mesh.inheritDeform = inheritDeform; + if (nonessential) + { + mesh.Width = width * scale; + mesh.Height = height * scale; + } + linkedMeshes.Add(new SkeletonJson.LinkedMesh(mesh, skinName, slotIndex, parent)); + return mesh; + } + case AttachmentType.Path: + { + bool closed = ReadBoolean(input); + bool constantSpeed = ReadBoolean(input); + int vertexCount = ReadVarint(input, true); + Vertices vertices = ReadVertices(input, vertexCount); + float[] lengths = new float[vertexCount / 3]; + for (int i = 0, n = lengths.Length; i < n; i++) + lengths[i] = ReadFloat(input) * scale; + if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0; + + PathAttachment path = attachmentLoader.NewPathAttachment(skin, name); + if (path == null) return null; + path.closed = closed; + path.constantSpeed = constantSpeed; + path.worldVerticesLength = vertexCount << 1; + path.vertices = vertices.vertices; + path.bones = vertices.bones; + path.lengths = lengths; + return path; + } + case AttachmentType.Point: + { + float rotation = ReadFloat(input); + float x = ReadFloat(input); + float y = ReadFloat(input); + if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0; + + PointAttachment point = attachmentLoader.NewPointAttachment(skin, name); + if (point == null) return null; + point.x = x * scale; + point.y = y * scale; + point.rotation = rotation; + //if (nonessential) point.color = color; + return point; + } + case AttachmentType.Clipping: + { + int endSlotIndex = ReadVarint(input, true); + int vertexCount = ReadVarint(input, true); + Vertices vertices = ReadVertices(input, vertexCount); + if (nonessential) ReadInt(input); + + ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name); + if (clip == null) return null; + clip.EndSlot = skeletonData.slots.Items[endSlotIndex]; + clip.worldVerticesLength = vertexCount << 1; + clip.vertices = vertices.vertices; + clip.bones = vertices.bones; + return clip; + } + } + return null; + } + + private Vertices ReadVertices(Stream input, int vertexCount) + { + float scale = Scale; + int verticesLength = vertexCount << 1; + Vertices vertices = new Vertices(); + if (!ReadBoolean(input)) + { + vertices.vertices = ReadFloatArray(input, verticesLength, scale); + return vertices; + } + var weights = new ExposedList(verticesLength * 3 * 3); + var bonesArray = new ExposedList(verticesLength * 3); + for (int i = 0; i < vertexCount; i++) + { + int boneCount = ReadVarint(input, true); + bonesArray.Add(boneCount); + for (int ii = 0; ii < boneCount; ii++) + { + bonesArray.Add(ReadVarint(input, true)); + weights.Add(ReadFloat(input) * scale); + weights.Add(ReadFloat(input) * scale); + weights.Add(ReadFloat(input)); + } + } + + vertices.vertices = weights.ToArray(); + vertices.bones = bonesArray.ToArray(); + return vertices; + } + + private float[] ReadFloatArray(Stream input, int n, float scale) + { + float[] array = new float[n]; + if (scale == 1) + { + for (int i = 0; i < n; i++) + array[i] = ReadFloat(input); + } + else + { + for (int i = 0; i < n; i++) + array[i] = ReadFloat(input) * scale; + } + return array; + } + + private int[] ReadShortArray(Stream input) + { + int n = ReadVarint(input, true); + int[] array = new int[n]; + for (int i = 0; i < n; i++) + array[i] = (input.ReadByte() << 8) | input.ReadByte(); + return array; + } + + private void ReadAnimation(String name, Stream input, SkeletonData skeletonData) + { + var timelines = new ExposedList(); + float scale = Scale; + float duration = 0; + + // 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++) + { + int timelineType = input.ReadByte(); + int frameCount = ReadVarint(input, true); + switch (timelineType) + { + case SLOT_ATTACHMENT: + { + AttachmentTimeline timeline = new AttachmentTimeline(frameCount); + timeline.slotIndex = slotIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + timeline.SetFrame(frameIndex, ReadFloat(input), ReadString(input)); + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[frameCount - 1]); + break; + } + case SLOT_COLOR: + { + ColorTimeline timeline = new ColorTimeline(frameCount); + timeline.slotIndex = slotIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + float time = ReadFloat(input); + int color = ReadInt(input); + float r = ((color & 0xff000000) >> 24) / 255f; + float g = ((color & 0x00ff0000) >> 16) / 255f; + float b = ((color & 0x0000ff00) >> 8) / 255f; + float a = ((color & 0x000000ff)) / 255f; + timeline.SetFrame(frameIndex, time, r, g, b, a); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * ColorTimeline.ENTRIES]); + break; + } + case SLOT_TWO_COLOR: + { + TwoColorTimeline timeline = new TwoColorTimeline(frameCount); + timeline.slotIndex = slotIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + float time = ReadFloat(input); + int color = ReadInt(input); + float r = ((color & 0xff000000) >> 24) / 255f; + float g = ((color & 0x00ff0000) >> 16) / 255f; + float b = ((color & 0x0000ff00) >> 8) / 255f; + float a = ((color & 0x000000ff)) / 255f; + int color2 = ReadInt(input); // 0x00rrggbb + float r2 = ((color2 & 0x00ff0000) >> 16) / 255f; + float g2 = ((color2 & 0x0000ff00) >> 8) / 255f; + float b2 = ((color2 & 0x000000ff)) / 255f; + + timeline.SetFrame(frameIndex, time, r, g, b, a, r2, g2, b2); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TwoColorTimeline.ENTRIES]); + break; + } + } + } + } + + // 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++) + { + int timelineType = input.ReadByte(); + int frameCount = ReadVarint(input, true); + switch (timelineType) + { + case BONE_ROTATE: + { + RotateTimeline timeline = new RotateTimeline(frameCount); + timeline.boneIndex = boneIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input)); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(frameCount - 1) * RotateTimeline.ENTRIES]); + break; + } + case BONE_TRANSLATE: + case BONE_SCALE: + case BONE_SHEAR: + { + TranslateTimeline timeline; + float timelineScale = 1; + if (timelineType == BONE_SCALE) + timeline = new ScaleTimeline(frameCount); + else if (timelineType == BONE_SHEAR) + timeline = new ShearTimeline(frameCount); + else + { + timeline = new TranslateTimeline(frameCount); + timelineScale = scale; + } + timeline.boneIndex = boneIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input) * timelineScale, ReadFloat(input) + * timelineScale); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TranslateTimeline.ENTRIES]); + break; + } + } + } + } + + // IK timelines. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + int index = ReadVarint(input, true); + int frameCount = ReadVarint(input, true); + IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount); + timeline.ikConstraintIndex = index; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadSByte(input)); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(frameCount - 1) * IkConstraintTimeline.ENTRIES]); + } + + // Transform constraint timelines. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + int index = ReadVarint(input, true); + int frameCount = ReadVarint(input, true); + TransformConstraintTimeline timeline = new TransformConstraintTimeline(frameCount); + timeline.transformConstraintIndex = index; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input)); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TransformConstraintTimeline.ENTRIES]); + } + + // Path constraint timelines. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + int index = ReadVarint(input, true); + PathConstraintData data = skeletonData.pathConstraints.Items[index]; + for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) + { + int timelineType = ReadSByte(input); + int frameCount = ReadVarint(input, true); + switch (timelineType) + { + case PATH_POSITION: + case PATH_SPACING: + { + PathConstraintPositionTimeline timeline; + float timelineScale = 1; + if (timelineType == PATH_SPACING) + { + timeline = new PathConstraintSpacingTimeline(frameCount); + if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale; + } + else + { + timeline = new PathConstraintPositionTimeline(frameCount); + if (data.positionMode == PositionMode.Fixed) timelineScale = scale; + } + timeline.pathConstraintIndex = index; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input) * timelineScale); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintPositionTimeline.ENTRIES]); + break; + } + case PATH_MIX: + { + PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(frameCount); + timeline.pathConstraintIndex = index; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input)); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintMixTimeline.ENTRIES]); + break; + } + } + } + } + + // Deform timelines. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + Skin skin = skeletonData.skins.Items[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++) + { + VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, ReadString(input)); + bool weighted = attachment.bones != null; + float[] vertices = attachment.vertices; + int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length; + + int frameCount = ReadVarint(input, true); + DeformTimeline timeline = new DeformTimeline(frameCount); + timeline.slotIndex = slotIndex; + timeline.attachment = attachment; + + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + float time = ReadFloat(input); + float[] deform; + int end = ReadVarint(input, true); + if (end == 0) + deform = weighted ? new float[deformLength] : vertices; + else + { + deform = new float[deformLength]; + int start = ReadVarint(input, true); + end += start; + if (scale == 1) + { + for (int v = start; v < end; v++) + deform[v] = ReadFloat(input); + } + else + { + for (int v = start; v < end; v++) + deform[v] = ReadFloat(input) * scale; + } + if (!weighted) + { + for (int v = 0, vn = deform.Length; v < vn; v++) + deform[v] += vertices[v]; + } + } + + timeline.SetFrame(frameIndex, time, deform); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[frameCount - 1]); + } + } + } + + // Draw order timeline. + int drawOrderCount = ReadVarint(input, true); + if (drawOrderCount > 0) + { + DrawOrderTimeline timeline = new DrawOrderTimeline(drawOrderCount); + int slotCount = skeletonData.slots.Count; + for (int i = 0; i < drawOrderCount; i++) + { + float time = ReadFloat(input); + int offsetCount = ReadVarint(input, true); + int[] drawOrder = new int[slotCount]; + for (int ii = slotCount - 1; ii >= 0; ii--) + drawOrder[ii] = -1; + int[] unchanged = new int[slotCount - offsetCount]; + int originalIndex = 0, unchangedIndex = 0; + for (int ii = 0; ii < offsetCount; ii++) + { + int slotIndex = ReadVarint(input, true); + // Collect unchanged items. + while (originalIndex != slotIndex) + unchanged[unchangedIndex++] = originalIndex++; + // Set changed items. + drawOrder[originalIndex + ReadVarint(input, true)] = originalIndex++; + } + // Collect remaining unchanged items. + while (originalIndex < slotCount) + unchanged[unchangedIndex++] = originalIndex++; + // Fill in unchanged items. + for (int ii = slotCount - 1; ii >= 0; ii--) + if (drawOrder[ii] == -1) drawOrder[ii] = unchanged[--unchangedIndex]; + timeline.SetFrame(i, time, drawOrder); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[drawOrderCount - 1]); + } + + // Event timeline. + int eventCount = ReadVarint(input, true); + if (eventCount > 0) + { + EventTimeline timeline = new EventTimeline(eventCount); + for (int i = 0; i < eventCount; i++) + { + float time = ReadFloat(input); + EventData eventData = skeletonData.events.Items[ReadVarint(input, true)]; + Event e = new Event(time, eventData); + e.Int = ReadVarint(input, false); + e.Float = ReadFloat(input); + e.String = ReadBoolean(input) ? ReadString(input) : eventData.String; + timeline.SetFrame(i, e); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[eventCount - 1]); + } + + timelines.TrimExcess(); + skeletonData.animations.Add(new Animation(name, timelines, duration)); + } + + private void ReadCurve(Stream input, int frameIndex, CurveTimeline timeline) + { + switch (input.ReadByte()) + { + case CURVE_STEPPED: + timeline.SetStepped(frameIndex); + break; + case CURVE_BEZIER: + timeline.SetCurve(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input)); + break; + } + } + + private static sbyte ReadSByte(Stream input) + { + int value = input.ReadByte(); + if (value == -1) throw new EndOfStreamException(); + return (sbyte)value; + } + + private static bool ReadBoolean(Stream input) + { + return input.ReadByte() != 0; + } + + private float ReadFloat(Stream input) + { + buffer[3] = (byte)input.ReadByte(); + buffer[2] = (byte)input.ReadByte(); + buffer[1] = (byte)input.ReadByte(); + buffer[0] = (byte)input.ReadByte(); + return BitConverter.ToSingle(buffer, 0); + } + + private static int ReadInt(Stream input) + { + return (input.ReadByte() << 24) + (input.ReadByte() << 16) + (input.ReadByte() << 8) + input.ReadByte(); + } + + private static int ReadVarint(Stream input, bool optimizePositive) + { + int b = input.ReadByte(); + int result = b & 0x7F; + if ((b & 0x80) != 0) + { + b = input.ReadByte(); + result |= (b & 0x7F) << 7; + if ((b & 0x80) != 0) + { + b = input.ReadByte(); + result |= (b & 0x7F) << 14; + if ((b & 0x80) != 0) + { + b = input.ReadByte(); + result |= (b & 0x7F) << 21; + if ((b & 0x80) != 0) result |= (input.ReadByte() & 0x7F) << 28; + } + } + } + return optimizePositive ? result : ((result >> 1) ^ -(result & 1)); + } + + private string ReadString(Stream input) + { + int byteCount = ReadVarint(input, true); + switch (byteCount) + { + case 0: + return null; + case 1: + return ""; + } + byteCount--; + byte[] buffer = this.buffer; + if (buffer.Length < byteCount) buffer = new byte[byteCount]; + ReadFully(input, buffer, 0, byteCount); + return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount); + } + + private static void ReadFully(Stream input, byte[] buffer, int offset, int length) + { + while (length > 0) + { + int count = input.Read(buffer, offset, length); + if (count <= 0) throw new EndOfStreamException(); + offset += count; + length -= count; + } + } + + internal class Vertices + { + public int[] bones; + public float[] vertices; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/SkeletonBounds.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/SkeletonBounds.cs index 0e9bdb7..6ce75d2 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/SkeletonBounds.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/SkeletonBounds.cs @@ -30,205 +30,232 @@ using System; -namespace Spine3_6_53 { - - /// - /// 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. - /// - public class SkeletonBounds { - private ExposedList polygonPool = new ExposedList(); - private float minX, minY, maxX, maxY; - - public ExposedList BoundingBoxes { get; private set; } - public ExposedList Polygons { get; private set; } - public float MinX { get { return minX; } set { minX = value; } } - public float MinY { get { return minY; } set { minY = value; } } - public float MaxX { get { return maxX; } set { maxX = value; } } - public float MaxY { get { return maxY; } set { maxY = value; } } - public float Width { get { return maxX - minX; } } - public float Height { get { return maxY - minY; } } - - public SkeletonBounds () { - BoundingBoxes = new ExposedList(); - Polygons = new ExposedList(); - } - - /// - /// Clears any previous polygons, finds all visible bounding box attachments, - /// and computes the world vertices for each bounding box's polygon. - /// The skeleton. - /// - /// If true, the axis aligned bounding box containing all the polygons is computed. - /// If false, the SkeletonBounds AABB methods will always return true. - /// - public void Update (Skeleton skeleton, bool updateAabb) { - ExposedList boundingBoxes = BoundingBoxes; - ExposedList polygons = Polygons; - ExposedList slots = skeleton.slots; - int slotCount = slots.Count; - - boundingBoxes.Clear(); - for (int i = 0, n = polygons.Count; i < n; i++) - polygonPool.Add(polygons.Items[i]); - polygons.Clear(); - - for (int i = 0; i < slotCount; i++) { - Slot slot = slots.Items[i]; - BoundingBoxAttachment boundingBox = slot.attachment as BoundingBoxAttachment; - if (boundingBox == null) continue; - boundingBoxes.Add(boundingBox); - - Polygon polygon = null; - int poolCount = polygonPool.Count; - if (poolCount > 0) { - polygon = polygonPool.Items[poolCount - 1]; - polygonPool.RemoveAt(poolCount - 1); - } else - polygon = new Polygon(); - polygons.Add(polygon); - - int count = boundingBox.worldVerticesLength; - polygon.Count = count; - if (polygon.Vertices.Length < count) polygon.Vertices = new float[count]; - boundingBox.ComputeWorldVertices(slot, polygon.Vertices); - } - - if (updateAabb) { - AabbCompute(); - } else { - minX = int.MinValue; - minY = int.MinValue; - maxX = int.MaxValue; - maxY = int.MaxValue; - } - } - - private void AabbCompute () { - float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; - ExposedList polygons = Polygons; - for (int i = 0, n = polygons.Count; i < n; i++) { - Polygon polygon = polygons.Items[i]; - float[] vertices = polygon.Vertices; - for (int ii = 0, nn = polygon.Count; ii < nn; ii += 2) { - float x = vertices[ii]; - float y = vertices[ii + 1]; - minX = Math.Min(minX, x); - minY = Math.Min(minY, y); - maxX = Math.Max(maxX, x); - maxY = Math.Max(maxY, y); - } - } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; - } - - - /// Returns true if the axis aligned bounding box contains the point. - public bool AabbContainsPoint (float x, float y) { - return x >= minX && x <= maxX && y >= minY && y <= maxY; - } - - /// Returns true if the axis aligned bounding box intersects the line segment. - public bool AabbIntersectsSegment (float x1, float y1, float x2, float y2) { - float minX = this.minX; - float minY = this.minY; - float maxX = this.maxX; - float maxY = this.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; - } - - /// Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds. - public bool AabbIntersectsSkeleton (SkeletonBounds bounds) { - return minX < bounds.maxX && maxX > bounds.minX && minY < bounds.maxY && maxY > bounds.minY; - } - - /// Returns true if the polygon contains the point. - public bool ContainsPoint (Polygon polygon, float x, float y) { - float[] 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; - } - - /// 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. - public BoundingBoxAttachment ContainsPoint (float x, float y) { - ExposedList polygons = Polygons; - for (int i = 0, n = polygons.Count; i < n; i++) - if (ContainsPoint(polygons.Items[i], x, y)) return BoundingBoxes.Items[i]; - return null; - } - - /// 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. - public BoundingBoxAttachment IntersectsSegment (float x1, float y1, float x2, float y2) { - ExposedList polygons = Polygons; - for (int i = 0, n = polygons.Count; i < n; i++) - if (IntersectsSegment(polygons.Items[i], x1, y1, x2, y2)) return BoundingBoxes.Items[i]; - return null; - } - - /// Returns true if the polygon contains the line segment. - public bool IntersectsSegment (Polygon polygon, float x1, float y1, float x2, float y2) { - float[] vertices = polygon.Vertices; - int 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 (int 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; - } - - public Polygon GetPolygon (BoundingBoxAttachment attachment) { - int index = BoundingBoxes.IndexOf(attachment); - return index == -1 ? null : Polygons.Items[index]; - } - } - - public class Polygon { - public float[] Vertices { get; set; } - public int Count { get; set; } - - public Polygon () { - Vertices = new float[16]; - } - } +namespace Spine3_6_53 +{ + + /// + /// 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. + /// + public class SkeletonBounds + { + private ExposedList polygonPool = new ExposedList(); + private float minX, minY, maxX, maxY; + + public ExposedList BoundingBoxes { get; private set; } + public ExposedList Polygons { get; private set; } + public float MinX { get { return minX; } set { minX = value; } } + public float MinY { get { return minY; } set { minY = value; } } + public float MaxX { get { return maxX; } set { maxX = value; } } + public float MaxY { get { return maxY; } set { maxY = value; } } + public float Width { get { return maxX - minX; } } + public float Height { get { return maxY - minY; } } + + public SkeletonBounds() + { + BoundingBoxes = new ExposedList(); + Polygons = new ExposedList(); + } + + /// + /// Clears any previous polygons, finds all visible bounding box attachments, + /// and computes the world vertices for each bounding box's polygon. + /// The skeleton. + /// + /// If true, the axis aligned bounding box containing all the polygons is computed. + /// If false, the SkeletonBounds AABB methods will always return true. + /// + public void Update(Skeleton skeleton, bool updateAabb) + { + ExposedList boundingBoxes = BoundingBoxes; + ExposedList polygons = Polygons; + ExposedList slots = skeleton.slots; + int slotCount = slots.Count; + + boundingBoxes.Clear(); + for (int i = 0, n = polygons.Count; i < n; i++) + polygonPool.Add(polygons.Items[i]); + polygons.Clear(); + + for (int i = 0; i < slotCount; i++) + { + Slot slot = slots.Items[i]; + BoundingBoxAttachment boundingBox = slot.attachment as BoundingBoxAttachment; + if (boundingBox == null) continue; + boundingBoxes.Add(boundingBox); + + Polygon polygon = null; + int poolCount = polygonPool.Count; + if (poolCount > 0) + { + polygon = polygonPool.Items[poolCount - 1]; + polygonPool.RemoveAt(poolCount - 1); + } + else + polygon = new Polygon(); + polygons.Add(polygon); + + int count = boundingBox.worldVerticesLength; + polygon.Count = count; + if (polygon.Vertices.Length < count) polygon.Vertices = new float[count]; + boundingBox.ComputeWorldVertices(slot, polygon.Vertices); + } + + if (updateAabb) + { + AabbCompute(); + } + else + { + minX = int.MinValue; + minY = int.MinValue; + maxX = int.MaxValue; + maxY = int.MaxValue; + } + } + + private void AabbCompute() + { + float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; + ExposedList polygons = Polygons; + for (int i = 0, n = polygons.Count; i < n; i++) + { + Polygon polygon = polygons.Items[i]; + float[] vertices = polygon.Vertices; + for (int ii = 0, nn = polygon.Count; ii < nn; ii += 2) + { + float x = vertices[ii]; + float y = vertices[ii + 1]; + minX = Math.Min(minX, x); + minY = Math.Min(minY, y); + maxX = Math.Max(maxX, x); + maxY = Math.Max(maxY, y); + } + } + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + + /// Returns true if the axis aligned bounding box contains the point. + public bool AabbContainsPoint(float x, float y) + { + return x >= minX && x <= maxX && y >= minY && y <= maxY; + } + + /// Returns true if the axis aligned bounding box intersects the line segment. + public bool AabbIntersectsSegment(float x1, float y1, float x2, float y2) + { + float minX = this.minX; + float minY = this.minY; + float maxX = this.maxX; + float maxY = this.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; + } + + /// Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds. + public bool AabbIntersectsSkeleton(SkeletonBounds bounds) + { + return minX < bounds.maxX && maxX > bounds.minX && minY < bounds.maxY && maxY > bounds.minY; + } + + /// Returns true if the polygon contains the point. + public bool ContainsPoint(Polygon polygon, float x, float y) + { + float[] 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; + } + + /// 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. + public BoundingBoxAttachment ContainsPoint(float x, float y) + { + ExposedList polygons = Polygons; + for (int i = 0, n = polygons.Count; i < n; i++) + if (ContainsPoint(polygons.Items[i], x, y)) return BoundingBoxes.Items[i]; + return null; + } + + /// 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. + public BoundingBoxAttachment IntersectsSegment(float x1, float y1, float x2, float y2) + { + ExposedList polygons = Polygons; + for (int i = 0, n = polygons.Count; i < n; i++) + if (IntersectsSegment(polygons.Items[i], x1, y1, x2, y2)) return BoundingBoxes.Items[i]; + return null; + } + + /// Returns true if the polygon contains the line segment. + public bool IntersectsSegment(Polygon polygon, float x1, float y1, float x2, float y2) + { + float[] vertices = polygon.Vertices; + int 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 (int 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; + } + + public Polygon GetPolygon(BoundingBoxAttachment attachment) + { + int index = BoundingBoxes.IndexOf(attachment); + return index == -1 ? null : Polygons.Items[index]; + } + } + + public class Polygon + { + public float[] Vertices { get; set; } + public int Count { get; set; } + + public Polygon() + { + Vertices = new float[16]; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/SkeletonClipping.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/SkeletonClipping.cs index 3ec9b6b..2a2b10c 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/SkeletonClipping.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/SkeletonClipping.cs @@ -28,258 +28,286 @@ * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; +namespace Spine3_6_53 +{ + public class SkeletonClipping + { + internal readonly Triangulator triangulator = new Triangulator(); + internal readonly ExposedList clippingPolygon = new ExposedList(); + internal readonly ExposedList clipOutput = new ExposedList(128); + internal readonly ExposedList clippedVertices = new ExposedList(128); + internal readonly ExposedList clippedTriangles = new ExposedList(128); + internal readonly ExposedList clippedUVs = new ExposedList(128); + internal readonly ExposedList scratch = new ExposedList(); -namespace Spine3_6_53 { - public class SkeletonClipping { - internal readonly Triangulator triangulator = new Triangulator(); - internal readonly ExposedList clippingPolygon = new ExposedList(); - internal readonly ExposedList clipOutput = new ExposedList(128); - internal readonly ExposedList clippedVertices = new ExposedList(128); - internal readonly ExposedList clippedTriangles = new ExposedList(128); - internal readonly ExposedList clippedUVs = new ExposedList(128); - internal readonly ExposedList scratch = new ExposedList(); + internal ClippingAttachment clipAttachment; + internal ExposedList> clippingPolygons; - internal ClippingAttachment clipAttachment; - internal ExposedList> clippingPolygons; + public ExposedList ClippedVertices { get { return clippedVertices; } } + public ExposedList ClippedTriangles { get { return clippedTriangles; } } + public ExposedList ClippedUVs { get { return clippedUVs; } } - public ExposedList ClippedVertices { get { return clippedVertices; } } - public ExposedList ClippedTriangles { get { return clippedTriangles; } } - public ExposedList ClippedUVs { get { return clippedUVs; } } + public bool IsClipping { get { return clipAttachment != null; } } - public bool IsClipping { get { return clipAttachment != null; } } + public int ClipStart(Slot slot, ClippingAttachment clip) + { + if (clipAttachment != null) return 0; + clipAttachment = clip; - public int ClipStart (Slot slot, ClippingAttachment clip) { - if (clipAttachment != null) return 0; - clipAttachment = clip; + int n = clip.worldVerticesLength; + float[] vertices = clippingPolygon.Resize(n).Items; + clip.ComputeWorldVertices(slot, 0, n, vertices, 0, 2); + MakeClockwise(clippingPolygon); + clippingPolygons = triangulator.Decompose(clippingPolygon, triangulator.Triangulate(clippingPolygon)); + foreach (var polygon in clippingPolygons) + { + MakeClockwise(polygon); + polygon.Add(polygon.Items[0]); + polygon.Add(polygon.Items[1]); + } + return clippingPolygons.Count; + } - int n = clip.worldVerticesLength; - float[] vertices = clippingPolygon.Resize(n).Items; - clip.ComputeWorldVertices(slot, 0, n, vertices, 0, 2); - MakeClockwise(clippingPolygon); - clippingPolygons = triangulator.Decompose(clippingPolygon, triangulator.Triangulate(clippingPolygon)); - foreach (var polygon in clippingPolygons) { - MakeClockwise(polygon); - polygon.Add(polygon.Items[0]); - polygon.Add(polygon.Items[1]); - } - return clippingPolygons.Count; - } + public void ClipEnd(Slot slot) + { + if (clipAttachment != null && clipAttachment.endSlot == slot.data) ClipEnd(); + } - public void ClipEnd (Slot slot) { - if (clipAttachment != null && clipAttachment.endSlot == slot.data) ClipEnd(); - } + public void ClipEnd() + { + if (clipAttachment == null) return; + clipAttachment = null; + clippingPolygons = null; + clippedVertices.Clear(); + clippedTriangles.Clear(); + clippingPolygon.Clear(); + } - public void ClipEnd () { - if (clipAttachment == null) return; - clipAttachment = null; - clippingPolygons = null; - clippedVertices.Clear(); - clippedTriangles.Clear(); - clippingPolygon.Clear(); - } - - public void ClipTriangles (float[] vertices, int verticesLength, int[] triangles, int trianglesLength, float[] uvs) { - ExposedList clipOutput = this.clipOutput, clippedVertices = this.clippedVertices; - var clippedTriangles = this.clippedTriangles; - var polygons = clippingPolygons.Items; - int polygonsCount = clippingPolygons.Count; + public void ClipTriangles(float[] vertices, int verticesLength, int[] triangles, int trianglesLength, float[] uvs) + { + ExposedList clipOutput = this.clipOutput, clippedVertices = this.clippedVertices; + var clippedTriangles = this.clippedTriangles; + var polygons = clippingPolygons.Items; + int polygonsCount = clippingPolygons.Count; - int index = 0; - clippedVertices.Clear(); - clippedUVs.Clear(); - clippedTriangles.Clear(); - //outer: - for (int i = 0; i < trianglesLength; i += 3) { - int vertexOffset = triangles[i] << 1; - float x1 = vertices[vertexOffset], y1 = vertices[vertexOffset + 1]; - float u1 = uvs[vertexOffset], v1 = uvs[vertexOffset + 1]; + int index = 0; + clippedVertices.Clear(); + clippedUVs.Clear(); + clippedTriangles.Clear(); + //outer: + for (int i = 0; i < trianglesLength; i += 3) + { + int vertexOffset = triangles[i] << 1; + float x1 = vertices[vertexOffset], y1 = vertices[vertexOffset + 1]; + float u1 = uvs[vertexOffset], v1 = uvs[vertexOffset + 1]; - vertexOffset = triangles[i + 1] << 1; - float x2 = vertices[vertexOffset], y2 = vertices[vertexOffset + 1]; - float u2 = uvs[vertexOffset], v2 = uvs[vertexOffset + 1]; + vertexOffset = triangles[i + 1] << 1; + float x2 = vertices[vertexOffset], y2 = vertices[vertexOffset + 1]; + float u2 = uvs[vertexOffset], v2 = uvs[vertexOffset + 1]; - vertexOffset = triangles[i + 2] << 1; - float x3 = vertices[vertexOffset], y3 = vertices[vertexOffset + 1]; - float u3 = uvs[vertexOffset], v3 = uvs[vertexOffset + 1]; + vertexOffset = triangles[i + 2] << 1; + float x3 = vertices[vertexOffset], y3 = vertices[vertexOffset + 1]; + float u3 = uvs[vertexOffset], v3 = uvs[vertexOffset + 1]; - for (int p = 0; p < polygonsCount; p++) { - int s = clippedVertices.Count; - if (Clip(x1, y1, x2, y2, x3, y3, polygons[p], clipOutput)) { - int clipOutputLength = clipOutput.Count; - 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)); + for (int p = 0; p < polygonsCount; p++) + { + int s = clippedVertices.Count; + if (Clip(x1, y1, x2, y2, x3, y3, polygons[p], clipOutput)) + { + int clipOutputLength = clipOutput.Count; + 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)); - int clipOutputCount = clipOutputLength >> 1; - float[] clipOutputItems = clipOutput.Items; - float[] clippedVerticesItems = clippedVertices.Resize(s + clipOutputCount * 2).Items; - float[] clippedUVsItems = clippedUVs.Resize(s + clipOutputCount * 2).Items; - for (int ii = 0; ii < clipOutputLength; ii += 2) { - float x = clipOutputItems[ii], y = clipOutputItems[ii + 1]; - clippedVerticesItems[s] = x; - clippedVerticesItems[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; - clippedUVsItems[s] = u1 * a + u2 * b + u3 * c; - clippedUVsItems[s + 1] = v1 * a + v2 * b + v3 * c; - s += 2; - } + int clipOutputCount = clipOutputLength >> 1; + float[] clipOutputItems = clipOutput.Items; + float[] clippedVerticesItems = clippedVertices.Resize(s + clipOutputCount * 2).Items; + float[] clippedUVsItems = clippedUVs.Resize(s + clipOutputCount * 2).Items; + for (int ii = 0; ii < clipOutputLength; ii += 2) + { + float x = clipOutputItems[ii], y = clipOutputItems[ii + 1]; + clippedVerticesItems[s] = x; + clippedVerticesItems[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; + clippedUVsItems[s] = u1 * a + u2 * b + u3 * c; + clippedUVsItems[s + 1] = v1 * a + v2 * b + v3 * c; + s += 2; + } - s = clippedTriangles.Count; - int[] clippedTrianglesItems = clippedTriangles.Resize(s + 3 * (clipOutputCount - 2)).Items; - clipOutputCount--; - for (int ii = 1; ii < clipOutputCount; ii++) { - clippedTrianglesItems[s] = index; - clippedTrianglesItems[s + 1] = index + ii; - clippedTrianglesItems[s + 2] = index + ii + 1; - s += 3; - } - index += clipOutputCount + 1; - } - else { - float[] clippedVerticesItems = clippedVertices.Resize(s + 3 * 2).Items; - float[] clippedUVsItems = clippedUVs.Resize(s + 3 * 2).Items; - clippedVerticesItems[s] = x1; - clippedVerticesItems[s + 1] = y1; - clippedVerticesItems[s + 2] = x2; - clippedVerticesItems[s + 3] = y2; - clippedVerticesItems[s + 4] = x3; - clippedVerticesItems[s + 5] = y3; + s = clippedTriangles.Count; + int[] clippedTrianglesItems = clippedTriangles.Resize(s + 3 * (clipOutputCount - 2)).Items; + clipOutputCount--; + for (int ii = 1; ii < clipOutputCount; ii++) + { + clippedTrianglesItems[s] = index; + clippedTrianglesItems[s + 1] = index + ii; + clippedTrianglesItems[s + 2] = index + ii + 1; + s += 3; + } + index += clipOutputCount + 1; + } + else + { + float[] clippedVerticesItems = clippedVertices.Resize(s + 3 * 2).Items; + float[] clippedUVsItems = clippedUVs.Resize(s + 3 * 2).Items; + clippedVerticesItems[s] = x1; + clippedVerticesItems[s + 1] = y1; + clippedVerticesItems[s + 2] = x2; + clippedVerticesItems[s + 3] = y2; + clippedVerticesItems[s + 4] = x3; + clippedVerticesItems[s + 5] = y3; - clippedUVsItems[s] = u1; - clippedUVsItems[s + 1] = v1; - clippedUVsItems[s + 2] = u2; - clippedUVsItems[s + 3] = v2; - clippedUVsItems[s + 4] = u3; - clippedUVsItems[s + 5] = v3; + clippedUVsItems[s] = u1; + clippedUVsItems[s + 1] = v1; + clippedUVsItems[s + 2] = u2; + clippedUVsItems[s + 3] = v2; + clippedUVsItems[s + 4] = u3; + clippedUVsItems[s + 5] = v3; - s = clippedTriangles.Count; - int[] clippedTrianglesItems = clippedTriangles.Resize(s + 3).Items; - clippedTrianglesItems[s] = index; - clippedTrianglesItems[s + 1] = index + 1; - clippedTrianglesItems[s + 2] = index + 2; - index += 3; - break; //continue outer; - } - } - } + s = clippedTriangles.Count; + int[] clippedTrianglesItems = clippedTriangles.Resize(s + 3).Items; + clippedTrianglesItems[s] = index; + clippedTrianglesItems[s + 1] = index + 1; + clippedTrianglesItems[s + 2] = index + 2; + index += 3; + break; //continue outer; + } + } + } - } + } - /** Clips the input triangle against the convex, clockwise clipping area. If the triangle lies entirely within the clipping + /** 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. */ - internal bool Clip (float x1, float y1, float x2, float y2, float x3, float y3, ExposedList clippingArea, ExposedList output) { - var originalOutput = output; - var clipped = false; + internal bool Clip(float x1, float y1, float x2, float y2, float x3, float y3, ExposedList clippingArea, ExposedList output) + { + var originalOutput = output; + var clipped = false; - // Avoid copy at the end. - ExposedList input = null; - if (clippingArea.Count % 4 >= 2) { - input = output; - output = scratch; - } else { - input = scratch; - } + // Avoid copy at the end. + ExposedList input = null; + if (clippingArea.Count % 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(); + 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(); - float[] clippingVertices = clippingArea.Items; - int clippingVerticesLast = clippingArea.Count - 4; - for (int i = 0; ; i += 2) { - float edgeX = clippingVertices[i], edgeY = clippingVertices[i + 1]; - float edgeX2 = clippingVertices[i + 2], edgeY2 = clippingVertices[i + 3]; - float deltaX = edgeX - edgeX2, deltaY = edgeY - edgeY2; + float[] clippingVertices = clippingArea.Items; + int clippingVerticesLast = clippingArea.Count - 4; + for (int i = 0; ; i += 2) + { + float edgeX = clippingVertices[i], edgeY = clippingVertices[i + 1]; + float edgeX2 = clippingVertices[i + 2], edgeY2 = clippingVertices[i + 3]; + float deltaX = edgeX - edgeX2, deltaY = edgeY - edgeY2; - float[] inputVertices = input.Items; - int inputVerticesLength = input.Count - 2, outputStart = output.Count; - for (int ii = 0; ii < inputVerticesLength; ii += 2) { - float inputX = inputVertices[ii], inputY = inputVertices[ii + 1]; - float inputX2 = inputVertices[ii + 2], inputY2 = inputVertices[ii + 3]; - bool side2 = deltaX * (inputY2 - edgeY2) - deltaY * (inputX2 - edgeX2) > 0; - if (deltaX * (inputY - edgeY2) - deltaY * (inputX - edgeX2) > 0) { - if (side2) { // v1 inside, v2 inside - output.Add(inputX2); - output.Add(inputY2); - continue; - } - // v1 inside, v2 outside - float c0 = inputY2 - inputY, c2 = inputX2 - inputX; - float ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / (c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY)); - output.Add(edgeX + (edgeX2 - edgeX) * ua); - output.Add(edgeY + (edgeY2 - edgeY) * ua); - } - else if (side2) { // v1 outside, v2 inside - float c0 = inputY2 - inputY, c2 = inputX2 - inputX; - float ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / (c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY)); - output.Add(edgeX + (edgeX2 - edgeX) * ua); - output.Add(edgeY + (edgeY2 - edgeY) * ua); - output.Add(inputX2); - output.Add(inputY2); - } - clipped = true; - } + float[] inputVertices = input.Items; + int inputVerticesLength = input.Count - 2, outputStart = output.Count; + for (int ii = 0; ii < inputVerticesLength; ii += 2) + { + float inputX = inputVertices[ii], inputY = inputVertices[ii + 1]; + float inputX2 = inputVertices[ii + 2], inputY2 = inputVertices[ii + 3]; + bool side2 = deltaX * (inputY2 - edgeY2) - deltaY * (inputX2 - edgeX2) > 0; + if (deltaX * (inputY - edgeY2) - deltaY * (inputX - edgeX2) > 0) + { + if (side2) + { // v1 inside, v2 inside + output.Add(inputX2); + output.Add(inputY2); + continue; + } + // v1 inside, v2 outside + float c0 = inputY2 - inputY, c2 = inputX2 - inputX; + float ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / (c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY)); + output.Add(edgeX + (edgeX2 - edgeX) * ua); + output.Add(edgeY + (edgeY2 - edgeY) * ua); + } + else if (side2) + { // v1 outside, v2 inside + float c0 = inputY2 - inputY, c2 = inputX2 - inputX; + float ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / (c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY)); + output.Add(edgeX + (edgeX2 - edgeX) * ua); + output.Add(edgeY + (edgeY2 - edgeY) * ua); + output.Add(inputX2); + output.Add(inputY2); + } + clipped = true; + } - if (outputStart == output.Count) { // All edges outside. - originalOutput.Clear(); - return true; - } + if (outputStart == output.Count) + { // All edges outside. + originalOutput.Clear(); + return true; + } - output.Add(output.Items[0]); - output.Add(output.Items[1]); + output.Add(output.Items[0]); + output.Add(output.Items[1]); - if (i == clippingVerticesLast) break; - var temp = output; - output = input; - output.Clear(); - input = temp; - } + if (i == clippingVerticesLast) break; + var temp = output; + output = input; + output.Clear(); + input = temp; + } - if (originalOutput != output) { - originalOutput.Clear(); - for (int i = 0, n = output.Count - 2; i < n; i++) { - originalOutput.Add(output.Items[i]); - } - } else { - originalOutput.Resize(originalOutput.Count - 2); - } + if (originalOutput != output) + { + originalOutput.Clear(); + for (int i = 0, n = output.Count - 2; i < n; i++) + { + originalOutput.Add(output.Items[i]); + } + } + else + { + originalOutput.Resize(originalOutput.Count - 2); + } - return clipped; - } + return clipped; + } - static void MakeClockwise (ExposedList polygon) { - float[] vertices = polygon.Items; - int verticeslength = polygon.Count; + static void MakeClockwise(ExposedList polygon) + { + float[] vertices = polygon.Items; + int verticeslength = polygon.Count; - float area = vertices[verticeslength - 2] * vertices[1] - vertices[0] * vertices[verticeslength - 1], p1x, p1y, p2x, p2y; - for (int i = 0, n = verticeslength - 3; i < n; i += 2) { - p1x = vertices[i]; - p1y = vertices[i + 1]; - p2x = vertices[i + 2]; - p2y = vertices[i + 3]; - area += p1x * p2y - p2x * p1y; - } - if (area < 0) return; + float area = vertices[verticeslength - 2] * vertices[1] - vertices[0] * vertices[verticeslength - 1], p1x, p1y, p2x, p2y; + for (int i = 0, n = verticeslength - 3; i < n; i += 2) + { + p1x = vertices[i]; + p1y = vertices[i + 1]; + p2x = vertices[i + 2]; + p2y = vertices[i + 3]; + area += p1x * p2y - p2x * p1y; + } + if (area < 0) return; - for (int i = 0, lastX = verticeslength - 2, n = verticeslength >> 1; i < n; i += 2) { - float x = vertices[i], y = vertices[i + 1]; - int other = lastX - i; - vertices[i] = vertices[other]; - vertices[i + 1] = vertices[other + 1]; - vertices[other] = x; - vertices[other + 1] = y; - } - } - } + for (int i = 0, lastX = verticeslength - 2, n = verticeslength >> 1; i < n; i += 2) + { + float x = vertices[i], y = vertices[i + 1]; + int other = lastX - i; + vertices[i] = vertices[other]; + vertices[i + 1] = vertices[other + 1]; + vertices[other] = x; + vertices[other + 1] = y; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/SkeletonData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/SkeletonData.cs index 8a59077..5302f40 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/SkeletonData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/SkeletonData.cs @@ -30,195 +30,215 @@ using System; -namespace Spine3_6_53 { - - /// Stores the setup pose and all of the stateless data for a skeleton. - public class SkeletonData { - internal string name; - internal ExposedList bones = new ExposedList(); // Ordered parents first - internal ExposedList slots = new ExposedList(); // Setup pose draw order. - internal ExposedList skins = new ExposedList(); - internal Skin defaultSkin; - internal ExposedList events = new ExposedList(); - internal ExposedList animations = new ExposedList(); - internal ExposedList ikConstraints = new ExposedList(); - internal ExposedList transformConstraints = new ExposedList(); - internal ExposedList pathConstraints = new ExposedList(); - internal float width, height; - internal string version, hash; - - // Nonessential. - internal float fps; - internal string imagesPath; - - public string Name { get { return name; } set { name = value; } } - - /// The skeleton's bones, sorted parent first. The root bone is always the first bone. - public ExposedList Bones { get { return bones; } } - - public ExposedList Slots { get { return slots; } } - - /// All skins, including the default skin. - public ExposedList Skins { get { return skins; } set { skins = value; } } - - /// - /// The skeleton's default skin. - /// By default this skin contains all attachments that were not in a skin in Spine. - /// - /// May be null. - public Skin DefaultSkin { get { return defaultSkin; } set { defaultSkin = value; } } - - public ExposedList Events { get { return events; } set { events = value; } } - public ExposedList Animations { get { return animations; } set { animations = value; } } - public ExposedList IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } } - public ExposedList TransformConstraints { get { return transformConstraints; } set { transformConstraints = value; } } - public ExposedList PathConstraints { get { return pathConstraints; } set { pathConstraints = value; } } - - public float Width { get { return width; } set { width = value; } } - public float Height { get { return height; } set { height = value; } } - /// The Spine version used to export this data, or null. - public string Version { get { return version; } set { version = value; } } - public string Hash { get { return hash; } set { hash = value; } } - public string ImagesPath { get { return imagesPath; } set { imagesPath = value; } } - - /// - /// The dopesheet FPS in Spine. Available only when nonessential data was exported. - public float Fps { get { return fps; } set { fps = value; } } - - // --- Bones. - - /// - /// 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. - /// May be null. - public BoneData FindBone (string boneName) { - if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); - var bones = this.bones; - var bonesItems = bones.Items; - for (int i = 0, n = bones.Count; i < n; i++) { - BoneData bone = bonesItems[i]; - if (bone.name == boneName) return bone; - } - return null; - } - - /// -1 if the bone was not found. - public int FindBoneIndex (string boneName) { - if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); - var bones = this.bones; - var bonesItems = bones.Items; - for (int i = 0, n = bones.Count; i < n; i++) - if (bonesItems[i].name == boneName) return i; - return -1; - } - - // --- Slots. - - /// May be null. - public SlotData FindSlot (string slotName) { - if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); - ExposedList slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) { - SlotData slot = slots.Items[i]; - if (slot.name == slotName) return slot; - } - return null; - } - - /// -1 if the slot was not found. - public int FindSlotIndex (string slotName) { - if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); - ExposedList slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) - if (slots.Items[i].name == slotName) return i; - return -1; - } - - // --- Skins. - - /// May be null. - public Skin FindSkin (string skinName) { - if (skinName == null) throw new ArgumentNullException("skinName", "skinName cannot be null."); - foreach (Skin skin in skins) - if (skin.name == skinName) return skin; - return null; - } - - // --- Events. - - /// May be null. - public EventData FindEvent (string eventDataName) { - if (eventDataName == null) throw new ArgumentNullException("eventDataName", "eventDataName cannot be null."); - foreach (EventData eventData in events) - if (eventData.name == eventDataName) return eventData; - return null; - } - - // --- Animations. - - /// May be null. - public Animation FindAnimation (string animationName) { - if (animationName == null) throw new ArgumentNullException("animationName", "animationName cannot be null."); - ExposedList animations = this.animations; - for (int i = 0, n = animations.Count; i < n; i++) { - Animation animation = animations.Items[i]; - if (animation.name == animationName) return animation; - } - return null; - } - - // --- IK constraints. - - /// May be null. - public IkConstraintData FindIkConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - ExposedList ikConstraints = this.ikConstraints; - for (int i = 0, n = ikConstraints.Count; i < n; i++) { - IkConstraintData ikConstraint = ikConstraints.Items[i]; - if (ikConstraint.name == constraintName) return ikConstraint; - } - return null; - } - - // --- Transform constraints. - - /// May be null. - public TransformConstraintData FindTransformConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - ExposedList transformConstraints = this.transformConstraints; - for (int i = 0, n = transformConstraints.Count; i < n; i++) { - TransformConstraintData transformConstraint = transformConstraints.Items[i]; - if (transformConstraint.name == constraintName) return transformConstraint; - } - return null; - } - - // --- Path constraints. - - /// May be null. - public PathConstraintData FindPathConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - ExposedList pathConstraints = this.pathConstraints; - for (int i = 0, n = pathConstraints.Count; i < n; i++) { - PathConstraintData constraint = pathConstraints.Items[i]; - if (constraint.name.Equals(constraintName)) return constraint; - } - return null; - } - - /// -1 if the path constraint was not found. - public int FindPathConstraintIndex (string pathConstraintName) { - if (pathConstraintName == null) throw new ArgumentNullException("pathConstraintName", "pathConstraintName cannot be null."); - ExposedList pathConstraints = this.pathConstraints; - for (int i = 0, n = pathConstraints.Count; i < n; i++) - if (pathConstraints.Items[i].name.Equals(pathConstraintName)) return i; - return -1; - } - - // --- - - override public string ToString () { - return name ?? base.ToString(); - } - } +namespace Spine3_6_53 +{ + + /// Stores the setup pose and all of the stateless data for a skeleton. + public class SkeletonData + { + internal string name; + internal ExposedList bones = new ExposedList(); // Ordered parents first + internal ExposedList slots = new ExposedList(); // Setup pose draw order. + internal ExposedList skins = new ExposedList(); + internal Skin defaultSkin; + internal ExposedList events = new ExposedList(); + internal ExposedList animations = new ExposedList(); + internal ExposedList ikConstraints = new ExposedList(); + internal ExposedList transformConstraints = new ExposedList(); + internal ExposedList pathConstraints = new ExposedList(); + internal float width, height; + internal string version, hash; + + // Nonessential. + internal float fps; + internal string imagesPath; + + public string Name { get { return name; } set { name = value; } } + + /// The skeleton's bones, sorted parent first. The root bone is always the first bone. + public ExposedList Bones { get { return bones; } } + + public ExposedList Slots { get { return slots; } } + + /// All skins, including the default skin. + public ExposedList Skins { get { return skins; } set { skins = value; } } + + /// + /// The skeleton's default skin. + /// By default this skin contains all attachments that were not in a skin in Spine. + /// + /// May be null. + public Skin DefaultSkin { get { return defaultSkin; } set { defaultSkin = value; } } + + public ExposedList Events { get { return events; } set { events = value; } } + public ExposedList Animations { get { return animations; } set { animations = value; } } + public ExposedList IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } } + public ExposedList TransformConstraints { get { return transformConstraints; } set { transformConstraints = value; } } + public ExposedList PathConstraints { get { return pathConstraints; } set { pathConstraints = value; } } + + public float Width { get { return width; } set { width = value; } } + public float Height { get { return height; } set { height = value; } } + /// The Spine version used to export this data, or null. + public string Version { get { return version; } set { version = value; } } + public string Hash { get { return hash; } set { hash = value; } } + public string ImagesPath { get { return imagesPath; } set { imagesPath = value; } } + + /// + /// The dopesheet FPS in Spine. Available only when nonessential data was exported. + public float Fps { get { return fps; } set { fps = value; } } + + // --- Bones. + + /// + /// 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. + /// May be null. + public BoneData FindBone(string boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); + var bones = this.bones; + var bonesItems = bones.Items; + for (int i = 0, n = bones.Count; i < n; i++) + { + BoneData bone = bonesItems[i]; + if (bone.name == boneName) return bone; + } + return null; + } + + /// -1 if the bone was not found. + public int FindBoneIndex(string boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); + var bones = this.bones; + var bonesItems = bones.Items; + for (int i = 0, n = bones.Count; i < n; i++) + if (bonesItems[i].name == boneName) return i; + return -1; + } + + // --- Slots. + + /// May be null. + public SlotData FindSlot(string slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + ExposedList slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + { + SlotData slot = slots.Items[i]; + if (slot.name == slotName) return slot; + } + return null; + } + + /// -1 if the slot was not found. + public int FindSlotIndex(string slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + ExposedList slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + if (slots.Items[i].name == slotName) return i; + return -1; + } + + // --- Skins. + + /// May be null. + public Skin FindSkin(string skinName) + { + if (skinName == null) throw new ArgumentNullException("skinName", "skinName cannot be null."); + foreach (Skin skin in skins) + if (skin.name == skinName) return skin; + return null; + } + + // --- Events. + + /// May be null. + public EventData FindEvent(string eventDataName) + { + if (eventDataName == null) throw new ArgumentNullException("eventDataName", "eventDataName cannot be null."); + foreach (EventData eventData in events) + if (eventData.name == eventDataName) return eventData; + return null; + } + + // --- Animations. + + /// May be null. + public Animation FindAnimation(string animationName) + { + if (animationName == null) throw new ArgumentNullException("animationName", "animationName cannot be null."); + ExposedList animations = this.animations; + for (int i = 0, n = animations.Count; i < n; i++) + { + Animation animation = animations.Items[i]; + if (animation.name == animationName) return animation; + } + return null; + } + + // --- IK constraints. + + /// May be null. + public IkConstraintData FindIkConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + ExposedList ikConstraints = this.ikConstraints; + for (int i = 0, n = ikConstraints.Count; i < n; i++) + { + IkConstraintData ikConstraint = ikConstraints.Items[i]; + if (ikConstraint.name == constraintName) return ikConstraint; + } + return null; + } + + // --- Transform constraints. + + /// May be null. + public TransformConstraintData FindTransformConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + ExposedList transformConstraints = this.transformConstraints; + for (int i = 0, n = transformConstraints.Count; i < n; i++) + { + TransformConstraintData transformConstraint = transformConstraints.Items[i]; + if (transformConstraint.name == constraintName) return transformConstraint; + } + return null; + } + + // --- Path constraints. + + /// May be null. + public PathConstraintData FindPathConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + ExposedList pathConstraints = this.pathConstraints; + for (int i = 0, n = pathConstraints.Count; i < n; i++) + { + PathConstraintData constraint = pathConstraints.Items[i]; + if (constraint.name.Equals(constraintName)) return constraint; + } + return null; + } + + /// -1 if the path constraint was not found. + public int FindPathConstraintIndex(string pathConstraintName) + { + if (pathConstraintName == null) throw new ArgumentNullException("pathConstraintName", "pathConstraintName cannot be null."); + ExposedList pathConstraints = this.pathConstraints; + for (int i = 0, n = pathConstraints.Count; i < n; i++) + if (pathConstraints.Items[i].name.Equals(pathConstraintName)) return i; + return -1; + } + + // --- + + override public string ToString() + { + return name ?? base.ToString(); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/SkeletonJson.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/SkeletonJson.cs index a7f7c8f..0e72dda 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/SkeletonJson.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/SkeletonJson.cs @@ -33,32 +33,36 @@ #endif using System; -using System.IO; using System.Collections.Generic; +using System.IO; #if WINDOWS_STOREAPP using System.Threading.Tasks; using Windows.Storage; #endif -namespace Spine3_6_53 { - public class SkeletonJson { - public float Scale { get; set; } +namespace Spine3_6_53 +{ + public class SkeletonJson + { + public float Scale { get; set; } - private AttachmentLoader attachmentLoader; - private List linkedMeshes = new List(); + private AttachmentLoader attachmentLoader; + private List linkedMeshes = new List(); - public SkeletonJson (params Atlas[] atlasArray) - : this(new AtlasAttachmentLoader(atlasArray)) { - } + public SkeletonJson(params Atlas[] atlasArray) + : this(new AtlasAttachmentLoader(atlasArray)) + { + } - public SkeletonJson (AttachmentLoader attachmentLoader) { - if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader", "attachmentLoader cannot be null."); - this.attachmentLoader = attachmentLoader; - Scale = 1; - } + public SkeletonJson(AttachmentLoader attachmentLoader) + { + if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader", "attachmentLoader cannot be null."); + this.attachmentLoader = attachmentLoader; + Scale = 1; + } - #if !IS_UNITY && WINDOWS_STOREAPP +#if !IS_UNITY && WINDOWS_STOREAPP private async Task ReadFile(string path) { var folder = Windows.ApplicationModel.Package.Current.InstalledLocation; var file = await folder.GetFileAsync(path).AsTask().ConfigureAwait(false); @@ -72,794 +76,912 @@ private async Task ReadFile(string path) { public SkeletonData ReadSkeletonData (string path) { return this.ReadFile(path).Result; } - #else - public SkeletonData ReadSkeletonData (string path) { - #if WINDOWS_PHONE +#else + public SkeletonData ReadSkeletonData(string path) + { +#if WINDOWS_PHONE using (var reader = new StreamReader(Microsoft.Xna.Framework.TitleContainer.OpenStream(path))) { - #else - using (var reader = new StreamReader(new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))) { - #endif - SkeletonData skeletonData = ReadSkeletonData(reader); - skeletonData.name = Path.GetFileNameWithoutExtension(path); - return skeletonData; - } - } - #endif - - public SkeletonData ReadSkeletonData (TextReader reader) { - if (reader == null) throw new ArgumentNullException("reader", "reader cannot be null."); - - float scale = this.Scale; - var skeletonData = new SkeletonData(); - - var root = Json.Deserialize(reader) as Dictionary; - if (root == null) throw new Exception("Invalid JSON."); - - // Skeleton. - if (root.ContainsKey("skeleton")) { - var skeletonMap = (Dictionary)root["skeleton"]; - skeletonData.hash = (string)skeletonMap["hash"]; - skeletonData.version = (string)skeletonMap["spine"]; - skeletonData.width = GetFloat(skeletonMap, "width", 0); - skeletonData.height = GetFloat(skeletonMap, "height", 0); - skeletonData.fps = GetFloat(skeletonMap, "fps", 0); - skeletonData.imagesPath = GetString(skeletonMap, "images", null); - } - - // Bones. - foreach (Dictionary boneMap in (List)root["bones"]) { - BoneData parent = null; - if (boneMap.ContainsKey("parent")) { - parent = skeletonData.FindBone((string)boneMap["parent"]); - if (parent == null) - throw new Exception("Parent bone not found: " + boneMap["parent"]); - } - var data = new BoneData(skeletonData.Bones.Count, (string)boneMap["name"], parent); - data.length = GetFloat(boneMap, "length", 0) * scale; - data.x = GetFloat(boneMap, "x", 0) * scale; - data.y = GetFloat(boneMap, "y", 0) * scale; - data.rotation = GetFloat(boneMap, "rotation", 0); - data.scaleX = GetFloat(boneMap, "scaleX", 1); - data.scaleY = GetFloat(boneMap, "scaleY", 1); - data.shearX = GetFloat(boneMap, "shearX", 0); - data.shearY = GetFloat(boneMap, "shearY", 0); - - string tm = GetString(boneMap, "transform", TransformMode.Normal.ToString()); - data.transformMode = (TransformMode)Enum.Parse(typeof(TransformMode), tm, true); - - skeletonData.bones.Add(data); - } - - // Slots. - if (root.ContainsKey("slots")) { - foreach (Dictionary slotMap in (List)root["slots"]) { - var slotName = (string)slotMap["name"]; - var boneName = (string)slotMap["bone"]; - BoneData boneData = skeletonData.FindBone(boneName); - if (boneData == null) throw new Exception("Slot bone not found: " + boneName); - var data = new SlotData(skeletonData.Slots.Count, slotName, boneData); - - if (slotMap.ContainsKey("color")) { - string color = (string)slotMap["color"]; - data.r = ToColor(color, 0); - data.g = ToColor(color, 1); - data.b = ToColor(color, 2); - data.a = ToColor(color, 3); - } - - if (slotMap.ContainsKey("dark")) { - var color2 = (string)slotMap["dark"]; - data.r2 = ToColor(color2, 0, 6); // expectedLength = 6. ie. "RRGGBB" - data.g2 = ToColor(color2, 1, 6); - data.b2 = ToColor(color2, 2, 6); - data.hasSecondColor = true; - } - - data.attachmentName = GetString(slotMap, "attachment", null); - if (slotMap.ContainsKey("blend")) - data.blendMode = (BlendMode)Enum.Parse(typeof(BlendMode), (string)slotMap["blend"], true); - else - data.blendMode = BlendMode.Normal; - skeletonData.slots.Add(data); - } - } - - // IK constraints. - if (root.ContainsKey("ik")) { - foreach (Dictionary constraintMap in (List)root["ik"]) { - IkConstraintData data = new IkConstraintData((string)constraintMap["name"]); - data.order = GetInt(constraintMap, "order", 0); - - foreach (string boneName in (List)constraintMap["bones"]) { - BoneData bone = skeletonData.FindBone(boneName); - if (bone == null) throw new Exception("IK constraint bone not found: " + boneName); - data.bones.Add(bone); - } - - string targetName = (string)constraintMap["target"]; - data.target = skeletonData.FindBone(targetName); - if (data.target == null) throw new Exception("Target bone not found: " + targetName); - - data.bendDirection = GetBoolean(constraintMap, "bendPositive", true) ? 1 : -1; - data.mix = GetFloat(constraintMap, "mix", 1); - - skeletonData.ikConstraints.Add(data); - } - } - - // Transform constraints. - if (root.ContainsKey("transform")) { - foreach (Dictionary constraintMap in (List)root["transform"]) { - TransformConstraintData data = new TransformConstraintData((string)constraintMap["name"]); - data.order = GetInt(constraintMap, "order", 0); - - foreach (string boneName in (List)constraintMap["bones"]) { - BoneData bone = skeletonData.FindBone(boneName); - if (bone == null) throw new Exception("Transform constraint bone not found: " + boneName); - data.bones.Add(bone); - } - - string targetName = (string)constraintMap["target"]; - data.target = skeletonData.FindBone(targetName); - if (data.target == null) throw new Exception("Target bone not found: " + targetName); - - data.local = GetBoolean(constraintMap, "local", false); - data.relative = GetBoolean(constraintMap, "relative", false); - - data.offsetRotation = GetFloat(constraintMap, "rotation", 0); - data.offsetX = GetFloat(constraintMap, "x", 0) * scale; - data.offsetY = GetFloat(constraintMap, "y", 0) * scale; - data.offsetScaleX = GetFloat(constraintMap, "scaleX", 0); - data.offsetScaleY = GetFloat(constraintMap, "scaleY", 0); - data.offsetShearY = GetFloat(constraintMap, "shearY", 0); - - data.rotateMix = GetFloat(constraintMap, "rotateMix", 1); - data.translateMix = GetFloat(constraintMap, "translateMix", 1); - data.scaleMix = GetFloat(constraintMap, "scaleMix", 1); - data.shearMix = GetFloat(constraintMap, "shearMix", 1); - - skeletonData.transformConstraints.Add(data); - } - } - - // Path constraints. - if(root.ContainsKey("path")) { - foreach (Dictionary constraintMap in (List)root["path"]) { - PathConstraintData data = new PathConstraintData((string)constraintMap["name"]); - data.order = GetInt(constraintMap, "order", 0); - - foreach (string boneName in (List)constraintMap["bones"]) { - BoneData bone = skeletonData.FindBone(boneName); - if (bone == null) throw new Exception("Path bone not found: " + boneName); - data.bones.Add(bone); - } - - string targetName = (string)constraintMap["target"]; - data.target = skeletonData.FindSlot(targetName); - if (data.target == null) throw new Exception("Target slot not found: " + targetName); - - data.positionMode = (PositionMode)Enum.Parse(typeof(PositionMode), GetString(constraintMap, "positionMode", "percent"), true); - data.spacingMode = (SpacingMode)Enum.Parse(typeof(SpacingMode), GetString(constraintMap, "spacingMode", "length"), true); - data.rotateMode = (RotateMode)Enum.Parse(typeof(RotateMode), GetString(constraintMap, "rotateMode", "tangent"), true); - data.offsetRotation = GetFloat(constraintMap, "rotation", 0); - data.position = GetFloat(constraintMap, "position", 0); - if (data.positionMode == PositionMode.Fixed) data.position *= scale; - data.spacing = GetFloat(constraintMap, "spacing", 0); - if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale; - data.rotateMix = GetFloat(constraintMap, "rotateMix", 1); - data.translateMix = GetFloat(constraintMap, "translateMix", 1); - - skeletonData.pathConstraints.Add(data); - } - } - - // Skins. - if (root.ContainsKey("skins")) { - foreach (KeyValuePair skinMap in (Dictionary)root["skins"]) { - var skin = new Skin(skinMap.Key); - foreach (KeyValuePair slotEntry in (Dictionary)skinMap.Value) { - int slotIndex = skeletonData.FindSlotIndex(slotEntry.Key); - foreach (KeyValuePair entry in ((Dictionary)slotEntry.Value)) { - try { - Attachment attachment = ReadAttachment((Dictionary)entry.Value, skin, slotIndex, entry.Key, skeletonData); - if (attachment != null) skin.AddAttachment(slotIndex, entry.Key, attachment); - } catch (Exception e) { - throw new Exception("Error reading attachment: " + entry.Key + ", skin: " + skin, e); - } - } - } - skeletonData.skins.Add(skin); - if (skin.name == "default") skeletonData.defaultSkin = skin; - } - } - - // Linked meshes. - for (int i = 0, n = linkedMeshes.Count; i < n; i++) { - LinkedMesh linkedMesh = linkedMeshes[i]; - Skin skin = linkedMesh.skin == null ? skeletonData.defaultSkin : skeletonData.FindSkin(linkedMesh.skin); - if (skin == null) throw new Exception("Slot not found: " + linkedMesh.skin); - Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); - if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); - linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; - linkedMesh.mesh.UpdateUVs(); - } - linkedMeshes.Clear(); - - // Events. - if (root.ContainsKey("events")) { - foreach (KeyValuePair entry in (Dictionary)root["events"]) { - var entryMap = (Dictionary)entry.Value; - var data = new EventData(entry.Key); - data.Int = GetInt(entryMap, "int", 0); - data.Float = GetFloat(entryMap, "float", 0); - data.String = GetString(entryMap, "string", string.Empty); - skeletonData.events.Add(data); - } - } - - // Animations. - if (root.ContainsKey("animations")) { - foreach (KeyValuePair entry in (Dictionary)root["animations"]) { - try { - ReadAnimation((Dictionary)entry.Value, entry.Key, skeletonData); - } catch (Exception e) { - throw new Exception("Error reading animation: " + entry.Key, e); - } - } - } - - skeletonData.bones.TrimExcess(); - skeletonData.slots.TrimExcess(); - skeletonData.skins.TrimExcess(); - skeletonData.events.TrimExcess(); - skeletonData.animations.TrimExcess(); - skeletonData.ikConstraints.TrimExcess(); - return skeletonData; - } - - private Attachment ReadAttachment (Dictionary map, Skin skin, int slotIndex, string name, SkeletonData skeletonData) { - float scale = this.Scale; - name = GetString(map, "name", name); - - var typeName = GetString(map, "type", "region"); - if (typeName == "skinnedmesh") typeName = "weightedmesh"; - if (typeName == "weightedmesh") typeName = "mesh"; - if (typeName == "weightedlinkedmesh") typeName = "linkedmesh"; - var type = (AttachmentType)Enum.Parse(typeof(AttachmentType), typeName, true); - - string path = GetString(map, "path", name); - - switch (type) { - case AttachmentType.Region: - RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); - if (region == null) return null; - region.Path = path; - region.x = GetFloat(map, "x", 0) * scale; - region.y = GetFloat(map, "y", 0) * scale; - region.scaleX = GetFloat(map, "scaleX", 1); - region.scaleY = GetFloat(map, "scaleY", 1); - region.rotation = GetFloat(map, "rotation", 0); - region.width = GetFloat(map, "width", 32) * scale; - region.height = GetFloat(map, "height", 32) * scale; - - if (map.ContainsKey("color")) { - var color = (string)map["color"]; - region.r = ToColor(color, 0); - region.g = ToColor(color, 1); - region.b = ToColor(color, 2); - region.a = ToColor(color, 3); - } - - region.UpdateOffset(); - return region; - case AttachmentType.Boundingbox: - BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); - if (box == null) return null; - ReadVertices(map, box, GetInt(map, "vertexCount", 0) << 1); - return box; - case AttachmentType.Mesh: - case AttachmentType.Linkedmesh: { - MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); - if (mesh == null) return null; - mesh.Path = path; - - if (map.ContainsKey("color")) { - var color = (string)map["color"]; - mesh.r = ToColor(color, 0); - mesh.g = ToColor(color, 1); - mesh.b = ToColor(color, 2); - mesh.a = ToColor(color, 3); - } - - mesh.Width = GetFloat(map, "width", 0) * scale; - mesh.Height = GetFloat(map, "height", 0) * scale; - - string parent = GetString(map, "parent", null); - if (parent != null) { - mesh.InheritDeform = GetBoolean(map, "deform", true); - linkedMeshes.Add(new LinkedMesh(mesh, GetString(map, "skin", null), slotIndex, parent)); - return mesh; - } - - float[] uvs = GetFloatArray(map, "uvs", 1); - ReadVertices(map, mesh, uvs.Length); - mesh.triangles = GetIntArray(map, "triangles"); - mesh.regionUVs = uvs; - mesh.UpdateUVs(); - - if (map.ContainsKey("hull")) mesh.HullLength = GetInt(map, "hull", 0) * 2; - if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges"); - return mesh; - } - case AttachmentType.Path: { - PathAttachment pathAttachment = attachmentLoader.NewPathAttachment(skin, name); - if (pathAttachment == null) return null; - pathAttachment.closed = GetBoolean(map, "closed", false); - pathAttachment.constantSpeed = GetBoolean(map, "constantSpeed", true); - - int vertexCount = GetInt(map, "vertexCount", 0); - ReadVertices(map, pathAttachment, vertexCount << 1); - - // potential BOZO see Java impl - pathAttachment.lengths = GetFloatArray(map, "lengths", scale); - return pathAttachment; - } - case AttachmentType.Point: { - PointAttachment point = attachmentLoader.NewPointAttachment(skin, name); - if (point == null) return null; - point.x = GetFloat(map, "x", 0) * scale; - point.y = GetFloat(map, "y", 0) * scale; - point.rotation = GetFloat(map, "rotation", 0); - - //string color = GetString(map, "color", null); - //if (color != null) point.color = color; - return point; - } - case AttachmentType.Clipping: { - ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name); - if (clip == null) return null; - - string end = GetString(map, "end", null); - if (end != null) { - SlotData slot = skeletonData.FindSlot(end); - if (slot == null) throw new Exception("Clipping end slot not found: " + end); - clip.EndSlot = slot; - } - - ReadVertices(map, clip, GetInt(map, "vertexCount", 0) << 1); - - //string color = GetString(map, "color", null); - // if (color != null) clip.color = color; - return clip; - } - } - return null; - } - - private void ReadVertices (Dictionary map, VertexAttachment attachment, int verticesLength) { - attachment.WorldVerticesLength = verticesLength; - float[] vertices = GetFloatArray(map, "vertices", 1); - float scale = Scale; - if (verticesLength == vertices.Length) { - if (scale != 1) { - for (int i = 0; i < vertices.Length; i++) { - vertices[i] *= scale; - } - } - attachment.vertices = vertices; - return; - } - ExposedList weights = new ExposedList(verticesLength * 3 * 3); - ExposedList bones = new ExposedList(verticesLength * 3); - for (int i = 0, n = vertices.Length; i < n;) { - int boneCount = (int)vertices[i++]; - bones.Add(boneCount); - for (int nn = i + boneCount * 4; i < nn; i += 4) { - bones.Add((int)vertices[i]); - weights.Add(vertices[i + 1] * this.Scale); - weights.Add(vertices[i + 2] * this.Scale); - weights.Add(vertices[i + 3]); - } - } - attachment.bones = bones.ToArray(); - attachment.vertices = weights.ToArray(); - } - - private void ReadAnimation (Dictionary map, string name, SkeletonData skeletonData) { - var scale = this.Scale; - var timelines = new ExposedList(); - float duration = 0; - - // Slot timelines. - if (map.ContainsKey("slots")) { - foreach (KeyValuePair entry in (Dictionary)map["slots"]) { - string slotName = entry.Key; - int slotIndex = skeletonData.FindSlotIndex(slotName); - var timelineMap = (Dictionary)entry.Value; - foreach (KeyValuePair timelineEntry in timelineMap) { - var values = (List)timelineEntry.Value; - var timelineName = (string)timelineEntry.Key; - if (timelineName == "attachment") { - var timeline = new AttachmentTimeline(values.Count); - timeline.slotIndex = slotIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - timeline.SetFrame(frameIndex++, time, (string)valueMap["name"]); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); - - } else if (timelineName == "color") { - var timeline = new ColorTimeline(values.Count); - timeline.slotIndex = slotIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - string c = (string)valueMap["color"]; - timeline.SetFrame(frameIndex, time, ToColor(c, 0), ToColor(c, 1), ToColor(c, 2), ToColor(c, 3)); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * ColorTimeline.ENTRIES]); - - } else if (timelineName == "twoColor") { - var timeline = new TwoColorTimeline(values.Count); - timeline.slotIndex = slotIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - string light = (string)valueMap["light"]; - string dark = (string)valueMap["dark"]; - timeline.SetFrame(frameIndex, time, ToColor(light, 0), ToColor(light, 1), ToColor(light, 2), ToColor(light, 3), - ToColor(dark, 0, 6), ToColor(dark, 1, 6), ToColor(dark, 2, 6)); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TwoColorTimeline.ENTRIES]); - - } else - throw new Exception("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")"); - } - } - } - - // Bone timelines. - if (map.ContainsKey("bones")) { - foreach (KeyValuePair entry in (Dictionary)map["bones"]) { - string boneName = entry.Key; - int boneIndex = skeletonData.FindBoneIndex(boneName); - if (boneIndex == -1) throw new Exception("Bone not found: " + boneName); - var timelineMap = (Dictionary)entry.Value; - foreach (KeyValuePair timelineEntry in timelineMap) { - var values = (List)timelineEntry.Value; - var timelineName = (string)timelineEntry.Key; - if (timelineName == "rotate") { - var timeline = new RotateTimeline(values.Count); - timeline.boneIndex = boneIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - timeline.SetFrame(frameIndex, (float)valueMap["time"], (float)valueMap["angle"]); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * RotateTimeline.ENTRIES]); - - } else if (timelineName == "translate" || timelineName == "scale" || timelineName == "shear") { - TranslateTimeline timeline; - float timelineScale = 1; - if (timelineName == "scale") - timeline = new ScaleTimeline(values.Count); - else if (timelineName == "shear") - timeline = new ShearTimeline(values.Count); - else { - timeline = new TranslateTimeline(values.Count); - timelineScale = scale; - } - timeline.boneIndex = boneIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - float x = GetFloat(valueMap, "x", 0); - float y = GetFloat(valueMap, "y", 0); - timeline.SetFrame(frameIndex, time, x * timelineScale, y * timelineScale); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TranslateTimeline.ENTRIES]); - - } else - throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")"); - } - } - } - - // IK constraint timelines. - if (map.ContainsKey("ik")) { - foreach (KeyValuePair constraintMap in (Dictionary)map["ik"]) { - IkConstraintData constraint = skeletonData.FindIkConstraint(constraintMap.Key); - var values = (List)constraintMap.Value; - var timeline = new IkConstraintTimeline(values.Count); - timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(constraint); - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - float mix = GetFloat(valueMap, "mix", 1); - bool bendPositive = GetBoolean(valueMap, "bendPositive", true); - timeline.SetFrame(frameIndex, time, mix, bendPositive ? 1 : -1); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * IkConstraintTimeline.ENTRIES]); - } - } - - // Transform constraint timelines. - if (map.ContainsKey("transform")) { - foreach (KeyValuePair constraintMap in (Dictionary)map["transform"]) { - TransformConstraintData constraint = skeletonData.FindTransformConstraint(constraintMap.Key); - var values = (List)constraintMap.Value; - var timeline = new TransformConstraintTimeline(values.Count); - timeline.transformConstraintIndex = skeletonData.transformConstraints.IndexOf(constraint); - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - float rotateMix = GetFloat(valueMap, "rotateMix", 1); - float translateMix = GetFloat(valueMap, "translateMix", 1); - float scaleMix = GetFloat(valueMap, "scaleMix", 1); - float shearMix = GetFloat(valueMap, "shearMix", 1); - timeline.SetFrame(frameIndex, time, rotateMix, translateMix, scaleMix, shearMix); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TransformConstraintTimeline.ENTRIES]); - } - } - - // Path constraint timelines. - if (map.ContainsKey("paths")) { - foreach (KeyValuePair constraintMap in (Dictionary)map["paths"]) { - int index = skeletonData.FindPathConstraintIndex(constraintMap.Key); - if (index == -1) throw new Exception("Path constraint not found: " + constraintMap.Key); - PathConstraintData data = skeletonData.pathConstraints.Items[index]; - var timelineMap = (Dictionary)constraintMap.Value; - foreach (KeyValuePair timelineEntry in timelineMap) { - var values = (List)timelineEntry.Value; - var timelineName = (string)timelineEntry.Key; - if (timelineName == "position" || timelineName == "spacing") { - PathConstraintPositionTimeline timeline; - float timelineScale = 1; - if (timelineName == "spacing") { - timeline = new PathConstraintSpacingTimeline(values.Count); - if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale; - } - else { - timeline = new PathConstraintPositionTimeline(values.Count); - if (data.positionMode == PositionMode.Fixed) timelineScale = scale; - } - timeline.pathConstraintIndex = index; - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - timeline.SetFrame(frameIndex, (float)valueMap["time"], GetFloat(valueMap, timelineName, 0) * timelineScale); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintPositionTimeline.ENTRIES]); - } - else if (timelineName == "mix") { - PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(values.Count); - timeline.pathConstraintIndex = index; - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - timeline.SetFrame(frameIndex, (float)valueMap["time"], GetFloat(valueMap, "rotateMix", 1), GetFloat(valueMap, "translateMix", 1)); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintMixTimeline.ENTRIES]); - } - } - } - } - - // Deform timelines. - if (map.ContainsKey("deform")) { - foreach (KeyValuePair deformMap in (Dictionary)map["deform"]) { - Skin skin = skeletonData.FindSkin(deformMap.Key); - foreach (KeyValuePair slotMap in (Dictionary)deformMap.Value) { - int slotIndex = skeletonData.FindSlotIndex(slotMap.Key); - if (slotIndex == -1) throw new Exception("Slot not found: " + slotMap.Key); - foreach (KeyValuePair timelineMap in (Dictionary)slotMap.Value) { - var values = (List)timelineMap.Value; - VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, timelineMap.Key); - if (attachment == null) throw new Exception("Deform attachment not found: " + timelineMap.Key); - bool weighted = attachment.bones != null; - float[] vertices = attachment.vertices; - int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length; - - var timeline = new DeformTimeline(values.Count); - timeline.slotIndex = slotIndex; - timeline.attachment = attachment; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float[] deform; - if (!valueMap.ContainsKey("vertices")) { - deform = weighted ? new float[deformLength] : vertices; - } else { - deform = new float[deformLength]; - int start = GetInt(valueMap, "offset", 0); - float[] verticesValue = GetFloatArray(valueMap, "vertices", 1); - Array.Copy(verticesValue, 0, deform, start, verticesValue.Length); - if (scale != 1) { - for (int i = start, n = i + verticesValue.Length; i < n; i++) - deform[i] *= scale; - } - - if (!weighted) { - for (int i = 0; i < deformLength; i++) - deform[i] += vertices[i]; - } - } - - timeline.SetFrame(frameIndex, (float)valueMap["time"], deform); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); - } - } - } - } - - // Draw order timeline. - if (map.ContainsKey("drawOrder") || map.ContainsKey("draworder")) { - var values = (List)map[map.ContainsKey("drawOrder") ? "drawOrder" : "draworder"]; - var timeline = new DrawOrderTimeline(values.Count); - int slotCount = skeletonData.slots.Count; - int frameIndex = 0; - foreach (Dictionary drawOrderMap in values) { - int[] drawOrder = null; - if (drawOrderMap.ContainsKey("offsets")) { - drawOrder = new int[slotCount]; - for (int i = slotCount - 1; i >= 0; i--) - drawOrder[i] = -1; - var offsets = (List)drawOrderMap["offsets"]; - int[] unchanged = new int[slotCount - offsets.Count]; - int originalIndex = 0, unchangedIndex = 0; - foreach (Dictionary offsetMap in offsets) { - int slotIndex = skeletonData.FindSlotIndex((string)offsetMap["slot"]); - if (slotIndex == -1) throw new Exception("Slot not found: " + offsetMap["slot"]); - // Collect unchanged items. - while (originalIndex != slotIndex) - unchanged[unchangedIndex++] = originalIndex++; - // Set changed items. - int index = originalIndex + (int)(float)offsetMap["offset"]; - drawOrder[index] = originalIndex++; - } - // Collect remaining unchanged items. - while (originalIndex < slotCount) - unchanged[unchangedIndex++] = originalIndex++; - // Fill in unchanged items. - for (int i = slotCount - 1; i >= 0; i--) - if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex]; - } - timeline.SetFrame(frameIndex++, (float)drawOrderMap["time"], drawOrder); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); - } - - // Event timeline. - if (map.ContainsKey("events")) { - var eventsMap = (List)map["events"]; - var timeline = new EventTimeline(eventsMap.Count); - int frameIndex = 0; - foreach (Dictionary eventMap in eventsMap) { - EventData eventData = skeletonData.FindEvent((string)eventMap["name"]); - if (eventData == null) throw new Exception("Event not found: " + eventMap["name"]); - var e = new Event((float)eventMap["time"], eventData); - e.Int = GetInt(eventMap, "int", eventData.Int); - e.Float = GetFloat(eventMap, "float", eventData.Float); - e.String = GetString(eventMap, "string", eventData.String); - timeline.SetFrame(frameIndex++, e); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); - } - - timelines.TrimExcess(); - skeletonData.animations.Add(new Animation(name, timelines, duration)); - } - - static void ReadCurve (Dictionary valueMap, CurveTimeline timeline, int frameIndex) { - if (!valueMap.ContainsKey("curve")) - return; - Object curveObject = valueMap["curve"]; - if (curveObject.Equals("stepped")) - timeline.SetStepped(frameIndex); - else { - var curve = curveObject as List; - if (curve != null) - timeline.SetCurve(frameIndex, (float)curve[0], (float)curve[1], (float)curve[2], (float)curve[3]); - } - } - - internal class LinkedMesh { - internal string parent, skin; - internal int slotIndex; - internal MeshAttachment mesh; - - public LinkedMesh (MeshAttachment mesh, string skin, int slotIndex, string parent) { - this.mesh = mesh; - this.skin = skin; - this.slotIndex = slotIndex; - this.parent = parent; - } - } - - static float[] GetFloatArray(Dictionary map, string name, float scale) { - var list = (List)map[name]; - var values = new float[list.Count]; - if (scale == 1) { - for (int i = 0, n = list.Count; i < n; i++) - values[i] = (float)list[i]; - } else { - for (int i = 0, n = list.Count; i < n; i++) - values[i] = (float)list[i] * scale; - } - return values; - } - - static int[] GetIntArray(Dictionary map, string name) { - var list = (List)map[name]; - var values = new int[list.Count]; - for (int i = 0, n = list.Count; i < n; i++) - values[i] = (int)(float)list[i]; - return values; - } - - static float GetFloat(Dictionary map, string name, float defaultValue) { - if (!map.ContainsKey(name)) - return defaultValue; - return (float)map[name]; - } - - static int GetInt(Dictionary map, string name, int defaultValue) { - if (!map.ContainsKey(name)) - return defaultValue; - return (int)(float)map[name]; - } - - static bool GetBoolean(Dictionary map, string name, bool defaultValue) { - if (!map.ContainsKey(name)) - return defaultValue; - return (bool)map[name]; - } - - static string GetString(Dictionary map, string name, string defaultValue) { - if (!map.ContainsKey(name)) - return defaultValue; - return (string)map[name]; - } +#else + using (var reader = new StreamReader(new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))) + { +#endif + SkeletonData skeletonData = ReadSkeletonData(reader); + skeletonData.name = Path.GetFileNameWithoutExtension(path); + return skeletonData; + } + } +#endif - static float ToColor(string hexString, int colorIndex, int expectedLength = 8) { - if (hexString.Length != expectedLength) - throw new ArgumentException("Color hexidecimal length must be " + expectedLength + ", recieved: " + hexString, "hexString"); - return Convert.ToInt32(hexString.Substring(colorIndex * 2, 2), 16) / (float)255; - } - } + public SkeletonData ReadSkeletonData(TextReader reader) + { + if (reader == null) throw new ArgumentNullException("reader", "reader cannot be null."); + + float scale = this.Scale; + var skeletonData = new SkeletonData(); + + var root = Json.Deserialize(reader) as Dictionary; + if (root == null) throw new Exception("Invalid JSON."); + + // Skeleton. + if (root.ContainsKey("skeleton")) + { + var skeletonMap = (Dictionary)root["skeleton"]; + skeletonData.hash = (string)skeletonMap["hash"]; + skeletonData.version = (string)skeletonMap["spine"]; + skeletonData.width = GetFloat(skeletonMap, "width", 0); + skeletonData.height = GetFloat(skeletonMap, "height", 0); + skeletonData.fps = GetFloat(skeletonMap, "fps", 0); + skeletonData.imagesPath = GetString(skeletonMap, "images", null); + } + + // Bones. + foreach (Dictionary boneMap in (List)root["bones"]) + { + BoneData parent = null; + if (boneMap.ContainsKey("parent")) + { + parent = skeletonData.FindBone((string)boneMap["parent"]); + if (parent == null) + throw new Exception("Parent bone not found: " + boneMap["parent"]); + } + var data = new BoneData(skeletonData.Bones.Count, (string)boneMap["name"], parent); + data.length = GetFloat(boneMap, "length", 0) * scale; + data.x = GetFloat(boneMap, "x", 0) * scale; + data.y = GetFloat(boneMap, "y", 0) * scale; + data.rotation = GetFloat(boneMap, "rotation", 0); + data.scaleX = GetFloat(boneMap, "scaleX", 1); + data.scaleY = GetFloat(boneMap, "scaleY", 1); + data.shearX = GetFloat(boneMap, "shearX", 0); + data.shearY = GetFloat(boneMap, "shearY", 0); + + string tm = GetString(boneMap, "transform", TransformMode.Normal.ToString()); + data.transformMode = (TransformMode)Enum.Parse(typeof(TransformMode), tm, true); + + skeletonData.bones.Add(data); + } + + // Slots. + if (root.ContainsKey("slots")) + { + foreach (Dictionary slotMap in (List)root["slots"]) + { + var slotName = (string)slotMap["name"]; + var boneName = (string)slotMap["bone"]; + BoneData boneData = skeletonData.FindBone(boneName); + if (boneData == null) throw new Exception("Slot bone not found: " + boneName); + var data = new SlotData(skeletonData.Slots.Count, slotName, boneData); + + if (slotMap.ContainsKey("color")) + { + string color = (string)slotMap["color"]; + data.r = ToColor(color, 0); + data.g = ToColor(color, 1); + data.b = ToColor(color, 2); + data.a = ToColor(color, 3); + } + + if (slotMap.ContainsKey("dark")) + { + var color2 = (string)slotMap["dark"]; + data.r2 = ToColor(color2, 0, 6); // expectedLength = 6. ie. "RRGGBB" + data.g2 = ToColor(color2, 1, 6); + data.b2 = ToColor(color2, 2, 6); + data.hasSecondColor = true; + } + + data.attachmentName = GetString(slotMap, "attachment", null); + if (slotMap.ContainsKey("blend")) + data.blendMode = (BlendMode)Enum.Parse(typeof(BlendMode), (string)slotMap["blend"], true); + else + data.blendMode = BlendMode.Normal; + skeletonData.slots.Add(data); + } + } + + // IK constraints. + if (root.ContainsKey("ik")) + { + foreach (Dictionary constraintMap in (List)root["ik"]) + { + IkConstraintData data = new IkConstraintData((string)constraintMap["name"]); + data.order = GetInt(constraintMap, "order", 0); + + foreach (string boneName in (List)constraintMap["bones"]) + { + BoneData bone = skeletonData.FindBone(boneName); + if (bone == null) throw new Exception("IK constraint bone not found: " + boneName); + data.bones.Add(bone); + } + + string targetName = (string)constraintMap["target"]; + data.target = skeletonData.FindBone(targetName); + if (data.target == null) throw new Exception("Target bone not found: " + targetName); + + data.bendDirection = GetBoolean(constraintMap, "bendPositive", true) ? 1 : -1; + data.mix = GetFloat(constraintMap, "mix", 1); + + skeletonData.ikConstraints.Add(data); + } + } + + // Transform constraints. + if (root.ContainsKey("transform")) + { + foreach (Dictionary constraintMap in (List)root["transform"]) + { + TransformConstraintData data = new TransformConstraintData((string)constraintMap["name"]); + data.order = GetInt(constraintMap, "order", 0); + + foreach (string boneName in (List)constraintMap["bones"]) + { + BoneData bone = skeletonData.FindBone(boneName); + if (bone == null) throw new Exception("Transform constraint bone not found: " + boneName); + data.bones.Add(bone); + } + + string targetName = (string)constraintMap["target"]; + data.target = skeletonData.FindBone(targetName); + if (data.target == null) throw new Exception("Target bone not found: " + targetName); + + data.local = GetBoolean(constraintMap, "local", false); + data.relative = GetBoolean(constraintMap, "relative", false); + + data.offsetRotation = GetFloat(constraintMap, "rotation", 0); + data.offsetX = GetFloat(constraintMap, "x", 0) * scale; + data.offsetY = GetFloat(constraintMap, "y", 0) * scale; + data.offsetScaleX = GetFloat(constraintMap, "scaleX", 0); + data.offsetScaleY = GetFloat(constraintMap, "scaleY", 0); + data.offsetShearY = GetFloat(constraintMap, "shearY", 0); + + data.rotateMix = GetFloat(constraintMap, "rotateMix", 1); + data.translateMix = GetFloat(constraintMap, "translateMix", 1); + data.scaleMix = GetFloat(constraintMap, "scaleMix", 1); + data.shearMix = GetFloat(constraintMap, "shearMix", 1); + + skeletonData.transformConstraints.Add(data); + } + } + + // Path constraints. + if (root.ContainsKey("path")) + { + foreach (Dictionary constraintMap in (List)root["path"]) + { + PathConstraintData data = new PathConstraintData((string)constraintMap["name"]); + data.order = GetInt(constraintMap, "order", 0); + + foreach (string boneName in (List)constraintMap["bones"]) + { + BoneData bone = skeletonData.FindBone(boneName); + if (bone == null) throw new Exception("Path bone not found: " + boneName); + data.bones.Add(bone); + } + + string targetName = (string)constraintMap["target"]; + data.target = skeletonData.FindSlot(targetName); + if (data.target == null) throw new Exception("Target slot not found: " + targetName); + + data.positionMode = (PositionMode)Enum.Parse(typeof(PositionMode), GetString(constraintMap, "positionMode", "percent"), true); + data.spacingMode = (SpacingMode)Enum.Parse(typeof(SpacingMode), GetString(constraintMap, "spacingMode", "length"), true); + data.rotateMode = (RotateMode)Enum.Parse(typeof(RotateMode), GetString(constraintMap, "rotateMode", "tangent"), true); + data.offsetRotation = GetFloat(constraintMap, "rotation", 0); + data.position = GetFloat(constraintMap, "position", 0); + if (data.positionMode == PositionMode.Fixed) data.position *= scale; + data.spacing = GetFloat(constraintMap, "spacing", 0); + if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale; + data.rotateMix = GetFloat(constraintMap, "rotateMix", 1); + data.translateMix = GetFloat(constraintMap, "translateMix", 1); + + skeletonData.pathConstraints.Add(data); + } + } + + // Skins. + if (root.ContainsKey("skins")) + { + foreach (KeyValuePair skinMap in (Dictionary)root["skins"]) + { + var skin = new Skin(skinMap.Key); + foreach (KeyValuePair slotEntry in (Dictionary)skinMap.Value) + { + int slotIndex = skeletonData.FindSlotIndex(slotEntry.Key); + foreach (KeyValuePair entry in ((Dictionary)slotEntry.Value)) + { + try + { + Attachment attachment = ReadAttachment((Dictionary)entry.Value, skin, slotIndex, entry.Key, skeletonData); + if (attachment != null) skin.AddAttachment(slotIndex, entry.Key, attachment); + } + catch (Exception e) + { + throw new Exception("Error reading attachment: " + entry.Key + ", skin: " + skin, e); + } + } + } + skeletonData.skins.Add(skin); + if (skin.name == "default") skeletonData.defaultSkin = skin; + } + } + + // Linked meshes. + for (int i = 0, n = linkedMeshes.Count; i < n; i++) + { + LinkedMesh linkedMesh = linkedMeshes[i]; + Skin skin = linkedMesh.skin == null ? skeletonData.defaultSkin : skeletonData.FindSkin(linkedMesh.skin); + if (skin == null) throw new Exception("Slot not found: " + linkedMesh.skin); + Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); + if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); + linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; + linkedMesh.mesh.UpdateUVs(); + } + linkedMeshes.Clear(); + + // Events. + if (root.ContainsKey("events")) + { + foreach (KeyValuePair entry in (Dictionary)root["events"]) + { + var entryMap = (Dictionary)entry.Value; + var data = new EventData(entry.Key); + data.Int = GetInt(entryMap, "int", 0); + data.Float = GetFloat(entryMap, "float", 0); + data.String = GetString(entryMap, "string", string.Empty); + skeletonData.events.Add(data); + } + } + + // Animations. + if (root.ContainsKey("animations")) + { + foreach (KeyValuePair entry in (Dictionary)root["animations"]) + { + try + { + ReadAnimation((Dictionary)entry.Value, entry.Key, skeletonData); + } + catch (Exception e) + { + throw new Exception("Error reading animation: " + entry.Key, e); + } + } + } + + skeletonData.bones.TrimExcess(); + skeletonData.slots.TrimExcess(); + skeletonData.skins.TrimExcess(); + skeletonData.events.TrimExcess(); + skeletonData.animations.TrimExcess(); + skeletonData.ikConstraints.TrimExcess(); + return skeletonData; + } + + private Attachment ReadAttachment(Dictionary map, Skin skin, int slotIndex, string name, SkeletonData skeletonData) + { + float scale = this.Scale; + name = GetString(map, "name", name); + + var typeName = GetString(map, "type", "region"); + if (typeName == "skinnedmesh") typeName = "weightedmesh"; + if (typeName == "weightedmesh") typeName = "mesh"; + if (typeName == "weightedlinkedmesh") typeName = "linkedmesh"; + var type = (AttachmentType)Enum.Parse(typeof(AttachmentType), typeName, true); + + string path = GetString(map, "path", name); + + switch (type) + { + case AttachmentType.Region: + RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); + if (region == null) return null; + region.Path = path; + region.x = GetFloat(map, "x", 0) * scale; + region.y = GetFloat(map, "y", 0) * scale; + region.scaleX = GetFloat(map, "scaleX", 1); + region.scaleY = GetFloat(map, "scaleY", 1); + region.rotation = GetFloat(map, "rotation", 0); + region.width = GetFloat(map, "width", 32) * scale; + region.height = GetFloat(map, "height", 32) * scale; + + if (map.ContainsKey("color")) + { + var color = (string)map["color"]; + region.r = ToColor(color, 0); + region.g = ToColor(color, 1); + region.b = ToColor(color, 2); + region.a = ToColor(color, 3); + } + + region.UpdateOffset(); + return region; + case AttachmentType.Boundingbox: + BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); + if (box == null) return null; + ReadVertices(map, box, GetInt(map, "vertexCount", 0) << 1); + return box; + case AttachmentType.Mesh: + case AttachmentType.Linkedmesh: + { + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); + if (mesh == null) return null; + mesh.Path = path; + + if (map.ContainsKey("color")) + { + var color = (string)map["color"]; + mesh.r = ToColor(color, 0); + mesh.g = ToColor(color, 1); + mesh.b = ToColor(color, 2); + mesh.a = ToColor(color, 3); + } + + mesh.Width = GetFloat(map, "width", 0) * scale; + mesh.Height = GetFloat(map, "height", 0) * scale; + + string parent = GetString(map, "parent", null); + if (parent != null) + { + mesh.InheritDeform = GetBoolean(map, "deform", true); + linkedMeshes.Add(new LinkedMesh(mesh, GetString(map, "skin", null), slotIndex, parent)); + return mesh; + } + + float[] uvs = GetFloatArray(map, "uvs", 1); + ReadVertices(map, mesh, uvs.Length); + mesh.triangles = GetIntArray(map, "triangles"); + mesh.regionUVs = uvs; + mesh.UpdateUVs(); + + if (map.ContainsKey("hull")) mesh.HullLength = GetInt(map, "hull", 0) * 2; + if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges"); + return mesh; + } + case AttachmentType.Path: + { + PathAttachment pathAttachment = attachmentLoader.NewPathAttachment(skin, name); + if (pathAttachment == null) return null; + pathAttachment.closed = GetBoolean(map, "closed", false); + pathAttachment.constantSpeed = GetBoolean(map, "constantSpeed", true); + + int vertexCount = GetInt(map, "vertexCount", 0); + ReadVertices(map, pathAttachment, vertexCount << 1); + + // potential BOZO see Java impl + pathAttachment.lengths = GetFloatArray(map, "lengths", scale); + return pathAttachment; + } + case AttachmentType.Point: + { + PointAttachment point = attachmentLoader.NewPointAttachment(skin, name); + if (point == null) return null; + point.x = GetFloat(map, "x", 0) * scale; + point.y = GetFloat(map, "y", 0) * scale; + point.rotation = GetFloat(map, "rotation", 0); + + //string color = GetString(map, "color", null); + //if (color != null) point.color = color; + return point; + } + case AttachmentType.Clipping: + { + ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name); + if (clip == null) return null; + + string end = GetString(map, "end", null); + if (end != null) + { + SlotData slot = skeletonData.FindSlot(end); + if (slot == null) throw new Exception("Clipping end slot not found: " + end); + clip.EndSlot = slot; + } + + ReadVertices(map, clip, GetInt(map, "vertexCount", 0) << 1); + + //string color = GetString(map, "color", null); + // if (color != null) clip.color = color; + return clip; + } + } + return null; + } + + private void ReadVertices(Dictionary map, VertexAttachment attachment, int verticesLength) + { + attachment.WorldVerticesLength = verticesLength; + float[] vertices = GetFloatArray(map, "vertices", 1); + float scale = Scale; + if (verticesLength == vertices.Length) + { + if (scale != 1) + { + for (int i = 0; i < vertices.Length; i++) + { + vertices[i] *= scale; + } + } + attachment.vertices = vertices; + return; + } + ExposedList weights = new ExposedList(verticesLength * 3 * 3); + ExposedList bones = new ExposedList(verticesLength * 3); + for (int i = 0, n = vertices.Length; i < n;) + { + int boneCount = (int)vertices[i++]; + bones.Add(boneCount); + for (int nn = i + boneCount * 4; i < nn; i += 4) + { + bones.Add((int)vertices[i]); + weights.Add(vertices[i + 1] * this.Scale); + weights.Add(vertices[i + 2] * this.Scale); + weights.Add(vertices[i + 3]); + } + } + attachment.bones = bones.ToArray(); + attachment.vertices = weights.ToArray(); + } + + private void ReadAnimation(Dictionary map, string name, SkeletonData skeletonData) + { + var scale = this.Scale; + var timelines = new ExposedList(); + float duration = 0; + + // Slot timelines. + if (map.ContainsKey("slots")) + { + foreach (KeyValuePair entry in (Dictionary)map["slots"]) + { + string slotName = entry.Key; + int slotIndex = skeletonData.FindSlotIndex(slotName); + var timelineMap = (Dictionary)entry.Value; + foreach (KeyValuePair timelineEntry in timelineMap) + { + var values = (List)timelineEntry.Value; + var timelineName = timelineEntry.Key; + if (timelineName == "attachment") + { + var timeline = new AttachmentTimeline(values.Count); + timeline.slotIndex = slotIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + timeline.SetFrame(frameIndex++, time, (string)valueMap["name"]); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); + + } + else if (timelineName == "color") + { + var timeline = new ColorTimeline(values.Count); + timeline.slotIndex = slotIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + string c = (string)valueMap["color"]; + timeline.SetFrame(frameIndex, time, ToColor(c, 0), ToColor(c, 1), ToColor(c, 2), ToColor(c, 3)); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * ColorTimeline.ENTRIES]); + + } + else if (timelineName == "twoColor") + { + var timeline = new TwoColorTimeline(values.Count); + timeline.slotIndex = slotIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + string light = (string)valueMap["light"]; + string dark = (string)valueMap["dark"]; + timeline.SetFrame(frameIndex, time, ToColor(light, 0), ToColor(light, 1), ToColor(light, 2), ToColor(light, 3), + ToColor(dark, 0, 6), ToColor(dark, 1, 6), ToColor(dark, 2, 6)); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TwoColorTimeline.ENTRIES]); + + } + else + throw new Exception("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")"); + } + } + } + + // Bone timelines. + if (map.ContainsKey("bones")) + { + foreach (KeyValuePair entry in (Dictionary)map["bones"]) + { + string boneName = entry.Key; + int boneIndex = skeletonData.FindBoneIndex(boneName); + if (boneIndex == -1) throw new Exception("Bone not found: " + boneName); + var timelineMap = (Dictionary)entry.Value; + foreach (KeyValuePair timelineEntry in timelineMap) + { + var values = (List)timelineEntry.Value; + var timelineName = timelineEntry.Key; + if (timelineName == "rotate") + { + var timeline = new RotateTimeline(values.Count); + timeline.boneIndex = boneIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + timeline.SetFrame(frameIndex, (float)valueMap["time"], (float)valueMap["angle"]); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * RotateTimeline.ENTRIES]); + + } + else if (timelineName == "translate" || timelineName == "scale" || timelineName == "shear") + { + TranslateTimeline timeline; + float timelineScale = 1; + if (timelineName == "scale") + timeline = new ScaleTimeline(values.Count); + else if (timelineName == "shear") + timeline = new ShearTimeline(values.Count); + else + { + timeline = new TranslateTimeline(values.Count); + timelineScale = scale; + } + timeline.boneIndex = boneIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + float x = GetFloat(valueMap, "x", 0); + float y = GetFloat(valueMap, "y", 0); + timeline.SetFrame(frameIndex, time, x * timelineScale, y * timelineScale); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TranslateTimeline.ENTRIES]); + + } + else + throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")"); + } + } + } + + // IK constraint timelines. + if (map.ContainsKey("ik")) + { + foreach (KeyValuePair constraintMap in (Dictionary)map["ik"]) + { + IkConstraintData constraint = skeletonData.FindIkConstraint(constraintMap.Key); + var values = (List)constraintMap.Value; + var timeline = new IkConstraintTimeline(values.Count); + timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(constraint); + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + float mix = GetFloat(valueMap, "mix", 1); + bool bendPositive = GetBoolean(valueMap, "bendPositive", true); + timeline.SetFrame(frameIndex, time, mix, bendPositive ? 1 : -1); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * IkConstraintTimeline.ENTRIES]); + } + } + + // Transform constraint timelines. + if (map.ContainsKey("transform")) + { + foreach (KeyValuePair constraintMap in (Dictionary)map["transform"]) + { + TransformConstraintData constraint = skeletonData.FindTransformConstraint(constraintMap.Key); + var values = (List)constraintMap.Value; + var timeline = new TransformConstraintTimeline(values.Count); + timeline.transformConstraintIndex = skeletonData.transformConstraints.IndexOf(constraint); + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + float rotateMix = GetFloat(valueMap, "rotateMix", 1); + float translateMix = GetFloat(valueMap, "translateMix", 1); + float scaleMix = GetFloat(valueMap, "scaleMix", 1); + float shearMix = GetFloat(valueMap, "shearMix", 1); + timeline.SetFrame(frameIndex, time, rotateMix, translateMix, scaleMix, shearMix); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TransformConstraintTimeline.ENTRIES]); + } + } + + // Path constraint timelines. + if (map.ContainsKey("paths")) + { + foreach (KeyValuePair constraintMap in (Dictionary)map["paths"]) + { + int index = skeletonData.FindPathConstraintIndex(constraintMap.Key); + if (index == -1) throw new Exception("Path constraint not found: " + constraintMap.Key); + PathConstraintData data = skeletonData.pathConstraints.Items[index]; + var timelineMap = (Dictionary)constraintMap.Value; + foreach (KeyValuePair timelineEntry in timelineMap) + { + var values = (List)timelineEntry.Value; + var timelineName = timelineEntry.Key; + if (timelineName == "position" || timelineName == "spacing") + { + PathConstraintPositionTimeline timeline; + float timelineScale = 1; + if (timelineName == "spacing") + { + timeline = new PathConstraintSpacingTimeline(values.Count); + if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale; + } + else + { + timeline = new PathConstraintPositionTimeline(values.Count); + if (data.positionMode == PositionMode.Fixed) timelineScale = scale; + } + timeline.pathConstraintIndex = index; + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + timeline.SetFrame(frameIndex, (float)valueMap["time"], GetFloat(valueMap, timelineName, 0) * timelineScale); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintPositionTimeline.ENTRIES]); + } + else if (timelineName == "mix") + { + PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(values.Count); + timeline.pathConstraintIndex = index; + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + timeline.SetFrame(frameIndex, (float)valueMap["time"], GetFloat(valueMap, "rotateMix", 1), GetFloat(valueMap, "translateMix", 1)); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintMixTimeline.ENTRIES]); + } + } + } + } + + // Deform timelines. + if (map.ContainsKey("deform")) + { + foreach (KeyValuePair deformMap in (Dictionary)map["deform"]) + { + Skin skin = skeletonData.FindSkin(deformMap.Key); + foreach (KeyValuePair slotMap in (Dictionary)deformMap.Value) + { + int slotIndex = skeletonData.FindSlotIndex(slotMap.Key); + if (slotIndex == -1) throw new Exception("Slot not found: " + slotMap.Key); + foreach (KeyValuePair timelineMap in (Dictionary)slotMap.Value) + { + var values = (List)timelineMap.Value; + VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, timelineMap.Key); + if (attachment == null) throw new Exception("Deform attachment not found: " + timelineMap.Key); + bool weighted = attachment.bones != null; + float[] vertices = attachment.vertices; + int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length; + + var timeline = new DeformTimeline(values.Count); + timeline.slotIndex = slotIndex; + timeline.attachment = attachment; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float[] deform; + if (!valueMap.ContainsKey("vertices")) + { + deform = weighted ? new float[deformLength] : vertices; + } + else + { + deform = new float[deformLength]; + int start = GetInt(valueMap, "offset", 0); + float[] verticesValue = GetFloatArray(valueMap, "vertices", 1); + Array.Copy(verticesValue, 0, deform, start, verticesValue.Length); + if (scale != 1) + { + for (int i = start, n = i + verticesValue.Length; i < n; i++) + deform[i] *= scale; + } + + if (!weighted) + { + for (int i = 0; i < deformLength; i++) + deform[i] += vertices[i]; + } + } + + timeline.SetFrame(frameIndex, (float)valueMap["time"], deform); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); + } + } + } + } + + // Draw order timeline. + if (map.ContainsKey("drawOrder") || map.ContainsKey("draworder")) + { + var values = (List)map[map.ContainsKey("drawOrder") ? "drawOrder" : "draworder"]; + var timeline = new DrawOrderTimeline(values.Count); + int slotCount = skeletonData.slots.Count; + int frameIndex = 0; + foreach (Dictionary drawOrderMap in values) + { + int[] drawOrder = null; + if (drawOrderMap.ContainsKey("offsets")) + { + drawOrder = new int[slotCount]; + for (int i = slotCount - 1; i >= 0; i--) + drawOrder[i] = -1; + var offsets = (List)drawOrderMap["offsets"]; + int[] unchanged = new int[slotCount - offsets.Count]; + int originalIndex = 0, unchangedIndex = 0; + foreach (Dictionary offsetMap in offsets) + { + int slotIndex = skeletonData.FindSlotIndex((string)offsetMap["slot"]); + if (slotIndex == -1) throw new Exception("Slot not found: " + offsetMap["slot"]); + // Collect unchanged items. + while (originalIndex != slotIndex) + unchanged[unchangedIndex++] = originalIndex++; + // Set changed items. + int index = originalIndex + (int)(float)offsetMap["offset"]; + drawOrder[index] = originalIndex++; + } + // Collect remaining unchanged items. + while (originalIndex < slotCount) + unchanged[unchangedIndex++] = originalIndex++; + // Fill in unchanged items. + for (int i = slotCount - 1; i >= 0; i--) + if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex]; + } + timeline.SetFrame(frameIndex++, (float)drawOrderMap["time"], drawOrder); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); + } + + // Event timeline. + if (map.ContainsKey("events")) + { + var eventsMap = (List)map["events"]; + var timeline = new EventTimeline(eventsMap.Count); + int frameIndex = 0; + foreach (Dictionary eventMap in eventsMap) + { + EventData eventData = skeletonData.FindEvent((string)eventMap["name"]); + if (eventData == null) throw new Exception("Event not found: " + eventMap["name"]); + var e = new Event((float)eventMap["time"], eventData); + e.Int = GetInt(eventMap, "int", eventData.Int); + e.Float = GetFloat(eventMap, "float", eventData.Float); + e.String = GetString(eventMap, "string", eventData.String); + timeline.SetFrame(frameIndex++, e); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); + } + + timelines.TrimExcess(); + skeletonData.animations.Add(new Animation(name, timelines, duration)); + } + + static void ReadCurve(Dictionary valueMap, CurveTimeline timeline, int frameIndex) + { + if (!valueMap.ContainsKey("curve")) + return; + Object curveObject = valueMap["curve"]; + if (curveObject.Equals("stepped")) + timeline.SetStepped(frameIndex); + else + { + var curve = curveObject as List; + if (curve != null) + timeline.SetCurve(frameIndex, (float)curve[0], (float)curve[1], (float)curve[2], (float)curve[3]); + } + } + + internal class LinkedMesh + { + internal string parent, skin; + internal int slotIndex; + internal MeshAttachment mesh; + + public LinkedMesh(MeshAttachment mesh, string skin, int slotIndex, string parent) + { + this.mesh = mesh; + this.skin = skin; + this.slotIndex = slotIndex; + this.parent = parent; + } + } + + static float[] GetFloatArray(Dictionary map, string name, float scale) + { + var list = (List)map[name]; + var values = new float[list.Count]; + if (scale == 1) + { + for (int i = 0, n = list.Count; i < n; i++) + values[i] = (float)list[i]; + } + else + { + for (int i = 0, n = list.Count; i < n; i++) + values[i] = (float)list[i] * scale; + } + return values; + } + + static int[] GetIntArray(Dictionary map, string name) + { + var list = (List)map[name]; + var values = new int[list.Count]; + for (int i = 0, n = list.Count; i < n; i++) + values[i] = (int)(float)list[i]; + return values; + } + + static float GetFloat(Dictionary map, string name, float defaultValue) + { + if (!map.ContainsKey(name)) + return defaultValue; + return (float)map[name]; + } + + static int GetInt(Dictionary map, string name, int defaultValue) + { + if (!map.ContainsKey(name)) + return defaultValue; + return (int)(float)map[name]; + } + + static bool GetBoolean(Dictionary map, string name, bool defaultValue) + { + if (!map.ContainsKey(name)) + return defaultValue; + return (bool)map[name]; + } + + static string GetString(Dictionary map, string name, string defaultValue) + { + if (!map.ContainsKey(name)) + return defaultValue; + return (string)map[name]; + } + + static float ToColor(string hexString, int colorIndex, int expectedLength = 8) + { + if (hexString.Length != expectedLength) + throw new ArgumentException("Color hexidecimal length must be " + expectedLength + ", recieved: " + hexString, "hexString"); + return Convert.ToInt32(hexString.Substring(colorIndex * 2, 2), 16) / (float)255; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Skin.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Skin.cs index ad9fdfd..5e00c86 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Skin.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Skin.cs @@ -31,95 +31,111 @@ using System; using System.Collections.Generic; -namespace Spine3_6_53 { - /// Stores attachments by slot index and attachment name. - /// See SkeletonData , Skeleton , and - /// Runtime skins in the Spine Runtimes Guide. - /// - public class Skin { - internal string name; - private Dictionary attachments = - new Dictionary(AttachmentKeyTupleComparer.Instance); +namespace Spine3_6_53 +{ + /// Stores attachments by slot index and attachment name. + /// See SkeletonData , Skeleton , and + /// Runtime skins in the Spine Runtimes Guide. + /// + public class Skin + { + internal string name; + private Dictionary attachments = + new Dictionary(AttachmentKeyTupleComparer.Instance); - public string Name { get { return name; } } - public Dictionary Attachments { get { return attachments; } } + public string Name { get { return name; } } + public Dictionary Attachments { get { return attachments; } } - public Skin (string name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; - } + public Skin(string name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + } - /// 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. - public void AddAttachment (int slotIndex, string name, Attachment attachment) { - if (attachment == null) throw new ArgumentNullException("attachment", "attachment cannot be null."); - attachments[new AttachmentKeyTuple(slotIndex, name)] = attachment; - } + /// 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. + public void AddAttachment(int slotIndex, string name, Attachment attachment) + { + if (attachment == null) throw new ArgumentNullException("attachment", "attachment cannot be null."); + attachments[new AttachmentKeyTuple(slotIndex, name)] = attachment; + } - /// Returns the attachment for the specified slot index and name, or null. - /// May be null. - public Attachment GetAttachment (int slotIndex, string name) { - Attachment attachment; - attachments.TryGetValue(new AttachmentKeyTuple(slotIndex, name), out attachment); - return attachment; - } + /// Returns the attachment for the specified slot index and name, or null. + /// May be null. + public Attachment GetAttachment(int slotIndex, string name) + { + Attachment attachment; + attachments.TryGetValue(new AttachmentKeyTuple(slotIndex, name), out attachment); + return attachment; + } - /// Finds the skin keys for a given slot. The results are added to the passed List(names). - /// The target slotIndex. To find the slot index, use or - /// Found skin key names will be added to this list. - public void FindNamesForSlot (int slotIndex, List names) { - if (names == null) throw new ArgumentNullException("names", "names cannot be null."); - foreach (AttachmentKeyTuple key in attachments.Keys) - if (key.slotIndex == slotIndex) names.Add(key.name); - } + /// Finds the skin keys for a given slot. The results are added to the passed List(names). + /// The target slotIndex. To find the slot index, use or + /// Found skin key names will be added to this list. + public void FindNamesForSlot(int slotIndex, List names) + { + if (names == null) throw new ArgumentNullException("names", "names cannot be null."); + foreach (AttachmentKeyTuple key in attachments.Keys) + if (key.slotIndex == slotIndex) names.Add(key.name); + } - /// Finds the attachments for a given slot. The results are added to the passed List(Attachment). - /// The target slotIndex. To find the slot index, use or - /// Found Attachments will be added to this list. - public void FindAttachmentsForSlot (int slotIndex, List attachments) { - if (attachments == null) throw new ArgumentNullException("attachments", "attachments cannot be null."); - foreach (KeyValuePair entry in this.attachments) - if (entry.Key.slotIndex == slotIndex) attachments.Add(entry.Value); - } + /// Finds the attachments for a given slot. The results are added to the passed List(Attachment). + /// The target slotIndex. To find the slot index, use or + /// Found Attachments will be added to this list. + public void FindAttachmentsForSlot(int slotIndex, List attachments) + { + if (attachments == null) throw new ArgumentNullException("attachments", "attachments cannot be null."); + foreach (KeyValuePair entry in this.attachments) + if (entry.Key.slotIndex == slotIndex) attachments.Add(entry.Value); + } - override public string ToString () { - return name; - } + override public string ToString() + { + return name; + } - /// Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached. - internal void AttachAll (Skeleton skeleton, Skin oldSkin) { - foreach (KeyValuePair entry in oldSkin.attachments) { - int slotIndex = entry.Key.slotIndex; - Slot slot = skeleton.slots.Items[slotIndex]; - if (slot.Attachment == entry.Value) { - Attachment attachment = GetAttachment(slotIndex, entry.Key.name); - if (attachment != null) slot.Attachment = attachment; - } - } - } + /// Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached. + internal void AttachAll(Skeleton skeleton, Skin oldSkin) + { + foreach (KeyValuePair entry in oldSkin.attachments) + { + int slotIndex = entry.Key.slotIndex; + Slot slot = skeleton.slots.Items[slotIndex]; + if (slot.Attachment == entry.Value) + { + Attachment attachment = GetAttachment(slotIndex, entry.Key.name); + if (attachment != null) slot.Attachment = attachment; + } + } + } - public struct AttachmentKeyTuple { - public readonly int slotIndex; - public readonly string name; - internal readonly int nameHashCode; + public struct AttachmentKeyTuple + { + public readonly int slotIndex; + public readonly string name; + internal readonly int nameHashCode; - public AttachmentKeyTuple (int slotIndex, string name) { - this.slotIndex = slotIndex; - this.name = name; - nameHashCode = this.name.GetHashCode(); - } - } + public AttachmentKeyTuple(int slotIndex, string name) + { + this.slotIndex = slotIndex; + this.name = name; + nameHashCode = this.name.GetHashCode(); + } + } - // Avoids boxing in the dictionary. - class AttachmentKeyTupleComparer : IEqualityComparer { - internal static readonly AttachmentKeyTupleComparer Instance = new AttachmentKeyTupleComparer(); + // Avoids boxing in the dictionary. + class AttachmentKeyTupleComparer : IEqualityComparer + { + internal static readonly AttachmentKeyTupleComparer Instance = new AttachmentKeyTupleComparer(); - bool IEqualityComparer.Equals (AttachmentKeyTuple o1, AttachmentKeyTuple o2) { - return o1.slotIndex == o2.slotIndex && o1.nameHashCode == o2.nameHashCode && string.Equals(o1.name, o2.name, StringComparison.Ordinal); - } + bool IEqualityComparer.Equals(AttachmentKeyTuple o1, AttachmentKeyTuple o2) + { + return o1.slotIndex == o2.slotIndex && o1.nameHashCode == o2.nameHashCode && string.Equals(o1.name, o2.name, StringComparison.Ordinal); + } - int IEqualityComparer.GetHashCode (AttachmentKeyTuple o) { - return o.slotIndex; - } - } - } + int IEqualityComparer.GetHashCode(AttachmentKeyTuple o) + { + return o.slotIndex; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Slot.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Slot.cs index 93ae830..b0b792e 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Slot.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Slot.cs @@ -30,71 +30,80 @@ using System; -namespace Spine3_6_53 { - public class Slot { - internal SlotData data; - internal Bone bone; - internal float r, g, b, a; - internal float r2, g2, b2; - internal bool hasSecondColor; - internal Attachment attachment; - internal float attachmentTime; - internal ExposedList attachmentVertices = new ExposedList(); +namespace Spine3_6_53 +{ + public class Slot + { + internal SlotData data; + internal Bone bone; + internal float r, g, b, a; + internal float r2, g2, b2; + internal bool hasSecondColor; + internal Attachment attachment; + internal float attachmentTime; + internal ExposedList attachmentVertices = new ExposedList(); - public SlotData Data { get { return data; } } - public Bone Bone { get { return bone; } } - public Skeleton Skeleton { get { return bone.skeleton; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } + public SlotData Data { get { return data; } } + public Bone Bone { get { return bone; } } + public Skeleton Skeleton { get { return bone.skeleton; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } - public float R2 { get { return r2; } set { r2 = value; } } - public float G2 { get { return g2; } set { g2 = value; } } - public float B2 { get { return b2; } set { b2 = value; } } - public bool HasSecondColor { get { return data.hasSecondColor; } set { data.hasSecondColor = value; } } + public float R2 { get { return r2; } set { r2 = value; } } + public float G2 { get { return g2; } set { g2 = value; } } + public float B2 { get { return b2; } set { b2 = value; } } + public bool HasSecondColor { get { return data.hasSecondColor; } set { data.hasSecondColor = value; } } - /// May be null. - public Attachment Attachment { - get { return attachment; } - set { - if (attachment == value) return; - attachment = value; - attachmentTime = bone.skeleton.time; - attachmentVertices.Clear(false); - } - } + /// May be null. + public Attachment Attachment + { + get { return attachment; } + set + { + if (attachment == value) return; + attachment = value; + attachmentTime = bone.skeleton.time; + attachmentVertices.Clear(false); + } + } - public float AttachmentTime { - get { return bone.skeleton.time - attachmentTime; } - set { attachmentTime = bone.skeleton.time - value; } - } + public float AttachmentTime + { + get { return bone.skeleton.time - attachmentTime; } + set { attachmentTime = bone.skeleton.time - value; } + } - public ExposedList AttachmentVertices { get { return attachmentVertices; } set { attachmentVertices = value; } } + public ExposedList AttachmentVertices { get { return attachmentVertices; } set { attachmentVertices = value; } } - public Slot (SlotData data, Bone bone) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); - this.data = data; - this.bone = bone; - SetToSetupPose(); - } + public Slot(SlotData data, Bone bone) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); + this.data = data; + this.bone = bone; + SetToSetupPose(); + } - public void SetToSetupPose () { - r = data.r; - g = data.g; - b = data.b; - a = data.a; - if (data.attachmentName == null) - Attachment = null; - else { - attachment = null; - Attachment = bone.skeleton.GetAttachment(data.index, data.attachmentName); - } - } + public void SetToSetupPose() + { + r = data.r; + g = data.g; + b = data.b; + a = data.a; + if (data.attachmentName == null) + Attachment = null; + else + { + attachment = null; + Attachment = bone.skeleton.GetAttachment(data.index, data.attachmentName); + } + } - override public string ToString () { - return data.name; - } - } + override public string ToString() + { + return data.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/SlotData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/SlotData.cs index a5734a0..001eaa4 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/SlotData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/SlotData.cs @@ -30,45 +30,49 @@ using System; -namespace Spine3_6_53 { - public class SlotData { - internal int index; - internal string name; - internal BoneData boneData; - internal float r = 1, g = 1, b = 1, a = 1; - internal float r2 = 0, g2 = 0, b2 = 0; - internal bool hasSecondColor = false; - internal string attachmentName; - internal BlendMode blendMode; +namespace Spine3_6_53 +{ + public class SlotData + { + internal int index; + internal string name; + internal BoneData boneData; + internal float r = 1, g = 1, b = 1, a = 1; + internal float r2 = 0, g2 = 0, b2 = 0; + internal bool hasSecondColor = false; + internal string attachmentName; + internal BlendMode blendMode; - public int Index { get { return index; } } - public string Name { get { return name; } } - public BoneData BoneData { get { return boneData; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } + public int Index { get { return index; } } + public string Name { get { return name; } } + public BoneData BoneData { get { return boneData; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } - public float R2 { get { return r2; } set { r2 = value; } } - public float G2 { get { return g2; } set { g2 = value; } } - public float B2 { get { return b2; } set { b2 = value; } } - public bool HasSecondColor { get { return hasSecondColor; } set { hasSecondColor = value; } } + public float R2 { get { return r2; } set { r2 = value; } } + public float G2 { get { return g2; } set { g2 = value; } } + public float B2 { get { return b2; } set { b2 = value; } } + public bool HasSecondColor { get { return hasSecondColor; } set { hasSecondColor = value; } } - /// May be null. - public String AttachmentName { get { return attachmentName; } set { attachmentName = value; } } - public BlendMode BlendMode { get { return blendMode; } set { blendMode = value; } } + /// May be null. + public String AttachmentName { get { return attachmentName; } set { attachmentName = value; } } + public BlendMode BlendMode { get { return blendMode; } set { blendMode = value; } } - public SlotData (int index, String name, BoneData boneData) { - if (index < 0) throw new ArgumentException ("index must be >= 0.", "index"); - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - if (boneData == null) throw new ArgumentNullException("boneData", "boneData cannot be null."); - this.index = index; - this.name = name; - this.boneData = boneData; - } + public SlotData(int index, String name, BoneData boneData) + { + if (index < 0) throw new ArgumentException("index must be >= 0.", "index"); + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + if (boneData == null) throw new ArgumentNullException("boneData", "boneData cannot be null."); + this.index = index; + this.name = name; + this.boneData = boneData; + } - override public string ToString () { - return name; - } - } + override public string ToString() + { + return name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/TransformConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/TransformConstraint.cs index 6da6da4..6d8a999 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/TransformConstraint.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/TransformConstraint.cs @@ -30,255 +30,286 @@ using System; -namespace Spine3_6_53 { - public class TransformConstraint : IConstraint { - internal TransformConstraintData data; - internal ExposedList bones; - internal Bone target; - internal float rotateMix, translateMix, scaleMix, shearMix; - - public TransformConstraintData Data { get { return data; } } - public int Order { get { return data.order; } } - public ExposedList Bones { get { return bones; } } - public Bone Target { get { return target; } set { target = value; } } - public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } - public float TranslateMix { get { return translateMix; } set { translateMix = value; } } - public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } } - public float ShearMix { get { return shearMix; } set { shearMix = value; } } - - public TransformConstraint (TransformConstraintData data, Skeleton skeleton) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.data = data; - rotateMix = data.rotateMix; - translateMix = data.translateMix; - scaleMix = data.scaleMix; - shearMix = data.shearMix; - - bones = new ExposedList(); - foreach (BoneData boneData in data.bones) - bones.Add (skeleton.FindBone (boneData.name)); - - target = skeleton.FindBone(data.target.name); - } - - public void Apply () { - Update(); - } - - public void Update () { - if (data.local) { - if (data.relative) - ApplyRelativeLocal(); - else - ApplyAbsoluteLocal(); - } else { - if (data.relative) - ApplyRelativeWorld(); - else - ApplyAbsoluteWorld(); - } - } - - void ApplyAbsoluteWorld () { - float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; - Bone target = this.target; - float ta = target.a, tb = target.b, tc = target.c, td = target.d; - float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad; - float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect; - var bones = this.bones; - for (int i = 0, n = bones.Count; i < n; i++) { - Bone bone = bones.Items[i]; - bool modified = false; - - if (rotateMix != 0) { - float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - float r = MathUtils.Atan2(tc, ta) - MathUtils.Atan2(c, a) + offsetRotation; - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) r += MathUtils.PI2; - r *= rotateMix; - float cos = MathUtils.Cos(r), sin = MathUtils.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; - modified = true; - } - - if (translateMix != 0) { - float tx, ty; //Vector2 temp = this.temp; - target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY)); - bone.worldX += (tx - bone.worldX) * translateMix; - bone.worldY += (ty - bone.worldY) * translateMix; - modified = true; - } - - if (scaleMix > 0) { - float s = (float)Math.Sqrt(bone.a * bone.a + bone.c * bone.c); - //float ts = (float)Math.sqrt(ta * ta + tc * tc); - if (s > 0.00001f) s = (s + ((float)Math.Sqrt(ta * ta + tc * tc) - s + data.offsetScaleX) * scaleMix) / s; - bone.a *= s; - bone.c *= s; - s = (float)Math.Sqrt(bone.b * bone.b + bone.d * bone.d); - //ts = (float)Math.Sqrt(tb * tb + td * td); - if (s > 0.00001f) s = (s + ((float)Math.Sqrt(tb * tb + td * td) - s + data.offsetScaleY) * scaleMix) / s; - bone.b *= s; - bone.d *= s; - modified = true; - } - - if (shearMix > 0) { - float b = bone.b, d = bone.d; - float by = MathUtils.Atan2(d, b); - float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta) - (by - MathUtils.Atan2(bone.c, bone.a)); - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) r += MathUtils.PI2; - r = by + (r + offsetShearY) * shearMix; - float s = (float)Math.Sqrt(b * b + d * d); - bone.b = MathUtils.Cos(r) * s; - bone.d = MathUtils.Sin(r) * s; - modified = true; - } - - if (modified) bone.appliedValid = false; - } - } - - void ApplyRelativeWorld () { - float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; - Bone target = this.target; - float ta = target.a, tb = target.b, tc = target.c, td = target.d; - float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad; - float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect; - var bones = this.bones; - for (int i = 0, n = bones.Count; i < n; i++) { - Bone bone = bones.Items[i]; - bool modified = false; - - if (rotateMix != 0) { - float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - float r = MathUtils.Atan2(tc, ta) + offsetRotation; - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) r += MathUtils.PI2; - r *= rotateMix; - float cos = MathUtils.Cos(r), sin = MathUtils.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; - modified = true; - } - - if (translateMix != 0) { - float tx, ty; //Vector2 temp = this.temp; - target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY)); - bone.worldX += tx * translateMix; - bone.worldY += ty * translateMix; - modified = true; - } - - if (scaleMix > 0) { - float s = ((float)Math.Sqrt(ta * ta + tc * tc) - 1 + data.offsetScaleX) * scaleMix + 1; - bone.a *= s; - bone.c *= s; - s = ((float)Math.Sqrt(tb * tb + td * td) - 1 + data.offsetScaleY) * scaleMix + 1; - bone.b *= s; - bone.d *= s; - modified = true; - } - - if (shearMix > 0) { - float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta); - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) r += MathUtils.PI2; - float b = bone.b, d = bone.d; - r = MathUtils.Atan2(d, b) + (r - MathUtils.PI / 2 + offsetShearY) * shearMix; - float s = (float)Math.Sqrt(b * b + d * d); - bone.b = MathUtils.Cos(r) * s; - bone.d = MathUtils.Sin(r) * s; - modified = true; - } - - if (modified) bone.appliedValid = false; - } - } - - void ApplyAbsoluteLocal () { - float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; - Bone target = this.target; - if (!target.appliedValid) target.UpdateAppliedTransform(); - var bonesItems = this.bones.Items; - for (int i = 0, n = this.bones.Count; i < n; i++) { - Bone bone = bonesItems[i]; - if (!bone.appliedValid) bone.UpdateAppliedTransform(); - - float rotation = bone.arotation; - if (rotateMix != 0) { - float r = target.arotation - rotation + data.offsetRotation; - r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; - rotation += r * rotateMix; - } - - float x = bone.ax, y = bone.ay; - if (translateMix != 0) { - x += (target.ax - x + data.offsetX) * translateMix; - y += (target.ay - y + data.offsetY) * translateMix; - } - - float scaleX = bone.ascaleX, scaleY = bone.ascaleY; - if (scaleMix > 0) { - if (scaleX > 0.00001f) scaleX = (scaleX + (target.ascaleX - scaleX + data.offsetScaleX) * scaleMix) / scaleX; - if (scaleY > 0.00001f) scaleY = (scaleY + (target.ascaleY - scaleY + data.offsetScaleY) * scaleMix) / scaleY; - } - - float shearY = bone.ashearY; - if (shearMix > 0) { - float r = target.ashearY - shearY + data.offsetShearY; - r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; - bone.shearY += r * shearMix; - } - - bone.UpdateWorldTransform(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY); - } - } - - void ApplyRelativeLocal () { - float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; - Bone target = this.target; - if (!target.appliedValid) target.UpdateAppliedTransform(); - var bonesItems = this.bones.Items; - for (int i = 0, n = this.bones.Count; i < n; i++) { - Bone bone = bonesItems[i]; - if (!bone.appliedValid) bone.UpdateAppliedTransform(); - - float rotation = bone.arotation; - if (rotateMix != 0) rotation += (target.arotation + data.offsetRotation) * rotateMix; - - float x = bone.ax, y = bone.ay; - if (translateMix != 0) { - x += (target.ax + data.offsetX) * translateMix; - y += (target.ay + data.offsetY) * translateMix; - } - - float scaleX = bone.ascaleX, scaleY = bone.ascaleY; - if (scaleMix > 0) { - if (scaleX > 0.00001f) scaleX *= ((target.ascaleX - 1 + data.offsetScaleX) * scaleMix) + 1; - if (scaleY > 0.00001f) scaleY *= ((target.ascaleY - 1 + data.offsetScaleY) * scaleMix) + 1; - } - - float shearY = bone.ashearY; - if (shearMix > 0) shearY += (target.ashearY + data.offsetShearY) * shearMix; - - bone.UpdateWorldTransform(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY); - } - } - - override public string ToString () { - return data.name; - } - } +namespace Spine3_6_53 +{ + public class TransformConstraint : IConstraint + { + internal TransformConstraintData data; + internal ExposedList bones; + internal Bone target; + internal float rotateMix, translateMix, scaleMix, shearMix; + + public TransformConstraintData Data { get { return data; } } + public int Order { get { return data.order; } } + public ExposedList Bones { get { return bones; } } + public Bone Target { get { return target; } set { target = value; } } + public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } + public float TranslateMix { get { return translateMix; } set { translateMix = value; } } + public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } } + public float ShearMix { get { return shearMix; } set { shearMix = value; } } + + public TransformConstraint(TransformConstraintData data, Skeleton skeleton) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + rotateMix = data.rotateMix; + translateMix = data.translateMix; + scaleMix = data.scaleMix; + shearMix = data.shearMix; + + bones = new ExposedList(); + foreach (BoneData boneData in data.bones) + bones.Add(skeleton.FindBone(boneData.name)); + + target = skeleton.FindBone(data.target.name); + } + + public void Apply() + { + Update(); + } + + public void Update() + { + if (data.local) + { + if (data.relative) + ApplyRelativeLocal(); + else + ApplyAbsoluteLocal(); + } + else + { + if (data.relative) + ApplyRelativeWorld(); + else + ApplyAbsoluteWorld(); + } + } + + void ApplyAbsoluteWorld() + { + float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; + Bone target = this.target; + float ta = target.a, tb = target.b, tc = target.c, td = target.d; + float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad; + float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect; + var bones = this.bones; + for (int i = 0, n = bones.Count; i < n; i++) + { + Bone bone = bones.Items[i]; + bool modified = false; + + if (rotateMix != 0) + { + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + float r = MathUtils.Atan2(tc, ta) - MathUtils.Atan2(c, a) + offsetRotation; + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) r += MathUtils.PI2; + r *= rotateMix; + float cos = MathUtils.Cos(r), sin = MathUtils.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; + modified = true; + } + + if (translateMix != 0) + { + float tx, ty; //Vector2 temp = this.temp; + target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY)); + bone.worldX += (tx - bone.worldX) * translateMix; + bone.worldY += (ty - bone.worldY) * translateMix; + modified = true; + } + + if (scaleMix > 0) + { + float s = (float)Math.Sqrt(bone.a * bone.a + bone.c * bone.c); + //float ts = (float)Math.sqrt(ta * ta + tc * tc); + if (s > 0.00001f) s = (s + ((float)Math.Sqrt(ta * ta + tc * tc) - s + data.offsetScaleX) * scaleMix) / s; + bone.a *= s; + bone.c *= s; + s = (float)Math.Sqrt(bone.b * bone.b + bone.d * bone.d); + //ts = (float)Math.Sqrt(tb * tb + td * td); + if (s > 0.00001f) s = (s + ((float)Math.Sqrt(tb * tb + td * td) - s + data.offsetScaleY) * scaleMix) / s; + bone.b *= s; + bone.d *= s; + modified = true; + } + + if (shearMix > 0) + { + float b = bone.b, d = bone.d; + float by = MathUtils.Atan2(d, b); + float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta) - (by - MathUtils.Atan2(bone.c, bone.a)); + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) r += MathUtils.PI2; + r = by + (r + offsetShearY) * shearMix; + float s = (float)Math.Sqrt(b * b + d * d); + bone.b = MathUtils.Cos(r) * s; + bone.d = MathUtils.Sin(r) * s; + modified = true; + } + + if (modified) bone.appliedValid = false; + } + } + + void ApplyRelativeWorld() + { + float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; + Bone target = this.target; + float ta = target.a, tb = target.b, tc = target.c, td = target.d; + float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad; + float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect; + var bones = this.bones; + for (int i = 0, n = bones.Count; i < n; i++) + { + Bone bone = bones.Items[i]; + bool modified = false; + + if (rotateMix != 0) + { + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + float r = MathUtils.Atan2(tc, ta) + offsetRotation; + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) r += MathUtils.PI2; + r *= rotateMix; + float cos = MathUtils.Cos(r), sin = MathUtils.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; + modified = true; + } + + if (translateMix != 0) + { + float tx, ty; //Vector2 temp = this.temp; + target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY)); + bone.worldX += tx * translateMix; + bone.worldY += ty * translateMix; + modified = true; + } + + if (scaleMix > 0) + { + float s = ((float)Math.Sqrt(ta * ta + tc * tc) - 1 + data.offsetScaleX) * scaleMix + 1; + bone.a *= s; + bone.c *= s; + s = ((float)Math.Sqrt(tb * tb + td * td) - 1 + data.offsetScaleY) * scaleMix + 1; + bone.b *= s; + bone.d *= s; + modified = true; + } + + if (shearMix > 0) + { + float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta); + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) r += MathUtils.PI2; + float b = bone.b, d = bone.d; + r = MathUtils.Atan2(d, b) + (r - MathUtils.PI / 2 + offsetShearY) * shearMix; + float s = (float)Math.Sqrt(b * b + d * d); + bone.b = MathUtils.Cos(r) * s; + bone.d = MathUtils.Sin(r) * s; + modified = true; + } + + if (modified) bone.appliedValid = false; + } + } + + void ApplyAbsoluteLocal() + { + float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; + Bone target = this.target; + if (!target.appliedValid) target.UpdateAppliedTransform(); + var bonesItems = this.bones.Items; + for (int i = 0, n = this.bones.Count; i < n; i++) + { + Bone bone = bonesItems[i]; + if (!bone.appliedValid) bone.UpdateAppliedTransform(); + + float rotation = bone.arotation; + if (rotateMix != 0) + { + float r = target.arotation - rotation + data.offsetRotation; + r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; + rotation += r * rotateMix; + } + + float x = bone.ax, y = bone.ay; + if (translateMix != 0) + { + x += (target.ax - x + data.offsetX) * translateMix; + y += (target.ay - y + data.offsetY) * translateMix; + } + + float scaleX = bone.ascaleX, scaleY = bone.ascaleY; + if (scaleMix > 0) + { + if (scaleX > 0.00001f) scaleX = (scaleX + (target.ascaleX - scaleX + data.offsetScaleX) * scaleMix) / scaleX; + if (scaleY > 0.00001f) scaleY = (scaleY + (target.ascaleY - scaleY + data.offsetScaleY) * scaleMix) / scaleY; + } + + float shearY = bone.ashearY; + if (shearMix > 0) + { + float r = target.ashearY - shearY + data.offsetShearY; + r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; + bone.shearY += r * shearMix; + } + + bone.UpdateWorldTransform(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY); + } + } + + void ApplyRelativeLocal() + { + float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; + Bone target = this.target; + if (!target.appliedValid) target.UpdateAppliedTransform(); + var bonesItems = this.bones.Items; + for (int i = 0, n = this.bones.Count; i < n; i++) + { + Bone bone = bonesItems[i]; + if (!bone.appliedValid) bone.UpdateAppliedTransform(); + + float rotation = bone.arotation; + if (rotateMix != 0) rotation += (target.arotation + data.offsetRotation) * rotateMix; + + float x = bone.ax, y = bone.ay; + if (translateMix != 0) + { + x += (target.ax + data.offsetX) * translateMix; + y += (target.ay + data.offsetY) * translateMix; + } + + float scaleX = bone.ascaleX, scaleY = bone.ascaleY; + if (scaleMix > 0) + { + if (scaleX > 0.00001f) scaleX *= ((target.ascaleX - 1 + data.offsetScaleX) * scaleMix) + 1; + if (scaleY > 0.00001f) scaleY *= ((target.ascaleY - 1 + data.offsetScaleY) * scaleMix) + 1; + } + + float shearY = bone.ashearY; + if (shearMix > 0) shearY += (target.ashearY + data.offsetShearY) * shearMix; + + bone.UpdateWorldTransform(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY); + } + } + + override public string ToString() + { + return data.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/TransformConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/TransformConstraintData.cs index 5076134..0c98095 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/TransformConstraintData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/TransformConstraintData.cs @@ -30,42 +30,46 @@ using System; -namespace Spine3_6_53 { - public class TransformConstraintData { - internal string name; - internal int order; - internal ExposedList bones = new ExposedList(); - internal BoneData target; - internal float rotateMix, translateMix, scaleMix, shearMix; - internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY; - internal bool relative, local; +namespace Spine3_6_53 +{ + public class TransformConstraintData + { + internal string name; + internal int order; + internal ExposedList bones = new ExposedList(); + internal BoneData target; + internal float rotateMix, translateMix, scaleMix, shearMix; + internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY; + internal bool relative, local; - public string Name { get { return name; } } - public int Order { get { return order; } set { order = value; } } - public ExposedList Bones { get { return bones; } } - public BoneData Target { get { return target; } set { target = value; } } - public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } - public float TranslateMix { get { return translateMix; } set { translateMix = value; } } - public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } } - public float ShearMix { get { return shearMix; } set { shearMix = value; } } + public string Name { get { return name; } } + public int Order { get { return order; } set { order = value; } } + public ExposedList Bones { get { return bones; } } + public BoneData Target { get { return target; } set { target = value; } } + public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } + public float TranslateMix { get { return translateMix; } set { translateMix = value; } } + public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } } + public float ShearMix { get { return shearMix; } set { shearMix = value; } } - public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } - public float OffsetX { get { return offsetX; } set { offsetX = value; } } - public float OffsetY { get { return offsetY; } set { offsetY = value; } } - public float OffsetScaleX { get { return offsetScaleX; } set { offsetScaleX = value; } } - public float OffsetScaleY { get { return offsetScaleY; } set { offsetScaleY = value; } } - public float OffsetShearY { get { return offsetShearY; } set { offsetShearY = value; } } + public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } + public float OffsetX { get { return offsetX; } set { offsetX = value; } } + public float OffsetY { get { return offsetY; } set { offsetY = value; } } + public float OffsetScaleX { get { return offsetScaleX; } set { offsetScaleX = value; } } + public float OffsetScaleY { get { return offsetScaleY; } set { offsetScaleY = value; } } + public float OffsetShearY { get { return offsetShearY; } set { offsetShearY = value; } } - public bool Relative { get { return relative; } set { relative = value; } } - public bool Local { get { return local; } set { local = value; } } + public bool Relative { get { return relative; } set { relative = value; } } + public bool Local { get { return local; } set { local = value; } } - public TransformConstraintData (string name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; - } + public TransformConstraintData(string name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + } - override public string ToString () { - return name; - } - } + override public string ToString() + { + return name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Triangulator.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Triangulator.cs index ab4ab5f..5c4ea23 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Triangulator.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/Triangulator.cs @@ -30,249 +30,280 @@ using System; -namespace Spine3_6_53 { - internal class Triangulator { - private readonly ExposedList> convexPolygons = new ExposedList>(); - private readonly ExposedList> convexPolygonsIndices = new ExposedList>(); - - private readonly ExposedList indicesArray = new ExposedList(); - private readonly ExposedList isConcaveArray = new ExposedList(); - private readonly ExposedList triangles = new ExposedList(); - - private readonly Pool> polygonPool = new Pool>(); - private readonly Pool> polygonIndicesPool = new Pool>(); - - public ExposedList Triangulate (ExposedList verticesArray) { - var vertices = verticesArray.Items; - int vertexCount = verticesArray.Count >> 1; - - var indicesArray = this.indicesArray; - indicesArray.Clear(); - int[] indices = indicesArray.Resize(vertexCount).Items; - for (int i = 0; i < vertexCount; i++) - indices[i] = i; - - var isConcaveArray = this.isConcaveArray; - bool[] isConcave = isConcaveArray.Resize(vertexCount).Items; - for (int i = 0, n = vertexCount; i < n; ++i) - isConcave[i] = IsConcave(i, vertexCount, vertices, indices); - - var triangles = this.triangles; - triangles.Clear(); - triangles.EnsureCapacity(Math.Max(0, vertexCount - 2) << 2); - - while (vertexCount > 3) { - // Find ear tip. - int previous = vertexCount - 1, i = 0, next = 1; - - // outer: - while (true) { - if (!isConcave[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 (int ii = (next + 1) % vertexCount; ii != previous; ii = (ii + 1) % vertexCount) { - if (!isConcave[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 (!isConcave[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]); - indicesArray.RemoveAt(i); - isConcaveArray.RemoveAt(i); - vertexCount--; - - int previousIndex = (vertexCount + i - 1) % vertexCount; - int nextIndex = i == vertexCount ? 0 : i; - isConcave[previousIndex] = IsConcave(previousIndex, vertexCount, vertices, indices); - isConcave[nextIndex] = IsConcave(nextIndex, vertexCount, vertices, indices); - } - - if (vertexCount == 3) { - triangles.Add(indices[2]); - triangles.Add(indices[0]); - triangles.Add(indices[1]); - } - - return triangles; - } - - public ExposedList> Decompose (ExposedList verticesArray, ExposedList triangles) { - var vertices = verticesArray.Items; - var convexPolygons = this.convexPolygons; - for (int i = 0, n = convexPolygons.Count; i < n; i++) { - polygonPool.Free(convexPolygons.Items[i]); - } - convexPolygons.Clear(); - - var convexPolygonsIndices = this.convexPolygonsIndices; - for (int i = 0, n = convexPolygonsIndices.Count; i < n; i++) { - polygonIndicesPool.Free(convexPolygonsIndices.Items[i]); - } - convexPolygonsIndices.Clear(); - - var polygonIndices = polygonIndicesPool.Obtain(); - polygonIndices.Clear(); - - var polygon = polygonPool.Obtain(); - polygon.Clear(); - - // Merge subsequent triangles if they form a triangle fan. - int fanBaseIndex = -1, lastWinding = 0; - int[] trianglesItems = triangles.Items; - for (int i = 0, n = triangles.Count; i < n; i += 3) { - int t1 = trianglesItems[i] << 1, t2 = trianglesItems[i + 1] << 1, t3 = trianglesItems[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). - var merged = false; - if (fanBaseIndex == t1) { - int o = polygon.Count - 4; - float[] p = polygon.Items; - 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.Count > 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.Count > 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 (int i = 0, n = convexPolygons.Count; i < n; i++) { - polygonIndices = convexPolygonsIndices.Items[i]; - if (polygonIndices.Count == 0) continue; - int firstIndex = polygonIndices.Items[0]; - int lastIndex = polygonIndices.Items[polygonIndices.Count - 1]; - - polygon = convexPolygons.Items[i]; - int o = polygon.Count - 4; - float[] p = polygon.Items; - 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 winding = Winding(prevPrevX, prevPrevY, prevX, prevY, firstX, firstY); - - for (int ii = 0; ii < n; ii++) { - if (ii == i) continue; - var otherIndices = convexPolygonsIndices.Items[ii]; - if (otherIndices.Count != 3) continue; - int otherFirstIndex = otherIndices.Items[0]; - int otherSecondIndex = otherIndices.Items[1]; - int otherLastIndex = otherIndices.Items[2]; - - var otherPoly = convexPolygons.Items[ii]; - float x3 = otherPoly.Items[otherPoly.Count - 2], y3 = otherPoly.Items[otherPoly.Count - 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 == winding && winding2 == winding) { - 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 = convexPolygons.Count - 1; i >= 0; i--) { - polygon = convexPolygons.Items[i]; - if (polygon.Count == 0) { - convexPolygons.RemoveAt(i); - polygonPool.Free(polygon); - polygonIndices = convexPolygonsIndices.Items[i]; - convexPolygonsIndices.RemoveAt(i); - polygonIndicesPool.Free(polygonIndices); - } - } - - return convexPolygons; - } - - static private bool IsConcave (int index, int vertexCount, float[] vertices, int[] 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]); - } - - static private bool PositiveArea (float p1x, float p1y, float p2x, float p2y, float p3x, float p3y) { - return p1x * (p3y - p2y) + p2x * (p1y - p3y) + p3x * (p2y - p1y) >= 0; - } - - static private int 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; - } - } +namespace Spine3_6_53 +{ + internal class Triangulator + { + private readonly ExposedList> convexPolygons = new ExposedList>(); + private readonly ExposedList> convexPolygonsIndices = new ExposedList>(); + + private readonly ExposedList indicesArray = new ExposedList(); + private readonly ExposedList isConcaveArray = new ExposedList(); + private readonly ExposedList triangles = new ExposedList(); + + private readonly Pool> polygonPool = new Pool>(); + private readonly Pool> polygonIndicesPool = new Pool>(); + + public ExposedList Triangulate(ExposedList verticesArray) + { + var vertices = verticesArray.Items; + int vertexCount = verticesArray.Count >> 1; + + var indicesArray = this.indicesArray; + indicesArray.Clear(); + int[] indices = indicesArray.Resize(vertexCount).Items; + for (int i = 0; i < vertexCount; i++) + indices[i] = i; + + var isConcaveArray = this.isConcaveArray; + bool[] isConcave = isConcaveArray.Resize(vertexCount).Items; + for (int i = 0, n = vertexCount; i < n; ++i) + isConcave[i] = IsConcave(i, vertexCount, vertices, indices); + + var triangles = this.triangles; + triangles.Clear(); + triangles.EnsureCapacity(Math.Max(0, vertexCount - 2) << 2); + + while (vertexCount > 3) + { + // Find ear tip. + int previous = vertexCount - 1, i = 0, next = 1; + + // outer: + while (true) + { + if (!isConcave[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 (int ii = (next + 1) % vertexCount; ii != previous; ii = (ii + 1) % vertexCount) + { + if (!isConcave[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 (!isConcave[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]); + indicesArray.RemoveAt(i); + isConcaveArray.RemoveAt(i); + vertexCount--; + + int previousIndex = (vertexCount + i - 1) % vertexCount; + int nextIndex = i == vertexCount ? 0 : i; + isConcave[previousIndex] = IsConcave(previousIndex, vertexCount, vertices, indices); + isConcave[nextIndex] = IsConcave(nextIndex, vertexCount, vertices, indices); + } + + if (vertexCount == 3) + { + triangles.Add(indices[2]); + triangles.Add(indices[0]); + triangles.Add(indices[1]); + } + + return triangles; + } + + public ExposedList> Decompose(ExposedList verticesArray, ExposedList triangles) + { + var vertices = verticesArray.Items; + var convexPolygons = this.convexPolygons; + for (int i = 0, n = convexPolygons.Count; i < n; i++) + { + polygonPool.Free(convexPolygons.Items[i]); + } + convexPolygons.Clear(); + + var convexPolygonsIndices = this.convexPolygonsIndices; + for (int i = 0, n = convexPolygonsIndices.Count; i < n; i++) + { + polygonIndicesPool.Free(convexPolygonsIndices.Items[i]); + } + convexPolygonsIndices.Clear(); + + var polygonIndices = polygonIndicesPool.Obtain(); + polygonIndices.Clear(); + + var polygon = polygonPool.Obtain(); + polygon.Clear(); + + // Merge subsequent triangles if they form a triangle fan. + int fanBaseIndex = -1, lastWinding = 0; + int[] trianglesItems = triangles.Items; + for (int i = 0, n = triangles.Count; i < n; i += 3) + { + int t1 = trianglesItems[i] << 1, t2 = trianglesItems[i + 1] << 1, t3 = trianglesItems[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). + var merged = false; + if (fanBaseIndex == t1) + { + int o = polygon.Count - 4; + float[] p = polygon.Items; + 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.Count > 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.Count > 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 (int i = 0, n = convexPolygons.Count; i < n; i++) + { + polygonIndices = convexPolygonsIndices.Items[i]; + if (polygonIndices.Count == 0) continue; + int firstIndex = polygonIndices.Items[0]; + int lastIndex = polygonIndices.Items[polygonIndices.Count - 1]; + + polygon = convexPolygons.Items[i]; + int o = polygon.Count - 4; + float[] p = polygon.Items; + 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 winding = Winding(prevPrevX, prevPrevY, prevX, prevY, firstX, firstY); + + for (int ii = 0; ii < n; ii++) + { + if (ii == i) continue; + var otherIndices = convexPolygonsIndices.Items[ii]; + if (otherIndices.Count != 3) continue; + int otherFirstIndex = otherIndices.Items[0]; + int otherSecondIndex = otherIndices.Items[1]; + int otherLastIndex = otherIndices.Items[2]; + + var otherPoly = convexPolygons.Items[ii]; + float x3 = otherPoly.Items[otherPoly.Count - 2], y3 = otherPoly.Items[otherPoly.Count - 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 == winding && winding2 == winding) + { + 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 = convexPolygons.Count - 1; i >= 0; i--) + { + polygon = convexPolygons.Items[i]; + if (polygon.Count == 0) + { + convexPolygons.RemoveAt(i); + polygonPool.Free(polygon); + polygonIndices = convexPolygonsIndices.Items[i]; + convexPolygonsIndices.RemoveAt(i); + polygonIndicesPool.Free(polygonIndices); + } + } + + return convexPolygons; + } + + static private bool IsConcave(int index, int vertexCount, float[] vertices, int[] 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]); + } + + static private bool PositiveArea(float p1x, float p1y, float p2x, float p2y, float p3x, float p3y) + { + return p1x * (p3y - p2y) + p2x * (p1y - p3y) + p3x * (p2y - p1y) >= 0; + } + + static private int 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/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/XnaLoader/MeshBatcher.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/XnaLoader/MeshBatcher.cs index 81d2a6e..d2da203 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/XnaLoader/MeshBatcher.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/XnaLoader/MeshBatcher.cs @@ -31,156 +31,169 @@ using System; using System.Collections.Generic; -using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; -namespace Spine3_6_53 { - public struct VertexPositionColorTextureColor : IVertexType { - public Vector3 Position; - public Color Color; - public Vector2 TextureCoordinate; - public Color Color2; - - public readonly static VertexDeclaration VertexDeclaration = new VertexDeclaration - ( - new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0), - new VertexElement(12, VertexElementFormat.Color, VertexElementUsage.Color, 0), - new VertexElement(16, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0), - new VertexElement(24, VertexElementFormat.Color, VertexElementUsage.Color, 1) - ); - - VertexDeclaration IVertexType.VertexDeclaration { - get { return VertexDeclaration; } - } - } - - // #region License - // /* - // Microsoft Public License (Ms-PL) - // MonoGame - Copyright 2009 The MonoGame Team - // - // All rights reserved. - // - // This license governs use of the accompanying software. If you use the software, you accept this license. If you do not - // accept the license, do not use the software. - // - // 1. Definitions - // The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under - // U.S. copyright law. - // - // A "contribution" is the original software, or any additions or changes to the software. - // A "contributor" is any person that distributes its contribution under this license. - // "Licensed patents" are a contributor's patent claims that read directly on its contribution. - // - // 2. Grant of Rights - // (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, - // each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. - // (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, - // each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. - // - // 3. Conditions and Limitations - // (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. - // (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, - // your patent license from such contributor to the software ends automatically. - // (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution - // notices that are present in the software. - // (D) If you distribute any portion of the software in source code form, you may do so only under this license by including - // a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object - // code form, you may only do so under a license that complies with this license. - // (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees - // or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent - // permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular - // purpose and non-infringement. - // */ - // #endregion License - // - - /// Draws batched meshes. - public class MeshBatcher { - private readonly List items; - private readonly Queue freeItems; - private VertexPositionColorTextureColor[] vertexArray = { }; - private short[] triangles = { }; - - public MeshBatcher () { - items = new List(256); - freeItems = new Queue(256); - EnsureCapacity(256, 512); - } - - /// Returns a pooled MeshItem. - public MeshItem NextItem (int vertexCount, int triangleCount) { - MeshItem item = freeItems.Count > 0 ? freeItems.Dequeue() : new MeshItem(); - if (item.vertices.Length < vertexCount) item.vertices = new VertexPositionColorTextureColor[vertexCount]; - if (item.triangles.Length < triangleCount) item.triangles = new int[triangleCount]; - item.vertexCount = vertexCount; - item.triangleCount = triangleCount; - items.Add(item); - return item; - } - - private void EnsureCapacity (int vertexCount, int triangleCount) { - if (vertexArray.Length < vertexCount) vertexArray = new VertexPositionColorTextureColor[vertexCount]; - if (triangles.Length < triangleCount) triangles = new short[triangleCount]; - } - - public void Draw (GraphicsDevice device) { - if (items.Count == 0) return; - - int itemCount = items.Count; - int vertexCount = 0, triangleCount = 0; - for (int i = 0; i < itemCount; i++) { - MeshItem item = items[i]; - vertexCount += item.vertexCount; - triangleCount += item.triangleCount; - } - EnsureCapacity(vertexCount, triangleCount); - - vertexCount = 0; - triangleCount = 0; - Texture2D lastTexture = null; - for (int i = 0; i < itemCount; i++) { - MeshItem item = items[i]; - int itemVertexCount = item.vertexCount; - - if (item.texture != lastTexture || vertexCount + itemVertexCount > short.MaxValue) { - FlushVertexArray(device, vertexCount, triangleCount); - vertexCount = 0; - triangleCount = 0; - lastTexture = item.texture; - device.Textures[0] = lastTexture; - } - - int[] itemTriangles = item.triangles; - int itemTriangleCount = item.triangleCount; - for (int ii = 0, t = triangleCount; ii < itemTriangleCount; ii++, t++) - triangles[t] = (short)(itemTriangles[ii] + vertexCount); - triangleCount += itemTriangleCount; - - Array.Copy(item.vertices, 0, vertexArray, vertexCount, itemVertexCount); - vertexCount += itemVertexCount; - - item.texture = null; - freeItems.Enqueue(item); - } - FlushVertexArray(device, vertexCount, triangleCount); - items.Clear(); - } - - private void FlushVertexArray (GraphicsDevice device, int vertexCount, int triangleCount) { - if (vertexCount == 0) return; - device.DrawUserIndexedPrimitives( - PrimitiveType.TriangleList, - vertexArray, 0, vertexCount, - triangles, 0, triangleCount / 3, - VertexPositionColorTextureColor.VertexDeclaration); - } - } - - public class MeshItem { - public Texture2D texture; - public int vertexCount, triangleCount; - public VertexPositionColorTextureColor[] vertices = { }; - public int[] triangles = { }; - } +namespace Spine3_6_53 +{ + public struct VertexPositionColorTextureColor : IVertexType + { + public Vector3 Position; + public Color Color; + public Vector2 TextureCoordinate; + public Color Color2; + + public readonly static VertexDeclaration VertexDeclaration = new VertexDeclaration + ( + new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0), + new VertexElement(12, VertexElementFormat.Color, VertexElementUsage.Color, 0), + new VertexElement(16, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0), + new VertexElement(24, VertexElementFormat.Color, VertexElementUsage.Color, 1) + ); + + VertexDeclaration IVertexType.VertexDeclaration + { + get { return VertexDeclaration; } + } + } + + // #region License + // /* + // Microsoft Public License (Ms-PL) + // MonoGame - Copyright 2009 The MonoGame Team + // + // All rights reserved. + // + // This license governs use of the accompanying software. If you use the software, you accept this license. If you do not + // accept the license, do not use the software. + // + // 1. Definitions + // The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under + // U.S. copyright law. + // + // A "contribution" is the original software, or any additions or changes to the software. + // A "contributor" is any person that distributes its contribution under this license. + // "Licensed patents" are a contributor's patent claims that read directly on its contribution. + // + // 2. Grant of Rights + // (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, + // each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. + // (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, + // each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. + // + // 3. Conditions and Limitations + // (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. + // (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, + // your patent license from such contributor to the software ends automatically. + // (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution + // notices that are present in the software. + // (D) If you distribute any portion of the software in source code form, you may do so only under this license by including + // a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object + // code form, you may only do so under a license that complies with this license. + // (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees + // or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent + // permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular + // purpose and non-infringement. + // */ + // #endregion License + // + + /// Draws batched meshes. + public class MeshBatcher + { + private readonly List items; + private readonly Queue freeItems; + private VertexPositionColorTextureColor[] vertexArray = { }; + private short[] triangles = { }; + + public MeshBatcher() + { + items = new List(256); + freeItems = new Queue(256); + EnsureCapacity(256, 512); + } + + /// Returns a pooled MeshItem. + public MeshItem NextItem(int vertexCount, int triangleCount) + { + MeshItem item = freeItems.Count > 0 ? freeItems.Dequeue() : new MeshItem(); + if (item.vertices.Length < vertexCount) item.vertices = new VertexPositionColorTextureColor[vertexCount]; + if (item.triangles.Length < triangleCount) item.triangles = new int[triangleCount]; + item.vertexCount = vertexCount; + item.triangleCount = triangleCount; + items.Add(item); + return item; + } + + private void EnsureCapacity(int vertexCount, int triangleCount) + { + if (vertexArray.Length < vertexCount) vertexArray = new VertexPositionColorTextureColor[vertexCount]; + if (triangles.Length < triangleCount) triangles = new short[triangleCount]; + } + + public void Draw(GraphicsDevice device) + { + if (items.Count == 0) return; + + int itemCount = items.Count; + int vertexCount = 0, triangleCount = 0; + for (int i = 0; i < itemCount; i++) + { + MeshItem item = items[i]; + vertexCount += item.vertexCount; + triangleCount += item.triangleCount; + } + EnsureCapacity(vertexCount, triangleCount); + + vertexCount = 0; + triangleCount = 0; + Texture2D lastTexture = null; + for (int i = 0; i < itemCount; i++) + { + MeshItem item = items[i]; + int itemVertexCount = item.vertexCount; + + if (item.texture != lastTexture || vertexCount + itemVertexCount > short.MaxValue) + { + FlushVertexArray(device, vertexCount, triangleCount); + vertexCount = 0; + triangleCount = 0; + lastTexture = item.texture; + device.Textures[0] = lastTexture; + } + + int[] itemTriangles = item.triangles; + int itemTriangleCount = item.triangleCount; + for (int ii = 0, t = triangleCount; ii < itemTriangleCount; ii++, t++) + triangles[t] = (short)(itemTriangles[ii] + vertexCount); + triangleCount += itemTriangleCount; + + Array.Copy(item.vertices, 0, vertexArray, vertexCount, itemVertexCount); + vertexCount += itemVertexCount; + + item.texture = null; + freeItems.Enqueue(item); + } + FlushVertexArray(device, vertexCount, triangleCount); + items.Clear(); + } + + private void FlushVertexArray(GraphicsDevice device, int vertexCount, int triangleCount) + { + if (vertexCount == 0) return; + device.DrawUserIndexedPrimitives( + PrimitiveType.TriangleList, + vertexArray, 0, vertexCount, + triangles, 0, triangleCount / 3, + VertexPositionColorTextureColor.VertexDeclaration); + } + } + + public class MeshItem + { + public Texture2D texture; + public int vertexCount, triangleCount; + public VertexPositionColorTextureColor[] vertices = { }; + public int[] triangles = { }; + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/XnaLoader/ShapeRenderer.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/XnaLoader/ShapeRenderer.cs index c95ecdf..e7ec97d 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/XnaLoader/ShapeRenderer.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/XnaLoader/ShapeRenderer.cs @@ -29,140 +29,157 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Spine3_6_53 { - /// - /// Batch drawing of lines and shapes that can be derrived from lines. - /// - /// Call drawing methods in between Begin()/End() - /// - public class ShapeRenderer { - GraphicsDevice device; - List vertices = new List(); - Color color = Color.White; - BasicEffect effect; - public BasicEffect Effect { get { return effect; } set { effect = value; } } - - public ShapeRenderer(GraphicsDevice device) { - this.device = device; - this.effect = new BasicEffect(device); - effect.World = Matrix.Identity; - effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); - effect.TextureEnabled = false; - effect.VertexColorEnabled = true; - } - - public void SetColor(Color color) { - this.color = color; - } - - public void Begin() { - device.RasterizerState = new RasterizerState(); - device.BlendState = BlendState.AlphaBlend; - } - - public void Line(float x1, float y1, float x2, float y2) { - vertices.Add(new VertexPositionColor(new Vector3(x1, y1, 0), color)); - vertices.Add(new VertexPositionColor(new Vector3(x2, y2, 0), color)); - } - - /** Calls {@link #circle(float, float, float, int)} by estimating the number of segments needed for a smooth circle. */ - public void Circle(float x, float y, float radius) { - Circle(x, y, radius, Math.Max(1, (int)(6 * (float)Math.Pow(radius, 1.0f / 3.0f)))); - } - - /** Draws a circle using {@link ShapeType#Line} or {@link ShapeType#Filled}. */ - public void Circle(float x, float y, float radius, int segments) { - if (segments <= 0) throw new ArgumentException("segments must be > 0."); - float angle = 2 * MathUtils.PI / segments; - float cos = MathUtils.Cos(angle); - float sin = MathUtils.Sin(angle); - float cx = radius, cy = 0; - float temp = 0; - - for (int i = 0; i < segments; i++) { - vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); - temp = cx; - cx = cos * cx - sin * cy; - cy = sin * temp + cos * cy; - vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); - } - vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); - - temp = cx; - cx = radius; - cy = 0; - vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); - } - - public void Triangle(float x1, float y1, float x2, float y2, float x3, float y3) { - vertices.Add(new VertexPositionColor(new Vector3(x1, y1, 0), color)); - vertices.Add(new VertexPositionColor(new Vector3(x2, y2, 0), color)); - - vertices.Add(new VertexPositionColor(new Vector3(x2, y2, 0), color)); - vertices.Add(new VertexPositionColor(new Vector3(x3, y3, 0), color)); - - vertices.Add(new VertexPositionColor(new Vector3(x3, y3, 0), color)); - vertices.Add(new VertexPositionColor(new Vector3(x1, y1, 0), color)); - } - - public void X(float x, float y, float len) { - Line(x + len, y + len, x - len, y - len); - Line(x - len, y + len, x + len, y - len); - } - - public void Polygon(float[] polygonVertices, int offset, int count) { - if (count< 3) throw new ArgumentException("Polygon must contain at least 3 vertices"); - - offset <<= 1; - count <<= 1; - - var firstX = polygonVertices[offset]; - var firstY = polygonVertices[offset + 1]; - var last = offset + count; - - for (int i = offset, n = offset + count - 2; i= last) { - x2 = firstX; - y2 = firstY; - } else { - x2 = polygonVertices[i + 2]; - y2 = polygonVertices[i + 3]; - } - - Line(x1, y1, x2, y2); - } - } - - public void Rect(float x, float y, float width, float height) { - Line(x, y, x + width, y); - Line(x + width, y, x + width, y + height); - Line(x + width, y + height, x, y + height); - Line(x, y + height, x, y); - } - - public void End() { - if (vertices.Count == 0) return; - var verticesArray = vertices.ToArray(); - - foreach (EffectPass pass in effect.CurrentTechnique.Passes) { - pass.Apply(); - device.DrawUserPrimitives(PrimitiveType.LineList, verticesArray, 0, verticesArray.Length / 2); - } - - vertices.Clear(); - } - } +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Spine3_6_53 +{ + /// + /// Batch drawing of lines and shapes that can be derrived from lines. + /// + /// Call drawing methods in between Begin()/End() + /// + public class ShapeRenderer + { + GraphicsDevice device; + List vertices = new List(); + Color color = Color.White; + BasicEffect effect; + public BasicEffect Effect { get { return effect; } set { effect = value; } } + + public ShapeRenderer(GraphicsDevice device) + { + this.device = device; + this.effect = new BasicEffect(device); + effect.World = Matrix.Identity; + effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); + effect.TextureEnabled = false; + effect.VertexColorEnabled = true; + } + + public void SetColor(Color color) + { + this.color = color; + } + + public void Begin() + { + device.RasterizerState = new RasterizerState(); + device.BlendState = BlendState.AlphaBlend; + } + + public void Line(float x1, float y1, float x2, float y2) + { + vertices.Add(new VertexPositionColor(new Vector3(x1, y1, 0), color)); + vertices.Add(new VertexPositionColor(new Vector3(x2, y2, 0), color)); + } + + /** Calls {@link #circle(float, float, float, int)} by estimating the number of segments needed for a smooth circle. */ + public void Circle(float x, float y, float radius) + { + Circle(x, y, radius, Math.Max(1, (int)(6 * (float)Math.Pow(radius, 1.0f / 3.0f)))); + } + + /** Draws a circle using {@link ShapeType#Line} or {@link ShapeType#Filled}. */ + public void Circle(float x, float y, float radius, int segments) + { + if (segments <= 0) throw new ArgumentException("segments must be > 0."); + float angle = 2 * MathUtils.PI / segments; + float cos = MathUtils.Cos(angle); + float sin = MathUtils.Sin(angle); + float cx = radius, cy = 0; + float temp = 0; + + for (int i = 0; i < segments; i++) + { + vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); + temp = cx; + cx = cos * cx - sin * cy; + cy = sin * temp + cos * cy; + vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); + } + vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); + + temp = cx; + cx = radius; + cy = 0; + vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); + } + + public void Triangle(float x1, float y1, float x2, float y2, float x3, float y3) + { + vertices.Add(new VertexPositionColor(new Vector3(x1, y1, 0), color)); + vertices.Add(new VertexPositionColor(new Vector3(x2, y2, 0), color)); + + vertices.Add(new VertexPositionColor(new Vector3(x2, y2, 0), color)); + vertices.Add(new VertexPositionColor(new Vector3(x3, y3, 0), color)); + + vertices.Add(new VertexPositionColor(new Vector3(x3, y3, 0), color)); + vertices.Add(new VertexPositionColor(new Vector3(x1, y1, 0), color)); + } + + public void X(float x, float y, float len) + { + Line(x + len, y + len, x - len, y - len); + Line(x - len, y + len, x + len, y - len); + } + + public void Polygon(float[] polygonVertices, int offset, int count) + { + if (count < 3) throw new ArgumentException("Polygon must contain at least 3 vertices"); + + offset <<= 1; + count <<= 1; + + var firstX = polygonVertices[offset]; + var firstY = polygonVertices[offset + 1]; + var last = offset + count; + + for (int i = offset, n = offset + count - 2; i < n; i += 2) + { + var x1 = polygonVertices[i]; + var y1 = polygonVertices[i + 1]; + + var x2 = 0f; + var y2 = 0f; + + if (i + 2 >= last) + { + x2 = firstX; + y2 = firstY; + } + else + { + x2 = polygonVertices[i + 2]; + y2 = polygonVertices[i + 3]; + } + + Line(x1, y1, x2, y2); + } + } + + public void Rect(float x, float y, float width, float height) + { + Line(x, y, x + width, y); + Line(x + width, y, x + width, y + height); + Line(x + width, y + height, x, y + height); + Line(x, y + height, x, y); + } + + public void End() + { + if (vertices.Count == 0) return; + var verticesArray = vertices.ToArray(); + + foreach (EffectPass pass in effect.CurrentTechnique.Passes) + { + pass.Apply(); + device.DrawUserPrimitives(PrimitiveType.LineList, verticesArray, 0, verticesArray.Length / 2); + } + + vertices.Clear(); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/XnaLoader/SkeletonRenderer.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/XnaLoader/SkeletonRenderer.cs index 04a6df2..14c94f0 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/XnaLoader/SkeletonRenderer.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/XnaLoader/SkeletonRenderer.cs @@ -29,116 +29,126 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; -using System.Collections.Generic; -using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; -namespace Spine3_6_53 { - /// Draws region and mesh attachments. - public class SkeletonRenderer { - private const int TL = 0; - private const int TR = 1; - private const int BL = 2; - private const int BR = 3; +namespace Spine3_6_53 +{ + /// Draws region and mesh attachments. + public class SkeletonRenderer + { + private const int TL = 0; + private const int TR = 1; + private const int BL = 2; + private const int BR = 3; - SkeletonClipping clipper = new SkeletonClipping(); - GraphicsDevice device; - MeshBatcher batcher; - public MeshBatcher Batcher { get { return batcher; } } - RasterizerState rasterizerState; - float[] vertices = new float[8]; - int[] quadTriangles = { 0, 1, 2, 2, 3, 0 }; - BlendState defaultBlendState; + SkeletonClipping clipper = new SkeletonClipping(); + GraphicsDevice device; + MeshBatcher batcher; + public MeshBatcher Batcher { get { return batcher; } } + RasterizerState rasterizerState; + float[] vertices = new float[8]; + int[] quadTriangles = { 0, 1, 2, 2, 3, 0 }; + BlendState defaultBlendState; - Effect effect; - public Effect Effect { get { return effect; } set { effect = value; } } + Effect effect; + public Effect Effect { get { return effect; } set { effect = value; } } - private bool premultipliedAlpha; - public bool PremultipliedAlpha { get { return premultipliedAlpha; } set { premultipliedAlpha = value; } } + private bool premultipliedAlpha; + public bool PremultipliedAlpha { get { return premultipliedAlpha; } set { premultipliedAlpha = value; } } - public SkeletonRenderer (GraphicsDevice device) { - this.device = device; + public SkeletonRenderer(GraphicsDevice device) + { + this.device = device; - batcher = new MeshBatcher(); + batcher = new MeshBatcher(); - var basicEffect = new BasicEffect(device); - basicEffect.World = Matrix.Identity; - basicEffect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); - basicEffect.TextureEnabled = true; - basicEffect.VertexColorEnabled = true; - effect = basicEffect; + var basicEffect = new BasicEffect(device); + basicEffect.World = Matrix.Identity; + basicEffect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); + basicEffect.TextureEnabled = true; + basicEffect.VertexColorEnabled = true; + effect = basicEffect; - rasterizerState = new RasterizerState(); - rasterizerState.CullMode = CullMode.None; + rasterizerState = new RasterizerState(); + rasterizerState.CullMode = CullMode.None; - Bone.yDown = true; - } + Bone.yDown = true; + } - public void Begin () { - defaultBlendState = premultipliedAlpha ? BlendState.AlphaBlend : BlendState.NonPremultiplied; + public void Begin() + { + defaultBlendState = premultipliedAlpha ? BlendState.AlphaBlend : BlendState.NonPremultiplied; - device.RasterizerState = rasterizerState; - device.BlendState = defaultBlendState; - } + device.RasterizerState = rasterizerState; + device.BlendState = defaultBlendState; + } - public void End () { - foreach (EffectPass pass in effect.CurrentTechnique.Passes) { - pass.Apply(); - batcher.Draw(device); - } - } + public void End() + { + foreach (EffectPass pass in effect.CurrentTechnique.Passes) + { + pass.Apply(); + batcher.Draw(device); + } + } - public void Draw(Skeleton skeleton) { - var drawOrder = skeleton.DrawOrder; - var drawOrderItems = skeleton.DrawOrder.Items; - float skeletonR = skeleton.R, skeletonG = skeleton.G, skeletonB = skeleton.B, skeletonA = skeleton.A; - Color color = new Color(); + public void Draw(Skeleton skeleton) + { + var drawOrder = skeleton.DrawOrder; + var drawOrderItems = skeleton.DrawOrder.Items; + float skeletonR = skeleton.R, skeletonG = skeleton.G, skeletonB = skeleton.B, skeletonA = skeleton.A; + Color color = new Color(); - for (int i = 0, n = drawOrder.Count; i < n; i++) { - Slot slot = drawOrderItems[i]; - Attachment attachment = slot.Attachment; + for (int i = 0, n = drawOrder.Count; i < n; i++) + { + Slot slot = drawOrderItems[i]; + Attachment attachment = slot.Attachment; - float attachmentColorR, attachmentColorG, attachmentColorB, attachmentColorA; - Texture2D texture = null; - int verticesCount = 0; - float[] vertices = this.vertices; - int indicesCount = 0; - int[] indices = null; - float[] uvs = null; + float attachmentColorR, attachmentColorG, attachmentColorB, attachmentColorA; + Texture2D texture = null; + int verticesCount = 0; + float[] vertices = this.vertices; + int indicesCount = 0; + int[] indices = null; + float[] uvs = null; - if (attachment is RegionAttachment) { - RegionAttachment regionAttachment = (RegionAttachment)attachment; - attachmentColorR = regionAttachment.R; attachmentColorG = regionAttachment.G; attachmentColorB = regionAttachment.B; attachmentColorA = regionAttachment.A; - AtlasRegion region = (AtlasRegion)regionAttachment.RendererObject; - texture = (Texture2D)region.page.rendererObject; - verticesCount = 4; - regionAttachment.ComputeWorldVertices(slot.Bone, vertices, 0, 2); - indicesCount = 6; - indices = quadTriangles; - uvs = regionAttachment.UVs; - } - else if (attachment is MeshAttachment) { - MeshAttachment mesh = (MeshAttachment)attachment; - attachmentColorR = mesh.R; attachmentColorG = mesh.G; attachmentColorB = mesh.B; attachmentColorA = mesh.A; - AtlasRegion region = (AtlasRegion)mesh.RendererObject; - texture = (Texture2D)region.page.rendererObject; - int vertexCount = mesh.WorldVerticesLength; - if (vertices.Length < vertexCount) vertices = new float[vertexCount]; - verticesCount = vertexCount >> 1; - mesh.ComputeWorldVertices(slot, vertices); - indicesCount = mesh.Triangles.Length; - indices = mesh.Triangles; - uvs = mesh.UVs; - } - else if (attachment is ClippingAttachment) { - ClippingAttachment clip = (ClippingAttachment)attachment; - clipper.ClipStart(slot, clip); - continue; - } - else { - continue; - } + if (attachment is RegionAttachment) + { + RegionAttachment regionAttachment = (RegionAttachment)attachment; + attachmentColorR = regionAttachment.R; attachmentColorG = regionAttachment.G; attachmentColorB = regionAttachment.B; attachmentColorA = regionAttachment.A; + AtlasRegion region = (AtlasRegion)regionAttachment.RendererObject; + texture = (Texture2D)region.page.rendererObject; + verticesCount = 4; + regionAttachment.ComputeWorldVertices(slot.Bone, vertices, 0, 2); + indicesCount = 6; + indices = quadTriangles; + uvs = regionAttachment.UVs; + } + else if (attachment is MeshAttachment) + { + MeshAttachment mesh = (MeshAttachment)attachment; + attachmentColorR = mesh.R; attachmentColorG = mesh.G; attachmentColorB = mesh.B; attachmentColorA = mesh.A; + AtlasRegion region = (AtlasRegion)mesh.RendererObject; + texture = (Texture2D)region.page.rendererObject; + int vertexCount = mesh.WorldVerticesLength; + if (vertices.Length < vertexCount) vertices = new float[vertexCount]; + verticesCount = vertexCount >> 1; + mesh.ComputeWorldVertices(slot, vertices); + indicesCount = mesh.Triangles.Length; + indices = mesh.Triangles; + uvs = mesh.UVs; + } + else if (attachment is ClippingAttachment) + { + ClippingAttachment clip = (ClippingAttachment)attachment; + clipper.ClipStart(slot, clip); + continue; + } + else + { + continue; + } // set blend state BlendState blendState = new BlendState(); @@ -200,62 +210,71 @@ public void Draw(Skeleton skeleton) { // calculate color float a = skeletonA * slot.A * attachmentColorA; - if (premultipliedAlpha) { - color = new Color( - skeletonR * slot.R * attachmentColorR * a, - skeletonG * slot.G * attachmentColorG * a, - skeletonB * slot.B * attachmentColorB * a, a); - } - else { - color = new Color( - skeletonR * slot.R * attachmentColorR, - skeletonG * slot.G * attachmentColorG, - skeletonB * slot.B * attachmentColorB, a); - } + if (premultipliedAlpha) + { + color = new Color( + skeletonR * slot.R * attachmentColorR * a, + skeletonG * slot.G * attachmentColorG * a, + skeletonB * slot.B * attachmentColorB * a, a); + } + else + { + color = new Color( + skeletonR * slot.R * attachmentColorR, + skeletonG * slot.G * attachmentColorG, + skeletonB * slot.B * attachmentColorB, a); + } - Color darkColor = new Color(); - if (slot.HasSecondColor) { - if (premultipliedAlpha) { - darkColor = new Color(slot.R2 * a, slot.G2 * a, slot.B2 * a); - } else { - darkColor = new Color(slot.R2 * a, slot.G2 * a, slot.B2 * a); - } - } - darkColor.A = premultipliedAlpha ? (byte)255 : (byte)0; + Color darkColor = new Color(); + if (slot.HasSecondColor) + { + if (premultipliedAlpha) + { + darkColor = new Color(slot.R2 * a, slot.G2 * a, slot.B2 * a); + } + else + { + darkColor = new Color(slot.R2 * a, slot.G2 * a, slot.B2 * a); + } + } + darkColor.A = premultipliedAlpha ? (byte)255 : (byte)0; - // clip - if (clipper.IsClipping) { - clipper.ClipTriangles(vertices, verticesCount << 1, indices, indicesCount, uvs); - vertices = clipper.ClippedVertices.Items; - verticesCount = clipper.ClippedVertices.Count >> 1; - indices = clipper.ClippedTriangles.Items; - indicesCount = clipper.ClippedTriangles.Count; - uvs = clipper.ClippedUVs.Items; - } + // clip + if (clipper.IsClipping) + { + clipper.ClipTriangles(vertices, verticesCount << 1, indices, indicesCount, uvs); + vertices = clipper.ClippedVertices.Items; + verticesCount = clipper.ClippedVertices.Count >> 1; + indices = clipper.ClippedTriangles.Items; + indicesCount = clipper.ClippedTriangles.Count; + uvs = clipper.ClippedUVs.Items; + } - if (verticesCount == 0 || indicesCount == 0) - continue; + if (verticesCount == 0 || indicesCount == 0) + continue; - // submit to batch - MeshItem item = batcher.NextItem(verticesCount, indicesCount); - item.texture = texture; - for (int ii = 0, nn = indicesCount; ii < nn; ii++) { - item.triangles[ii] = indices[ii]; - } - VertexPositionColorTextureColor[] itemVertices = item.vertices; - for (int ii = 0, v = 0, nn = verticesCount << 1; v < nn; ii++, v += 2) { - itemVertices[ii].Color = color; - itemVertices[ii].Color2 = darkColor; - itemVertices[ii].Position.X = vertices[v]; - itemVertices[ii].Position.Y = vertices[v + 1]; - itemVertices[ii].Position.Z = 0; - itemVertices[ii].TextureCoordinate.X = uvs[v]; - itemVertices[ii].TextureCoordinate.Y = uvs[v + 1]; - } + // submit to batch + MeshItem item = batcher.NextItem(verticesCount, indicesCount); + item.texture = texture; + for (int ii = 0, nn = indicesCount; ii < nn; ii++) + { + item.triangles[ii] = indices[ii]; + } + VertexPositionColorTextureColor[] itemVertices = item.vertices; + for (int ii = 0, v = 0, nn = verticesCount << 1; v < nn; ii++, v += 2) + { + itemVertices[ii].Color = color; + itemVertices[ii].Color2 = darkColor; + itemVertices[ii].Position.X = vertices[v]; + itemVertices[ii].Position.Y = vertices[v + 1]; + itemVertices[ii].Position.Z = 0; + itemVertices[ii].TextureCoordinate.X = uvs[v]; + itemVertices[ii].TextureCoordinate.Y = uvs[v + 1]; + } - clipper.ClipEnd(slot); - } - clipper.ClipEnd(); - } - } + clipper.ClipEnd(slot); + } + clipper.ClipEnd(); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/XnaLoader/XnaTextureLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/XnaLoader/XnaTextureLoader.cs index ae03e13..ad557c4 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/XnaLoader/XnaTextureLoader.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.6.53/XnaLoader/XnaTextureLoader.cs @@ -30,8 +30,6 @@ *****************************************************************************/ using System; -using System.IO; -using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; namespace Spine3_6_53 diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Animation.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Animation.cs index 3f0948a..f3eb212 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Animation.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Animation.cs @@ -28,1748 +28,2034 @@ *****************************************************************************/ using System; -using System.Collections.Generic; - -namespace Spine3_7_94 { - - /// - /// A simple container for a list of timelines and a name. - public class Animation { - internal String name; - internal ExposedList timelines; - internal float duration; - - public Animation (string name, ExposedList timelines, float duration) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - if (timelines == null) throw new ArgumentNullException("timelines", "timelines cannot be null."); - this.name = name; - this.timelines = timelines; - this.duration = duration; - } - - public ExposedList Timelines { get { return timelines; } set { timelines = value; } } - - /// The duration of the animation in seconds, which is the highest time of all keys in the timeline. - public float Duration { get { return duration; } set { duration = value; } } - - /// The animation's name, which is unique within the skeleton. - public string Name { get { return name; } } - - /// Applies all the animation's timelines to the specified skeleton. - /// - public void Apply (Skeleton skeleton, float lastTime, float time, bool loop, ExposedList events, float alpha, MixBlend blend, - MixDirection direction) { - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - - if (loop && duration != 0) { - time %= duration; - if (lastTime > 0) lastTime %= duration; - } - - ExposedList timelines = this.timelines; - for (int i = 0, n = timelines.Count; i < n; i++) - timelines.Items[i].Apply(skeleton, lastTime, time, events, alpha, blend, direction); - } - - override public string ToString () { - return name; - } - - /// After the first and before the last entry. - /// Index of first value greater than the target. - internal static int BinarySearch (float[] values, float target, int step) { - int low = 0; - int high = values.Length / step - 2; - if (high == 0) return step; - int current = (int)((uint)high >> 1); - while (true) { - if (values[(current + 1) * step] <= target) - low = current + 1; - else - high = current; - if (low == high) return (low + 1) * step; - current = (int)((uint)(low + high) >> 1); - } - } - - /// After the first and before the last entry. - internal static int BinarySearch (float[] values, float target) { - int low = 0; - int high = values.Length - 2; - if (high == 0) return 1; - int current = (int)((uint)high >> 1); - while (true) { - if (values[current + 1] <= target) - low = current + 1; - else - high = current; - if (low == high) return (low + 1); - current = (int)((uint)(low + high) >> 1); - } - } - - internal static int LinearSearch (float[] values, float target, int step) { - for (int i = 0, last = values.Length - step; i <= last; i += step) - if (values[i] > target) return i; - return -1; - } - } - - /// - /// The interface for all timelines. - public interface Timeline { - /// Applies this timeline to the skeleton. - /// The skeleton the timeline is being applied to. This provides access to the bones, slots, and other - /// skeleton components the timeline may change. - /// The time this timeline was last applied. Timelines such as trigger only at specific - /// times rather than every frame. In that case, the timeline triggers everything between lastTime - /// (exclusive) and time (inclusive). - /// The time within the animation. Most timelines find the key before and the key after this time so they can - /// interpolate between the keys. - /// If any events are fired, they are added to this list. Can be null to ignore firing events or if the - /// timeline does not fire events. - /// 0 applies the current or setup value (depending on blend). 1 applies the timeline value. - /// Between 0 and 1 applies a value between the current or setup value 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). - /// Controls how mixing is applied when alpha < 1. - /// Indicates whether the timeline is mixing in or out. Used by timelines which perform instant transitions, - /// such as or . - void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, MixBlend blend, MixDirection direction); - /// Uniquely encodes both the type of this timeline and the skeleton property that it affects. - int PropertyId { get; } - } - - /// - /// Controls how a timeline is mixed with the setup or current pose. - /// - public enum MixBlend { - - /// Transitions from the setup value to the timeline value (the current value is not used). Before the first key, the setup - /// value is set. - Setup, - - /// - /// - /// Transitions from the current value to the timeline value. Before the first key, transitions from the current value to - /// the setup value. Timelines which perform instant transitions, such as or - /// , use the setup value before the first key. - /// - /// First is intended for the first animations applied, not for animations layered on top of those. - /// - First, - - /// - /// - /// Transitions from the current value to the timeline value. No change is made before the first key (the current value is - /// kept until the first key). - /// - /// Replace is intended for animations layered on top of others, not for the first animations applied. - /// - Replace, - - /// - /// - /// Transitions from the current value to the current value plus the timeline value. No change is made before the first key - /// (the current value is kept until the first key). - /// - /// Add is intended for animations layered on top of others, not for the first animations applied. - /// - Add - } - - /// - /// Indicates whether a timeline's alpha is mixing out over time toward 0 (the setup or current pose value) or - /// mixing in toward 1 (the timeline's value). - /// - public enum MixDirection { - In, - Out - } - - internal enum TimelineType { - Rotate = 0, Translate, Scale, Shear, // - Attachment, Color, Deform, // - Event, DrawOrder, // - IkConstraint, TransformConstraint, // - PathConstraintPosition, PathConstraintSpacing, PathConstraintMix, // - TwoColor - } - - /// An interface for timelines which change the property of a bone. - public interface IBoneTimeline { - /// The index of the bone in that will be changed. - int BoneIndex { get; } - } - - /// An interface for timelines which change the property of a slot. - public interface ISlotTimeline { - /// The index of the slot in that will be changed. - int SlotIndex { get; } - } - - /// The base class for timelines that use interpolation between key frame values. - abstract public class CurveTimeline : Timeline { - protected const float LINEAR = 0, STEPPED = 1, BEZIER = 2; - protected const int BEZIER_SIZE = 10 * 2 - 1; - - internal float[] curves; // type, x, y, ... - /// The number of key frames for this timeline. - public int FrameCount { get { return curves.Length / BEZIER_SIZE + 1; } } - - public CurveTimeline (int frameCount) { - if (frameCount <= 0) throw new ArgumentOutOfRangeException("frameCount must be > 0: "); - curves = new float[(frameCount - 1) * BEZIER_SIZE]; - } - - abstract public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, MixDirection direction); - - abstract public int PropertyId { get; } - - /// Sets the specified key frame to linear interpolation. - public void SetLinear (int frameIndex) { - curves[frameIndex * BEZIER_SIZE] = LINEAR; - } - - /// Sets the specified key frame to stepped interpolation. - public void SetStepped (int frameIndex) { - curves[frameIndex * BEZIER_SIZE] = STEPPED; - } - - /// Returns the interpolation type for the specified key frame. - /// Linear is 0, stepped is 1, Bezier is 2. - public float GetCurveType (int frameIndex) { - int index = frameIndex * BEZIER_SIZE; - if (index == curves.Length) return LINEAR; - float type = curves[index]; - if (type == LINEAR) return LINEAR; - if (type == STEPPED) return STEPPED; - return BEZIER; - } - - /// Sets the specified key frame to Bezier interpolation. cx1 and cx2 are from 0 to 1, - /// representing the percent of time between the two key frames. cy1 and cy2 are the percent of the - /// difference between the key frame's values. - public void SetCurve (int frameIndex, float cx1, float cy1, float cx2, float cy2) { - float tmpx = (-cx1 * 2 + cx2) * 0.03f, tmpy = (-cy1 * 2 + cy2) * 0.03f; - float dddfx = ((cx1 - cx2) * 3 + 1) * 0.006f, dddfy = ((cy1 - cy2) * 3 + 1) * 0.006f; - float ddfx = tmpx * 2 + dddfx, ddfy = tmpy * 2 + dddfy; - float dfx = cx1 * 0.3f + tmpx + dddfx * 0.16666667f, dfy = cy1 * 0.3f + tmpy + dddfy * 0.16666667f; - - int i = frameIndex * BEZIER_SIZE; - float[] curves = this.curves; - curves[i++] = BEZIER; - - float x = dfx, y = dfy; - for (int n = i + BEZIER_SIZE - 1; i < n; i += 2) { - curves[i] = x; - curves[i + 1] = y; - dfx += ddfx; - dfy += ddfy; - ddfx += dddfx; - ddfy += dddfy; - x += dfx; - y += dfy; - } - } - - /// Returns the interpolated percentage for the specified key frame and linear percentage. - public float GetCurvePercent (int frameIndex, float percent) { - percent = MathUtils.Clamp (percent, 0, 1); - float[] curves = this.curves; - int i = frameIndex * BEZIER_SIZE; - float type = curves[i]; - if (type == LINEAR) return percent; - if (type == STEPPED) return 0; - i++; - float x = 0; - for (int start = i, n = i + BEZIER_SIZE - 1; i < n; i += 2) { - x = curves[i]; - if (x >= percent) { - if (i == start) return curves[i + 1] * percent / x; // First point is 0,0. - float prevX = curves[i - 2], prevY = curves[i - 1]; - return prevY + (curves[i + 1] - prevY) * (percent - prevX) / (x - prevX); - } - } - float y = curves[i - 1]; - return y + (1 - y) * (percent - x) / (1 - x); // Last point is 1,1. - } - } - - /// Changes a bone's local . - public class RotateTimeline : CurveTimeline, IBoneTimeline { - public const int ENTRIES = 2; - internal const int PREV_TIME = -2, PREV_ROTATION = -1; - internal const int ROTATION = 1; - - internal int boneIndex; - internal float[] frames; // time, degrees, ... - - public RotateTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount << 1]; - } - - override public int PropertyId { - get { return ((int)TimelineType.Rotate << 24) + boneIndex; } - } - /// The index of the bone in that will be changed. - public int BoneIndex { - set { - if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0."); - this.boneIndex = value; - } - get { - return boneIndex; - } - } - /// The time in seconds and rotation in degrees for each key frame. - public float[] Frames { get { return frames; } set { frames = value; } } - - /// Sets the time in seconds and the rotation in degrees for the specified key frame. - public void SetFrame (int frameIndex, float time, float degrees) { - frameIndex <<= 1; - frames[frameIndex] = time; - frames[frameIndex + ROTATION] = degrees; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case MixBlend.Setup: - bone.rotation = bone.data.rotation; - return; - case MixBlend.First: - float r = bone.data.rotation - bone.rotation; - bone.rotation += (r - (16384 - (int)(16384.499999999996 - r / 360)) * 360) * alpha; - return; - } - return; - } - - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - float r = frames[frames.Length + PREV_ROTATION]; - switch (blend) { - case MixBlend.Setup: - bone.rotation = bone.data.rotation + r * alpha; - break; - case MixBlend.First: - case MixBlend.Replace: - r += bone.data.rotation - bone.rotation; - r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; - goto case MixBlend.Add; // Fall through. - - case MixBlend.Add: - bone.rotation += r * alpha; - break; - } - return; - } - - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - float prevRotation = frames[frame + PREV_ROTATION]; - float frameTime = frames[frame]; - float percent = GetCurvePercent((frame >> 1) - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - // scope for 'r' to prevent compile error. - { - float r = frames[frame + ROTATION] - prevRotation; - r = prevRotation + (r - (16384 - (int)(16384.499999999996 - r / 360)) * 360) * percent; - switch (blend) { - case MixBlend.Setup: - bone.rotation = bone.data.rotation + (r - (16384 - (int)(16384.499999999996 - r / 360)) * 360) * alpha; - break; - case MixBlend.First: - case MixBlend.Replace: - r += bone.data.rotation - bone.rotation; - goto case MixBlend.Add; // Fall through. - case MixBlend.Add: - bone.rotation += (r - (16384 - (int)(16384.499999999996 - r / 360)) * 360) * alpha; - break; - } - } - } - } - - /// Changes a bone's local and . - public class TranslateTimeline : CurveTimeline, IBoneTimeline { - public const int ENTRIES = 3; - protected const int PREV_TIME = -3, PREV_X = -2, PREV_Y = -1; - protected const int X = 1, Y = 2; - - internal int boneIndex; - internal float[] frames; // time, x, y, ... - - public TranslateTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - override public int PropertyId { - get { return ((int)TimelineType.Translate << 24) + boneIndex; } - } - - /// The index of the bone in that will be changed. - public int BoneIndex { - set { - if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0."); - this.boneIndex = value; - } - get { - return boneIndex; - } - } - /// The time in seconds, x, and y values for each key frame. - public float[] Frames { get { return frames; } set { frames = value; } } - - - /// Sets the time in seconds, x, and y values for the specified key frame. - public void SetFrame (int frameIndex, float time, float x, float y) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + X] = x; - frames[frameIndex + Y] = y; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - 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; - return; - } - return; - } - - float x, y; - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - x = frames[frames.Length + PREV_X]; - y = frames[frames.Length + PREV_Y]; - } else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - x = frames[frame + PREV_X]; - y = frames[frame + PREV_Y]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - x += (frames[frame + X] - x) * percent; - y += (frames[frame + Y] - y) * percent; - } - 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; - break; - } - } - } - - /// Changes a bone's local and . - public class ScaleTimeline : TranslateTimeline, IBoneTimeline { - public ScaleTimeline (int frameCount) - : base(frameCount) { - } - - override public int PropertyId { - get { return ((int)TimelineType.Scale << 24) + boneIndex; } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - 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; - return; - } - return; - } - - float x, y; - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - x = frames[frames.Length + PREV_X] * bone.data.scaleX; - y = frames[frames.Length + PREV_Y] * bone.data.scaleY; - } else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - x = frames[frame + PREV_X]; - y = frames[frame + PREV_Y]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - x = (x + (frames[frame + X] - x) * percent) * bone.data.scaleX; - y = (y + (frames[frame + Y] - y) * percent) * 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 { - // Mixing out uses sign of setup or current pose, else use sign of key. - float bx, by; - if (direction == MixDirection.Out) { - switch (blend) { - case MixBlend.Setup: - bx = bone.data.scaleX; - by = bone.data.scaleY; - bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha; - bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha; - break; - case MixBlend.First: - case MixBlend.Replace: - bx = bone.scaleX; - by = bone.scaleY; - bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha; - bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha; - break; - case MixBlend.Add: - bx = bone.scaleX; - by = bone.scaleY; - bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bone.data.scaleX) * alpha; - bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - bone.data.scaleY) * alpha; - break; - } - } else { - switch (blend) { - case MixBlend.Setup: - bx = Math.Abs(bone.data.scaleX) * Math.Sign(x); - by = Math.Abs(bone.data.scaleY) * Math.Sign(y); - bone.scaleX = bx + (x - bx) * alpha; - bone.scaleY = by + (y - by) * alpha; - break; - case MixBlend.First: - case MixBlend.Replace: - bx = Math.Abs(bone.scaleX) * Math.Sign(x); - by = Math.Abs(bone.scaleY) * Math.Sign(y); - bone.scaleX = bx + (x - bx) * alpha; - bone.scaleY = by + (y - by) * alpha; - break; - case MixBlend.Add: - bx = Math.Sign(x); - by = Math.Sign(y); - bone.scaleX = Math.Abs(bone.scaleX) * bx + (x - Math.Abs(bone.data.scaleX) * bx) * alpha; - bone.scaleY = Math.Abs(bone.scaleY) * by + (y - Math.Abs(bone.data.scaleY) * by) * alpha; - break; - } - } - } - } - } - - /// Changes a bone's local and . - public class ShearTimeline : TranslateTimeline, IBoneTimeline { - public ShearTimeline (int frameCount) - : base(frameCount) { - } - - override public int PropertyId { - get { return ((int)TimelineType.Shear << 24) + boneIndex; } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - 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; - return; - } - return; - } - - float x, y; - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - x = frames[frames.Length + PREV_X]; - y = frames[frames.Length + PREV_Y]; - } else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - x = frames[frame + PREV_X]; - y = frames[frame + PREV_Y]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - x = x + (frames[frame + X] - x) * percent; - y = y + (frames[frame + Y] - y) * percent; - } - 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; - break; - } - } - } - - /// Changes a slot's . - public class ColorTimeline : CurveTimeline, ISlotTimeline { - public const int ENTRIES = 5; - protected const int PREV_TIME = -5, PREV_R = -4, PREV_G = -3, PREV_B = -2, PREV_A = -1; - protected const int R = 1, G = 2, B = 3, A = 4; - - internal int slotIndex; - internal float[] frames; // time, r, g, b, a, ... - - public ColorTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - override public int PropertyId { - get { return ((int)TimelineType.Color << 24) + slotIndex; } - } - - /// The index of the slot in that will be changed. - public int SlotIndex { - set { - if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0."); - this.slotIndex = value; - } - get { - return slotIndex; - } - } - /// The time in seconds, red, green, blue, and alpha values for each key frame. - public float[] Frames { get { return frames; } set { frames = value; } } - - /// Sets the time in seconds, red, green, blue, and alpha for the specified key frame. - public void SetFrame (int frameIndex, float time, float r, float g, float b, float a) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + R] = r; - frames[frameIndex + G] = g; - frames[frameIndex + B] = b; - frames[frameIndex + A] = a; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Slot slot = skeleton.slots.Items[slotIndex]; - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - var slotData = slot.data; - switch (blend) { - case MixBlend.Setup: - slot.r = slotData.r; - slot.g = slotData.g; - slot.b = slotData.b; - slot.a = slotData.a; - return; - case MixBlend.First: - slot.r += (slotData.r - slot.r) * alpha; - slot.g += (slotData.g - slot.g) * alpha; - slot.b += (slotData.b - slot.b) * alpha; - slot.a += (slotData.a - slot.a) * alpha; - return; - } - return; - } - - float r, g, b, a; - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - int i = frames.Length; - r = frames[i + PREV_R]; - g = frames[i + PREV_G]; - b = frames[i + PREV_B]; - a = frames[i + PREV_A]; - } else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - r = frames[frame + PREV_R]; - g = frames[frame + PREV_G]; - b = frames[frame + PREV_B]; - a = frames[frame + PREV_A]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - r += (frames[frame + R] - r) * percent; - g += (frames[frame + G] - g) * percent; - b += (frames[frame + B] - b) * percent; - a += (frames[frame + A] - a) * percent; - } - if (alpha == 1) { - slot.r = r; - slot.g = g; - slot.b = b; - slot.a = a; - } else { - float br, bg, bb, ba; - if (blend == MixBlend.Setup) { - br = slot.data.r; - bg = slot.data.g; - bb = slot.data.b; - ba = slot.data.a; - } else { - br = slot.r; - bg = slot.g; - bb = slot.b; - ba = slot.a; - } - slot.r = br + ((r - br) * alpha); - slot.g = bg + ((g - bg) * alpha); - slot.b = bb + ((b - bb) * alpha); - slot.a = ba + ((a - ba) * alpha); - } - } - } - - /// Changes a slot's and for two color tinting. - public class TwoColorTimeline : CurveTimeline, ISlotTimeline { - public const int ENTRIES = 8; - protected const int PREV_TIME = -8, PREV_R = -7, PREV_G = -6, PREV_B = -5, PREV_A = -4; - protected const int PREV_R2 = -3, PREV_G2 = -2, PREV_B2 = -1; - protected const int R = 1, G = 2, B = 3, A = 4, R2 = 5, G2 = 6, B2 = 7; - - internal int slotIndex; - internal float[] frames; // time, r, g, b, a, r2, g2, b2, ... - - public TwoColorTimeline (int frameCount) : - base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - override public int PropertyId { - get { return ((int)TimelineType.TwoColor << 24) + slotIndex; } - } - - /// The index of the slot in that will be changed. - public int SlotIndex { - set { - if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0."); - this.slotIndex = value; - } - get { - return slotIndex; - } - } - /// The time in seconds, red, green, blue, and alpha values for each key frame. - public float[] Frames { get { return frames; } } - - /// Sets the time in seconds, light, and dark colors for the specified key frame.. - public void SetFrame (int frameIndex, float time, float r, float g, float b, float a, float r2, float g2, float b2) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + R] = r; - frames[frameIndex + G] = g; - frames[frameIndex + B] = b; - frames[frameIndex + A] = a; - frames[frameIndex + R2] = r2; - frames[frameIndex + G2] = g2; - frames[frameIndex + B2] = b2; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Slot slot = skeleton.slots.Items[slotIndex]; - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - var slotData = slot.data; - switch (blend) { - case MixBlend.Setup: - // slot.color.set(slot.data.color); - // slot.darkColor.set(slot.data.darkColor); - slot.r = slotData.r; - slot.g = slotData.g; - slot.b = slotData.b; - slot.a = slotData.a; - slot.r2 = slotData.r2; - slot.g2 = slotData.g2; - slot.b2 = slotData.b2; - return; - case MixBlend.First: - slot.r += (slot.r - slotData.r) * alpha; - slot.g += (slot.g - slotData.g) * alpha; - slot.b += (slot.b - slotData.b) * alpha; - slot.a += (slot.a - slotData.a) * alpha; - slot.r2 += (slot.r2 - slotData.r2) * alpha; - slot.g2 += (slot.g2 - slotData.g2) * alpha; - slot.b2 += (slot.b2 - slotData.b2) * alpha; - return; - } - return; - } - - float r, g, b, a, r2, g2, b2; - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - int i = frames.Length; - r = frames[i + PREV_R]; - g = frames[i + PREV_G]; - b = frames[i + PREV_B]; - a = frames[i + PREV_A]; - r2 = frames[i + PREV_R2]; - g2 = frames[i + PREV_G2]; - b2 = frames[i + PREV_B2]; - } else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - r = frames[frame + PREV_R]; - g = frames[frame + PREV_G]; - b = frames[frame + PREV_B]; - a = frames[frame + PREV_A]; - r2 = frames[frame + PREV_R2]; - g2 = frames[frame + PREV_G2]; - b2 = frames[frame + PREV_B2]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - r += (frames[frame + R] - r) * percent; - g += (frames[frame + G] - g) * percent; - b += (frames[frame + B] - b) * percent; - a += (frames[frame + A] - a) * percent; - r2 += (frames[frame + R2] - r2) * percent; - g2 += (frames[frame + G2] - g2) * percent; - b2 += (frames[frame + B2] - b2) * percent; - } - if (alpha == 1) { - slot.r = r; - slot.g = g; - slot.b = b; - slot.a = a; - slot.r2 = r2; - slot.g2 = g2; - slot.b2 = b2; - } else { - float br, bg, bb, ba, br2, bg2, bb2; - if (blend == MixBlend.Setup) { - br = slot.data.r; - bg = slot.data.g; - bb = slot.data.b; - ba = slot.data.a; - br2 = slot.data.r2; - bg2 = slot.data.g2; - bb2 = slot.data.b2; - } else { - br = slot.r; - bg = slot.g; - bb = slot.b; - ba = slot.a; - br2 = slot.r2; - bg2 = slot.g2; - bb2 = slot.b2; - } - slot.r = br + ((r - br) * alpha); - slot.g = bg + ((g - bg) * alpha); - slot.b = bb + ((b - bb) * alpha); - slot.a = ba + ((a - ba) * alpha); - slot.r2 = br2 + ((r2 - br2) * alpha); - slot.g2 = bg2 + ((g2 - bg2) * alpha); - slot.b2 = bb2 + ((b2 - bb2) * alpha); - } - } - - } - - /// Changes a slot's . - public class AttachmentTimeline : Timeline, ISlotTimeline { - internal int slotIndex; - internal float[] frames; // time, ... - internal string[] attachmentNames; - - public AttachmentTimeline (int frameCount) { - frames = new float[frameCount]; - attachmentNames = new String[frameCount]; - } - - public int PropertyId { - get { return ((int)TimelineType.Attachment << 24) + slotIndex; } - } - - /// The number of key frames for this timeline. - public int FrameCount { get { return frames.Length; } } - - /// The index of the slot in that will be changed. - public int SlotIndex { - set { - if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0."); - this.slotIndex = value; - } - get { - return slotIndex; - } - } - - /// The time in seconds for each key frame. - public float[] Frames { get { return frames; } set { frames = value; } } - - /// The attachment name for each key frame. May contain null values to clear the attachment. - public string[] AttachmentNames { get { return attachmentNames; } set { attachmentNames = value; } } - - /// Sets the time in seconds and the attachment name for the specified key frame. - public void SetFrame (int frameIndex, float time, String attachmentName) { - frames[frameIndex] = time; - attachmentNames[frameIndex] = attachmentName; - } - - public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - string attachmentName; - Slot slot = skeleton.slots.Items[slotIndex]; - if (direction == MixDirection.Out && blend == MixBlend.Setup) { - attachmentName = slot.data.attachmentName; - slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); - return; - } - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - if (blend == MixBlend.Setup || blend == MixBlend.First) { - attachmentName = slot.data.attachmentName; - slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); - } - return; - } - - int frameIndex; - if (time >= frames[frames.Length - 1]) // Time is after last frame. - frameIndex = frames.Length - 1; - else - frameIndex = Animation.BinarySearch(frames, time) - 1; - - attachmentName = attachmentNames[frameIndex]; - slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); - } - } - - /// Changes a slot's to deform a . - public class DeformTimeline : CurveTimeline, ISlotTimeline { - internal int slotIndex; - internal VertexAttachment attachment; - internal float[] frames; // time, ... - internal float[][] frameVertices; - - public DeformTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount]; - frameVertices = new float[frameCount][]; - } - - override public int PropertyId { - get { return ((int)TimelineType.Deform << 27) + attachment.id + slotIndex; } - } - - /// The index of the slot in that will be changed. - public int SlotIndex { - set { - if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0."); - this.slotIndex = value; - } - get { - return slotIndex; - } - } - /// The attachment that will be deformed. - public VertexAttachment Attachment { get { return attachment; } set { attachment = value; } } - - /// The time in seconds for each key frame. - public float[] Frames { get { return frames; } set { frames = value; } } - - /// The vertices for each key frame. - public float[][] Vertices { get { return frameVertices; } set { frameVertices = value; } } - - - /// Sets the time in seconds and the vertices for the specified key frame. - /// Vertex positions for an unweighted VertexAttachment, or deform offsets if it has weights. - public void SetFrame (int frameIndex, float time, float[] vertices) { - frames[frameIndex] = time; - frameVertices[frameIndex] = vertices; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Slot slot = skeleton.slots.Items[slotIndex]; - VertexAttachment vertexAttachment = slot.attachment as VertexAttachment; - if (vertexAttachment == null || !vertexAttachment.ApplyDeform(attachment)) return; - - var verticesArray = slot.attachmentVertices; - if (verticesArray.Count == 0) blend = MixBlend.Setup; - - float[][] frameVertices = this.frameVertices; - int vertexCount = frameVertices[0].Length; - float[] frames = this.frames; - float[] vertices; - - if (time < frames[0]) { // Time is before first frame. - - switch (blend) { - case MixBlend.Setup: - verticesArray.Clear(); - return; - case MixBlend.Replace: - if (alpha == 1) { - verticesArray.Clear(); - return; - } - - // verticesArray.SetSize(vertexCount) // Ensure size and preemptively set count. - if (verticesArray.Capacity < vertexCount) verticesArray.Capacity = vertexCount; - verticesArray.Count = vertexCount; - vertices = verticesArray.Items; - - if (vertexAttachment.bones == null) { - // Unweighted vertex positions. - float[] setupVertices = vertexAttachment.vertices; - for (int i = 0; i < vertexCount; i++) - vertices[i] += (setupVertices[i] - vertices[i]) * alpha; - } else { - // Weighted deform offsets. - alpha = 1 - alpha; - for (int i = 0; i < vertexCount; i++) - vertices[i] *= alpha; - } - return; - default: - return; - } - - } - - // verticesArray.SetSize(vertexCount) // Ensure size and preemptively set count. - if (verticesArray.Capacity < vertexCount) verticesArray.Capacity = vertexCount; - verticesArray.Count = vertexCount; - vertices = verticesArray.Items; - - if (time >= frames[frames.Length - 1]) { // Time is after last frame. - - float[] lastVertices = frameVertices[frames.Length - 1]; - if (alpha == 1) { - if (blend == MixBlend.Add) { - if (vertexAttachment.bones == null) { - // Unweighted vertex positions, no alpha. - float[] setupVertices = vertexAttachment.vertices; - for (int i = 0; i < vertexCount; i++) - vertices[i] += lastVertices[i] - setupVertices[i]; - } else { - // Weighted deform offsets, no alpha. - for (int i = 0; i < vertexCount; i++) - vertices[i] += lastVertices[i]; - } - } else { - // Vertex positions or deform offsets, no alpha. - Array.Copy(lastVertices, 0, vertices, 0, vertexCount); - } - } else { - switch (blend) { - case MixBlend.Setup: { - if (vertexAttachment.bones == null) { - // Unweighted vertex positions, with alpha. - float[] setupVertices = vertexAttachment.vertices; - for (int i = 0; i < vertexCount; i++) { - float setup = setupVertices[i]; - vertices[i] = setup + (lastVertices[i] - setup) * alpha; - } - } else { - // Weighted deform offsets, with alpha. - for (int i = 0; i < vertexCount; i++) - vertices[i] = lastVertices[i] * alpha; - } - break; - } - case MixBlend.First: - case MixBlend.Replace: - // Vertex positions or deform offsets, with alpha. - for (int i = 0; i < vertexCount; i++) - vertices[i] += (lastVertices[i] - vertices[i]) * alpha; - break; - case MixBlend.Add: - if (vertexAttachment.bones == null) { - // Unweighted vertex positions, no alpha. - float[] setupVertices = vertexAttachment.vertices; - for (int i = 0; i < vertexCount; i++) - vertices[i] += (lastVertices[i] - setupVertices[i]) * alpha; - } else { - // Weighted deform offsets, alpha. - for (int i = 0; i < vertexCount; i++) - vertices[i] += lastVertices[i] * alpha; - } - break; - } - } - return; - } - - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time); - float[] prevVertices = frameVertices[frame - 1]; - float[] nextVertices = frameVertices[frame]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame - 1, 1 - (time - frameTime) / (frames[frame - 1] - frameTime)); - - if (alpha == 1) { - if (blend == MixBlend.Add) { - if (vertexAttachment.bones == null) { - // Unweighted vertex positions, no alpha. - float[] setupVertices = vertexAttachment.vertices; - for (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - vertices[i] += prev + (nextVertices[i] - prev) * percent - setupVertices[i]; - } - } else { - // Weighted deform offsets, no alpha. - for (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - vertices[i] += prev + (nextVertices[i] - prev) * percent; - } - } - } else { - // Vertex positions or deform offsets, no alpha. - for (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - vertices[i] = prev + (nextVertices[i] - prev) * percent; - } - } - } else { - switch (blend) { - case MixBlend.Setup: { - if (vertexAttachment.bones == null) { - // Unweighted vertex positions, with alpha. - float[] setupVertices = vertexAttachment.vertices; - for (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i], setup = setupVertices[i]; - vertices[i] = setup + (prev + (nextVertices[i] - prev) * percent - setup) * alpha; - } - } else { - // Weighted deform offsets, with alpha. - for (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - vertices[i] = (prev + (nextVertices[i] - prev) * percent) * alpha; - } - } - break; - } - case MixBlend.First: - case MixBlend.Replace: { - // Vertex positions or deform offsets, with alpha. - for (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - vertices[i] += (prev + (nextVertices[i] - prev) * percent - vertices[i]) * alpha; - } - break; - } - case MixBlend.Add: { - if (vertexAttachment.bones == null) { - // Unweighted vertex positions, with alpha. - float[] setupVertices = vertexAttachment.vertices; - for (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - vertices[i] += (prev + (nextVertices[i] - prev) * percent - setupVertices[i]) * alpha; - } - } else { - // Weighted deform offsets, with alpha. - for (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - vertices[i] += (prev + (nextVertices[i] - prev) * percent) * alpha; - } - } - break; - } - } - } - } - } - - /// Fires an when specific animation times are reached. - public class EventTimeline : Timeline { - internal float[] frames; // time, ... - private Event[] events; - - public EventTimeline (int frameCount) { - frames = new float[frameCount]; - events = new Event[frameCount]; - } - - public int PropertyId { - get { return ((int)TimelineType.Event << 24); } - } - - /// The number of key frames for this timeline. - public int FrameCount { get { return frames.Length; } } - - /// The time in seconds for each key frame. - public float[] Frames { get { return frames; } set { frames = value; } } - - /// The event for each key frame. - public Event[] Events { get { return events; } set { events = value; } } - - /// Sets the time in seconds and the event for the specified key frame. - public void SetFrame (int frameIndex, Event e) { - frames[frameIndex] = e.Time; - events[frameIndex] = e; - } - - /// Fires events for frames > lastTime and <= time. - public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - if (firedEvents == null) return; - float[] frames = this.frames; - int frameCount = frames.Length; - - if (lastTime > time) { // Fire events after last time for looped animations. - Apply(skeleton, lastTime, int.MaxValue, firedEvents, alpha, blend, direction); - lastTime = -1f; - } else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame. - return; - if (time < frames[0]) return; // Time is before first frame. - - int frame; - if (lastTime < frames[0]) - frame = 0; - else { - frame = Animation.BinarySearch(frames, lastTime); - float frameTime = frames[frame]; - while (frame > 0) { // Fire multiple events with the same frame. - if (frames[frame - 1] != frameTime) break; - frame--; - } - } - for (; frame < frameCount && time >= frames[frame]; frame++) - firedEvents.Add(events[frame]); - } - } - - /// Changes a skeleton's . - public class DrawOrderTimeline : Timeline { - internal float[] frames; // time, ... - private int[][] drawOrders; - - public DrawOrderTimeline (int frameCount) { - frames = new float[frameCount]; - drawOrders = new int[frameCount][]; - } - - public int PropertyId { - get { return ((int)TimelineType.DrawOrder << 24); } - } - - /// The number of key frames for this timeline. - public int FrameCount { get { return frames.Length; } } - - /// The time in seconds for each key frame. - public float[] Frames { get { return frames; } set { frames = value; } } // time, ... - - /// The draw order for each key frame. - /// . - public int[][] DrawOrders { get { return drawOrders; } set { drawOrders = value; } } - - /// Sets the time in seconds and the draw order for the specified key frame. - /// For each slot in the index of the new draw order. May be null to use setup pose - /// draw order.. - public void SetFrame (int frameIndex, float time, int[] drawOrder) { - frames[frameIndex] = time; - drawOrders[frameIndex] = drawOrder; - } - - public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - ExposedList drawOrder = skeleton.drawOrder; - ExposedList slots = skeleton.slots; - if (direction == MixDirection.Out && blend == MixBlend.Setup) { - Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count); - return; - } - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - if (blend == MixBlend.Setup || blend == MixBlend.First) Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count); - return; - } - - int frame; - if (time >= frames[frames.Length - 1]) // Time is after last frame. - frame = frames.Length - 1; - else - frame = Animation.BinarySearch(frames, time) - 1; - - int[] drawOrderToSetupIndex = drawOrders[frame]; - if (drawOrderToSetupIndex == null) { - Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count); - } else { - var drawOrderItems = drawOrder.Items; - var slotsItems = slots.Items; - for (int i = 0, n = drawOrderToSetupIndex.Length; i < n; i++) - drawOrderItems[i] = slotsItems[drawOrderToSetupIndex[i]]; - } - } - } - - /// Changes an IK constraint's , , - /// , and . - public class IkConstraintTimeline : CurveTimeline { - public const int ENTRIES = 5; - private const int PREV_TIME = -5, PREV_MIX = -4, PREV_BEND_DIRECTION = -3, PREV_COMPRESS = -2, PREV_STRETCH = -1; - private const int MIX = 1, BEND_DIRECTION = 2, COMPRESS = 3, STRETCH = 4; - - internal int ikConstraintIndex; - internal float[] frames; // time, mix, bendDirection, compress, stretch, ... - - public IkConstraintTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - override public int PropertyId { - get { return ((int)TimelineType.IkConstraint << 24) + ikConstraintIndex; } - } - - /// The index of the IK constraint slot in that will be changed. - public int IkConstraintIndex { - set { - if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0."); - this.ikConstraintIndex = value; - } - get { - return ikConstraintIndex; - } - } - - /// The time in seconds, mix, bend direction, compress, and stretch for each key frame. - public float[] Frames { get { return frames; } set { frames = value; } } - - /// Sets the time in seconds, mix, bend direction, compress, and stretch for the specified key frame. - public void SetFrame (int frameIndex, float time, float mix, int bendDirection, bool compress, bool stretch) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + MIX] = mix; - frames[frameIndex + BEND_DIRECTION] = bendDirection; - frames[frameIndex + COMPRESS] = compress ? 1 : 0; - frames[frameIndex + STRETCH] = stretch ? 1 : 0; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - IkConstraint constraint = skeleton.ikConstraints.Items[ikConstraintIndex]; - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case MixBlend.Setup: - constraint.mix = constraint.data.mix; - 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.bendDirection = constraint.data.bendDirection; - constraint.compress = constraint.data.compress; - constraint.stretch = constraint.data.stretch; - return; - } - return; - } - - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - if (blend == MixBlend.Setup) { - constraint.mix = constraint.data.mix + (frames[frames.Length + PREV_MIX] - constraint.data.mix) * alpha; - if (direction == MixDirection.Out) { - constraint.bendDirection = constraint.data.bendDirection; - constraint.compress = constraint.data.compress; - constraint.stretch = constraint.data.stretch; - } else { - constraint.bendDirection = (int)frames[frames.Length + PREV_BEND_DIRECTION]; - constraint.compress = frames[frames.Length + PREV_COMPRESS] != 0; - constraint.stretch = frames[frames.Length + PREV_STRETCH] != 0; - } - } else { - constraint.mix += (frames[frames.Length + PREV_MIX] - constraint.mix) * alpha; - if (direction == MixDirection.In) { - constraint.bendDirection = (int)frames[frames.Length + PREV_BEND_DIRECTION]; - constraint.compress = frames[frames.Length + PREV_COMPRESS] != 0; - constraint.stretch = frames[frames.Length + PREV_STRETCH] != 0; - } - } - return; - } - - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - float mix = frames[frame + PREV_MIX]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - if (blend == MixBlend.Setup) { - constraint.mix = constraint.data.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.data.mix) * alpha; - if (direction == MixDirection.Out) { - constraint.bendDirection = constraint.data.bendDirection; - constraint.compress = constraint.data.compress; - constraint.stretch = constraint.data.stretch; - } else { - constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION]; - constraint.compress = frames[frame + PREV_COMPRESS] != 0; - constraint.stretch = frames[frame + PREV_STRETCH] != 0; - } - } else { - constraint.mix += (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha; - if (direction == MixDirection.In) { - constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION]; - constraint.compress = frames[frame + PREV_COMPRESS] != 0; - constraint.stretch = frames[frame + PREV_STRETCH] != 0; - } - } - } - } - - /// Changes a transform constraint's mixes. - public class TransformConstraintTimeline : CurveTimeline { - public const int ENTRIES = 5; - private const int PREV_TIME = -5, PREV_ROTATE = -4, PREV_TRANSLATE = -3, PREV_SCALE = -2, PREV_SHEAR = -1; - private const int ROTATE = 1, TRANSLATE = 2, SCALE = 3, SHEAR = 4; - - internal int transformConstraintIndex; - internal float[] frames; // time, rotate mix, translate mix, scale mix, shear mix, ... - - public TransformConstraintTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - override public int PropertyId { - get { return ((int)TimelineType.TransformConstraint << 24) + transformConstraintIndex; } - } - - /// The index of the transform constraint slot in that will be changed. - public int TransformConstraintIndex { - set { - if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0."); - this.transformConstraintIndex = value; - } - get { - return transformConstraintIndex; - } - } - - /// The time in seconds, rotate mix, translate mix, scale mix, and shear mix for each key frame. - public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, scale mix, shear mix, ... - - /// The time in seconds, rotate mix, translate mix, scale mix, and shear mix for the specified key frame. - public void SetFrame (int frameIndex, float time, float rotateMix, float translateMix, float scaleMix, float shearMix) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + ROTATE] = rotateMix; - frames[frameIndex + TRANSLATE] = translateMix; - frames[frameIndex + SCALE] = scaleMix; - frames[frameIndex + SHEAR] = shearMix; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - TransformConstraint constraint = skeleton.transformConstraints.Items[transformConstraintIndex]; - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - TransformConstraintData data = constraint.data; - switch (blend) { - case MixBlend.Setup: - constraint.rotateMix = data.rotateMix; - constraint.translateMix = data.translateMix; - constraint.scaleMix = data.scaleMix; - constraint.shearMix = data.shearMix; - return; - case MixBlend.First: - constraint.rotateMix += (data.rotateMix - constraint.rotateMix) * alpha; - constraint.translateMix += (data.translateMix - constraint.translateMix) * alpha; - constraint.scaleMix += (data.scaleMix - constraint.scaleMix) * alpha; - constraint.shearMix += (data.shearMix - constraint.shearMix) * alpha; - return; - } - return; - } - - float rotate, translate, scale, shear; - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - int i = frames.Length; - rotate = frames[i + PREV_ROTATE]; - translate = frames[i + PREV_TRANSLATE]; - scale = frames[i + PREV_SCALE]; - shear = frames[i + PREV_SHEAR]; - } else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - rotate = frames[frame + PREV_ROTATE]; - translate = frames[frame + PREV_TRANSLATE]; - scale = frames[frame + PREV_SCALE]; - shear = frames[frame + PREV_SHEAR]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - rotate += (frames[frame + ROTATE] - rotate) * percent; - translate += (frames[frame + TRANSLATE] - translate) * percent; - scale += (frames[frame + SCALE] - scale) * percent; - shear += (frames[frame + SHEAR] - shear) * percent; - } - if (blend == MixBlend.Setup) { - TransformConstraintData data = constraint.data; - constraint.rotateMix = data.rotateMix + (rotate - data.rotateMix) * alpha; - constraint.translateMix = data.translateMix + (translate - data.translateMix) * alpha; - constraint.scaleMix = data.scaleMix + (scale - data.scaleMix) * alpha; - constraint.shearMix = data.shearMix + (shear - data.shearMix) * alpha; - } else { - constraint.rotateMix += (rotate - constraint.rotateMix) * alpha; - constraint.translateMix += (translate - constraint.translateMix) * alpha; - constraint.scaleMix += (scale - constraint.scaleMix) * alpha; - constraint.shearMix += (shear - constraint.shearMix) * alpha; - } - } - } - - /// Changes a path constraint's . - public class PathConstraintPositionTimeline : CurveTimeline { - public const int ENTRIES = 2; - protected const int PREV_TIME = -2, PREV_VALUE = -1; - protected const int VALUE = 1; - - internal int pathConstraintIndex; - internal float[] frames; // time, position, ... - - public PathConstraintPositionTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - override public int PropertyId { - get { return ((int)TimelineType.PathConstraintPosition << 24) + pathConstraintIndex; } - } - - /// The index of the path constraint slot in that will be changed. - public int PathConstraintIndex { - set { - if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0."); - this.pathConstraintIndex = value; - } - get { - return pathConstraintIndex; - } - } - - /// The time in seconds and path constraint position for each key frame. - public float[] Frames { get { return frames; } set { frames = value; } } // time, position, ... - - /// Sets the time in seconds and path constraint position for the specified key frame. - public void SetFrame (int frameIndex, float time, float position) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + VALUE] = position; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case MixBlend.Setup: - constraint.position = constraint.data.position; - return; - case MixBlend.First: - constraint.position += (constraint.data.position - constraint.position) * alpha; - return; - } - return; - } - - float position; - if (time >= frames[frames.Length - ENTRIES]) // Time is after last frame. - position = frames[frames.Length + PREV_VALUE]; - else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - position = frames[frame + PREV_VALUE]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - position += (frames[frame + VALUE] - position) * percent; - } - if (blend == MixBlend.Setup) - constraint.position = constraint.data.position + (position - constraint.data.position) * alpha; - else - constraint.position += (position - constraint.position) * alpha; - } - } - - /// Changes a path constraint's . - public class PathConstraintSpacingTimeline : PathConstraintPositionTimeline { - public PathConstraintSpacingTimeline (int frameCount) - : base(frameCount) { - } - - override public int PropertyId { - get { return ((int)TimelineType.PathConstraintSpacing << 24) + pathConstraintIndex; } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, MixBlend blend, - MixDirection direction) { - PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case MixBlend.Setup: - constraint.spacing = constraint.data.spacing; - return; - case MixBlend.First: - constraint.spacing += (constraint.data.spacing - constraint.spacing) * alpha; - return; - } - return; - } - - float spacing; - if (time >= frames[frames.Length - ENTRIES]) // Time is after last frame. - spacing = frames[frames.Length + PREV_VALUE]; - else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - spacing = frames[frame + PREV_VALUE]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - spacing += (frames[frame + VALUE] - spacing) * percent; - } - - if (blend == MixBlend.Setup) - constraint.spacing = constraint.data.spacing + (spacing - constraint.data.spacing) * alpha; - else - constraint.spacing += (spacing - constraint.spacing) * alpha; - } - } - - /// Changes a path constraint's mixes. - public class PathConstraintMixTimeline : CurveTimeline { - public const int ENTRIES = 3; - private const int PREV_TIME = -3, PREV_ROTATE = -2, PREV_TRANSLATE = -1; - private const int ROTATE = 1, TRANSLATE = 2; - - internal int pathConstraintIndex; - internal float[] frames; // time, rotate mix, translate mix, ... - - public PathConstraintMixTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - override public int PropertyId { - get { return ((int)TimelineType.PathConstraintMix << 24) + pathConstraintIndex; } - } - - /// The index of the path constraint slot in that will be changed. - public int PathConstraintIndex { - set { - if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0."); - this.pathConstraintIndex = value; - } - get { - return pathConstraintIndex; - } - } - - /// The time in seconds, rotate mix, and translate mix for each key frame. - public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, ... - - /// The time in seconds, rotate mix, and translate mix for the specified key frame. - public void SetFrame (int frameIndex, float time, float rotateMix, float translateMix) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + ROTATE] = rotateMix; - frames[frameIndex + TRANSLATE] = translateMix; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case MixBlend.Setup: - constraint.rotateMix = constraint.data.rotateMix; - constraint.translateMix = constraint.data.translateMix; - return; - case MixBlend.First: - constraint.rotateMix += (constraint.data.rotateMix - constraint.rotateMix) * alpha; - constraint.translateMix += (constraint.data.translateMix - constraint.translateMix) * alpha; - return; - } - return; - } - - float rotate, translate; - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - rotate = frames[frames.Length + PREV_ROTATE]; - translate = frames[frames.Length + PREV_TRANSLATE]; - } else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - rotate = frames[frame + PREV_ROTATE]; - translate = frames[frame + PREV_TRANSLATE]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - rotate += (frames[frame + ROTATE] - rotate) * percent; - translate += (frames[frame + TRANSLATE] - translate) * percent; - } - - if (blend == MixBlend.Setup) { - constraint.rotateMix = constraint.data.rotateMix + (rotate - constraint.data.rotateMix) * alpha; - constraint.translateMix = constraint.data.translateMix + (translate - constraint.data.translateMix) * alpha; - } else { - constraint.rotateMix += (rotate - constraint.rotateMix) * alpha; - constraint.translateMix += (translate - constraint.translateMix) * alpha; - } - } - } + +namespace Spine3_7_94 +{ + + /// + /// A simple container for a list of timelines and a name. + public class Animation + { + internal String name; + internal ExposedList timelines; + internal float duration; + + public Animation(string name, ExposedList timelines, float duration) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + if (timelines == null) throw new ArgumentNullException("timelines", "timelines cannot be null."); + this.name = name; + this.timelines = timelines; + this.duration = duration; + } + + public ExposedList Timelines { get { return timelines; } set { timelines = value; } } + + /// The duration of the animation in seconds, which is the highest time of all keys in the timeline. + public float Duration { get { return duration; } set { duration = value; } } + + /// The animation's name, which is unique within the skeleton. + public string Name { get { return name; } } + + /// Applies all the animation's timelines to the specified skeleton. + /// + public void Apply(Skeleton skeleton, float lastTime, float time, bool loop, ExposedList events, float alpha, MixBlend blend, + MixDirection direction) + { + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + + if (loop && duration != 0) + { + time %= duration; + if (lastTime > 0) lastTime %= duration; + } + + ExposedList timelines = this.timelines; + for (int i = 0, n = timelines.Count; i < n; i++) + timelines.Items[i].Apply(skeleton, lastTime, time, events, alpha, blend, direction); + } + + override public string ToString() + { + return name; + } + + /// After the first and before the last entry. + /// Index of first value greater than the target. + internal static int BinarySearch(float[] values, float target, int step) + { + int low = 0; + int high = values.Length / step - 2; + if (high == 0) return step; + int current = (int)((uint)high >> 1); + while (true) + { + if (values[(current + 1) * step] <= target) + low = current + 1; + else + high = current; + if (low == high) return (low + 1) * step; + current = (int)((uint)(low + high) >> 1); + } + } + + /// After the first and before the last entry. + internal static int BinarySearch(float[] values, float target) + { + int low = 0; + int high = values.Length - 2; + if (high == 0) return 1; + int current = (int)((uint)high >> 1); + while (true) + { + if (values[current + 1] <= target) + low = current + 1; + else + high = current; + if (low == high) return (low + 1); + current = (int)((uint)(low + high) >> 1); + } + } + + internal static int LinearSearch(float[] values, float target, int step) + { + for (int i = 0, last = values.Length - step; i <= last; i += step) + if (values[i] > target) return i; + return -1; + } + } + + /// + /// The interface for all timelines. + public interface Timeline + { + /// Applies this timeline to the skeleton. + /// The skeleton the timeline is being applied to. This provides access to the bones, slots, and other + /// skeleton components the timeline may change. + /// The time this timeline was last applied. Timelines such as trigger only at specific + /// times rather than every frame. In that case, the timeline triggers everything between lastTime + /// (exclusive) and time (inclusive). + /// The time within the animation. Most timelines find the key before and the key after this time so they can + /// interpolate between the keys. + /// If any events are fired, they are added to this list. Can be null to ignore firing events or if the + /// timeline does not fire events. + /// 0 applies the current or setup value (depending on blend). 1 applies the timeline value. + /// Between 0 and 1 applies a value between the current or setup value 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). + /// Controls how mixing is applied when alpha < 1. + /// Indicates whether the timeline is mixing in or out. Used by timelines which perform instant transitions, + /// such as or . + void Apply(Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, MixBlend blend, MixDirection direction); + /// Uniquely encodes both the type of this timeline and the skeleton property that it affects. + int PropertyId { get; } + } + + /// + /// Controls how a timeline is mixed with the setup or current pose. + /// + public enum MixBlend + { + + /// Transitions from the setup value to the timeline value (the current value is not used). Before the first key, the setup + /// value is set. + Setup, + + /// + /// + /// Transitions from the current value to the timeline value. Before the first key, transitions from the current value to + /// the setup value. Timelines which perform instant transitions, such as or + /// , use the setup value before the first key. + /// + /// First is intended for the first animations applied, not for animations layered on top of those. + /// + First, + + /// + /// + /// Transitions from the current value to the timeline value. No change is made before the first key (the current value is + /// kept until the first key). + /// + /// Replace is intended for animations layered on top of others, not for the first animations applied. + /// + Replace, + + /// + /// + /// Transitions from the current value to the current value plus the timeline value. No change is made before the first key + /// (the current value is kept until the first key). + /// + /// Add is intended for animations layered on top of others, not for the first animations applied. + /// + Add + } + + /// + /// Indicates whether a timeline's alpha is mixing out over time toward 0 (the setup or current pose value) or + /// mixing in toward 1 (the timeline's value). + /// + public enum MixDirection + { + In, + Out + } + + internal enum TimelineType + { + Rotate = 0, Translate, Scale, Shear, // + Attachment, Color, Deform, // + Event, DrawOrder, // + IkConstraint, TransformConstraint, // + PathConstraintPosition, PathConstraintSpacing, PathConstraintMix, // + TwoColor + } + + /// An interface for timelines which change the property of a bone. + public interface IBoneTimeline + { + /// The index of the bone in that will be changed. + int BoneIndex { get; } + } + + /// An interface for timelines which change the property of a slot. + public interface ISlotTimeline + { + /// The index of the slot in that will be changed. + int SlotIndex { get; } + } + + /// The base class for timelines that use interpolation between key frame values. + abstract public class CurveTimeline : Timeline + { + protected const float LINEAR = 0, STEPPED = 1, BEZIER = 2; + protected const int BEZIER_SIZE = 10 * 2 - 1; + + internal float[] curves; // type, x, y, ... + /// The number of key frames for this timeline. + public int FrameCount { get { return curves.Length / BEZIER_SIZE + 1; } } + + public CurveTimeline(int frameCount) + { + if (frameCount <= 0) throw new ArgumentOutOfRangeException("frameCount must be > 0: "); + curves = new float[(frameCount - 1) * BEZIER_SIZE]; + } + + abstract public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, MixDirection direction); + + abstract public int PropertyId { get; } + + /// Sets the specified key frame to linear interpolation. + public void SetLinear(int frameIndex) + { + curves[frameIndex * BEZIER_SIZE] = LINEAR; + } + + /// Sets the specified key frame to stepped interpolation. + public void SetStepped(int frameIndex) + { + curves[frameIndex * BEZIER_SIZE] = STEPPED; + } + + /// Returns the interpolation type for the specified key frame. + /// Linear is 0, stepped is 1, Bezier is 2. + public float GetCurveType(int frameIndex) + { + int index = frameIndex * BEZIER_SIZE; + if (index == curves.Length) return LINEAR; + float type = curves[index]; + if (type == LINEAR) return LINEAR; + if (type == STEPPED) return STEPPED; + return BEZIER; + } + + /// Sets the specified key frame to Bezier interpolation. cx1 and cx2 are from 0 to 1, + /// representing the percent of time between the two key frames. cy1 and cy2 are the percent of the + /// difference between the key frame's values. + public void SetCurve(int frameIndex, float cx1, float cy1, float cx2, float cy2) + { + float tmpx = (-cx1 * 2 + cx2) * 0.03f, tmpy = (-cy1 * 2 + cy2) * 0.03f; + float dddfx = ((cx1 - cx2) * 3 + 1) * 0.006f, dddfy = ((cy1 - cy2) * 3 + 1) * 0.006f; + float ddfx = tmpx * 2 + dddfx, ddfy = tmpy * 2 + dddfy; + float dfx = cx1 * 0.3f + tmpx + dddfx * 0.16666667f, dfy = cy1 * 0.3f + tmpy + dddfy * 0.16666667f; + + int i = frameIndex * BEZIER_SIZE; + float[] curves = this.curves; + curves[i++] = BEZIER; + + float x = dfx, y = dfy; + for (int n = i + BEZIER_SIZE - 1; i < n; i += 2) + { + curves[i] = x; + curves[i + 1] = y; + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + x += dfx; + y += dfy; + } + } + + /// Returns the interpolated percentage for the specified key frame and linear percentage. + public float GetCurvePercent(int frameIndex, float percent) + { + percent = MathUtils.Clamp(percent, 0, 1); + float[] curves = this.curves; + int i = frameIndex * BEZIER_SIZE; + float type = curves[i]; + if (type == LINEAR) return percent; + if (type == STEPPED) return 0; + i++; + float x = 0; + for (int start = i, n = i + BEZIER_SIZE - 1; i < n; i += 2) + { + x = curves[i]; + if (x >= percent) + { + if (i == start) return curves[i + 1] * percent / x; // First point is 0,0. + float prevX = curves[i - 2], prevY = curves[i - 1]; + return prevY + (curves[i + 1] - prevY) * (percent - prevX) / (x - prevX); + } + } + float y = curves[i - 1]; + return y + (1 - y) * (percent - x) / (1 - x); // Last point is 1,1. + } + } + + /// Changes a bone's local . + public class RotateTimeline : CurveTimeline, IBoneTimeline + { + public const int ENTRIES = 2; + internal const int PREV_TIME = -2, PREV_ROTATION = -1; + internal const int ROTATION = 1; + + internal int boneIndex; + internal float[] frames; // time, degrees, ... + + public RotateTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount << 1]; + } + + override public int PropertyId + { + get { return ((int)TimelineType.Rotate << 24) + boneIndex; } + } + /// The index of the bone in that will be changed. + public int BoneIndex + { + set + { + if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0."); + this.boneIndex = value; + } + get + { + return boneIndex; + } + } + /// The time in seconds and rotation in degrees for each key frame. + public float[] Frames { get { return frames; } set { frames = value; } } + + /// Sets the time in seconds and the rotation in degrees for the specified key frame. + public void SetFrame(int frameIndex, float time, float degrees) + { + frameIndex <<= 1; + frames[frameIndex] = time; + frames[frameIndex + ROTATION] = degrees; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + switch (blend) + { + case MixBlend.Setup: + bone.rotation = bone.data.rotation; + return; + case MixBlend.First: + float r = bone.data.rotation - bone.rotation; + bone.rotation += (r - (16384 - (int)(16384.499999999996 - r / 360)) * 360) * alpha; + return; + } + return; + } + + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + float r = frames[frames.Length + PREV_ROTATION]; + switch (blend) + { + case MixBlend.Setup: + bone.rotation = bone.data.rotation + r * alpha; + break; + case MixBlend.First: + case MixBlend.Replace: + r += bone.data.rotation - bone.rotation; + r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; + goto case MixBlend.Add; // Fall through. + + case MixBlend.Add: + bone.rotation += r * alpha; + break; + } + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + float prevRotation = frames[frame + PREV_ROTATION]; + float frameTime = frames[frame]; + float percent = GetCurvePercent((frame >> 1) - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + // scope for 'r' to prevent compile error. + { + float r = frames[frame + ROTATION] - prevRotation; + r = prevRotation + (r - (16384 - (int)(16384.499999999996 - r / 360)) * 360) * percent; + switch (blend) + { + case MixBlend.Setup: + bone.rotation = bone.data.rotation + (r - (16384 - (int)(16384.499999999996 - r / 360)) * 360) * alpha; + break; + case MixBlend.First: + case MixBlend.Replace: + r += bone.data.rotation - bone.rotation; + goto case MixBlend.Add; // Fall through. + case MixBlend.Add: + bone.rotation += (r - (16384 - (int)(16384.499999999996 - r / 360)) * 360) * alpha; + break; + } + } + } + } + + /// Changes a bone's local and . + public class TranslateTimeline : CurveTimeline, IBoneTimeline + { + public const int ENTRIES = 3; + protected const int PREV_TIME = -3, PREV_X = -2, PREV_Y = -1; + protected const int X = 1, Y = 2; + + internal int boneIndex; + internal float[] frames; // time, x, y, ... + + public TranslateTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + override public int PropertyId + { + get { return ((int)TimelineType.Translate << 24) + boneIndex; } + } + + /// The index of the bone in that will be changed. + public int BoneIndex + { + set + { + if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0."); + this.boneIndex = value; + } + get + { + return boneIndex; + } + } + /// The time in seconds, x, and y values for each key frame. + public float[] Frames { get { return frames; } set { frames = value; } } + + + /// Sets the time in seconds, x, and y values for the specified key frame. + public void SetFrame(int frameIndex, float time, float x, float y) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + X] = x; + frames[frameIndex + Y] = y; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + 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; + return; + } + return; + } + + float x, y; + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + x = frames[frames.Length + PREV_X]; + y = frames[frames.Length + PREV_Y]; + } + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + x = frames[frame + PREV_X]; + y = frames[frame + PREV_Y]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + x += (frames[frame + X] - x) * percent; + y += (frames[frame + Y] - y) * percent; + } + 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; + break; + } + } + } + + /// Changes a bone's local and . + public class ScaleTimeline : TranslateTimeline, IBoneTimeline + { + public ScaleTimeline(int frameCount) + : base(frameCount) + { + } + + override public int PropertyId + { + get { return ((int)TimelineType.Scale << 24) + boneIndex; } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + 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; + return; + } + return; + } + + float x, y; + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + x = frames[frames.Length + PREV_X] * bone.data.scaleX; + y = frames[frames.Length + PREV_Y] * bone.data.scaleY; + } + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + x = frames[frame + PREV_X]; + y = frames[frame + PREV_Y]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + x = (x + (frames[frame + X] - x) * percent) * bone.data.scaleX; + y = (y + (frames[frame + Y] - y) * percent) * 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 + { + // Mixing out uses sign of setup or current pose, else use sign of key. + float bx, by; + if (direction == MixDirection.Out) + { + switch (blend) + { + case MixBlend.Setup: + bx = bone.data.scaleX; + by = bone.data.scaleY; + bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha; + bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha; + break; + case MixBlend.First: + case MixBlend.Replace: + bx = bone.scaleX; + by = bone.scaleY; + bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha; + bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha; + break; + case MixBlend.Add: + bx = bone.scaleX; + by = bone.scaleY; + bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bone.data.scaleX) * alpha; + bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - bone.data.scaleY) * alpha; + break; + } + } + else + { + switch (blend) + { + case MixBlend.Setup: + bx = Math.Abs(bone.data.scaleX) * Math.Sign(x); + by = Math.Abs(bone.data.scaleY) * Math.Sign(y); + bone.scaleX = bx + (x - bx) * alpha; + bone.scaleY = by + (y - by) * alpha; + break; + case MixBlend.First: + case MixBlend.Replace: + bx = Math.Abs(bone.scaleX) * Math.Sign(x); + by = Math.Abs(bone.scaleY) * Math.Sign(y); + bone.scaleX = bx + (x - bx) * alpha; + bone.scaleY = by + (y - by) * alpha; + break; + case MixBlend.Add: + bx = Math.Sign(x); + by = Math.Sign(y); + bone.scaleX = Math.Abs(bone.scaleX) * bx + (x - Math.Abs(bone.data.scaleX) * bx) * alpha; + bone.scaleY = Math.Abs(bone.scaleY) * by + (y - Math.Abs(bone.data.scaleY) * by) * alpha; + break; + } + } + } + } + } + + /// Changes a bone's local and . + public class ShearTimeline : TranslateTimeline, IBoneTimeline + { + public ShearTimeline(int frameCount) + : base(frameCount) + { + } + + override public int PropertyId + { + get { return ((int)TimelineType.Shear << 24) + boneIndex; } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + 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; + return; + } + return; + } + + float x, y; + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + x = frames[frames.Length + PREV_X]; + y = frames[frames.Length + PREV_Y]; + } + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + x = frames[frame + PREV_X]; + y = frames[frame + PREV_Y]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + x = x + (frames[frame + X] - x) * percent; + y = y + (frames[frame + Y] - y) * percent; + } + 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; + break; + } + } + } + + /// Changes a slot's . + public class ColorTimeline : CurveTimeline, ISlotTimeline + { + public const int ENTRIES = 5; + protected const int PREV_TIME = -5, PREV_R = -4, PREV_G = -3, PREV_B = -2, PREV_A = -1; + protected const int R = 1, G = 2, B = 3, A = 4; + + internal int slotIndex; + internal float[] frames; // time, r, g, b, a, ... + + public ColorTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + override public int PropertyId + { + get { return ((int)TimelineType.Color << 24) + slotIndex; } + } + + /// The index of the slot in that will be changed. + public int SlotIndex + { + set + { + if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0."); + this.slotIndex = value; + } + get + { + return slotIndex; + } + } + /// The time in seconds, red, green, blue, and alpha values for each key frame. + public float[] Frames { get { return frames; } set { frames = value; } } + + /// Sets the time in seconds, red, green, blue, and alpha for the specified key frame. + public void SetFrame(int frameIndex, float time, float r, float g, float b, float a) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + R] = r; + frames[frameIndex + G] = g; + frames[frameIndex + B] = b; + frames[frameIndex + A] = a; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Slot slot = skeleton.slots.Items[slotIndex]; + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + var slotData = slot.data; + switch (blend) + { + case MixBlend.Setup: + slot.r = slotData.r; + slot.g = slotData.g; + slot.b = slotData.b; + slot.a = slotData.a; + return; + case MixBlend.First: + slot.r += (slotData.r - slot.r) * alpha; + slot.g += (slotData.g - slot.g) * alpha; + slot.b += (slotData.b - slot.b) * alpha; + slot.a += (slotData.a - slot.a) * alpha; + return; + } + return; + } + + float r, g, b, a; + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + int i = frames.Length; + r = frames[i + PREV_R]; + g = frames[i + PREV_G]; + b = frames[i + PREV_B]; + a = frames[i + PREV_A]; + } + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + r = frames[frame + PREV_R]; + g = frames[frame + PREV_G]; + b = frames[frame + PREV_B]; + a = frames[frame + PREV_A]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + r += (frames[frame + R] - r) * percent; + g += (frames[frame + G] - g) * percent; + b += (frames[frame + B] - b) * percent; + a += (frames[frame + A] - a) * percent; + } + if (alpha == 1) + { + slot.r = r; + slot.g = g; + slot.b = b; + slot.a = a; + } + else + { + float br, bg, bb, ba; + if (blend == MixBlend.Setup) + { + br = slot.data.r; + bg = slot.data.g; + bb = slot.data.b; + ba = slot.data.a; + } + else + { + br = slot.r; + bg = slot.g; + bb = slot.b; + ba = slot.a; + } + slot.r = br + ((r - br) * alpha); + slot.g = bg + ((g - bg) * alpha); + slot.b = bb + ((b - bb) * alpha); + slot.a = ba + ((a - ba) * alpha); + } + } + } + + /// Changes a slot's and for two color tinting. + public class TwoColorTimeline : CurveTimeline, ISlotTimeline + { + public const int ENTRIES = 8; + protected const int PREV_TIME = -8, PREV_R = -7, PREV_G = -6, PREV_B = -5, PREV_A = -4; + protected const int PREV_R2 = -3, PREV_G2 = -2, PREV_B2 = -1; + protected const int R = 1, G = 2, B = 3, A = 4, R2 = 5, G2 = 6, B2 = 7; + + internal int slotIndex; + internal float[] frames; // time, r, g, b, a, r2, g2, b2, ... + + public TwoColorTimeline(int frameCount) : + base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + override public int PropertyId + { + get { return ((int)TimelineType.TwoColor << 24) + slotIndex; } + } + + /// The index of the slot in that will be changed. + public int SlotIndex + { + set + { + if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0."); + this.slotIndex = value; + } + get + { + return slotIndex; + } + } + /// The time in seconds, red, green, blue, and alpha values for each key frame. + public float[] Frames { get { return frames; } } + + /// Sets the time in seconds, light, and dark colors for the specified key frame.. + public void SetFrame(int frameIndex, float time, float r, float g, float b, float a, float r2, float g2, float b2) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + R] = r; + frames[frameIndex + G] = g; + frames[frameIndex + B] = b; + frames[frameIndex + A] = a; + frames[frameIndex + R2] = r2; + frames[frameIndex + G2] = g2; + frames[frameIndex + B2] = b2; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Slot slot = skeleton.slots.Items[slotIndex]; + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + var slotData = slot.data; + switch (blend) + { + case MixBlend.Setup: + // slot.color.set(slot.data.color); + // slot.darkColor.set(slot.data.darkColor); + slot.r = slotData.r; + slot.g = slotData.g; + slot.b = slotData.b; + slot.a = slotData.a; + slot.r2 = slotData.r2; + slot.g2 = slotData.g2; + slot.b2 = slotData.b2; + return; + case MixBlend.First: + slot.r += (slot.r - slotData.r) * alpha; + slot.g += (slot.g - slotData.g) * alpha; + slot.b += (slot.b - slotData.b) * alpha; + slot.a += (slot.a - slotData.a) * alpha; + slot.r2 += (slot.r2 - slotData.r2) * alpha; + slot.g2 += (slot.g2 - slotData.g2) * alpha; + slot.b2 += (slot.b2 - slotData.b2) * alpha; + return; + } + return; + } + + float r, g, b, a, r2, g2, b2; + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + int i = frames.Length; + r = frames[i + PREV_R]; + g = frames[i + PREV_G]; + b = frames[i + PREV_B]; + a = frames[i + PREV_A]; + r2 = frames[i + PREV_R2]; + g2 = frames[i + PREV_G2]; + b2 = frames[i + PREV_B2]; + } + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + r = frames[frame + PREV_R]; + g = frames[frame + PREV_G]; + b = frames[frame + PREV_B]; + a = frames[frame + PREV_A]; + r2 = frames[frame + PREV_R2]; + g2 = frames[frame + PREV_G2]; + b2 = frames[frame + PREV_B2]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + r += (frames[frame + R] - r) * percent; + g += (frames[frame + G] - g) * percent; + b += (frames[frame + B] - b) * percent; + a += (frames[frame + A] - a) * percent; + r2 += (frames[frame + R2] - r2) * percent; + g2 += (frames[frame + G2] - g2) * percent; + b2 += (frames[frame + B2] - b2) * percent; + } + if (alpha == 1) + { + slot.r = r; + slot.g = g; + slot.b = b; + slot.a = a; + slot.r2 = r2; + slot.g2 = g2; + slot.b2 = b2; + } + else + { + float br, bg, bb, ba, br2, bg2, bb2; + if (blend == MixBlend.Setup) + { + br = slot.data.r; + bg = slot.data.g; + bb = slot.data.b; + ba = slot.data.a; + br2 = slot.data.r2; + bg2 = slot.data.g2; + bb2 = slot.data.b2; + } + else + { + br = slot.r; + bg = slot.g; + bb = slot.b; + ba = slot.a; + br2 = slot.r2; + bg2 = slot.g2; + bb2 = slot.b2; + } + slot.r = br + ((r - br) * alpha); + slot.g = bg + ((g - bg) * alpha); + slot.b = bb + ((b - bb) * alpha); + slot.a = ba + ((a - ba) * alpha); + slot.r2 = br2 + ((r2 - br2) * alpha); + slot.g2 = bg2 + ((g2 - bg2) * alpha); + slot.b2 = bb2 + ((b2 - bb2) * alpha); + } + } + + } + + /// Changes a slot's . + public class AttachmentTimeline : Timeline, ISlotTimeline + { + internal int slotIndex; + internal float[] frames; // time, ... + internal string[] attachmentNames; + + public AttachmentTimeline(int frameCount) + { + frames = new float[frameCount]; + attachmentNames = new String[frameCount]; + } + + public int PropertyId + { + get { return ((int)TimelineType.Attachment << 24) + slotIndex; } + } + + /// The number of key frames for this timeline. + public int FrameCount { get { return frames.Length; } } + + /// The index of the slot in that will be changed. + public int SlotIndex + { + set + { + if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0."); + this.slotIndex = value; + } + get + { + return slotIndex; + } + } + + /// The time in seconds for each key frame. + public float[] Frames { get { return frames; } set { frames = value; } } + + /// The attachment name for each key frame. May contain null values to clear the attachment. + public string[] AttachmentNames { get { return attachmentNames; } set { attachmentNames = value; } } + + /// Sets the time in seconds and the attachment name for the specified key frame. + public void SetFrame(int frameIndex, float time, String attachmentName) + { + frames[frameIndex] = time; + attachmentNames[frameIndex] = attachmentName; + } + + public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + string attachmentName; + Slot slot = skeleton.slots.Items[slotIndex]; + if (direction == MixDirection.Out && blend == MixBlend.Setup) + { + attachmentName = slot.data.attachmentName; + slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); + return; + } + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + if (blend == MixBlend.Setup || blend == MixBlend.First) + { + attachmentName = slot.data.attachmentName; + slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); + } + return; + } + + int frameIndex; + if (time >= frames[frames.Length - 1]) // Time is after last frame. + frameIndex = frames.Length - 1; + else + frameIndex = Animation.BinarySearch(frames, time) - 1; + + attachmentName = attachmentNames[frameIndex]; + slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); + } + } + + /// Changes a slot's to deform a . + public class DeformTimeline : CurveTimeline, ISlotTimeline + { + internal int slotIndex; + internal VertexAttachment attachment; + internal float[] frames; // time, ... + internal float[][] frameVertices; + + public DeformTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount]; + frameVertices = new float[frameCount][]; + } + + override public int PropertyId + { + get { return ((int)TimelineType.Deform << 27) + attachment.id + slotIndex; } + } + + /// The index of the slot in that will be changed. + public int SlotIndex + { + set + { + if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0."); + this.slotIndex = value; + } + get + { + return slotIndex; + } + } + /// The attachment that will be deformed. + public VertexAttachment Attachment { get { return attachment; } set { attachment = value; } } + + /// The time in seconds for each key frame. + public float[] Frames { get { return frames; } set { frames = value; } } + + /// The vertices for each key frame. + public float[][] Vertices { get { return frameVertices; } set { frameVertices = value; } } + + + /// Sets the time in seconds and the vertices for the specified key frame. + /// Vertex positions for an unweighted VertexAttachment, or deform offsets if it has weights. + public void SetFrame(int frameIndex, float time, float[] vertices) + { + frames[frameIndex] = time; + frameVertices[frameIndex] = vertices; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Slot slot = skeleton.slots.Items[slotIndex]; + VertexAttachment vertexAttachment = slot.attachment as VertexAttachment; + if (vertexAttachment == null || !vertexAttachment.ApplyDeform(attachment)) return; + + var verticesArray = slot.attachmentVertices; + if (verticesArray.Count == 0) blend = MixBlend.Setup; + + float[][] frameVertices = this.frameVertices; + int vertexCount = frameVertices[0].Length; + float[] frames = this.frames; + float[] vertices; + + if (time < frames[0]) + { // Time is before first frame. + + switch (blend) + { + case MixBlend.Setup: + verticesArray.Clear(); + return; + case MixBlend.Replace: + if (alpha == 1) + { + verticesArray.Clear(); + return; + } + + // verticesArray.SetSize(vertexCount) // Ensure size and preemptively set count. + if (verticesArray.Capacity < vertexCount) verticesArray.Capacity = vertexCount; + verticesArray.Count = vertexCount; + vertices = verticesArray.Items; + + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions. + float[] setupVertices = vertexAttachment.vertices; + for (int i = 0; i < vertexCount; i++) + vertices[i] += (setupVertices[i] - vertices[i]) * alpha; + } + else + { + // Weighted deform offsets. + alpha = 1 - alpha; + for (int i = 0; i < vertexCount; i++) + vertices[i] *= alpha; + } + return; + default: + return; + } + + } + + // verticesArray.SetSize(vertexCount) // Ensure size and preemptively set count. + if (verticesArray.Capacity < vertexCount) verticesArray.Capacity = vertexCount; + verticesArray.Count = vertexCount; + vertices = verticesArray.Items; + + if (time >= frames[frames.Length - 1]) + { // Time is after last frame. + + float[] lastVertices = frameVertices[frames.Length - 1]; + if (alpha == 1) + { + if (blend == MixBlend.Add) + { + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions, no alpha. + float[] setupVertices = vertexAttachment.vertices; + for (int i = 0; i < vertexCount; i++) + vertices[i] += lastVertices[i] - setupVertices[i]; + } + else + { + // Weighted deform offsets, no alpha. + for (int i = 0; i < vertexCount; i++) + vertices[i] += lastVertices[i]; + } + } + else + { + // Vertex positions or deform offsets, no alpha. + Array.Copy(lastVertices, 0, vertices, 0, vertexCount); + } + } + else + { + switch (blend) + { + case MixBlend.Setup: + { + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions, with alpha. + float[] setupVertices = vertexAttachment.vertices; + for (int i = 0; i < vertexCount; i++) + { + float setup = setupVertices[i]; + vertices[i] = setup + (lastVertices[i] - setup) * alpha; + } + } + else + { + // Weighted deform offsets, with alpha. + for (int i = 0; i < vertexCount; i++) + vertices[i] = lastVertices[i] * alpha; + } + break; + } + case MixBlend.First: + case MixBlend.Replace: + // Vertex positions or deform offsets, with alpha. + for (int i = 0; i < vertexCount; i++) + vertices[i] += (lastVertices[i] - vertices[i]) * alpha; + break; + case MixBlend.Add: + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions, no alpha. + float[] setupVertices = vertexAttachment.vertices; + for (int i = 0; i < vertexCount; i++) + vertices[i] += (lastVertices[i] - setupVertices[i]) * alpha; + } + else + { + // Weighted deform offsets, alpha. + for (int i = 0; i < vertexCount; i++) + vertices[i] += lastVertices[i] * alpha; + } + break; + } + } + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time); + float[] prevVertices = frameVertices[frame - 1]; + float[] nextVertices = frameVertices[frame]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame - 1, 1 - (time - frameTime) / (frames[frame - 1] - frameTime)); + + if (alpha == 1) + { + if (blend == MixBlend.Add) + { + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions, no alpha. + float[] setupVertices = vertexAttachment.vertices; + for (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i]; + vertices[i] += prev + (nextVertices[i] - prev) * percent - setupVertices[i]; + } + } + else + { + // Weighted deform offsets, no alpha. + for (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i]; + vertices[i] += prev + (nextVertices[i] - prev) * percent; + } + } + } + else + { + // Vertex positions or deform offsets, no alpha. + for (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i]; + vertices[i] = prev + (nextVertices[i] - prev) * percent; + } + } + } + else + { + switch (blend) + { + case MixBlend.Setup: + { + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions, with alpha. + float[] setupVertices = vertexAttachment.vertices; + for (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i], setup = setupVertices[i]; + vertices[i] = setup + (prev + (nextVertices[i] - prev) * percent - setup) * alpha; + } + } + else + { + // Weighted deform offsets, with alpha. + for (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i]; + vertices[i] = (prev + (nextVertices[i] - prev) * percent) * alpha; + } + } + break; + } + case MixBlend.First: + case MixBlend.Replace: + { + // Vertex positions or deform offsets, with alpha. + for (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i]; + vertices[i] += (prev + (nextVertices[i] - prev) * percent - vertices[i]) * alpha; + } + break; + } + case MixBlend.Add: + { + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions, with alpha. + float[] setupVertices = vertexAttachment.vertices; + for (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i]; + vertices[i] += (prev + (nextVertices[i] - prev) * percent - setupVertices[i]) * alpha; + } + } + else + { + // Weighted deform offsets, with alpha. + for (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i]; + vertices[i] += (prev + (nextVertices[i] - prev) * percent) * alpha; + } + } + break; + } + } + } + } + } + + /// Fires an when specific animation times are reached. + public class EventTimeline : Timeline + { + internal float[] frames; // time, ... + private Event[] events; + + public EventTimeline(int frameCount) + { + frames = new float[frameCount]; + events = new Event[frameCount]; + } + + public int PropertyId + { + get { return ((int)TimelineType.Event << 24); } + } + + /// The number of key frames for this timeline. + public int FrameCount { get { return frames.Length; } } + + /// The time in seconds for each key frame. + public float[] Frames { get { return frames; } set { frames = value; } } + + /// The event for each key frame. + public Event[] Events { get { return events; } set { events = value; } } + + /// Sets the time in seconds and the event for the specified key frame. + public void SetFrame(int frameIndex, Event e) + { + frames[frameIndex] = e.Time; + events[frameIndex] = e; + } + + /// Fires events for frames > lastTime and <= time. + public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + if (firedEvents == null) return; + float[] frames = this.frames; + int frameCount = frames.Length; + + if (lastTime > time) + { // Fire events after last time for looped animations. + Apply(skeleton, lastTime, int.MaxValue, firedEvents, alpha, blend, direction); + lastTime = -1f; + } + else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame. + return; + if (time < frames[0]) return; // Time is before first frame. + + int frame; + if (lastTime < frames[0]) + frame = 0; + else + { + frame = Animation.BinarySearch(frames, lastTime); + float frameTime = frames[frame]; + while (frame > 0) + { // Fire multiple events with the same frame. + if (frames[frame - 1] != frameTime) break; + frame--; + } + } + for (; frame < frameCount && time >= frames[frame]; frame++) + firedEvents.Add(events[frame]); + } + } + + /// Changes a skeleton's . + public class DrawOrderTimeline : Timeline + { + internal float[] frames; // time, ... + private int[][] drawOrders; + + public DrawOrderTimeline(int frameCount) + { + frames = new float[frameCount]; + drawOrders = new int[frameCount][]; + } + + public int PropertyId + { + get { return ((int)TimelineType.DrawOrder << 24); } + } + + /// The number of key frames for this timeline. + public int FrameCount { get { return frames.Length; } } + + /// The time in seconds for each key frame. + public float[] Frames { get { return frames; } set { frames = value; } } // time, ... + + /// The draw order for each key frame. + /// . + public int[][] DrawOrders { get { return drawOrders; } set { drawOrders = value; } } + + /// Sets the time in seconds and the draw order for the specified key frame. + /// For each slot in the index of the new draw order. May be null to use setup pose + /// draw order.. + public void SetFrame(int frameIndex, float time, int[] drawOrder) + { + frames[frameIndex] = time; + drawOrders[frameIndex] = drawOrder; + } + + public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + ExposedList drawOrder = skeleton.drawOrder; + ExposedList slots = skeleton.slots; + if (direction == MixDirection.Out && blend == MixBlend.Setup) + { + Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count); + return; + } + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + if (blend == MixBlend.Setup || blend == MixBlend.First) Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count); + return; + } + + int frame; + if (time >= frames[frames.Length - 1]) // Time is after last frame. + frame = frames.Length - 1; + else + frame = Animation.BinarySearch(frames, time) - 1; + + int[] drawOrderToSetupIndex = drawOrders[frame]; + if (drawOrderToSetupIndex == null) + { + Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count); + } + else + { + var drawOrderItems = drawOrder.Items; + var slotsItems = slots.Items; + for (int i = 0, n = drawOrderToSetupIndex.Length; i < n; i++) + drawOrderItems[i] = slotsItems[drawOrderToSetupIndex[i]]; + } + } + } + + /// Changes an IK constraint's , , + /// , and . + public class IkConstraintTimeline : CurveTimeline + { + public const int ENTRIES = 5; + private const int PREV_TIME = -5, PREV_MIX = -4, PREV_BEND_DIRECTION = -3, PREV_COMPRESS = -2, PREV_STRETCH = -1; + private const int MIX = 1, BEND_DIRECTION = 2, COMPRESS = 3, STRETCH = 4; + + internal int ikConstraintIndex; + internal float[] frames; // time, mix, bendDirection, compress, stretch, ... + + public IkConstraintTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + override public int PropertyId + { + get { return ((int)TimelineType.IkConstraint << 24) + ikConstraintIndex; } + } + + /// The index of the IK constraint slot in that will be changed. + public int IkConstraintIndex + { + set + { + if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0."); + this.ikConstraintIndex = value; + } + get + { + return ikConstraintIndex; + } + } + + /// The time in seconds, mix, bend direction, compress, and stretch for each key frame. + public float[] Frames { get { return frames; } set { frames = value; } } + + /// Sets the time in seconds, mix, bend direction, compress, and stretch for the specified key frame. + public void SetFrame(int frameIndex, float time, float mix, int bendDirection, bool compress, bool stretch) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + MIX] = mix; + frames[frameIndex + BEND_DIRECTION] = bendDirection; + frames[frameIndex + COMPRESS] = compress ? 1 : 0; + frames[frameIndex + STRETCH] = stretch ? 1 : 0; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + IkConstraint constraint = skeleton.ikConstraints.Items[ikConstraintIndex]; + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + switch (blend) + { + case MixBlend.Setup: + constraint.mix = constraint.data.mix; + 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.bendDirection = constraint.data.bendDirection; + constraint.compress = constraint.data.compress; + constraint.stretch = constraint.data.stretch; + return; + } + return; + } + + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + if (blend == MixBlend.Setup) + { + constraint.mix = constraint.data.mix + (frames[frames.Length + PREV_MIX] - constraint.data.mix) * alpha; + if (direction == MixDirection.Out) + { + constraint.bendDirection = constraint.data.bendDirection; + constraint.compress = constraint.data.compress; + constraint.stretch = constraint.data.stretch; + } + else + { + constraint.bendDirection = (int)frames[frames.Length + PREV_BEND_DIRECTION]; + constraint.compress = frames[frames.Length + PREV_COMPRESS] != 0; + constraint.stretch = frames[frames.Length + PREV_STRETCH] != 0; + } + } + else + { + constraint.mix += (frames[frames.Length + PREV_MIX] - constraint.mix) * alpha; + if (direction == MixDirection.In) + { + constraint.bendDirection = (int)frames[frames.Length + PREV_BEND_DIRECTION]; + constraint.compress = frames[frames.Length + PREV_COMPRESS] != 0; + constraint.stretch = frames[frames.Length + PREV_STRETCH] != 0; + } + } + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + float mix = frames[frame + PREV_MIX]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + if (blend == MixBlend.Setup) + { + constraint.mix = constraint.data.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.data.mix) * alpha; + if (direction == MixDirection.Out) + { + constraint.bendDirection = constraint.data.bendDirection; + constraint.compress = constraint.data.compress; + constraint.stretch = constraint.data.stretch; + } + else + { + constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION]; + constraint.compress = frames[frame + PREV_COMPRESS] != 0; + constraint.stretch = frames[frame + PREV_STRETCH] != 0; + } + } + else + { + constraint.mix += (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha; + if (direction == MixDirection.In) + { + constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION]; + constraint.compress = frames[frame + PREV_COMPRESS] != 0; + constraint.stretch = frames[frame + PREV_STRETCH] != 0; + } + } + } + } + + /// Changes a transform constraint's mixes. + public class TransformConstraintTimeline : CurveTimeline + { + public const int ENTRIES = 5; + private const int PREV_TIME = -5, PREV_ROTATE = -4, PREV_TRANSLATE = -3, PREV_SCALE = -2, PREV_SHEAR = -1; + private const int ROTATE = 1, TRANSLATE = 2, SCALE = 3, SHEAR = 4; + + internal int transformConstraintIndex; + internal float[] frames; // time, rotate mix, translate mix, scale mix, shear mix, ... + + public TransformConstraintTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + override public int PropertyId + { + get { return ((int)TimelineType.TransformConstraint << 24) + transformConstraintIndex; } + } + + /// The index of the transform constraint slot in that will be changed. + public int TransformConstraintIndex + { + set + { + if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0."); + this.transformConstraintIndex = value; + } + get + { + return transformConstraintIndex; + } + } + + /// The time in seconds, rotate mix, translate mix, scale mix, and shear mix for each key frame. + public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, scale mix, shear mix, ... + + /// The time in seconds, rotate mix, translate mix, scale mix, and shear mix for the specified key frame. + public void SetFrame(int frameIndex, float time, float rotateMix, float translateMix, float scaleMix, float shearMix) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + ROTATE] = rotateMix; + frames[frameIndex + TRANSLATE] = translateMix; + frames[frameIndex + SCALE] = scaleMix; + frames[frameIndex + SHEAR] = shearMix; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + TransformConstraint constraint = skeleton.transformConstraints.Items[transformConstraintIndex]; + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + TransformConstraintData data = constraint.data; + switch (blend) + { + case MixBlend.Setup: + constraint.rotateMix = data.rotateMix; + constraint.translateMix = data.translateMix; + constraint.scaleMix = data.scaleMix; + constraint.shearMix = data.shearMix; + return; + case MixBlend.First: + constraint.rotateMix += (data.rotateMix - constraint.rotateMix) * alpha; + constraint.translateMix += (data.translateMix - constraint.translateMix) * alpha; + constraint.scaleMix += (data.scaleMix - constraint.scaleMix) * alpha; + constraint.shearMix += (data.shearMix - constraint.shearMix) * alpha; + return; + } + return; + } + + float rotate, translate, scale, shear; + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + int i = frames.Length; + rotate = frames[i + PREV_ROTATE]; + translate = frames[i + PREV_TRANSLATE]; + scale = frames[i + PREV_SCALE]; + shear = frames[i + PREV_SHEAR]; + } + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + rotate = frames[frame + PREV_ROTATE]; + translate = frames[frame + PREV_TRANSLATE]; + scale = frames[frame + PREV_SCALE]; + shear = frames[frame + PREV_SHEAR]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + rotate += (frames[frame + ROTATE] - rotate) * percent; + translate += (frames[frame + TRANSLATE] - translate) * percent; + scale += (frames[frame + SCALE] - scale) * percent; + shear += (frames[frame + SHEAR] - shear) * percent; + } + if (blend == MixBlend.Setup) + { + TransformConstraintData data = constraint.data; + constraint.rotateMix = data.rotateMix + (rotate - data.rotateMix) * alpha; + constraint.translateMix = data.translateMix + (translate - data.translateMix) * alpha; + constraint.scaleMix = data.scaleMix + (scale - data.scaleMix) * alpha; + constraint.shearMix = data.shearMix + (shear - data.shearMix) * alpha; + } + else + { + constraint.rotateMix += (rotate - constraint.rotateMix) * alpha; + constraint.translateMix += (translate - constraint.translateMix) * alpha; + constraint.scaleMix += (scale - constraint.scaleMix) * alpha; + constraint.shearMix += (shear - constraint.shearMix) * alpha; + } + } + } + + /// Changes a path constraint's . + public class PathConstraintPositionTimeline : CurveTimeline + { + public const int ENTRIES = 2; + protected const int PREV_TIME = -2, PREV_VALUE = -1; + protected const int VALUE = 1; + + internal int pathConstraintIndex; + internal float[] frames; // time, position, ... + + public PathConstraintPositionTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + override public int PropertyId + { + get { return ((int)TimelineType.PathConstraintPosition << 24) + pathConstraintIndex; } + } + + /// The index of the path constraint slot in that will be changed. + public int PathConstraintIndex + { + set + { + if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0."); + this.pathConstraintIndex = value; + } + get + { + return pathConstraintIndex; + } + } + + /// The time in seconds and path constraint position for each key frame. + public float[] Frames { get { return frames; } set { frames = value; } } // time, position, ... + + /// Sets the time in seconds and path constraint position for the specified key frame. + public void SetFrame(int frameIndex, float time, float position) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + VALUE] = position; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + switch (blend) + { + case MixBlend.Setup: + constraint.position = constraint.data.position; + return; + case MixBlend.First: + constraint.position += (constraint.data.position - constraint.position) * alpha; + return; + } + return; + } + + float position; + if (time >= frames[frames.Length - ENTRIES]) // Time is after last frame. + position = frames[frames.Length + PREV_VALUE]; + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + position = frames[frame + PREV_VALUE]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + position += (frames[frame + VALUE] - position) * percent; + } + if (blend == MixBlend.Setup) + constraint.position = constraint.data.position + (position - constraint.data.position) * alpha; + else + constraint.position += (position - constraint.position) * alpha; + } + } + + /// Changes a path constraint's . + public class PathConstraintSpacingTimeline : PathConstraintPositionTimeline + { + public PathConstraintSpacingTimeline(int frameCount) + : base(frameCount) + { + } + + override public int PropertyId + { + get { return ((int)TimelineType.PathConstraintSpacing << 24) + pathConstraintIndex; } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, MixBlend blend, + MixDirection direction) + { + PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + switch (blend) + { + case MixBlend.Setup: + constraint.spacing = constraint.data.spacing; + return; + case MixBlend.First: + constraint.spacing += (constraint.data.spacing - constraint.spacing) * alpha; + return; + } + return; + } + + float spacing; + if (time >= frames[frames.Length - ENTRIES]) // Time is after last frame. + spacing = frames[frames.Length + PREV_VALUE]; + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + spacing = frames[frame + PREV_VALUE]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + spacing += (frames[frame + VALUE] - spacing) * percent; + } + + if (blend == MixBlend.Setup) + constraint.spacing = constraint.data.spacing + (spacing - constraint.data.spacing) * alpha; + else + constraint.spacing += (spacing - constraint.spacing) * alpha; + } + } + + /// Changes a path constraint's mixes. + public class PathConstraintMixTimeline : CurveTimeline + { + public const int ENTRIES = 3; + private const int PREV_TIME = -3, PREV_ROTATE = -2, PREV_TRANSLATE = -1; + private const int ROTATE = 1, TRANSLATE = 2; + + internal int pathConstraintIndex; + internal float[] frames; // time, rotate mix, translate mix, ... + + public PathConstraintMixTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + override public int PropertyId + { + get { return ((int)TimelineType.PathConstraintMix << 24) + pathConstraintIndex; } + } + + /// The index of the path constraint slot in that will be changed. + public int PathConstraintIndex + { + set + { + if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0."); + this.pathConstraintIndex = value; + } + get + { + return pathConstraintIndex; + } + } + + /// The time in seconds, rotate mix, and translate mix for each key frame. + public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, ... + + /// The time in seconds, rotate mix, and translate mix for the specified key frame. + public void SetFrame(int frameIndex, float time, float rotateMix, float translateMix) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + ROTATE] = rotateMix; + frames[frameIndex + TRANSLATE] = translateMix; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + switch (blend) + { + case MixBlend.Setup: + constraint.rotateMix = constraint.data.rotateMix; + constraint.translateMix = constraint.data.translateMix; + return; + case MixBlend.First: + constraint.rotateMix += (constraint.data.rotateMix - constraint.rotateMix) * alpha; + constraint.translateMix += (constraint.data.translateMix - constraint.translateMix) * alpha; + return; + } + return; + } + + float rotate, translate; + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + rotate = frames[frames.Length + PREV_ROTATE]; + translate = frames[frames.Length + PREV_TRANSLATE]; + } + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + rotate = frames[frame + PREV_ROTATE]; + translate = frames[frame + PREV_TRANSLATE]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + rotate += (frames[frame + ROTATE] - rotate) * percent; + translate += (frames[frame + TRANSLATE] - translate) * percent; + } + + if (blend == MixBlend.Setup) + { + constraint.rotateMix = constraint.data.rotateMix + (rotate - constraint.data.rotateMix) * alpha; + constraint.translateMix = constraint.data.translateMix + (translate - constraint.data.translateMix) * alpha; + } + else + { + constraint.rotateMix += (rotate - constraint.rotateMix) * alpha; + constraint.translateMix += (translate - constraint.translateMix) * alpha; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/AnimationState.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/AnimationState.cs index 8d6d3bd..b30ba91 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/AnimationState.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/AnimationState.cs @@ -30,1230 +30,1369 @@ using System; using System.Collections.Generic; -namespace Spine3_7_94 { - - /// - /// - /// Applies animations over time, queues animations for later playback, mixes (crossfading) between animations, and applies - /// multiple animations on top of each other (layering). - /// - /// See Applying Animations in the Spine Runtimes Guide. - /// - public class AnimationState { - static readonly Animation EmptyAnimation = new Animation("", new ExposedList(), 0); - - /// 1) A previously applied timeline has set this property. - /// Result: Mix from the current pose to the timeline pose. - internal const int Subsequent = 0; - /// 1) This is the first timeline to set this property. - /// 2) The next track entry applied after this one does not have a timeline to set this property. - /// Result: Mix from the setup pose to the timeline pose. - internal const int First = 1; - /// 1) This is the first timeline to set this property. - /// 2) The next track entry to be applied does have a timeline to set this property. - /// 3) The next track entry after that one does not have a timeline to set this property. - /// Result: Mix from the setup pose to the timeline pose, but do not mix out. This avoids "dipping" when crossfading animations - /// that key the same property. A subsequent timeline will set this property using a mix. - internal const int Hold = 2; - /// 1) This is the first timeline to set this property. - /// 2) The next track entry to be applied does have a timeline to set this property. - /// 3) The next track entry after that one does have a timeline to set this property. - /// 4) timelineHoldMix stores the first subsequent track entry that does not have a timeline to set this property. - /// Result: The same as HOLD except the mix percentage from the timelineHoldMix track entry is used. This handles when more than - /// 2 track entries in a row have a timeline that sets the same property. - /// Eg, A -> B -> C -> D where A, B, and C have a timeline setting same property, but D does not. When A is applied, to avoid - /// "dipping" A is not mixed out, however D (the first entry that doesn't set the property) mixing in is used to mix out A - /// (which affects B and C). Without using D to mix out, A would be applied fully until mixing completes, then snap into - /// place. - internal const int HoldMix = 3; - - protected AnimationStateData data; - private readonly ExposedList tracks = new ExposedList(); - private readonly ExposedList events = new ExposedList(); - - // difference to libgdx reference: delegates are used for event callbacks instead of 'Array listeners'. - internal void OnStart (TrackEntry entry) { if (Start != null) Start(entry); } - internal void OnInterrupt (TrackEntry entry) { if (Interrupt != null) Interrupt(entry); } - internal void OnEnd (TrackEntry entry) { if (End != null) End(entry); } - internal void OnDispose (TrackEntry entry) { if (Dispose != null) Dispose(entry); } - internal void OnComplete (TrackEntry entry) { if (Complete != null) Complete(entry); } - internal void OnEvent (TrackEntry entry, Event e) { if (Event != null) Event(entry, e); } - - public delegate void TrackEntryDelegate(TrackEntry trackEntry); - public event TrackEntryDelegate Start, Interrupt, End, Dispose, Complete; - - public delegate void TrackEntryEventDelegate(TrackEntry trackEntry, Event e); - public event TrackEntryEventDelegate Event; - - private readonly EventQueue queue; // Initialized by constructor. - private readonly HashSet propertyIDs = new HashSet(); - private bool animationsChanged; - private float timeScale = 1; - - private readonly Pool trackEntryPool = new Pool(); - - public AnimationState(AnimationStateData data) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - this.data = data; - this.queue = new EventQueue( - this, - delegate { this.animationsChanged = true; }, - trackEntryPool - ); - } - - /// - /// Increments the track entry , setting queued animations as current if needed. - /// delta time - public void Update (float delta) { - delta *= timeScale; - var tracksItems = tracks.Items; - for (int i = 0, n = tracks.Count; i < n; i++) { - TrackEntry current = tracksItems[i]; - if (current == null) continue; - - 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. - tracksItems[i] = null; - queue.End(current); - DisposeNext(current); - continue; - } - if (current.mixingFrom != null && UpdateMixingFrom(current, 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(); - } - - /// Returns true when all mixing from entries are complete. - private bool 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; - - // Require mixTime > 0 to ensure the mixing from entry was applied at least once. - if (to.mixTime > 0 && to.mixTime >= to.mixDuration) { - // Require totalAlpha == 0 to ensure mixing is complete, unless mixDuration == 0 (the transition is a single frame). - if (from.totalAlpha == 0 || to.mixDuration == 0) { - to.mixingFrom = from.mixingFrom; - if (from.mixingFrom != null) from.mixingFrom.mixingTo = to; - to.interruptAlpha = from.interruptAlpha; - queue.End(from); - } - return finished; - } - - from.trackTime += delta * from.timeScale; - to.mixTime += delta; - return false; - } - - /// - /// 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. - /// True if any animations were applied. - public bool Apply (Skeleton skeleton) { - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - if (animationsChanged) AnimationsChanged(); - - var events = this.events; - bool applied = false; - var tracksItems = tracks.Items; - for (int i = 0, n = tracks.Count; i < n; i++) { - TrackEntry current = tracksItems[i]; - if (current == null || current.delay > 0) continue; - applied = true; - - // Track 0 animations aren't for layering, so do not show the previously applied animations before the first key. - MixBlend blend = i == 0 ? MixBlend.First : current.mixBlend; - - // Apply mixing from entries first. - float mix = current.alpha; - if (current.mixingFrom != null) - mix *= ApplyMixingFrom(current, skeleton, blend); - else if (current.trackTime >= current.trackEnd && current.next == null) // - mix = 0; // Set to setup pose the last time the entry will be applied. - - // Apply current entry. - float animationLast = current.animationLast, animationTime = current.AnimationTime; - int timelineCount = current.animation.timelines.Count; - var timelines = current.animation.timelines; - var timelinesItems = timelines.Items; - if ((i == 0 && mix == 1) || blend == MixBlend.Add) { - for (int ii = 0; ii < timelineCount; ii++) - timelinesItems[ii].Apply(skeleton, animationLast, animationTime, events, mix, blend, MixDirection.In); - } else { - var timelineMode = current.timelineMode.Items; - - bool firstFrame = current.timelinesRotation.Count == 0; - if (firstFrame) current.timelinesRotation.EnsureCapacity(timelines.Count << 1); - var timelinesRotation = current.timelinesRotation.Items; - - for (int ii = 0; ii < timelineCount; ii++) { - Timeline timeline = timelinesItems[ii]; - MixBlend timelineBlend = timelineMode[ii] == AnimationState.Subsequent ? blend : MixBlend.Setup; - var rotateTimeline = timeline as RotateTimeline; - if (rotateTimeline != null) - ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, mix, timelineBlend, timelinesRotation, ii << 1, - firstFrame); - else - timeline.Apply(skeleton, animationLast, animationTime, events, mix, timelineBlend, MixDirection.In); - } - } - QueueEvents(current, animationTime); - events.Clear(false); - current.nextAnimationLast = animationTime; - current.nextTrackLast = current.trackTime; - } - - queue.Drain(); - return applied; - } - - private float 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; // Tracks > 0 are transparent and can't reset to setup pose. - } else { - mix = to.mixTime / to.mixDuration; - if (mix > 1) mix = 1; - if (blend != MixBlend.First) blend = from.mixBlend; // Track 0 ignores track mix blend. - } - - var eventBuffer = mix < from.eventThreshold ? this.events : null; - bool attachments = mix < from.attachmentThreshold, drawOrder = mix < from.drawOrderThreshold; - float animationLast = from.animationLast, animationTime = from.AnimationTime; - var timelines = from.animation.timelines; - int timelineCount = timelines.Count; - var timelinesItems = timelines.Items; - float alphaHold = from.alpha * to.interruptAlpha, alphaMix = alphaHold * (1 - mix); - - if (blend == MixBlend.Add) { - for (int i = 0; i < timelineCount; i++) - timelinesItems[i].Apply(skeleton, animationLast, animationTime, eventBuffer, alphaMix, blend, MixDirection.Out); - } else { - var timelineMode = from.timelineMode.Items; - var timelineHoldMix = from.timelineHoldMix.Items; - - bool firstFrame = from.timelinesRotation.Count == 0; - if (firstFrame) from.timelinesRotation.Resize(timelines.Count << 1); // from.timelinesRotation.setSize - var timelinesRotation = from.timelinesRotation.Items; - - from.totalAlpha = 0; - for (int i = 0; i < timelineCount; i++) { - Timeline timeline = timelinesItems[i]; - MixDirection direction = MixDirection.Out; - MixBlend timelineBlend; - float alpha; - switch (timelineMode[i]) { - case AnimationState.Subsequent: - if (!attachments && timeline is AttachmentTimeline) continue; - if (!drawOrder && timeline is DrawOrderTimeline) continue; - timelineBlend = blend; - alpha = alphaMix; - break; - case AnimationState.First: - timelineBlend = MixBlend.Setup; - alpha = alphaMix; - break; - case AnimationState.Hold: - timelineBlend = MixBlend.Setup; - alpha = alphaHold; - break; - default: - timelineBlend = MixBlend.Setup; - TrackEntry holdMix = timelineHoldMix[i]; - alpha = alphaHold * Math.Max(0, 1 - holdMix.mixTime / holdMix.mixDuration); - break; - } - from.totalAlpha += alpha; - - var rotateTimeline = timeline as RotateTimeline; - if (rotateTimeline != null) { - ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, alpha, timelineBlend, timelinesRotation, i << 1, - firstFrame); - } else { - if (timelineBlend == MixBlend.Setup) { - if (timeline is AttachmentTimeline) { - if (attachments) direction = MixDirection.In; - } else if (timeline is DrawOrderTimeline) { - if (drawOrder) { - direction = MixDirection.In; - } - } - } - - timeline.Apply(skeleton, animationLast, animationTime, eventBuffer, alpha, timelineBlend, direction); - } - } - } - - if (to.mixDuration > 0) QueueEvents(from, animationTime); - this.events.Clear(false); - from.nextAnimationLast = animationTime; - from.nextTrackLast = from.trackTime; - - return mix; - } - - static private void ApplyRotateTimeline (RotateTimeline rotateTimeline, Skeleton skeleton, float time, float alpha, MixBlend blend, - float[] timelinesRotation, int 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.Items[rotateTimeline.boneIndex]; - float[] frames = rotateTimeline.frames; - float r1, r2; - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case MixBlend.Setup: - bone.rotation = bone.data.rotation; - return; - default: - return; - case MixBlend.First: - r1 = bone.rotation; - r2 = bone.data.rotation; - break; - } - } else { - r1 = blend == MixBlend.Setup ? bone.data.rotation : bone.rotation; - if (time >= frames[frames.Length - RotateTimeline.ENTRIES]) // Time is after last frame. - r2 = bone.data.rotation + frames[frames.Length + RotateTimeline.PREV_ROTATION]; - else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, RotateTimeline.ENTRIES); - float prevRotation = frames[frame + RotateTimeline.PREV_ROTATION]; - float frameTime = frames[frame]; - float percent = rotateTimeline.GetCurvePercent((frame >> 1) - 1, - 1 - (time - frameTime) / (frames[frame + RotateTimeline.PREV_TIME] - frameTime)); - - r2 = frames[frame + RotateTimeline.ROTATION] - prevRotation; - r2 -= (16384 - (int)(16384.499999999996 - r2 / 360)) * 360; - r2 = prevRotation + r2 * percent + bone.data.rotation; - r2 -= (16384 - (int)(16384.499999999996 - r2 / 360)) * 360; - } - } - - // Mix between rotations using the direction of the shortest route on the first frame. - float total, diff = r2 - r1; - diff -= (16384 - (int)(16384.499999999996 - diff / 360)) * 360; - if (diff == 0) { - total = timelinesRotation[i]; - } else { - float lastTotal, lastDiff; - if (firstFrame) { - lastTotal = 0; - lastDiff = diff; - } else { - lastTotal = timelinesRotation[i]; // Angle and direction of mix, including loops. - lastDiff = timelinesRotation[i + 1]; // Difference between bones. - } - bool current = diff > 0, dir = lastTotal >= 0; - // Detect cross at 0 (not 180). - if (Math.Sign(lastDiff) != Math.Sign(diff) && Math.Abs(lastDiff) <= 90) { - // A cross after a 360 rotation is a loop. - if (Math.Abs(lastTotal) > 180) lastTotal += 360 * Math.Sign(lastTotal); - dir = current; - } - total = diff + lastTotal - lastTotal % 360; // Store loops as part of lastTotal. - if (dir != current) total += 360 * Math.Sign(lastTotal); - timelinesRotation[i] = total; - } - timelinesRotation[i + 1] = diff; - r1 += total * alpha; - bone.rotation = r1 - (16384 - (int)(16384.499999999996 - r1 / 360)) * 360; - } - - private void QueueEvents (TrackEntry entry, float animationTime) { - float animationStart = entry.animationStart, animationEnd = entry.animationEnd; - float duration = animationEnd - animationStart; - float trackLastWrapped = entry.trackLast % duration; - - // Queue events before complete. - var events = this.events; - var eventsItems = events.Items; - int i = 0, n = events.Count; - for (; i < n; i++) { - Event e = eventsItems[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) - complete = duration == 0 || (trackLastWrapped > entry.trackTime % duration); - else - complete = animationTime >= animationEnd && entry.animationLast < animationEnd; - if (complete) queue.Complete(entry); - - // Queue events after complete. - for (; i < n; i++) { - Event e = eventsItems[i]; - if (e.time < animationStart) continue; // Discard events outside animation start/end. - queue.Event(entry, eventsItems[i]); - } - } - - /// - /// Removes all animations from all tracks, leaving skeletons in their current pose. - /// - /// It may be desired to use to mix the skeletons back to the setup pose, - /// rather than leaving them in their current pose. - /// - public void ClearTracks () { - bool oldDrainDisabled = queue.drainDisabled; - queue.drainDisabled = true; - for (int i = 0, n = tracks.Count; i < n; i++) { - ClearTrack(i); - } - tracks.Clear(); - queue.drainDisabled = oldDrainDisabled; - queue.Drain(); - } - - /// - /// Removes all animations from the track, leaving skeletons in their current pose. - /// - /// It may be desired to use to mix the skeletons back to the setup pose, - /// rather than leaving them in their current pose. - /// - public void ClearTrack (int trackIndex) { - if (trackIndex >= tracks.Count) return; - TrackEntry current = tracks.Items[trackIndex]; - if (current == null) return; - - queue.End(current); - - DisposeNext(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.Items[current.trackIndex] = null; - - queue.Drain(); - } - - /// Sets the active TrackEntry for a given track number. - private void SetCurrent (int index, TrackEntry current, bool interrupt) { - TrackEntry from = ExpandToIndex(index); - tracks.Items[index] = current; - - if (from != null) { - if (interrupt) queue.Interrupt(from); - current.mixingFrom = from; - from.mixingTo = current; - current.mixTime = 0; - - // Store the interrupted mix percentage. - if (from.mixingFrom != null && from.mixDuration > 0) - current.interruptAlpha *= Math.Min(1, from.mixTime / from.mixDuration); - - from.timelinesRotation.Clear(); // Reset rotation for mixing out, in case entry was mixed in. - } - - queue.Start(current); // triggers AnimationsChanged - } - - - /// Sets an animation by name. - public TrackEntry SetAnimation (int trackIndex, string animationName, bool loop) { - Animation animation = data.skeletonData.FindAnimation(animationName); - if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName"); - return SetAnimation(trackIndex, animation, loop); - } - - /// Sets the current animation for a track, discarding any queued animations. If the formerly current track entry was never - /// applied to a skeleton, it is replaced (not mixed from). - /// 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 determines when the track is cleared. - /// A track entry to allow further customization of animation playback. References to the track entry must not be kept - /// after the event occurs. - public TrackEntry SetAnimation (int trackIndex, Animation animation, bool loop) { - if (animation == null) throw new ArgumentNullException("animation", "animation cannot be 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.Items[trackIndex] = current.mixingFrom; - queue.Interrupt(current); - queue.End(current); - DisposeNext(current); - current = current.mixingFrom; - interrupt = false; // mixingFrom is current again, but don't interrupt it twice. - } else { - DisposeNext(current); - } - } - TrackEntry entry = NewTrackEntry(trackIndex, animation, loop, current); - SetCurrent(trackIndex, entry, interrupt); - queue.Drain(); - return entry; - } - - /// Queues an animation by name. - /// - public TrackEntry AddAnimation (int trackIndex, string animationName, bool loop, float delay) { - Animation animation = data.skeletonData.FindAnimation(animationName); - if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName"); - return AddAnimation(trackIndex, animation, loop, delay); - } - - /// Adds an animation to be played after the current or last queued animation for a track. If the track is empty, it is - /// equivalent to calling . - /// - /// If > 0, sets . If <= 0, the delay set is the duration of the previous track entry - /// minus any mix duration (from the {@link AnimationStateData}) plus the specified Delay (ie the mix - /// ends at (Delay = 0) or before (Delay < 0) the previous track entry duration). If the - /// previous entry is looping, its next loop completion is used instead of its duration. - /// - /// A track entry to allow further customization of animation playback. References to the track entry must not be kept - /// after the event occurs. - public TrackEntry AddAnimation (int trackIndex, Animation animation, bool loop, float delay) { - if (animation == null) throw new ArgumentNullException("animation", "animation cannot be 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(); - } else { - last.next = entry; - if (delay <= 0) { - float duration = last.animationEnd - last.animationStart; - if (duration != 0) { - if (last.loop) { - delay += duration * (1 + (int)(last.trackTime / duration)); // Completion of next loop. - } else { - delay += Math.Max(duration, last.trackTime); // After duration, else next update. - } - delay -= data.GetMix(last.animation, animation); - } else - delay = last.trackTime; // Next update. - } - } - - entry.delay = delay; - return entry; - } - - /// - /// Sets an empty animation for a track, discarding any queued animations, and sets the track entry's - /// . An empty animation has no timelines and serves as a placeholder for mixing in or out. - /// - /// Mixing out is done by setting an empty animation with a mix duration using either , - /// , or . Mixing to an empty animation causes - /// the previous animation to be applied less and less over the mix duration. Properties keyed in the previous animation - /// transition to the value from lower tracks or to the setup pose value if no lower tracks key the property. A mix duration of - /// 0 still mixes out over one frame. - /// - /// Mixing in is done by first setting an empty animation, then adding an animation using - /// and on the returned track entry, set the - /// . Mixing from an empty animation causes the new animation to be applied more and - /// more over the mix duration. Properties keyed in the new animation transition from the value from lower tracks or from the - /// setup pose value if no lower tracks key the property to the value keyed in the new animation. - /// - public TrackEntry SetEmptyAnimation (int trackIndex, float mixDuration) { - TrackEntry entry = SetAnimation(trackIndex, AnimationState.EmptyAnimation, false); - entry.mixDuration = mixDuration; - entry.trackEnd = mixDuration; - return entry; - } - - /// - /// Adds an empty animation to be played after the current or last queued animation for a track, and sets the track entry's - /// . If the track is empty, it is equivalent to calling - /// . - /// - /// Track number. - /// Mix duration. - /// If > 0, sets . If <= 0, the delay set is the duration of the previous track entry - /// minus any mix duration plus the specified Delay (ie the mix ends at (Delay = 0) or - /// before (Delay < 0) the previous track entry duration). If the previous entry is looping, its next - /// loop completion is used instead of its duration. - /// A track entry to allow further customization of animation playback. References to the track entry must not be kept - /// after the event occurs. - /// - public TrackEntry AddEmptyAnimation (int trackIndex, float mixDuration, float delay) { - if (delay <= 0) delay -= mixDuration; - TrackEntry entry = AddAnimation(trackIndex, AnimationState.EmptyAnimation, false, delay); - entry.mixDuration = mixDuration; - entry.trackEnd = mixDuration; - return entry; - } - - /// - /// Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix - /// duration. - public void SetEmptyAnimations (float mixDuration) { - bool oldDrainDisabled = queue.drainDisabled; - queue.drainDisabled = true; - for (int i = 0, n = tracks.Count; i < n; i++) { - TrackEntry current = tracks.Items[i]; - if (current != null) SetEmptyAnimation(current.trackIndex, mixDuration); - } - queue.drainDisabled = oldDrainDisabled; - queue.Drain(); - } - - private TrackEntry ExpandToIndex (int index) { - if (index < tracks.Count) return tracks.Items[index]; - tracks.Resize(index + 1); - return null; - } - - /// Object-pooling version of new TrackEntry. Obtain an unused TrackEntry from the pool and clear/initialize its values. - /// May be null. - private TrackEntry NewTrackEntry (int trackIndex, Animation animation, bool loop, TrackEntry last) { - TrackEntry entry = trackEntryPool.Obtain(); // Pooling - entry.trackIndex = trackIndex; - entry.animation = animation; - entry.loop = loop; - entry.holdPrevious = false; - - entry.eventThreshold = 0; - entry.attachmentThreshold = 0; - entry.drawOrderThreshold = 0; - - entry.animationStart = 0; - entry.animationEnd = animation.Duration; - 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 = float.MaxValue; // loop ? float.MaxValue : animation.Duration; - entry.timeScale = 1; - - entry.alpha = 1; - entry.interruptAlpha = 1; - entry.mixTime = 0; - entry.mixDuration = (last == null) ? 0 : data.GetMix(last.animation, animation); - return entry; - } - - /// Dispose all track entries queued after the given TrackEntry. - private void DisposeNext (TrackEntry entry) { - TrackEntry next = entry.next; - while (next != null) { - queue.Dispose(next); - next = next.next; - } - entry.next = null; - } - - private void AnimationsChanged () { - animationsChanged = false; - - propertyIDs.Clear(); - - var tracksItems = tracks.Items; - for (int i = 0, n = tracks.Count; i < n; i++) { - TrackEntry entry = tracksItems[i]; - if (entry == null) continue; - // Move to last entry, then iterate in reverse (the order animations are applied). - while (entry.mixingFrom != null) - entry = entry.mixingFrom; - - do { - if (entry.mixingTo == null || entry.mixBlend != MixBlend.Add) SetTimelineModes(entry); - entry = entry.mixingTo; - } while (entry != null); - - } - } - - private void SetTimelineModes (TrackEntry entry) { - TrackEntry to = entry.mixingTo; - var timelines = entry.animation.timelines.Items; - int timelinesCount = entry.animation.timelines.Count; - var timelineMode = entry.timelineMode.Resize(timelinesCount).Items; //timelineMode.setSize(timelinesCount); - entry.timelineHoldMix.Clear(); - var timelineHoldMix = entry.timelineHoldMix.Resize(timelinesCount).Items; //timelineHoldMix.setSize(timelinesCount); - var propertyIDs = this.propertyIDs; - - if (to != null && to.holdPrevious) { - for (int i = 0; i < timelinesCount; i++) { - propertyIDs.Add(timelines[i].PropertyId); - timelineMode[i] = AnimationState.Hold; - } - return; - } - - // outer: - for (int i = 0; i < timelinesCount; i++) { - int id = timelines[i].PropertyId; - if (!propertyIDs.Add(id)) - timelineMode[i] = AnimationState.Subsequent; - else if (to == null || !HasTimeline(to, id)) - timelineMode[i] = AnimationState.First; - else { - for (TrackEntry next = to.mixingTo; next != null; next = next.mixingTo) { - if (HasTimeline(next, id)) continue; - if (next.mixDuration > 0) { - timelineMode[i] = AnimationState.HoldMix; - timelineHoldMix[i] = next; - goto continue_outer; // continue outer; - } - break; - } - timelineMode[i] = AnimationState.Hold; - } - continue_outer: {} - } - } - - static bool HasTimeline (TrackEntry entry, int id) { - var timelines = entry.animation.timelines.Items; - for (int i = 0, n = entry.animation.timelines.Count; i < n; i++) - if (timelines[i].PropertyId == id) return true; - return false; - } - - /// The track entry for the animation currently playing on the track, or null if no animation is currently playing. - public TrackEntry GetCurrent (int trackIndex) { - if (trackIndex >= tracks.Count) return null; - return tracks.Items[trackIndex]; - } - - /// Discards all listener notifications that have not yet been delivered. This can be useful to call from an - /// AnimationState event subscriber when it is known that further notifications that may have been already queued for delivery - /// are not wanted because new animations are being set. - public void ClearListenerNotifications () { - queue.Clear(); - } - - /// - /// Multiplier for the delta time when the animation state is updated, causing time for all animations and mixes to play slower - /// or faster. Defaults to 1. - /// - /// See TrackEntry for affecting a single animation. - /// - public float TimeScale { get { return timeScale; } set { timeScale = value; } } - - /// The AnimationStateData to look up mix durations. - public AnimationStateData Data { - get { - return data; - } - set { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - this.data = value; - } - } - - /// A list of tracks that have animations, which may contain nulls. - public ExposedList Tracks { get { return tracks; } } - - override public string ToString () { - var buffer = new System.Text.StringBuilder(); - for (int i = 0, n = tracks.Count; i < n; i++) { - TrackEntry entry = tracks.Items[i]; - if (entry == null) continue; - if (buffer.Length > 0) buffer.Append(", "); - buffer.Append(entry.ToString()); - } - if (buffer.Length == 0) return ""; - return buffer.ToString(); - } - } - - /// - /// - /// Stores settings and other state for the playback of an animation on an track. - /// - /// References to a track entry must not be kept after the event occurs. - /// - public class TrackEntry : Pool.IPoolable { - internal Animation animation; - - internal TrackEntry next, mixingFrom, mixingTo; - // difference to libgdx reference: delegates are used for event callbacks instead of 'AnimationStateListener listener'. - public event AnimationState.TrackEntryDelegate Start, Interrupt, End, Dispose, Complete; - public event AnimationState.TrackEntryEventDelegate Event; - internal void OnStart () { if (Start != null) Start(this); } - internal void OnInterrupt () { if (Interrupt != null) Interrupt(this); } - internal void OnEnd () { if (End != null) End(this); } - internal void OnDispose () { if (Dispose != null) Dispose(this); } - internal void OnComplete () { if (Complete != null) Complete(this); } - internal void OnEvent (Event e) { if (Event != null) Event(this, e); } - - internal int trackIndex; - - internal bool loop, holdPrevious; - internal float eventThreshold, attachmentThreshold, drawOrderThreshold; - internal float animationStart, animationEnd, animationLast, nextAnimationLast; - internal float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale = 1f; - internal float alpha, mixTime, mixDuration, interruptAlpha, totalAlpha; - internal MixBlend mixBlend = MixBlend.Replace; - internal readonly ExposedList timelineMode = new ExposedList(); - internal readonly ExposedList timelineHoldMix = new ExposedList(); - internal readonly ExposedList timelinesRotation = new ExposedList(); - - // IPoolable.Reset() - public void Reset () { - next = null; - mixingFrom = null; - mixingTo = null; - animation = null; - // replaces 'listener = null;' since delegates are used for event callbacks - Start = null; - Interrupt = null; - End = null; - Dispose = null; - Complete = null; - Event = null; - timelineMode.Clear(); - timelineHoldMix.Clear(); - timelinesRotation.Clear(); - } - - /// The index of the track where this entry is either current or queued. - /// - public int TrackIndex { get { return trackIndex; } } - - /// The animation to apply for this track entry. - public Animation Animation { get { return animation; } } - - /// - /// If true, the animation will repeat. If false it will not, instead its last frame is applied if played beyond its - /// duration. - public bool Loop { get { return loop; } set { loop = value; } } - - /// - /// - /// Seconds to postpone playing the animation. When this track entry is the current track entry, Delay - /// postpones incrementing the . When this track entry is queued, Delay is the time from - /// the start of the previous animation to when this track entry will become the current track entry (ie when the previous - /// track entry >= this track entry's Delay). - /// - /// affects the delay. - /// - public float Delay { get { return delay; } set { delay = value; } } - - /// - /// Current time in seconds this track entry has been the current track entry. The track time determines - /// . The track time can be set to start the animation at a time other than 0, without affecting - /// looping. - public float TrackTime { get { return trackTime; } set { trackTime = value; } } - - /// - /// - /// The track time in seconds when this animation will be removed from the track. Defaults to the highest possible float - /// value, meaning the animation will be applied until a new animation is set or the track is cleared. If the track end time - /// is reached, no other animations are queued for playback, and mixing from any previous animations is complete, then the - /// properties keyed by the animation are set to the setup pose and the track is cleared. - /// - /// It may be desired to use rather than have the animation - /// abruptly cease being applied. - /// - public float TrackEnd { get { return trackEnd; } set { trackEnd = value; } } - - /// - /// - /// Seconds when this animation starts, both initially and after looping. Defaults to 0. - /// - /// When changing the AnimationStart time, it often makes sense to set to the same - /// value to prevent timeline keys before the start time from triggering. - /// - public float AnimationStart { get { return animationStart; } set { animationStart = value; } } - - /// - /// Seconds for the last frame of this animation. Non-looping animations won't play past this time. Looping animations will - /// loop back to at this time. Defaults to the animation . - /// - public float AnimationEnd { get { return animationEnd; } set { animationEnd = value; } } - - /// - /// 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 AnimationLast time (exclusive) and - /// AnimationTime (inclusive). Defaults to -1 to ensure triggers on frame 0 happen the first time this animation - /// is applied. - public float AnimationLast { - get { return animationLast; } - set { - animationLast = value; - nextAnimationLast = value; - } - } - - /// - /// Uses to compute the AnimationTime, which is between - /// and . When the TrackTime is 0, the AnimationTime is equal to the - /// AnimationStart time. - /// - public float AnimationTime { - get { - if (loop) { - float duration = animationEnd - animationStart; - if (duration == 0) return animationStart; - return (trackTime % duration) + animationStart; - } - return Math.Min(trackTime + animationStart, animationEnd); - } - } - - /// - /// - /// Multiplier for the delta time when this track entry is updated, causing time for this animation to pass slower or - /// faster. Defaults to 1. - /// - /// is not affected by track entry time scale, so may need to be adjusted to - /// match the animation speed. - /// - /// When using with a Delay <= 0, note the - /// { is set using the mix duration from the , assuming time scale to be 1. If - /// the time scale is not 1, the delay may need to be adjusted. - /// - /// See AnimationState for affecting all animations. - /// - public float TimeScale { get { return timeScale; } set { timeScale = value; } } - - /// - /// - /// Values < 1 mix this animation with the skeleton's current pose (usually the pose resulting from lower tracks). Defaults - /// to 1, which overwrites the skeleton's current pose with this animation. - /// - /// Typically track 0 is used to completely pose the skeleton, then alpha is 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. - /// - public float Alpha { get { return alpha; } set { alpha = value; } } - - /// - /// When the mix percentage ( / ) is less than the - /// EventThreshold, event timelines are applied while this animation is being mixed out. Defaults to 0, so event - /// timelines are not applied while this animation is being mixed out. - /// - public float EventThreshold { get { return eventThreshold; } set { eventThreshold = value; } } - - /// - /// When the mix percentage ( / ) is less than the - /// AttachmentThreshold, attachment timelines are applied while this animation is being mixed out. Defaults to - /// 0, so attachment timelines are not applied while this animation is being mixed out. - /// - public float AttachmentThreshold { get { return attachmentThreshold; } set { attachmentThreshold = value; } } - - /// - /// When the mix percentage ( / ) is less than the - /// DrawOrderThreshold, draw order timelines are applied while this animation is being mixed out. Defaults to 0, - /// so draw order timelines are not applied while this animation is being mixed out. - /// - public float DrawOrderThreshold { get { return drawOrderThreshold; } set { drawOrderThreshold = value; } } - - /// - /// The animation queued to start after this animation, or null. Next makes up a linked list. - public TrackEntry Next { get { return next; } } - - /// - /// Returns true if at least one loop has been completed. - /// - public bool IsComplete { - get { return trackTime >= animationEnd - animationStart; } - } - - /// - /// Seconds from 0 to the when mixing from the previous animation to this animation. May be - /// slightly more than MixDuration when the mix is complete. - public float MixTime { get { return mixTime; } set { mixTime = value; } } - - /// - /// - /// 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 MixDuration can be set manually rather than use the value from - /// . In that case, the MixDuration can be set for a new - /// track entry only before is first called. - /// - /// When using with a Delay <= 0, note the - /// is set using the mix duration from the , not a mix duration set - /// afterward. - /// - public float MixDuration { get { return mixDuration; } set { mixDuration = value; } } - - /// - /// - /// Controls how properties keyed in the animation are mixed with lower tracks. Defaults to , which - /// replaces the values from the lower tracks with the animation values. adds the animation values to - /// the values from the lower tracks. - /// - /// The MixBlend can be set for a new track entry only before is first - /// called. - /// - public MixBlend MixBlend { get { return mixBlend; } set { mixBlend = value; } } - - /// - /// 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 linked list. - public TrackEntry MixingFrom { get { return mixingFrom; } } - - /// - /// The track entry for the next animation when mixing from this animation to the next animation, or null if no mixing is - /// currently occuring. When mixing to multiple animations, MixingTo makes up a linked list. - public TrackEntry MixingTo { get { return mixingTo; } } - - /// - /// - /// 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. - /// - public bool HoldPrevious { get { return holdPrevious; } set { holdPrevious = value; } } - - /// - /// - /// 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 and starting animations on other tracks. - /// - /// Mixing with 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. - /// - public void ResetRotationDirections () { - timelinesRotation.Clear(); - } - - override public string ToString () { - return animation == null ? "" : animation.name; - } - } - - class EventQueue { - private readonly List eventQueueEntries = new List(); - internal bool drainDisabled; - - private readonly AnimationState state; - private readonly Pool trackEntryPool; - internal event Action AnimationsChanged; - - internal EventQueue (AnimationState state, Action HandleAnimationsChanged, Pool trackEntryPool) { - this.state = state; - this.AnimationsChanged += HandleAnimationsChanged; - this.trackEntryPool = trackEntryPool; - } - - struct EventQueueEntry { - public EventType type; - public TrackEntry entry; - public Event e; - - public EventQueueEntry (EventType eventType, TrackEntry trackEntry, Event e = null) { - this.type = eventType; - this.entry = trackEntry; - this.e = e; - } - } - - enum EventType { - Start, Interrupt, End, Dispose, Complete, Event - } - - internal void Start (TrackEntry entry) { - eventQueueEntries.Add(new EventQueueEntry(EventType.Start, entry)); - if (AnimationsChanged != null) AnimationsChanged(); - } - - internal void Interrupt (TrackEntry entry) { - eventQueueEntries.Add(new EventQueueEntry(EventType.Interrupt, entry)); - } - - internal void End (TrackEntry entry) { - eventQueueEntries.Add(new EventQueueEntry(EventType.End, entry)); - if (AnimationsChanged != null) AnimationsChanged(); - } - - internal void Dispose (TrackEntry entry) { - eventQueueEntries.Add(new EventQueueEntry(EventType.Dispose, entry)); - } - - internal void Complete (TrackEntry entry) { - eventQueueEntries.Add(new EventQueueEntry(EventType.Complete, entry)); - } - - internal void Event (TrackEntry entry, Event e) { - eventQueueEntries.Add(new EventQueueEntry(EventType.Event, entry, e)); - } - - /// Raises all events in the queue and drains the queue. - internal void Drain () { - if (drainDisabled) return; - drainDisabled = true; - - var entries = this.eventQueueEntries; - AnimationState state = this.state; - - // Don't cache entries.Count so callbacks can queue their own events (eg, call SetAnimation in AnimationState_Complete). - for (int i = 0; i < entries.Count; i++) { - var queueEntry = entries[i]; - TrackEntry trackEntry = queueEntry.entry; - - switch (queueEntry.type) { - case EventType.Start: - trackEntry.OnStart(); - state.OnStart(trackEntry); - break; - case EventType.Interrupt: - trackEntry.OnInterrupt(); - state.OnInterrupt(trackEntry); - break; - case EventType.End: - trackEntry.OnEnd(); - state.OnEnd(trackEntry); - goto case EventType.Dispose; // Fall through. (C#) - case EventType.Dispose: - trackEntry.OnDispose(); - state.OnDispose(trackEntry); - trackEntryPool.Free(trackEntry); // Pooling - break; - case EventType.Complete: - trackEntry.OnComplete(); - state.OnComplete(trackEntry); - break; - case EventType.Event: - trackEntry.OnEvent(queueEntry.e); - state.OnEvent(trackEntry, queueEntry.e); - break; - } - } - eventQueueEntries.Clear(); - - drainDisabled = false; - } - - internal void Clear () { - eventQueueEntries.Clear(); - } - } - - public class Pool where T : class, new() { - public readonly int max; - readonly Stack freeObjects; - - public int Count { get { return freeObjects.Count; } } - public int Peak { get; private set; } - - public Pool (int initialCapacity = 16, int max = int.MaxValue) { - freeObjects = new Stack(initialCapacity); - this.max = max; - } - - public T Obtain () { - return freeObjects.Count == 0 ? new T() : freeObjects.Pop(); - } - - public void Free (T obj) { - if (obj == null) throw new ArgumentNullException("obj", "obj cannot be null"); - if (freeObjects.Count < max) { - freeObjects.Push(obj); - Peak = Math.Max(Peak, freeObjects.Count); - } - Reset(obj); - } - -// protected void FreeAll (List objects) { -// if (objects == null) throw new ArgumentNullException("objects", "objects cannot be null."); -// var freeObjects = this.freeObjects; -// int max = this.max; -// for (int i = 0; i < objects.Count; i++) { -// T obj = objects[i]; -// if (obj == null) continue; -// if (freeObjects.Count < max) freeObjects.Push(obj); -// Reset(obj); -// } -// Peak = Math.Max(Peak, freeObjects.Count); -// } - - public void Clear () { - freeObjects.Clear(); - } - - protected void Reset (T obj) { - var poolable = obj as IPoolable; - if (poolable != null) poolable.Reset(); - } - - public interface IPoolable { - void Reset (); - } - } +namespace Spine3_7_94 +{ + + /// + /// + /// Applies animations over time, queues animations for later playback, mixes (crossfading) between animations, and applies + /// multiple animations on top of each other (layering). + /// + /// See Applying Animations in the Spine Runtimes Guide. + /// + public class AnimationState + { + static readonly Animation EmptyAnimation = new Animation("", new ExposedList(), 0); + + /// 1) A previously applied timeline has set this property. + /// Result: Mix from the current pose to the timeline pose. + internal const int Subsequent = 0; + /// 1) This is the first timeline to set this property. + /// 2) The next track entry applied after this one does not have a timeline to set this property. + /// Result: Mix from the setup pose to the timeline pose. + internal const int First = 1; + /// 1) This is the first timeline to set this property. + /// 2) The next track entry to be applied does have a timeline to set this property. + /// 3) The next track entry after that one does not have a timeline to set this property. + /// Result: Mix from the setup pose to the timeline pose, but do not mix out. This avoids "dipping" when crossfading animations + /// that key the same property. A subsequent timeline will set this property using a mix. + internal const int Hold = 2; + /// 1) This is the first timeline to set this property. + /// 2) The next track entry to be applied does have a timeline to set this property. + /// 3) The next track entry after that one does have a timeline to set this property. + /// 4) timelineHoldMix stores the first subsequent track entry that does not have a timeline to set this property. + /// Result: The same as HOLD except the mix percentage from the timelineHoldMix track entry is used. This handles when more than + /// 2 track entries in a row have a timeline that sets the same property. + /// Eg, A -> B -> C -> D where A, B, and C have a timeline setting same property, but D does not. When A is applied, to avoid + /// "dipping" A is not mixed out, however D (the first entry that doesn't set the property) mixing in is used to mix out A + /// (which affects B and C). Without using D to mix out, A would be applied fully until mixing completes, then snap into + /// place. + internal const int HoldMix = 3; + + protected AnimationStateData data; + private readonly ExposedList tracks = new ExposedList(); + private readonly ExposedList events = new ExposedList(); + + // difference to libgdx reference: delegates are used for event callbacks instead of 'Array listeners'. + internal void OnStart(TrackEntry entry) { if (Start != null) Start(entry); } + internal void OnInterrupt(TrackEntry entry) { if (Interrupt != null) Interrupt(entry); } + internal void OnEnd(TrackEntry entry) { if (End != null) End(entry); } + internal void OnDispose(TrackEntry entry) { if (Dispose != null) Dispose(entry); } + internal void OnComplete(TrackEntry entry) { if (Complete != null) Complete(entry); } + internal void OnEvent(TrackEntry entry, Event e) { if (Event != null) Event(entry, e); } + + public delegate void TrackEntryDelegate(TrackEntry trackEntry); + public event TrackEntryDelegate Start, Interrupt, End, Dispose, Complete; + + public delegate void TrackEntryEventDelegate(TrackEntry trackEntry, Event e); + public event TrackEntryEventDelegate Event; + + private readonly EventQueue queue; // Initialized by constructor. + private readonly HashSet propertyIDs = new HashSet(); + private bool animationsChanged; + private float timeScale = 1; + + private readonly Pool trackEntryPool = new Pool(); + + public AnimationState(AnimationStateData data) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + this.data = data; + this.queue = new EventQueue( + this, + delegate { this.animationsChanged = true; }, + trackEntryPool + ); + } + + /// + /// Increments the track entry , setting queued animations as current if needed. + /// delta time + public void Update(float delta) + { + delta *= timeScale; + var tracksItems = tracks.Items; + for (int i = 0, n = tracks.Count; i < n; i++) + { + TrackEntry current = tracksItems[i]; + if (current == null) continue; + + 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. + tracksItems[i] = null; + queue.End(current); + DisposeNext(current); + continue; + } + if (current.mixingFrom != null && UpdateMixingFrom(current, 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(); + } + + /// Returns true when all mixing from entries are complete. + private bool 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; + + // Require mixTime > 0 to ensure the mixing from entry was applied at least once. + if (to.mixTime > 0 && to.mixTime >= to.mixDuration) + { + // Require totalAlpha == 0 to ensure mixing is complete, unless mixDuration == 0 (the transition is a single frame). + if (from.totalAlpha == 0 || to.mixDuration == 0) + { + to.mixingFrom = from.mixingFrom; + if (from.mixingFrom != null) from.mixingFrom.mixingTo = to; + to.interruptAlpha = from.interruptAlpha; + queue.End(from); + } + return finished; + } + + from.trackTime += delta * from.timeScale; + to.mixTime += delta; + return false; + } + + /// + /// 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. + /// True if any animations were applied. + public bool Apply(Skeleton skeleton) + { + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + if (animationsChanged) AnimationsChanged(); + + var events = this.events; + bool applied = false; + var tracksItems = tracks.Items; + for (int i = 0, n = tracks.Count; i < n; i++) + { + TrackEntry current = tracksItems[i]; + if (current == null || current.delay > 0) continue; + applied = true; + + // Track 0 animations aren't for layering, so do not show the previously applied animations before the first key. + MixBlend blend = i == 0 ? MixBlend.First : current.mixBlend; + + // Apply mixing from entries first. + float mix = current.alpha; + if (current.mixingFrom != null) + mix *= ApplyMixingFrom(current, skeleton, blend); + else if (current.trackTime >= current.trackEnd && current.next == null) // + mix = 0; // Set to setup pose the last time the entry will be applied. + + // Apply current entry. + float animationLast = current.animationLast, animationTime = current.AnimationTime; + int timelineCount = current.animation.timelines.Count; + var timelines = current.animation.timelines; + var timelinesItems = timelines.Items; + if ((i == 0 && mix == 1) || blend == MixBlend.Add) + { + for (int ii = 0; ii < timelineCount; ii++) + timelinesItems[ii].Apply(skeleton, animationLast, animationTime, events, mix, blend, MixDirection.In); + } + else + { + var timelineMode = current.timelineMode.Items; + + bool firstFrame = current.timelinesRotation.Count == 0; + if (firstFrame) current.timelinesRotation.EnsureCapacity(timelines.Count << 1); + var timelinesRotation = current.timelinesRotation.Items; + + for (int ii = 0; ii < timelineCount; ii++) + { + Timeline timeline = timelinesItems[ii]; + MixBlend timelineBlend = timelineMode[ii] == AnimationState.Subsequent ? blend : MixBlend.Setup; + var rotateTimeline = timeline as RotateTimeline; + if (rotateTimeline != null) + ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, mix, timelineBlend, timelinesRotation, ii << 1, + firstFrame); + else + timeline.Apply(skeleton, animationLast, animationTime, events, mix, timelineBlend, MixDirection.In); + } + } + QueueEvents(current, animationTime); + events.Clear(false); + current.nextAnimationLast = animationTime; + current.nextTrackLast = current.trackTime; + } + + queue.Drain(); + return applied; + } + + private float 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; // Tracks > 0 are transparent and can't reset to setup pose. + } + else + { + mix = to.mixTime / to.mixDuration; + if (mix > 1) mix = 1; + if (blend != MixBlend.First) blend = from.mixBlend; // Track 0 ignores track mix blend. + } + + var eventBuffer = mix < from.eventThreshold ? this.events : null; + bool attachments = mix < from.attachmentThreshold, drawOrder = mix < from.drawOrderThreshold; + float animationLast = from.animationLast, animationTime = from.AnimationTime; + var timelines = from.animation.timelines; + int timelineCount = timelines.Count; + var timelinesItems = timelines.Items; + float alphaHold = from.alpha * to.interruptAlpha, alphaMix = alphaHold * (1 - mix); + + if (blend == MixBlend.Add) + { + for (int i = 0; i < timelineCount; i++) + timelinesItems[i].Apply(skeleton, animationLast, animationTime, eventBuffer, alphaMix, blend, MixDirection.Out); + } + else + { + var timelineMode = from.timelineMode.Items; + var timelineHoldMix = from.timelineHoldMix.Items; + + bool firstFrame = from.timelinesRotation.Count == 0; + if (firstFrame) from.timelinesRotation.Resize(timelines.Count << 1); // from.timelinesRotation.setSize + var timelinesRotation = from.timelinesRotation.Items; + + from.totalAlpha = 0; + for (int i = 0; i < timelineCount; i++) + { + Timeline timeline = timelinesItems[i]; + MixDirection direction = MixDirection.Out; + MixBlend timelineBlend; + float alpha; + switch (timelineMode[i]) + { + case AnimationState.Subsequent: + if (!attachments && timeline is AttachmentTimeline) continue; + if (!drawOrder && timeline is DrawOrderTimeline) continue; + timelineBlend = blend; + alpha = alphaMix; + break; + case AnimationState.First: + timelineBlend = MixBlend.Setup; + alpha = alphaMix; + break; + case AnimationState.Hold: + timelineBlend = MixBlend.Setup; + alpha = alphaHold; + break; + default: + timelineBlend = MixBlend.Setup; + TrackEntry holdMix = timelineHoldMix[i]; + alpha = alphaHold * Math.Max(0, 1 - holdMix.mixTime / holdMix.mixDuration); + break; + } + from.totalAlpha += alpha; + + var rotateTimeline = timeline as RotateTimeline; + if (rotateTimeline != null) + { + ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, alpha, timelineBlend, timelinesRotation, i << 1, + firstFrame); + } + else + { + if (timelineBlend == MixBlend.Setup) + { + if (timeline is AttachmentTimeline) + { + if (attachments) direction = MixDirection.In; + } + else if (timeline is DrawOrderTimeline) + { + if (drawOrder) + { + direction = MixDirection.In; + } + } + } + + timeline.Apply(skeleton, animationLast, animationTime, eventBuffer, alpha, timelineBlend, direction); + } + } + } + + if (to.mixDuration > 0) QueueEvents(from, animationTime); + this.events.Clear(false); + from.nextAnimationLast = animationTime; + from.nextTrackLast = from.trackTime; + + return mix; + } + + static private void ApplyRotateTimeline(RotateTimeline rotateTimeline, Skeleton skeleton, float time, float alpha, MixBlend blend, + float[] timelinesRotation, int 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.Items[rotateTimeline.boneIndex]; + float[] frames = rotateTimeline.frames; + float r1, r2; + if (time < frames[0]) + { // Time is before first frame. + switch (blend) + { + case MixBlend.Setup: + bone.rotation = bone.data.rotation; + return; + default: + return; + case MixBlend.First: + r1 = bone.rotation; + r2 = bone.data.rotation; + break; + } + } + else + { + r1 = blend == MixBlend.Setup ? bone.data.rotation : bone.rotation; + if (time >= frames[frames.Length - RotateTimeline.ENTRIES]) // Time is after last frame. + r2 = bone.data.rotation + frames[frames.Length + RotateTimeline.PREV_ROTATION]; + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, RotateTimeline.ENTRIES); + float prevRotation = frames[frame + RotateTimeline.PREV_ROTATION]; + float frameTime = frames[frame]; + float percent = rotateTimeline.GetCurvePercent((frame >> 1) - 1, + 1 - (time - frameTime) / (frames[frame + RotateTimeline.PREV_TIME] - frameTime)); + + r2 = frames[frame + RotateTimeline.ROTATION] - prevRotation; + r2 -= (16384 - (int)(16384.499999999996 - r2 / 360)) * 360; + r2 = prevRotation + r2 * percent + bone.data.rotation; + r2 -= (16384 - (int)(16384.499999999996 - r2 / 360)) * 360; + } + } + + // Mix between rotations using the direction of the shortest route on the first frame. + float total, diff = r2 - r1; + diff -= (16384 - (int)(16384.499999999996 - diff / 360)) * 360; + if (diff == 0) + { + total = timelinesRotation[i]; + } + else + { + float lastTotal, lastDiff; + if (firstFrame) + { + lastTotal = 0; + lastDiff = diff; + } + else + { + lastTotal = timelinesRotation[i]; // Angle and direction of mix, including loops. + lastDiff = timelinesRotation[i + 1]; // Difference between bones. + } + bool current = diff > 0, dir = lastTotal >= 0; + // Detect cross at 0 (not 180). + if (Math.Sign(lastDiff) != Math.Sign(diff) && Math.Abs(lastDiff) <= 90) + { + // A cross after a 360 rotation is a loop. + if (Math.Abs(lastTotal) > 180) lastTotal += 360 * Math.Sign(lastTotal); + dir = current; + } + total = diff + lastTotal - lastTotal % 360; // Store loops as part of lastTotal. + if (dir != current) total += 360 * Math.Sign(lastTotal); + timelinesRotation[i] = total; + } + timelinesRotation[i + 1] = diff; + r1 += total * alpha; + bone.rotation = r1 - (16384 - (int)(16384.499999999996 - r1 / 360)) * 360; + } + + private void QueueEvents(TrackEntry entry, float animationTime) + { + float animationStart = entry.animationStart, animationEnd = entry.animationEnd; + float duration = animationEnd - animationStart; + float trackLastWrapped = entry.trackLast % duration; + + // Queue events before complete. + var events = this.events; + var eventsItems = events.Items; + int i = 0, n = events.Count; + for (; i < n; i++) + { + Event e = eventsItems[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) + complete = duration == 0 || (trackLastWrapped > entry.trackTime % duration); + else + complete = animationTime >= animationEnd && entry.animationLast < animationEnd; + if (complete) queue.Complete(entry); + + // Queue events after complete. + for (; i < n; i++) + { + Event e = eventsItems[i]; + if (e.time < animationStart) continue; // Discard events outside animation start/end. + queue.Event(entry, eventsItems[i]); + } + } + + /// + /// Removes all animations from all tracks, leaving skeletons in their current pose. + /// + /// It may be desired to use to mix the skeletons back to the setup pose, + /// rather than leaving them in their current pose. + /// + public void ClearTracks() + { + bool oldDrainDisabled = queue.drainDisabled; + queue.drainDisabled = true; + for (int i = 0, n = tracks.Count; i < n; i++) + { + ClearTrack(i); + } + tracks.Clear(); + queue.drainDisabled = oldDrainDisabled; + queue.Drain(); + } + + /// + /// Removes all animations from the track, leaving skeletons in their current pose. + /// + /// It may be desired to use to mix the skeletons back to the setup pose, + /// rather than leaving them in their current pose. + /// + public void ClearTrack(int trackIndex) + { + if (trackIndex >= tracks.Count) return; + TrackEntry current = tracks.Items[trackIndex]; + if (current == null) return; + + queue.End(current); + + DisposeNext(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.Items[current.trackIndex] = null; + + queue.Drain(); + } + + /// Sets the active TrackEntry for a given track number. + private void SetCurrent(int index, TrackEntry current, bool interrupt) + { + TrackEntry from = ExpandToIndex(index); + tracks.Items[index] = current; + + if (from != null) + { + if (interrupt) queue.Interrupt(from); + current.mixingFrom = from; + from.mixingTo = current; + current.mixTime = 0; + + // Store the interrupted mix percentage. + if (from.mixingFrom != null && from.mixDuration > 0) + current.interruptAlpha *= Math.Min(1, from.mixTime / from.mixDuration); + + from.timelinesRotation.Clear(); // Reset rotation for mixing out, in case entry was mixed in. + } + + queue.Start(current); // triggers AnimationsChanged + } + + + /// Sets an animation by name. + public TrackEntry SetAnimation(int trackIndex, string animationName, bool loop) + { + Animation animation = data.skeletonData.FindAnimation(animationName); + if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName"); + return SetAnimation(trackIndex, animation, loop); + } + + /// Sets the current animation for a track, discarding any queued animations. If the formerly current track entry was never + /// applied to a skeleton, it is replaced (not mixed from). + /// 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 determines when the track is cleared. + /// A track entry to allow further customization of animation playback. References to the track entry must not be kept + /// after the event occurs. + public TrackEntry SetAnimation(int trackIndex, Animation animation, bool loop) + { + if (animation == null) throw new ArgumentNullException("animation", "animation cannot be 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.Items[trackIndex] = current.mixingFrom; + queue.Interrupt(current); + queue.End(current); + DisposeNext(current); + current = current.mixingFrom; + interrupt = false; // mixingFrom is current again, but don't interrupt it twice. + } + else + { + DisposeNext(current); + } + } + TrackEntry entry = NewTrackEntry(trackIndex, animation, loop, current); + SetCurrent(trackIndex, entry, interrupt); + queue.Drain(); + return entry; + } + + /// Queues an animation by name. + /// + public TrackEntry AddAnimation(int trackIndex, string animationName, bool loop, float delay) + { + Animation animation = data.skeletonData.FindAnimation(animationName); + if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName"); + return AddAnimation(trackIndex, animation, loop, delay); + } + + /// Adds an animation to be played after the current or last queued animation for a track. If the track is empty, it is + /// equivalent to calling . + /// + /// If > 0, sets . If <= 0, the delay set is the duration of the previous track entry + /// minus any mix duration (from the {@link AnimationStateData}) plus the specified Delay (ie the mix + /// ends at (Delay = 0) or before (Delay < 0) the previous track entry duration). If the + /// previous entry is looping, its next loop completion is used instead of its duration. + /// + /// A track entry to allow further customization of animation playback. References to the track entry must not be kept + /// after the event occurs. + public TrackEntry AddAnimation(int trackIndex, Animation animation, bool loop, float delay) + { + if (animation == null) throw new ArgumentNullException("animation", "animation cannot be 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(); + } + else + { + last.next = entry; + if (delay <= 0) + { + float duration = last.animationEnd - last.animationStart; + if (duration != 0) + { + if (last.loop) + { + delay += duration * (1 + (int)(last.trackTime / duration)); // Completion of next loop. + } + else + { + delay += Math.Max(duration, last.trackTime); // After duration, else next update. + } + delay -= data.GetMix(last.animation, animation); + } + else + delay = last.trackTime; // Next update. + } + } + + entry.delay = delay; + return entry; + } + + /// + /// Sets an empty animation for a track, discarding any queued animations, and sets the track entry's + /// . An empty animation has no timelines and serves as a placeholder for mixing in or out. + /// + /// Mixing out is done by setting an empty animation with a mix duration using either , + /// , or . Mixing to an empty animation causes + /// the previous animation to be applied less and less over the mix duration. Properties keyed in the previous animation + /// transition to the value from lower tracks or to the setup pose value if no lower tracks key the property. A mix duration of + /// 0 still mixes out over one frame. + /// + /// Mixing in is done by first setting an empty animation, then adding an animation using + /// and on the returned track entry, set the + /// . Mixing from an empty animation causes the new animation to be applied more and + /// more over the mix duration. Properties keyed in the new animation transition from the value from lower tracks or from the + /// setup pose value if no lower tracks key the property to the value keyed in the new animation. + /// + public TrackEntry SetEmptyAnimation(int trackIndex, float mixDuration) + { + TrackEntry entry = SetAnimation(trackIndex, AnimationState.EmptyAnimation, false); + entry.mixDuration = mixDuration; + entry.trackEnd = mixDuration; + return entry; + } + + /// + /// Adds an empty animation to be played after the current or last queued animation for a track, and sets the track entry's + /// . If the track is empty, it is equivalent to calling + /// . + /// + /// Track number. + /// Mix duration. + /// If > 0, sets . If <= 0, the delay set is the duration of the previous track entry + /// minus any mix duration plus the specified Delay (ie the mix ends at (Delay = 0) or + /// before (Delay < 0) the previous track entry duration). If the previous entry is looping, its next + /// loop completion is used instead of its duration. + /// A track entry to allow further customization of animation playback. References to the track entry must not be kept + /// after the event occurs. + /// + public TrackEntry AddEmptyAnimation(int trackIndex, float mixDuration, float delay) + { + if (delay <= 0) delay -= mixDuration; + TrackEntry entry = AddAnimation(trackIndex, AnimationState.EmptyAnimation, false, delay); + entry.mixDuration = mixDuration; + entry.trackEnd = mixDuration; + return entry; + } + + /// + /// Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix + /// duration. + public void SetEmptyAnimations(float mixDuration) + { + bool oldDrainDisabled = queue.drainDisabled; + queue.drainDisabled = true; + for (int i = 0, n = tracks.Count; i < n; i++) + { + TrackEntry current = tracks.Items[i]; + if (current != null) SetEmptyAnimation(current.trackIndex, mixDuration); + } + queue.drainDisabled = oldDrainDisabled; + queue.Drain(); + } + + private TrackEntry ExpandToIndex(int index) + { + if (index < tracks.Count) return tracks.Items[index]; + tracks.Resize(index + 1); + return null; + } + + /// Object-pooling version of new TrackEntry. Obtain an unused TrackEntry from the pool and clear/initialize its values. + /// May be null. + private TrackEntry NewTrackEntry(int trackIndex, Animation animation, bool loop, TrackEntry last) + { + TrackEntry entry = trackEntryPool.Obtain(); // Pooling + entry.trackIndex = trackIndex; + entry.animation = animation; + entry.loop = loop; + entry.holdPrevious = false; + + entry.eventThreshold = 0; + entry.attachmentThreshold = 0; + entry.drawOrderThreshold = 0; + + entry.animationStart = 0; + entry.animationEnd = animation.Duration; + 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 = float.MaxValue; // loop ? float.MaxValue : animation.Duration; + entry.timeScale = 1; + + entry.alpha = 1; + entry.interruptAlpha = 1; + entry.mixTime = 0; + entry.mixDuration = (last == null) ? 0 : data.GetMix(last.animation, animation); + return entry; + } + + /// Dispose all track entries queued after the given TrackEntry. + private void DisposeNext(TrackEntry entry) + { + TrackEntry next = entry.next; + while (next != null) + { + queue.Dispose(next); + next = next.next; + } + entry.next = null; + } + + private void AnimationsChanged() + { + animationsChanged = false; + + propertyIDs.Clear(); + + var tracksItems = tracks.Items; + for (int i = 0, n = tracks.Count; i < n; i++) + { + TrackEntry entry = tracksItems[i]; + if (entry == null) continue; + // Move to last entry, then iterate in reverse (the order animations are applied). + while (entry.mixingFrom != null) + entry = entry.mixingFrom; + + do + { + if (entry.mixingTo == null || entry.mixBlend != MixBlend.Add) SetTimelineModes(entry); + entry = entry.mixingTo; + } while (entry != null); + + } + } + + private void SetTimelineModes(TrackEntry entry) + { + TrackEntry to = entry.mixingTo; + var timelines = entry.animation.timelines.Items; + int timelinesCount = entry.animation.timelines.Count; + var timelineMode = entry.timelineMode.Resize(timelinesCount).Items; //timelineMode.setSize(timelinesCount); + entry.timelineHoldMix.Clear(); + var timelineHoldMix = entry.timelineHoldMix.Resize(timelinesCount).Items; //timelineHoldMix.setSize(timelinesCount); + var propertyIDs = this.propertyIDs; + + if (to != null && to.holdPrevious) + { + for (int i = 0; i < timelinesCount; i++) + { + propertyIDs.Add(timelines[i].PropertyId); + timelineMode[i] = AnimationState.Hold; + } + return; + } + + // outer: + for (int i = 0; i < timelinesCount; i++) + { + int id = timelines[i].PropertyId; + if (!propertyIDs.Add(id)) + timelineMode[i] = AnimationState.Subsequent; + else if (to == null || !HasTimeline(to, id)) + timelineMode[i] = AnimationState.First; + else + { + for (TrackEntry next = to.mixingTo; next != null; next = next.mixingTo) + { + if (HasTimeline(next, id)) continue; + if (next.mixDuration > 0) + { + timelineMode[i] = AnimationState.HoldMix; + timelineHoldMix[i] = next; + goto continue_outer; // continue outer; + } + break; + } + timelineMode[i] = AnimationState.Hold; + } + continue_outer: { } + } + } + + static bool HasTimeline(TrackEntry entry, int id) + { + var timelines = entry.animation.timelines.Items; + for (int i = 0, n = entry.animation.timelines.Count; i < n; i++) + if (timelines[i].PropertyId == id) return true; + return false; + } + + /// The track entry for the animation currently playing on the track, or null if no animation is currently playing. + public TrackEntry GetCurrent(int trackIndex) + { + if (trackIndex >= tracks.Count) return null; + return tracks.Items[trackIndex]; + } + + /// Discards all listener notifications that have not yet been delivered. This can be useful to call from an + /// AnimationState event subscriber when it is known that further notifications that may have been already queued for delivery + /// are not wanted because new animations are being set. + public void ClearListenerNotifications() + { + queue.Clear(); + } + + /// + /// Multiplier for the delta time when the animation state is updated, causing time for all animations and mixes to play slower + /// or faster. Defaults to 1. + /// + /// See TrackEntry for affecting a single animation. + /// + public float TimeScale { get { return timeScale; } set { timeScale = value; } } + + /// The AnimationStateData to look up mix durations. + public AnimationStateData Data + { + get + { + return data; + } + set + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + this.data = value; + } + } + + /// A list of tracks that have animations, which may contain nulls. + public ExposedList Tracks { get { return tracks; } } + + override public string ToString() + { + var buffer = new System.Text.StringBuilder(); + for (int i = 0, n = tracks.Count; i < n; i++) + { + TrackEntry entry = tracks.Items[i]; + if (entry == null) continue; + if (buffer.Length > 0) buffer.Append(", "); + buffer.Append(entry.ToString()); + } + if (buffer.Length == 0) return ""; + return buffer.ToString(); + } + } + + /// + /// + /// Stores settings and other state for the playback of an animation on an track. + /// + /// References to a track entry must not be kept after the event occurs. + /// + public class TrackEntry : Pool.IPoolable + { + internal Animation animation; + + internal TrackEntry next, mixingFrom, mixingTo; + // difference to libgdx reference: delegates are used for event callbacks instead of 'AnimationStateListener listener'. + public event AnimationState.TrackEntryDelegate Start, Interrupt, End, Dispose, Complete; + public event AnimationState.TrackEntryEventDelegate Event; + internal void OnStart() { if (Start != null) Start(this); } + internal void OnInterrupt() { if (Interrupt != null) Interrupt(this); } + internal void OnEnd() { if (End != null) End(this); } + internal void OnDispose() { if (Dispose != null) Dispose(this); } + internal void OnComplete() { if (Complete != null) Complete(this); } + internal void OnEvent(Event e) { if (Event != null) Event(this, e); } + + internal int trackIndex; + + internal bool loop, holdPrevious; + internal float eventThreshold, attachmentThreshold, drawOrderThreshold; + internal float animationStart, animationEnd, animationLast, nextAnimationLast; + internal float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale = 1f; + internal float alpha, mixTime, mixDuration, interruptAlpha, totalAlpha; + internal MixBlend mixBlend = MixBlend.Replace; + internal readonly ExposedList timelineMode = new ExposedList(); + internal readonly ExposedList timelineHoldMix = new ExposedList(); + internal readonly ExposedList timelinesRotation = new ExposedList(); + + // IPoolable.Reset() + public void Reset() + { + next = null; + mixingFrom = null; + mixingTo = null; + animation = null; + // replaces 'listener = null;' since delegates are used for event callbacks + Start = null; + Interrupt = null; + End = null; + Dispose = null; + Complete = null; + Event = null; + timelineMode.Clear(); + timelineHoldMix.Clear(); + timelinesRotation.Clear(); + } + + /// The index of the track where this entry is either current or queued. + /// + public int TrackIndex { get { return trackIndex; } } + + /// The animation to apply for this track entry. + public Animation Animation { get { return animation; } } + + /// + /// If true, the animation will repeat. If false it will not, instead its last frame is applied if played beyond its + /// duration. + public bool Loop { get { return loop; } set { loop = value; } } + + /// + /// + /// Seconds to postpone playing the animation. When this track entry is the current track entry, Delay + /// postpones incrementing the . When this track entry is queued, Delay is the time from + /// the start of the previous animation to when this track entry will become the current track entry (ie when the previous + /// track entry >= this track entry's Delay). + /// + /// affects the delay. + /// + public float Delay { get { return delay; } set { delay = value; } } + + /// + /// Current time in seconds this track entry has been the current track entry. The track time determines + /// . The track time can be set to start the animation at a time other than 0, without affecting + /// looping. + public float TrackTime { get { return trackTime; } set { trackTime = value; } } + + /// + /// + /// The track time in seconds when this animation will be removed from the track. Defaults to the highest possible float + /// value, meaning the animation will be applied until a new animation is set or the track is cleared. If the track end time + /// is reached, no other animations are queued for playback, and mixing from any previous animations is complete, then the + /// properties keyed by the animation are set to the setup pose and the track is cleared. + /// + /// It may be desired to use rather than have the animation + /// abruptly cease being applied. + /// + public float TrackEnd { get { return trackEnd; } set { trackEnd = value; } } + + /// + /// + /// Seconds when this animation starts, both initially and after looping. Defaults to 0. + /// + /// When changing the AnimationStart time, it often makes sense to set to the same + /// value to prevent timeline keys before the start time from triggering. + /// + public float AnimationStart { get { return animationStart; } set { animationStart = value; } } + + /// + /// Seconds for the last frame of this animation. Non-looping animations won't play past this time. Looping animations will + /// loop back to at this time. Defaults to the animation . + /// + public float AnimationEnd { get { return animationEnd; } set { animationEnd = value; } } + + /// + /// 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 AnimationLast time (exclusive) and + /// AnimationTime (inclusive). Defaults to -1 to ensure triggers on frame 0 happen the first time this animation + /// is applied. + public float AnimationLast + { + get { return animationLast; } + set + { + animationLast = value; + nextAnimationLast = value; + } + } + + /// + /// Uses to compute the AnimationTime, which is between + /// and . When the TrackTime is 0, the AnimationTime is equal to the + /// AnimationStart time. + /// + public float AnimationTime + { + get + { + if (loop) + { + float duration = animationEnd - animationStart; + if (duration == 0) return animationStart; + return (trackTime % duration) + animationStart; + } + return Math.Min(trackTime + animationStart, animationEnd); + } + } + + /// + /// + /// Multiplier for the delta time when this track entry is updated, causing time for this animation to pass slower or + /// faster. Defaults to 1. + /// + /// is not affected by track entry time scale, so may need to be adjusted to + /// match the animation speed. + /// + /// When using with a Delay <= 0, note the + /// { is set using the mix duration from the , assuming time scale to be 1. If + /// the time scale is not 1, the delay may need to be adjusted. + /// + /// See AnimationState for affecting all animations. + /// + public float TimeScale { get { return timeScale; } set { timeScale = value; } } + + /// + /// + /// Values < 1 mix this animation with the skeleton's current pose (usually the pose resulting from lower tracks). Defaults + /// to 1, which overwrites the skeleton's current pose with this animation. + /// + /// Typically track 0 is used to completely pose the skeleton, then alpha is 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. + /// + public float Alpha { get { return alpha; } set { alpha = value; } } + + /// + /// When the mix percentage ( / ) is less than the + /// EventThreshold, event timelines are applied while this animation is being mixed out. Defaults to 0, so event + /// timelines are not applied while this animation is being mixed out. + /// + public float EventThreshold { get { return eventThreshold; } set { eventThreshold = value; } } + + /// + /// When the mix percentage ( / ) is less than the + /// AttachmentThreshold, attachment timelines are applied while this animation is being mixed out. Defaults to + /// 0, so attachment timelines are not applied while this animation is being mixed out. + /// + public float AttachmentThreshold { get { return attachmentThreshold; } set { attachmentThreshold = value; } } + + /// + /// When the mix percentage ( / ) is less than the + /// DrawOrderThreshold, draw order timelines are applied while this animation is being mixed out. Defaults to 0, + /// so draw order timelines are not applied while this animation is being mixed out. + /// + public float DrawOrderThreshold { get { return drawOrderThreshold; } set { drawOrderThreshold = value; } } + + /// + /// The animation queued to start after this animation, or null. Next makes up a linked list. + public TrackEntry Next { get { return next; } } + + /// + /// Returns true if at least one loop has been completed. + /// + public bool IsComplete + { + get { return trackTime >= animationEnd - animationStart; } + } + + /// + /// Seconds from 0 to the when mixing from the previous animation to this animation. May be + /// slightly more than MixDuration when the mix is complete. + public float MixTime { get { return mixTime; } set { mixTime = value; } } + + /// + /// + /// 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 MixDuration can be set manually rather than use the value from + /// . In that case, the MixDuration can be set for a new + /// track entry only before is first called. + /// + /// When using with a Delay <= 0, note the + /// is set using the mix duration from the , not a mix duration set + /// afterward. + /// + public float MixDuration { get { return mixDuration; } set { mixDuration = value; } } + + /// + /// + /// Controls how properties keyed in the animation are mixed with lower tracks. Defaults to , which + /// replaces the values from the lower tracks with the animation values. adds the animation values to + /// the values from the lower tracks. + /// + /// The MixBlend can be set for a new track entry only before is first + /// called. + /// + public MixBlend MixBlend { get { return mixBlend; } set { mixBlend = value; } } + + /// + /// 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 linked list. + public TrackEntry MixingFrom { get { return mixingFrom; } } + + /// + /// The track entry for the next animation when mixing from this animation to the next animation, or null if no mixing is + /// currently occuring. When mixing to multiple animations, MixingTo makes up a linked list. + public TrackEntry MixingTo { get { return mixingTo; } } + + /// + /// + /// 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. + /// + public bool HoldPrevious { get { return holdPrevious; } set { holdPrevious = value; } } + + /// + /// + /// 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 and starting animations on other tracks. + /// + /// Mixing with 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. + /// + public void ResetRotationDirections() + { + timelinesRotation.Clear(); + } + + override public string ToString() + { + return animation == null ? "" : animation.name; + } + } + + class EventQueue + { + private readonly List eventQueueEntries = new List(); + internal bool drainDisabled; + + private readonly AnimationState state; + private readonly Pool trackEntryPool; + internal event Action AnimationsChanged; + + internal EventQueue(AnimationState state, Action HandleAnimationsChanged, Pool trackEntryPool) + { + this.state = state; + this.AnimationsChanged += HandleAnimationsChanged; + this.trackEntryPool = trackEntryPool; + } + + struct EventQueueEntry + { + public EventType type; + public TrackEntry entry; + public Event e; + + public EventQueueEntry(EventType eventType, TrackEntry trackEntry, Event e = null) + { + this.type = eventType; + this.entry = trackEntry; + this.e = e; + } + } + + enum EventType + { + Start, Interrupt, End, Dispose, Complete, Event + } + + internal void Start(TrackEntry entry) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.Start, entry)); + if (AnimationsChanged != null) AnimationsChanged(); + } + + internal void Interrupt(TrackEntry entry) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.Interrupt, entry)); + } + + internal void End(TrackEntry entry) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.End, entry)); + if (AnimationsChanged != null) AnimationsChanged(); + } + + internal void Dispose(TrackEntry entry) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.Dispose, entry)); + } + + internal void Complete(TrackEntry entry) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.Complete, entry)); + } + + internal void Event(TrackEntry entry, Event e) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.Event, entry, e)); + } + + /// Raises all events in the queue and drains the queue. + internal void Drain() + { + if (drainDisabled) return; + drainDisabled = true; + + var entries = this.eventQueueEntries; + AnimationState state = this.state; + + // Don't cache entries.Count so callbacks can queue their own events (eg, call SetAnimation in AnimationState_Complete). + for (int i = 0; i < entries.Count; i++) + { + var queueEntry = entries[i]; + TrackEntry trackEntry = queueEntry.entry; + + switch (queueEntry.type) + { + case EventType.Start: + trackEntry.OnStart(); + state.OnStart(trackEntry); + break; + case EventType.Interrupt: + trackEntry.OnInterrupt(); + state.OnInterrupt(trackEntry); + break; + case EventType.End: + trackEntry.OnEnd(); + state.OnEnd(trackEntry); + goto case EventType.Dispose; // Fall through. (C#) + case EventType.Dispose: + trackEntry.OnDispose(); + state.OnDispose(trackEntry); + trackEntryPool.Free(trackEntry); // Pooling + break; + case EventType.Complete: + trackEntry.OnComplete(); + state.OnComplete(trackEntry); + break; + case EventType.Event: + trackEntry.OnEvent(queueEntry.e); + state.OnEvent(trackEntry, queueEntry.e); + break; + } + } + eventQueueEntries.Clear(); + + drainDisabled = false; + } + + internal void Clear() + { + eventQueueEntries.Clear(); + } + } + + public class Pool where T : class, new() + { + public readonly int max; + readonly Stack freeObjects; + + public int Count { get { return freeObjects.Count; } } + public int Peak { get; private set; } + + public Pool(int initialCapacity = 16, int max = int.MaxValue) + { + freeObjects = new Stack(initialCapacity); + this.max = max; + } + + public T Obtain() + { + return freeObjects.Count == 0 ? new T() : freeObjects.Pop(); + } + + public void Free(T obj) + { + if (obj == null) throw new ArgumentNullException("obj", "obj cannot be null"); + if (freeObjects.Count < max) + { + freeObjects.Push(obj); + Peak = Math.Max(Peak, freeObjects.Count); + } + Reset(obj); + } + + // protected void FreeAll (List objects) { + // if (objects == null) throw new ArgumentNullException("objects", "objects cannot be null."); + // var freeObjects = this.freeObjects; + // int max = this.max; + // for (int i = 0; i < objects.Count; i++) { + // T obj = objects[i]; + // if (obj == null) continue; + // if (freeObjects.Count < max) freeObjects.Push(obj); + // Reset(obj); + // } + // Peak = Math.Max(Peak, freeObjects.Count); + // } + + public void Clear() + { + freeObjects.Clear(); + } + + protected void Reset(T obj) + { + var poolable = obj as IPoolable; + if (poolable != null) poolable.Reset(); + } + + public interface IPoolable + { + void Reset(); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/AnimationStateData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/AnimationStateData.cs index 9ca8b38..5b5a903 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/AnimationStateData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/AnimationStateData.cs @@ -30,85 +30,97 @@ using System; using System.Collections.Generic; -namespace Spine3_7_94 { +namespace Spine3_7_94 +{ - /// Stores mix (crossfade) durations to be applied when AnimationState animations are changed. - public class AnimationStateData { - internal SkeletonData skeletonData; - readonly Dictionary animationToMixTime = new Dictionary(AnimationPairComparer.Instance); - internal float defaultMix; + /// Stores mix (crossfade) durations to be applied when AnimationState animations are changed. + public class AnimationStateData + { + internal SkeletonData skeletonData; + readonly Dictionary animationToMixTime = new Dictionary(AnimationPairComparer.Instance); + internal float defaultMix; - /// The SkeletonData to look up animations when they are specified by name. - public SkeletonData SkeletonData { get { return skeletonData; } } + /// The SkeletonData to look up animations when they are specified by name. + public SkeletonData SkeletonData { get { return skeletonData; } } - /// - /// The mix duration to use when no mix duration has been specifically defined between two animations. - public float DefaultMix { get { return defaultMix; } set { defaultMix = value; } } + /// + /// The mix duration to use when no mix duration has been specifically defined between two animations. + public float DefaultMix { get { return defaultMix; } set { defaultMix = value; } } - public AnimationStateData (SkeletonData skeletonData) { - if (skeletonData == null) throw new ArgumentException("skeletonData cannot be null.", "skeletonData"); - this.skeletonData = skeletonData; - } + public AnimationStateData(SkeletonData skeletonData) + { + if (skeletonData == null) throw new ArgumentException("skeletonData cannot be null.", "skeletonData"); + this.skeletonData = skeletonData; + } - /// Sets a mix duration by animation names. - public void SetMix (string fromName, string toName, float duration) { - Animation from = skeletonData.FindAnimation(fromName); - if (from == null) throw new ArgumentException("Animation not found: " + fromName, "fromName"); - Animation to = skeletonData.FindAnimation(toName); - if (to == null) throw new ArgumentException("Animation not found: " + toName, "toName"); - SetMix(from, to, duration); - } + /// Sets a mix duration by animation names. + public void SetMix(string fromName, string toName, float duration) + { + Animation from = skeletonData.FindAnimation(fromName); + if (from == null) throw new ArgumentException("Animation not found: " + fromName, "fromName"); + Animation to = skeletonData.FindAnimation(toName); + if (to == null) throw new ArgumentException("Animation not found: " + toName, "toName"); + SetMix(from, to, duration); + } - /// Sets a mix duration when changing from the specified animation to the other. - /// See TrackEntry.MixDuration. - public void SetMix (Animation from, Animation to, float duration) { - if (from == null) throw new ArgumentNullException("from", "from cannot be null."); - if (to == null) throw new ArgumentNullException("to", "to cannot be null."); - AnimationPair key = new AnimationPair(from, to); - animationToMixTime.Remove(key); - animationToMixTime.Add(key, duration); - } + /// Sets a mix duration when changing from the specified animation to the other. + /// See TrackEntry.MixDuration. + public void SetMix(Animation from, Animation to, float duration) + { + if (from == null) throw new ArgumentNullException("from", "from cannot be null."); + if (to == null) throw new ArgumentNullException("to", "to cannot be null."); + AnimationPair key = new AnimationPair(from, to); + animationToMixTime.Remove(key); + animationToMixTime.Add(key, 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. - /// - public float GetMix (Animation from, Animation to) { - if (from == null) throw new ArgumentNullException("from", "from cannot be null."); - if (to == null) throw new ArgumentNullException("to", "to cannot be null."); - AnimationPair key = new AnimationPair(from, to); - float duration; - if (animationToMixTime.TryGetValue(key, out duration)) return duration; - return defaultMix; - } + /// + /// The mix duration to use when changing from the specified animation to the other, + /// or the DefaultMix if no mix duration has been set. + /// + public float GetMix(Animation from, Animation to) + { + if (from == null) throw new ArgumentNullException("from", "from cannot be null."); + if (to == null) throw new ArgumentNullException("to", "to cannot be null."); + AnimationPair key = new AnimationPair(from, to); + float duration; + if (animationToMixTime.TryGetValue(key, out duration)) return duration; + return defaultMix; + } - public struct AnimationPair { - public readonly Animation a1; - public readonly Animation a2; + public struct AnimationPair + { + public readonly Animation a1; + public readonly Animation a2; - public AnimationPair (Animation a1, Animation a2) { - this.a1 = a1; - this.a2 = a2; - } + public AnimationPair(Animation a1, Animation a2) + { + this.a1 = a1; + this.a2 = a2; + } - public override string ToString () { - return a1.name + "->" + a2.name; - } - } + public override string ToString() + { + return a1.name + "->" + a2.name; + } + } - // Avoids boxing in the dictionary. - public class AnimationPairComparer : IEqualityComparer { - public static readonly AnimationPairComparer Instance = new AnimationPairComparer(); + // Avoids boxing in the dictionary. + public class AnimationPairComparer : IEqualityComparer + { + public static readonly AnimationPairComparer Instance = new AnimationPairComparer(); - bool IEqualityComparer.Equals (AnimationPair x, AnimationPair y) { - return ReferenceEquals(x.a1, y.a1) && ReferenceEquals(x.a2, y.a2); - } + bool IEqualityComparer.Equals(AnimationPair x, AnimationPair y) + { + return ReferenceEquals(x.a1, y.a1) && ReferenceEquals(x.a2, y.a2); + } - int IEqualityComparer.GetHashCode (AnimationPair obj) { - // from Tuple.CombineHashCodes // return (((h1 << 5) + h1) ^ h2); - int h1 = obj.a1.GetHashCode(); - return (((h1 << 5) + h1) ^ obj.a2.GetHashCode()); - } - } - } + int IEqualityComparer.GetHashCode(AnimationPair obj) + { + // from Tuple.CombineHashCodes // return (((h1 << 5) + h1) ^ h2); + int h1 = obj.a1.GetHashCode(); + return (((h1 << 5) + h1) ^ obj.a2.GetHashCode()); + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Atlas.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Atlas.cs index a6026f6..22c31c3 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Atlas.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Atlas.cs @@ -35,31 +35,34 @@ using System.Collections.Generic; using System.Globalization; using System.IO; -using System.Reflection; #if WINDOWS_STOREAPP using System.Threading.Tasks; using Windows.Storage; #endif -namespace Spine3_7_94 { - public class Atlas : IEnumerable { - readonly List pages = new List(); - List regions = new List(); - TextureLoader textureLoader; - - #region IEnumerable implementation - public IEnumerator GetEnumerator () { - return regions.GetEnumerator(); - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () { - return regions.GetEnumerator(); - } - #endregion - - #if !(IS_UNITY) - #if WINDOWS_STOREAPP +namespace Spine3_7_94 +{ + public class Atlas : IEnumerable + { + readonly List pages = new List(); + List regions = new List(); + TextureLoader textureLoader; + + #region IEnumerable implementation + public IEnumerator GetEnumerator() + { + return regions.GetEnumerator(); + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return regions.GetEnumerator(); + } + #endregion + +#if !(IS_UNITY) +#if WINDOWS_STOREAPP private async Task ReadFile(string path, TextureLoader textureLoader) { var folder = Windows.ApplicationModel.Package.Current.InstalledLocation; var file = await folder.GetFileAsync(path).AsTask().ConfigureAwait(false); @@ -75,245 +78,278 @@ private async Task ReadFile(string path, TextureLoader textureLoader) { public Atlas(string path, TextureLoader textureLoader) { this.ReadFile(path, textureLoader).Wait(); } - #else +#else - public Atlas (string path, TextureLoader textureLoader) { + public Atlas(string path, TextureLoader textureLoader) + { - #if WINDOWS_PHONE +#if WINDOWS_PHONE Stream stream = Microsoft.Xna.Framework.TitleContainer.OpenStream(path); using (StreamReader reader = new StreamReader(stream)) { - #else - using (StreamReader reader = new StreamReader(path)) { - #endif // WINDOWS_PHONE - - try { - Load(reader, Path.GetDirectoryName(path), textureLoader); - } catch (Exception ex) { - throw new Exception("Error reading atlas file: " + path, ex); - } - - } - } - #endif // WINDOWS_STOREAPP - - #endif - - public Atlas (TextReader reader, string dir, TextureLoader textureLoader) { - Load(reader, dir, textureLoader); - } - - public Atlas (List pages, List regions) { - this.pages = pages; - this.regions = regions; - this.textureLoader = null; - } - - private void Load (TextReader reader, string imagesDir, TextureLoader textureLoader) { - if (textureLoader == null) throw new ArgumentNullException("textureLoader", "textureLoader cannot be null."); - this.textureLoader = textureLoader; - - string[] tuple = new string[4]; - AtlasPage page = null; - while (true) { - string line = reader.ReadLine(); - if (line == null) break; - if (line.Trim().Length == 0) - page = null; - else if (page == null) { - page = new AtlasPage(); - page.name = line; - - if (ReadTuple(reader, tuple) == 2) { // size is only optional for an atlas packed with an old TexturePacker. - page.width = int.Parse(tuple[0], CultureInfo.InvariantCulture); - page.height = int.Parse(tuple[1], CultureInfo.InvariantCulture); - ReadTuple(reader, tuple); - } - page.format = (Format)Enum.Parse(typeof(Format), tuple[0], false); - - ReadTuple(reader, tuple); - page.minFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[0], false); - page.magFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[1], false); - - string direction = ReadValue(reader); - page.uWrap = TextureWrap.ClampToEdge; - page.vWrap = TextureWrap.ClampToEdge; - if (direction == "x") - page.uWrap = TextureWrap.Repeat; - else if (direction == "y") - page.vWrap = TextureWrap.Repeat; - else if (direction == "xy") - page.uWrap = page.vWrap = TextureWrap.Repeat; - - textureLoader.Load(page, Path.Combine(imagesDir, line)); - - pages.Add(page); - - } else { - AtlasRegion region = new AtlasRegion(); - region.name = line; - region.page = page; - - region.rotate = Boolean.Parse(ReadValue(reader)); - - ReadTuple(reader, tuple); - int x = int.Parse(tuple[0], CultureInfo.InvariantCulture); - int y = int.Parse(tuple[1], CultureInfo.InvariantCulture); - - ReadTuple(reader, tuple); - int width = int.Parse(tuple[0], CultureInfo.InvariantCulture); - int height = int.Parse(tuple[1], CultureInfo.InvariantCulture); - - region.u = x / (float)page.width; - region.v = y / (float)page.height; - if (region.rotate) { - region.u2 = (x + height) / (float)page.width; - region.v2 = (y + width) / (float)page.height; - } else { - region.u2 = (x + width) / (float)page.width; - region.v2 = (y + height) / (float)page.height; - } - region.x = x; - region.y = y; - region.width = Math.Abs(width); - region.height = Math.Abs(height); - - if (ReadTuple(reader, tuple) == 4) { // split is optional - region.splits = new [] {int.Parse(tuple[0], CultureInfo.InvariantCulture), - int.Parse(tuple[1], CultureInfo.InvariantCulture), - int.Parse(tuple[2], CultureInfo.InvariantCulture), - int.Parse(tuple[3], CultureInfo.InvariantCulture)}; - - if (ReadTuple(reader, tuple) == 4) { // pad is optional, but only present with splits - region.pads = new [] {int.Parse(tuple[0], CultureInfo.InvariantCulture), - int.Parse(tuple[1], CultureInfo.InvariantCulture), - int.Parse(tuple[2], CultureInfo.InvariantCulture), - int.Parse(tuple[3], CultureInfo.InvariantCulture)}; - - ReadTuple(reader, tuple); - } - } - - region.originalWidth = int.Parse(tuple[0], CultureInfo.InvariantCulture); - region.originalHeight = int.Parse(tuple[1], CultureInfo.InvariantCulture); - - ReadTuple(reader, tuple); - region.offsetX = int.Parse(tuple[0], CultureInfo.InvariantCulture); - region.offsetY = int.Parse(tuple[1], CultureInfo.InvariantCulture); - - region.index = int.Parse(ReadValue(reader), CultureInfo.InvariantCulture); - - regions.Add(region); - } - } - } - - static string ReadValue (TextReader reader) { - string line = reader.ReadLine(); - int colon = line.IndexOf(':'); - if (colon == -1) throw new Exception("Invalid line: " + line); - return line.Substring(colon + 1).Trim(); - } - - /// Returns the number of tuple values read (1, 2 or 4). - static int ReadTuple (TextReader reader, string[] tuple) { - string line = reader.ReadLine(); - int colon = line.IndexOf(':'); - if (colon == -1) throw new Exception("Invalid line: " + line); - int i = 0, lastMatch = colon + 1; - for (; i < 3; i++) { - int comma = line.IndexOf(',', lastMatch); - if (comma == -1) break; - tuple[i] = line.Substring(lastMatch, comma - lastMatch).Trim(); - lastMatch = comma + 1; - } - tuple[i] = line.Substring(lastMatch).Trim(); - return i + 1; - } +#else + using (StreamReader reader = new StreamReader(path)) + { +#endif // WINDOWS_PHONE + + try + { + Load(reader, Path.GetDirectoryName(path), textureLoader); + } + catch (Exception ex) + { + throw new Exception("Error reading atlas file: " + path, ex); + } + + } + } +#endif // WINDOWS_STOREAPP - public void FlipV () { - for (int i = 0, n = regions.Count; i < n; i++) { - AtlasRegion region = regions[i]; - region.v = 1 - region.v; - region.v2 = 1 - region.v2; - } - } - - /// 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. - /// The region, or null. - public AtlasRegion FindRegion (string name) { - for (int i = 0, n = regions.Count; i < n; i++) - if (regions[i].name == name) return regions[i]; - return null; - } - - public void Dispose () { - if (textureLoader == null) return; - for (int i = 0, n = pages.Count; i < n; i++) - textureLoader.Unload(pages[i].rendererObject); - } - } - - public enum Format { - Alpha, - Intensity, - LuminanceAlpha, - RGB565, - RGBA4444, - RGB888, - RGBA8888 - } - - public enum TextureFilter { - Nearest, - Linear, - MipMap, - MipMapNearestNearest, - MipMapLinearNearest, - MipMapNearestLinear, - MipMapLinearLinear - } - - public enum TextureWrap { - MirroredRepeat, - ClampToEdge, - Repeat - } - - public class AtlasPage { - public string name; - public Format format; - public TextureFilter minFilter; - public TextureFilter magFilter; - public TextureWrap uWrap; - public TextureWrap vWrap; - public object rendererObject; - public int width, height; - - public AtlasPage Clone () { - return MemberwiseClone() as AtlasPage; - } - } - - public class AtlasRegion { - public AtlasPage page; - public string name; - public int x, y, width, height; - public float u, v, u2, v2; - public float offsetX, offsetY; - public int originalWidth, originalHeight; - public int index; - public bool rotate; - public int[] splits; - public int[] pads; - - public AtlasRegion Clone () { - return MemberwiseClone() as AtlasRegion; - } - } +#endif - public interface TextureLoader { - void Load (AtlasPage page, string path); - void Unload (Object texture); - } + public Atlas(TextReader reader, string dir, TextureLoader textureLoader) + { + Load(reader, dir, textureLoader); + } + + public Atlas(List pages, List regions) + { + this.pages = pages; + this.regions = regions; + this.textureLoader = null; + } + + private void Load(TextReader reader, string imagesDir, TextureLoader textureLoader) + { + if (textureLoader == null) throw new ArgumentNullException("textureLoader", "textureLoader cannot be null."); + this.textureLoader = textureLoader; + + string[] tuple = new string[4]; + AtlasPage page = null; + while (true) + { + string line = reader.ReadLine(); + if (line == null) break; + if (line.Trim().Length == 0) + page = null; + else if (page == null) + { + page = new AtlasPage(); + page.name = line; + + if (ReadTuple(reader, tuple) == 2) + { // size is only optional for an atlas packed with an old TexturePacker. + page.width = int.Parse(tuple[0], CultureInfo.InvariantCulture); + page.height = int.Parse(tuple[1], CultureInfo.InvariantCulture); + ReadTuple(reader, tuple); + } + page.format = (Format)Enum.Parse(typeof(Format), tuple[0], false); + + ReadTuple(reader, tuple); + page.minFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[0], false); + page.magFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[1], false); + + string direction = ReadValue(reader); + page.uWrap = TextureWrap.ClampToEdge; + page.vWrap = TextureWrap.ClampToEdge; + if (direction == "x") + page.uWrap = TextureWrap.Repeat; + else if (direction == "y") + page.vWrap = TextureWrap.Repeat; + else if (direction == "xy") + page.uWrap = page.vWrap = TextureWrap.Repeat; + + textureLoader.Load(page, Path.Combine(imagesDir, line)); + + pages.Add(page); + + } + else + { + AtlasRegion region = new AtlasRegion(); + region.name = line; + region.page = page; + + region.rotate = Boolean.Parse(ReadValue(reader)); + + ReadTuple(reader, tuple); + int x = int.Parse(tuple[0], CultureInfo.InvariantCulture); + int y = int.Parse(tuple[1], CultureInfo.InvariantCulture); + + ReadTuple(reader, tuple); + int width = int.Parse(tuple[0], CultureInfo.InvariantCulture); + int height = int.Parse(tuple[1], CultureInfo.InvariantCulture); + + region.u = x / (float)page.width; + region.v = y / (float)page.height; + if (region.rotate) + { + region.u2 = (x + height) / (float)page.width; + region.v2 = (y + width) / (float)page.height; + } + else + { + region.u2 = (x + width) / (float)page.width; + region.v2 = (y + height) / (float)page.height; + } + region.x = x; + region.y = y; + region.width = Math.Abs(width); + region.height = Math.Abs(height); + + if (ReadTuple(reader, tuple) == 4) + { // split is optional + region.splits = new[] {int.Parse(tuple[0], CultureInfo.InvariantCulture), + int.Parse(tuple[1], CultureInfo.InvariantCulture), + int.Parse(tuple[2], CultureInfo.InvariantCulture), + int.Parse(tuple[3], CultureInfo.InvariantCulture)}; + + if (ReadTuple(reader, tuple) == 4) + { // pad is optional, but only present with splits + region.pads = new[] {int.Parse(tuple[0], CultureInfo.InvariantCulture), + int.Parse(tuple[1], CultureInfo.InvariantCulture), + int.Parse(tuple[2], CultureInfo.InvariantCulture), + int.Parse(tuple[3], CultureInfo.InvariantCulture)}; + + ReadTuple(reader, tuple); + } + } + + region.originalWidth = int.Parse(tuple[0], CultureInfo.InvariantCulture); + region.originalHeight = int.Parse(tuple[1], CultureInfo.InvariantCulture); + + ReadTuple(reader, tuple); + region.offsetX = int.Parse(tuple[0], CultureInfo.InvariantCulture); + region.offsetY = int.Parse(tuple[1], CultureInfo.InvariantCulture); + + region.index = int.Parse(ReadValue(reader), CultureInfo.InvariantCulture); + + regions.Add(region); + } + } + } + + static string ReadValue(TextReader reader) + { + string line = reader.ReadLine(); + int colon = line.IndexOf(':'); + if (colon == -1) throw new Exception("Invalid line: " + line); + return line.Substring(colon + 1).Trim(); + } + + /// Returns the number of tuple values read (1, 2 or 4). + static int ReadTuple(TextReader reader, string[] tuple) + { + string line = reader.ReadLine(); + int colon = line.IndexOf(':'); + if (colon == -1) throw new Exception("Invalid line: " + line); + int i = 0, lastMatch = colon + 1; + for (; i < 3; i++) + { + int comma = line.IndexOf(',', lastMatch); + if (comma == -1) break; + tuple[i] = line.Substring(lastMatch, comma - lastMatch).Trim(); + lastMatch = comma + 1; + } + tuple[i] = line.Substring(lastMatch).Trim(); + return i + 1; + } + + public void FlipV() + { + for (int i = 0, n = regions.Count; i < n; i++) + { + AtlasRegion region = regions[i]; + region.v = 1 - region.v; + region.v2 = 1 - region.v2; + } + } + + /// 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. + /// The region, or null. + public AtlasRegion FindRegion(string name) + { + for (int i = 0, n = regions.Count; i < n; i++) + if (regions[i].name == name) return regions[i]; + return null; + } + + public void Dispose() + { + if (textureLoader == null) return; + for (int i = 0, n = pages.Count; i < n; i++) + textureLoader.Unload(pages[i].rendererObject); + } + } + + public enum Format + { + Alpha, + Intensity, + LuminanceAlpha, + RGB565, + RGBA4444, + RGB888, + RGBA8888 + } + + public enum TextureFilter + { + Nearest, + Linear, + MipMap, + MipMapNearestNearest, + MipMapLinearNearest, + MipMapNearestLinear, + MipMapLinearLinear + } + + public enum TextureWrap + { + MirroredRepeat, + ClampToEdge, + Repeat + } + + public class AtlasPage + { + public string name; + public Format format; + public TextureFilter minFilter; + public TextureFilter magFilter; + public TextureWrap uWrap; + public TextureWrap vWrap; + public object rendererObject; + public int width, height; + + public AtlasPage Clone() + { + return MemberwiseClone() as AtlasPage; + } + } + + public class AtlasRegion + { + public AtlasPage page; + public string name; + public int x, y, width, height; + public float u, v, u2, v2; + public float offsetX, offsetY; + public int originalWidth, originalHeight; + public int index; + public bool rotate; + public int[] splits; + public int[] pads; + + public AtlasRegion Clone() + { + return MemberwiseClone() as AtlasRegion; + } + } + + public interface TextureLoader + { + void Load(AtlasPage page, string path); + void Unload(Object texture); + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Attachments/AtlasAttachmentLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Attachments/AtlasAttachmentLoader.cs index 7411013..879903f 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Attachments/AtlasAttachmentLoader.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Attachments/AtlasAttachmentLoader.cs @@ -29,80 +29,91 @@ using System; -namespace Spine3_7_94 { +namespace Spine3_7_94 +{ - /// - /// An AttachmentLoader that configures attachments using texture regions from an Atlas. - /// See Loading Skeleton Data in the Spine Runtimes Guide. - /// - public class AtlasAttachmentLoader : AttachmentLoader { - private Atlas[] atlasArray; + /// + /// An AttachmentLoader that configures attachments using texture regions from an Atlas. + /// See Loading Skeleton Data in the Spine Runtimes Guide. + /// + public class AtlasAttachmentLoader : AttachmentLoader + { + private Atlas[] atlasArray; - public AtlasAttachmentLoader (params Atlas[] atlasArray) { - if (atlasArray == null) throw new ArgumentNullException("atlas array cannot be null."); - this.atlasArray = atlasArray; - } + public AtlasAttachmentLoader(params Atlas[] atlasArray) + { + if (atlasArray == null) throw new ArgumentNullException("atlas array cannot be null."); + this.atlasArray = atlasArray; + } - public RegionAttachment NewRegionAttachment (Skin skin, string name, string path) { - AtlasRegion region = FindRegion(path); - if (region == null) throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name)); - RegionAttachment attachment = new RegionAttachment(name); - attachment.RendererObject = region; - attachment.SetUVs(region.u, region.v, region.u2, region.v2, region.rotate); - attachment.regionOffsetX = region.offsetX; - attachment.regionOffsetY = region.offsetY; - attachment.regionWidth = region.width; - attachment.regionHeight = region.height; - attachment.regionOriginalWidth = region.originalWidth; - attachment.regionOriginalHeight = region.originalHeight; - return attachment; - } + public RegionAttachment NewRegionAttachment(Skin skin, string name, string path) + { + AtlasRegion region = FindRegion(path); + if (region == null) throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name)); + RegionAttachment attachment = new RegionAttachment(name); + attachment.RendererObject = region; + attachment.SetUVs(region.u, region.v, region.u2, region.v2, region.rotate); + attachment.regionOffsetX = region.offsetX; + attachment.regionOffsetY = region.offsetY; + attachment.regionWidth = region.width; + attachment.regionHeight = region.height; + attachment.regionOriginalWidth = region.originalWidth; + attachment.regionOriginalHeight = region.originalHeight; + return attachment; + } - public MeshAttachment NewMeshAttachment (Skin skin, string name, string path) { - AtlasRegion region = FindRegion(path); - if (region == null) throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name)); - MeshAttachment attachment = new MeshAttachment(name); - attachment.RendererObject = region; - attachment.RegionU = region.u; - attachment.RegionV = region.v; - attachment.RegionU2 = region.u2; - attachment.RegionV2 = region.v2; - attachment.RegionRotate = region.rotate; - attachment.regionOffsetX = region.offsetX; - attachment.regionOffsetY = region.offsetY; - attachment.regionWidth = region.width; - attachment.regionHeight = region.height; - attachment.regionOriginalWidth = region.originalWidth; - attachment.regionOriginalHeight = region.originalHeight; - return attachment; - } + public MeshAttachment NewMeshAttachment(Skin skin, string name, string path) + { + AtlasRegion region = FindRegion(path); + if (region == null) throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name)); + MeshAttachment attachment = new MeshAttachment(name); + attachment.RendererObject = region; + attachment.RegionU = region.u; + attachment.RegionV = region.v; + attachment.RegionU2 = region.u2; + attachment.RegionV2 = region.v2; + attachment.RegionRotate = region.rotate; + attachment.regionOffsetX = region.offsetX; + attachment.regionOffsetY = region.offsetY; + attachment.regionWidth = region.width; + attachment.regionHeight = region.height; + attachment.regionOriginalWidth = region.originalWidth; + attachment.regionOriginalHeight = region.originalHeight; + return attachment; + } - public BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, string name) { - return new BoundingBoxAttachment(name); - } + public BoundingBoxAttachment NewBoundingBoxAttachment(Skin skin, string name) + { + return new BoundingBoxAttachment(name); + } - public PathAttachment NewPathAttachment (Skin skin, string name) { - return new PathAttachment(name); - } + public PathAttachment NewPathAttachment(Skin skin, string name) + { + return new PathAttachment(name); + } - public PointAttachment NewPointAttachment (Skin skin, string name) { - return new PointAttachment(name); - } + public PointAttachment NewPointAttachment(Skin skin, string name) + { + return new PointAttachment(name); + } - public ClippingAttachment NewClippingAttachment(Skin skin, string name) { - return new ClippingAttachment(name); - } + public ClippingAttachment NewClippingAttachment(Skin skin, string name) + { + return new ClippingAttachment(name); + } - public AtlasRegion FindRegion (string name) { - AtlasRegion region; + public AtlasRegion FindRegion(string name) + { + AtlasRegion region; - for (int i = 0; i < atlasArray.Length; i++) { - region = atlasArray[i].FindRegion(name); - if (region != null) - return region; - } + for (int i = 0; i < atlasArray.Length; i++) + { + region = atlasArray[i].FindRegion(name); + if (region != null) + return region; + } - return null; - } - } + return null; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Attachments/Attachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Attachments/Attachment.cs index a9ecff1..d76601e 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Attachments/Attachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Attachments/Attachment.cs @@ -29,21 +29,26 @@ using System; -namespace Spine3_7_94 { - abstract public class Attachment { - public string Name { get; private set; } +namespace Spine3_7_94 +{ + abstract public class Attachment + { + public string Name { get; private set; } - protected Attachment (string name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null"); - Name = name; - } + protected Attachment(string name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null"); + Name = name; + } - override public string ToString () { - return Name; - } - } + override public string ToString() + { + return Name; + } + } - public interface IHasRendererObject { - object RendererObject { get; set; } - } + public interface IHasRendererObject + { + object RendererObject { get; set; } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Attachments/AttachmentLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Attachments/AttachmentLoader.cs index 06f1749..9783aa1 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Attachments/AttachmentLoader.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Attachments/AttachmentLoader.cs @@ -27,22 +27,24 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine3_7_94 { - public interface AttachmentLoader { - /// May be null to not load any attachment. - RegionAttachment NewRegionAttachment (Skin skin, string name, string path); +namespace Spine3_7_94 +{ + public interface AttachmentLoader + { + /// May be null to not load any attachment. + RegionAttachment NewRegionAttachment(Skin skin, string name, string path); - /// May be null to not load any attachment. - MeshAttachment NewMeshAttachment (Skin skin, string name, string path); + /// May be null to not load any attachment. + MeshAttachment NewMeshAttachment(Skin skin, string name, string path); - /// May be null to not load any attachment. - BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, string name); + /// May be null to not load any attachment. + BoundingBoxAttachment NewBoundingBoxAttachment(Skin skin, string name); - /// May be null to not load any attachment - PathAttachment NewPathAttachment (Skin skin, string name); + /// May be null to not load any attachment + PathAttachment NewPathAttachment(Skin skin, string name); - PointAttachment NewPointAttachment (Skin skin, string name); + PointAttachment NewPointAttachment(Skin skin, string name); - ClippingAttachment NewClippingAttachment (Skin skin, string name); - } + ClippingAttachment NewClippingAttachment(Skin skin, string name); + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Attachments/AttachmentType.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Attachments/AttachmentType.cs index 16d8426..24016a8 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Attachments/AttachmentType.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Attachments/AttachmentType.cs @@ -27,8 +27,10 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine3_7_94 { - public enum AttachmentType { - Region, Boundingbox, Mesh, Linkedmesh, Path, Point, Clipping - } +namespace Spine3_7_94 +{ + public enum AttachmentType + { + Region, Boundingbox, Mesh, Linkedmesh, Path, Point, Clipping + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Attachments/BoundingBoxAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Attachments/BoundingBoxAttachment.cs index d7d5bdf..9c83279 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Attachments/BoundingBoxAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Attachments/BoundingBoxAttachment.cs @@ -27,13 +27,14 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; - -namespace Spine3_7_94 { - /// Attachment that has a polygon for bounds checking. - public class BoundingBoxAttachment : VertexAttachment { - public BoundingBoxAttachment (string name) - : base(name) { - } - } +namespace Spine3_7_94 +{ + /// Attachment that has a polygon for bounds checking. + public class BoundingBoxAttachment : VertexAttachment + { + public BoundingBoxAttachment(string name) + : base(name) + { + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Attachments/ClippingAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Attachments/ClippingAttachment.cs index 9b60e18..da9f435 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Attachments/ClippingAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Attachments/ClippingAttachment.cs @@ -27,15 +27,16 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; - -namespace Spine3_7_94 { - public class ClippingAttachment : VertexAttachment { +namespace Spine3_7_94 +{ + public class ClippingAttachment : VertexAttachment + { internal SlotData endSlot; public SlotData EndSlot { get { return endSlot; } set { endSlot = value; } } - public ClippingAttachment(string name) : base(name) { + public ClippingAttachment(string name) : base(name) + { } } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Attachments/MeshAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Attachments/MeshAttachment.cs index 44946b5..c7cb23c 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Attachments/MeshAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Attachments/MeshAttachment.cs @@ -27,107 +27,118 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; +namespace Spine3_7_94 +{ + /// Attachment that displays a texture region using a mesh. + public class MeshAttachment : VertexAttachment, IHasRendererObject + { + internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; + private MeshAttachment parentMesh; + internal float[] uvs, regionUVs; + internal int[] triangles; + internal float r = 1, g = 1, b = 1, a = 1; + internal int hulllength; + internal bool inheritDeform; -namespace Spine3_7_94 { - /// Attachment that displays a texture region using a mesh. - public class MeshAttachment : VertexAttachment, IHasRendererObject { - internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; - private MeshAttachment parentMesh; - internal float[] uvs, regionUVs; - internal int[] triangles; - internal float r = 1, g = 1, b = 1, a = 1; - internal int hulllength; - internal bool inheritDeform; + public int HullLength { get { return hulllength; } set { hulllength = value; } } + public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } } + /// The UV pair for each vertex, normalized within the entire texture. + public float[] UVs { get { return uvs; } set { uvs = value; } } + public int[] Triangles { get { return triangles; } set { triangles = value; } } - public int HullLength { get { return hulllength; } set { hulllength = value; } } - public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } } - /// The UV pair for each vertex, normalized within the entire texture. - public float[] UVs { get { return uvs; } set { uvs = value; } } - public int[] Triangles { get { return triangles; } set { triangles = value; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } + public string Path { get; set; } + public object RendererObject { get; set; } + public float RegionU { get; set; } + public float RegionV { get; set; } + public float RegionU2 { get; set; } + public float RegionV2 { get; set; } + public bool RegionRotate { get; set; } + public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } + public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. + public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } + public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. + public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } + public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. - public string Path { get; set; } - public object RendererObject { get; set; } - public float RegionU { get; set; } - public float RegionV { get; set; } - public float RegionU2 { get; set; } - public float RegionV2 { get; set; } - public bool RegionRotate { get; set; } - public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } - public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. - public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } - public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. - public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } - public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. + public bool InheritDeform { get { return inheritDeform; } set { inheritDeform = value; } } - public bool InheritDeform { get { return inheritDeform; } set { inheritDeform = value; } } + public MeshAttachment ParentMesh + { + get { return parentMesh; } + set + { + parentMesh = value; + if (value != null) + { + bones = value.bones; + vertices = value.vertices; + worldVerticesLength = value.worldVerticesLength; + regionUVs = value.regionUVs; + triangles = value.triangles; + HullLength = value.HullLength; + Edges = value.Edges; + Width = value.Width; + Height = value.Height; + } + } + } - public MeshAttachment ParentMesh { - get { return parentMesh; } - set { - parentMesh = value; - if (value != null) { - bones = value.bones; - vertices = value.vertices; - worldVerticesLength = value.worldVerticesLength; - regionUVs = value.regionUVs; - triangles = value.triangles; - HullLength = value.HullLength; - Edges = value.Edges; - Width = value.Width; - Height = value.Height; - } - } - } + // Nonessential. + public int[] Edges { get; set; } + public float Width { get; set; } + public float Height { get; set; } - // Nonessential. - public int[] Edges { get; set; } - public float Width { get; set; } - public float Height { get; set; } + public MeshAttachment(string name) + : base(name) + { + } - public MeshAttachment (string name) - : base(name) { - } + public void UpdateUVs() + { + float[] regionUVs = this.regionUVs; + if (this.uvs == null || this.uvs.Length != regionUVs.Length) this.uvs = new float[regionUVs.Length]; + float[] uvs = this.uvs; - public void UpdateUVs () { - float[] regionUVs = this.regionUVs; - if (this.uvs == null || this.uvs.Length != regionUVs.Length) this.uvs = new float[regionUVs.Length]; - float[] uvs = this.uvs; + if (RegionRotate) + { + float textureHeight = this.regionWidth / (RegionV2 - RegionV); + float textureWidth = this.regionHeight / (RegionU2 - RegionU); + float u = RegionU - (RegionOriginalHeight - RegionOffsetY - RegionHeight) / textureWidth; + float v = RegionV - (RegionOriginalWidth - RegionOffsetX - RegionWidth) / textureHeight; + float width = RegionOriginalHeight / textureWidth; + float height = RegionOriginalWidth / textureHeight; - if (RegionRotate) { - float textureHeight = this.regionWidth / (RegionV2 - RegionV); - float textureWidth = this.regionHeight / (RegionU2 - RegionU); - float u = RegionU - (RegionOriginalHeight - RegionOffsetY - RegionHeight) / textureWidth; - float v = RegionV - (RegionOriginalWidth - RegionOffsetX - RegionWidth) / textureHeight; - float width = RegionOriginalHeight / textureWidth; - float height = RegionOriginalWidth / textureHeight; + for (int i = 0, n = uvs.Length; i < n; i += 2) + { + uvs[i] = u + regionUVs[i + 1] * width; + uvs[i + 1] = v + height - regionUVs[i] * height; + } + } + else + { + float textureWidth = this.regionWidth / (RegionU2 - RegionU); + float textureHeight = this.regionHeight / (RegionV2 - RegionV); + float u = RegionU - RegionOffsetX / textureWidth; + float v = RegionV - (RegionOriginalHeight - RegionOffsetY - RegionHeight) / textureHeight; + float width = RegionOriginalWidth / textureWidth; + float height = RegionOriginalHeight / textureHeight; - for (int i = 0, n = uvs.Length; i < n; i += 2) { - uvs[i] = u + regionUVs[i + 1] * width; - uvs[i + 1] = v + height - regionUVs[i] * height; - } - } else { - float textureWidth = this.regionWidth / (RegionU2 - RegionU); - float textureHeight = this.regionHeight / (RegionV2 - RegionV); - float u = RegionU - RegionOffsetX / textureWidth; - float v = RegionV - (RegionOriginalHeight - RegionOffsetY - RegionHeight) / textureHeight; - float width = RegionOriginalWidth / textureWidth; - float height = RegionOriginalHeight / textureHeight; + for (int i = 0, n = uvs.Length; i < n; i += 2) + { + uvs[i] = u + regionUVs[i] * width; + uvs[i + 1] = v + regionUVs[i + 1] * height; + } + } + } - for (int i = 0, n = uvs.Length; i < n; i += 2) { - uvs[i] = u + regionUVs[i] * width; - uvs[i + 1] = v + regionUVs[i + 1] * height; - } - } - } - - override public bool ApplyDeform (VertexAttachment sourceAttachment) { - return this == sourceAttachment || (inheritDeform && parentMesh == sourceAttachment); - } - } + override public bool ApplyDeform(VertexAttachment sourceAttachment) + { + return this == sourceAttachment || (inheritDeform && parentMesh == sourceAttachment); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Attachments/PathAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Attachments/PathAttachment.cs index 51b4d9a..a097116 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Attachments/PathAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Attachments/PathAttachment.cs @@ -28,20 +28,22 @@ *****************************************************************************/ using System; -using System.Collections.Generic; -namespace Spine3_7_94 { - public class PathAttachment : VertexAttachment { - internal float[] lengths; - internal bool closed, constantSpeed; +namespace Spine3_7_94 +{ + public class PathAttachment : VertexAttachment + { + internal float[] lengths; + internal bool closed, constantSpeed; - /// The length in the setup pose from the start of the path to the end of each curve. - public float[] Lengths { get { return lengths; } set { lengths = value; } } - public bool Closed { get { return closed; } set { closed = value; } } - public bool ConstantSpeed { get { return constantSpeed; } set { constantSpeed = value; } } + /// The length in the setup pose from the start of the path to the end of each curve. + public float[] Lengths { get { return lengths; } set { lengths = value; } } + public bool Closed { get { return closed; } set { closed = value; } } + public bool ConstantSpeed { get { return constantSpeed; } set { constantSpeed = value; } } - public PathAttachment (String name) - : base(name) { - } - } + public PathAttachment(String name) + : base(name) + { + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Attachments/PointAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Attachments/PointAttachment.cs index 9202bc8..16fd1cd 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Attachments/PointAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Attachments/PointAttachment.cs @@ -27,33 +27,38 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine3_7_94 { - /// - /// 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 Point Attachments in the Spine User Guide. - ///

- public class PointAttachment : Attachment { - internal float x, y, rotation; - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public float Rotation { get { return rotation; } set { rotation = value; } } +namespace Spine3_7_94 +{ + /// + /// 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 Point Attachments in the Spine User Guide. + ///

+ public class PointAttachment : Attachment + { + internal float x, y, rotation; + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public float Rotation { get { return rotation; } set { rotation = value; } } - public PointAttachment (string name) - : base(name) { - } + public PointAttachment(string name) + : base(name) + { + } - public void ComputeWorldPosition (Bone bone, out float ox, out float oy) { - bone.LocalToWorld(this.x, this.y, out ox, out oy); - } + public void ComputeWorldPosition(Bone bone, out float ox, out float oy) + { + bone.LocalToWorld(this.x, this.y, out ox, out oy); + } - public float ComputeWorldRotation (Bone bone) { - float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation); - float ix = cos * bone.a + sin * bone.b; - float iy = cos * bone.c + sin * bone.d; - return MathUtils.Atan2(iy, ix) * MathUtils.RadDeg; - } - } + public float ComputeWorldRotation(Bone bone) + { + float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation); + float ix = cos * bone.a + sin * bone.b; + float iy = cos * bone.c + sin * bone.d; + return MathUtils.Atan2(iy, ix) * MathUtils.RadDeg; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Attachments/RegionAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Attachments/RegionAttachment.cs index 28209df..dce2362 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Attachments/RegionAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Attachments/RegionAttachment.cs @@ -27,156 +27,164 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; +namespace Spine3_7_94 +{ + /// Attachment that displays a texture region. + public class RegionAttachment : Attachment, IHasRendererObject + { + public const int BLX = 0; + public const int BLY = 1; + public const int ULX = 2; + public const int ULY = 3; + public const int URX = 4; + public const int URY = 5; + public const int BRX = 6; + public const int BRY = 7; -namespace Spine3_7_94 { - /// Attachment that displays a texture region. - public class RegionAttachment : Attachment, IHasRendererObject { - public const int BLX = 0; - public const int BLY = 1; - public const int ULX = 2; - public const int ULY = 3; - public const int URX = 4; - public const int URY = 5; - public const int BRX = 6; - public const int BRY = 7; + internal float x, y, rotation, scaleX = 1, scaleY = 1, width, height; + internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; + internal float[] offset = new float[8], uvs = new float[8]; + internal float r = 1, g = 1, b = 1, a = 1; - internal float x, y, rotation, scaleX = 1, scaleY = 1, width, height; - internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; - internal float[] offset = new float[8], uvs = new float[8]; - internal float r = 1, g = 1, b = 1, a = 1; + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public float Rotation { get { return rotation; } set { rotation = value; } } + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + public float Width { get { return width; } set { width = value; } } + public float Height { get { return height; } set { height = value; } } - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public float Rotation { get { return rotation; } set { rotation = value; } } - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - public float ScaleY { get { return scaleY; } set { scaleY = value; } } - public float Width { get { return width; } set { width = value; } } - public float Height { get { return height; } set { height = value; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } + public string Path { get; set; } + public object RendererObject { get; set; } + public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } + public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. + public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } + public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. + public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } + public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. - public string Path { get; set; } - public object RendererObject { get; set; } - public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } - public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. - public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } - public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. - public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } - public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. + public float[] Offset { get { return offset; } } + public float[] UVs { get { return uvs; } } - public float[] Offset { get { return offset; } } - public float[] UVs { get { return uvs; } } + public RegionAttachment(string name) + : base(name) + { + } - public RegionAttachment (string name) - : base(name) { - } + public void UpdateOffset() + { + float width = this.width; + float height = this.height; + float localX2 = width * 0.5f; + float localY2 = height * 0.5f; + float localX = -localX2; + float localY = -localY2; + if (regionOriginalWidth != 0) + { // if (region != null) + localX += regionOffsetX / regionOriginalWidth * width; + localY += regionOffsetY / regionOriginalHeight * height; + localX2 -= (regionOriginalWidth - regionOffsetX - regionWidth) / regionOriginalWidth * width; + localY2 -= (regionOriginalHeight - regionOffsetY - regionHeight) / regionOriginalHeight * height; + } + float scaleX = this.scaleX; + float scaleY = this.scaleY; + localX *= scaleX; + localY *= scaleY; + localX2 *= scaleX; + localY2 *= scaleY; + float rotation = this.rotation; + float cos = MathUtils.CosDeg(rotation); + float sin = MathUtils.SinDeg(rotation); + float x = this.x; + float y = this.y; + 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; + float[] offset = this.offset; + offset[BLX] = localXCos - localYSin; + offset[BLY] = localYCos + localXSin; + offset[ULX] = localXCos - localY2Sin; + offset[ULY] = localY2Cos + localXSin; + offset[URX] = localX2Cos - localY2Sin; + offset[URY] = localY2Cos + localX2Sin; + offset[BRX] = localX2Cos - localYSin; + offset[BRY] = localYCos + localX2Sin; + } - public void UpdateOffset () { - float width = this.width; - float height = this.height; - float localX2 = width * 0.5f; - float localY2 = height * 0.5f; - float localX = -localX2; - float localY = -localY2; - if (regionOriginalWidth != 0) { // if (region != null) - localX += regionOffsetX / regionOriginalWidth * width; - localY += regionOffsetY / regionOriginalHeight * height; - localX2 -= (regionOriginalWidth - regionOffsetX - regionWidth) / regionOriginalWidth * width; - localY2 -= (regionOriginalHeight - regionOffsetY - regionHeight) / regionOriginalHeight * height; - } - float scaleX = this.scaleX; - float scaleY = this.scaleY; - localX *= scaleX; - localY *= scaleY; - localX2 *= scaleX; - localY2 *= scaleY; - float rotation = this.rotation; - float cos = MathUtils.CosDeg(rotation); - float sin = MathUtils.SinDeg(rotation); - float x = this.x; - float y = this.y; - 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; - float[] offset = this.offset; - offset[BLX] = localXCos - localYSin; - offset[BLY] = localYCos + localXSin; - offset[ULX] = localXCos - localY2Sin; - offset[ULY] = localY2Cos + localXSin; - offset[URX] = localX2Cos - localY2Sin; - offset[URY] = localY2Cos + localX2Sin; - offset[BRX] = localX2Cos - localYSin; - offset[BRY] = localYCos + localX2Sin; - } + public void SetUVs(float u, float v, float u2, float v2, bool rotate) + { + float[] uvs = this.uvs; + // UV values differ from RegionAttachment.java + if (rotate) + { + uvs[URX] = u; + uvs[URY] = v2; + uvs[BRX] = u; + uvs[BRY] = v; + uvs[BLX] = u2; + uvs[BLY] = v; + uvs[ULX] = u2; + uvs[ULY] = v2; + } + else + { + uvs[ULX] = u; + uvs[ULY] = v2; + uvs[URX] = u; + uvs[URY] = v; + uvs[BRX] = u2; + uvs[BRY] = v; + uvs[BLX] = u2; + uvs[BLY] = v2; + } + } - public void SetUVs (float u, float v, float u2, float v2, bool rotate) { - float[] uvs = this.uvs; - // UV values differ from RegionAttachment.java - if (rotate) { - uvs[URX] = u; - uvs[URY] = v2; - uvs[BRX] = u; - uvs[BRY] = v; - uvs[BLX] = u2; - uvs[BLY] = v; - uvs[ULX] = u2; - uvs[ULY] = v2; - } else { - uvs[ULX] = u; - uvs[ULY] = v2; - uvs[URX] = u; - uvs[URY] = v; - uvs[BRX] = u2; - uvs[BRY] = v; - uvs[BLX] = u2; - uvs[BLY] = v2; - } - } + /// Transforms the attachment's four vertices to world coordinates. + /// The parent bone. + /// The output world vertices. Must have a length greater than or equal to offset + 8. + /// The worldVertices index to begin writing values. + /// The number of worldVertices entries between the value pairs written. + public void ComputeWorldVertices(Bone bone, float[] worldVertices, int offset, int stride = 2) + { + float[] vertexOffset = this.offset; + float bwx = bone.worldX, bwy = bone.worldY; + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + float offsetX, offsetY; - /// Transforms the attachment's four vertices to world coordinates. - /// The parent bone. - /// The output world vertices. Must have a length greater than or equal to offset + 8. - /// The worldVertices index to begin writing values. - /// The number of worldVertices entries between the value pairs written. - public void ComputeWorldVertices (Bone bone, float[] worldVertices, int offset, int stride = 2) { - float[] vertexOffset = this.offset; - float bwx = bone.worldX, bwy = bone.worldY; - float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - float offsetX, offsetY; + // Vertex order is different from RegionAttachment.java + offsetX = vertexOffset[BRX]; // 0 + offsetY = vertexOffset[BRY]; // 1 + worldVertices[offset] = offsetX * a + offsetY * b + bwx; // bl + worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; + offset += stride; - // Vertex order is different from RegionAttachment.java - offsetX = vertexOffset[BRX]; // 0 - offsetY = vertexOffset[BRY]; // 1 - worldVertices[offset] = offsetX * a + offsetY * b + bwx; // bl - worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; - offset += stride; + offsetX = vertexOffset[BLX]; // 2 + offsetY = vertexOffset[BLY]; // 3 + worldVertices[offset] = offsetX * a + offsetY * b + bwx; // ul + worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; + offset += stride; - offsetX = vertexOffset[BLX]; // 2 - offsetY = vertexOffset[BLY]; // 3 - worldVertices[offset] = offsetX * a + offsetY * b + bwx; // ul - worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; - offset += stride; + offsetX = vertexOffset[ULX]; // 4 + offsetY = vertexOffset[ULY]; // 5 + worldVertices[offset] = offsetX * a + offsetY * b + bwx; // ur + worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; + offset += stride; - offsetX = vertexOffset[ULX]; // 4 - offsetY = vertexOffset[ULY]; // 5 - worldVertices[offset] = offsetX * a + offsetY * b + bwx; // ur - worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; - offset += stride; - - offsetX = vertexOffset[URX]; // 6 - offsetY = vertexOffset[URY]; // 7 - worldVertices[offset] = offsetX * a + offsetY * b + bwx; // br - worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; - //offset += stride; - } - } + offsetX = vertexOffset[URX]; // 6 + offsetY = vertexOffset[URY]; // 7 + worldVertices[offset] = offsetX * a + offsetY * b + bwx; // br + worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; + //offset += stride; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Attachments/VertexAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Attachments/VertexAttachment.cs index 302ac68..332e58e 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Attachments/VertexAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Attachments/VertexAttachment.cs @@ -29,101 +29,118 @@ using System; -namespace Spine3_7_94 { - /// >An attachment with vertices that are transformed by one or more bones and can be deformed by a slot's vertices. - public class VertexAttachment : Attachment { - static int nextID = 0; - static readonly Object nextIdLock = new Object(); +namespace Spine3_7_94 +{ + /// >An attachment with vertices that are transformed by one or more bones and can be deformed by a slot's vertices. + public class VertexAttachment : Attachment + { + static int nextID = 0; + static readonly Object nextIdLock = new Object(); - internal readonly int id; - internal int[] bones; - internal float[] vertices; - internal int worldVerticesLength; + internal readonly int id; + internal int[] bones; + internal float[] vertices; + internal int worldVerticesLength; - /// Gets a unique ID for this attachment. - public int Id { get { return id; } } - public int[] Bones { get { return bones; } set { bones = value; } } - public float[] Vertices { get { return vertices; } set { vertices = value; } } - public int WorldVerticesLength { get { return worldVerticesLength; } set { worldVerticesLength = value; } } + /// Gets a unique ID for this attachment. + public int Id { get { return id; } } + public int[] Bones { get { return bones; } set { bones = value; } } + public float[] Vertices { get { return vertices; } set { vertices = value; } } + public int WorldVerticesLength { get { return worldVerticesLength; } set { worldVerticesLength = value; } } - public VertexAttachment (string name) - : base(name) { + public VertexAttachment(string name) + : base(name) + { - lock (VertexAttachment.nextIdLock) { - id = (VertexAttachment.nextID++ & 65535) << 11; - } - } + lock (VertexAttachment.nextIdLock) + { + id = (VertexAttachment.nextID++ & 65535) << 11; + } + } - public void ComputeWorldVertices (Slot slot, float[] worldVertices) { - ComputeWorldVertices(slot, 0, worldVerticesLength, worldVertices, 0); - } + public void ComputeWorldVertices(Slot slot, float[] worldVertices) + { + ComputeWorldVertices(slot, 0, worldVerticesLength, worldVertices, 0); + } - /// Transforms local vertices to world coordinates. - /// The index of the first value to transform. Each vertex has 2 values, x and y. - /// The number of world vertex values to output. Must be less than or equal to - start. - /// The output world vertices. Must have a length greater than or equal to + . - /// The index to begin writing values. - /// The number of entries between the value pairs written. - public void ComputeWorldVertices (Slot slot, int start, int count, float[] worldVertices, int offset, int stride = 2) { - count = offset + (count >> 1) * stride; - Skeleton skeleton = slot.bone.skeleton; - var deformArray = slot.attachmentVertices; - float[] vertices = this.vertices; - int[] bones = this.bones; - if (bones == null) { - if (deformArray.Count > 0) vertices = deformArray.Items; - Bone bone = slot.bone; - float x = bone.worldX, y = bone.worldY; - float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - for (int vv = start, w = offset; w < count; vv += 2, w += stride) { - float vx = vertices[vv], 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 (int i = 0; i < start; i += 2) { - int n = bones[v]; - v += n + 1; - skip += n; - } - var skeletonBones = skeleton.bones.Items; - if (deformArray.Count == 0) { - for (int w = offset, b = skip * 3; w < count; w += stride) { - float wx = 0, wy = 0; - int n = bones[v++]; - n += v; - for (; v < n; v++, b += 3) { - Bone bone = skeletonBones[bones[v]]; - float vx = vertices[b], vy = vertices[b + 1], 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 { - float[] deform = deformArray.Items; - for (int w = offset, b = skip * 3, f = skip << 1; w < count; w += stride) { - float wx = 0, wy = 0; - int n = bones[v++]; - n += v; - for (; v < n; v++, b += 3, f += 2) { - Bone bone = skeletonBones[bones[v]]; - float vx = vertices[b] + deform[f], vy = vertices[b + 1] + deform[f + 1], 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; - } - } - } + /// Transforms local vertices to world coordinates. + /// The index of the first value to transform. Each vertex has 2 values, x and y. + /// The number of world vertex values to output. Must be less than or equal to - start. + /// The output world vertices. Must have a length greater than or equal to + . + /// The index to begin writing values. + /// The number of entries between the value pairs written. + public void ComputeWorldVertices(Slot slot, int start, int count, float[] worldVertices, int offset, int stride = 2) + { + count = offset + (count >> 1) * stride; + Skeleton skeleton = slot.bone.skeleton; + var deformArray = slot.attachmentVertices; + float[] vertices = this.vertices; + int[] bones = this.bones; + if (bones == null) + { + if (deformArray.Count > 0) vertices = deformArray.Items; + Bone bone = slot.bone; + float x = bone.worldX, y = bone.worldY; + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + for (int vv = start, w = offset; w < count; vv += 2, w += stride) + { + float vx = vertices[vv], 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 (int i = 0; i < start; i += 2) + { + int n = bones[v]; + v += n + 1; + skip += n; + } + var skeletonBones = skeleton.bones.Items; + if (deformArray.Count == 0) + { + for (int w = offset, b = skip * 3; w < count; w += stride) + { + float wx = 0, wy = 0; + int n = bones[v++]; + n += v; + for (; v < n; v++, b += 3) + { + Bone bone = skeletonBones[bones[v]]; + float vx = vertices[b], vy = vertices[b + 1], 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 + { + float[] deform = deformArray.Items; + for (int w = offset, b = skip * 3, f = skip << 1; w < count; w += stride) + { + float wx = 0, wy = 0; + int n = bones[v++]; + n += v; + for (; v < n; v++, b += 3, f += 2) + { + Bone bone = skeletonBones[bones[v]]; + float vx = vertices[b] + deform[f], vy = vertices[b + 1] + deform[f + 1], 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; + } + } + } - /// Returns true if a deform originally applied to the specified attachment should be applied to this attachment. - virtual public bool ApplyDeform (VertexAttachment sourceAttachment) { - return this == sourceAttachment; - } - } + /// Returns true if a deform originally applied to the specified attachment should be applied to this attachment. + virtual public bool ApplyDeform(VertexAttachment sourceAttachment) + { + return this == sourceAttachment; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/BlendMode.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/BlendMode.cs index 9b6c957..2adf823 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/BlendMode.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/BlendMode.cs @@ -27,8 +27,10 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine3_7_94 { - public enum BlendMode { - Normal, Additive, Multiply, Screen - } +namespace Spine3_7_94 +{ + public enum BlendMode + { + Normal, Additive, Multiply, Screen + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Bone.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Bone.cs index 5b64876..8ad9481 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Bone.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Bone.cs @@ -29,335 +29,366 @@ using System; -namespace Spine3_7_94 { - /// - /// 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. - /// - /// - public class Bone : IUpdatable { - static public bool yDown; - - internal BoneData data; - internal Skeleton skeleton; - internal Bone parent; - internal ExposedList children = new ExposedList(); - internal float x, y, rotation, scaleX, scaleY, shearX, shearY; - internal float ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY; - internal bool appliedValid; - - internal float a, b, worldX; - internal float c, d, worldY; - - internal bool sorted; - - public BoneData Data { get { return data; } } - public Skeleton Skeleton { get { return skeleton; } } - public Bone Parent { get { return parent; } } - public ExposedList Children { get { return children; } } - /// The local X translation. - public float X { get { return x; } set { x = value; } } - /// The local Y translation. - public float Y { get { return y; } set { y = value; } } - /// The local rotation. - public float Rotation { get { return rotation; } set { rotation = value; } } - - /// The local scaleX. - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - - /// The local scaleY. - public float ScaleY { get { return scaleY; } set { scaleY = value; } } - - /// The local shearX. - public float ShearX { get { return shearX; } set { shearX = value; } } - - /// The local shearY. - public float ShearY { get { return shearY; } set { shearY = value; } } - - /// The rotation, as calculated by any constraints. - public float AppliedRotation { get { return arotation; } set { arotation = value; } } - - /// The applied local x translation. - public float AX { get { return ax; } set { ax = value; } } - - /// The applied local y translation. - public float AY { get { return ay; } set { ay = value; } } - - /// The applied local scaleX. - public float AScaleX { get { return ascaleX; } set { ascaleX = value; } } - - /// The applied local scaleY. - public float AScaleY { get { return ascaleY; } set { ascaleY = value; } } - - /// The applied local shearX. - public float AShearX { get { return ashearX; } set { ashearX = value; } } - - /// The applied local shearY. - public float AShearY { get { return ashearY; } set { ashearY = value; } } - - public float A { get { return a; } } - public float B { get { return b; } } - public float C { get { return c; } } - public float D { get { return d; } } - - public float WorldX { get { return worldX; } } - public float WorldY { get { return worldY; } } - public float WorldRotationX { get { return MathUtils.Atan2(c, a) * MathUtils.RadDeg; } } - public float WorldRotationY { get { return MathUtils.Atan2(d, b) * MathUtils.RadDeg; } } - - /// Returns the magnitide (always positive) of the world scale X. - public float WorldScaleX { get { return (float)Math.Sqrt(a * a + c * c); } } - /// Returns the magnitide (always positive) of the world scale Y. - public float WorldScaleY { get { return (float)Math.Sqrt(b * b + d * d); } } - - /// May be null. - public Bone (BoneData data, Skeleton skeleton, Bone parent) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.data = data; - this.skeleton = skeleton; - this.parent = parent; - SetToSetupPose(); - } - - /// Same as . This method exists for Bone to implement . - public void Update () { - UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); - } - - /// Computes the world transform using the parent bone and this bone's local transform. - public void UpdateWorldTransform () { - UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); - } - - /// Computes the world transform using the parent bone and the specified local transform. - public void UpdateWorldTransform (float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY) { - ax = x; - ay = y; - arotation = rotation; - ascaleX = scaleX; - ascaleY = scaleY; - ashearX = shearX; - ashearY = shearY; - appliedValid = true; - Skeleton skeleton = this.skeleton; - - Bone parent = this.parent; - if (parent == null) { // Root bone. - float rotationY = rotation + 90 + shearY, sx = skeleton.scaleX, sy = skeleton.scaleY; - a = MathUtils.CosDeg(rotation + shearX) * scaleX * sx; - b = MathUtils.CosDeg(rotationY) * scaleY * sx; - c = MathUtils.SinDeg(rotation + shearX) * scaleX * sy; - d = MathUtils.SinDeg(rotationY) * scaleY * sy; - worldX = x * sx + skeleton.x; - worldY = y * sy + skeleton.y; - return; - } - - float 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 (data.transformMode) { - case TransformMode.Normal: { - float rotationY = rotation + 90 + shearY; - float la = MathUtils.CosDeg(rotation + shearX) * scaleX; - float lb = MathUtils.CosDeg(rotationY) * scaleY; - float lc = MathUtils.SinDeg(rotation + shearX) * scaleX; - float ld = MathUtils.SinDeg(rotationY) * scaleY; - a = pa * la + pb * lc; - b = pa * lb + pb * ld; - c = pc * la + pd * lc; - d = pc * lb + pd * ld; - return; - } - case TransformMode.OnlyTranslation: { - float rotationY = rotation + 90 + shearY; - a = MathUtils.CosDeg(rotation + shearX) * scaleX; - b = MathUtils.CosDeg(rotationY) * scaleY; - c = MathUtils.SinDeg(rotation + shearX) * scaleX; - d = MathUtils.SinDeg(rotationY) * scaleY; - break; - } - case TransformMode.NoRotationOrReflection: { - float s = pa * pa + pc * pc, prx; - if (s > 0.0001f) { - s = Math.Abs(pa * pd - pb * pc) / s; - pa /= skeleton.ScaleX; - pc /= skeleton.ScaleY; - pb = pc * s; - pd = pa * s; - prx = MathUtils.Atan2(pc, pa) * MathUtils.RadDeg; - } else { - pa = 0; - pc = 0; - prx = 90 - MathUtils.Atan2(pd, pb) * MathUtils.RadDeg; - } - float rx = rotation + shearX - prx; - float ry = rotation + shearY - prx + 90; - float la = MathUtils.CosDeg(rx) * scaleX; - float lb = MathUtils.CosDeg(ry) * scaleY; - float lc = MathUtils.SinDeg(rx) * scaleX; - float ld = MathUtils.SinDeg(ry) * scaleY; - a = pa * la - pb * lc; - b = pa * lb - pb * ld; - c = pc * la + pd * lc; - d = pc * lb + pd * ld; - break; - } - case TransformMode.NoScale: - case TransformMode.NoScaleOrReflection: { - float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation); - float za = (pa * cos + pb * sin) / skeleton.scaleX; - float zc = (pc * cos + pd * sin) / skeleton.scaleY; - float s = (float)Math.Sqrt(za * za + zc * zc); - if (s > 0.00001f) s = 1 / s; - za *= s; - zc *= s; - s = (float)Math.Sqrt(za * za + zc * zc); - if (data.transformMode == TransformMode.NoScale - && (pa * pd - pb * pc < 0) != (skeleton.scaleX < 0 != skeleton.scaleY < 0)) s = -s; - - float r = MathUtils.PI / 2 + MathUtils.Atan2(zc, za); - float zb = MathUtils.Cos(r) * s; - float zd = MathUtils.Sin(r) * s; - float la = MathUtils.CosDeg(shearX) * scaleX; - float lb = MathUtils.CosDeg(90 + shearY) * scaleY; - float lc = MathUtils.SinDeg(shearX) * scaleX; - float ld = MathUtils.SinDeg(90 + shearY) * scaleY; - a = za * la + zb * lc; - b = za * lb + zb * ld; - c = zc * la + zd * lc; - d = zc * lb + zd * ld; - break; - } - } - - a *= skeleton.scaleX; - b *= skeleton.scaleX; - c *= skeleton.scaleY; - d *= skeleton.scaleY; - } - - public void SetToSetupPose () { - BoneData data = this.data; - x = data.x; - y = data.y; - rotation = data.rotation; - scaleX = data.scaleX; - scaleY = data.scaleY; - shearX = data.shearX; - shearY = data.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. - /// - internal void UpdateAppliedTransform () { - appliedValid = true; - Bone parent = this.parent; - if (parent == null) { - ax = worldX; - ay = worldY; - arotation = MathUtils.Atan2(c, a) * MathUtils.RadDeg; - ascaleX = (float)Math.Sqrt(a * a + c * c); - ascaleY = (float)Math.Sqrt(b * b + d * d); - ashearX = 0; - ashearY = MathUtils.Atan2(a * b + c * d, a * d - b * c) * MathUtils.RadDeg; - return; - } - float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d; - float pid = 1 / (pa * pd - pb * pc); - float dx = worldX - parent.worldX, dy = worldY - parent.worldY; - ax = (dx * pd * pid - dy * pb * pid); - ay = (dy * pa * pid - dx * pc * pid); - float ia = pid * pd; - float id = pid * pa; - float ib = pid * pb; - float ic = pid * pc; - float ra = ia * a - ib * c; - float rb = ia * b - ib * d; - float rc = id * c - ic * a; - float rd = id * d - ic * b; - ashearX = 0; - ascaleX = (float)Math.Sqrt(ra * ra + rc * rc); - if (ascaleX > 0.0001f) { - float det = ra * rd - rb * rc; - ascaleY = det / ascaleX; - ashearY = MathUtils.Atan2(ra * rb + rc * rd, det) * MathUtils.RadDeg; - arotation = MathUtils.Atan2(rc, ra) * MathUtils.RadDeg; - } else { - ascaleX = 0; - ascaleY = (float)Math.Sqrt(rb * rb + rd * rd); - ashearY = 0; - arotation = 90 - MathUtils.Atan2(rd, rb) * MathUtils.RadDeg; - } - } - - public void WorldToLocal (float worldX, float worldY, out float localX, out float localY) { - float a = this.a, b = this.b, c = this.c, d = this.d; - float invDet = 1 / (a * d - b * c); - float x = worldX - this.worldX, y = worldY - this.worldY; - localX = (x * d * invDet - y * b * invDet); - localY = (y * a * invDet - x * c * invDet); - } - - public void LocalToWorld (float localX, float localY, out float worldX, out float worldY) { - worldX = localX * a + localY * b + this.worldX; - worldY = localX * c + localY * d + this.worldY; - } - - public float WorldToLocalRotationX { - get { - Bone parent = this.parent; - if (parent == null) return arotation; - float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, a = this.a, c = this.c; - return MathUtils.Atan2(pa * c - pc * a, pd * a - pb * c) * MathUtils.RadDeg; - } - } - - public float WorldToLocalRotationY { - get { - Bone parent = this.parent; - if (parent == null) return arotation; - float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, b = this.b, d = this.d; - return MathUtils.Atan2(pa * d - pc * b, pd * b - pb * d) * MathUtils.RadDeg; - } - } - - public float WorldToLocalRotation (float worldRotation) { - float sin = MathUtils.SinDeg(worldRotation), cos = MathUtils.CosDeg(worldRotation); - return MathUtils.Atan2(a * sin - c * cos, d * cos - b * sin) * MathUtils.RadDeg + rotation - shearX; - } - - public float LocalToWorldRotation (float localRotation) { - localRotation -= rotation - shearX; - float sin = MathUtils.SinDeg(localRotation), cos = MathUtils.CosDeg(localRotation); - return MathUtils.Atan2(cos * c + sin * d, cos * a + sin * b) * MathUtils.RadDeg; - } - - /// - /// Rotates the world transform the specified amount and sets isAppliedValid to false. - /// - /// Degrees. - public void RotateWorld (float degrees) { - float a = this.a, b = this.b, c = this.c, d = this.d; - float cos = MathUtils.CosDeg(degrees), sin = MathUtils.SinDeg(degrees); - this.a = cos * a - sin * c; - this.b = cos * b - sin * d; - this.c = sin * a + cos * c; - this.d = sin * b + cos * d; - appliedValid = false; - } - - override public string ToString () { - return data.name; - } - } +namespace Spine3_7_94 +{ + /// + /// 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. + /// + /// + public class Bone : IUpdatable + { + static public bool yDown; + + internal BoneData data; + internal Skeleton skeleton; + internal Bone parent; + internal ExposedList children = new ExposedList(); + internal float x, y, rotation, scaleX, scaleY, shearX, shearY; + internal float ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY; + internal bool appliedValid; + + internal float a, b, worldX; + internal float c, d, worldY; + + internal bool sorted; + + public BoneData Data { get { return data; } } + public Skeleton Skeleton { get { return skeleton; } } + public Bone Parent { get { return parent; } } + public ExposedList Children { get { return children; } } + /// The local X translation. + public float X { get { return x; } set { x = value; } } + /// The local Y translation. + public float Y { get { return y; } set { y = value; } } + /// The local rotation. + public float Rotation { get { return rotation; } set { rotation = value; } } + + /// The local scaleX. + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + + /// The local scaleY. + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + + /// The local shearX. + public float ShearX { get { return shearX; } set { shearX = value; } } + + /// The local shearY. + public float ShearY { get { return shearY; } set { shearY = value; } } + + /// The rotation, as calculated by any constraints. + public float AppliedRotation { get { return arotation; } set { arotation = value; } } + + /// The applied local x translation. + public float AX { get { return ax; } set { ax = value; } } + + /// The applied local y translation. + public float AY { get { return ay; } set { ay = value; } } + + /// The applied local scaleX. + public float AScaleX { get { return ascaleX; } set { ascaleX = value; } } + + /// The applied local scaleY. + public float AScaleY { get { return ascaleY; } set { ascaleY = value; } } + + /// The applied local shearX. + public float AShearX { get { return ashearX; } set { ashearX = value; } } + + /// The applied local shearY. + public float AShearY { get { return ashearY; } set { ashearY = value; } } + + public float A { get { return a; } } + public float B { get { return b; } } + public float C { get { return c; } } + public float D { get { return d; } } + + public float WorldX { get { return worldX; } } + public float WorldY { get { return worldY; } } + public float WorldRotationX { get { return MathUtils.Atan2(c, a) * MathUtils.RadDeg; } } + public float WorldRotationY { get { return MathUtils.Atan2(d, b) * MathUtils.RadDeg; } } + + /// Returns the magnitide (always positive) of the world scale X. + public float WorldScaleX { get { return (float)Math.Sqrt(a * a + c * c); } } + /// Returns the magnitide (always positive) of the world scale Y. + public float WorldScaleY { get { return (float)Math.Sqrt(b * b + d * d); } } + + /// May be null. + public Bone(BoneData data, Skeleton skeleton, Bone parent) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + this.skeleton = skeleton; + this.parent = parent; + SetToSetupPose(); + } + + /// Same as . This method exists for Bone to implement . + public void Update() + { + UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); + } + + /// Computes the world transform using the parent bone and this bone's local transform. + public void UpdateWorldTransform() + { + UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); + } + + /// Computes the world transform using the parent bone and the specified local transform. + public void UpdateWorldTransform(float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY) + { + ax = x; + ay = y; + arotation = rotation; + ascaleX = scaleX; + ascaleY = scaleY; + ashearX = shearX; + ashearY = shearY; + appliedValid = true; + Skeleton skeleton = this.skeleton; + + Bone parent = this.parent; + if (parent == null) + { // Root bone. + float rotationY = rotation + 90 + shearY, sx = skeleton.scaleX, sy = skeleton.scaleY; + a = MathUtils.CosDeg(rotation + shearX) * scaleX * sx; + b = MathUtils.CosDeg(rotationY) * scaleY * sx; + c = MathUtils.SinDeg(rotation + shearX) * scaleX * sy; + d = MathUtils.SinDeg(rotationY) * scaleY * sy; + worldX = x * sx + skeleton.x; + worldY = y * sy + skeleton.y; + return; + } + + float 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 (data.transformMode) + { + case TransformMode.Normal: + { + float rotationY = rotation + 90 + shearY; + float la = MathUtils.CosDeg(rotation + shearX) * scaleX; + float lb = MathUtils.CosDeg(rotationY) * scaleY; + float lc = MathUtils.SinDeg(rotation + shearX) * scaleX; + float ld = MathUtils.SinDeg(rotationY) * scaleY; + a = pa * la + pb * lc; + b = pa * lb + pb * ld; + c = pc * la + pd * lc; + d = pc * lb + pd * ld; + return; + } + case TransformMode.OnlyTranslation: + { + float rotationY = rotation + 90 + shearY; + a = MathUtils.CosDeg(rotation + shearX) * scaleX; + b = MathUtils.CosDeg(rotationY) * scaleY; + c = MathUtils.SinDeg(rotation + shearX) * scaleX; + d = MathUtils.SinDeg(rotationY) * scaleY; + break; + } + case TransformMode.NoRotationOrReflection: + { + float s = pa * pa + pc * pc, prx; + if (s > 0.0001f) + { + s = Math.Abs(pa * pd - pb * pc) / s; + pa /= skeleton.ScaleX; + pc /= skeleton.ScaleY; + pb = pc * s; + pd = pa * s; + prx = MathUtils.Atan2(pc, pa) * MathUtils.RadDeg; + } + else + { + pa = 0; + pc = 0; + prx = 90 - MathUtils.Atan2(pd, pb) * MathUtils.RadDeg; + } + float rx = rotation + shearX - prx; + float ry = rotation + shearY - prx + 90; + float la = MathUtils.CosDeg(rx) * scaleX; + float lb = MathUtils.CosDeg(ry) * scaleY; + float lc = MathUtils.SinDeg(rx) * scaleX; + float ld = MathUtils.SinDeg(ry) * scaleY; + a = pa * la - pb * lc; + b = pa * lb - pb * ld; + c = pc * la + pd * lc; + d = pc * lb + pd * ld; + break; + } + case TransformMode.NoScale: + case TransformMode.NoScaleOrReflection: + { + float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation); + float za = (pa * cos + pb * sin) / skeleton.scaleX; + float zc = (pc * cos + pd * sin) / skeleton.scaleY; + float s = (float)Math.Sqrt(za * za + zc * zc); + if (s > 0.00001f) s = 1 / s; + za *= s; + zc *= s; + s = (float)Math.Sqrt(za * za + zc * zc); + if (data.transformMode == TransformMode.NoScale + && (pa * pd - pb * pc < 0) != (skeleton.scaleX < 0 != skeleton.scaleY < 0)) s = -s; + + float r = MathUtils.PI / 2 + MathUtils.Atan2(zc, za); + float zb = MathUtils.Cos(r) * s; + float zd = MathUtils.Sin(r) * s; + float la = MathUtils.CosDeg(shearX) * scaleX; + float lb = MathUtils.CosDeg(90 + shearY) * scaleY; + float lc = MathUtils.SinDeg(shearX) * scaleX; + float ld = MathUtils.SinDeg(90 + shearY) * scaleY; + a = za * la + zb * lc; + b = za * lb + zb * ld; + c = zc * la + zd * lc; + d = zc * lb + zd * ld; + break; + } + } + + a *= skeleton.scaleX; + b *= skeleton.scaleX; + c *= skeleton.scaleY; + d *= skeleton.scaleY; + } + + public void SetToSetupPose() + { + BoneData data = this.data; + x = data.x; + y = data.y; + rotation = data.rotation; + scaleX = data.scaleX; + scaleY = data.scaleY; + shearX = data.shearX; + shearY = data.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. + /// + internal void UpdateAppliedTransform() + { + appliedValid = true; + Bone parent = this.parent; + if (parent == null) + { + ax = worldX; + ay = worldY; + arotation = MathUtils.Atan2(c, a) * MathUtils.RadDeg; + ascaleX = (float)Math.Sqrt(a * a + c * c); + ascaleY = (float)Math.Sqrt(b * b + d * d); + ashearX = 0; + ashearY = MathUtils.Atan2(a * b + c * d, a * d - b * c) * MathUtils.RadDeg; + return; + } + float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d; + float pid = 1 / (pa * pd - pb * pc); + float dx = worldX - parent.worldX, dy = worldY - parent.worldY; + ax = (dx * pd * pid - dy * pb * pid); + ay = (dy * pa * pid - dx * pc * pid); + float ia = pid * pd; + float id = pid * pa; + float ib = pid * pb; + float ic = pid * pc; + float ra = ia * a - ib * c; + float rb = ia * b - ib * d; + float rc = id * c - ic * a; + float rd = id * d - ic * b; + ashearX = 0; + ascaleX = (float)Math.Sqrt(ra * ra + rc * rc); + if (ascaleX > 0.0001f) + { + float det = ra * rd - rb * rc; + ascaleY = det / ascaleX; + ashearY = MathUtils.Atan2(ra * rb + rc * rd, det) * MathUtils.RadDeg; + arotation = MathUtils.Atan2(rc, ra) * MathUtils.RadDeg; + } + else + { + ascaleX = 0; + ascaleY = (float)Math.Sqrt(rb * rb + rd * rd); + ashearY = 0; + arotation = 90 - MathUtils.Atan2(rd, rb) * MathUtils.RadDeg; + } + } + + public void WorldToLocal(float worldX, float worldY, out float localX, out float localY) + { + float a = this.a, b = this.b, c = this.c, d = this.d; + float invDet = 1 / (a * d - b * c); + float x = worldX - this.worldX, y = worldY - this.worldY; + localX = (x * d * invDet - y * b * invDet); + localY = (y * a * invDet - x * c * invDet); + } + + public void LocalToWorld(float localX, float localY, out float worldX, out float worldY) + { + worldX = localX * a + localY * b + this.worldX; + worldY = localX * c + localY * d + this.worldY; + } + + public float WorldToLocalRotationX + { + get + { + Bone parent = this.parent; + if (parent == null) return arotation; + float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, a = this.a, c = this.c; + return MathUtils.Atan2(pa * c - pc * a, pd * a - pb * c) * MathUtils.RadDeg; + } + } + + public float WorldToLocalRotationY + { + get + { + Bone parent = this.parent; + if (parent == null) return arotation; + float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, b = this.b, d = this.d; + return MathUtils.Atan2(pa * d - pc * b, pd * b - pb * d) * MathUtils.RadDeg; + } + } + + public float WorldToLocalRotation(float worldRotation) + { + float sin = MathUtils.SinDeg(worldRotation), cos = MathUtils.CosDeg(worldRotation); + return MathUtils.Atan2(a * sin - c * cos, d * cos - b * sin) * MathUtils.RadDeg + rotation - shearX; + } + + public float LocalToWorldRotation(float localRotation) + { + localRotation -= rotation - shearX; + float sin = MathUtils.SinDeg(localRotation), cos = MathUtils.CosDeg(localRotation); + return MathUtils.Atan2(cos * c + sin * d, cos * a + sin * b) * MathUtils.RadDeg; + } + + /// + /// Rotates the world transform the specified amount and sets isAppliedValid to false. + /// + /// Degrees. + public void RotateWorld(float degrees) + { + float a = this.a, b = this.b, c = this.c, d = this.d; + float cos = MathUtils.CosDeg(degrees), sin = MathUtils.SinDeg(degrees); + this.a = cos * a - sin * c; + this.b = cos * b - sin * d; + this.c = sin * a + cos * c; + this.d = sin * b + cos * d; + appliedValid = false; + } + + override public string ToString() + { + return data.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/BoneData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/BoneData.cs index 3b8ab4c..06edf43 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/BoneData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/BoneData.cs @@ -29,71 +29,76 @@ using System; -namespace Spine3_7_94 { - public class BoneData { - internal int index; - internal string name; - internal BoneData parent; - internal float length; - internal float x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY; - internal TransformMode transformMode = TransformMode.Normal; - - /// The index of the bone in Skeleton.Bones - public int Index { get { return index; } } - - /// The name of the bone, which is unique within the skeleton. - public string Name { get { return name; } } - - /// May be null. - public BoneData Parent { get { return parent; } } - - public float Length { get { return length; } set { length = value; } } - - /// Local X translation. - public float X { get { return x; } set { x = value; } } - - /// Local Y translation. - public float Y { get { return y; } set { y = value; } } - - /// Local rotation. - public float Rotation { get { return rotation; } set { rotation = value; } } - - /// Local scaleX. - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - - /// Local scaleY. - public float ScaleY { get { return scaleY; } set { scaleY = value; } } - - /// Local shearX. - public float ShearX { get { return shearX; } set { shearX = value; } } - - /// Local shearY. - public float ShearY { get { return shearY; } set { shearY = value; } } - - /// The transform mode for how parent world transforms affect this bone. - public TransformMode TransformMode { get { return transformMode; } set { transformMode = value; } } - - /// May be null. - public BoneData (int index, string name, BoneData parent) { - if (index < 0) throw new ArgumentException("index must be >= 0", "index"); - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.index = index; - this.name = name; - this.parent = parent; - } - - override public string ToString () { - return name; - } - } - - [Flags] - public enum TransformMode { - //0000 0 Flip Scale Rotation - Normal = 0, // 0000 - OnlyTranslation = 7, // 0111 - NoRotationOrReflection = 1, // 0001 - NoScale = 2, // 0010 - NoScaleOrReflection = 6, // 0110 - } +namespace Spine3_7_94 +{ + public class BoneData + { + internal int index; + internal string name; + internal BoneData parent; + internal float length; + internal float x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY; + internal TransformMode transformMode = TransformMode.Normal; + + /// The index of the bone in Skeleton.Bones + public int Index { get { return index; } } + + /// The name of the bone, which is unique within the skeleton. + public string Name { get { return name; } } + + /// May be null. + public BoneData Parent { get { return parent; } } + + public float Length { get { return length; } set { length = value; } } + + /// Local X translation. + public float X { get { return x; } set { x = value; } } + + /// Local Y translation. + public float Y { get { return y; } set { y = value; } } + + /// Local rotation. + public float Rotation { get { return rotation; } set { rotation = value; } } + + /// Local scaleX. + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + + /// Local scaleY. + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + + /// Local shearX. + public float ShearX { get { return shearX; } set { shearX = value; } } + + /// Local shearY. + public float ShearY { get { return shearY; } set { shearY = value; } } + + /// The transform mode for how parent world transforms affect this bone. + public TransformMode TransformMode { get { return transformMode; } set { transformMode = value; } } + + /// May be null. + public BoneData(int index, string name, BoneData parent) + { + if (index < 0) throw new ArgumentException("index must be >= 0", "index"); + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.index = index; + this.name = name; + this.parent = parent; + } + + override public string ToString() + { + return name; + } + } + + [Flags] + public enum TransformMode + { + //0000 0 Flip Scale Rotation + Normal = 0, // 0000 + OnlyTranslation = 7, // 0111 + NoRotationOrReflection = 1, // 0001 + NoScale = 2, // 0010 + NoScaleOrReflection = 6, // 0110 + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Event.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Event.cs index 5792e0d..d8d099a 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Event.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Event.cs @@ -29,36 +29,40 @@ using System; -namespace Spine3_7_94 { - /// Stores the current pose values for an Event. - public class Event { - internal readonly EventData data; - internal readonly float time; - internal int intValue; - internal float floatValue; - internal string stringValue; - internal float volume; - internal float balance; +namespace Spine3_7_94 +{ + /// Stores the current pose values for an Event. + public class Event + { + internal readonly EventData data; + internal readonly float time; + internal int intValue; + internal float floatValue; + internal string stringValue; + internal float volume; + internal float balance; - public EventData Data { get { return data; } } - /// The animation time this event was keyed. - public float Time { get { return time; } } + public EventData Data { get { return data; } } + /// The animation time this event was keyed. + public float Time { get { return time; } } - public int Int { get { return intValue; } set { intValue = value; } } - public float Float { get { return floatValue; } set { floatValue = value; } } - public string String { get { return stringValue; } set { stringValue = value; } } + public int Int { get { return intValue; } set { intValue = value; } } + public float Float { get { return floatValue; } set { floatValue = value; } } + public string String { get { return stringValue; } set { stringValue = value; } } - public float Volume { get { return volume; } set { volume = value; } } - public float Balance { get { return balance; } set { balance = value; } } + public float Volume { get { return volume; } set { volume = value; } } + public float Balance { get { return balance; } set { balance = value; } } - public Event (float time, EventData data) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - this.time = time; - this.data = data; - } + public Event(float time, EventData data) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + this.time = time; + this.data = data; + } - override public string ToString () { - return this.data.Name; - } - } + override public string ToString() + { + return this.data.Name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/EventData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/EventData.cs index 477cd84..3855cd5 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/EventData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/EventData.cs @@ -29,28 +29,32 @@ using System; -namespace Spine3_7_94 { - /// Stores the setup pose values for an Event. - public class EventData { - internal string name; +namespace Spine3_7_94 +{ + /// Stores the setup pose values for an Event. + public class EventData + { + internal string name; - /// The name of the event, which is unique within the skeleton. - public string Name { get { return name; } } - public int Int { get; set; } - public float Float { get; set; } - public string @String { get; set; } + /// The name of the event, which is unique within the skeleton. + public string Name { get { return name; } } + public int Int { get; set; } + public float Float { get; set; } + public string @String { get; set; } - public string AudioPath { get; set; } - public float Volume { get; set; } - public float Balance { get; set; } + public string AudioPath { get; set; } + public float Volume { get; set; } + public float Balance { get; set; } - public EventData (string name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; - } + public EventData(string name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + } - override public string ToString () { - return Name; - } - } + override public string ToString() + { + return Name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/ExposedList.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/ExposedList.cs index 83d98af..869439e 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/ExposedList.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/ExposedList.cs @@ -35,590 +35,686 @@ using System.Collections.Generic; using System.Diagnostics; -namespace Spine3_7_94 { - [DebuggerDisplay("Count={Count}")] - public class ExposedList : IEnumerable { - public T[] Items; - public int Count; - private const int DefaultCapacity = 4; - private static readonly T[] EmptyArray = new T[0]; - private int version; - - public ExposedList () { - Items = EmptyArray; - } - - public ExposedList (IEnumerable collection) { - CheckCollection(collection); - - // initialize to needed size (if determinable) - ICollection c = collection as ICollection; - if (c == null) { - Items = EmptyArray; - AddEnumerable(collection); - } else { - Items = new T[c.Count]; - AddCollection(c); - } - } - - public ExposedList (int capacity) { - if (capacity < 0) - throw new ArgumentOutOfRangeException("capacity"); - Items = new T[capacity]; - } - - internal ExposedList (T[] data, int size) { - Items = data; - Count = size; - } - - public void Add (T item) { - // If we check to see if we need to grow before trying to grow - // we can speed things up by 25% - if (Count == Items.Length) - GrowIfNeeded(1); - Items[Count++] = item; - version++; - } - - public void GrowIfNeeded (int addedCount) { - int minimumSize = Count + addedCount; - if (minimumSize > Items.Length) - Capacity = Math.Max(Math.Max(Capacity * 2, DefaultCapacity), minimumSize); - } - - public ExposedList Resize (int newSize) { - int itemsLength = Items.Length; - var oldItems = Items; - if (newSize > itemsLength) { - Array.Resize(ref Items, newSize); -// var newItems = new T[newSize]; -// Array.Copy(oldItems, newItems, Count); -// Items = newItems; - } else if (newSize < itemsLength) { - // Allow nulling of T reference type to allow GC. - for (int i = newSize; i < itemsLength; i++) - oldItems[i] = default(T); - } - Count = newSize; - return this; - } - - public void EnsureCapacity (int min) { - if (Items.Length < min) { - int newCapacity = Items.Length == 0 ? DefaultCapacity : Items.Length * 2; - //if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength; - if (newCapacity < min) newCapacity = min; - Capacity = newCapacity; - } - } - - private void CheckRange (int index, int count) { - if (index < 0) - throw new ArgumentOutOfRangeException("index"); - - if (count < 0) - throw new ArgumentOutOfRangeException("count"); - - if ((uint)index + (uint)count > (uint)Count) - throw new ArgumentException("index and count exceed length of list"); - } - - private void AddCollection (ICollection collection) { - int collectionCount = collection.Count; - if (collectionCount == 0) - return; - - GrowIfNeeded(collectionCount); - collection.CopyTo(Items, Count); - Count += collectionCount; - } - - private void AddEnumerable (IEnumerable enumerable) { - foreach (T t in enumerable) { - Add(t); - } - } - - public void AddRange (IEnumerable collection) { - CheckCollection(collection); - - ICollection c = collection as ICollection; - if (c != null) - AddCollection(c); - else - AddEnumerable(collection); - version++; - } - - public int BinarySearch (T item) { - return Array.BinarySearch(Items, 0, Count, item); - } - - public int BinarySearch (T item, IComparer comparer) { - return Array.BinarySearch(Items, 0, Count, item, comparer); - } - - public int BinarySearch (int index, int count, T item, IComparer comparer) { - CheckRange(index, count); - return Array.BinarySearch(Items, index, count, item, comparer); - } - - public void Clear (bool clearArray = true) { - if (clearArray) - Array.Clear(Items, 0, Items.Length); - - Count = 0; - version++; - } - - public bool Contains (T item) { - return Array.IndexOf(Items, item, 0, Count) != -1; - } - - public ExposedList ConvertAll (Converter converter) { - if (converter == null) - throw new ArgumentNullException("converter"); - ExposedList u = new ExposedList(Count); - for (int i = 0; i < Count; i++) - u.Items[i] = converter(Items[i]); - - u.Count = Count; - return u; - } - - public void CopyTo (T[] array) { - Array.Copy(Items, 0, array, 0, Count); - } - - public void CopyTo (T[] array, int arrayIndex) { - Array.Copy(Items, 0, array, arrayIndex, Count); - } - - public void CopyTo (int index, T[] array, int arrayIndex, int count) { - CheckRange(index, count); - Array.Copy(Items, index, array, arrayIndex, count); - } - - - - public bool Exists (Predicate match) { - CheckMatch(match); - return GetIndex(0, Count, match) != -1; - } - - public T Find (Predicate match) { - CheckMatch(match); - int i = GetIndex(0, Count, match); - return (i != -1) ? Items[i] : default(T); - } - - private static void CheckMatch (Predicate match) { - if (match == null) - throw new ArgumentNullException("match"); - } - - public ExposedList FindAll (Predicate match) { - CheckMatch(match); - return FindAllList(match); - } - - private ExposedList FindAllList (Predicate match) { - ExposedList results = new ExposedList(); - for (int i = 0; i < Count; i++) - if (match(Items[i])) - results.Add(Items[i]); - - return results; - } - - public int FindIndex (Predicate match) { - CheckMatch(match); - return GetIndex(0, Count, match); - } - - public int FindIndex (int startIndex, Predicate match) { - CheckMatch(match); - CheckIndex(startIndex); - return GetIndex(startIndex, Count - startIndex, match); - } - - public int FindIndex (int startIndex, int count, Predicate match) { - CheckMatch(match); - CheckRange(startIndex, count); - return GetIndex(startIndex, count, match); - } - - private int GetIndex (int startIndex, int count, Predicate match) { - int end = startIndex + count; - for (int i = startIndex; i < end; i++) - if (match(Items[i])) - return i; - - return -1; - } - - public T FindLast (Predicate match) { - CheckMatch(match); - int i = GetLastIndex(0, Count, match); - return i == -1 ? default(T) : Items[i]; - } - - public int FindLastIndex (Predicate match) { - CheckMatch(match); - return GetLastIndex(0, Count, match); - } - - public int FindLastIndex (int startIndex, Predicate match) { - CheckMatch(match); - CheckIndex(startIndex); - return GetLastIndex(0, startIndex + 1, match); - } - - public int FindLastIndex (int startIndex, int count, Predicate match) { - CheckMatch(match); - int start = startIndex - count + 1; - CheckRange(start, count); - return GetLastIndex(start, count, match); - } - - private int GetLastIndex (int startIndex, int count, Predicate match) { - // unlike FindLastIndex, takes regular params for search range - for (int i = startIndex + count; i != startIndex; ) - if (match(Items[--i])) - return i; - return -1; - } - - public void ForEach (Action action) { - if (action == null) - throw new ArgumentNullException("action"); - for (int i = 0; i < Count; i++) - action(Items[i]); - } - - public Enumerator GetEnumerator () { - return new Enumerator(this); - } - - public ExposedList GetRange (int index, int count) { - CheckRange(index, count); - T[] tmpArray = new T[count]; - Array.Copy(Items, index, tmpArray, 0, count); - return new ExposedList(tmpArray, count); - } - - public int IndexOf (T item) { - return Array.IndexOf(Items, item, 0, Count); - } - - public int IndexOf (T item, int index) { - CheckIndex(index); - return Array.IndexOf(Items, item, index, Count - index); - } - - public int IndexOf (T item, int index, int count) { - if (index < 0) - throw new ArgumentOutOfRangeException("index"); - - if (count < 0) - throw new ArgumentOutOfRangeException("count"); - - if ((uint)index + (uint)count > (uint)Count) - throw new ArgumentOutOfRangeException("index and count exceed length of list"); - - return Array.IndexOf(Items, item, index, count); - } - - private void Shift (int start, int delta) { - if (delta < 0) - start -= delta; - - if (start < Count) - Array.Copy(Items, start, Items, start + delta, Count - start); - - Count += delta; - - if (delta < 0) - Array.Clear(Items, Count, -delta); - } - - private void CheckIndex (int index) { - if (index < 0 || (uint)index > (uint)Count) - throw new ArgumentOutOfRangeException("index"); - } - - public void Insert (int index, T item) { - CheckIndex(index); - if (Count == Items.Length) - GrowIfNeeded(1); - Shift(index, 1); - Items[index] = item; - version++; - } - - private void CheckCollection (IEnumerable collection) { - if (collection == null) - throw new ArgumentNullException("collection"); - } - - public void InsertRange (int index, IEnumerable collection) { - CheckCollection(collection); - CheckIndex(index); - if (collection == this) { - T[] buffer = new T[Count]; - CopyTo(buffer, 0); - GrowIfNeeded(Count); - Shift(index, buffer.Length); - Array.Copy(buffer, 0, Items, index, buffer.Length); - } else { - ICollection c = collection as ICollection; - if (c != null) - InsertCollection(index, c); - else - InsertEnumeration(index, collection); - } - version++; - } - - private void InsertCollection (int index, ICollection collection) { - int collectionCount = collection.Count; - GrowIfNeeded(collectionCount); - - Shift(index, collectionCount); - collection.CopyTo(Items, index); - } - - private void InsertEnumeration (int index, IEnumerable enumerable) { - foreach (T t in enumerable) - Insert(index++, t); - } - - public int LastIndexOf (T item) { - return Array.LastIndexOf(Items, item, Count - 1, Count); - } - - public int LastIndexOf (T item, int index) { - CheckIndex(index); - return Array.LastIndexOf(Items, item, index, index + 1); - } - - public int LastIndexOf (T item, int index, int count) { - if (index < 0) - throw new ArgumentOutOfRangeException("index", index, "index is negative"); - - if (count < 0) - throw new ArgumentOutOfRangeException("count", count, "count is negative"); - - if (index - count + 1 < 0) - throw new ArgumentOutOfRangeException("count", count, "count is too large"); - - return Array.LastIndexOf(Items, item, index, count); - } - - public bool Remove (T item) { - int loc = IndexOf(item); - if (loc != -1) - RemoveAt(loc); - - return loc != -1; - } - - public int RemoveAll (Predicate match) { - CheckMatch(match); - int i = 0; - int j = 0; - - // Find the first item to remove - for (i = 0; i < Count; i++) - if (match(Items[i])) - break; - - if (i == Count) - return 0; - - version++; - - // Remove any additional items - for (j = i + 1; j < Count; j++) { - if (!match(Items[j])) - Items[i++] = Items[j]; - } - if (j - i > 0) - Array.Clear(Items, i, j - i); - - Count = i; - return (j - i); - } - - public void RemoveAt (int index) { - if (index < 0 || (uint)index >= (uint)Count) - throw new ArgumentOutOfRangeException("index"); - Shift(index, -1); - Array.Clear(Items, Count, 1); - version++; - } - - // Spine Added Method - // Based on Stack.Pop(); https://referencesource.microsoft.com/#mscorlib/system/collections/stack.cs - /// Pops the last item of the list. If the list is empty, Pop throws an InvalidOperationException. - public T Pop () { - if (Count == 0) - throw new InvalidOperationException("List is empty. Nothing to pop."); - - int i = Count - 1; - T item = Items[i]; - Items[i] = default(T); - Count--; - version++; - return item; - } - - public void RemoveRange (int index, int count) { - CheckRange(index, count); - if (count > 0) { - Shift(index, -count); - Array.Clear(Items, Count, count); - version++; - } - } - - public void Reverse () { - Array.Reverse(Items, 0, Count); - version++; - } - - public void Reverse (int index, int count) { - CheckRange(index, count); - Array.Reverse(Items, index, count); - version++; - } - - public void Sort () { - Array.Sort(Items, 0, Count, Comparer.Default); - version++; - } - - public void Sort (IComparer comparer) { - Array.Sort(Items, 0, Count, comparer); - version++; - } - - public void Sort (Comparison comparison) { - Array.Sort(Items, comparison); - version++; - } - - public void Sort (int index, int count, IComparer comparer) { - CheckRange(index, count); - Array.Sort(Items, index, count, comparer); - version++; - } - - public T[] ToArray () { - T[] t = new T[Count]; - Array.Copy(Items, t, Count); - - return t; - } - - public void TrimExcess () { - Capacity = Count; - } - - public bool TrueForAll (Predicate match) { - CheckMatch(match); - - for (int i = 0; i < Count; i++) - if (!match(Items[i])) - return false; - - return true; - } - - public int Capacity { - get { - return Items.Length; - } - set { - if ((uint)value < (uint)Count) - throw new ArgumentOutOfRangeException(); - - Array.Resize(ref Items, value); - } - } - - #region Interface implementations. - - IEnumerator IEnumerable.GetEnumerator () { - return GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator () { - return GetEnumerator(); - } - - #endregion - - public struct Enumerator : IEnumerator, IDisposable { - private ExposedList l; - private int next; - private int ver; - private T current; - - internal Enumerator (ExposedList l) - : this() { - this.l = l; - ver = l.version; - } - - public void Dispose () { - l = null; - } - - private void VerifyState () { - if (l == null) - throw new ObjectDisposedException(GetType().FullName); - if (ver != l.version) - throw new InvalidOperationException( - "Collection was modified; enumeration operation may not execute."); - } - - public bool MoveNext () { - VerifyState(); - - if (next < 0) - return false; - - if (next < l.Count) { - current = l.Items[next++]; - return true; - } - - next = -1; - return false; - } - - public T Current { - get { - return current; - } - } - - void IEnumerator.Reset () { - VerifyState(); - next = 0; - } - - object IEnumerator.Current { - get { - VerifyState(); - if (next <= 0) - throw new InvalidOperationException(); - return current; - } - } - } - } +namespace Spine3_7_94 +{ + [DebuggerDisplay("Count={Count}")] + public class ExposedList : IEnumerable + { + public T[] Items; + public int Count; + private const int DefaultCapacity = 4; + private static readonly T[] EmptyArray = new T[0]; + private int version; + + public ExposedList() + { + Items = EmptyArray; + } + + public ExposedList(IEnumerable collection) + { + CheckCollection(collection); + + // initialize to needed size (if determinable) + ICollection c = collection as ICollection; + if (c == null) + { + Items = EmptyArray; + AddEnumerable(collection); + } + else + { + Items = new T[c.Count]; + AddCollection(c); + } + } + + public ExposedList(int capacity) + { + if (capacity < 0) + throw new ArgumentOutOfRangeException("capacity"); + Items = new T[capacity]; + } + + internal ExposedList(T[] data, int size) + { + Items = data; + Count = size; + } + + public void Add(T item) + { + // If we check to see if we need to grow before trying to grow + // we can speed things up by 25% + if (Count == Items.Length) + GrowIfNeeded(1); + Items[Count++] = item; + version++; + } + + public void GrowIfNeeded(int addedCount) + { + int minimumSize = Count + addedCount; + if (minimumSize > Items.Length) + Capacity = Math.Max(Math.Max(Capacity * 2, DefaultCapacity), minimumSize); + } + + public ExposedList Resize(int newSize) + { + int itemsLength = Items.Length; + var oldItems = Items; + if (newSize > itemsLength) + { + Array.Resize(ref Items, newSize); + // var newItems = new T[newSize]; + // Array.Copy(oldItems, newItems, Count); + // Items = newItems; + } + else if (newSize < itemsLength) + { + // Allow nulling of T reference type to allow GC. + for (int i = newSize; i < itemsLength; i++) + oldItems[i] = default(T); + } + Count = newSize; + return this; + } + + public void EnsureCapacity(int min) + { + if (Items.Length < min) + { + int newCapacity = Items.Length == 0 ? DefaultCapacity : Items.Length * 2; + //if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength; + if (newCapacity < min) newCapacity = min; + Capacity = newCapacity; + } + } + + private void CheckRange(int index, int count) + { + if (index < 0) + throw new ArgumentOutOfRangeException("index"); + + if (count < 0) + throw new ArgumentOutOfRangeException("count"); + + if ((uint)index + (uint)count > (uint)Count) + throw new ArgumentException("index and count exceed length of list"); + } + + private void AddCollection(ICollection collection) + { + int collectionCount = collection.Count; + if (collectionCount == 0) + return; + + GrowIfNeeded(collectionCount); + collection.CopyTo(Items, Count); + Count += collectionCount; + } + + private void AddEnumerable(IEnumerable enumerable) + { + foreach (T t in enumerable) + { + Add(t); + } + } + + public void AddRange(IEnumerable collection) + { + CheckCollection(collection); + + ICollection c = collection as ICollection; + if (c != null) + AddCollection(c); + else + AddEnumerable(collection); + version++; + } + + public int BinarySearch(T item) + { + return Array.BinarySearch(Items, 0, Count, item); + } + + public int BinarySearch(T item, IComparer comparer) + { + return Array.BinarySearch(Items, 0, Count, item, comparer); + } + + public int BinarySearch(int index, int count, T item, IComparer comparer) + { + CheckRange(index, count); + return Array.BinarySearch(Items, index, count, item, comparer); + } + + public void Clear(bool clearArray = true) + { + if (clearArray) + Array.Clear(Items, 0, Items.Length); + + Count = 0; + version++; + } + + public bool Contains(T item) + { + return Array.IndexOf(Items, item, 0, Count) != -1; + } + + public ExposedList ConvertAll(Converter converter) + { + if (converter == null) + throw new ArgumentNullException("converter"); + ExposedList u = new ExposedList(Count); + for (int i = 0; i < Count; i++) + u.Items[i] = converter(Items[i]); + + u.Count = Count; + return u; + } + + public void CopyTo(T[] array) + { + Array.Copy(Items, 0, array, 0, Count); + } + + public void CopyTo(T[] array, int arrayIndex) + { + Array.Copy(Items, 0, array, arrayIndex, Count); + } + + public void CopyTo(int index, T[] array, int arrayIndex, int count) + { + CheckRange(index, count); + Array.Copy(Items, index, array, arrayIndex, count); + } + + + + public bool Exists(Predicate match) + { + CheckMatch(match); + return GetIndex(0, Count, match) != -1; + } + + public T Find(Predicate match) + { + CheckMatch(match); + int i = GetIndex(0, Count, match); + return (i != -1) ? Items[i] : default(T); + } + + private static void CheckMatch(Predicate match) + { + if (match == null) + throw new ArgumentNullException("match"); + } + + public ExposedList FindAll(Predicate match) + { + CheckMatch(match); + return FindAllList(match); + } + + private ExposedList FindAllList(Predicate match) + { + ExposedList results = new ExposedList(); + for (int i = 0; i < Count; i++) + if (match(Items[i])) + results.Add(Items[i]); + + return results; + } + + public int FindIndex(Predicate match) + { + CheckMatch(match); + return GetIndex(0, Count, match); + } + + public int FindIndex(int startIndex, Predicate match) + { + CheckMatch(match); + CheckIndex(startIndex); + return GetIndex(startIndex, Count - startIndex, match); + } + + public int FindIndex(int startIndex, int count, Predicate match) + { + CheckMatch(match); + CheckRange(startIndex, count); + return GetIndex(startIndex, count, match); + } + + private int GetIndex(int startIndex, int count, Predicate match) + { + int end = startIndex + count; + for (int i = startIndex; i < end; i++) + if (match(Items[i])) + return i; + + return -1; + } + + public T FindLast(Predicate match) + { + CheckMatch(match); + int i = GetLastIndex(0, Count, match); + return i == -1 ? default(T) : Items[i]; + } + + public int FindLastIndex(Predicate match) + { + CheckMatch(match); + return GetLastIndex(0, Count, match); + } + + public int FindLastIndex(int startIndex, Predicate match) + { + CheckMatch(match); + CheckIndex(startIndex); + return GetLastIndex(0, startIndex + 1, match); + } + + public int FindLastIndex(int startIndex, int count, Predicate match) + { + CheckMatch(match); + int start = startIndex - count + 1; + CheckRange(start, count); + return GetLastIndex(start, count, match); + } + + private int GetLastIndex(int startIndex, int count, Predicate match) + { + // unlike FindLastIndex, takes regular params for search range + for (int i = startIndex + count; i != startIndex;) + if (match(Items[--i])) + return i; + return -1; + } + + public void ForEach(Action action) + { + if (action == null) + throw new ArgumentNullException("action"); + for (int i = 0; i < Count; i++) + action(Items[i]); + } + + public Enumerator GetEnumerator() + { + return new Enumerator(this); + } + + public ExposedList GetRange(int index, int count) + { + CheckRange(index, count); + T[] tmpArray = new T[count]; + Array.Copy(Items, index, tmpArray, 0, count); + return new ExposedList(tmpArray, count); + } + + public int IndexOf(T item) + { + return Array.IndexOf(Items, item, 0, Count); + } + + public int IndexOf(T item, int index) + { + CheckIndex(index); + return Array.IndexOf(Items, item, index, Count - index); + } + + public int IndexOf(T item, int index, int count) + { + if (index < 0) + throw new ArgumentOutOfRangeException("index"); + + if (count < 0) + throw new ArgumentOutOfRangeException("count"); + + if ((uint)index + (uint)count > (uint)Count) + throw new ArgumentOutOfRangeException("index and count exceed length of list"); + + return Array.IndexOf(Items, item, index, count); + } + + private void Shift(int start, int delta) + { + if (delta < 0) + start -= delta; + + if (start < Count) + Array.Copy(Items, start, Items, start + delta, Count - start); + + Count += delta; + + if (delta < 0) + Array.Clear(Items, Count, -delta); + } + + private void CheckIndex(int index) + { + if (index < 0 || (uint)index > (uint)Count) + throw new ArgumentOutOfRangeException("index"); + } + + public void Insert(int index, T item) + { + CheckIndex(index); + if (Count == Items.Length) + GrowIfNeeded(1); + Shift(index, 1); + Items[index] = item; + version++; + } + + private void CheckCollection(IEnumerable collection) + { + if (collection == null) + throw new ArgumentNullException("collection"); + } + + public void InsertRange(int index, IEnumerable collection) + { + CheckCollection(collection); + CheckIndex(index); + if (collection == this) + { + T[] buffer = new T[Count]; + CopyTo(buffer, 0); + GrowIfNeeded(Count); + Shift(index, buffer.Length); + Array.Copy(buffer, 0, Items, index, buffer.Length); + } + else + { + ICollection c = collection as ICollection; + if (c != null) + InsertCollection(index, c); + else + InsertEnumeration(index, collection); + } + version++; + } + + private void InsertCollection(int index, ICollection collection) + { + int collectionCount = collection.Count; + GrowIfNeeded(collectionCount); + + Shift(index, collectionCount); + collection.CopyTo(Items, index); + } + + private void InsertEnumeration(int index, IEnumerable enumerable) + { + foreach (T t in enumerable) + Insert(index++, t); + } + + public int LastIndexOf(T item) + { + return Array.LastIndexOf(Items, item, Count - 1, Count); + } + + public int LastIndexOf(T item, int index) + { + CheckIndex(index); + return Array.LastIndexOf(Items, item, index, index + 1); + } + + public int LastIndexOf(T item, int index, int count) + { + if (index < 0) + throw new ArgumentOutOfRangeException("index", index, "index is negative"); + + if (count < 0) + throw new ArgumentOutOfRangeException("count", count, "count is negative"); + + if (index - count + 1 < 0) + throw new ArgumentOutOfRangeException("count", count, "count is too large"); + + return Array.LastIndexOf(Items, item, index, count); + } + + public bool Remove(T item) + { + int loc = IndexOf(item); + if (loc != -1) + RemoveAt(loc); + + return loc != -1; + } + + public int RemoveAll(Predicate match) + { + CheckMatch(match); + int i = 0; + int j = 0; + + // Find the first item to remove + for (i = 0; i < Count; i++) + if (match(Items[i])) + break; + + if (i == Count) + return 0; + + version++; + + // Remove any additional items + for (j = i + 1; j < Count; j++) + { + if (!match(Items[j])) + Items[i++] = Items[j]; + } + if (j - i > 0) + Array.Clear(Items, i, j - i); + + Count = i; + return (j - i); + } + + public void RemoveAt(int index) + { + if (index < 0 || (uint)index >= (uint)Count) + throw new ArgumentOutOfRangeException("index"); + Shift(index, -1); + Array.Clear(Items, Count, 1); + version++; + } + + // Spine Added Method + // Based on Stack.Pop(); https://referencesource.microsoft.com/#mscorlib/system/collections/stack.cs + /// Pops the last item of the list. If the list is empty, Pop throws an InvalidOperationException. + public T Pop() + { + if (Count == 0) + throw new InvalidOperationException("List is empty. Nothing to pop."); + + int i = Count - 1; + T item = Items[i]; + Items[i] = default(T); + Count--; + version++; + return item; + } + + public void RemoveRange(int index, int count) + { + CheckRange(index, count); + if (count > 0) + { + Shift(index, -count); + Array.Clear(Items, Count, count); + version++; + } + } + + public void Reverse() + { + Array.Reverse(Items, 0, Count); + version++; + } + + public void Reverse(int index, int count) + { + CheckRange(index, count); + Array.Reverse(Items, index, count); + version++; + } + + public void Sort() + { + Array.Sort(Items, 0, Count, Comparer.Default); + version++; + } + + public void Sort(IComparer comparer) + { + Array.Sort(Items, 0, Count, comparer); + version++; + } + + public void Sort(Comparison comparison) + { + Array.Sort(Items, comparison); + version++; + } + + public void Sort(int index, int count, IComparer comparer) + { + CheckRange(index, count); + Array.Sort(Items, index, count, comparer); + version++; + } + + public T[] ToArray() + { + T[] t = new T[Count]; + Array.Copy(Items, t, Count); + + return t; + } + + public void TrimExcess() + { + Capacity = Count; + } + + public bool TrueForAll(Predicate match) + { + CheckMatch(match); + + for (int i = 0; i < Count; i++) + if (!match(Items[i])) + return false; + + return true; + } + + public int Capacity + { + get + { + return Items.Length; + } + set + { + if ((uint)value < (uint)Count) + throw new ArgumentOutOfRangeException(); + + Array.Resize(ref Items, value); + } + } + + #region Interface implementations. + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion + + public struct Enumerator : IEnumerator, IDisposable + { + private ExposedList l; + private int next; + private int ver; + private T current; + + internal Enumerator(ExposedList l) + : this() + { + this.l = l; + ver = l.version; + } + + public void Dispose() + { + l = null; + } + + private void VerifyState() + { + if (l == null) + throw new ObjectDisposedException(GetType().FullName); + if (ver != l.version) + throw new InvalidOperationException( + "Collection was modified; enumeration operation may not execute."); + } + + public bool MoveNext() + { + VerifyState(); + + if (next < 0) + return false; + + if (next < l.Count) + { + current = l.Items[next++]; + return true; + } + + next = -1; + return false; + } + + public T Current + { + get + { + return current; + } + } + + void IEnumerator.Reset() + { + VerifyState(); + next = 0; + } + + object IEnumerator.Current + { + get + { + VerifyState(); + if (next <= 0) + throw new InvalidOperationException(); + return current; + } + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/IConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/IConstraint.cs index f554879..9a1a85a 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/IConstraint.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/IConstraint.cs @@ -27,13 +27,15 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine3_7_94 { - - /// The interface for all constraints. - public interface IConstraint : IUpdatable { - /// The ordinal for the order a skeleton's constraints will be applied. - int Order { get; } +namespace Spine3_7_94 +{ - } + /// The interface for all constraints. + public interface IConstraint : IUpdatable + { + /// The ordinal for the order a skeleton's constraints will be applied. + int Order { get; } + + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/IUpdatable.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/IUpdatable.cs index 17cbaae..f5b02a3 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/IUpdatable.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/IUpdatable.cs @@ -27,8 +27,10 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine3_7_94 { - public interface IUpdatable { - void Update (); - } +namespace Spine3_7_94 +{ + public interface IUpdatable + { + void Update(); + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/IkConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/IkConstraint.cs index 1c20bb6..7c27541 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/IkConstraint.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/IkConstraint.cs @@ -29,310 +29,356 @@ using System; -namespace Spine3_7_94 { - /// - /// - /// Stores the current pose for an IK constraint. An IK constraint adjusts the rotation of 1 or 2 constrained bones so the tip of - /// the last bone is as close to the target bone as possible. - /// - /// See IK constraints in the Spine User Guide. - /// - public class IkConstraint : IConstraint { - internal IkConstraintData data; - internal ExposedList bones = new ExposedList(); - internal Bone target; - internal int bendDirection; - internal bool compress, stretch; - internal float mix = 1; +namespace Spine3_7_94 +{ + /// + /// + /// Stores the current pose for an IK constraint. An IK constraint adjusts the rotation of 1 or 2 constrained bones so the tip of + /// the last bone is as close to the target bone as possible. + /// + /// See IK constraints in the Spine User Guide. + /// + public class IkConstraint : IConstraint + { + internal IkConstraintData data; + internal ExposedList bones = new ExposedList(); + internal Bone target; + internal int bendDirection; + internal bool compress, stretch; + internal float mix = 1; - public IkConstraint (IkConstraintData data, Skeleton skeleton) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.data = data; - mix = data.mix; - bendDirection = data.bendDirection; - compress = data.compress; - stretch = data.stretch; + public IkConstraint(IkConstraintData data, Skeleton skeleton) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + mix = data.mix; + bendDirection = data.bendDirection; + compress = data.compress; + stretch = data.stretch; - bones = new ExposedList(data.bones.Count); - foreach (BoneData boneData in data.bones) - bones.Add(skeleton.FindBone(boneData.name)); - target = skeleton.FindBone(data.target.name); - } + bones = new ExposedList(data.bones.Count); + foreach (BoneData boneData in data.bones) + bones.Add(skeleton.FindBone(boneData.name)); + target = skeleton.FindBone(data.target.name); + } - /// Copy constructor. - public IkConstraint (IkConstraint constraint, Skeleton skeleton) { - if (constraint == null) throw new ArgumentNullException("constraint cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); - data = constraint.data; - bones = new ExposedList(constraint.Bones.Count); - foreach (Bone bone in constraint.Bones) - bones.Add(skeleton.Bones.Items[bone.data.index]); - target = skeleton.Bones.Items[constraint.target.data.index]; - mix = constraint.mix; - bendDirection = constraint.bendDirection; - compress = constraint.compress; - stretch = constraint.stretch; - } + /// Copy constructor. + public IkConstraint(IkConstraint constraint, Skeleton skeleton) + { + if (constraint == null) throw new ArgumentNullException("constraint cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); + data = constraint.data; + bones = new ExposedList(constraint.Bones.Count); + foreach (Bone bone in constraint.Bones) + bones.Add(skeleton.Bones.Items[bone.data.index]); + target = skeleton.Bones.Items[constraint.target.data.index]; + mix = constraint.mix; + bendDirection = constraint.bendDirection; + compress = constraint.compress; + stretch = constraint.stretch; + } - /// Applies the constraint to the constrained bones. - public void Apply () { - Update(); - } + /// Applies the constraint to the constrained bones. + public void Apply() + { + Update(); + } - public void Update () { - Bone target = this.target; - ExposedList bones = this.bones; - switch (bones.Count) { - case 1: - Apply(bones.Items[0], target.worldX, target.worldY, compress, stretch, data.uniform, mix); - break; - case 2: - Apply(bones.Items[0], bones.Items[1], target.worldX, target.worldY, bendDirection, stretch, mix); - break; - } - } + public void Update() + { + Bone target = this.target; + ExposedList bones = this.bones; + switch (bones.Count) + { + case 1: + Apply(bones.Items[0], target.worldX, target.worldY, compress, stretch, data.uniform, mix); + break; + case 2: + Apply(bones.Items[0], bones.Items[1], target.worldX, target.worldY, bendDirection, stretch, mix); + break; + } + } - public int Order { - get { return data.order; } - } + public int Order + { + get { return data.order; } + } - /// The bones that will be modified by this IK constraint. - public ExposedList Bones { - get { return bones; } - } + /// The bones that will be modified by this IK constraint. + public ExposedList Bones + { + get { return bones; } + } - /// The bone that is the IK target. - public Bone Target { - get { return target; } - set { target = value; } - } + /// The bone that is the IK target. + public Bone Target + { + get { return target; } + set { target = value; } + } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotations. - public float Mix { - get { return mix; } - set { mix = value; } - } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotations. + public float Mix + { + get { return mix; } + set { mix = value; } + } - /// Controls the bend direction of the IK bones, either 1 or -1. - public int BendDirection { - get { return bendDirection; } - set { bendDirection = value; } - } + /// Controls the bend direction of the IK bones, either 1 or -1. + public int BendDirection + { + get { return bendDirection; } + set { bendDirection = value; } + } - /// - /// When true and only a single bone is being constrained, if the target is too close, the bone is scaled to reach it. - public bool Compress { - get { return compress; } - set { compress = value; } - } + /// + /// When true and only a single bone is being constrained, if the target is too close, the bone is scaled to reach it. + public bool Compress + { + get { return compress; } + set { compress = value; } + } - /// - /// When true, if the target is out of range, the parent bone is scaled to reach it. If more than one bone is being constrained - /// and the parent bone has local nonuniform scale, stretch is not applied. - public bool Stretch { - get { return stretch; } - set { stretch = value; } - } + /// + /// When true, if the target is out of range, the parent bone is scaled to reach it. If more than one bone is being constrained + /// and the parent bone has local nonuniform scale, stretch is not applied. + public bool Stretch + { + get { return stretch; } + set { stretch = value; } + } - /// The IK constraint's setup pose data. - public IkConstraintData Data { - get { return data; } - } + /// The IK constraint's setup pose data. + public IkConstraintData Data + { + get { return data; } + } - override public string ToString () { - return data.name; - } + override public string ToString() + { + return data.name; + } - /// Applies 1 bone IK. The target is specified in the world coordinate system. - static public void Apply (Bone bone, float targetX, float targetY, bool compress, bool stretch, bool uniform, - float alpha) { - if (!bone.appliedValid) bone.UpdateAppliedTransform(); - Bone p = bone.parent; + /// Applies 1 bone IK. The target is specified in the world coordinate system. + static public void Apply(Bone bone, float targetX, float targetY, bool compress, bool stretch, bool uniform, + float alpha) + { + if (!bone.appliedValid) bone.UpdateAppliedTransform(); + Bone p = bone.parent; - float pa = p.a, pb = p.b, pc = p.c, pd = p.d; - float rotationIK = -bone.ashearX - bone.arotation; - float tx = 0, ty = 0; + 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.data.transformMode) { - case TransformMode.OnlyTranslation: - tx = targetX - bone.worldX; - ty = targetY - bone.worldY; - break; - case TransformMode.NoRotationOrReflection: { - float s = Math.Abs(pa * pd - pb * pc) / (pa * pa + pc * pc); - float sa = pa / bone.skeleton.ScaleX; - float sc = pc / bone.skeleton.ScaleY; - pb = -sc * s * bone.skeleton.ScaleX; - pd = sa * s * bone.skeleton.ScaleY; - rotationIK += (float)Math.Atan2(pc, pa) * MathUtils.RadDeg; - goto default; // Fall through. - } - default: { - float x = targetX - p.worldX, y = targetY - p.worldY; - float d = pa * pd - pb * pc; - tx = (x * pd - y * pb) / d - bone.ax; - ty = (y * pa - x * pc) / d - bone.ay; + switch (bone.data.transformMode) + { + case TransformMode.OnlyTranslation: + tx = targetX - bone.worldX; + ty = targetY - bone.worldY; break; - } - } + case TransformMode.NoRotationOrReflection: + { + float s = Math.Abs(pa * pd - pb * pc) / (pa * pa + pc * pc); + float sa = pa / bone.skeleton.ScaleX; + float sc = pc / bone.skeleton.ScaleY; + pb = -sc * s * bone.skeleton.ScaleX; + pd = sa * s * bone.skeleton.ScaleY; + rotationIK += (float)Math.Atan2(pc, pa) * MathUtils.RadDeg; + goto default; // Fall through. + } + default: + { + float x = targetX - p.worldX, y = targetY - p.worldY; + float d = pa * pd - pb * pc; + tx = (x * pd - y * pb) / d - bone.ax; + ty = (y * pa - x * pc) / d - bone.ay; + break; + } + } - rotationIK += (float)Math.Atan2(ty, tx) * MathUtils.RadDeg; - if (bone.ascaleX < 0) rotationIK += 180; - if (rotationIK > 180) - rotationIK -= 360; - else if (rotationIK < -180) // - rotationIK += 360; + rotationIK += (float)Math.Atan2(ty, tx) * MathUtils.RadDeg; + if (bone.ascaleX < 0) rotationIK += 180; + if (rotationIK > 180) + rotationIK -= 360; + else if (rotationIK < -180) // + rotationIK += 360; - float sx = bone.ascaleX, sy = bone.ascaleY; - if (compress || stretch) { - switch (bone.data.transformMode) { - case TransformMode.NoScale: + float sx = bone.ascaleX, sy = bone.ascaleY; + if (compress || stretch) + { + switch (bone.data.transformMode) + { + case TransformMode.NoScale: tx = targetX - bone.worldX; ty = targetY - bone.worldY; break; case TransformMode.NoScaleOrReflection: - tx = targetX - bone.worldX; - ty = targetY - bone.worldY; + tx = targetX - bone.worldX; + ty = targetY - bone.worldY; break; - } - float b = bone.data.length * sx, dd = (float)Math.Sqrt(tx * tx + ty * ty); - if ((compress && dd < b) || (stretch && dd > b) && b > 0.0001f) { - float s = (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); - } + } + float b = bone.data.length * sx, dd = (float)Math.Sqrt(tx * tx + ty * ty); + if ((compress && dd < b) || (stretch && dd > b) && b > 0.0001f) + { + float s = (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); + } - /// Applies 2 bone IK. The target is specified in the world coordinate system. - /// A direct descendant of the parent bone. - static public void Apply (Bone parent, Bone child, float targetX, float targetY, int bendDir, bool stretch, float alpha) { - if (alpha == 0) { - child.UpdateWorldTransform(); - return; - } - if (!parent.appliedValid) parent.UpdateAppliedTransform(); - if (!child.appliedValid) child.UpdateAppliedTransform(); - float px = parent.ax, py = parent.ay, psx = parent.ascaleX, sx = psx, psy = parent.ascaleY, csx = child.ascaleX; - int os1, os2, s2; - if (psx < 0) { - psx = -psx; - os1 = 180; - s2 = -1; - } else { - os1 = 0; - s2 = 1; - } - if (psy < 0) { - psy = -psy; - s2 = -s2; - } - if (csx < 0) { - csx = -csx; - os2 = 180; - } else - os2 = 0; - float cx = child.ax, cy, cwx, cwy, a = parent.a, b = parent.b, c = parent.c, d = parent.d; - bool u = Math.Abs(psx - psy) <= 0.0001f; - if (!u) { - cy = 0; - cwx = a * cx + parent.worldX; - cwy = c * cx + parent.worldY; - } else { - cy = child.ay; - cwx = a * cx + b * cy + parent.worldX; - cwy = c * cx + d * cy + parent.worldY; - } - Bone pp = parent.parent; - a = pp.a; - b = pp.b; - c = pp.c; - d = pp.d; - float id = 1 / (a * d - b * c), x = targetX - pp.worldX, y = targetY - pp.worldY; - float tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py, dd = tx * tx + ty * ty; - x = cwx - pp.worldX; - y = cwy - pp.worldY; - float dx = (x * d - y * b) * id - px, dy = (y * a - x * c) * id - py; - float l1 = (float)Math.Sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2; - if (u) { - l2 *= psx; - float cos = (dd - l1 * l1 - l2 * l2) / (2 * l1 * l2); - if (cos < -1) - cos = -1; - else if (cos > 1) { - cos = 1; - if (stretch && l1 + l2 > 0.0001f) sx *= ((float)Math.Sqrt(dd) / (l1 + l2) - 1) * alpha + 1; - } - a2 = (float)Math.Acos(cos) * bendDir; - a = l1 + l2 * cos; - b = l2 * (float)Math.Sin(a2); - a1 = (float)Math.Atan2(ty * a - tx * b, tx * a + ty * b); - } else { - a = psx * l2; - b = psy * l2; - float aa = a * a, bb = b * b, ta = (float)Math.Atan2(ty, tx); - c = bb * l1 * l1 + aa * dd - aa * bb; - float c1 = -2 * bb * l1, c2 = bb - aa; - d = c1 * c1 - 4 * c2 * c; - if (d >= 0) { - float q = (float)Math.Sqrt(d); - if (c1 < 0) q = -q; - q = -(c1 + q) / 2; - float r0 = q / c2, r1 = c / q; - float r = Math.Abs(r0) < Math.Abs(r1) ? r0 : r1; - if (r * r <= dd) { - y = (float)Math.Sqrt(dd - r * r) * bendDir; - a1 = ta - (float)Math.Atan2(y, r); - a2 = (float)Math.Atan2(y / psy, (r - l1) / psx); - goto break_outer; // break outer; - } - } - float minAngle = MathUtils.PI, minX = l1 - a, minDist = minX * minX, minY = 0; - float maxAngle = 0, maxX = l1 + a, maxDist = maxX * maxX, maxY = 0; - c = -a * l1 / (aa - bb); - if (c >= -1 && c <= 1) { - c = (float)Math.Acos(c); - x = a * (float)Math.Cos(c) + l1; - y = b * (float)Math.Sin(c); - d = x * x + y * y; - if (d < minDist) { - minAngle = c; - minDist = d; - minX = x; - minY = y; - } - if (d > maxDist) { - maxAngle = c; - maxDist = d; - maxX = x; - maxY = y; - } - } - if (dd <= (minDist + maxDist) / 2) { - a1 = ta - (float)Math.Atan2(minY * bendDir, minX); - a2 = minAngle * bendDir; - } else { - a1 = ta - (float)Math.Atan2(maxY * bendDir, maxX); - a2 = maxAngle * bendDir; - } - } - break_outer: - float os = (float)Math.Atan2(cy, cx) * s2; - float rotation = parent.arotation; - a1 = (a1 - os) * MathUtils.RadDeg + os1 - rotation; - if (a1 > 180) - a1 -= 360; - else if (a1 < -180) a1 += 360; - parent.UpdateWorldTransform(px, py, rotation + a1 * alpha, sx, parent.ascaleY, 0, 0); - rotation = child.arotation; - a2 = ((a2 + os) * MathUtils.RadDeg - child.ashearX) * s2 + os2 - rotation; - if (a2 > 180) - a2 -= 360; - else if (a2 < -180) a2 += 360; - child.UpdateWorldTransform(cx, cy, rotation + a2 * alpha, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY); - } - } + /// Applies 2 bone IK. The target is specified in the world coordinate system. + /// A direct descendant of the parent bone. + static public void Apply(Bone parent, Bone child, float targetX, float targetY, int bendDir, bool stretch, float alpha) + { + if (alpha == 0) + { + child.UpdateWorldTransform(); + return; + } + if (!parent.appliedValid) parent.UpdateAppliedTransform(); + if (!child.appliedValid) child.UpdateAppliedTransform(); + float px = parent.ax, py = parent.ay, psx = parent.ascaleX, sx = psx, psy = parent.ascaleY, csx = child.ascaleX; + int os1, os2, s2; + if (psx < 0) + { + psx = -psx; + os1 = 180; + s2 = -1; + } + else + { + os1 = 0; + s2 = 1; + } + if (psy < 0) + { + psy = -psy; + s2 = -s2; + } + if (csx < 0) + { + csx = -csx; + os2 = 180; + } + else + os2 = 0; + float cx = child.ax, cy, cwx, cwy, a = parent.a, b = parent.b, c = parent.c, d = parent.d; + bool u = Math.Abs(psx - psy) <= 0.0001f; + if (!u) + { + cy = 0; + cwx = a * cx + parent.worldX; + cwy = c * cx + parent.worldY; + } + else + { + cy = child.ay; + cwx = a * cx + b * cy + parent.worldX; + cwy = c * cx + d * cy + parent.worldY; + } + Bone pp = parent.parent; + a = pp.a; + b = pp.b; + c = pp.c; + d = pp.d; + float id = 1 / (a * d - b * c), x = targetX - pp.worldX, y = targetY - pp.worldY; + float tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py, dd = tx * tx + ty * ty; + x = cwx - pp.worldX; + y = cwy - pp.worldY; + float dx = (x * d - y * b) * id - px, dy = (y * a - x * c) * id - py; + float l1 = (float)Math.Sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2; + if (u) + { + l2 *= psx; + float cos = (dd - l1 * l1 - l2 * l2) / (2 * l1 * l2); + if (cos < -1) + cos = -1; + else if (cos > 1) + { + cos = 1; + if (stretch && l1 + l2 > 0.0001f) sx *= ((float)Math.Sqrt(dd) / (l1 + l2) - 1) * alpha + 1; + } + a2 = (float)Math.Acos(cos) * bendDir; + a = l1 + l2 * cos; + b = l2 * (float)Math.Sin(a2); + a1 = (float)Math.Atan2(ty * a - tx * b, tx * a + ty * b); + } + else + { + a = psx * l2; + b = psy * l2; + float aa = a * a, bb = b * b, ta = (float)Math.Atan2(ty, tx); + c = bb * l1 * l1 + aa * dd - aa * bb; + float c1 = -2 * bb * l1, c2 = bb - aa; + d = c1 * c1 - 4 * c2 * c; + if (d >= 0) + { + float q = (float)Math.Sqrt(d); + if (c1 < 0) q = -q; + q = -(c1 + q) / 2; + float r0 = q / c2, r1 = c / q; + float r = Math.Abs(r0) < Math.Abs(r1) ? r0 : r1; + if (r * r <= dd) + { + y = (float)Math.Sqrt(dd - r * r) * bendDir; + a1 = ta - (float)Math.Atan2(y, r); + a2 = (float)Math.Atan2(y / psy, (r - l1) / psx); + goto break_outer; // break outer; + } + } + float minAngle = MathUtils.PI, minX = l1 - a, minDist = minX * minX, minY = 0; + float maxAngle = 0, maxX = l1 + a, maxDist = maxX * maxX, maxY = 0; + c = -a * l1 / (aa - bb); + if (c >= -1 && c <= 1) + { + c = (float)Math.Acos(c); + x = a * (float)Math.Cos(c) + l1; + y = b * (float)Math.Sin(c); + d = x * x + y * y; + if (d < minDist) + { + minAngle = c; + minDist = d; + minX = x; + minY = y; + } + if (d > maxDist) + { + maxAngle = c; + maxDist = d; + maxX = x; + maxY = y; + } + } + if (dd <= (minDist + maxDist) / 2) + { + a1 = ta - (float)Math.Atan2(minY * bendDir, minX); + a2 = minAngle * bendDir; + } + else + { + a1 = ta - (float)Math.Atan2(maxY * bendDir, maxX); + a2 = maxAngle * bendDir; + } + } + break_outer: + float os = (float)Math.Atan2(cy, cx) * s2; + float rotation = parent.arotation; + a1 = (a1 - os) * MathUtils.RadDeg + os1 - rotation; + if (a1 > 180) + a1 -= 360; + else if (a1 < -180) a1 += 360; + parent.UpdateWorldTransform(px, py, rotation + a1 * alpha, sx, parent.ascaleY, 0, 0); + rotation = child.arotation; + a2 = ((a2 + os) * MathUtils.RadDeg - child.ashearX) * s2 + os2 - rotation; + if (a2 > 180) + a2 -= 360; + else if (a2 < -180) a2 += 360; + child.UpdateWorldTransform(cx, cy, rotation + a2 * alpha, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/IkConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/IkConstraintData.cs index e9620aa..701d823 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/IkConstraintData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/IkConstraintData.cs @@ -30,82 +30,95 @@ using System; using System.Collections.Generic; -namespace Spine3_7_94 { - /// Stores the setup pose for an IkConstraint. - public class IkConstraintData { - internal string name; - internal int order; - internal List bones = new List(); - internal BoneData target; - internal int bendDirection = 1; - internal bool compress, stretch, uniform; - internal float mix = 1; +namespace Spine3_7_94 +{ + /// Stores the setup pose for an IkConstraint. + public class IkConstraintData + { + internal string name; + internal int order; + internal List bones = new List(); + internal BoneData target; + internal int bendDirection = 1; + internal bool compress, stretch, uniform; + internal float mix = 1; - /// The IK constraint's name, which is unique within the skeleton. - public string Name { - get { return name; } - } + /// The IK constraint's name, which is unique within the skeleton. + public string Name + { + get { return name; } + } - public int Order { - get { return order; } - set { order = value; } - } + public int Order + { + get { return order; } + set { order = value; } + } - /// The bones that are constrained by this IK Constraint. - public List Bones { - get { return bones; } - } + /// The bones that are constrained by this IK Constraint. + public List Bones + { + get { return bones; } + } - /// The bone that is the IK target. - public BoneData Target { - get { return target; } - set { target = value; } - } + /// The bone that is the IK target. + public BoneData Target + { + get { return target; } + set { target = value; } + } - /// - /// A percentage (0-1) that controls the mix between the constraint and unconstrained rotations. - public float Mix { - get { return mix; } - set { mix = value; } - } + /// + /// A percentage (0-1) that controls the mix between the constraint and unconstrained rotations. + public float Mix + { + get { return mix; } + set { mix = value; } + } - /// Controls the bend direction of the IK bones, either 1 or -1. - public int BendDirection { - get { return bendDirection; } - set { bendDirection = value; } - } + /// Controls the bend direction of the IK bones, either 1 or -1. + public int BendDirection + { + get { return bendDirection; } + set { bendDirection = value; } + } - /// - /// When true, and only a single bone is being constrained, - /// if the target is too close, the bone is scaled to reach it. - public bool Compress { - get { return compress; } - set { compress = value; } - } + /// + /// When true, and only a single bone is being constrained, + /// if the target is too close, the bone is scaled to reach it. + public bool Compress + { + get { return compress; } + set { compress = value; } + } - /// - /// When true, if the target is out of range, the parent bone is scaled on the X axis to reach it. - /// If the bone has local nonuniform scale, stretching is not applied. - public bool Stretch { - get { return stretch; } - set { stretch = value; } - } + /// + /// When true, if the target is out of range, the parent bone is scaled on the X axis to reach it. + /// If the bone has local nonuniform scale, stretching is not applied. + public bool Stretch + { + get { return stretch; } + set { stretch = value; } + } - /// - /// When true, only a single bone is being constrained and Compress or Stretch is used, - /// the bone is scaled both on the X and Y axes. - public bool Uniform { - get { return uniform; } - set { uniform = value; } - } + /// + /// When true, only a single bone is being constrained and Compress or Stretch is used, + /// the bone is scaled both on the X and Y axes. + public bool Uniform + { + get { return uniform; } + set { uniform = value; } + } - public IkConstraintData (string name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; - } + public IkConstraintData(string name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + } - override public string ToString () { - return name; - } - } + override public string ToString() + { + return name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Json.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Json.cs index 4690549..68f5773 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Json.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Json.cs @@ -28,20 +28,22 @@ *****************************************************************************/ using System; +using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Text; -using System.Collections; -using System.Globalization; -using System.Collections.Generic; -namespace Spine3_7_94 { - public static class Json { - public static object Deserialize (TextReader text) { - var parser = new SharpJson.JsonDecoder(); - parser.parseNumbersAsFloat = true; - return parser.Decode(text.ReadToEnd()); - } - } +namespace Spine3_7_94 +{ + public static class Json + { + public static object Deserialize(TextReader text) + { + var parser = new SharpJson.JsonDecoder(); + parser.parseNumbersAsFloat = true; + return parser.Decode(text.ReadToEnd()); + } + } } /** @@ -75,460 +77,502 @@ public static object Deserialize (TextReader text) { */ namespace SharpJson3_7_94 { - class Lexer - { - public enum Token { - None, - Null, - True, - False, - Colon, - Comma, - String, - Number, - CurlyOpen, - CurlyClose, - SquaredOpen, - SquaredClose, - }; - - public bool hasError { - get { - return !success; - } - } - - public int lineNumber { - get; - private set; - } - - public bool parseNumbersAsFloat { - get; - set; - } - - char[] json; - int index = 0; - bool success = true; - char[] stringBuffer = new char[4096]; - - public Lexer(string text) - { - Reset(); - - json = text.ToCharArray(); - parseNumbersAsFloat = false; - } - - public void Reset() - { - index = 0; - lineNumber = 1; - success = true; - } - - public string ParseString() - { - int idx = 0; - StringBuilder builder = null; - - SkipWhiteSpaces(); - - // " - char c = json[index++]; - - bool failed = false; - bool complete = false; - - while (!complete && !failed) { - if (index == json.Length) - break; - - c = json[index++]; - if (c == '"') { - complete = true; - break; - } else if (c == '\\') { - if (index == json.Length) - break; - - c = json[index++]; - - switch (c) { - case '"': - stringBuffer[idx++] = '"'; - break; - case '\\': - stringBuffer[idx++] = '\\'; - break; - case '/': - stringBuffer[idx++] = '/'; - break; - case 'b': - stringBuffer[idx++] = '\b'; - break; - case'f': - stringBuffer[idx++] = '\f'; - break; - case 'n': - stringBuffer[idx++] = '\n'; - break; - case 'r': - stringBuffer[idx++] = '\r'; - break; - case 't': - stringBuffer[idx++] = '\t'; - break; - case 'u': - int remainingLength = json.Length - index; - if (remainingLength >= 4) { - var hex = new string(json, index, 4); - - // XXX: handle UTF - stringBuffer[idx++] = (char) Convert.ToInt32(hex, 16); - - // skip 4 chars - index += 4; - } else { - failed = true; - } - break; - } - } else { - stringBuffer[idx++] = c; - } - - if (idx >= stringBuffer.Length) { - if (builder == null) - builder = new StringBuilder(); - - builder.Append(stringBuffer, 0, idx); - idx = 0; - } - } - - if (!complete) { - success = false; - return null; - } - - if (builder != null) - return builder.ToString (); - else - return new string (stringBuffer, 0, idx); - } - - string GetNumberString() - { - SkipWhiteSpaces(); - - int lastIndex = GetLastIndexOfNumber(index); - int charLength = (lastIndex - index) + 1; - - var result = new string (json, index, charLength); - - index = lastIndex + 1; - - return result; - } - - public float ParseFloatNumber() - { - float number; - var str = GetNumberString (); - - if (!float.TryParse (str, NumberStyles.Float, CultureInfo.InvariantCulture, out number)) - return 0; - - return number; - } - - public double ParseDoubleNumber() - { - double number; - var str = GetNumberString (); - - if (!double.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out number)) - return 0; - - return number; - } - - int GetLastIndexOfNumber(int index) - { - int lastIndex; - - for (lastIndex = index; lastIndex < json.Length; lastIndex++) { - char ch = json[lastIndex]; - - if ((ch < '0' || ch > '9') && ch != '+' && ch != '-' - && ch != '.' && ch != 'e' && ch != 'E') - break; - } - - return lastIndex - 1; - } - - void SkipWhiteSpaces() - { - for (; index < json.Length; index++) { - char ch = json[index]; - - if (ch == '\n') - lineNumber++; - - if (!char.IsWhiteSpace(json[index])) - break; - } - } - - public Token LookAhead() - { - SkipWhiteSpaces(); - - int savedIndex = index; - return NextToken(json, ref savedIndex); - } - - public Token NextToken() - { - SkipWhiteSpaces(); - return NextToken(json, ref index); - } - - static Token NextToken(char[] json, ref int index) - { - if (index == json.Length) - return Token.None; - - char c = json[index++]; - - switch (c) { - case '{': - return Token.CurlyOpen; - case '}': - return Token.CurlyClose; - case '[': - return Token.SquaredOpen; - case ']': - return Token.SquaredClose; - case ',': - return Token.Comma; - case '"': - return Token.String; - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - case '-': - return Token.Number; - case ':': - return Token.Colon; - } - - index--; - - int remainingLength = json.Length - index; - - // false - if (remainingLength >= 5) { - if (json[index] == 'f' && - json[index + 1] == 'a' && - json[index + 2] == 'l' && - json[index + 3] == 's' && - json[index + 4] == 'e') { - index += 5; - return Token.False; - } - } - - // true - if (remainingLength >= 4) { - if (json[index] == 't' && - json[index + 1] == 'r' && - json[index + 2] == 'u' && - json[index + 3] == 'e') { - index += 4; - return Token.True; - } - } - - // null - if (remainingLength >= 4) { - if (json[index] == 'n' && - json[index + 1] == 'u' && - json[index + 2] == 'l' && - json[index + 3] == 'l') { - index += 4; - return Token.Null; - } - } - - return Token.None; - } - } - - public class JsonDecoder - { - public string errorMessage { - get; - private set; - } - - public bool parseNumbersAsFloat { - get; - set; - } - - Lexer lexer; - - public JsonDecoder() - { - errorMessage = null; - parseNumbersAsFloat = false; - } - - public object Decode(string text) - { - errorMessage = null; - - lexer = new Lexer(text); - lexer.parseNumbersAsFloat = parseNumbersAsFloat; - - return ParseValue(); - } - - public static object DecodeText(string text) - { - var builder = new JsonDecoder(); - return builder.Decode(text); - } - - IDictionary ParseObject() - { - var table = new Dictionary(); - - // { - lexer.NextToken(); - - while (true) { - var token = lexer.LookAhead(); - - switch (token) { - case Lexer.Token.None: - TriggerError("Invalid token"); - return null; - case Lexer.Token.Comma: - lexer.NextToken(); - break; - case Lexer.Token.CurlyClose: - lexer.NextToken(); - return table; - default: - // name - string name = EvalLexer(lexer.ParseString()); - - if (errorMessage != null) - return null; - - // : - token = lexer.NextToken(); - - if (token != Lexer.Token.Colon) { - TriggerError("Invalid token; expected ':'"); - return null; - } - - // value - object value = ParseValue(); - - if (errorMessage != null) - return null; - - table[name] = value; - break; - } - } - - //return null; // Unreachable code - } - - IList ParseArray() - { - var array = new List(); - - // [ - lexer.NextToken(); - - while (true) { - var token = lexer.LookAhead(); - - switch (token) { - case Lexer.Token.None: - TriggerError("Invalid token"); - return null; - case Lexer.Token.Comma: - lexer.NextToken(); - break; - case Lexer.Token.SquaredClose: - lexer.NextToken(); - return array; - default: - object value = ParseValue(); - - if (errorMessage != null) - return null; - - array.Add(value); - break; - } - } - - //return null; // Unreachable code - } - - object ParseValue() - { - switch (lexer.LookAhead()) { - case Lexer.Token.String: - return EvalLexer(lexer.ParseString()); - case Lexer.Token.Number: - if (parseNumbersAsFloat) - return EvalLexer(lexer.ParseFloatNumber()); - else - return EvalLexer(lexer.ParseDoubleNumber()); - case Lexer.Token.CurlyOpen: - return ParseObject(); - case Lexer.Token.SquaredOpen: - return ParseArray(); - case Lexer.Token.True: - lexer.NextToken(); - return true; - case Lexer.Token.False: - lexer.NextToken(); - return false; - case Lexer.Token.Null: - lexer.NextToken(); - return null; - case Lexer.Token.None: - break; - } - - TriggerError("Unable to parse value"); - return null; - } - - void TriggerError(string message) - { - errorMessage = string.Format("Error: '{0}' at line {1}", - message, lexer.lineNumber); - } - - T EvalLexer(T value) - { - if (lexer.hasError) - TriggerError("Lexical error ocurred"); - - return value; - } - } + class Lexer + { + public enum Token + { + None, + Null, + True, + False, + Colon, + Comma, + String, + Number, + CurlyOpen, + CurlyClose, + SquaredOpen, + SquaredClose, + }; + + public bool hasError + { + get + { + return !success; + } + } + + public int lineNumber + { + get; + private set; + } + + public bool parseNumbersAsFloat + { + get; + set; + } + + char[] json; + int index = 0; + bool success = true; + char[] stringBuffer = new char[4096]; + + public Lexer(string text) + { + Reset(); + + json = text.ToCharArray(); + parseNumbersAsFloat = false; + } + + public void Reset() + { + index = 0; + lineNumber = 1; + success = true; + } + + public string ParseString() + { + int idx = 0; + StringBuilder builder = null; + + SkipWhiteSpaces(); + + // " + char c = json[index++]; + + bool failed = false; + bool complete = false; + + while (!complete && !failed) + { + if (index == json.Length) + break; + + c = json[index++]; + if (c == '"') + { + complete = true; + break; + } + else if (c == '\\') + { + if (index == json.Length) + break; + + c = json[index++]; + + switch (c) + { + case '"': + stringBuffer[idx++] = '"'; + break; + case '\\': + stringBuffer[idx++] = '\\'; + break; + case '/': + stringBuffer[idx++] = '/'; + break; + case 'b': + stringBuffer[idx++] = '\b'; + break; + case 'f': + stringBuffer[idx++] = '\f'; + break; + case 'n': + stringBuffer[idx++] = '\n'; + break; + case 'r': + stringBuffer[idx++] = '\r'; + break; + case 't': + stringBuffer[idx++] = '\t'; + break; + case 'u': + int remainingLength = json.Length - index; + if (remainingLength >= 4) + { + var hex = new string(json, index, 4); + + // XXX: handle UTF + stringBuffer[idx++] = (char)Convert.ToInt32(hex, 16); + + // skip 4 chars + index += 4; + } + else + { + failed = true; + } + break; + } + } + else + { + stringBuffer[idx++] = c; + } + + if (idx >= stringBuffer.Length) + { + if (builder == null) + builder = new StringBuilder(); + + builder.Append(stringBuffer, 0, idx); + idx = 0; + } + } + + if (!complete) + { + success = false; + return null; + } + + if (builder != null) + return builder.ToString(); + else + return new string(stringBuffer, 0, idx); + } + + string GetNumberString() + { + SkipWhiteSpaces(); + + int lastIndex = GetLastIndexOfNumber(index); + int charLength = (lastIndex - index) + 1; + + var result = new string(json, index, charLength); + + index = lastIndex + 1; + + return result; + } + + public float ParseFloatNumber() + { + float number; + var str = GetNumberString(); + + if (!float.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out number)) + return 0; + + return number; + } + + public double ParseDoubleNumber() + { + double number; + var str = GetNumberString(); + + if (!double.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out number)) + return 0; + + return number; + } + + int GetLastIndexOfNumber(int index) + { + int lastIndex; + + for (lastIndex = index; lastIndex < json.Length; lastIndex++) + { + char ch = json[lastIndex]; + + if ((ch < '0' || ch > '9') && ch != '+' && ch != '-' + && ch != '.' && ch != 'e' && ch != 'E') + break; + } + + return lastIndex - 1; + } + + void SkipWhiteSpaces() + { + for (; index < json.Length; index++) + { + char ch = json[index]; + + if (ch == '\n') + lineNumber++; + + if (!char.IsWhiteSpace(json[index])) + break; + } + } + + public Token LookAhead() + { + SkipWhiteSpaces(); + + int savedIndex = index; + return NextToken(json, ref savedIndex); + } + + public Token NextToken() + { + SkipWhiteSpaces(); + return NextToken(json, ref index); + } + + static Token NextToken(char[] json, ref int index) + { + if (index == json.Length) + return Token.None; + + char c = json[index++]; + + switch (c) + { + case '{': + return Token.CurlyOpen; + case '}': + return Token.CurlyClose; + case '[': + return Token.SquaredOpen; + case ']': + return Token.SquaredClose; + case ',': + return Token.Comma; + case '"': + return Token.String; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + return Token.Number; + case ':': + return Token.Colon; + } + + index--; + + int remainingLength = json.Length - index; + + // false + if (remainingLength >= 5) + { + if (json[index] == 'f' && + json[index + 1] == 'a' && + json[index + 2] == 'l' && + json[index + 3] == 's' && + json[index + 4] == 'e') + { + index += 5; + return Token.False; + } + } + + // true + if (remainingLength >= 4) + { + if (json[index] == 't' && + json[index + 1] == 'r' && + json[index + 2] == 'u' && + json[index + 3] == 'e') + { + index += 4; + return Token.True; + } + } + + // null + if (remainingLength >= 4) + { + if (json[index] == 'n' && + json[index + 1] == 'u' && + json[index + 2] == 'l' && + json[index + 3] == 'l') + { + index += 4; + return Token.Null; + } + } + + return Token.None; + } + } + + public class JsonDecoder + { + public string errorMessage + { + get; + private set; + } + + public bool parseNumbersAsFloat + { + get; + set; + } + + Lexer lexer; + + public JsonDecoder() + { + errorMessage = null; + parseNumbersAsFloat = false; + } + + public object Decode(string text) + { + errorMessage = null; + + lexer = new Lexer(text); + lexer.parseNumbersAsFloat = parseNumbersAsFloat; + + return ParseValue(); + } + + public static object DecodeText(string text) + { + var builder = new JsonDecoder(); + return builder.Decode(text); + } + + IDictionary ParseObject() + { + var table = new Dictionary(); + + // { + lexer.NextToken(); + + while (true) + { + var token = lexer.LookAhead(); + + switch (token) + { + case Lexer.Token.None: + TriggerError("Invalid token"); + return null; + case Lexer.Token.Comma: + lexer.NextToken(); + break; + case Lexer.Token.CurlyClose: + lexer.NextToken(); + return table; + default: + // name + string name = EvalLexer(lexer.ParseString()); + + if (errorMessage != null) + return null; + + // : + token = lexer.NextToken(); + + if (token != Lexer.Token.Colon) + { + TriggerError("Invalid token; expected ':'"); + return null; + } + + // value + object value = ParseValue(); + + if (errorMessage != null) + return null; + + table[name] = value; + break; + } + } + + //return null; // Unreachable code + } + + IList ParseArray() + { + var array = new List(); + + // [ + lexer.NextToken(); + + while (true) + { + var token = lexer.LookAhead(); + + switch (token) + { + case Lexer.Token.None: + TriggerError("Invalid token"); + return null; + case Lexer.Token.Comma: + lexer.NextToken(); + break; + case Lexer.Token.SquaredClose: + lexer.NextToken(); + return array; + default: + object value = ParseValue(); + + if (errorMessage != null) + return null; + + array.Add(value); + break; + } + } + + //return null; // Unreachable code + } + + object ParseValue() + { + switch (lexer.LookAhead()) + { + case Lexer.Token.String: + return EvalLexer(lexer.ParseString()); + case Lexer.Token.Number: + if (parseNumbersAsFloat) + return EvalLexer(lexer.ParseFloatNumber()); + else + return EvalLexer(lexer.ParseDoubleNumber()); + case Lexer.Token.CurlyOpen: + return ParseObject(); + case Lexer.Token.SquaredOpen: + return ParseArray(); + case Lexer.Token.True: + lexer.NextToken(); + return true; + case Lexer.Token.False: + lexer.NextToken(); + return false; + case Lexer.Token.Null: + lexer.NextToken(); + return null; + case Lexer.Token.None: + break; + } + + TriggerError("Unable to parse value"); + return null; + } + + void TriggerError(string message) + { + errorMessage = string.Format("Error: '{0}' at line {1}", + message, lexer.lineNumber); + } + + T EvalLexer(T value) + { + if (lexer.hasError) + TriggerError("Lexical error ocurred"); + + return value; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/MathUtils.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/MathUtils.cs index a73ce93..73d2e0a 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/MathUtils.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/MathUtils.cs @@ -29,117 +29,138 @@ using System; -namespace Spine3_7_94 { - public static class MathUtils { - public const float PI = 3.1415927f; - public const float PI2 = PI * 2; - public const float RadDeg = 180f / PI; - public const float DegRad = PI / 180; - - const int SIN_BITS = 14; // 16KB. Adjust for accuracy. - const int SIN_MASK = ~(-1 << SIN_BITS); - const int SIN_COUNT = SIN_MASK + 1; - const float RadFull = PI * 2; - const float DegFull = 360; - const float RadToIndex = SIN_COUNT / RadFull; - const float DegToIndex = SIN_COUNT / DegFull; - static float[] sin = new float[SIN_COUNT]; - - static Random random = new Random(); - - static MathUtils () { - for (int i = 0; i < SIN_COUNT; i++) - sin[i] = (float)Math.Sin((i + 0.5f) / SIN_COUNT * RadFull); - for (int i = 0; i < 360; i += 90) - sin[(int)(i * DegToIndex) & SIN_MASK] = (float)Math.Sin(i * DegRad); - } - - /// Returns the sine in radians from a lookup table. - static public float Sin (float radians) { - return sin[(int)(radians * RadToIndex) & SIN_MASK]; - } - - /// Returns the cosine in radians from a lookup table. - static public float Cos (float radians) { - return sin[(int)((radians + PI / 2) * RadToIndex) & SIN_MASK]; - } - - /// Returns the sine in radians from a lookup table. - static public float SinDeg (float degrees) { - return sin[(int)(degrees * DegToIndex) & SIN_MASK]; - } - - /// Returns the cosine in radians from a lookup table. - static public float CosDeg (float degrees) { - return sin[(int)((degrees + 90) * DegToIndex) & SIN_MASK]; - } - - /// 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 public float Atan2 (float y, float x) { - if (x == 0f) { - if (y > 0f) return PI / 2; - if (y == 0f) return 0f; - return -PI / 2; - } - float atan, z = y / x; - if (Math.Abs(z) < 1f) { - atan = z / (1f + 0.28f * z * z); - if (x < 0f) return atan + (y < 0f ? -PI : PI); - return atan; - } - atan = PI / 2 - z / (z * z + 0.28f); - return y < 0f ? atan - PI : atan; - } - - static public float Clamp (float value, float min, float max) { - if (value < min) return min; - if (value > max) return max; - return value; - } - - static public float RandomTriangle(float min, float max) { - return RandomTriangle(min, max, (min + max) * 0.5f); - } - - static public float RandomTriangle(float min, float max, float mode) { - float u = (float)random.NextDouble(); - float d = max - min; - if (u <= (mode - min) / d) return min + (float)Math.Sqrt(u * d * (mode - min)); - return max - (float)Math.Sqrt((1 - u) * d * (max - mode)); - } - } - - public abstract class IInterpolation { - public static IInterpolation Pow2 = new Pow(2); - public static IInterpolation Pow2Out = new PowOut(2); - - protected abstract float Apply(float a); - - public float Apply(float start, float end, float a) { - return start + (end - start) * Apply(a); - } - } - - public class Pow: IInterpolation { - public float Power { get; set; } - - public Pow(float power) { - Power = power; - } - - protected override float Apply(float a) { - if (a <= 0.5f) return (float)Math.Pow(a * 2, Power) / 2; - return (float)Math.Pow((a - 1) * 2, Power) / (Power % 2 == 0 ? -2 : 2) + 1; - } - } - - public class PowOut : Pow { - public PowOut(float power) : base(power) { - } - - protected override float Apply(float a) { - return (float)Math.Pow(a - 1, Power) * (Power % 2 == 0 ? -1 : 1) + 1; - } - } +namespace Spine3_7_94 +{ + public static class MathUtils + { + public const float PI = 3.1415927f; + public const float PI2 = PI * 2; + public const float RadDeg = 180f / PI; + public const float DegRad = PI / 180; + + const int SIN_BITS = 14; // 16KB. Adjust for accuracy. + const int SIN_MASK = ~(-1 << SIN_BITS); + const int SIN_COUNT = SIN_MASK + 1; + const float RadFull = PI * 2; + const float DegFull = 360; + const float RadToIndex = SIN_COUNT / RadFull; + const float DegToIndex = SIN_COUNT / DegFull; + static float[] sin = new float[SIN_COUNT]; + + static Random random = new Random(); + + static MathUtils() + { + for (int i = 0; i < SIN_COUNT; i++) + sin[i] = (float)Math.Sin((i + 0.5f) / SIN_COUNT * RadFull); + for (int i = 0; i < 360; i += 90) + sin[(int)(i * DegToIndex) & SIN_MASK] = (float)Math.Sin(i * DegRad); + } + + /// Returns the sine in radians from a lookup table. + static public float Sin(float radians) + { + return sin[(int)(radians * RadToIndex) & SIN_MASK]; + } + + /// Returns the cosine in radians from a lookup table. + static public float Cos(float radians) + { + return sin[(int)((radians + PI / 2) * RadToIndex) & SIN_MASK]; + } + + /// Returns the sine in radians from a lookup table. + static public float SinDeg(float degrees) + { + return sin[(int)(degrees * DegToIndex) & SIN_MASK]; + } + + /// Returns the cosine in radians from a lookup table. + static public float CosDeg(float degrees) + { + return sin[(int)((degrees + 90) * DegToIndex) & SIN_MASK]; + } + + /// 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 public float Atan2(float y, float x) + { + if (x == 0f) + { + if (y > 0f) return PI / 2; + if (y == 0f) return 0f; + return -PI / 2; + } + float atan, z = y / x; + if (Math.Abs(z) < 1f) + { + atan = z / (1f + 0.28f * z * z); + if (x < 0f) return atan + (y < 0f ? -PI : PI); + return atan; + } + atan = PI / 2 - z / (z * z + 0.28f); + return y < 0f ? atan - PI : atan; + } + + static public float Clamp(float value, float min, float max) + { + if (value < min) return min; + if (value > max) return max; + return value; + } + + static public float RandomTriangle(float min, float max) + { + return RandomTriangle(min, max, (min + max) * 0.5f); + } + + static public float RandomTriangle(float min, float max, float mode) + { + float u = (float)random.NextDouble(); + float d = max - min; + if (u <= (mode - min) / d) return min + (float)Math.Sqrt(u * d * (mode - min)); + return max - (float)Math.Sqrt((1 - u) * d * (max - mode)); + } + } + + public abstract class IInterpolation + { + public static IInterpolation Pow2 = new Pow(2); + public static IInterpolation Pow2Out = new PowOut(2); + + protected abstract float Apply(float a); + + public float Apply(float start, float end, float a) + { + return start + (end - start) * Apply(a); + } + } + + public class Pow : IInterpolation + { + public float Power { get; set; } + + public Pow(float power) + { + Power = power; + } + + protected override float Apply(float a) + { + if (a <= 0.5f) return (float)Math.Pow(a * 2, Power) / 2; + return (float)Math.Pow((a - 1) * 2, Power) / (Power % 2 == 0 ? -2 : 2) + 1; + } + } + + public class PowOut : Pow + { + public PowOut(float power) : base(power) + { + } + + protected override float Apply(float a) + { + return (float)Math.Pow(a - 1, Power) * (Power % 2 == 0 ? -1 : 1) + 1; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/PathConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/PathConstraint.cs index 8226087..8277c28 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/PathConstraint.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/PathConstraint.cs @@ -29,441 +29,505 @@ using System; -namespace Spine3_7_94 { +namespace Spine3_7_94 +{ - /// - /// - /// Stores the current pose for a path constraint. A path constraint adjusts the rotation, translation, and scale of the - /// constrained bones so they follow a {@link PathAttachment}. - /// - /// See Path constraints in the Spine User Guide. - /// - public class PathConstraint : IConstraint { - const int NONE = -1, BEFORE = -2, AFTER = -3; - const float Epsilon = 0.00001f; + /// + /// + /// Stores the current pose for a path constraint. A path constraint adjusts the rotation, translation, and scale of the + /// constrained bones so they follow a {@link PathAttachment}. + /// + /// See Path constraints in the Spine User Guide. + /// + public class PathConstraint : IConstraint + { + const int NONE = -1, BEFORE = -2, AFTER = -3; + const float Epsilon = 0.00001f; - internal PathConstraintData data; - internal ExposedList bones; - internal Slot target; - internal float position, spacing, rotateMix, translateMix; + internal PathConstraintData data; + internal ExposedList bones; + internal Slot target; + internal float position, spacing, rotateMix, translateMix; - internal ExposedList spaces = new ExposedList(), positions = new ExposedList(); - internal ExposedList world = new ExposedList(), curves = new ExposedList(), lengths = new ExposedList(); - internal float[] segments = new float[10]; + internal ExposedList spaces = new ExposedList(), positions = new ExposedList(); + internal ExposedList world = new ExposedList(), curves = new ExposedList(), lengths = new ExposedList(); + internal float[] segments = new float[10]; - public PathConstraint (PathConstraintData data, Skeleton skeleton) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.data = data; - bones = new ExposedList(data.Bones.Count); - foreach (BoneData boneData in data.bones) - bones.Add(skeleton.FindBone(boneData.name)); - target = skeleton.FindSlot(data.target.name); - position = data.position; - spacing = data.spacing; - rotateMix = data.rotateMix; - translateMix = data.translateMix; - } + public PathConstraint(PathConstraintData data, Skeleton skeleton) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + bones = new ExposedList(data.Bones.Count); + foreach (BoneData boneData in data.bones) + bones.Add(skeleton.FindBone(boneData.name)); + target = skeleton.FindSlot(data.target.name); + position = data.position; + spacing = data.spacing; + rotateMix = data.rotateMix; + translateMix = data.translateMix; + } - /// Copy constructor. - public PathConstraint (PathConstraint constraint, Skeleton skeleton) { - if (constraint == null) throw new ArgumentNullException("constraint cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); - data = constraint.data; - bones = new ExposedList(constraint.Bones.Count); - foreach (Bone bone in constraint.Bones) - bones.Add(skeleton.Bones.Items[bone.data.index]); - target = skeleton.slots.Items[constraint.target.data.index]; - position = constraint.position; - spacing = constraint.spacing; - rotateMix = constraint.rotateMix; - translateMix = constraint.translateMix; - } + /// Copy constructor. + public PathConstraint(PathConstraint constraint, Skeleton skeleton) + { + if (constraint == null) throw new ArgumentNullException("constraint cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); + data = constraint.data; + bones = new ExposedList(constraint.Bones.Count); + foreach (Bone bone in constraint.Bones) + bones.Add(skeleton.Bones.Items[bone.data.index]); + target = skeleton.slots.Items[constraint.target.data.index]; + position = constraint.position; + spacing = constraint.spacing; + rotateMix = constraint.rotateMix; + translateMix = constraint.translateMix; + } - /// Applies the constraint to the constrained bones. - public void Apply () { - Update(); - } - - public void Update () { - PathAttachment attachment = target.Attachment as PathAttachment; - if (attachment == null) return; + /// Applies the constraint to the constrained bones. + public void Apply() + { + Update(); + } - float rotateMix = this.rotateMix, translateMix = this.translateMix; - bool translate = translateMix > 0, rotate = rotateMix > 0; - if (!translate && !rotate) return; + public void Update() + { + PathAttachment attachment = target.Attachment as PathAttachment; + if (attachment == null) return; - PathConstraintData data = this.data; - bool percentSpacing = data.spacingMode == SpacingMode.Percent; - RotateMode rotateMode = data.rotateMode; - bool tangents = rotateMode == RotateMode.Tangent, scale = rotateMode == RotateMode.ChainScale; - int boneCount = this.bones.Count, spacesCount = tangents ? boneCount : boneCount + 1; - Bone[] bonesItems = this.bones.Items; - ExposedList spaces = this.spaces.Resize(spacesCount), lengths = null; - float spacing = this.spacing; - if (scale || !percentSpacing) { - if (scale) lengths = this.lengths.Resize(boneCount); - bool lengthSpacing = data.spacingMode == SpacingMode.Length; - for (int i = 0, n = spacesCount - 1; i < n;) { - Bone bone = bonesItems[i]; - float setupLength = bone.data.length; - if (setupLength < PathConstraint.Epsilon) { - if (scale) lengths.Items[i] = 0; - spaces.Items[++i] = 0; - } else if (percentSpacing) { - if (scale) { - float x = setupLength * bone.a, y = setupLength * bone.c; - float length = (float)Math.Sqrt(x * x + y * y); - lengths.Items[i] = length; - } - spaces.Items[++i] = spacing; - } else { - float x = setupLength * bone.a, y = setupLength * bone.c; - float length = (float)Math.Sqrt(x * x + y * y); - if (scale) lengths.Items[i] = length; - spaces.Items[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length / setupLength; - } - } - } else { - for (int i = 1; i < spacesCount; i++) - spaces.Items[i] = spacing; - } + float rotateMix = this.rotateMix, translateMix = this.translateMix; + bool translate = translateMix > 0, rotate = rotateMix > 0; + if (!translate && !rotate) return; - float[] positions = ComputeWorldPositions(attachment, spacesCount, tangents, - data.positionMode == PositionMode.Percent, percentSpacing); - float boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation; - bool tip; - if (offsetRotation == 0) { - tip = rotateMode == RotateMode.Chain; - } else { - tip = false; - Bone p = target.bone; - offsetRotation *= p.a * p.d - p.b * p.c > 0 ? MathUtils.DegRad : -MathUtils.DegRad; - } - for (int i = 0, p = 3; i < boneCount; i++, p += 3) { - Bone bone = bonesItems[i]; - bone.worldX += (boneX - bone.worldX) * translateMix; - bone.worldY += (boneY - bone.worldY) * translateMix; - float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY; - if (scale) { - float length = lengths.Items[i]; - if (length >= PathConstraint.Epsilon) { - float s = ((float)Math.Sqrt(dx * dx + dy * dy) / length - 1) * rotateMix + 1; - bone.a *= s; - bone.c *= s; - } - } - boneX = x; - boneY = y; - if (rotate) { - 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.Items[i + 1] < PathConstraint.Epsilon) - r = positions[p + 2]; - else - r = MathUtils.Atan2(dy, dx); - r -= MathUtils.Atan2(c, a); - if (tip) { - cos = MathUtils.Cos(r); - sin = MathUtils.Sin(r); - float length = bone.data.length; - boneX += (length * (cos * a - sin * c) - dx) * rotateMix; - boneY += (length * (sin * a + cos * c) - dy) * rotateMix; - } else - r += offsetRotation; - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) // - r += MathUtils.PI2; - r *= rotateMix; - cos = MathUtils.Cos(r); - sin = MathUtils.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.appliedValid = false; - } - } + PathConstraintData data = this.data; + bool percentSpacing = data.spacingMode == SpacingMode.Percent; + RotateMode rotateMode = data.rotateMode; + bool tangents = rotateMode == RotateMode.Tangent, scale = rotateMode == RotateMode.ChainScale; + int boneCount = this.bones.Count, spacesCount = tangents ? boneCount : boneCount + 1; + Bone[] bonesItems = this.bones.Items; + ExposedList spaces = this.spaces.Resize(spacesCount), lengths = null; + float spacing = this.spacing; + if (scale || !percentSpacing) + { + if (scale) lengths = this.lengths.Resize(boneCount); + bool lengthSpacing = data.spacingMode == SpacingMode.Length; + for (int i = 0, n = spacesCount - 1; i < n;) + { + Bone bone = bonesItems[i]; + float setupLength = bone.data.length; + if (setupLength < PathConstraint.Epsilon) + { + if (scale) lengths.Items[i] = 0; + spaces.Items[++i] = 0; + } + else if (percentSpacing) + { + if (scale) + { + float x = setupLength * bone.a, y = setupLength * bone.c; + float length = (float)Math.Sqrt(x * x + y * y); + lengths.Items[i] = length; + } + spaces.Items[++i] = spacing; + } + else + { + float x = setupLength * bone.a, y = setupLength * bone.c; + float length = (float)Math.Sqrt(x * x + y * y); + if (scale) lengths.Items[i] = length; + spaces.Items[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length / setupLength; + } + } + } + else + { + for (int i = 1; i < spacesCount; i++) + spaces.Items[i] = spacing; + } - float[] ComputeWorldPositions (PathAttachment path, int spacesCount, bool tangents, bool percentPosition, - bool percentSpacing) { + float[] positions = ComputeWorldPositions(attachment, spacesCount, tangents, + data.positionMode == PositionMode.Percent, percentSpacing); + float boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation; + bool tip; + if (offsetRotation == 0) + { + tip = rotateMode == RotateMode.Chain; + } + else + { + tip = false; + Bone p = target.bone; + offsetRotation *= p.a * p.d - p.b * p.c > 0 ? MathUtils.DegRad : -MathUtils.DegRad; + } + for (int i = 0, p = 3; i < boneCount; i++, p += 3) + { + Bone bone = bonesItems[i]; + bone.worldX += (boneX - bone.worldX) * translateMix; + bone.worldY += (boneY - bone.worldY) * translateMix; + float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY; + if (scale) + { + float length = lengths.Items[i]; + if (length >= PathConstraint.Epsilon) + { + float s = ((float)Math.Sqrt(dx * dx + dy * dy) / length - 1) * rotateMix + 1; + bone.a *= s; + bone.c *= s; + } + } + boneX = x; + boneY = y; + if (rotate) + { + 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.Items[i + 1] < PathConstraint.Epsilon) + r = positions[p + 2]; + else + r = MathUtils.Atan2(dy, dx); + r -= MathUtils.Atan2(c, a); + if (tip) + { + cos = MathUtils.Cos(r); + sin = MathUtils.Sin(r); + float length = bone.data.length; + boneX += (length * (cos * a - sin * c) - dx) * rotateMix; + boneY += (length * (sin * a + cos * c) - dy) * rotateMix; + } + else + r += offsetRotation; + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) // + r += MathUtils.PI2; + r *= rotateMix; + cos = MathUtils.Cos(r); + sin = MathUtils.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.appliedValid = false; + } + } - Slot target = this.target; - float position = this.position; - float[] spacesItems = this.spaces.Items, output = this.positions.Resize(spacesCount * 3 + 2).Items, world; - bool closed = path.Closed; - int verticesLength = path.WorldVerticesLength, curveCount = verticesLength / 6, prevCurve = NONE; - float pathLength = 0; + float[] ComputeWorldPositions(PathAttachment path, int spacesCount, bool tangents, bool percentPosition, + bool percentSpacing) + { - if (!path.ConstantSpeed) { - float[] lengths = path.Lengths; - curveCount -= closed ? 1 : 2; - pathLength = lengths[curveCount]; - if (percentPosition) position *= pathLength; - if (percentSpacing) { - for (int i = 1; i < spacesCount; i++) - spacesItems[i] *= pathLength; - } - world = this.world.Resize(8).Items; - for (int i = 0, o = 0, curve = 0; i < spacesCount; i++, o += 3) { - float space = spacesItems[i]; - position += space; - float p = position; + Slot target = this.target; + float position = this.position; + float[] spacesItems = this.spaces.Items, output = this.positions.Resize(spacesCount * 3 + 2).Items, world; + bool closed = path.Closed; + int verticesLength = path.WorldVerticesLength, curveCount = verticesLength / 6, prevCurve = NONE; + float pathLength = 0; - if (closed) { - 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, 2); - } - AddBeforePosition(p, world, 0, output, o); - continue; - } else if (p > pathLength) { - if (prevCurve != AFTER) { - prevCurve = AFTER; - path.ComputeWorldVertices(target, verticesLength - 6, 4, world, 0, 2); - } - AddAfterPosition(p - pathLength, world, 0, output, o); - continue; - } + if (!path.ConstantSpeed) + { + float[] lengths = path.Lengths; + curveCount -= closed ? 1 : 2; + pathLength = lengths[curveCount]; + if (percentPosition) position *= pathLength; + if (percentSpacing) + { + for (int i = 1; i < spacesCount; i++) + spacesItems[i] *= pathLength; + } + world = this.world.Resize(8).Items; + for (int i = 0, o = 0, curve = 0; i < spacesCount; i++, o += 3) + { + float space = spacesItems[i]; + position += space; + float p = position; - // 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, 2); - path.ComputeWorldVertices(target, 0, 4, world, 4, 2); - } else - path.ComputeWorldVertices(target, curve * 6 + 2, 8, world, 0, 2); - } - AddCurvePosition(p, world[0], world[1], world[2], world[3], world[4], world[5], world[6], world[7], output, o, - tangents || (i > 0 && space < PathConstraint.Epsilon)); - } - return output; - } + if (closed) + { + 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, 2); + } + AddBeforePosition(p, world, 0, output, o); + continue; + } + else if (p > pathLength) + { + if (prevCurve != AFTER) + { + prevCurve = AFTER; + path.ComputeWorldVertices(target, verticesLength - 6, 4, world, 0, 2); + } + AddAfterPosition(p - pathLength, world, 0, output, o); + continue; + } - // World vertices. - if (closed) { - verticesLength += 2; - world = this.world.Resize(verticesLength).Items; - path.ComputeWorldVertices(target, 2, verticesLength - 4, world, 0, 2); - path.ComputeWorldVertices(target, 0, 2, world, verticesLength - 4, 2); - world[verticesLength - 2] = world[0]; - world[verticesLength - 1] = world[1]; - } else { - curveCount--; - verticesLength -= 4; - world = this.world.Resize(verticesLength).Items; - path.ComputeWorldVertices(target, 2, verticesLength, world, 0, 2); - } + // 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, 2); + path.ComputeWorldVertices(target, 0, 4, world, 4, 2); + } + else + path.ComputeWorldVertices(target, curve * 6 + 2, 8, world, 0, 2); + } + AddCurvePosition(p, world[0], world[1], world[2], world[3], world[4], world[5], world[6], world[7], output, o, + tangents || (i > 0 && space < PathConstraint.Epsilon)); + } + return output; + } - // Curve lengths. - float[] curves = this.curves.Resize(curveCount).Items; - 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 += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - dfx += ddfx; - dfy += ddfy; - ddfx += dddfx; - ddfy += dddfy; - pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - dfx += ddfx; - dfy += ddfy; - pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - dfx += ddfx + dddfx; - dfy += ddfy + dddfy; - pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - curves[i] = pathLength; - x1 = x2; - y1 = y2; - } - if (percentPosition) - position *= pathLength; - else - position *= pathLength / path.lengths[curveCount - 1]; + // World vertices. + if (closed) + { + verticesLength += 2; + world = this.world.Resize(verticesLength).Items; + path.ComputeWorldVertices(target, 2, verticesLength - 4, world, 0, 2); + path.ComputeWorldVertices(target, 0, 2, world, verticesLength - 4, 2); + world[verticesLength - 2] = world[0]; + world[verticesLength - 1] = world[1]; + } + else + { + curveCount--; + verticesLength -= 4; + world = this.world.Resize(verticesLength).Items; + path.ComputeWorldVertices(target, 2, verticesLength, world, 0, 2); + } - if (percentSpacing) { - for (int i = 1; i < spacesCount; i++) - spacesItems[i] *= pathLength; - } + // Curve lengths. + float[] curves = this.curves.Resize(curveCount).Items; + 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 += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx; + dfy += ddfy; + pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx + dddfx; + dfy += ddfy + dddfy; + pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + curves[i] = pathLength; + x1 = x2; + y1 = y2; + } + if (percentPosition) + position *= pathLength; + else + position *= pathLength / path.lengths[curveCount - 1]; - float[] segments = this.segments; - float curveLength = 0; - for (int i = 0, o = 0, curve = 0, segment = 0; i < spacesCount; i++, o += 3) { - float space = spacesItems[i]; - position += space; - float p = position; + if (percentSpacing) + { + for (int i = 1; i < spacesCount; i++) + spacesItems[i] *= pathLength; + } - if (closed) { - p %= pathLength; - if (p < 0) p += pathLength; - curve = 0; - } else if (p < 0) { - AddBeforePosition(p, world, 0, output, o); - continue; - } else if (p > pathLength) { - AddAfterPosition(p - pathLength, world, verticesLength - 4, output, o); - continue; - } + float[] segments = this.segments; + float curveLength = 0; + for (int i = 0, o = 0, curve = 0, segment = 0; i < spacesCount; i++, o += 3) + { + float space = spacesItems[i]; + position += space; + float p = position; - // 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; - } + if (closed) + { + p %= pathLength; + if (p < 0) p += pathLength; + curve = 0; + } + else if (p < 0) + { + AddBeforePosition(p, world, 0, output, o); + continue; + } + else if (p > pathLength) + { + AddAfterPosition(p - pathLength, world, verticesLength - 4, output, o); + continue; + } - // 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 = (float)Math.Sqrt(dfx * dfx + dfy * dfy); - segments[0] = curveLength; - for (ii = 1; ii < 8; ii++) { - dfx += ddfx; - dfy += ddfy; - ddfx += dddfx; - ddfy += dddfy; - curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - segments[ii] = curveLength; - } - dfx += ddfx; - dfy += ddfy; - curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - segments[8] = curveLength; - dfx += ddfx + dddfx; - dfy += ddfy + dddfy; - curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - segments[9] = curveLength; - segment = 0; - } + // 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; + } - // 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, output, o, tangents || (i > 0 && space < PathConstraint.Epsilon)); - } - return output; - } + // 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 = (float)Math.Sqrt(dfx * dfx + dfy * dfy); + segments[0] = curveLength; + for (ii = 1; ii < 8; ii++) + { + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + segments[ii] = curveLength; + } + dfx += ddfx; + dfy += ddfy; + curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + segments[8] = curveLength; + dfx += ddfx + dddfx; + dfy += ddfy + dddfy; + curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + segments[9] = curveLength; + segment = 0; + } - static void AddBeforePosition (float p, float[] temp, int i, float[] output, int o) { - float x1 = temp[i], y1 = temp[i + 1], dx = temp[i + 2] - x1, dy = temp[i + 3] - y1, r = MathUtils.Atan2(dy, dx); - output[o] = x1 + p * MathUtils.Cos(r); - output[o + 1] = y1 + p * MathUtils.Sin(r); - output[o + 2] = r; - } + // 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, output, o, tangents || (i > 0 && space < PathConstraint.Epsilon)); + } + return output; + } - static void AddAfterPosition (float p, float[] temp, int i, float[] output, int o) { - float x1 = temp[i + 2], y1 = temp[i + 3], dx = x1 - temp[i], dy = y1 - temp[i + 1], r = MathUtils.Atan2(dy, dx); - output[o] = x1 + p * MathUtils.Cos(r); - output[o + 1] = y1 + p * MathUtils.Sin(r); - output[o + 2] = r; - } + static void AddBeforePosition(float p, float[] temp, int i, float[] output, int o) + { + float x1 = temp[i], y1 = temp[i + 1], dx = temp[i + 2] - x1, dy = temp[i + 3] - y1, r = MathUtils.Atan2(dy, dx); + output[o] = x1 + p * MathUtils.Cos(r); + output[o + 1] = y1 + p * MathUtils.Sin(r); + output[o + 2] = r; + } - static void AddCurvePosition (float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2, - float[] output, int o, bool tangents) { - if (p < PathConstraint.Epsilon || float.IsNaN(p)) { - output[o] = x1; - output[o + 1] = y1; - output[o + 2] = (float)Math.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.001f) - output[o + 2] = (float)Math.Atan2(cy1 - y1, cx1 - x1); - else - output[o + 2] = (float)Math.Atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt)); - } - } - - public int Order { get { return data.order; } } - /// The position along the path. - public float Position { get { return position; } set { position = value; } } - /// The spacing between bones. - public float Spacing { get { return spacing; } set { spacing = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotations. - public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained translations. - public float TranslateMix { get { return translateMix; } set { translateMix = value; } } - /// The bones that will be modified by this path constraint. - public ExposedList Bones { get { return bones; } } - /// The slot whose path attachment will be used to constrained the bones. - public Slot Target { get { return target; } set { target = value; } } - /// The path constraint's setup pose data. - public PathConstraintData Data { get { return data; } } + static void AddAfterPosition(float p, float[] temp, int i, float[] output, int o) + { + float x1 = temp[i + 2], y1 = temp[i + 3], dx = x1 - temp[i], dy = y1 - temp[i + 1], r = MathUtils.Atan2(dy, dx); + output[o] = x1 + p * MathUtils.Cos(r); + output[o + 1] = y1 + p * MathUtils.Sin(r); + output[o + 2] = r; + } - override public string ToString () { - return data.name; - } - } + static void AddCurvePosition(float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2, + float[] output, int o, bool tangents) + { + if (p < PathConstraint.Epsilon || float.IsNaN(p)) + { + output[o] = x1; + output[o + 1] = y1; + output[o + 2] = (float)Math.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.001f) + output[o + 2] = (float)Math.Atan2(cy1 - y1, cx1 - x1); + else + output[o + 2] = (float)Math.Atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt)); + } + } + + public int Order { get { return data.order; } } + /// The position along the path. + public float Position { get { return position; } set { position = value; } } + /// The spacing between bones. + public float Spacing { get { return spacing; } set { spacing = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotations. + public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained translations. + public float TranslateMix { get { return translateMix; } set { translateMix = value; } } + /// The bones that will be modified by this path constraint. + public ExposedList Bones { get { return bones; } } + /// The slot whose path attachment will be used to constrained the bones. + public Slot Target { get { return target; } set { target = value; } } + /// The path constraint's setup pose data. + public PathConstraintData Data { get { return data; } } + + override public string ToString() + { + return data.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/PathConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/PathConstraintData.cs index 6a7044d..31dc127 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/PathConstraintData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/PathConstraintData.cs @@ -29,50 +29,57 @@ using System; -namespace Spine3_7_94 { - public class PathConstraintData { - internal string name; - internal int order; - internal ExposedList bones = new ExposedList(); - internal SlotData target; - internal PositionMode positionMode; - internal SpacingMode spacingMode; - internal RotateMode rotateMode; - internal float offsetRotation; - internal float position, spacing, rotateMix, translateMix; +namespace Spine3_7_94 +{ + public class PathConstraintData + { + internal string name; + internal int order; + internal ExposedList bones = new ExposedList(); + internal SlotData target; + internal PositionMode positionMode; + internal SpacingMode spacingMode; + internal RotateMode rotateMode; + internal float offsetRotation; + internal float position, spacing, rotateMix, translateMix; - public string Name { get { return name; } } - public int Order { get { return order; } set { order = value; } } - public ExposedList Bones { get { return bones; } } - public SlotData Target { get { return target; } set { target = value; } } - public PositionMode PositionMode { get { return positionMode; } set { positionMode = value; } } - public SpacingMode SpacingMode { get { return spacingMode; } set { spacingMode = value; } } - public RotateMode RotateMode { get { return rotateMode; } set { rotateMode = value; } } - public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } - public float Position { get { return position; } set { position = value; } } - public float Spacing { get { return spacing; } set { spacing = value; } } - public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } - public float TranslateMix { get { return translateMix; } set { translateMix = value; } } + public string Name { get { return name; } } + public int Order { get { return order; } set { order = value; } } + public ExposedList Bones { get { return bones; } } + public SlotData Target { get { return target; } set { target = value; } } + public PositionMode PositionMode { get { return positionMode; } set { positionMode = value; } } + public SpacingMode SpacingMode { get { return spacingMode; } set { spacingMode = value; } } + public RotateMode RotateMode { get { return rotateMode; } set { rotateMode = value; } } + public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } + public float Position { get { return position; } set { position = value; } } + public float Spacing { get { return spacing; } set { spacing = value; } } + public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } + public float TranslateMix { get { return translateMix; } set { translateMix = value; } } - public PathConstraintData (String name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; - } + public PathConstraintData(String name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + } - public override string ToString () { - return name; - } - } - - public enum PositionMode { - Fixed, Percent - } + public override string ToString() + { + return name; + } + } - public enum SpacingMode { - Length, Fixed, Percent - } + public enum PositionMode + { + Fixed, Percent + } - public enum RotateMode { - Tangent, Chain, ChainScale - } + public enum SpacingMode + { + Length, Fixed, Percent + } + + public enum RotateMode + { + Tangent, Chain, ChainScale + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Skeleton.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Skeleton.cs index e0bfafd..989def1 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Skeleton.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Skeleton.cs @@ -28,572 +28,648 @@ *****************************************************************************/ using System; -using System.Collections.Generic; - -namespace Spine3_7_94 { - public class Skeleton { - internal SkeletonData data; - internal ExposedList bones; - internal ExposedList slots; - internal ExposedList drawOrder; - internal ExposedList ikConstraints; - internal ExposedList transformConstraints; - internal ExposedList pathConstraints; - internal ExposedList updateCache = new ExposedList(); - internal ExposedList updateCacheReset = new ExposedList(); - internal Skin skin; - internal float r = 1, g = 1, b = 1, a = 1; - internal float time; - internal float scaleX = 1, scaleY = 1; - internal float x, y; - - public SkeletonData Data { get { return data; } } - public ExposedList Bones { get { return bones; } } - public ExposedList UpdateCacheList { get { return updateCache; } } - public ExposedList Slots { get { return slots; } } - public ExposedList DrawOrder { get { return drawOrder; } } - public ExposedList IkConstraints { get { return ikConstraints; } } - public ExposedList PathConstraints { get { return pathConstraints; } } - public ExposedList TransformConstraints { get { return transformConstraints; } } - public Skin Skin { get { return skin; } set { skin = value; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } - public float Time { get { return time; } set { time = value; } } - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - public float ScaleY { get { return scaleY; } set { scaleY = value; } } - - [Obsolete("Use ScaleX instead. FlipX is when ScaleX is negative.")] - public bool FlipX { get { return scaleX < 0; } set { scaleX = value ? -1f : 1f; } } - - [Obsolete("Use ScaleY instead. FlipY is when ScaleY is negative.")] - public bool FlipY { get { return scaleY < 0; } set { scaleY = value ? -1f : 1f; } } - - public Bone RootBone { - get { return bones.Count == 0 ? null : bones.Items[0]; } - } - - public Skeleton (SkeletonData data) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - this.data = data; - - bones = new ExposedList(data.bones.Count); - foreach (BoneData boneData in data.bones) { - Bone bone; - if (boneData.parent == null) { - bone = new Bone(boneData, this, null); - } else { - Bone parent = bones.Items[boneData.parent.index]; - bone = new Bone(boneData, this, parent); - parent.children.Add(bone); - } - bones.Add(bone); - } - - slots = new ExposedList(data.slots.Count); - drawOrder = new ExposedList(data.slots.Count); - foreach (SlotData slotData in data.slots) { - Bone bone = bones.Items[slotData.boneData.index]; - Slot slot = new Slot(slotData, bone); - slots.Add(slot); - drawOrder.Add(slot); - } - - ikConstraints = new ExposedList(data.ikConstraints.Count); - foreach (IkConstraintData ikConstraintData in data.ikConstraints) - ikConstraints.Add(new IkConstraint(ikConstraintData, this)); - - transformConstraints = new ExposedList(data.transformConstraints.Count); - foreach (TransformConstraintData transformConstraintData in data.transformConstraints) - transformConstraints.Add(new TransformConstraint(transformConstraintData, this)); - - pathConstraints = new ExposedList (data.pathConstraints.Count); - foreach (PathConstraintData pathConstraintData in data.pathConstraints) - pathConstraints.Add(new PathConstraint(pathConstraintData, this)); - - UpdateCache(); - UpdateWorldTransform(); - } - - /// Caches information about bones and constraints. Must be called if bones, constraints or weighted path attachments are added - /// or removed. - public void UpdateCache () { - var updateCache = this.updateCache; - updateCache.Clear(); - this.updateCacheReset.Clear(); - - var bones = this.bones; - for (int i = 0, n = bones.Count; i < n; i++) - bones.Items[i].sorted = false; - - var ikConstraints = this.ikConstraints; - var transformConstraints = this.transformConstraints; - var pathConstraints = this.pathConstraints; - int ikCount = ikConstraints.Count, transformCount = transformConstraints.Count, pathCount = pathConstraints.Count; - int constraintCount = ikCount + transformCount + pathCount; - //outer: - for (int i = 0; i < constraintCount; i++) { - for (int ii = 0; ii < ikCount; ii++) { - IkConstraint constraint = ikConstraints.Items[ii]; - if (constraint.data.order == i) { - SortIkConstraint(constraint); - goto continue_outer; //continue outer; - } - } - for (int ii = 0; ii < transformCount; ii++) { - TransformConstraint constraint = transformConstraints.Items[ii]; - if (constraint.data.order == i) { - SortTransformConstraint(constraint); - goto continue_outer; //continue outer; - } - } - for (int ii = 0; ii < pathCount; ii++) { - PathConstraint constraint = pathConstraints.Items[ii]; - if (constraint.data.order == i) { - SortPathConstraint(constraint); - goto continue_outer; //continue outer; - } - } - continue_outer: {} - } - - for (int i = 0, n = bones.Count; i < n; i++) - SortBone(bones.Items[i]); - } - - private void SortIkConstraint (IkConstraint constraint) { - Bone target = constraint.target; - SortBone(target); - - var constrained = constraint.bones; - Bone parent = constrained.Items[0]; - SortBone(parent); - - if (constrained.Count > 1) { - Bone child = constrained.Items[constrained.Count - 1]; - if (!updateCache.Contains(child)) - updateCacheReset.Add(child); - } - - updateCache.Add(constraint); - - SortReset(parent.children); - constrained.Items[constrained.Count - 1].sorted = true; - } - - private void SortPathConstraint (PathConstraint constraint) { - Slot slot = constraint.target; - int slotIndex = slot.data.index; - Bone slotBone = slot.bone; - if (skin != null) SortPathConstraintAttachment(skin, slotIndex, slotBone); - if (data.defaultSkin != null && data.defaultSkin != skin) - SortPathConstraintAttachment(data.defaultSkin, slotIndex, slotBone); - for (int ii = 0, nn = data.skins.Count; ii < nn; ii++) - SortPathConstraintAttachment(data.skins.Items[ii], slotIndex, slotBone); - - Attachment attachment = slot.attachment; - if (attachment is PathAttachment) SortPathConstraintAttachment(attachment, slotBone); - - var constrained = constraint.bones; - int boneCount = constrained.Count; - for (int i = 0; i < boneCount; i++) - SortBone(constrained.Items[i]); - - updateCache.Add(constraint); - - for (int i = 0; i < boneCount; i++) - SortReset(constrained.Items[i].children); - for (int i = 0; i < boneCount; i++) - constrained.Items[i].sorted = true; - } - - private void SortTransformConstraint (TransformConstraint constraint) { - SortBone(constraint.target); - - var constrained = constraint.bones; - int boneCount = constrained.Count; - if (constraint.data.local) { - for (int i = 0; i < boneCount; i++) { - Bone child = constrained.Items[i]; - SortBone(child.parent); - if (!updateCache.Contains(child)) updateCacheReset.Add(child); - } - } else { - for (int i = 0; i < boneCount; i++) - SortBone(constrained.Items[i]); - } - - updateCache.Add(constraint); - - for (int i = 0; i < boneCount; i++) - SortReset(constrained.Items[i].children); - for (int i = 0; i < boneCount; i++) - constrained.Items[i].sorted = true; - } - - private void SortPathConstraintAttachment (Skin skin, int slotIndex, Bone slotBone) { - foreach (var entry in skin.Attachments) - if (entry.Key.slotIndex == slotIndex) SortPathConstraintAttachment(entry.Value, slotBone); - } - - private void SortPathConstraintAttachment (Attachment attachment, Bone slotBone) { - if (!(attachment is PathAttachment)) return; - int[] pathBones = ((PathAttachment)attachment).bones; - if (pathBones == null) - SortBone(slotBone); - else { - var bones = this.bones; - for (int i = 0, n = pathBones.Length; i < n;) { - int nn = pathBones[i++]; - nn += i; - while (i < nn) - SortBone(bones.Items[pathBones[i++]]); - } - } - } - - private void SortBone (Bone bone) { - if (bone.sorted) return; - Bone parent = bone.parent; - if (parent != null) SortBone(parent); - bone.sorted = true; - updateCache.Add(bone); - } - - private static void SortReset (ExposedList bones) { - var bonesItems = bones.Items; - for (int i = 0, n = bones.Count; i < n; i++) { - Bone bone = bonesItems[i]; - if (bone.sorted) SortReset(bone.children); - bone.sorted = false; - } - } - - /// Updates the world transform for each bone and applies constraints. - public void UpdateWorldTransform () { - var updateCacheReset = this.updateCacheReset; - var updateCacheResetItems = updateCacheReset.Items; - for (int i = 0, n = updateCacheReset.Count; i < n; i++) { - Bone bone = updateCacheResetItems[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; - bone.appliedValid = true; - } - var updateItems = this.updateCache.Items; - for (int i = 0, n = updateCache.Count; i < n; i++) - updateItems[i].Update(); - } - - /// - /// Updates the world transform for each bone and applies all constraints. The root bone will be temporarily parented to the specified bone. - /// - public void UpdateWorldTransform (Bone parent) { - // This partial update avoids computing the world transform for constrained bones when 1) the bone is not updated - // before the constraint, 2) the constraint only needs to access the applied local transform, and 3) the constraint calls - // updateWorldTransform. - var updateCacheReset = this.updateCacheReset; - var updateCacheResetItems = updateCacheReset.Items; - for (int i = 0, n = updateCacheReset.Count; i < n; i++) { - Bone bone = updateCacheResetItems[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; - bone.appliedValid = true; - } - - // Apply the parent bone transform to the root bone. The root bone - // always inherits scale, rotation and reflection. - Bone rootBone = this.RootBone; - 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 rotationY = rootBone.rotation + 90 + rootBone.shearY; - float la = MathUtils.CosDeg(rootBone.rotation + rootBone.shearX) * rootBone.scaleX; - float lb = MathUtils.CosDeg(rotationY) * rootBone.scaleY; - float lc = MathUtils.SinDeg(rootBone.rotation + rootBone.shearX) * rootBone.scaleX; - float ld = MathUtils.SinDeg(rotationY) * 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. - var updateCache = this.updateCache; - var updateCacheItems = updateCache.Items; - for (int i = 0, n = updateCache.Count; i < n; i++) { - var updatable = updateCacheItems[i]; - if (updatable != rootBone) - updatable.Update(); - } - } - - /// Sets the bones, constraints, and slots to their setup pose values. - public void SetToSetupPose () { - SetBonesToSetupPose(); - SetSlotsToSetupPose(); - } - - /// Sets the bones and constraints to their setup pose values. - public void SetBonesToSetupPose () { - var bonesItems = this.bones.Items; - for (int i = 0, n = bones.Count; i < n; i++) - bonesItems[i].SetToSetupPose(); - - var ikConstraintsItems = this.ikConstraints.Items; - for (int i = 0, n = ikConstraints.Count; i < n; i++) { - IkConstraint constraint = ikConstraintsItems[i]; - constraint.mix = constraint.data.mix; - constraint.bendDirection = constraint.data.bendDirection; - constraint.compress = constraint.data.compress; - constraint.stretch = constraint.data.stretch; - } - - var transformConstraintsItems = this.transformConstraints.Items; - for (int i = 0, n = transformConstraints.Count; i < n; i++) { - TransformConstraint constraint = transformConstraintsItems[i]; - TransformConstraintData constraintData = constraint.data; - constraint.rotateMix = constraintData.rotateMix; - constraint.translateMix = constraintData.translateMix; - constraint.scaleMix = constraintData.scaleMix; - constraint.shearMix = constraintData.shearMix; - } - - var pathConstraintItems = this.pathConstraints.Items; - for (int i = 0, n = pathConstraints.Count; i < n; i++) { - PathConstraint constraint = pathConstraintItems[i]; - PathConstraintData constraintData = constraint.data; - constraint.position = constraintData.position; - constraint.spacing = constraintData.spacing; - constraint.rotateMix = constraintData.rotateMix; - constraint.translateMix = constraintData.translateMix; - } - } - - public void SetSlotsToSetupPose () { - var slots = this.slots; - var slotsItems = slots.Items; - drawOrder.Clear(); - for (int i = 0, n = slots.Count; i < n; i++) - drawOrder.Add(slotsItems[i]); - - for (int i = 0, n = slots.Count; i < n; i++) - slotsItems[i].SetToSetupPose(); - } - - /// May be null. - public Bone FindBone (string boneName) { - if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); - var bones = this.bones; - var bonesItems = bones.Items; - for (int i = 0, n = bones.Count; i < n; i++) { - Bone bone = bonesItems[i]; - if (bone.data.name == boneName) return bone; - } - return null; - } - - /// -1 if the bone was not found. - public int FindBoneIndex (string boneName) { - if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); - var bones = this.bones; - var bonesItems = bones.Items; - for (int i = 0, n = bones.Count; i < n; i++) - if (bonesItems[i].data.name == boneName) return i; - return -1; - } - - /// May be null. - public Slot FindSlot (string slotName) { - if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); - var slots = this.slots; - var slotsItems = slots.Items; - for (int i = 0, n = slots.Count; i < n; i++) { - Slot slot = slotsItems[i]; - if (slot.data.name == slotName) return slot; - } - return null; - } - - /// -1 if the bone was not found. - public int FindSlotIndex (string slotName) { - if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); - var slots = this.slots; - var slotsItems = slots.Items; - for (int i = 0, n = slots.Count; i < n; i++) - if (slotsItems[i].data.name.Equals(slotName)) return i; - return -1; - } - - /// Sets a skin by name (see SetSkin). - public void SetSkin (string skinName) { - Skin foundSkin = data.FindSkin(skinName); - if (foundSkin == null) throw new ArgumentException("Skin not found: " + skinName, "skinName"); - SetSkin(foundSkin); - } - - /// - /// 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 - /// . - /// Also, often 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. - /// - /// May be null. - public void SetSkin (Skin newSkin) { - if (newSkin != null) { - if (skin != null) - newSkin.AttachAll(this, skin); - else { - ExposedList slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) { - Slot slot = slots.Items[i]; - string name = slot.data.attachmentName; - if (name != null) { - Attachment attachment = newSkin.GetAttachment(i, name); - if (attachment != null) slot.Attachment = attachment; - } - } - } - } - skin = newSkin; - } - - /// Finds an attachment by looking in the {@link #skin} and {@link SkeletonData#defaultSkin} using the slot name and attachment name. - /// May be null. - public Attachment GetAttachment (string slotName, string attachmentName) { - return GetAttachment(data.FindSlotIndex(slotName), attachmentName); - } - - /// Finds an attachment by looking in the skin and skeletonData.defaultSkin using the slot index and attachment name.First the skin is checked and if the attachment was not found, the default skin is checked. - /// May be null. - public Attachment GetAttachment (int slotIndex, string attachmentName) { - if (attachmentName == null) throw new ArgumentNullException("attachmentName", "attachmentName cannot be null."); - if (skin != null) { - Attachment attachment = skin.GetAttachment(slotIndex, attachmentName); - if (attachment != null) return attachment; - } - return data.defaultSkin != null ? data.defaultSkin.GetAttachment(slotIndex, attachmentName) : null; - } - - /// A convenience method to set an attachment by finding the slot with FindSlot, finding the attachment with GetAttachment, then setting the slot's slot.Attachment. - /// May be null. - public void SetAttachment (string slotName, string attachmentName) { - if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); - ExposedList slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) { - Slot slot = slots.Items[i]; - if (slot.data.name == slotName) { - Attachment attachment = null; - if (attachmentName != null) { - attachment = GetAttachment(i, attachmentName); - if (attachment == null) throw new Exception("Attachment not found: " + attachmentName + ", for slot: " + slotName); - } - slot.Attachment = attachment; - return; - } - } - throw new Exception("Slot not found: " + slotName); - } - - /// May be null. - public IkConstraint FindIkConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - ExposedList ikConstraints = this.ikConstraints; - for (int i = 0, n = ikConstraints.Count; i < n; i++) { - IkConstraint ikConstraint = ikConstraints.Items[i]; - if (ikConstraint.data.name == constraintName) return ikConstraint; - } - return null; - } - - /// May be null. - public TransformConstraint FindTransformConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - ExposedList transformConstraints = this.transformConstraints; - for (int i = 0, n = transformConstraints.Count; i < n; i++) { - TransformConstraint transformConstraint = transformConstraints.Items[i]; - if (transformConstraint.data.name == constraintName) return transformConstraint; - } - return null; - } - - /// May be null. - public PathConstraint FindPathConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - ExposedList pathConstraints = this.pathConstraints; - for (int i = 0, n = pathConstraints.Count; i < n; i++) { - PathConstraint constraint = pathConstraints.Items[i]; - if (constraint.data.name.Equals(constraintName)) return constraint; - } - return null; - } - - public void Update (float delta) { - time += delta; - } - - /// Returns the axis aligned bounding box (AABB) of the region and mesh attachments for the current pose. - /// The horizontal distance between the skeleton origin and the left side of the AABB. - /// The vertical distance between the skeleton origin and the bottom side of the AABB. - /// The width of the AABB - /// The height of the AABB. - /// Reference to hold a float[]. May be a null reference. This method will assign it a new float[] with the appropriate size as needed. - public void GetBounds (out float x, out float y, out float width, out float height, ref float[] vertexBuffer) { - float[] temp = vertexBuffer; - temp = temp ?? new float[8]; - var drawOrderItems = this.drawOrder.Items; - float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; - for (int i = 0, n = this.drawOrder.Count; i < n; i++) { - Slot slot = drawOrderItems[i]; - int verticesLength = 0; - float[] vertices = null; - Attachment attachment = slot.attachment; - var regionAttachment = attachment as RegionAttachment; - if (regionAttachment != null) { - verticesLength = 8; - vertices = temp; - if (vertices.Length < 8) vertices = temp = new float[8]; - regionAttachment.ComputeWorldVertices(slot.bone, temp, 0); - } else { - var meshAttachment = attachment as MeshAttachment; - if (meshAttachment != null) { - MeshAttachment mesh = meshAttachment; - verticesLength = mesh.WorldVerticesLength; - vertices = temp; - if (vertices.Length < verticesLength) vertices = temp = new float[verticesLength]; - mesh.ComputeWorldVertices(slot, 0, verticesLength, temp, 0); - } - } - - if (vertices != null) { - for (int ii = 0; ii < verticesLength; ii += 2) { - float vx = vertices[ii], vy = vertices[ii + 1]; - minX = Math.Min(minX, vx); - minY = Math.Min(minY, vy); - maxX = Math.Max(maxX, vx); - maxY = Math.Max(maxY, vy); - } - } - } - x = minX; - y = minY; - width = maxX - minX; - height = maxY - minY; - vertexBuffer = temp; - } - } + +namespace Spine3_7_94 +{ + public class Skeleton + { + internal SkeletonData data; + internal ExposedList bones; + internal ExposedList slots; + internal ExposedList drawOrder; + internal ExposedList ikConstraints; + internal ExposedList transformConstraints; + internal ExposedList pathConstraints; + internal ExposedList updateCache = new ExposedList(); + internal ExposedList updateCacheReset = new ExposedList(); + internal Skin skin; + internal float r = 1, g = 1, b = 1, a = 1; + internal float time; + internal float scaleX = 1, scaleY = 1; + internal float x, y; + + public SkeletonData Data { get { return data; } } + public ExposedList Bones { get { return bones; } } + public ExposedList UpdateCacheList { get { return updateCache; } } + public ExposedList Slots { get { return slots; } } + public ExposedList DrawOrder { get { return drawOrder; } } + public ExposedList IkConstraints { get { return ikConstraints; } } + public ExposedList PathConstraints { get { return pathConstraints; } } + public ExposedList TransformConstraints { get { return transformConstraints; } } + public Skin Skin { get { return skin; } set { skin = value; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } + public float Time { get { return time; } set { time = value; } } + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + + [Obsolete("Use ScaleX instead. FlipX is when ScaleX is negative.")] + public bool FlipX { get { return scaleX < 0; } set { scaleX = value ? -1f : 1f; } } + + [Obsolete("Use ScaleY instead. FlipY is when ScaleY is negative.")] + public bool FlipY { get { return scaleY < 0; } set { scaleY = value ? -1f : 1f; } } + + public Bone RootBone + { + get { return bones.Count == 0 ? null : bones.Items[0]; } + } + + public Skeleton(SkeletonData data) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + this.data = data; + + bones = new ExposedList(data.bones.Count); + foreach (BoneData boneData in data.bones) + { + Bone bone; + if (boneData.parent == null) + { + bone = new Bone(boneData, this, null); + } + else + { + Bone parent = bones.Items[boneData.parent.index]; + bone = new Bone(boneData, this, parent); + parent.children.Add(bone); + } + bones.Add(bone); + } + + slots = new ExposedList(data.slots.Count); + drawOrder = new ExposedList(data.slots.Count); + foreach (SlotData slotData in data.slots) + { + Bone bone = bones.Items[slotData.boneData.index]; + Slot slot = new Slot(slotData, bone); + slots.Add(slot); + drawOrder.Add(slot); + } + + ikConstraints = new ExposedList(data.ikConstraints.Count); + foreach (IkConstraintData ikConstraintData in data.ikConstraints) + ikConstraints.Add(new IkConstraint(ikConstraintData, this)); + + transformConstraints = new ExposedList(data.transformConstraints.Count); + foreach (TransformConstraintData transformConstraintData in data.transformConstraints) + transformConstraints.Add(new TransformConstraint(transformConstraintData, this)); + + pathConstraints = new ExposedList(data.pathConstraints.Count); + foreach (PathConstraintData pathConstraintData in data.pathConstraints) + pathConstraints.Add(new PathConstraint(pathConstraintData, this)); + + UpdateCache(); + UpdateWorldTransform(); + } + + /// Caches information about bones and constraints. Must be called if bones, constraints or weighted path attachments are added + /// or removed. + public void UpdateCache() + { + var updateCache = this.updateCache; + updateCache.Clear(); + this.updateCacheReset.Clear(); + + var bones = this.bones; + for (int i = 0, n = bones.Count; i < n; i++) + bones.Items[i].sorted = false; + + var ikConstraints = this.ikConstraints; + var transformConstraints = this.transformConstraints; + var pathConstraints = this.pathConstraints; + int ikCount = ikConstraints.Count, transformCount = transformConstraints.Count, pathCount = pathConstraints.Count; + int constraintCount = ikCount + transformCount + pathCount; + //outer: + for (int i = 0; i < constraintCount; i++) + { + for (int ii = 0; ii < ikCount; ii++) + { + IkConstraint constraint = ikConstraints.Items[ii]; + if (constraint.data.order == i) + { + SortIkConstraint(constraint); + goto continue_outer; //continue outer; + } + } + for (int ii = 0; ii < transformCount; ii++) + { + TransformConstraint constraint = transformConstraints.Items[ii]; + if (constraint.data.order == i) + { + SortTransformConstraint(constraint); + goto continue_outer; //continue outer; + } + } + for (int ii = 0; ii < pathCount; ii++) + { + PathConstraint constraint = pathConstraints.Items[ii]; + if (constraint.data.order == i) + { + SortPathConstraint(constraint); + goto continue_outer; //continue outer; + } + } + continue_outer: { } + } + + for (int i = 0, n = bones.Count; i < n; i++) + SortBone(bones.Items[i]); + } + + private void SortIkConstraint(IkConstraint constraint) + { + Bone target = constraint.target; + SortBone(target); + + var constrained = constraint.bones; + Bone parent = constrained.Items[0]; + SortBone(parent); + + if (constrained.Count > 1) + { + Bone child = constrained.Items[constrained.Count - 1]; + if (!updateCache.Contains(child)) + updateCacheReset.Add(child); + } + + updateCache.Add(constraint); + + SortReset(parent.children); + constrained.Items[constrained.Count - 1].sorted = true; + } + + private void SortPathConstraint(PathConstraint constraint) + { + Slot slot = constraint.target; + int slotIndex = slot.data.index; + Bone slotBone = slot.bone; + if (skin != null) SortPathConstraintAttachment(skin, slotIndex, slotBone); + if (data.defaultSkin != null && data.defaultSkin != skin) + SortPathConstraintAttachment(data.defaultSkin, slotIndex, slotBone); + for (int ii = 0, nn = data.skins.Count; ii < nn; ii++) + SortPathConstraintAttachment(data.skins.Items[ii], slotIndex, slotBone); + + Attachment attachment = slot.attachment; + if (attachment is PathAttachment) SortPathConstraintAttachment(attachment, slotBone); + + var constrained = constraint.bones; + int boneCount = constrained.Count; + for (int i = 0; i < boneCount; i++) + SortBone(constrained.Items[i]); + + updateCache.Add(constraint); + + for (int i = 0; i < boneCount; i++) + SortReset(constrained.Items[i].children); + for (int i = 0; i < boneCount; i++) + constrained.Items[i].sorted = true; + } + + private void SortTransformConstraint(TransformConstraint constraint) + { + SortBone(constraint.target); + + var constrained = constraint.bones; + int boneCount = constrained.Count; + if (constraint.data.local) + { + for (int i = 0; i < boneCount; i++) + { + Bone child = constrained.Items[i]; + SortBone(child.parent); + if (!updateCache.Contains(child)) updateCacheReset.Add(child); + } + } + else + { + for (int i = 0; i < boneCount; i++) + SortBone(constrained.Items[i]); + } + + updateCache.Add(constraint); + + for (int i = 0; i < boneCount; i++) + SortReset(constrained.Items[i].children); + for (int i = 0; i < boneCount; i++) + constrained.Items[i].sorted = true; + } + + private void SortPathConstraintAttachment(Skin skin, int slotIndex, Bone slotBone) + { + foreach (var entry in skin.Attachments) + if (entry.Key.slotIndex == slotIndex) SortPathConstraintAttachment(entry.Value, slotBone); + } + + private void SortPathConstraintAttachment(Attachment attachment, Bone slotBone) + { + if (!(attachment is PathAttachment)) return; + int[] pathBones = ((PathAttachment)attachment).bones; + if (pathBones == null) + SortBone(slotBone); + else + { + var bones = this.bones; + for (int i = 0, n = pathBones.Length; i < n;) + { + int nn = pathBones[i++]; + nn += i; + while (i < nn) + SortBone(bones.Items[pathBones[i++]]); + } + } + } + + private void SortBone(Bone bone) + { + if (bone.sorted) return; + Bone parent = bone.parent; + if (parent != null) SortBone(parent); + bone.sorted = true; + updateCache.Add(bone); + } + + private static void SortReset(ExposedList bones) + { + var bonesItems = bones.Items; + for (int i = 0, n = bones.Count; i < n; i++) + { + Bone bone = bonesItems[i]; + if (bone.sorted) SortReset(bone.children); + bone.sorted = false; + } + } + + /// Updates the world transform for each bone and applies constraints. + public void UpdateWorldTransform() + { + var updateCacheReset = this.updateCacheReset; + var updateCacheResetItems = updateCacheReset.Items; + for (int i = 0, n = updateCacheReset.Count; i < n; i++) + { + Bone bone = updateCacheResetItems[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; + bone.appliedValid = true; + } + var updateItems = this.updateCache.Items; + for (int i = 0, n = updateCache.Count; i < n; i++) + updateItems[i].Update(); + } + + /// + /// Updates the world transform for each bone and applies all constraints. The root bone will be temporarily parented to the specified bone. + /// + public void UpdateWorldTransform(Bone parent) + { + // This partial update avoids computing the world transform for constrained bones when 1) the bone is not updated + // before the constraint, 2) the constraint only needs to access the applied local transform, and 3) the constraint calls + // updateWorldTransform. + var updateCacheReset = this.updateCacheReset; + var updateCacheResetItems = updateCacheReset.Items; + for (int i = 0, n = updateCacheReset.Count; i < n; i++) + { + Bone bone = updateCacheResetItems[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; + bone.appliedValid = true; + } + + // Apply the parent bone transform to the root bone. The root bone + // always inherits scale, rotation and reflection. + Bone rootBone = this.RootBone; + 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 rotationY = rootBone.rotation + 90 + rootBone.shearY; + float la = MathUtils.CosDeg(rootBone.rotation + rootBone.shearX) * rootBone.scaleX; + float lb = MathUtils.CosDeg(rotationY) * rootBone.scaleY; + float lc = MathUtils.SinDeg(rootBone.rotation + rootBone.shearX) * rootBone.scaleX; + float ld = MathUtils.SinDeg(rotationY) * 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. + var updateCache = this.updateCache; + var updateCacheItems = updateCache.Items; + for (int i = 0, n = updateCache.Count; i < n; i++) + { + var updatable = updateCacheItems[i]; + if (updatable != rootBone) + updatable.Update(); + } + } + + /// Sets the bones, constraints, and slots to their setup pose values. + public void SetToSetupPose() + { + SetBonesToSetupPose(); + SetSlotsToSetupPose(); + } + + /// Sets the bones and constraints to their setup pose values. + public void SetBonesToSetupPose() + { + var bonesItems = this.bones.Items; + for (int i = 0, n = bones.Count; i < n; i++) + bonesItems[i].SetToSetupPose(); + + var ikConstraintsItems = this.ikConstraints.Items; + for (int i = 0, n = ikConstraints.Count; i < n; i++) + { + IkConstraint constraint = ikConstraintsItems[i]; + constraint.mix = constraint.data.mix; + constraint.bendDirection = constraint.data.bendDirection; + constraint.compress = constraint.data.compress; + constraint.stretch = constraint.data.stretch; + } + + var transformConstraintsItems = this.transformConstraints.Items; + for (int i = 0, n = transformConstraints.Count; i < n; i++) + { + TransformConstraint constraint = transformConstraintsItems[i]; + TransformConstraintData constraintData = constraint.data; + constraint.rotateMix = constraintData.rotateMix; + constraint.translateMix = constraintData.translateMix; + constraint.scaleMix = constraintData.scaleMix; + constraint.shearMix = constraintData.shearMix; + } + + var pathConstraintItems = this.pathConstraints.Items; + for (int i = 0, n = pathConstraints.Count; i < n; i++) + { + PathConstraint constraint = pathConstraintItems[i]; + PathConstraintData constraintData = constraint.data; + constraint.position = constraintData.position; + constraint.spacing = constraintData.spacing; + constraint.rotateMix = constraintData.rotateMix; + constraint.translateMix = constraintData.translateMix; + } + } + + public void SetSlotsToSetupPose() + { + var slots = this.slots; + var slotsItems = slots.Items; + drawOrder.Clear(); + for (int i = 0, n = slots.Count; i < n; i++) + drawOrder.Add(slotsItems[i]); + + for (int i = 0, n = slots.Count; i < n; i++) + slotsItems[i].SetToSetupPose(); + } + + /// May be null. + public Bone FindBone(string boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); + var bones = this.bones; + var bonesItems = bones.Items; + for (int i = 0, n = bones.Count; i < n; i++) + { + Bone bone = bonesItems[i]; + if (bone.data.name == boneName) return bone; + } + return null; + } + + /// -1 if the bone was not found. + public int FindBoneIndex(string boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); + var bones = this.bones; + var bonesItems = bones.Items; + for (int i = 0, n = bones.Count; i < n; i++) + if (bonesItems[i].data.name == boneName) return i; + return -1; + } + + /// May be null. + public Slot FindSlot(string slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + var slots = this.slots; + var slotsItems = slots.Items; + for (int i = 0, n = slots.Count; i < n; i++) + { + Slot slot = slotsItems[i]; + if (slot.data.name == slotName) return slot; + } + return null; + } + + /// -1 if the bone was not found. + public int FindSlotIndex(string slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + var slots = this.slots; + var slotsItems = slots.Items; + for (int i = 0, n = slots.Count; i < n; i++) + if (slotsItems[i].data.name.Equals(slotName)) return i; + return -1; + } + + /// Sets a skin by name (see SetSkin). + public void SetSkin(string skinName) + { + Skin foundSkin = data.FindSkin(skinName); + if (foundSkin == null) throw new ArgumentException("Skin not found: " + skinName, "skinName"); + SetSkin(foundSkin); + } + + /// + /// 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 + /// . + /// Also, often 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. + /// + /// May be null. + public void SetSkin(Skin newSkin) + { + if (newSkin != null) + { + if (skin != null) + newSkin.AttachAll(this, skin); + else + { + ExposedList slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + { + Slot slot = slots.Items[i]; + string name = slot.data.attachmentName; + if (name != null) + { + Attachment attachment = newSkin.GetAttachment(i, name); + if (attachment != null) slot.Attachment = attachment; + } + } + } + } + skin = newSkin; + } + + /// Finds an attachment by looking in the {@link #skin} and {@link SkeletonData#defaultSkin} using the slot name and attachment name. + /// May be null. + public Attachment GetAttachment(string slotName, string attachmentName) + { + return GetAttachment(data.FindSlotIndex(slotName), attachmentName); + } + + /// Finds an attachment by looking in the skin and skeletonData.defaultSkin using the slot index and attachment name.First the skin is checked and if the attachment was not found, the default skin is checked. + /// May be null. + public Attachment GetAttachment(int slotIndex, string attachmentName) + { + if (attachmentName == null) throw new ArgumentNullException("attachmentName", "attachmentName cannot be null."); + if (skin != null) + { + Attachment attachment = skin.GetAttachment(slotIndex, attachmentName); + if (attachment != null) return attachment; + } + return data.defaultSkin != null ? data.defaultSkin.GetAttachment(slotIndex, attachmentName) : null; + } + + /// A convenience method to set an attachment by finding the slot with FindSlot, finding the attachment with GetAttachment, then setting the slot's slot.Attachment. + /// May be null. + public void SetAttachment(string slotName, string attachmentName) + { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + ExposedList slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + { + Slot slot = slots.Items[i]; + if (slot.data.name == slotName) + { + Attachment attachment = null; + if (attachmentName != null) + { + attachment = GetAttachment(i, attachmentName); + if (attachment == null) throw new Exception("Attachment not found: " + attachmentName + ", for slot: " + slotName); + } + slot.Attachment = attachment; + return; + } + } + throw new Exception("Slot not found: " + slotName); + } + + /// May be null. + public IkConstraint FindIkConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + ExposedList ikConstraints = this.ikConstraints; + for (int i = 0, n = ikConstraints.Count; i < n; i++) + { + IkConstraint ikConstraint = ikConstraints.Items[i]; + if (ikConstraint.data.name == constraintName) return ikConstraint; + } + return null; + } + + /// May be null. + public TransformConstraint FindTransformConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + ExposedList transformConstraints = this.transformConstraints; + for (int i = 0, n = transformConstraints.Count; i < n; i++) + { + TransformConstraint transformConstraint = transformConstraints.Items[i]; + if (transformConstraint.data.name == constraintName) return transformConstraint; + } + return null; + } + + /// May be null. + public PathConstraint FindPathConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + ExposedList pathConstraints = this.pathConstraints; + for (int i = 0, n = pathConstraints.Count; i < n; i++) + { + PathConstraint constraint = pathConstraints.Items[i]; + if (constraint.data.name.Equals(constraintName)) return constraint; + } + return null; + } + + public void Update(float delta) + { + time += delta; + } + + /// Returns the axis aligned bounding box (AABB) of the region and mesh attachments for the current pose. + /// The horizontal distance between the skeleton origin and the left side of the AABB. + /// The vertical distance between the skeleton origin and the bottom side of the AABB. + /// The width of the AABB + /// The height of the AABB. + /// Reference to hold a float[]. May be a null reference. This method will assign it a new float[] with the appropriate size as needed. + public void GetBounds(out float x, out float y, out float width, out float height, ref float[] vertexBuffer) + { + float[] temp = vertexBuffer; + temp = temp ?? new float[8]; + var drawOrderItems = this.drawOrder.Items; + float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; + for (int i = 0, n = this.drawOrder.Count; i < n; i++) + { + Slot slot = drawOrderItems[i]; + int verticesLength = 0; + float[] vertices = null; + Attachment attachment = slot.attachment; + var regionAttachment = attachment as RegionAttachment; + if (regionAttachment != null) + { + verticesLength = 8; + vertices = temp; + if (vertices.Length < 8) vertices = temp = new float[8]; + regionAttachment.ComputeWorldVertices(slot.bone, temp, 0); + } + else + { + var meshAttachment = attachment as MeshAttachment; + if (meshAttachment != null) + { + MeshAttachment mesh = meshAttachment; + verticesLength = mesh.WorldVerticesLength; + vertices = temp; + if (vertices.Length < verticesLength) vertices = temp = new float[verticesLength]; + mesh.ComputeWorldVertices(slot, 0, verticesLength, temp, 0); + } + } + + if (vertices != null) + { + for (int ii = 0; ii < verticesLength; ii += 2) + { + float vx = vertices[ii], vy = vertices[ii + 1]; + minX = Math.Min(minX, vx); + minY = Math.Min(minY, vy); + maxX = Math.Max(maxX, vx); + maxY = Math.Max(maxY, vy); + } + } + } + x = minX; + y = minY; + width = maxX - minX; + height = maxY - minY; + vertexBuffer = temp; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/SkeletonBinary.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/SkeletonBinary.cs index 193927e..798594f 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/SkeletonBinary.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/SkeletonBinary.cs @@ -32,50 +32,54 @@ #endif using System; -using System.IO; using System.Collections.Generic; +using System.IO; #if WINDOWS_STOREAPP using System.Threading.Tasks; using Windows.Storage; #endif -namespace Spine3_7_94 { - public class SkeletonBinary { - public const int BONE_ROTATE = 0; - public const int BONE_TRANSLATE = 1; - public const int BONE_SCALE = 2; - public const int BONE_SHEAR = 3; - - public const int SLOT_ATTACHMENT = 0; - public const int SLOT_COLOR = 1; - public const int SLOT_TWO_COLOR = 2; - - public const int PATH_POSITION = 0; - public const int PATH_SPACING = 1; - public const int PATH_MIX = 2; - - public const int CURVE_LINEAR = 0; - public const int CURVE_STEPPED = 1; - public const int CURVE_BEZIER = 2; - - public float Scale { get; set; } - - private AttachmentLoader attachmentLoader; - private byte[] buffer = new byte[32]; - private List linkedMeshes = new List(); - - public SkeletonBinary (params Atlas[] atlasArray) - : this(new AtlasAttachmentLoader(atlasArray)) { - } - - public SkeletonBinary (AttachmentLoader attachmentLoader) { - if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader"); - this.attachmentLoader = attachmentLoader; - Scale = 1; - } - - #if !ISUNITY && WINDOWS_STOREAPP +namespace Spine3_7_94 +{ + public class SkeletonBinary + { + public const int BONE_ROTATE = 0; + public const int BONE_TRANSLATE = 1; + public const int BONE_SCALE = 2; + public const int BONE_SHEAR = 3; + + public const int SLOT_ATTACHMENT = 0; + public const int SLOT_COLOR = 1; + public const int SLOT_TWO_COLOR = 2; + + public const int PATH_POSITION = 0; + public const int PATH_SPACING = 1; + public const int PATH_MIX = 2; + + public const int CURVE_LINEAR = 0; + public const int CURVE_STEPPED = 1; + public const int CURVE_BEZIER = 2; + + public float Scale { get; set; } + + private AttachmentLoader attachmentLoader; + private byte[] buffer = new byte[32]; + private List linkedMeshes = new List(); + + public SkeletonBinary(params Atlas[] atlasArray) + : this(new AtlasAttachmentLoader(atlasArray)) + { + } + + public SkeletonBinary(AttachmentLoader attachmentLoader) + { + if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader"); + this.attachmentLoader = attachmentLoader; + Scale = 1; + } + +#if !ISUNITY && WINDOWS_STOREAPP private async Task ReadFile(string path) { var folder = Windows.ApplicationModel.Package.Current.InstalledLocation; using (var input = new BufferedStream(await folder.GetFileAsync(path).AsTask().ConfigureAwait(false))) { @@ -88,823 +92,930 @@ private async Task ReadFile(string path) { public SkeletonData ReadSkeletonData (String path) { return this.ReadFile(path).Result; } - #else - public SkeletonData ReadSkeletonData (String path) { - #if WINDOWS_PHONE +#else + public SkeletonData ReadSkeletonData(String path) + { +#if WINDOWS_PHONE using (var input = new BufferedStream(Microsoft.Xna.Framework.TitleContainer.OpenStream(path))) { - #else - using (var input = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) { - #endif - SkeletonData skeletonData = ReadSkeletonData(input); - skeletonData.name = Path.GetFileNameWithoutExtension(path); - return skeletonData; - } - } - #endif // WINDOWS_STOREAPP - - public static readonly TransformMode[] TransformModeValues = { - TransformMode.Normal, - TransformMode.OnlyTranslation, - TransformMode.NoRotationOrReflection, - TransformMode.NoScale, - TransformMode.NoScaleOrReflection - }; - - /// Returns the version string of binary skeleton data. - public static string GetVersionString (Stream input) { - if (input == null) throw new ArgumentNullException("input"); - - try { - // Hash. - int byteCount = ReadVarint(input, true); - if (byteCount > 1) input.Position += byteCount - 1; - - // Version. - byteCount = ReadVarint(input, true); - if (byteCount > 1) { - byteCount--; - var buffer = new byte[byteCount]; - ReadFully(input, buffer, 0, byteCount); - return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount); - } - - throw new ArgumentException("Stream does not contain a valid binary Skeleton Data.", "input"); - } catch (Exception e) { - throw new ArgumentException("Stream does not contain a valid binary Skeleton Data.\n" + e, "input"); - } - } - - public SkeletonData ReadSkeletonData (Stream input) { - if (input == null) throw new ArgumentNullException("input"); - float scale = Scale; - - var skeletonData = new SkeletonData(); - skeletonData.hash = ReadString(input); - if (skeletonData.hash.Length == 0) skeletonData.hash = null; - skeletonData.version = ReadString(input); - if (skeletonData.version.Length == 0) skeletonData.version = null; - skeletonData.width = ReadFloat(input); - skeletonData.height = ReadFloat(input); - - bool nonessential = ReadBoolean(input); - - if (nonessential) { - skeletonData.fps = ReadFloat(input); - - skeletonData.imagesPath = ReadString(input); - if (string.IsNullOrEmpty(skeletonData.imagesPath)) skeletonData.imagesPath = null; - - skeletonData.audioPath = ReadString(input); - if (string.IsNullOrEmpty(skeletonData.audioPath)) skeletonData.audioPath = null; - } - - // Bones. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - String name = ReadString(input); - BoneData parent = i == 0 ? null : skeletonData.bones.Items[ReadVarint(input, true)]; - BoneData data = new BoneData(i, name, 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.transformMode = TransformModeValues[ReadVarint(input, true)]; - if (nonessential) ReadInt(input); // Skip bone color. - skeletonData.bones.Add(data); - } - - // Slots. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - String slotName = ReadString(input); - BoneData boneData = skeletonData.bones.Items[ReadVarint(input, true)]; - SlotData slotData = new SlotData(i, slotName, boneData); - int color = ReadInt(input); - slotData.r = ((color & 0xff000000) >> 24) / 255f; - slotData.g = ((color & 0x00ff0000) >> 16) / 255f; - slotData.b = ((color & 0x0000ff00) >> 8) / 255f; - slotData.a = ((color & 0x000000ff)) / 255f; - - int darkColor = ReadInt(input); // 0x00rrggbb - if (darkColor != -1) { - slotData.hasSecondColor = true; - slotData.r2 = ((darkColor & 0x00ff0000) >> 16) / 255f; - slotData.g2 = ((darkColor & 0x0000ff00) >> 8) / 255f; - slotData.b2 = ((darkColor & 0x000000ff)) / 255f; - } - - slotData.attachmentName = ReadString(input); - slotData.blendMode = (BlendMode)ReadVarint(input, true); - skeletonData.slots.Add(slotData); - } - - // IK constraints. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - IkConstraintData data = new IkConstraintData(ReadString(input)); - data.order = ReadVarint(input, true); - for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) - data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]); - data.target = skeletonData.bones.Items[ReadVarint(input, true)]; - data.mix = ReadFloat(input); - data.bendDirection = ReadSByte(input); - data.compress = ReadBoolean(input); - data.stretch = ReadBoolean(input); - data.uniform = ReadBoolean(input); - skeletonData.ikConstraints.Add(data); - } - - // Transform constraints. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - TransformConstraintData data = new TransformConstraintData(ReadString(input)); - data.order = ReadVarint(input, true); - for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) - data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]); - data.target = skeletonData.bones.Items[ReadVarint(input, true)]; - data.local = ReadBoolean(input); - data.relative = ReadBoolean(input); - data.offsetRotation = ReadFloat(input); - data.offsetX = ReadFloat(input) * scale; - data.offsetY = ReadFloat(input) * scale; - data.offsetScaleX = ReadFloat(input); - data.offsetScaleY = ReadFloat(input); - data.offsetShearY = ReadFloat(input); - data.rotateMix = ReadFloat(input); - data.translateMix = ReadFloat(input); - data.scaleMix = ReadFloat(input); - data.shearMix = ReadFloat(input); - skeletonData.transformConstraints.Add(data); - } - - // Path constraints - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - PathConstraintData data = new PathConstraintData(ReadString(input)); - data.order = ReadVarint(input, true); - for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) - data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]); - data.target = skeletonData.slots.Items[ReadVarint(input, true)]; - data.positionMode = (PositionMode)Enum.GetValues(typeof(PositionMode)).GetValue(ReadVarint(input, true)); - data.spacingMode = (SpacingMode)Enum.GetValues(typeof(SpacingMode)).GetValue(ReadVarint(input, true)); - data.rotateMode = (RotateMode)Enum.GetValues(typeof(RotateMode)).GetValue(ReadVarint(input, true)); - 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.rotateMix = ReadFloat(input); - data.translateMix = ReadFloat(input); - skeletonData.pathConstraints.Add(data); - } - - // Default skin. - Skin defaultSkin = ReadSkin(input, skeletonData, "default", nonessential); - if (defaultSkin != null) { - skeletonData.defaultSkin = defaultSkin; - skeletonData.skins.Add(defaultSkin); - } - - // Skins. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) - skeletonData.skins.Add(ReadSkin(input, skeletonData, ReadString(input), nonessential)); - - // Linked meshes. - for (int i = 0, n = linkedMeshes.Count; i < n; i++) { - SkeletonJson.LinkedMesh linkedMesh = linkedMeshes[i]; - Skin skin = linkedMesh.skin == null ? skeletonData.DefaultSkin : skeletonData.FindSkin(linkedMesh.skin); - if (skin == null) throw new Exception("Skin not found: " + linkedMesh.skin); - Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); - if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); - linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; - linkedMesh.mesh.UpdateUVs(); - } - linkedMeshes.Clear(); - - // Events. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - EventData data = new EventData(ReadString(input)); - data.Int = ReadVarint(input, false); - data.Float = ReadFloat(input); - data.String = ReadString(input); - data.AudioPath = ReadString(input); - if (data.AudioPath != null) { - data.Volume = ReadFloat(input); - data.Balance = ReadFloat(input); - } - skeletonData.events.Add(data); - } - - // Animations. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) - ReadAnimation(ReadString(input), input, skeletonData); - - skeletonData.bones.TrimExcess(); - skeletonData.slots.TrimExcess(); - skeletonData.skins.TrimExcess(); - skeletonData.events.TrimExcess(); - skeletonData.animations.TrimExcess(); - skeletonData.ikConstraints.TrimExcess(); - skeletonData.pathConstraints.TrimExcess(); - return skeletonData; - } - - - /// May be null. - private Skin ReadSkin (Stream input, SkeletonData skeletonData, String skinName, bool nonessential) { - int slotCount = ReadVarint(input, true); - if (slotCount == 0) return null; - Skin skin = new Skin(skinName); - 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 = ReadString(input); - Attachment attachment = ReadAttachment(input, skeletonData, skin, slotIndex, name, nonessential); - if (attachment != null) skin.AddAttachment(slotIndex, name, attachment); - } - } - return skin; - } - - private Attachment ReadAttachment (Stream input, SkeletonData skeletonData, Skin skin, int slotIndex, String attachmentName, bool nonessential) { - float scale = Scale; - - String name = ReadString(input); - if (name == null) name = attachmentName; - - AttachmentType type = (AttachmentType)input.ReadByte(); - switch (type) { - case AttachmentType.Region: { - String path = ReadString(input); - float rotation = ReadFloat(input); - float x = ReadFloat(input); - float y = ReadFloat(input); - float scaleX = ReadFloat(input); - float scaleY = ReadFloat(input); - float width = ReadFloat(input); - float height = ReadFloat(input); - int color = ReadInt(input); - - if (path == null) path = name; - RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); - if (region == null) return null; - region.Path = path; - region.x = x * scale; - region.y = y * scale; - region.scaleX = scaleX; - region.scaleY = scaleY; - region.rotation = rotation; - region.width = width * scale; - region.height = height * scale; - region.r = ((color & 0xff000000) >> 24) / 255f; - region.g = ((color & 0x00ff0000) >> 16) / 255f; - region.b = ((color & 0x0000ff00) >> 8) / 255f; - region.a = ((color & 0x000000ff)) / 255f; - region.UpdateOffset(); - return region; - } - case AttachmentType.Boundingbox: { - int vertexCount = ReadVarint(input, true); - Vertices vertices = ReadVertices(input, vertexCount); - if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0; // Avoid unused local warning. - - BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); - if (box == null) return null; - box.worldVerticesLength = vertexCount << 1; - box.vertices = vertices.vertices; - box.bones = vertices.bones; - return box; - } - case AttachmentType.Mesh: { - String path = ReadString(input); - int color = ReadInt(input); - int vertexCount = ReadVarint(input, true); - float[] uvs = ReadFloatArray(input, vertexCount << 1, 1); - int[] triangles = ReadShortArray(input); - Vertices vertices = ReadVertices(input, vertexCount); - int hullLength = ReadVarint(input, true); - int[] edges = null; - float width = 0, height = 0; - if (nonessential) { - edges = ReadShortArray(input); - width = ReadFloat(input); - height = ReadFloat(input); - } - - if (path == null) path = name; - MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); - if (mesh == null) return null; - mesh.Path = path; - mesh.r = ((color & 0xff000000) >> 24) / 255f; - mesh.g = ((color & 0x00ff0000) >> 16) / 255f; - mesh.b = ((color & 0x0000ff00) >> 8) / 255f; - mesh.a = ((color & 0x000000ff)) / 255f; - mesh.bones = vertices.bones; - mesh.vertices = vertices.vertices; - mesh.WorldVerticesLength = vertexCount << 1; - mesh.triangles = triangles; - mesh.regionUVs = uvs; - mesh.UpdateUVs(); - mesh.HullLength = hullLength << 1; - if (nonessential) { - mesh.Edges = edges; - mesh.Width = width * scale; - mesh.Height = height * scale; - } - return mesh; - } - case AttachmentType.Linkedmesh: { - String path = ReadString(input); - int color = ReadInt(input); - String skinName = ReadString(input); - String parent = ReadString(input); - bool inheritDeform = ReadBoolean(input); - float width = 0, height = 0; - if (nonessential) { - width = ReadFloat(input); - height = ReadFloat(input); - } - - if (path == null) path = name; - MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); - if (mesh == null) return null; - mesh.Path = path; - mesh.r = ((color & 0xff000000) >> 24) / 255f; - mesh.g = ((color & 0x00ff0000) >> 16) / 255f; - mesh.b = ((color & 0x0000ff00) >> 8) / 255f; - mesh.a = ((color & 0x000000ff)) / 255f; - mesh.inheritDeform = inheritDeform; - if (nonessential) { - mesh.Width = width * scale; - mesh.Height = height * scale; - } - linkedMeshes.Add(new SkeletonJson.LinkedMesh(mesh, skinName, slotIndex, parent)); - return mesh; - } - case AttachmentType.Path: { - bool closed = ReadBoolean(input); - bool constantSpeed = ReadBoolean(input); - int vertexCount = ReadVarint(input, true); - Vertices vertices = ReadVertices(input, vertexCount); - float[] lengths = new float[vertexCount / 3]; - for (int i = 0, n = lengths.Length; i < n; i++) - lengths[i] = ReadFloat(input) * scale; - if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0; - - PathAttachment path = attachmentLoader.NewPathAttachment(skin, name); - if (path == null) return null; - path.closed = closed; - path.constantSpeed = constantSpeed; - path.worldVerticesLength = vertexCount << 1; - path.vertices = vertices.vertices; - path.bones = vertices.bones; - path.lengths = lengths; - return path; - } - case AttachmentType.Point: { - float rotation = ReadFloat(input); - float x = ReadFloat(input); - float y = ReadFloat(input); - if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0; - - PointAttachment point = attachmentLoader.NewPointAttachment(skin, name); - if (point == null) return null; - point.x = x * scale; - point.y = y * scale; - point.rotation = rotation; - //if (nonessential) point.color = color; - return point; - } - case AttachmentType.Clipping: { - int endSlotIndex = ReadVarint(input, true); - int vertexCount = ReadVarint(input, true); - Vertices vertices = ReadVertices(input, vertexCount); - if (nonessential) ReadInt(input); - - ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name); - if (clip == null) return null; - clip.EndSlot = skeletonData.slots.Items[endSlotIndex]; - clip.worldVerticesLength = vertexCount << 1; - clip.vertices = vertices.vertices; - clip.bones = vertices.bones; - return clip; - } - } - return null; - } - - private Vertices ReadVertices (Stream input, int vertexCount) { - float scale = Scale; - int verticesLength = vertexCount << 1; - Vertices vertices = new Vertices(); - if(!ReadBoolean(input)) { - vertices.vertices = ReadFloatArray(input, verticesLength, scale); - return vertices; - } - var weights = new ExposedList(verticesLength * 3 * 3); - var bonesArray = new ExposedList(verticesLength * 3); - for (int i = 0; i < vertexCount; i++) { - int boneCount = ReadVarint(input, true); - bonesArray.Add(boneCount); - for (int ii = 0; ii < boneCount; ii++) { - bonesArray.Add(ReadVarint(input, true)); - weights.Add(ReadFloat(input) * scale); - weights.Add(ReadFloat(input) * scale); - weights.Add(ReadFloat(input)); - } - } - - vertices.vertices = weights.ToArray(); - vertices.bones = bonesArray.ToArray(); - return vertices; - } - - private float[] ReadFloatArray (Stream input, int n, float scale) { - float[] array = new float[n]; - if (scale == 1) { - for (int i = 0; i < n; i++) - array[i] = ReadFloat(input); - } else { - for (int i = 0; i < n; i++) - array[i] = ReadFloat(input) * scale; - } - return array; - } - - private int[] ReadShortArray (Stream input) { - int n = ReadVarint(input, true); - int[] array = new int[n]; - for (int i = 0; i < n; i++) - array[i] = (input.ReadByte() << 8) | input.ReadByte(); - return array; - } - - private void ReadAnimation (String name, Stream input, SkeletonData skeletonData) { - var timelines = new ExposedList(); - float scale = Scale; - float duration = 0; - - // 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++) { - int timelineType = input.ReadByte(); - int frameCount = ReadVarint(input, true); - switch (timelineType) { - case SLOT_ATTACHMENT: { - AttachmentTimeline timeline = new AttachmentTimeline(frameCount); - timeline.slotIndex = slotIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) - timeline.SetFrame(frameIndex, ReadFloat(input), ReadString(input)); - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[frameCount - 1]); - break; - } - case SLOT_COLOR: { - ColorTimeline timeline = new ColorTimeline(frameCount); - timeline.slotIndex = slotIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - float time = ReadFloat(input); - int color = ReadInt(input); - float r = ((color & 0xff000000) >> 24) / 255f; - float g = ((color & 0x00ff0000) >> 16) / 255f; - float b = ((color & 0x0000ff00) >> 8) / 255f; - float a = ((color & 0x000000ff)) / 255f; - timeline.SetFrame(frameIndex, time, r, g, b, a); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * ColorTimeline.ENTRIES]); - break; - } - case SLOT_TWO_COLOR: { - TwoColorTimeline timeline = new TwoColorTimeline(frameCount); - timeline.slotIndex = slotIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - float time = ReadFloat(input); - int color = ReadInt(input); - float r = ((color & 0xff000000) >> 24) / 255f; - float g = ((color & 0x00ff0000) >> 16) / 255f; - float b = ((color & 0x0000ff00) >> 8) / 255f; - float a = ((color & 0x000000ff)) / 255f; - int color2 = ReadInt(input); // 0x00rrggbb - float r2 = ((color2 & 0x00ff0000) >> 16) / 255f; - float g2 = ((color2 & 0x0000ff00) >> 8) / 255f; - float b2 = ((color2 & 0x000000ff)) / 255f; - - timeline.SetFrame(frameIndex, time, r, g, b, a, r2, g2, b2); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TwoColorTimeline.ENTRIES]); - break; - } - } - } - } - - // 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++) { - int timelineType = input.ReadByte(); - int frameCount = ReadVarint(input, true); - switch (timelineType) { - case BONE_ROTATE: { - RotateTimeline timeline = new RotateTimeline(frameCount); - timeline.boneIndex = boneIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input)); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(frameCount - 1) * RotateTimeline.ENTRIES]); - break; - } - case BONE_TRANSLATE: - case BONE_SCALE: - case BONE_SHEAR: { - TranslateTimeline timeline; - float timelineScale = 1; - if (timelineType == BONE_SCALE) - timeline = new ScaleTimeline(frameCount); - else if (timelineType == BONE_SHEAR) - timeline = new ShearTimeline(frameCount); - else { - timeline = new TranslateTimeline(frameCount); - timelineScale = scale; - } - timeline.boneIndex = boneIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input) * timelineScale, ReadFloat(input) - * timelineScale); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TranslateTimeline.ENTRIES]); - break; - } - } - } - } - - // IK timelines. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - int index = ReadVarint(input, true); - int frameCount = ReadVarint(input, true); - IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount) { - ikConstraintIndex = index - }; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadSByte(input), ReadBoolean(input), ReadBoolean(input)); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(frameCount - 1) * IkConstraintTimeline.ENTRIES]); - } - - // Transform constraint timelines. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - int index = ReadVarint(input, true); - int frameCount = ReadVarint(input, true); - TransformConstraintTimeline timeline = new TransformConstraintTimeline(frameCount); - timeline.transformConstraintIndex = index; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input)); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TransformConstraintTimeline.ENTRIES]); - } - - // Path constraint timelines. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - int index = ReadVarint(input, true); - PathConstraintData data = skeletonData.pathConstraints.Items[index]; - for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) { - int timelineType = ReadSByte(input); - int frameCount = ReadVarint(input, true); - switch(timelineType) { - case PATH_POSITION: - case PATH_SPACING: { - PathConstraintPositionTimeline timeline; - float timelineScale = 1; - if (timelineType == PATH_SPACING) { - timeline = new PathConstraintSpacingTimeline(frameCount); - if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale; - } else { - timeline = new PathConstraintPositionTimeline(frameCount); - if (data.positionMode == PositionMode.Fixed) timelineScale = scale; - } - timeline.pathConstraintIndex = index; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input) * timelineScale); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintPositionTimeline.ENTRIES]); - break; - } - case PATH_MIX: { - PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(frameCount); - timeline.pathConstraintIndex = index; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input)); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintMixTimeline.ENTRIES]); - break; - } - } - } - } - - // Deform timelines. - for (int i = 0, n = ReadVarint(input, true); i < n; i++) { - Skin skin = skeletonData.skins.Items[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++) { - VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, ReadString(input)); - bool weighted = attachment.bones != null; - float[] vertices = attachment.vertices; - int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length; - - int frameCount = ReadVarint(input, true); - DeformTimeline timeline = new DeformTimeline(frameCount); - timeline.slotIndex = slotIndex; - timeline.attachment = attachment; - - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - float time = ReadFloat(input); - float[] deform; - int end = ReadVarint(input, true); - if (end == 0) - deform = weighted ? new float[deformLength] : vertices; - else { - deform = new float[deformLength]; - int start = ReadVarint(input, true); - end += start; - if (scale == 1) { - for (int v = start; v < end; v++) - deform[v] = ReadFloat(input); - } else { - for (int v = start; v < end; v++) - deform[v] = ReadFloat(input) * scale; - } - if (!weighted) { - for (int v = 0, vn = deform.Length; v < vn; v++) - deform[v] += vertices[v]; - } - } - - timeline.SetFrame(frameIndex, time, deform); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[frameCount - 1]); - } - } - } - - // Draw order timeline. - int drawOrderCount = ReadVarint(input, true); - if (drawOrderCount > 0) { - DrawOrderTimeline timeline = new DrawOrderTimeline(drawOrderCount); - int slotCount = skeletonData.slots.Count; - for (int i = 0; i < drawOrderCount; i++) { - float time = ReadFloat(input); - int offsetCount = ReadVarint(input, true); - int[] drawOrder = new int[slotCount]; - for (int ii = slotCount - 1; ii >= 0; ii--) - drawOrder[ii] = -1; - int[] unchanged = new int[slotCount - offsetCount]; - int originalIndex = 0, unchangedIndex = 0; - for (int ii = 0; ii < offsetCount; ii++) { - int slotIndex = ReadVarint(input, true); - // Collect unchanged items. - while (originalIndex != slotIndex) - unchanged[unchangedIndex++] = originalIndex++; - // Set changed items. - drawOrder[originalIndex + ReadVarint(input, true)] = originalIndex++; - } - // Collect remaining unchanged items. - while (originalIndex < slotCount) - unchanged[unchangedIndex++] = originalIndex++; - // Fill in unchanged items. - for (int ii = slotCount - 1; ii >= 0; ii--) - if (drawOrder[ii] == -1) drawOrder[ii] = unchanged[--unchangedIndex]; - timeline.SetFrame(i, time, drawOrder); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[drawOrderCount - 1]); - } - - // Event timeline. - int eventCount = ReadVarint(input, true); - if (eventCount > 0) { - EventTimeline timeline = new EventTimeline(eventCount); - for (int i = 0; i < eventCount; i++) { - float time = ReadFloat(input); - EventData eventData = skeletonData.events.Items[ReadVarint(input, true)]; - Event e = new Event(time, eventData) { - Int = ReadVarint(input, false), - Float = ReadFloat(input), - String = ReadBoolean(input) ? ReadString(input) : eventData.String - }; - if (e.data.AudioPath != null) { - e.volume = ReadFloat(input); - e.balance = ReadFloat(input); - } - timeline.SetFrame(i, e); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[eventCount - 1]); - } - - timelines.TrimExcess(); - skeletonData.animations.Add(new Animation(name, timelines, duration)); - } - - private void ReadCurve (Stream input, int frameIndex, CurveTimeline timeline) { - switch (input.ReadByte()) { - case CURVE_STEPPED: - timeline.SetStepped(frameIndex); - break; - case CURVE_BEZIER: - timeline.SetCurve(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input)); - break; - } - } - - private static sbyte ReadSByte (Stream input) { - int value = input.ReadByte(); - if (value == -1) throw new EndOfStreamException(); - return (sbyte)value; - } - - private static bool ReadBoolean (Stream input) { - return input.ReadByte() != 0; - } - - private float ReadFloat (Stream input) { - buffer[3] = (byte)input.ReadByte(); - buffer[2] = (byte)input.ReadByte(); - buffer[1] = (byte)input.ReadByte(); - buffer[0] = (byte)input.ReadByte(); - return BitConverter.ToSingle(buffer, 0); - } - - private static int ReadInt (Stream input) { - return (input.ReadByte() << 24) + (input.ReadByte() << 16) + (input.ReadByte() << 8) + input.ReadByte(); - } - - private static int ReadVarint (Stream input, bool optimizePositive) { - int b = input.ReadByte(); - int result = b & 0x7F; - if ((b & 0x80) != 0) { - b = input.ReadByte(); - result |= (b & 0x7F) << 7; - if ((b & 0x80) != 0) { - b = input.ReadByte(); - result |= (b & 0x7F) << 14; - if ((b & 0x80) != 0) { - b = input.ReadByte(); - result |= (b & 0x7F) << 21; - if ((b & 0x80) != 0) result |= (input.ReadByte() & 0x7F) << 28; - } - } - } - return optimizePositive ? result : ((result >> 1) ^ -(result & 1)); - } - - private string ReadString (Stream input) { - int byteCount = ReadVarint(input, true); - switch (byteCount) { - case 0: - return null; - case 1: - return ""; - } - byteCount--; - byte[] buffer = this.buffer; - if (buffer.Length < byteCount) buffer = new byte[byteCount]; - ReadFully(input, buffer, 0, byteCount); - return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount); - } - - private static void ReadFully (Stream input, byte[] buffer, int offset, int length) { - while (length > 0) { - int count = input.Read(buffer, offset, length); - if (count <= 0) throw new EndOfStreamException(); - offset += count; - length -= count; - } - } - - internal class Vertices { - public int[] bones; - public float[] vertices; - } - } +#else + using (var input = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) + { +#endif + SkeletonData skeletonData = ReadSkeletonData(input); + skeletonData.name = Path.GetFileNameWithoutExtension(path); + return skeletonData; + } + } +#endif // WINDOWS_STOREAPP + + public static readonly TransformMode[] TransformModeValues = { + TransformMode.Normal, + TransformMode.OnlyTranslation, + TransformMode.NoRotationOrReflection, + TransformMode.NoScale, + TransformMode.NoScaleOrReflection + }; + + /// Returns the version string of binary skeleton data. + public static string GetVersionString(Stream input) + { + if (input == null) throw new ArgumentNullException("input"); + + try + { + // Hash. + int byteCount = ReadVarint(input, true); + if (byteCount > 1) input.Position += byteCount - 1; + + // Version. + byteCount = ReadVarint(input, true); + if (byteCount > 1) + { + byteCount--; + var buffer = new byte[byteCount]; + ReadFully(input, buffer, 0, byteCount); + return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount); + } + + throw new ArgumentException("Stream does not contain a valid binary Skeleton Data.", "input"); + } + catch (Exception e) + { + throw new ArgumentException("Stream does not contain a valid binary Skeleton Data.\n" + e, "input"); + } + } + + public SkeletonData ReadSkeletonData(Stream input) + { + if (input == null) throw new ArgumentNullException("input"); + float scale = Scale; + + var skeletonData = new SkeletonData(); + skeletonData.hash = ReadString(input); + if (skeletonData.hash.Length == 0) skeletonData.hash = null; + skeletonData.version = ReadString(input); + if (skeletonData.version.Length == 0) skeletonData.version = null; + skeletonData.width = ReadFloat(input); + skeletonData.height = ReadFloat(input); + + bool nonessential = ReadBoolean(input); + + if (nonessential) + { + skeletonData.fps = ReadFloat(input); + + skeletonData.imagesPath = ReadString(input); + if (string.IsNullOrEmpty(skeletonData.imagesPath)) skeletonData.imagesPath = null; + + skeletonData.audioPath = ReadString(input); + if (string.IsNullOrEmpty(skeletonData.audioPath)) skeletonData.audioPath = null; + } + + // Bones. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + String name = ReadString(input); + BoneData parent = i == 0 ? null : skeletonData.bones.Items[ReadVarint(input, true)]; + BoneData data = new BoneData(i, name, 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.transformMode = TransformModeValues[ReadVarint(input, true)]; + if (nonessential) ReadInt(input); // Skip bone color. + skeletonData.bones.Add(data); + } + + // Slots. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + String slotName = ReadString(input); + BoneData boneData = skeletonData.bones.Items[ReadVarint(input, true)]; + SlotData slotData = new SlotData(i, slotName, boneData); + int color = ReadInt(input); + slotData.r = ((color & 0xff000000) >> 24) / 255f; + slotData.g = ((color & 0x00ff0000) >> 16) / 255f; + slotData.b = ((color & 0x0000ff00) >> 8) / 255f; + slotData.a = ((color & 0x000000ff)) / 255f; + + int darkColor = ReadInt(input); // 0x00rrggbb + if (darkColor != -1) + { + slotData.hasSecondColor = true; + slotData.r2 = ((darkColor & 0x00ff0000) >> 16) / 255f; + slotData.g2 = ((darkColor & 0x0000ff00) >> 8) / 255f; + slotData.b2 = ((darkColor & 0x000000ff)) / 255f; + } + + slotData.attachmentName = ReadString(input); + slotData.blendMode = (BlendMode)ReadVarint(input, true); + skeletonData.slots.Add(slotData); + } + + // IK constraints. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + IkConstraintData data = new IkConstraintData(ReadString(input)); + data.order = ReadVarint(input, true); + for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) + data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]); + data.target = skeletonData.bones.Items[ReadVarint(input, true)]; + data.mix = ReadFloat(input); + data.bendDirection = ReadSByte(input); + data.compress = ReadBoolean(input); + data.stretch = ReadBoolean(input); + data.uniform = ReadBoolean(input); + skeletonData.ikConstraints.Add(data); + } + + // Transform constraints. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + TransformConstraintData data = new TransformConstraintData(ReadString(input)); + data.order = ReadVarint(input, true); + for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) + data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]); + data.target = skeletonData.bones.Items[ReadVarint(input, true)]; + data.local = ReadBoolean(input); + data.relative = ReadBoolean(input); + data.offsetRotation = ReadFloat(input); + data.offsetX = ReadFloat(input) * scale; + data.offsetY = ReadFloat(input) * scale; + data.offsetScaleX = ReadFloat(input); + data.offsetScaleY = ReadFloat(input); + data.offsetShearY = ReadFloat(input); + data.rotateMix = ReadFloat(input); + data.translateMix = ReadFloat(input); + data.scaleMix = ReadFloat(input); + data.shearMix = ReadFloat(input); + skeletonData.transformConstraints.Add(data); + } + + // Path constraints + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + PathConstraintData data = new PathConstraintData(ReadString(input)); + data.order = ReadVarint(input, true); + for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) + data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]); + data.target = skeletonData.slots.Items[ReadVarint(input, true)]; + data.positionMode = (PositionMode)Enum.GetValues(typeof(PositionMode)).GetValue(ReadVarint(input, true)); + data.spacingMode = (SpacingMode)Enum.GetValues(typeof(SpacingMode)).GetValue(ReadVarint(input, true)); + data.rotateMode = (RotateMode)Enum.GetValues(typeof(RotateMode)).GetValue(ReadVarint(input, true)); + 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.rotateMix = ReadFloat(input); + data.translateMix = ReadFloat(input); + skeletonData.pathConstraints.Add(data); + } + + // Default skin. + Skin defaultSkin = ReadSkin(input, skeletonData, "default", nonessential); + if (defaultSkin != null) + { + skeletonData.defaultSkin = defaultSkin; + skeletonData.skins.Add(defaultSkin); + } + + // Skins. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + skeletonData.skins.Add(ReadSkin(input, skeletonData, ReadString(input), nonessential)); + + // Linked meshes. + for (int i = 0, n = linkedMeshes.Count; i < n; i++) + { + SkeletonJson.LinkedMesh linkedMesh = linkedMeshes[i]; + Skin skin = linkedMesh.skin == null ? skeletonData.DefaultSkin : skeletonData.FindSkin(linkedMesh.skin); + if (skin == null) throw new Exception("Skin not found: " + linkedMesh.skin); + Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); + if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); + linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; + linkedMesh.mesh.UpdateUVs(); + } + linkedMeshes.Clear(); + + // Events. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + EventData data = new EventData(ReadString(input)); + data.Int = ReadVarint(input, false); + data.Float = ReadFloat(input); + data.String = ReadString(input); + data.AudioPath = ReadString(input); + if (data.AudioPath != null) + { + data.Volume = ReadFloat(input); + data.Balance = ReadFloat(input); + } + skeletonData.events.Add(data); + } + + // Animations. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + ReadAnimation(ReadString(input), input, skeletonData); + + skeletonData.bones.TrimExcess(); + skeletonData.slots.TrimExcess(); + skeletonData.skins.TrimExcess(); + skeletonData.events.TrimExcess(); + skeletonData.animations.TrimExcess(); + skeletonData.ikConstraints.TrimExcess(); + skeletonData.pathConstraints.TrimExcess(); + return skeletonData; + } + + + /// May be null. + private Skin ReadSkin(Stream input, SkeletonData skeletonData, String skinName, bool nonessential) + { + int slotCount = ReadVarint(input, true); + if (slotCount == 0) return null; + Skin skin = new Skin(skinName); + 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 = ReadString(input); + Attachment attachment = ReadAttachment(input, skeletonData, skin, slotIndex, name, nonessential); + if (attachment != null) skin.AddAttachment(slotIndex, name, attachment); + } + } + return skin; + } + + private Attachment ReadAttachment(Stream input, SkeletonData skeletonData, Skin skin, int slotIndex, String attachmentName, bool nonessential) + { + float scale = Scale; + + String name = ReadString(input); + if (name == null) name = attachmentName; + + AttachmentType type = (AttachmentType)input.ReadByte(); + switch (type) + { + case AttachmentType.Region: + { + String path = ReadString(input); + float rotation = ReadFloat(input); + float x = ReadFloat(input); + float y = ReadFloat(input); + float scaleX = ReadFloat(input); + float scaleY = ReadFloat(input); + float width = ReadFloat(input); + float height = ReadFloat(input); + int color = ReadInt(input); + + if (path == null) path = name; + RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); + if (region == null) return null; + region.Path = path; + region.x = x * scale; + region.y = y * scale; + region.scaleX = scaleX; + region.scaleY = scaleY; + region.rotation = rotation; + region.width = width * scale; + region.height = height * scale; + region.r = ((color & 0xff000000) >> 24) / 255f; + region.g = ((color & 0x00ff0000) >> 16) / 255f; + region.b = ((color & 0x0000ff00) >> 8) / 255f; + region.a = ((color & 0x000000ff)) / 255f; + region.UpdateOffset(); + return region; + } + case AttachmentType.Boundingbox: + { + int vertexCount = ReadVarint(input, true); + Vertices vertices = ReadVertices(input, vertexCount); + if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0; // Avoid unused local warning. + + BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); + if (box == null) return null; + box.worldVerticesLength = vertexCount << 1; + box.vertices = vertices.vertices; + box.bones = vertices.bones; + return box; + } + case AttachmentType.Mesh: + { + String path = ReadString(input); + int color = ReadInt(input); + int vertexCount = ReadVarint(input, true); + float[] uvs = ReadFloatArray(input, vertexCount << 1, 1); + int[] triangles = ReadShortArray(input); + Vertices vertices = ReadVertices(input, vertexCount); + int hullLength = ReadVarint(input, true); + int[] edges = null; + float width = 0, height = 0; + if (nonessential) + { + edges = ReadShortArray(input); + width = ReadFloat(input); + height = ReadFloat(input); + } + + if (path == null) path = name; + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); + if (mesh == null) return null; + mesh.Path = path; + mesh.r = ((color & 0xff000000) >> 24) / 255f; + mesh.g = ((color & 0x00ff0000) >> 16) / 255f; + mesh.b = ((color & 0x0000ff00) >> 8) / 255f; + mesh.a = ((color & 0x000000ff)) / 255f; + mesh.bones = vertices.bones; + mesh.vertices = vertices.vertices; + mesh.WorldVerticesLength = vertexCount << 1; + mesh.triangles = triangles; + mesh.regionUVs = uvs; + mesh.UpdateUVs(); + mesh.HullLength = hullLength << 1; + if (nonessential) + { + mesh.Edges = edges; + mesh.Width = width * scale; + mesh.Height = height * scale; + } + return mesh; + } + case AttachmentType.Linkedmesh: + { + String path = ReadString(input); + int color = ReadInt(input); + String skinName = ReadString(input); + String parent = ReadString(input); + bool inheritDeform = ReadBoolean(input); + float width = 0, height = 0; + if (nonessential) + { + width = ReadFloat(input); + height = ReadFloat(input); + } + + if (path == null) path = name; + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); + if (mesh == null) return null; + mesh.Path = path; + mesh.r = ((color & 0xff000000) >> 24) / 255f; + mesh.g = ((color & 0x00ff0000) >> 16) / 255f; + mesh.b = ((color & 0x0000ff00) >> 8) / 255f; + mesh.a = ((color & 0x000000ff)) / 255f; + mesh.inheritDeform = inheritDeform; + if (nonessential) + { + mesh.Width = width * scale; + mesh.Height = height * scale; + } + linkedMeshes.Add(new SkeletonJson.LinkedMesh(mesh, skinName, slotIndex, parent)); + return mesh; + } + case AttachmentType.Path: + { + bool closed = ReadBoolean(input); + bool constantSpeed = ReadBoolean(input); + int vertexCount = ReadVarint(input, true); + Vertices vertices = ReadVertices(input, vertexCount); + float[] lengths = new float[vertexCount / 3]; + for (int i = 0, n = lengths.Length; i < n; i++) + lengths[i] = ReadFloat(input) * scale; + if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0; + + PathAttachment path = attachmentLoader.NewPathAttachment(skin, name); + if (path == null) return null; + path.closed = closed; + path.constantSpeed = constantSpeed; + path.worldVerticesLength = vertexCount << 1; + path.vertices = vertices.vertices; + path.bones = vertices.bones; + path.lengths = lengths; + return path; + } + case AttachmentType.Point: + { + float rotation = ReadFloat(input); + float x = ReadFloat(input); + float y = ReadFloat(input); + if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0; + + PointAttachment point = attachmentLoader.NewPointAttachment(skin, name); + if (point == null) return null; + point.x = x * scale; + point.y = y * scale; + point.rotation = rotation; + //if (nonessential) point.color = color; + return point; + } + case AttachmentType.Clipping: + { + int endSlotIndex = ReadVarint(input, true); + int vertexCount = ReadVarint(input, true); + Vertices vertices = ReadVertices(input, vertexCount); + if (nonessential) ReadInt(input); + + ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name); + if (clip == null) return null; + clip.EndSlot = skeletonData.slots.Items[endSlotIndex]; + clip.worldVerticesLength = vertexCount << 1; + clip.vertices = vertices.vertices; + clip.bones = vertices.bones; + return clip; + } + } + return null; + } + + private Vertices ReadVertices(Stream input, int vertexCount) + { + float scale = Scale; + int verticesLength = vertexCount << 1; + Vertices vertices = new Vertices(); + if (!ReadBoolean(input)) + { + vertices.vertices = ReadFloatArray(input, verticesLength, scale); + return vertices; + } + var weights = new ExposedList(verticesLength * 3 * 3); + var bonesArray = new ExposedList(verticesLength * 3); + for (int i = 0; i < vertexCount; i++) + { + int boneCount = ReadVarint(input, true); + bonesArray.Add(boneCount); + for (int ii = 0; ii < boneCount; ii++) + { + bonesArray.Add(ReadVarint(input, true)); + weights.Add(ReadFloat(input) * scale); + weights.Add(ReadFloat(input) * scale); + weights.Add(ReadFloat(input)); + } + } + + vertices.vertices = weights.ToArray(); + vertices.bones = bonesArray.ToArray(); + return vertices; + } + + private float[] ReadFloatArray(Stream input, int n, float scale) + { + float[] array = new float[n]; + if (scale == 1) + { + for (int i = 0; i < n; i++) + array[i] = ReadFloat(input); + } + else + { + for (int i = 0; i < n; i++) + array[i] = ReadFloat(input) * scale; + } + return array; + } + + private int[] ReadShortArray(Stream input) + { + int n = ReadVarint(input, true); + int[] array = new int[n]; + for (int i = 0; i < n; i++) + array[i] = (input.ReadByte() << 8) | input.ReadByte(); + return array; + } + + private void ReadAnimation(String name, Stream input, SkeletonData skeletonData) + { + var timelines = new ExposedList(); + float scale = Scale; + float duration = 0; + + // 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++) + { + int timelineType = input.ReadByte(); + int frameCount = ReadVarint(input, true); + switch (timelineType) + { + case SLOT_ATTACHMENT: + { + AttachmentTimeline timeline = new AttachmentTimeline(frameCount); + timeline.slotIndex = slotIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + timeline.SetFrame(frameIndex, ReadFloat(input), ReadString(input)); + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[frameCount - 1]); + break; + } + case SLOT_COLOR: + { + ColorTimeline timeline = new ColorTimeline(frameCount); + timeline.slotIndex = slotIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + float time = ReadFloat(input); + int color = ReadInt(input); + float r = ((color & 0xff000000) >> 24) / 255f; + float g = ((color & 0x00ff0000) >> 16) / 255f; + float b = ((color & 0x0000ff00) >> 8) / 255f; + float a = ((color & 0x000000ff)) / 255f; + timeline.SetFrame(frameIndex, time, r, g, b, a); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * ColorTimeline.ENTRIES]); + break; + } + case SLOT_TWO_COLOR: + { + TwoColorTimeline timeline = new TwoColorTimeline(frameCount); + timeline.slotIndex = slotIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + float time = ReadFloat(input); + int color = ReadInt(input); + float r = ((color & 0xff000000) >> 24) / 255f; + float g = ((color & 0x00ff0000) >> 16) / 255f; + float b = ((color & 0x0000ff00) >> 8) / 255f; + float a = ((color & 0x000000ff)) / 255f; + int color2 = ReadInt(input); // 0x00rrggbb + float r2 = ((color2 & 0x00ff0000) >> 16) / 255f; + float g2 = ((color2 & 0x0000ff00) >> 8) / 255f; + float b2 = ((color2 & 0x000000ff)) / 255f; + + timeline.SetFrame(frameIndex, time, r, g, b, a, r2, g2, b2); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TwoColorTimeline.ENTRIES]); + break; + } + } + } + } + + // 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++) + { + int timelineType = input.ReadByte(); + int frameCount = ReadVarint(input, true); + switch (timelineType) + { + case BONE_ROTATE: + { + RotateTimeline timeline = new RotateTimeline(frameCount); + timeline.boneIndex = boneIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input)); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(frameCount - 1) * RotateTimeline.ENTRIES]); + break; + } + case BONE_TRANSLATE: + case BONE_SCALE: + case BONE_SHEAR: + { + TranslateTimeline timeline; + float timelineScale = 1; + if (timelineType == BONE_SCALE) + timeline = new ScaleTimeline(frameCount); + else if (timelineType == BONE_SHEAR) + timeline = new ShearTimeline(frameCount); + else + { + timeline = new TranslateTimeline(frameCount); + timelineScale = scale; + } + timeline.boneIndex = boneIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input) * timelineScale, ReadFloat(input) + * timelineScale); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TranslateTimeline.ENTRIES]); + break; + } + } + } + } + + // IK timelines. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + int index = ReadVarint(input, true); + int frameCount = ReadVarint(input, true); + IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount) + { + ikConstraintIndex = index + }; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadSByte(input), ReadBoolean(input), ReadBoolean(input)); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(frameCount - 1) * IkConstraintTimeline.ENTRIES]); + } + + // Transform constraint timelines. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + int index = ReadVarint(input, true); + int frameCount = ReadVarint(input, true); + TransformConstraintTimeline timeline = new TransformConstraintTimeline(frameCount); + timeline.transformConstraintIndex = index; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input)); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TransformConstraintTimeline.ENTRIES]); + } + + // Path constraint timelines. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + int index = ReadVarint(input, true); + PathConstraintData data = skeletonData.pathConstraints.Items[index]; + for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) + { + int timelineType = ReadSByte(input); + int frameCount = ReadVarint(input, true); + switch (timelineType) + { + case PATH_POSITION: + case PATH_SPACING: + { + PathConstraintPositionTimeline timeline; + float timelineScale = 1; + if (timelineType == PATH_SPACING) + { + timeline = new PathConstraintSpacingTimeline(frameCount); + if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale; + } + else + { + timeline = new PathConstraintPositionTimeline(frameCount); + if (data.positionMode == PositionMode.Fixed) timelineScale = scale; + } + timeline.pathConstraintIndex = index; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input) * timelineScale); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintPositionTimeline.ENTRIES]); + break; + } + case PATH_MIX: + { + PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(frameCount); + timeline.pathConstraintIndex = index; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input)); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintMixTimeline.ENTRIES]); + break; + } + } + } + } + + // Deform timelines. + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + { + Skin skin = skeletonData.skins.Items[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++) + { + VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, ReadString(input)); + bool weighted = attachment.bones != null; + float[] vertices = attachment.vertices; + int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length; + + int frameCount = ReadVarint(input, true); + DeformTimeline timeline = new DeformTimeline(frameCount); + timeline.slotIndex = slotIndex; + timeline.attachment = attachment; + + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + float time = ReadFloat(input); + float[] deform; + int end = ReadVarint(input, true); + if (end == 0) + deform = weighted ? new float[deformLength] : vertices; + else + { + deform = new float[deformLength]; + int start = ReadVarint(input, true); + end += start; + if (scale == 1) + { + for (int v = start; v < end; v++) + deform[v] = ReadFloat(input); + } + else + { + for (int v = start; v < end; v++) + deform[v] = ReadFloat(input) * scale; + } + if (!weighted) + { + for (int v = 0, vn = deform.Length; v < vn; v++) + deform[v] += vertices[v]; + } + } + + timeline.SetFrame(frameIndex, time, deform); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[frameCount - 1]); + } + } + } + + // Draw order timeline. + int drawOrderCount = ReadVarint(input, true); + if (drawOrderCount > 0) + { + DrawOrderTimeline timeline = new DrawOrderTimeline(drawOrderCount); + int slotCount = skeletonData.slots.Count; + for (int i = 0; i < drawOrderCount; i++) + { + float time = ReadFloat(input); + int offsetCount = ReadVarint(input, true); + int[] drawOrder = new int[slotCount]; + for (int ii = slotCount - 1; ii >= 0; ii--) + drawOrder[ii] = -1; + int[] unchanged = new int[slotCount - offsetCount]; + int originalIndex = 0, unchangedIndex = 0; + for (int ii = 0; ii < offsetCount; ii++) + { + int slotIndex = ReadVarint(input, true); + // Collect unchanged items. + while (originalIndex != slotIndex) + unchanged[unchangedIndex++] = originalIndex++; + // Set changed items. + drawOrder[originalIndex + ReadVarint(input, true)] = originalIndex++; + } + // Collect remaining unchanged items. + while (originalIndex < slotCount) + unchanged[unchangedIndex++] = originalIndex++; + // Fill in unchanged items. + for (int ii = slotCount - 1; ii >= 0; ii--) + if (drawOrder[ii] == -1) drawOrder[ii] = unchanged[--unchangedIndex]; + timeline.SetFrame(i, time, drawOrder); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[drawOrderCount - 1]); + } + + // Event timeline. + int eventCount = ReadVarint(input, true); + if (eventCount > 0) + { + EventTimeline timeline = new EventTimeline(eventCount); + for (int i = 0; i < eventCount; i++) + { + float time = ReadFloat(input); + EventData eventData = skeletonData.events.Items[ReadVarint(input, true)]; + Event e = new Event(time, eventData) + { + Int = ReadVarint(input, false), + Float = ReadFloat(input), + String = ReadBoolean(input) ? ReadString(input) : eventData.String + }; + if (e.data.AudioPath != null) + { + e.volume = ReadFloat(input); + e.balance = ReadFloat(input); + } + timeline.SetFrame(i, e); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[eventCount - 1]); + } + + timelines.TrimExcess(); + skeletonData.animations.Add(new Animation(name, timelines, duration)); + } + + private void ReadCurve(Stream input, int frameIndex, CurveTimeline timeline) + { + switch (input.ReadByte()) + { + case CURVE_STEPPED: + timeline.SetStepped(frameIndex); + break; + case CURVE_BEZIER: + timeline.SetCurve(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input)); + break; + } + } + + private static sbyte ReadSByte(Stream input) + { + int value = input.ReadByte(); + if (value == -1) throw new EndOfStreamException(); + return (sbyte)value; + } + + private static bool ReadBoolean(Stream input) + { + return input.ReadByte() != 0; + } + + private float ReadFloat(Stream input) + { + buffer[3] = (byte)input.ReadByte(); + buffer[2] = (byte)input.ReadByte(); + buffer[1] = (byte)input.ReadByte(); + buffer[0] = (byte)input.ReadByte(); + return BitConverter.ToSingle(buffer, 0); + } + + private static int ReadInt(Stream input) + { + return (input.ReadByte() << 24) + (input.ReadByte() << 16) + (input.ReadByte() << 8) + input.ReadByte(); + } + + private static int ReadVarint(Stream input, bool optimizePositive) + { + int b = input.ReadByte(); + int result = b & 0x7F; + if ((b & 0x80) != 0) + { + b = input.ReadByte(); + result |= (b & 0x7F) << 7; + if ((b & 0x80) != 0) + { + b = input.ReadByte(); + result |= (b & 0x7F) << 14; + if ((b & 0x80) != 0) + { + b = input.ReadByte(); + result |= (b & 0x7F) << 21; + if ((b & 0x80) != 0) result |= (input.ReadByte() & 0x7F) << 28; + } + } + } + return optimizePositive ? result : ((result >> 1) ^ -(result & 1)); + } + + private string ReadString(Stream input) + { + int byteCount = ReadVarint(input, true); + switch (byteCount) + { + case 0: + return null; + case 1: + return ""; + } + byteCount--; + byte[] buffer = this.buffer; + if (buffer.Length < byteCount) buffer = new byte[byteCount]; + ReadFully(input, buffer, 0, byteCount); + return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount); + } + + private static void ReadFully(Stream input, byte[] buffer, int offset, int length) + { + while (length > 0) + { + int count = input.Read(buffer, offset, length); + if (count <= 0) throw new EndOfStreamException(); + offset += count; + length -= count; + } + } + + internal class Vertices + { + public int[] bones; + public float[] vertices; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/SkeletonBounds.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/SkeletonBounds.cs index 02abb03..a5bebcd 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/SkeletonBounds.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/SkeletonBounds.cs @@ -29,205 +29,232 @@ using System; -namespace Spine3_7_94 { - - /// - /// 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. - /// - public class SkeletonBounds { - private ExposedList polygonPool = new ExposedList(); - private float minX, minY, maxX, maxY; - - public ExposedList BoundingBoxes { get; private set; } - public ExposedList Polygons { get; private set; } - public float MinX { get { return minX; } set { minX = value; } } - public float MinY { get { return minY; } set { minY = value; } } - public float MaxX { get { return maxX; } set { maxX = value; } } - public float MaxY { get { return maxY; } set { maxY = value; } } - public float Width { get { return maxX - minX; } } - public float Height { get { return maxY - minY; } } - - public SkeletonBounds () { - BoundingBoxes = new ExposedList(); - Polygons = new ExposedList(); - } - - /// - /// Clears any previous polygons, finds all visible bounding box attachments, - /// and computes the world vertices for each bounding box's polygon. - /// The skeleton. - /// - /// If true, the axis aligned bounding box containing all the polygons is computed. - /// If false, the SkeletonBounds AABB methods will always return true. - /// - public void Update (Skeleton skeleton, bool updateAabb) { - ExposedList boundingBoxes = BoundingBoxes; - ExposedList polygons = Polygons; - ExposedList slots = skeleton.slots; - int slotCount = slots.Count; - - boundingBoxes.Clear(); - for (int i = 0, n = polygons.Count; i < n; i++) - polygonPool.Add(polygons.Items[i]); - polygons.Clear(); - - for (int i = 0; i < slotCount; i++) { - Slot slot = slots.Items[i]; - BoundingBoxAttachment boundingBox = slot.attachment as BoundingBoxAttachment; - if (boundingBox == null) continue; - boundingBoxes.Add(boundingBox); - - Polygon polygon = null; - int poolCount = polygonPool.Count; - if (poolCount > 0) { - polygon = polygonPool.Items[poolCount - 1]; - polygonPool.RemoveAt(poolCount - 1); - } else - polygon = new Polygon(); - polygons.Add(polygon); - - int count = boundingBox.worldVerticesLength; - polygon.Count = count; - if (polygon.Vertices.Length < count) polygon.Vertices = new float[count]; - boundingBox.ComputeWorldVertices(slot, polygon.Vertices); - } - - if (updateAabb) { - AabbCompute(); - } else { - minX = int.MinValue; - minY = int.MinValue; - maxX = int.MaxValue; - maxY = int.MaxValue; - } - } - - private void AabbCompute () { - float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; - ExposedList polygons = Polygons; - for (int i = 0, n = polygons.Count; i < n; i++) { - Polygon polygon = polygons.Items[i]; - float[] vertices = polygon.Vertices; - for (int ii = 0, nn = polygon.Count; ii < nn; ii += 2) { - float x = vertices[ii]; - float y = vertices[ii + 1]; - minX = Math.Min(minX, x); - minY = Math.Min(minY, y); - maxX = Math.Max(maxX, x); - maxY = Math.Max(maxY, y); - } - } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; - } - - - /// Returns true if the axis aligned bounding box contains the point. - public bool AabbContainsPoint (float x, float y) { - return x >= minX && x <= maxX && y >= minY && y <= maxY; - } - - /// Returns true if the axis aligned bounding box intersects the line segment. - public bool AabbIntersectsSegment (float x1, float y1, float x2, float y2) { - float minX = this.minX; - float minY = this.minY; - float maxX = this.maxX; - float maxY = this.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; - } - - /// Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds. - public bool AabbIntersectsSkeleton (SkeletonBounds bounds) { - return minX < bounds.maxX && maxX > bounds.minX && minY < bounds.maxY && maxY > bounds.minY; - } - - /// Returns true if the polygon contains the point. - public bool ContainsPoint (Polygon polygon, float x, float y) { - float[] 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; - } - - /// 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. - public BoundingBoxAttachment ContainsPoint (float x, float y) { - ExposedList polygons = Polygons; - for (int i = 0, n = polygons.Count; i < n; i++) - if (ContainsPoint(polygons.Items[i], x, y)) return BoundingBoxes.Items[i]; - return null; - } - - /// 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. - public BoundingBoxAttachment IntersectsSegment (float x1, float y1, float x2, float y2) { - ExposedList polygons = Polygons; - for (int i = 0, n = polygons.Count; i < n; i++) - if (IntersectsSegment(polygons.Items[i], x1, y1, x2, y2)) return BoundingBoxes.Items[i]; - return null; - } - - /// Returns true if the polygon contains the line segment. - public bool IntersectsSegment (Polygon polygon, float x1, float y1, float x2, float y2) { - float[] vertices = polygon.Vertices; - int 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 (int 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; - } - - public Polygon GetPolygon (BoundingBoxAttachment attachment) { - int index = BoundingBoxes.IndexOf(attachment); - return index == -1 ? null : Polygons.Items[index]; - } - } - - public class Polygon { - public float[] Vertices { get; set; } - public int Count { get; set; } - - public Polygon () { - Vertices = new float[16]; - } - } +namespace Spine3_7_94 +{ + + /// + /// 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. + /// + public class SkeletonBounds + { + private ExposedList polygonPool = new ExposedList(); + private float minX, minY, maxX, maxY; + + public ExposedList BoundingBoxes { get; private set; } + public ExposedList Polygons { get; private set; } + public float MinX { get { return minX; } set { minX = value; } } + public float MinY { get { return minY; } set { minY = value; } } + public float MaxX { get { return maxX; } set { maxX = value; } } + public float MaxY { get { return maxY; } set { maxY = value; } } + public float Width { get { return maxX - minX; } } + public float Height { get { return maxY - minY; } } + + public SkeletonBounds() + { + BoundingBoxes = new ExposedList(); + Polygons = new ExposedList(); + } + + /// + /// Clears any previous polygons, finds all visible bounding box attachments, + /// and computes the world vertices for each bounding box's polygon. + /// The skeleton. + /// + /// If true, the axis aligned bounding box containing all the polygons is computed. + /// If false, the SkeletonBounds AABB methods will always return true. + /// + public void Update(Skeleton skeleton, bool updateAabb) + { + ExposedList boundingBoxes = BoundingBoxes; + ExposedList polygons = Polygons; + ExposedList slots = skeleton.slots; + int slotCount = slots.Count; + + boundingBoxes.Clear(); + for (int i = 0, n = polygons.Count; i < n; i++) + polygonPool.Add(polygons.Items[i]); + polygons.Clear(); + + for (int i = 0; i < slotCount; i++) + { + Slot slot = slots.Items[i]; + BoundingBoxAttachment boundingBox = slot.attachment as BoundingBoxAttachment; + if (boundingBox == null) continue; + boundingBoxes.Add(boundingBox); + + Polygon polygon = null; + int poolCount = polygonPool.Count; + if (poolCount > 0) + { + polygon = polygonPool.Items[poolCount - 1]; + polygonPool.RemoveAt(poolCount - 1); + } + else + polygon = new Polygon(); + polygons.Add(polygon); + + int count = boundingBox.worldVerticesLength; + polygon.Count = count; + if (polygon.Vertices.Length < count) polygon.Vertices = new float[count]; + boundingBox.ComputeWorldVertices(slot, polygon.Vertices); + } + + if (updateAabb) + { + AabbCompute(); + } + else + { + minX = int.MinValue; + minY = int.MinValue; + maxX = int.MaxValue; + maxY = int.MaxValue; + } + } + + private void AabbCompute() + { + float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; + ExposedList polygons = Polygons; + for (int i = 0, n = polygons.Count; i < n; i++) + { + Polygon polygon = polygons.Items[i]; + float[] vertices = polygon.Vertices; + for (int ii = 0, nn = polygon.Count; ii < nn; ii += 2) + { + float x = vertices[ii]; + float y = vertices[ii + 1]; + minX = Math.Min(minX, x); + minY = Math.Min(minY, y); + maxX = Math.Max(maxX, x); + maxY = Math.Max(maxY, y); + } + } + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + + /// Returns true if the axis aligned bounding box contains the point. + public bool AabbContainsPoint(float x, float y) + { + return x >= minX && x <= maxX && y >= minY && y <= maxY; + } + + /// Returns true if the axis aligned bounding box intersects the line segment. + public bool AabbIntersectsSegment(float x1, float y1, float x2, float y2) + { + float minX = this.minX; + float minY = this.minY; + float maxX = this.maxX; + float maxY = this.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; + } + + /// Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds. + public bool AabbIntersectsSkeleton(SkeletonBounds bounds) + { + return minX < bounds.maxX && maxX > bounds.minX && minY < bounds.maxY && maxY > bounds.minY; + } + + /// Returns true if the polygon contains the point. + public bool ContainsPoint(Polygon polygon, float x, float y) + { + float[] 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; + } + + /// 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. + public BoundingBoxAttachment ContainsPoint(float x, float y) + { + ExposedList polygons = Polygons; + for (int i = 0, n = polygons.Count; i < n; i++) + if (ContainsPoint(polygons.Items[i], x, y)) return BoundingBoxes.Items[i]; + return null; + } + + /// 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. + public BoundingBoxAttachment IntersectsSegment(float x1, float y1, float x2, float y2) + { + ExposedList polygons = Polygons; + for (int i = 0, n = polygons.Count; i < n; i++) + if (IntersectsSegment(polygons.Items[i], x1, y1, x2, y2)) return BoundingBoxes.Items[i]; + return null; + } + + /// Returns true if the polygon contains the line segment. + public bool IntersectsSegment(Polygon polygon, float x1, float y1, float x2, float y2) + { + float[] vertices = polygon.Vertices; + int 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 (int 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; + } + + public Polygon GetPolygon(BoundingBoxAttachment attachment) + { + int index = BoundingBoxes.IndexOf(attachment); + return index == -1 ? null : Polygons.Items[index]; + } + } + + public class Polygon + { + public float[] Vertices { get; set; } + public int Count { get; set; } + + public Polygon() + { + Vertices = new float[16]; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/SkeletonClipping.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/SkeletonClipping.cs index bee8fff..0ced1a9 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/SkeletonClipping.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/SkeletonClipping.cs @@ -29,268 +29,304 @@ using System; -namespace Spine3_7_94 { - public class SkeletonClipping { - internal readonly Triangulator triangulator = new Triangulator(); - internal readonly ExposedList clippingPolygon = new ExposedList(); - internal readonly ExposedList clipOutput = new ExposedList(128); - internal readonly ExposedList clippedVertices = new ExposedList(128); - internal readonly ExposedList clippedTriangles = new ExposedList(128); - internal readonly ExposedList clippedUVs = new ExposedList(128); - internal readonly ExposedList scratch = new ExposedList(); - - internal ClippingAttachment clipAttachment; - internal ExposedList> clippingPolygons; - - public ExposedList ClippedVertices { get { return clippedVertices; } } - public ExposedList ClippedTriangles { get { return clippedTriangles; } } - public ExposedList ClippedUVs { get { return clippedUVs; } } - - public bool IsClipping { get { return clipAttachment != null; } } - - public int ClipStart (Slot slot, ClippingAttachment clip) { - if (clipAttachment != null) return 0; - clipAttachment = clip; - - int n = clip.worldVerticesLength; - float[] vertices = clippingPolygon.Resize(n).Items; - clip.ComputeWorldVertices(slot, 0, n, vertices, 0, 2); - MakeClockwise(clippingPolygon); - clippingPolygons = triangulator.Decompose(clippingPolygon, triangulator.Triangulate(clippingPolygon)); - foreach (var polygon in clippingPolygons) { - MakeClockwise(polygon); - polygon.Add(polygon.Items[0]); - polygon.Add(polygon.Items[1]); - } - return clippingPolygons.Count; - } - - public void ClipEnd (Slot slot) { - if (clipAttachment != null && clipAttachment.endSlot == slot.data) ClipEnd(); - } - - public void ClipEnd () { - if (clipAttachment == null) return; - clipAttachment = null; - clippingPolygons = null; - clippedVertices.Clear(); - clippedTriangles.Clear(); - clippingPolygon.Clear(); - } - - public void ClipTriangles (float[] vertices, int verticesLength, int[] triangles, int trianglesLength, float[] uvs) { - ExposedList clipOutput = this.clipOutput, clippedVertices = this.clippedVertices; - var clippedTriangles = this.clippedTriangles; - var polygons = clippingPolygons.Items; - int polygonsCount = clippingPolygons.Count; - - int index = 0; - clippedVertices.Clear(); - clippedUVs.Clear(); - clippedTriangles.Clear(); - //outer: - for (int i = 0; i < trianglesLength; i += 3) { - int vertexOffset = triangles[i] << 1; - float x1 = vertices[vertexOffset], y1 = vertices[vertexOffset + 1]; - float u1 = uvs[vertexOffset], v1 = uvs[vertexOffset + 1]; - - vertexOffset = triangles[i + 1] << 1; - float x2 = vertices[vertexOffset], y2 = vertices[vertexOffset + 1]; - float u2 = uvs[vertexOffset], v2 = uvs[vertexOffset + 1]; - - vertexOffset = triangles[i + 2] << 1; - float x3 = vertices[vertexOffset], y3 = vertices[vertexOffset + 1]; - float u3 = uvs[vertexOffset], v3 = uvs[vertexOffset + 1]; - - for (int p = 0; p < polygonsCount; p++) { - int s = clippedVertices.Count; - if (Clip(x1, y1, x2, y2, x3, y3, polygons[p], clipOutput)) { - int clipOutputLength = clipOutput.Count; - 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)); - - int clipOutputCount = clipOutputLength >> 1; - float[] clipOutputItems = clipOutput.Items; - float[] clippedVerticesItems = clippedVertices.Resize(s + clipOutputCount * 2).Items; - float[] clippedUVsItems = clippedUVs.Resize(s + clipOutputCount * 2).Items; - for (int ii = 0; ii < clipOutputLength; ii += 2) { - float x = clipOutputItems[ii], y = clipOutputItems[ii + 1]; - clippedVerticesItems[s] = x; - clippedVerticesItems[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; - clippedUVsItems[s] = u1 * a + u2 * b + u3 * c; - clippedUVsItems[s + 1] = v1 * a + v2 * b + v3 * c; - s += 2; - } - - s = clippedTriangles.Count; - int[] clippedTrianglesItems = clippedTriangles.Resize(s + 3 * (clipOutputCount - 2)).Items; - clipOutputCount--; - for (int ii = 1; ii < clipOutputCount; ii++) { - clippedTrianglesItems[s] = index; - clippedTrianglesItems[s + 1] = index + ii; - clippedTrianglesItems[s + 2] = index + ii + 1; - s += 3; - } - index += clipOutputCount + 1; - } - else { - float[] clippedVerticesItems = clippedVertices.Resize(s + 3 * 2).Items; - float[] clippedUVsItems = clippedUVs.Resize(s + 3 * 2).Items; - clippedVerticesItems[s] = x1; - clippedVerticesItems[s + 1] = y1; - clippedVerticesItems[s + 2] = x2; - clippedVerticesItems[s + 3] = y2; - clippedVerticesItems[s + 4] = x3; - clippedVerticesItems[s + 5] = y3; - - clippedUVsItems[s] = u1; - clippedUVsItems[s + 1] = v1; - clippedUVsItems[s + 2] = u2; - clippedUVsItems[s + 3] = v2; - clippedUVsItems[s + 4] = u3; - clippedUVsItems[s + 5] = v3; - - s = clippedTriangles.Count; - int[] clippedTrianglesItems = clippedTriangles.Resize(s + 3).Items; - clippedTrianglesItems[s] = index; - clippedTrianglesItems[s + 1] = index + 1; - clippedTrianglesItems[s + 2] = index + 2; - index += 3; - break; //continue outer; - } - } - } - - } - - /** Clips the input triangle against the convex, clockwise clipping area. If the triangle lies entirely within the clipping +namespace Spine3_7_94 +{ + public class SkeletonClipping + { + internal readonly Triangulator triangulator = new Triangulator(); + internal readonly ExposedList clippingPolygon = new ExposedList(); + internal readonly ExposedList clipOutput = new ExposedList(128); + internal readonly ExposedList clippedVertices = new ExposedList(128); + internal readonly ExposedList clippedTriangles = new ExposedList(128); + internal readonly ExposedList clippedUVs = new ExposedList(128); + internal readonly ExposedList scratch = new ExposedList(); + + internal ClippingAttachment clipAttachment; + internal ExposedList> clippingPolygons; + + public ExposedList ClippedVertices { get { return clippedVertices; } } + public ExposedList ClippedTriangles { get { return clippedTriangles; } } + public ExposedList ClippedUVs { get { return clippedUVs; } } + + public bool IsClipping { get { return clipAttachment != null; } } + + public int ClipStart(Slot slot, ClippingAttachment clip) + { + if (clipAttachment != null) return 0; + clipAttachment = clip; + + int n = clip.worldVerticesLength; + float[] vertices = clippingPolygon.Resize(n).Items; + clip.ComputeWorldVertices(slot, 0, n, vertices, 0, 2); + MakeClockwise(clippingPolygon); + clippingPolygons = triangulator.Decompose(clippingPolygon, triangulator.Triangulate(clippingPolygon)); + foreach (var polygon in clippingPolygons) + { + MakeClockwise(polygon); + polygon.Add(polygon.Items[0]); + polygon.Add(polygon.Items[1]); + } + return clippingPolygons.Count; + } + + public void ClipEnd(Slot slot) + { + if (clipAttachment != null && clipAttachment.endSlot == slot.data) ClipEnd(); + } + + public void ClipEnd() + { + if (clipAttachment == null) return; + clipAttachment = null; + clippingPolygons = null; + clippedVertices.Clear(); + clippedTriangles.Clear(); + clippingPolygon.Clear(); + } + + public void ClipTriangles(float[] vertices, int verticesLength, int[] triangles, int trianglesLength, float[] uvs) + { + ExposedList clipOutput = this.clipOutput, clippedVertices = this.clippedVertices; + var clippedTriangles = this.clippedTriangles; + var polygons = clippingPolygons.Items; + int polygonsCount = clippingPolygons.Count; + + int index = 0; + clippedVertices.Clear(); + clippedUVs.Clear(); + clippedTriangles.Clear(); + //outer: + for (int i = 0; i < trianglesLength; i += 3) + { + int vertexOffset = triangles[i] << 1; + float x1 = vertices[vertexOffset], y1 = vertices[vertexOffset + 1]; + float u1 = uvs[vertexOffset], v1 = uvs[vertexOffset + 1]; + + vertexOffset = triangles[i + 1] << 1; + float x2 = vertices[vertexOffset], y2 = vertices[vertexOffset + 1]; + float u2 = uvs[vertexOffset], v2 = uvs[vertexOffset + 1]; + + vertexOffset = triangles[i + 2] << 1; + float x3 = vertices[vertexOffset], y3 = vertices[vertexOffset + 1]; + float u3 = uvs[vertexOffset], v3 = uvs[vertexOffset + 1]; + + for (int p = 0; p < polygonsCount; p++) + { + int s = clippedVertices.Count; + if (Clip(x1, y1, x2, y2, x3, y3, polygons[p], clipOutput)) + { + int clipOutputLength = clipOutput.Count; + 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)); + + int clipOutputCount = clipOutputLength >> 1; + float[] clipOutputItems = clipOutput.Items; + float[] clippedVerticesItems = clippedVertices.Resize(s + clipOutputCount * 2).Items; + float[] clippedUVsItems = clippedUVs.Resize(s + clipOutputCount * 2).Items; + for (int ii = 0; ii < clipOutputLength; ii += 2) + { + float x = clipOutputItems[ii], y = clipOutputItems[ii + 1]; + clippedVerticesItems[s] = x; + clippedVerticesItems[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; + clippedUVsItems[s] = u1 * a + u2 * b + u3 * c; + clippedUVsItems[s + 1] = v1 * a + v2 * b + v3 * c; + s += 2; + } + + s = clippedTriangles.Count; + int[] clippedTrianglesItems = clippedTriangles.Resize(s + 3 * (clipOutputCount - 2)).Items; + clipOutputCount--; + for (int ii = 1; ii < clipOutputCount; ii++) + { + clippedTrianglesItems[s] = index; + clippedTrianglesItems[s + 1] = index + ii; + clippedTrianglesItems[s + 2] = index + ii + 1; + s += 3; + } + index += clipOutputCount + 1; + } + else + { + float[] clippedVerticesItems = clippedVertices.Resize(s + 3 * 2).Items; + float[] clippedUVsItems = clippedUVs.Resize(s + 3 * 2).Items; + clippedVerticesItems[s] = x1; + clippedVerticesItems[s + 1] = y1; + clippedVerticesItems[s + 2] = x2; + clippedVerticesItems[s + 3] = y2; + clippedVerticesItems[s + 4] = x3; + clippedVerticesItems[s + 5] = y3; + + clippedUVsItems[s] = u1; + clippedUVsItems[s + 1] = v1; + clippedUVsItems[s + 2] = u2; + clippedUVsItems[s + 3] = v2; + clippedUVsItems[s + 4] = u3; + clippedUVsItems[s + 5] = v3; + + s = clippedTriangles.Count; + int[] clippedTrianglesItems = clippedTriangles.Resize(s + 3).Items; + clippedTrianglesItems[s] = index; + clippedTrianglesItems[s + 1] = index + 1; + clippedTrianglesItems[s + 2] = index + 2; + index += 3; + break; //continue outer; + } + } + } + + } + + /** 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. */ - internal bool Clip (float x1, float y1, float x2, float y2, float x3, float y3, ExposedList clippingArea, ExposedList output) { - var originalOutput = output; - var clipped = false; - - // Avoid copy at the end. - ExposedList input = null; - if (clippingArea.Count % 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(); - - float[] clippingVertices = clippingArea.Items; - int clippingVerticesLast = clippingArea.Count - 4; - for (int i = 0; ; i += 2) { - float edgeX = clippingVertices[i], edgeY = clippingVertices[i + 1]; - float edgeX2 = clippingVertices[i + 2], edgeY2 = clippingVertices[i + 3]; - float deltaX = edgeX - edgeX2, deltaY = edgeY - edgeY2; - - float[] inputVertices = input.Items; - int inputVerticesLength = input.Count - 2, outputStart = output.Count; - for (int ii = 0; ii < inputVerticesLength; ii += 2) { - float inputX = inputVertices[ii], inputY = inputVertices[ii + 1]; - float inputX2 = inputVertices[ii + 2], inputY2 = inputVertices[ii + 3]; - bool side2 = deltaX * (inputY2 - edgeY2) - deltaY * (inputX2 - edgeX2) > 0; - if (deltaX * (inputY - edgeY2) - deltaY * (inputX - edgeX2) > 0) { - if (side2) { // v1 inside, v2 inside - output.Add(inputX2); - output.Add(inputY2); - continue; - } - // v1 inside, v2 outside - float c0 = inputY2 - inputY, c2 = inputX2 - inputX; - float s = c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY); - if (Math.Abs(s) > 0.000001f) { - float ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / s; - output.Add(edgeX + (edgeX2 - edgeX) * ua); - output.Add(edgeY + (edgeY2 - edgeY) * ua); - } else { - output.Add(edgeX); - output.Add(edgeY); - } - } - else if (side2) { // v1 outside, v2 inside - float c0 = inputY2 - inputY, c2 = inputX2 - inputX; - float s = c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY); - if (Math.Abs(s) > 0.000001f) { - float ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / s; - output.Add(edgeX + (edgeX2 - edgeX) * ua); - output.Add(edgeY + (edgeY2 - edgeY) * ua); - } else { - output.Add(edgeX); - output.Add(edgeY); - } - output.Add(inputX2); - output.Add(inputY2); - } - clipped = true; - } - - if (outputStart == output.Count) { // All edges outside. - originalOutput.Clear(); - return true; - } - - output.Add(output.Items[0]); - output.Add(output.Items[1]); - - if (i == clippingVerticesLast) break; - var temp = output; - output = input; - output.Clear(); - input = temp; - } - - if (originalOutput != output) { - originalOutput.Clear(); - for (int i = 0, n = output.Count - 2; i < n; i++) { - originalOutput.Add(output.Items[i]); - } - } else { - originalOutput.Resize(originalOutput.Count - 2); - } - - return clipped; - } - - public static void MakeClockwise (ExposedList polygon) { - float[] vertices = polygon.Items; - int verticeslength = polygon.Count; - - float area = vertices[verticeslength - 2] * vertices[1] - vertices[0] * vertices[verticeslength - 1], p1x, p1y, p2x, p2y; - for (int i = 0, n = verticeslength - 3; i < n; i += 2) { - p1x = vertices[i]; - p1y = vertices[i + 1]; - p2x = vertices[i + 2]; - p2y = vertices[i + 3]; - area += p1x * p2y - p2x * p1y; - } - if (area < 0) return; - - for (int i = 0, lastX = verticeslength - 2, n = verticeslength >> 1; i < n; i += 2) { - float x = vertices[i], y = vertices[i + 1]; - int other = lastX - i; - vertices[i] = vertices[other]; - vertices[i + 1] = vertices[other + 1]; - vertices[other] = x; - vertices[other + 1] = y; - } - } - } + internal bool Clip(float x1, float y1, float x2, float y2, float x3, float y3, ExposedList clippingArea, ExposedList output) + { + var originalOutput = output; + var clipped = false; + + // Avoid copy at the end. + ExposedList input = null; + if (clippingArea.Count % 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(); + + float[] clippingVertices = clippingArea.Items; + int clippingVerticesLast = clippingArea.Count - 4; + for (int i = 0; ; i += 2) + { + float edgeX = clippingVertices[i], edgeY = clippingVertices[i + 1]; + float edgeX2 = clippingVertices[i + 2], edgeY2 = clippingVertices[i + 3]; + float deltaX = edgeX - edgeX2, deltaY = edgeY - edgeY2; + + float[] inputVertices = input.Items; + int inputVerticesLength = input.Count - 2, outputStart = output.Count; + for (int ii = 0; ii < inputVerticesLength; ii += 2) + { + float inputX = inputVertices[ii], inputY = inputVertices[ii + 1]; + float inputX2 = inputVertices[ii + 2], inputY2 = inputVertices[ii + 3]; + bool side2 = deltaX * (inputY2 - edgeY2) - deltaY * (inputX2 - edgeX2) > 0; + if (deltaX * (inputY - edgeY2) - deltaY * (inputX - edgeX2) > 0) + { + if (side2) + { // v1 inside, v2 inside + output.Add(inputX2); + output.Add(inputY2); + continue; + } + // v1 inside, v2 outside + float c0 = inputY2 - inputY, c2 = inputX2 - inputX; + float s = c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY); + if (Math.Abs(s) > 0.000001f) + { + float ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / s; + output.Add(edgeX + (edgeX2 - edgeX) * ua); + output.Add(edgeY + (edgeY2 - edgeY) * ua); + } + else + { + output.Add(edgeX); + output.Add(edgeY); + } + } + else if (side2) + { // v1 outside, v2 inside + float c0 = inputY2 - inputY, c2 = inputX2 - inputX; + float s = c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY); + if (Math.Abs(s) > 0.000001f) + { + float ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / s; + output.Add(edgeX + (edgeX2 - edgeX) * ua); + output.Add(edgeY + (edgeY2 - edgeY) * ua); + } + else + { + output.Add(edgeX); + output.Add(edgeY); + } + output.Add(inputX2); + output.Add(inputY2); + } + clipped = true; + } + + if (outputStart == output.Count) + { // All edges outside. + originalOutput.Clear(); + return true; + } + + output.Add(output.Items[0]); + output.Add(output.Items[1]); + + if (i == clippingVerticesLast) break; + var temp = output; + output = input; + output.Clear(); + input = temp; + } + + if (originalOutput != output) + { + originalOutput.Clear(); + for (int i = 0, n = output.Count - 2; i < n; i++) + { + originalOutput.Add(output.Items[i]); + } + } + else + { + originalOutput.Resize(originalOutput.Count - 2); + } + + return clipped; + } + + public static void MakeClockwise(ExposedList polygon) + { + float[] vertices = polygon.Items; + int verticeslength = polygon.Count; + + float area = vertices[verticeslength - 2] * vertices[1] - vertices[0] * vertices[verticeslength - 1], p1x, p1y, p2x, p2y; + for (int i = 0, n = verticeslength - 3; i < n; i += 2) + { + p1x = vertices[i]; + p1y = vertices[i + 1]; + p2x = vertices[i + 2]; + p2y = vertices[i + 3]; + area += p1x * p2y - p2x * p1y; + } + if (area < 0) return; + + for (int i = 0, lastX = verticeslength - 2, n = verticeslength >> 1; i < n; i += 2) + { + float x = vertices[i], y = vertices[i + 1]; + int other = lastX - i; + vertices[i] = vertices[other]; + vertices[i + 1] = vertices[other + 1]; + vertices[other] = x; + vertices[other + 1] = y; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/SkeletonData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/SkeletonData.cs index ea0e102..c79d15d 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/SkeletonData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/SkeletonData.cs @@ -29,200 +29,220 @@ using System; -namespace Spine3_7_94 { - - /// Stores the setup pose and all of the stateless data for a skeleton. - public class SkeletonData { - internal string name; - internal ExposedList bones = new ExposedList(); // Ordered parents first - internal ExposedList slots = new ExposedList(); // Setup pose draw order. - internal ExposedList skins = new ExposedList(); - internal Skin defaultSkin; - internal ExposedList events = new ExposedList(); - internal ExposedList animations = new ExposedList(); - internal ExposedList ikConstraints = new ExposedList(); - internal ExposedList transformConstraints = new ExposedList(); - internal ExposedList pathConstraints = new ExposedList(); - internal float width, height; - internal string version, hash; - - // Nonessential. - internal float fps; - internal string imagesPath, audioPath; - - public string Name { get { return name; } set { name = value; } } - - /// The skeleton's bones, sorted parent first. The root bone is always the first bone. - public ExposedList Bones { get { return bones; } } - - public ExposedList Slots { get { return slots; } } - - /// All skins, including the default skin. - public ExposedList Skins { get { return skins; } set { skins = value; } } - - /// - /// The skeleton's default skin. - /// By default this skin contains all attachments that were not in a skin in Spine. - /// - /// May be null. - public Skin DefaultSkin { get { return defaultSkin; } set { defaultSkin = value; } } - - public ExposedList Events { get { return events; } set { events = value; } } - public ExposedList Animations { get { return animations; } set { animations = value; } } - public ExposedList IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } } - public ExposedList TransformConstraints { get { return transformConstraints; } set { transformConstraints = value; } } - public ExposedList PathConstraints { get { return pathConstraints; } set { pathConstraints = value; } } - - public float Width { get { return width; } set { width = value; } } - public float Height { get { return height; } set { height = value; } } - /// The Spine version used to export this data, or null. - public string Version { get { return version; } set { version = value; } } - public string Hash { get { return hash; } set { hash = value; } } - - /// The path to the images directory as defined in Spine. Available only when nonessential data was exported. May be null - public string ImagesPath { get { return imagesPath; } set { imagesPath = value; } } - - /// The path to the audio directory defined in Spine. Available only when nonessential data was exported. May be null. - public string AudioPath { get { return audioPath; } set { audioPath = value; } } - - /// - /// The dopesheet FPS in Spine. Available only when nonessential data was exported. - public float Fps { get { return fps; } set { fps = value; } } - - // --- Bones. - - /// - /// 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. - /// May be null. - public BoneData FindBone (string boneName) { - if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); - var bones = this.bones; - var bonesItems = bones.Items; - for (int i = 0, n = bones.Count; i < n; i++) { - BoneData bone = bonesItems[i]; - if (bone.name == boneName) return bone; - } - return null; - } - - /// -1 if the bone was not found. - public int FindBoneIndex (string boneName) { - if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); - var bones = this.bones; - var bonesItems = bones.Items; - for (int i = 0, n = bones.Count; i < n; i++) - if (bonesItems[i].name == boneName) return i; - return -1; - } - - // --- Slots. - - /// May be null. - public SlotData FindSlot (string slotName) { - if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); - ExposedList slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) { - SlotData slot = slots.Items[i]; - if (slot.name == slotName) return slot; - } - return null; - } - - /// -1 if the slot was not found. - public int FindSlotIndex (string slotName) { - if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); - ExposedList slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) - if (slots.Items[i].name == slotName) return i; - return -1; - } - - // --- Skins. - - /// May be null. - public Skin FindSkin (string skinName) { - if (skinName == null) throw new ArgumentNullException("skinName", "skinName cannot be null."); - foreach (Skin skin in skins) - if (skin.name == skinName) return skin; - return null; - } - - // --- Events. - - /// May be null. - public EventData FindEvent (string eventDataName) { - if (eventDataName == null) throw new ArgumentNullException("eventDataName", "eventDataName cannot be null."); - foreach (EventData eventData in events) - if (eventData.name == eventDataName) return eventData; - return null; - } - - // --- Animations. - - /// May be null. - public Animation FindAnimation (string animationName) { - if (animationName == null) throw new ArgumentNullException("animationName", "animationName cannot be null."); - ExposedList animations = this.animations; - for (int i = 0, n = animations.Count; i < n; i++) { - Animation animation = animations.Items[i]; - if (animation.name == animationName) return animation; - } - return null; - } - - // --- IK constraints. - - /// May be null. - public IkConstraintData FindIkConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - ExposedList ikConstraints = this.ikConstraints; - for (int i = 0, n = ikConstraints.Count; i < n; i++) { - IkConstraintData ikConstraint = ikConstraints.Items[i]; - if (ikConstraint.name == constraintName) return ikConstraint; - } - return null; - } - - // --- Transform constraints. - - /// May be null. - public TransformConstraintData FindTransformConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - ExposedList transformConstraints = this.transformConstraints; - for (int i = 0, n = transformConstraints.Count; i < n; i++) { - TransformConstraintData transformConstraint = transformConstraints.Items[i]; - if (transformConstraint.name == constraintName) return transformConstraint; - } - return null; - } - - // --- Path constraints. - - /// May be null. - public PathConstraintData FindPathConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - ExposedList pathConstraints = this.pathConstraints; - for (int i = 0, n = pathConstraints.Count; i < n; i++) { - PathConstraintData constraint = pathConstraints.Items[i]; - if (constraint.name.Equals(constraintName)) return constraint; - } - return null; - } - - /// -1 if the path constraint was not found. - public int FindPathConstraintIndex (string pathConstraintName) { - if (pathConstraintName == null) throw new ArgumentNullException("pathConstraintName", "pathConstraintName cannot be null."); - ExposedList pathConstraints = this.pathConstraints; - for (int i = 0, n = pathConstraints.Count; i < n; i++) - if (pathConstraints.Items[i].name.Equals(pathConstraintName)) return i; - return -1; - } - - // --- - - override public string ToString () { - return name ?? base.ToString(); - } - } +namespace Spine3_7_94 +{ + + /// Stores the setup pose and all of the stateless data for a skeleton. + public class SkeletonData + { + internal string name; + internal ExposedList bones = new ExposedList(); // Ordered parents first + internal ExposedList slots = new ExposedList(); // Setup pose draw order. + internal ExposedList skins = new ExposedList(); + internal Skin defaultSkin; + internal ExposedList events = new ExposedList(); + internal ExposedList animations = new ExposedList(); + internal ExposedList ikConstraints = new ExposedList(); + internal ExposedList transformConstraints = new ExposedList(); + internal ExposedList pathConstraints = new ExposedList(); + internal float width, height; + internal string version, hash; + + // Nonessential. + internal float fps; + internal string imagesPath, audioPath; + + public string Name { get { return name; } set { name = value; } } + + /// The skeleton's bones, sorted parent first. The root bone is always the first bone. + public ExposedList Bones { get { return bones; } } + + public ExposedList Slots { get { return slots; } } + + /// All skins, including the default skin. + public ExposedList Skins { get { return skins; } set { skins = value; } } + + /// + /// The skeleton's default skin. + /// By default this skin contains all attachments that were not in a skin in Spine. + /// + /// May be null. + public Skin DefaultSkin { get { return defaultSkin; } set { defaultSkin = value; } } + + public ExposedList Events { get { return events; } set { events = value; } } + public ExposedList Animations { get { return animations; } set { animations = value; } } + public ExposedList IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } } + public ExposedList TransformConstraints { get { return transformConstraints; } set { transformConstraints = value; } } + public ExposedList PathConstraints { get { return pathConstraints; } set { pathConstraints = value; } } + + public float Width { get { return width; } set { width = value; } } + public float Height { get { return height; } set { height = value; } } + /// The Spine version used to export this data, or null. + public string Version { get { return version; } set { version = value; } } + public string Hash { get { return hash; } set { hash = value; } } + + /// The path to the images directory as defined in Spine. Available only when nonessential data was exported. May be null + public string ImagesPath { get { return imagesPath; } set { imagesPath = value; } } + + /// The path to the audio directory defined in Spine. Available only when nonessential data was exported. May be null. + public string AudioPath { get { return audioPath; } set { audioPath = value; } } + + /// + /// The dopesheet FPS in Spine. Available only when nonessential data was exported. + public float Fps { get { return fps; } set { fps = value; } } + + // --- Bones. + + /// + /// 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. + /// May be null. + public BoneData FindBone(string boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); + var bones = this.bones; + var bonesItems = bones.Items; + for (int i = 0, n = bones.Count; i < n; i++) + { + BoneData bone = bonesItems[i]; + if (bone.name == boneName) return bone; + } + return null; + } + + /// -1 if the bone was not found. + public int FindBoneIndex(string boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); + var bones = this.bones; + var bonesItems = bones.Items; + for (int i = 0, n = bones.Count; i < n; i++) + if (bonesItems[i].name == boneName) return i; + return -1; + } + + // --- Slots. + + /// May be null. + public SlotData FindSlot(string slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + ExposedList slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + { + SlotData slot = slots.Items[i]; + if (slot.name == slotName) return slot; + } + return null; + } + + /// -1 if the slot was not found. + public int FindSlotIndex(string slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + ExposedList slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + if (slots.Items[i].name == slotName) return i; + return -1; + } + + // --- Skins. + + /// May be null. + public Skin FindSkin(string skinName) + { + if (skinName == null) throw new ArgumentNullException("skinName", "skinName cannot be null."); + foreach (Skin skin in skins) + if (skin.name == skinName) return skin; + return null; + } + + // --- Events. + + /// May be null. + public EventData FindEvent(string eventDataName) + { + if (eventDataName == null) throw new ArgumentNullException("eventDataName", "eventDataName cannot be null."); + foreach (EventData eventData in events) + if (eventData.name == eventDataName) return eventData; + return null; + } + + // --- Animations. + + /// May be null. + public Animation FindAnimation(string animationName) + { + if (animationName == null) throw new ArgumentNullException("animationName", "animationName cannot be null."); + ExposedList animations = this.animations; + for (int i = 0, n = animations.Count; i < n; i++) + { + Animation animation = animations.Items[i]; + if (animation.name == animationName) return animation; + } + return null; + } + + // --- IK constraints. + + /// May be null. + public IkConstraintData FindIkConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + ExposedList ikConstraints = this.ikConstraints; + for (int i = 0, n = ikConstraints.Count; i < n; i++) + { + IkConstraintData ikConstraint = ikConstraints.Items[i]; + if (ikConstraint.name == constraintName) return ikConstraint; + } + return null; + } + + // --- Transform constraints. + + /// May be null. + public TransformConstraintData FindTransformConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + ExposedList transformConstraints = this.transformConstraints; + for (int i = 0, n = transformConstraints.Count; i < n; i++) + { + TransformConstraintData transformConstraint = transformConstraints.Items[i]; + if (transformConstraint.name == constraintName) return transformConstraint; + } + return null; + } + + // --- Path constraints. + + /// May be null. + public PathConstraintData FindPathConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + ExposedList pathConstraints = this.pathConstraints; + for (int i = 0, n = pathConstraints.Count; i < n; i++) + { + PathConstraintData constraint = pathConstraints.Items[i]; + if (constraint.name.Equals(constraintName)) return constraint; + } + return null; + } + + /// -1 if the path constraint was not found. + public int FindPathConstraintIndex(string pathConstraintName) + { + if (pathConstraintName == null) throw new ArgumentNullException("pathConstraintName", "pathConstraintName cannot be null."); + ExposedList pathConstraints = this.pathConstraints; + for (int i = 0, n = pathConstraints.Count; i < n; i++) + if (pathConstraints.Items[i].name.Equals(pathConstraintName)) return i; + return -1; + } + + // --- + + override public string ToString() + { + return name ?? base.ToString(); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/SkeletonJson.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/SkeletonJson.cs index 16744d0..d030457 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/SkeletonJson.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/SkeletonJson.cs @@ -32,32 +32,36 @@ #endif using System; -using System.IO; using System.Collections.Generic; +using System.IO; #if WINDOWS_STOREAPP using System.Threading.Tasks; using Windows.Storage; #endif -namespace Spine3_7_94 { - public class SkeletonJson { - public float Scale { get; set; } +namespace Spine3_7_94 +{ + public class SkeletonJson + { + public float Scale { get; set; } - private AttachmentLoader attachmentLoader; - private List linkedMeshes = new List(); + private AttachmentLoader attachmentLoader; + private List linkedMeshes = new List(); - public SkeletonJson (params Atlas[] atlasArray) - : this(new AtlasAttachmentLoader(atlasArray)) { - } + public SkeletonJson(params Atlas[] atlasArray) + : this(new AtlasAttachmentLoader(atlasArray)) + { + } - public SkeletonJson (AttachmentLoader attachmentLoader) { - if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader", "attachmentLoader cannot be null."); - this.attachmentLoader = attachmentLoader; - Scale = 1; - } + public SkeletonJson(AttachmentLoader attachmentLoader) + { + if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader", "attachmentLoader cannot be null."); + this.attachmentLoader = attachmentLoader; + Scale = 1; + } - #if !IS_UNITY && WINDOWS_STOREAPP +#if !IS_UNITY && WINDOWS_STOREAPP private async Task ReadFile(string path) { var folder = Windows.ApplicationModel.Package.Current.InstalledLocation; var file = await folder.GetFileAsync(path).AsTask().ConfigureAwait(false); @@ -71,811 +75,932 @@ private async Task ReadFile(string path) { public SkeletonData ReadSkeletonData (string path) { return this.ReadFile(path).Result; } - #else - public SkeletonData ReadSkeletonData (string path) { - #if WINDOWS_PHONE +#else + public SkeletonData ReadSkeletonData(string path) + { +#if WINDOWS_PHONE using (var reader = new StreamReader(Microsoft.Xna.Framework.TitleContainer.OpenStream(path))) { - #else - using (var reader = new StreamReader(new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))) { - #endif - SkeletonData skeletonData = ReadSkeletonData(reader); - skeletonData.name = Path.GetFileNameWithoutExtension(path); - return skeletonData; - } - } - #endif - - public SkeletonData ReadSkeletonData (TextReader reader) { - if (reader == null) throw new ArgumentNullException("reader", "reader cannot be null."); - - float scale = this.Scale; - var skeletonData = new SkeletonData(); - - var root = Json.Deserialize(reader) as Dictionary; - if (root == null) throw new Exception("Invalid JSON."); - - // Skeleton. - if (root.ContainsKey("skeleton")) { - var skeletonMap = (Dictionary)root["skeleton"]; - skeletonData.hash = (string)skeletonMap["hash"]; - skeletonData.version = (string)skeletonMap["spine"]; - skeletonData.width = GetFloat(skeletonMap, "width", 0); - skeletonData.height = GetFloat(skeletonMap, "height", 0); - skeletonData.fps = GetFloat(skeletonMap, "fps", 0); - skeletonData.imagesPath = GetString(skeletonMap, "images", null); - skeletonData.audioPath = GetString(skeletonMap, "audio", null); - } - - // Bones. - foreach (Dictionary boneMap in (List)root["bones"]) { - BoneData parent = null; - if (boneMap.ContainsKey("parent")) { - parent = skeletonData.FindBone((string)boneMap["parent"]); - if (parent == null) - throw new Exception("Parent bone not found: " + boneMap["parent"]); - } - var data = new BoneData(skeletonData.Bones.Count, (string)boneMap["name"], parent); - data.length = GetFloat(boneMap, "length", 0) * scale; - data.x = GetFloat(boneMap, "x", 0) * scale; - data.y = GetFloat(boneMap, "y", 0) * scale; - data.rotation = GetFloat(boneMap, "rotation", 0); - data.scaleX = GetFloat(boneMap, "scaleX", 1); - data.scaleY = GetFloat(boneMap, "scaleY", 1); - data.shearX = GetFloat(boneMap, "shearX", 0); - data.shearY = GetFloat(boneMap, "shearY", 0); - - string tm = GetString(boneMap, "transform", TransformMode.Normal.ToString()); - data.transformMode = (TransformMode)Enum.Parse(typeof(TransformMode), tm, true); - - skeletonData.bones.Add(data); - } - - // Slots. - if (root.ContainsKey("slots")) { - foreach (Dictionary slotMap in (List)root["slots"]) { - var slotName = (string)slotMap["name"]; - var boneName = (string)slotMap["bone"]; - BoneData boneData = skeletonData.FindBone(boneName); - if (boneData == null) throw new Exception("Slot bone not found: " + boneName); - var data = new SlotData(skeletonData.Slots.Count, slotName, boneData); - - if (slotMap.ContainsKey("color")) { - string color = (string)slotMap["color"]; - data.r = ToColor(color, 0); - data.g = ToColor(color, 1); - data.b = ToColor(color, 2); - data.a = ToColor(color, 3); - } - - if (slotMap.ContainsKey("dark")) { - var color2 = (string)slotMap["dark"]; - data.r2 = ToColor(color2, 0, 6); // expectedLength = 6. ie. "RRGGBB" - data.g2 = ToColor(color2, 1, 6); - data.b2 = ToColor(color2, 2, 6); - data.hasSecondColor = true; - } - - data.attachmentName = GetString(slotMap, "attachment", null); - if (slotMap.ContainsKey("blend")) - data.blendMode = (BlendMode)Enum.Parse(typeof(BlendMode), (string)slotMap["blend"], true); - else - data.blendMode = BlendMode.Normal; - skeletonData.slots.Add(data); - } - } - - // IK constraints. - if (root.ContainsKey("ik")) { - foreach (Dictionary constraintMap in (List)root["ik"]) { - IkConstraintData data = new IkConstraintData((string)constraintMap["name"]); - data.order = GetInt(constraintMap, "order", 0); - - foreach (string boneName in (List)constraintMap["bones"]) { - BoneData bone = skeletonData.FindBone(boneName); - if (bone == null) throw new Exception("IK constraint bone not found: " + boneName); - data.bones.Add(bone); - } - - string targetName = (string)constraintMap["target"]; - data.target = skeletonData.FindBone(targetName); - if (data.target == null) throw new Exception("Target bone not found: " + targetName); - data.mix = GetFloat(constraintMap, "mix", 1); - data.bendDirection = GetBoolean(constraintMap, "bendPositive", true) ? 1 : -1; - data.compress = GetBoolean(constraintMap, "compress", false); - data.stretch = GetBoolean(constraintMap, "stretch", false); - data.uniform = GetBoolean(constraintMap, "uniform", false); - - skeletonData.ikConstraints.Add(data); - } - } - - // Transform constraints. - if (root.ContainsKey("transform")) { - foreach (Dictionary constraintMap in (List)root["transform"]) { - TransformConstraintData data = new TransformConstraintData((string)constraintMap["name"]); - data.order = GetInt(constraintMap, "order", 0); - - foreach (string boneName in (List)constraintMap["bones"]) { - BoneData bone = skeletonData.FindBone(boneName); - if (bone == null) throw new Exception("Transform constraint bone not found: " + boneName); - data.bones.Add(bone); - } - - string targetName = (string)constraintMap["target"]; - data.target = skeletonData.FindBone(targetName); - if (data.target == null) throw new Exception("Target bone not found: " + targetName); - - data.local = GetBoolean(constraintMap, "local", false); - data.relative = GetBoolean(constraintMap, "relative", false); - - data.offsetRotation = GetFloat(constraintMap, "rotation", 0); - data.offsetX = GetFloat(constraintMap, "x", 0) * scale; - data.offsetY = GetFloat(constraintMap, "y", 0) * scale; - data.offsetScaleX = GetFloat(constraintMap, "scaleX", 0); - data.offsetScaleY = GetFloat(constraintMap, "scaleY", 0); - data.offsetShearY = GetFloat(constraintMap, "shearY", 0); - - data.rotateMix = GetFloat(constraintMap, "rotateMix", 1); - data.translateMix = GetFloat(constraintMap, "translateMix", 1); - data.scaleMix = GetFloat(constraintMap, "scaleMix", 1); - data.shearMix = GetFloat(constraintMap, "shearMix", 1); - - skeletonData.transformConstraints.Add(data); - } - } - - // Path constraints. - if(root.ContainsKey("path")) { - foreach (Dictionary constraintMap in (List)root["path"]) { - PathConstraintData data = new PathConstraintData((string)constraintMap["name"]); - data.order = GetInt(constraintMap, "order", 0); - - foreach (string boneName in (List)constraintMap["bones"]) { - BoneData bone = skeletonData.FindBone(boneName); - if (bone == null) throw new Exception("Path bone not found: " + boneName); - data.bones.Add(bone); - } - - string targetName = (string)constraintMap["target"]; - data.target = skeletonData.FindSlot(targetName); - if (data.target == null) throw new Exception("Target slot not found: " + targetName); - - data.positionMode = (PositionMode)Enum.Parse(typeof(PositionMode), GetString(constraintMap, "positionMode", "percent"), true); - data.spacingMode = (SpacingMode)Enum.Parse(typeof(SpacingMode), GetString(constraintMap, "spacingMode", "length"), true); - data.rotateMode = (RotateMode)Enum.Parse(typeof(RotateMode), GetString(constraintMap, "rotateMode", "tangent"), true); - data.offsetRotation = GetFloat(constraintMap, "rotation", 0); - data.position = GetFloat(constraintMap, "position", 0); - if (data.positionMode == PositionMode.Fixed) data.position *= scale; - data.spacing = GetFloat(constraintMap, "spacing", 0); - if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale; - data.rotateMix = GetFloat(constraintMap, "rotateMix", 1); - data.translateMix = GetFloat(constraintMap, "translateMix", 1); - - skeletonData.pathConstraints.Add(data); - } - } - - // Skins. - if (root.ContainsKey("skins")) { - foreach (KeyValuePair skinMap in (Dictionary)root["skins"]) { - var skin = new Skin(skinMap.Key); - foreach (KeyValuePair slotEntry in (Dictionary)skinMap.Value) { - int slotIndex = skeletonData.FindSlotIndex(slotEntry.Key); - foreach (KeyValuePair entry in ((Dictionary)slotEntry.Value)) { - try { - Attachment attachment = ReadAttachment((Dictionary)entry.Value, skin, slotIndex, entry.Key, skeletonData); - if (attachment != null) skin.AddAttachment(slotIndex, entry.Key, attachment); - } catch (Exception e) { - throw new Exception("Error reading attachment: " + entry.Key + ", skin: " + skin, e); - } - } - } - skeletonData.skins.Add(skin); - if (skin.name == "default") skeletonData.defaultSkin = skin; - } - } - - // Linked meshes. - for (int i = 0, n = linkedMeshes.Count; i < n; i++) { - LinkedMesh linkedMesh = linkedMeshes[i]; - Skin skin = linkedMesh.skin == null ? skeletonData.defaultSkin : skeletonData.FindSkin(linkedMesh.skin); - if (skin == null) throw new Exception("Slot not found: " + linkedMesh.skin); - Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); - if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); - linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; - linkedMesh.mesh.UpdateUVs(); - } - linkedMeshes.Clear(); - - // Events. - if (root.ContainsKey("events")) { - foreach (KeyValuePair entry in (Dictionary)root["events"]) { - var entryMap = (Dictionary)entry.Value; - var data = new EventData(entry.Key); - data.Int = GetInt(entryMap, "int", 0); - data.Float = GetFloat(entryMap, "float", 0); - data.String = GetString(entryMap, "string", string.Empty); - data.AudioPath = GetString(entryMap, "audio", null); - if (data.AudioPath != null) { - data.Volume = GetFloat(entryMap, "volume", 1); - data.Balance = GetFloat(entryMap, "balance", 0); - } - skeletonData.events.Add(data); - } - } - - // Animations. - if (root.ContainsKey("animations")) { - foreach (KeyValuePair entry in (Dictionary)root["animations"]) { - try { - ReadAnimation((Dictionary)entry.Value, entry.Key, skeletonData); - } catch (Exception e) { - throw new Exception("Error reading animation: " + entry.Key, e); - } - } - } - - skeletonData.bones.TrimExcess(); - skeletonData.slots.TrimExcess(); - skeletonData.skins.TrimExcess(); - skeletonData.events.TrimExcess(); - skeletonData.animations.TrimExcess(); - skeletonData.ikConstraints.TrimExcess(); - return skeletonData; - } - - private Attachment ReadAttachment (Dictionary map, Skin skin, int slotIndex, string name, SkeletonData skeletonData) { - float scale = this.Scale; - name = GetString(map, "name", name); - - var typeName = GetString(map, "type", "region"); - if (typeName == "skinnedmesh") typeName = "weightedmesh"; - if (typeName == "weightedmesh") typeName = "mesh"; - if (typeName == "weightedlinkedmesh") typeName = "linkedmesh"; - var type = (AttachmentType)Enum.Parse(typeof(AttachmentType), typeName, true); - - string path = GetString(map, "path", name); - - switch (type) { - case AttachmentType.Region: - RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); - if (region == null) return null; - region.Path = path; - region.x = GetFloat(map, "x", 0) * scale; - region.y = GetFloat(map, "y", 0) * scale; - region.scaleX = GetFloat(map, "scaleX", 1); - region.scaleY = GetFloat(map, "scaleY", 1); - region.rotation = GetFloat(map, "rotation", 0); - region.width = GetFloat(map, "width", 32) * scale; - region.height = GetFloat(map, "height", 32) * scale; - - if (map.ContainsKey("color")) { - var color = (string)map["color"]; - region.r = ToColor(color, 0); - region.g = ToColor(color, 1); - region.b = ToColor(color, 2); - region.a = ToColor(color, 3); - } - - region.UpdateOffset(); - return region; - case AttachmentType.Boundingbox: - BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); - if (box == null) return null; - ReadVertices(map, box, GetInt(map, "vertexCount", 0) << 1); - return box; - case AttachmentType.Mesh: - case AttachmentType.Linkedmesh: { - MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); - if (mesh == null) return null; - mesh.Path = path; - - if (map.ContainsKey("color")) { - var color = (string)map["color"]; - mesh.r = ToColor(color, 0); - mesh.g = ToColor(color, 1); - mesh.b = ToColor(color, 2); - mesh.a = ToColor(color, 3); - } - - mesh.Width = GetFloat(map, "width", 0) * scale; - mesh.Height = GetFloat(map, "height", 0) * scale; - - string parent = GetString(map, "parent", null); - if (parent != null) { - mesh.InheritDeform = GetBoolean(map, "deform", true); - linkedMeshes.Add(new LinkedMesh(mesh, GetString(map, "skin", null), slotIndex, parent)); - return mesh; - } - - float[] uvs = GetFloatArray(map, "uvs", 1); - ReadVertices(map, mesh, uvs.Length); - mesh.triangles = GetIntArray(map, "triangles"); - mesh.regionUVs = uvs; - mesh.UpdateUVs(); - - if (map.ContainsKey("hull")) mesh.HullLength = GetInt(map, "hull", 0) * 2; - if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges"); - return mesh; - } - case AttachmentType.Path: { - PathAttachment pathAttachment = attachmentLoader.NewPathAttachment(skin, name); - if (pathAttachment == null) return null; - pathAttachment.closed = GetBoolean(map, "closed", false); - pathAttachment.constantSpeed = GetBoolean(map, "constantSpeed", true); - - int vertexCount = GetInt(map, "vertexCount", 0); - ReadVertices(map, pathAttachment, vertexCount << 1); - - // potential BOZO see Java impl - pathAttachment.lengths = GetFloatArray(map, "lengths", scale); - return pathAttachment; - } - case AttachmentType.Point: { - PointAttachment point = attachmentLoader.NewPointAttachment(skin, name); - if (point == null) return null; - point.x = GetFloat(map, "x", 0) * scale; - point.y = GetFloat(map, "y", 0) * scale; - point.rotation = GetFloat(map, "rotation", 0); - - //string color = GetString(map, "color", null); - //if (color != null) point.color = color; - return point; - } - case AttachmentType.Clipping: { - ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name); - if (clip == null) return null; - - string end = GetString(map, "end", null); - if (end != null) { - SlotData slot = skeletonData.FindSlot(end); - if (slot == null) throw new Exception("Clipping end slot not found: " + end); - clip.EndSlot = slot; - } - - ReadVertices(map, clip, GetInt(map, "vertexCount", 0) << 1); - - //string color = GetString(map, "color", null); - // if (color != null) clip.color = color; - return clip; - } - } - return null; - } - - private void ReadVertices (Dictionary map, VertexAttachment attachment, int verticesLength) { - attachment.WorldVerticesLength = verticesLength; - float[] vertices = GetFloatArray(map, "vertices", 1); - float scale = Scale; - if (verticesLength == vertices.Length) { - if (scale != 1) { - for (int i = 0; i < vertices.Length; i++) { - vertices[i] *= scale; - } - } - attachment.vertices = vertices; - return; - } - ExposedList weights = new ExposedList(verticesLength * 3 * 3); - ExposedList bones = new ExposedList(verticesLength * 3); - for (int i = 0, n = vertices.Length; i < n;) { - int boneCount = (int)vertices[i++]; - bones.Add(boneCount); - for (int nn = i + boneCount * 4; i < nn; i += 4) { - bones.Add((int)vertices[i]); - weights.Add(vertices[i + 1] * this.Scale); - weights.Add(vertices[i + 2] * this.Scale); - weights.Add(vertices[i + 3]); - } - } - attachment.bones = bones.ToArray(); - attachment.vertices = weights.ToArray(); - } - - private void ReadAnimation (Dictionary map, string name, SkeletonData skeletonData) { - var scale = this.Scale; - var timelines = new ExposedList(); - float duration = 0; - - // Slot timelines. - if (map.ContainsKey("slots")) { - foreach (KeyValuePair entry in (Dictionary)map["slots"]) { - string slotName = entry.Key; - int slotIndex = skeletonData.FindSlotIndex(slotName); - var timelineMap = (Dictionary)entry.Value; - foreach (KeyValuePair timelineEntry in timelineMap) { - var values = (List)timelineEntry.Value; - var timelineName = (string)timelineEntry.Key; - if (timelineName == "attachment") { - var timeline = new AttachmentTimeline(values.Count); - timeline.slotIndex = slotIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - timeline.SetFrame(frameIndex++, time, (string)valueMap["name"]); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); - - } else if (timelineName == "color") { - var timeline = new ColorTimeline(values.Count); - timeline.slotIndex = slotIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - string c = (string)valueMap["color"]; - timeline.SetFrame(frameIndex, time, ToColor(c, 0), ToColor(c, 1), ToColor(c, 2), ToColor(c, 3)); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * ColorTimeline.ENTRIES]); - - } else if (timelineName == "twoColor") { - var timeline = new TwoColorTimeline(values.Count); - timeline.slotIndex = slotIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - string light = (string)valueMap["light"]; - string dark = (string)valueMap["dark"]; - timeline.SetFrame(frameIndex, time, ToColor(light, 0), ToColor(light, 1), ToColor(light, 2), ToColor(light, 3), - ToColor(dark, 0, 6), ToColor(dark, 1, 6), ToColor(dark, 2, 6)); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TwoColorTimeline.ENTRIES]); - - } else - throw new Exception("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")"); - } - } - } - - // Bone timelines. - if (map.ContainsKey("bones")) { - foreach (KeyValuePair entry in (Dictionary)map["bones"]) { - string boneName = entry.Key; - int boneIndex = skeletonData.FindBoneIndex(boneName); - if (boneIndex == -1) throw new Exception("Bone not found: " + boneName); - var timelineMap = (Dictionary)entry.Value; - foreach (KeyValuePair timelineEntry in timelineMap) { - var values = (List)timelineEntry.Value; - var timelineName = (string)timelineEntry.Key; - if (timelineName == "rotate") { - var timeline = new RotateTimeline(values.Count); - timeline.boneIndex = boneIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - timeline.SetFrame(frameIndex, (float)valueMap["time"], (float)valueMap["angle"]); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * RotateTimeline.ENTRIES]); - - } else if (timelineName == "translate" || timelineName == "scale" || timelineName == "shear") { - TranslateTimeline timeline; - float timelineScale = 1; - if (timelineName == "scale") - timeline = new ScaleTimeline(values.Count); - else if (timelineName == "shear") - timeline = new ShearTimeline(values.Count); - else { - timeline = new TranslateTimeline(values.Count); - timelineScale = scale; - } - timeline.boneIndex = boneIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - float x = GetFloat(valueMap, "x", 0); - float y = GetFloat(valueMap, "y", 0); - timeline.SetFrame(frameIndex, time, x * timelineScale, y * timelineScale); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TranslateTimeline.ENTRIES]); - - } else - throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")"); - } - } - } - - // IK constraint timelines. - if (map.ContainsKey("ik")) { - foreach (KeyValuePair constraintMap in (Dictionary)map["ik"]) { - IkConstraintData constraint = skeletonData.FindIkConstraint(constraintMap.Key); - var values = (List)constraintMap.Value; - var timeline = new IkConstraintTimeline(values.Count); - timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(constraint); - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - timeline.SetFrame( - frameIndex, - (float)valueMap["time"], - GetFloat(valueMap, "mix", 1), - GetBoolean(valueMap, "bendPositive", true) ? 1 : -1, - GetBoolean(valueMap, "compress", true), - GetBoolean(valueMap, "stretch", false) - ); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * IkConstraintTimeline.ENTRIES]); - } - } - - // Transform constraint timelines. - if (map.ContainsKey("transform")) { - foreach (KeyValuePair constraintMap in (Dictionary)map["transform"]) { - TransformConstraintData constraint = skeletonData.FindTransformConstraint(constraintMap.Key); - var values = (List)constraintMap.Value; - var timeline = new TransformConstraintTimeline(values.Count); - timeline.transformConstraintIndex = skeletonData.transformConstraints.IndexOf(constraint); - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - float rotateMix = GetFloat(valueMap, "rotateMix", 1); - float translateMix = GetFloat(valueMap, "translateMix", 1); - float scaleMix = GetFloat(valueMap, "scaleMix", 1); - float shearMix = GetFloat(valueMap, "shearMix", 1); - timeline.SetFrame(frameIndex, time, rotateMix, translateMix, scaleMix, shearMix); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TransformConstraintTimeline.ENTRIES]); - } - } - - // Path constraint timelines. - if (map.ContainsKey("paths")) { - foreach (KeyValuePair constraintMap in (Dictionary)map["paths"]) { - int index = skeletonData.FindPathConstraintIndex(constraintMap.Key); - if (index == -1) throw new Exception("Path constraint not found: " + constraintMap.Key); - PathConstraintData data = skeletonData.pathConstraints.Items[index]; - var timelineMap = (Dictionary)constraintMap.Value; - foreach (KeyValuePair timelineEntry in timelineMap) { - var values = (List)timelineEntry.Value; - var timelineName = (string)timelineEntry.Key; - if (timelineName == "position" || timelineName == "spacing") { - PathConstraintPositionTimeline timeline; - float timelineScale = 1; - if (timelineName == "spacing") { - timeline = new PathConstraintSpacingTimeline(values.Count); - if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale; - } - else { - timeline = new PathConstraintPositionTimeline(values.Count); - if (data.positionMode == PositionMode.Fixed) timelineScale = scale; - } - timeline.pathConstraintIndex = index; - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - timeline.SetFrame(frameIndex, (float)valueMap["time"], GetFloat(valueMap, timelineName, 0) * timelineScale); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintPositionTimeline.ENTRIES]); - } - else if (timelineName == "mix") { - PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(values.Count); - timeline.pathConstraintIndex = index; - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - timeline.SetFrame(frameIndex, (float)valueMap["time"], GetFloat(valueMap, "rotateMix", 1), GetFloat(valueMap, "translateMix", 1)); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintMixTimeline.ENTRIES]); - } - } - } - } - - // Deform timelines. - if (map.ContainsKey("deform")) { - foreach (KeyValuePair deformMap in (Dictionary)map["deform"]) { - Skin skin = skeletonData.FindSkin(deformMap.Key); - foreach (KeyValuePair slotMap in (Dictionary)deformMap.Value) { - int slotIndex = skeletonData.FindSlotIndex(slotMap.Key); - if (slotIndex == -1) throw new Exception("Slot not found: " + slotMap.Key); - foreach (KeyValuePair timelineMap in (Dictionary)slotMap.Value) { - var values = (List)timelineMap.Value; - VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, timelineMap.Key); - if (attachment == null) throw new Exception("Deform attachment not found: " + timelineMap.Key); - bool weighted = attachment.bones != null; - float[] vertices = attachment.vertices; - int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length; - - var timeline = new DeformTimeline(values.Count); - timeline.slotIndex = slotIndex; - timeline.attachment = attachment; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float[] deform; - if (!valueMap.ContainsKey("vertices")) { - deform = weighted ? new float[deformLength] : vertices; - } else { - deform = new float[deformLength]; - int start = GetInt(valueMap, "offset", 0); - float[] verticesValue = GetFloatArray(valueMap, "vertices", 1); - Array.Copy(verticesValue, 0, deform, start, verticesValue.Length); - if (scale != 1) { - for (int i = start, n = i + verticesValue.Length; i < n; i++) - deform[i] *= scale; - } - - if (!weighted) { - for (int i = 0; i < deformLength; i++) - deform[i] += vertices[i]; - } - } - - timeline.SetFrame(frameIndex, (float)valueMap["time"], deform); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); - } - } - } - } - - // Draw order timeline. - if (map.ContainsKey("drawOrder") || map.ContainsKey("draworder")) { - var values = (List)map[map.ContainsKey("drawOrder") ? "drawOrder" : "draworder"]; - var timeline = new DrawOrderTimeline(values.Count); - int slotCount = skeletonData.slots.Count; - int frameIndex = 0; - foreach (Dictionary drawOrderMap in values) { - int[] drawOrder = null; - if (drawOrderMap.ContainsKey("offsets")) { - drawOrder = new int[slotCount]; - for (int i = slotCount - 1; i >= 0; i--) - drawOrder[i] = -1; - var offsets = (List)drawOrderMap["offsets"]; - int[] unchanged = new int[slotCount - offsets.Count]; - int originalIndex = 0, unchangedIndex = 0; - foreach (Dictionary offsetMap in offsets) { - int slotIndex = skeletonData.FindSlotIndex((string)offsetMap["slot"]); - if (slotIndex == -1) throw new Exception("Slot not found: " + offsetMap["slot"]); - // Collect unchanged items. - while (originalIndex != slotIndex) - unchanged[unchangedIndex++] = originalIndex++; - // Set changed items. - int index = originalIndex + (int)(float)offsetMap["offset"]; - drawOrder[index] = originalIndex++; - } - // Collect remaining unchanged items. - while (originalIndex < slotCount) - unchanged[unchangedIndex++] = originalIndex++; - // Fill in unchanged items. - for (int i = slotCount - 1; i >= 0; i--) - if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex]; - } - timeline.SetFrame(frameIndex++, (float)drawOrderMap["time"], drawOrder); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); - } - - // Event timeline. - if (map.ContainsKey("events")) { - var eventsMap = (List)map["events"]; - var timeline = new EventTimeline(eventsMap.Count); - int frameIndex = 0; - foreach (Dictionary eventMap in eventsMap) { - EventData eventData = skeletonData.FindEvent((string)eventMap["name"]); - if (eventData == null) throw new Exception("Event not found: " + eventMap["name"]); - var e = new Event((float)eventMap["time"], eventData) { - intValue = GetInt(eventMap, "int", eventData.Int), - floatValue = GetFloat(eventMap, "float", eventData.Float), - stringValue = GetString(eventMap, "string", eventData.String) - }; - if (e.data.AudioPath != null) { - e.volume = GetFloat(eventMap, "volume", eventData.Volume); - e.balance = GetFloat(eventMap, "balance", eventData.Balance); - } - timeline.SetFrame(frameIndex++, e); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); - } - - timelines.TrimExcess(); - skeletonData.animations.Add(new Animation(name, timelines, duration)); - } - - static void ReadCurve (Dictionary valueMap, CurveTimeline timeline, int frameIndex) { - if (!valueMap.ContainsKey("curve")) - return; - Object curveObject = valueMap["curve"]; - if (curveObject.Equals("stepped")) - timeline.SetStepped(frameIndex); - else { - var curve = curveObject as List; - if (curve != null) - timeline.SetCurve(frameIndex, (float)curve[0], (float)curve[1], (float)curve[2], (float)curve[3]); - } - } - - internal class LinkedMesh { - internal string parent, skin; - internal int slotIndex; - internal MeshAttachment mesh; - - public LinkedMesh (MeshAttachment mesh, string skin, int slotIndex, string parent) { - this.mesh = mesh; - this.skin = skin; - this.slotIndex = slotIndex; - this.parent = parent; - } - } - - static float[] GetFloatArray(Dictionary map, string name, float scale) { - var list = (List)map[name]; - var values = new float[list.Count]; - if (scale == 1) { - for (int i = 0, n = list.Count; i < n; i++) - values[i] = (float)list[i]; - } else { - for (int i = 0, n = list.Count; i < n; i++) - values[i] = (float)list[i] * scale; - } - return values; - } - - static int[] GetIntArray(Dictionary map, string name) { - var list = (List)map[name]; - var values = new int[list.Count]; - for (int i = 0, n = list.Count; i < n; i++) - values[i] = (int)(float)list[i]; - return values; - } - - static float GetFloat(Dictionary map, string name, float defaultValue) { - if (!map.ContainsKey(name)) - return defaultValue; - return (float)map[name]; - } - - static int GetInt(Dictionary map, string name, int defaultValue) { - if (!map.ContainsKey(name)) - return defaultValue; - return (int)(float)map[name]; - } - - static bool GetBoolean(Dictionary map, string name, bool defaultValue) { - if (!map.ContainsKey(name)) - return defaultValue; - return (bool)map[name]; - } - - static string GetString(Dictionary map, string name, string defaultValue) { - if (!map.ContainsKey(name)) - return defaultValue; - return (string)map[name]; - } +#else + using (var reader = new StreamReader(new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))) + { +#endif + SkeletonData skeletonData = ReadSkeletonData(reader); + skeletonData.name = Path.GetFileNameWithoutExtension(path); + return skeletonData; + } + } +#endif - static float ToColor(string hexString, int colorIndex, int expectedLength = 8) { - if (hexString.Length != expectedLength) - throw new ArgumentException("Color hexidecimal length must be " + expectedLength + ", recieved: " + hexString, "hexString"); - return Convert.ToInt32(hexString.Substring(colorIndex * 2, 2), 16) / (float)255; - } - } + public SkeletonData ReadSkeletonData(TextReader reader) + { + if (reader == null) throw new ArgumentNullException("reader", "reader cannot be null."); + + float scale = this.Scale; + var skeletonData = new SkeletonData(); + + var root = Json.Deserialize(reader) as Dictionary; + if (root == null) throw new Exception("Invalid JSON."); + + // Skeleton. + if (root.ContainsKey("skeleton")) + { + var skeletonMap = (Dictionary)root["skeleton"]; + skeletonData.hash = (string)skeletonMap["hash"]; + skeletonData.version = (string)skeletonMap["spine"]; + skeletonData.width = GetFloat(skeletonMap, "width", 0); + skeletonData.height = GetFloat(skeletonMap, "height", 0); + skeletonData.fps = GetFloat(skeletonMap, "fps", 0); + skeletonData.imagesPath = GetString(skeletonMap, "images", null); + skeletonData.audioPath = GetString(skeletonMap, "audio", null); + } + + // Bones. + foreach (Dictionary boneMap in (List)root["bones"]) + { + BoneData parent = null; + if (boneMap.ContainsKey("parent")) + { + parent = skeletonData.FindBone((string)boneMap["parent"]); + if (parent == null) + throw new Exception("Parent bone not found: " + boneMap["parent"]); + } + var data = new BoneData(skeletonData.Bones.Count, (string)boneMap["name"], parent); + data.length = GetFloat(boneMap, "length", 0) * scale; + data.x = GetFloat(boneMap, "x", 0) * scale; + data.y = GetFloat(boneMap, "y", 0) * scale; + data.rotation = GetFloat(boneMap, "rotation", 0); + data.scaleX = GetFloat(boneMap, "scaleX", 1); + data.scaleY = GetFloat(boneMap, "scaleY", 1); + data.shearX = GetFloat(boneMap, "shearX", 0); + data.shearY = GetFloat(boneMap, "shearY", 0); + + string tm = GetString(boneMap, "transform", TransformMode.Normal.ToString()); + data.transformMode = (TransformMode)Enum.Parse(typeof(TransformMode), tm, true); + + skeletonData.bones.Add(data); + } + + // Slots. + if (root.ContainsKey("slots")) + { + foreach (Dictionary slotMap in (List)root["slots"]) + { + var slotName = (string)slotMap["name"]; + var boneName = (string)slotMap["bone"]; + BoneData boneData = skeletonData.FindBone(boneName); + if (boneData == null) throw new Exception("Slot bone not found: " + boneName); + var data = new SlotData(skeletonData.Slots.Count, slotName, boneData); + + if (slotMap.ContainsKey("color")) + { + string color = (string)slotMap["color"]; + data.r = ToColor(color, 0); + data.g = ToColor(color, 1); + data.b = ToColor(color, 2); + data.a = ToColor(color, 3); + } + + if (slotMap.ContainsKey("dark")) + { + var color2 = (string)slotMap["dark"]; + data.r2 = ToColor(color2, 0, 6); // expectedLength = 6. ie. "RRGGBB" + data.g2 = ToColor(color2, 1, 6); + data.b2 = ToColor(color2, 2, 6); + data.hasSecondColor = true; + } + + data.attachmentName = GetString(slotMap, "attachment", null); + if (slotMap.ContainsKey("blend")) + data.blendMode = (BlendMode)Enum.Parse(typeof(BlendMode), (string)slotMap["blend"], true); + else + data.blendMode = BlendMode.Normal; + skeletonData.slots.Add(data); + } + } + + // IK constraints. + if (root.ContainsKey("ik")) + { + foreach (Dictionary constraintMap in (List)root["ik"]) + { + IkConstraintData data = new IkConstraintData((string)constraintMap["name"]); + data.order = GetInt(constraintMap, "order", 0); + + foreach (string boneName in (List)constraintMap["bones"]) + { + BoneData bone = skeletonData.FindBone(boneName); + if (bone == null) throw new Exception("IK constraint bone not found: " + boneName); + data.bones.Add(bone); + } + + string targetName = (string)constraintMap["target"]; + data.target = skeletonData.FindBone(targetName); + if (data.target == null) throw new Exception("Target bone not found: " + targetName); + data.mix = GetFloat(constraintMap, "mix", 1); + data.bendDirection = GetBoolean(constraintMap, "bendPositive", true) ? 1 : -1; + data.compress = GetBoolean(constraintMap, "compress", false); + data.stretch = GetBoolean(constraintMap, "stretch", false); + data.uniform = GetBoolean(constraintMap, "uniform", false); + + skeletonData.ikConstraints.Add(data); + } + } + + // Transform constraints. + if (root.ContainsKey("transform")) + { + foreach (Dictionary constraintMap in (List)root["transform"]) + { + TransformConstraintData data = new TransformConstraintData((string)constraintMap["name"]); + data.order = GetInt(constraintMap, "order", 0); + + foreach (string boneName in (List)constraintMap["bones"]) + { + BoneData bone = skeletonData.FindBone(boneName); + if (bone == null) throw new Exception("Transform constraint bone not found: " + boneName); + data.bones.Add(bone); + } + + string targetName = (string)constraintMap["target"]; + data.target = skeletonData.FindBone(targetName); + if (data.target == null) throw new Exception("Target bone not found: " + targetName); + + data.local = GetBoolean(constraintMap, "local", false); + data.relative = GetBoolean(constraintMap, "relative", false); + + data.offsetRotation = GetFloat(constraintMap, "rotation", 0); + data.offsetX = GetFloat(constraintMap, "x", 0) * scale; + data.offsetY = GetFloat(constraintMap, "y", 0) * scale; + data.offsetScaleX = GetFloat(constraintMap, "scaleX", 0); + data.offsetScaleY = GetFloat(constraintMap, "scaleY", 0); + data.offsetShearY = GetFloat(constraintMap, "shearY", 0); + + data.rotateMix = GetFloat(constraintMap, "rotateMix", 1); + data.translateMix = GetFloat(constraintMap, "translateMix", 1); + data.scaleMix = GetFloat(constraintMap, "scaleMix", 1); + data.shearMix = GetFloat(constraintMap, "shearMix", 1); + + skeletonData.transformConstraints.Add(data); + } + } + + // Path constraints. + if (root.ContainsKey("path")) + { + foreach (Dictionary constraintMap in (List)root["path"]) + { + PathConstraintData data = new PathConstraintData((string)constraintMap["name"]); + data.order = GetInt(constraintMap, "order", 0); + + foreach (string boneName in (List)constraintMap["bones"]) + { + BoneData bone = skeletonData.FindBone(boneName); + if (bone == null) throw new Exception("Path bone not found: " + boneName); + data.bones.Add(bone); + } + + string targetName = (string)constraintMap["target"]; + data.target = skeletonData.FindSlot(targetName); + if (data.target == null) throw new Exception("Target slot not found: " + targetName); + + data.positionMode = (PositionMode)Enum.Parse(typeof(PositionMode), GetString(constraintMap, "positionMode", "percent"), true); + data.spacingMode = (SpacingMode)Enum.Parse(typeof(SpacingMode), GetString(constraintMap, "spacingMode", "length"), true); + data.rotateMode = (RotateMode)Enum.Parse(typeof(RotateMode), GetString(constraintMap, "rotateMode", "tangent"), true); + data.offsetRotation = GetFloat(constraintMap, "rotation", 0); + data.position = GetFloat(constraintMap, "position", 0); + if (data.positionMode == PositionMode.Fixed) data.position *= scale; + data.spacing = GetFloat(constraintMap, "spacing", 0); + if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale; + data.rotateMix = GetFloat(constraintMap, "rotateMix", 1); + data.translateMix = GetFloat(constraintMap, "translateMix", 1); + + skeletonData.pathConstraints.Add(data); + } + } + + // Skins. + if (root.ContainsKey("skins")) + { + foreach (KeyValuePair skinMap in (Dictionary)root["skins"]) + { + var skin = new Skin(skinMap.Key); + foreach (KeyValuePair slotEntry in (Dictionary)skinMap.Value) + { + int slotIndex = skeletonData.FindSlotIndex(slotEntry.Key); + foreach (KeyValuePair entry in ((Dictionary)slotEntry.Value)) + { + try + { + Attachment attachment = ReadAttachment((Dictionary)entry.Value, skin, slotIndex, entry.Key, skeletonData); + if (attachment != null) skin.AddAttachment(slotIndex, entry.Key, attachment); + } + catch (Exception e) + { + throw new Exception("Error reading attachment: " + entry.Key + ", skin: " + skin, e); + } + } + } + skeletonData.skins.Add(skin); + if (skin.name == "default") skeletonData.defaultSkin = skin; + } + } + + // Linked meshes. + for (int i = 0, n = linkedMeshes.Count; i < n; i++) + { + LinkedMesh linkedMesh = linkedMeshes[i]; + Skin skin = linkedMesh.skin == null ? skeletonData.defaultSkin : skeletonData.FindSkin(linkedMesh.skin); + if (skin == null) throw new Exception("Slot not found: " + linkedMesh.skin); + Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); + if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); + linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; + linkedMesh.mesh.UpdateUVs(); + } + linkedMeshes.Clear(); + + // Events. + if (root.ContainsKey("events")) + { + foreach (KeyValuePair entry in (Dictionary)root["events"]) + { + var entryMap = (Dictionary)entry.Value; + var data = new EventData(entry.Key); + data.Int = GetInt(entryMap, "int", 0); + data.Float = GetFloat(entryMap, "float", 0); + data.String = GetString(entryMap, "string", string.Empty); + data.AudioPath = GetString(entryMap, "audio", null); + if (data.AudioPath != null) + { + data.Volume = GetFloat(entryMap, "volume", 1); + data.Balance = GetFloat(entryMap, "balance", 0); + } + skeletonData.events.Add(data); + } + } + + // Animations. + if (root.ContainsKey("animations")) + { + foreach (KeyValuePair entry in (Dictionary)root["animations"]) + { + try + { + ReadAnimation((Dictionary)entry.Value, entry.Key, skeletonData); + } + catch (Exception e) + { + throw new Exception("Error reading animation: " + entry.Key, e); + } + } + } + + skeletonData.bones.TrimExcess(); + skeletonData.slots.TrimExcess(); + skeletonData.skins.TrimExcess(); + skeletonData.events.TrimExcess(); + skeletonData.animations.TrimExcess(); + skeletonData.ikConstraints.TrimExcess(); + return skeletonData; + } + + private Attachment ReadAttachment(Dictionary map, Skin skin, int slotIndex, string name, SkeletonData skeletonData) + { + float scale = this.Scale; + name = GetString(map, "name", name); + + var typeName = GetString(map, "type", "region"); + if (typeName == "skinnedmesh") typeName = "weightedmesh"; + if (typeName == "weightedmesh") typeName = "mesh"; + if (typeName == "weightedlinkedmesh") typeName = "linkedmesh"; + var type = (AttachmentType)Enum.Parse(typeof(AttachmentType), typeName, true); + + string path = GetString(map, "path", name); + + switch (type) + { + case AttachmentType.Region: + RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); + if (region == null) return null; + region.Path = path; + region.x = GetFloat(map, "x", 0) * scale; + region.y = GetFloat(map, "y", 0) * scale; + region.scaleX = GetFloat(map, "scaleX", 1); + region.scaleY = GetFloat(map, "scaleY", 1); + region.rotation = GetFloat(map, "rotation", 0); + region.width = GetFloat(map, "width", 32) * scale; + region.height = GetFloat(map, "height", 32) * scale; + + if (map.ContainsKey("color")) + { + var color = (string)map["color"]; + region.r = ToColor(color, 0); + region.g = ToColor(color, 1); + region.b = ToColor(color, 2); + region.a = ToColor(color, 3); + } + + region.UpdateOffset(); + return region; + case AttachmentType.Boundingbox: + BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); + if (box == null) return null; + ReadVertices(map, box, GetInt(map, "vertexCount", 0) << 1); + return box; + case AttachmentType.Mesh: + case AttachmentType.Linkedmesh: + { + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); + if (mesh == null) return null; + mesh.Path = path; + + if (map.ContainsKey("color")) + { + var color = (string)map["color"]; + mesh.r = ToColor(color, 0); + mesh.g = ToColor(color, 1); + mesh.b = ToColor(color, 2); + mesh.a = ToColor(color, 3); + } + + mesh.Width = GetFloat(map, "width", 0) * scale; + mesh.Height = GetFloat(map, "height", 0) * scale; + + string parent = GetString(map, "parent", null); + if (parent != null) + { + mesh.InheritDeform = GetBoolean(map, "deform", true); + linkedMeshes.Add(new LinkedMesh(mesh, GetString(map, "skin", null), slotIndex, parent)); + return mesh; + } + + float[] uvs = GetFloatArray(map, "uvs", 1); + ReadVertices(map, mesh, uvs.Length); + mesh.triangles = GetIntArray(map, "triangles"); + mesh.regionUVs = uvs; + mesh.UpdateUVs(); + + if (map.ContainsKey("hull")) mesh.HullLength = GetInt(map, "hull", 0) * 2; + if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges"); + return mesh; + } + case AttachmentType.Path: + { + PathAttachment pathAttachment = attachmentLoader.NewPathAttachment(skin, name); + if (pathAttachment == null) return null; + pathAttachment.closed = GetBoolean(map, "closed", false); + pathAttachment.constantSpeed = GetBoolean(map, "constantSpeed", true); + + int vertexCount = GetInt(map, "vertexCount", 0); + ReadVertices(map, pathAttachment, vertexCount << 1); + + // potential BOZO see Java impl + pathAttachment.lengths = GetFloatArray(map, "lengths", scale); + return pathAttachment; + } + case AttachmentType.Point: + { + PointAttachment point = attachmentLoader.NewPointAttachment(skin, name); + if (point == null) return null; + point.x = GetFloat(map, "x", 0) * scale; + point.y = GetFloat(map, "y", 0) * scale; + point.rotation = GetFloat(map, "rotation", 0); + + //string color = GetString(map, "color", null); + //if (color != null) point.color = color; + return point; + } + case AttachmentType.Clipping: + { + ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name); + if (clip == null) return null; + + string end = GetString(map, "end", null); + if (end != null) + { + SlotData slot = skeletonData.FindSlot(end); + if (slot == null) throw new Exception("Clipping end slot not found: " + end); + clip.EndSlot = slot; + } + + ReadVertices(map, clip, GetInt(map, "vertexCount", 0) << 1); + + //string color = GetString(map, "color", null); + // if (color != null) clip.color = color; + return clip; + } + } + return null; + } + + private void ReadVertices(Dictionary map, VertexAttachment attachment, int verticesLength) + { + attachment.WorldVerticesLength = verticesLength; + float[] vertices = GetFloatArray(map, "vertices", 1); + float scale = Scale; + if (verticesLength == vertices.Length) + { + if (scale != 1) + { + for (int i = 0; i < vertices.Length; i++) + { + vertices[i] *= scale; + } + } + attachment.vertices = vertices; + return; + } + ExposedList weights = new ExposedList(verticesLength * 3 * 3); + ExposedList bones = new ExposedList(verticesLength * 3); + for (int i = 0, n = vertices.Length; i < n;) + { + int boneCount = (int)vertices[i++]; + bones.Add(boneCount); + for (int nn = i + boneCount * 4; i < nn; i += 4) + { + bones.Add((int)vertices[i]); + weights.Add(vertices[i + 1] * this.Scale); + weights.Add(vertices[i + 2] * this.Scale); + weights.Add(vertices[i + 3]); + } + } + attachment.bones = bones.ToArray(); + attachment.vertices = weights.ToArray(); + } + + private void ReadAnimation(Dictionary map, string name, SkeletonData skeletonData) + { + var scale = this.Scale; + var timelines = new ExposedList(); + float duration = 0; + + // Slot timelines. + if (map.ContainsKey("slots")) + { + foreach (KeyValuePair entry in (Dictionary)map["slots"]) + { + string slotName = entry.Key; + int slotIndex = skeletonData.FindSlotIndex(slotName); + var timelineMap = (Dictionary)entry.Value; + foreach (KeyValuePair timelineEntry in timelineMap) + { + var values = (List)timelineEntry.Value; + var timelineName = timelineEntry.Key; + if (timelineName == "attachment") + { + var timeline = new AttachmentTimeline(values.Count); + timeline.slotIndex = slotIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + timeline.SetFrame(frameIndex++, time, (string)valueMap["name"]); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); + + } + else if (timelineName == "color") + { + var timeline = new ColorTimeline(values.Count); + timeline.slotIndex = slotIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + string c = (string)valueMap["color"]; + timeline.SetFrame(frameIndex, time, ToColor(c, 0), ToColor(c, 1), ToColor(c, 2), ToColor(c, 3)); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * ColorTimeline.ENTRIES]); + + } + else if (timelineName == "twoColor") + { + var timeline = new TwoColorTimeline(values.Count); + timeline.slotIndex = slotIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + string light = (string)valueMap["light"]; + string dark = (string)valueMap["dark"]; + timeline.SetFrame(frameIndex, time, ToColor(light, 0), ToColor(light, 1), ToColor(light, 2), ToColor(light, 3), + ToColor(dark, 0, 6), ToColor(dark, 1, 6), ToColor(dark, 2, 6)); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TwoColorTimeline.ENTRIES]); + + } + else + throw new Exception("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")"); + } + } + } + + // Bone timelines. + if (map.ContainsKey("bones")) + { + foreach (KeyValuePair entry in (Dictionary)map["bones"]) + { + string boneName = entry.Key; + int boneIndex = skeletonData.FindBoneIndex(boneName); + if (boneIndex == -1) throw new Exception("Bone not found: " + boneName); + var timelineMap = (Dictionary)entry.Value; + foreach (KeyValuePair timelineEntry in timelineMap) + { + var values = (List)timelineEntry.Value; + var timelineName = timelineEntry.Key; + if (timelineName == "rotate") + { + var timeline = new RotateTimeline(values.Count); + timeline.boneIndex = boneIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + timeline.SetFrame(frameIndex, (float)valueMap["time"], (float)valueMap["angle"]); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * RotateTimeline.ENTRIES]); + + } + else if (timelineName == "translate" || timelineName == "scale" || timelineName == "shear") + { + TranslateTimeline timeline; + float timelineScale = 1; + if (timelineName == "scale") + timeline = new ScaleTimeline(values.Count); + else if (timelineName == "shear") + timeline = new ShearTimeline(values.Count); + else + { + timeline = new TranslateTimeline(values.Count); + timelineScale = scale; + } + timeline.boneIndex = boneIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + float x = GetFloat(valueMap, "x", 0); + float y = GetFloat(valueMap, "y", 0); + timeline.SetFrame(frameIndex, time, x * timelineScale, y * timelineScale); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TranslateTimeline.ENTRIES]); + + } + else + throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")"); + } + } + } + + // IK constraint timelines. + if (map.ContainsKey("ik")) + { + foreach (KeyValuePair constraintMap in (Dictionary)map["ik"]) + { + IkConstraintData constraint = skeletonData.FindIkConstraint(constraintMap.Key); + var values = (List)constraintMap.Value; + var timeline = new IkConstraintTimeline(values.Count); + timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(constraint); + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + timeline.SetFrame( + frameIndex, + (float)valueMap["time"], + GetFloat(valueMap, "mix", 1), + GetBoolean(valueMap, "bendPositive", true) ? 1 : -1, + GetBoolean(valueMap, "compress", true), + GetBoolean(valueMap, "stretch", false) + ); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * IkConstraintTimeline.ENTRIES]); + } + } + + // Transform constraint timelines. + if (map.ContainsKey("transform")) + { + foreach (KeyValuePair constraintMap in (Dictionary)map["transform"]) + { + TransformConstraintData constraint = skeletonData.FindTransformConstraint(constraintMap.Key); + var values = (List)constraintMap.Value; + var timeline = new TransformConstraintTimeline(values.Count); + timeline.transformConstraintIndex = skeletonData.transformConstraints.IndexOf(constraint); + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = (float)valueMap["time"]; + float rotateMix = GetFloat(valueMap, "rotateMix", 1); + float translateMix = GetFloat(valueMap, "translateMix", 1); + float scaleMix = GetFloat(valueMap, "scaleMix", 1); + float shearMix = GetFloat(valueMap, "shearMix", 1); + timeline.SetFrame(frameIndex, time, rotateMix, translateMix, scaleMix, shearMix); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TransformConstraintTimeline.ENTRIES]); + } + } + + // Path constraint timelines. + if (map.ContainsKey("paths")) + { + foreach (KeyValuePair constraintMap in (Dictionary)map["paths"]) + { + int index = skeletonData.FindPathConstraintIndex(constraintMap.Key); + if (index == -1) throw new Exception("Path constraint not found: " + constraintMap.Key); + PathConstraintData data = skeletonData.pathConstraints.Items[index]; + var timelineMap = (Dictionary)constraintMap.Value; + foreach (KeyValuePair timelineEntry in timelineMap) + { + var values = (List)timelineEntry.Value; + var timelineName = timelineEntry.Key; + if (timelineName == "position" || timelineName == "spacing") + { + PathConstraintPositionTimeline timeline; + float timelineScale = 1; + if (timelineName == "spacing") + { + timeline = new PathConstraintSpacingTimeline(values.Count); + if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale; + } + else + { + timeline = new PathConstraintPositionTimeline(values.Count); + if (data.positionMode == PositionMode.Fixed) timelineScale = scale; + } + timeline.pathConstraintIndex = index; + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + timeline.SetFrame(frameIndex, (float)valueMap["time"], GetFloat(valueMap, timelineName, 0) * timelineScale); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintPositionTimeline.ENTRIES]); + } + else if (timelineName == "mix") + { + PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(values.Count); + timeline.pathConstraintIndex = index; + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + timeline.SetFrame(frameIndex, (float)valueMap["time"], GetFloat(valueMap, "rotateMix", 1), GetFloat(valueMap, "translateMix", 1)); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintMixTimeline.ENTRIES]); + } + } + } + } + + // Deform timelines. + if (map.ContainsKey("deform")) + { + foreach (KeyValuePair deformMap in (Dictionary)map["deform"]) + { + Skin skin = skeletonData.FindSkin(deformMap.Key); + foreach (KeyValuePair slotMap in (Dictionary)deformMap.Value) + { + int slotIndex = skeletonData.FindSlotIndex(slotMap.Key); + if (slotIndex == -1) throw new Exception("Slot not found: " + slotMap.Key); + foreach (KeyValuePair timelineMap in (Dictionary)slotMap.Value) + { + var values = (List)timelineMap.Value; + VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, timelineMap.Key); + if (attachment == null) throw new Exception("Deform attachment not found: " + timelineMap.Key); + bool weighted = attachment.bones != null; + float[] vertices = attachment.vertices; + int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length; + + var timeline = new DeformTimeline(values.Count); + timeline.slotIndex = slotIndex; + timeline.attachment = attachment; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float[] deform; + if (!valueMap.ContainsKey("vertices")) + { + deform = weighted ? new float[deformLength] : vertices; + } + else + { + deform = new float[deformLength]; + int start = GetInt(valueMap, "offset", 0); + float[] verticesValue = GetFloatArray(valueMap, "vertices", 1); + Array.Copy(verticesValue, 0, deform, start, verticesValue.Length); + if (scale != 1) + { + for (int i = start, n = i + verticesValue.Length; i < n; i++) + deform[i] *= scale; + } + + if (!weighted) + { + for (int i = 0; i < deformLength; i++) + deform[i] += vertices[i]; + } + } + + timeline.SetFrame(frameIndex, (float)valueMap["time"], deform); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); + } + } + } + } + + // Draw order timeline. + if (map.ContainsKey("drawOrder") || map.ContainsKey("draworder")) + { + var values = (List)map[map.ContainsKey("drawOrder") ? "drawOrder" : "draworder"]; + var timeline = new DrawOrderTimeline(values.Count); + int slotCount = skeletonData.slots.Count; + int frameIndex = 0; + foreach (Dictionary drawOrderMap in values) + { + int[] drawOrder = null; + if (drawOrderMap.ContainsKey("offsets")) + { + drawOrder = new int[slotCount]; + for (int i = slotCount - 1; i >= 0; i--) + drawOrder[i] = -1; + var offsets = (List)drawOrderMap["offsets"]; + int[] unchanged = new int[slotCount - offsets.Count]; + int originalIndex = 0, unchangedIndex = 0; + foreach (Dictionary offsetMap in offsets) + { + int slotIndex = skeletonData.FindSlotIndex((string)offsetMap["slot"]); + if (slotIndex == -1) throw new Exception("Slot not found: " + offsetMap["slot"]); + // Collect unchanged items. + while (originalIndex != slotIndex) + unchanged[unchangedIndex++] = originalIndex++; + // Set changed items. + int index = originalIndex + (int)(float)offsetMap["offset"]; + drawOrder[index] = originalIndex++; + } + // Collect remaining unchanged items. + while (originalIndex < slotCount) + unchanged[unchangedIndex++] = originalIndex++; + // Fill in unchanged items. + for (int i = slotCount - 1; i >= 0; i--) + if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex]; + } + timeline.SetFrame(frameIndex++, (float)drawOrderMap["time"], drawOrder); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); + } + + // Event timeline. + if (map.ContainsKey("events")) + { + var eventsMap = (List)map["events"]; + var timeline = new EventTimeline(eventsMap.Count); + int frameIndex = 0; + foreach (Dictionary eventMap in eventsMap) + { + EventData eventData = skeletonData.FindEvent((string)eventMap["name"]); + if (eventData == null) throw new Exception("Event not found: " + eventMap["name"]); + var e = new Event((float)eventMap["time"], eventData) + { + intValue = GetInt(eventMap, "int", eventData.Int), + floatValue = GetFloat(eventMap, "float", eventData.Float), + stringValue = GetString(eventMap, "string", eventData.String) + }; + if (e.data.AudioPath != null) + { + e.volume = GetFloat(eventMap, "volume", eventData.Volume); + e.balance = GetFloat(eventMap, "balance", eventData.Balance); + } + timeline.SetFrame(frameIndex++, e); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); + } + + timelines.TrimExcess(); + skeletonData.animations.Add(new Animation(name, timelines, duration)); + } + + static void ReadCurve(Dictionary valueMap, CurveTimeline timeline, int frameIndex) + { + if (!valueMap.ContainsKey("curve")) + return; + Object curveObject = valueMap["curve"]; + if (curveObject.Equals("stepped")) + timeline.SetStepped(frameIndex); + else + { + var curve = curveObject as List; + if (curve != null) + timeline.SetCurve(frameIndex, (float)curve[0], (float)curve[1], (float)curve[2], (float)curve[3]); + } + } + + internal class LinkedMesh + { + internal string parent, skin; + internal int slotIndex; + internal MeshAttachment mesh; + + public LinkedMesh(MeshAttachment mesh, string skin, int slotIndex, string parent) + { + this.mesh = mesh; + this.skin = skin; + this.slotIndex = slotIndex; + this.parent = parent; + } + } + + static float[] GetFloatArray(Dictionary map, string name, float scale) + { + var list = (List)map[name]; + var values = new float[list.Count]; + if (scale == 1) + { + for (int i = 0, n = list.Count; i < n; i++) + values[i] = (float)list[i]; + } + else + { + for (int i = 0, n = list.Count; i < n; i++) + values[i] = (float)list[i] * scale; + } + return values; + } + + static int[] GetIntArray(Dictionary map, string name) + { + var list = (List)map[name]; + var values = new int[list.Count]; + for (int i = 0, n = list.Count; i < n; i++) + values[i] = (int)(float)list[i]; + return values; + } + + static float GetFloat(Dictionary map, string name, float defaultValue) + { + if (!map.ContainsKey(name)) + return defaultValue; + return (float)map[name]; + } + + static int GetInt(Dictionary map, string name, int defaultValue) + { + if (!map.ContainsKey(name)) + return defaultValue; + return (int)(float)map[name]; + } + + static bool GetBoolean(Dictionary map, string name, bool defaultValue) + { + if (!map.ContainsKey(name)) + return defaultValue; + return (bool)map[name]; + } + + static string GetString(Dictionary map, string name, string defaultValue) + { + if (!map.ContainsKey(name)) + return defaultValue; + return (string)map[name]; + } + + static float ToColor(string hexString, int colorIndex, int expectedLength = 8) + { + if (hexString.Length != expectedLength) + throw new ArgumentException("Color hexidecimal length must be " + expectedLength + ", recieved: " + hexString, "hexString"); + return Convert.ToInt32(hexString.Substring(colorIndex * 2, 2), 16) / (float)255; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Skin.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Skin.cs index 6e9b826..68905fb 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Skin.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Skin.cs @@ -30,101 +30,118 @@ using System; using System.Collections.Generic; -namespace Spine3_7_94 { - /// Stores attachments by slot index and attachment name. - /// See SkeletonData , Skeleton , and - /// Runtime skins in the Spine Runtimes Guide. - /// - public class Skin { - internal string name; - private Dictionary attachments = - new Dictionary(AttachmentKeyTupleComparer.Instance); - - public string Name { get { return name; } } - public Dictionary Attachments { get { return attachments; } } - - public Skin (string name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; - } - - /// 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. - public void AddAttachment (int slotIndex, string name, Attachment attachment) { - if (attachment == null) throw new ArgumentNullException("attachment", "attachment cannot be null."); - attachments[new AttachmentKeyTuple(slotIndex, name)] = attachment; - } - - /// Returns the attachment for the specified slot index and name, or null. - /// May be null. - public Attachment GetAttachment (int slotIndex, string name) { - Attachment attachment; - attachments.TryGetValue(new AttachmentKeyTuple(slotIndex, name), out attachment); - return attachment; - } - - /// Removes the attachment in the skin for the specified slot index and name, if any. - public void RemoveAttachment (int slotIndex, string name) { - if (slotIndex < 0) throw new ArgumentOutOfRangeException("slotIndex", "slotIndex must be >= 0"); - attachments.Remove(new AttachmentKeyTuple(slotIndex, name)); - } - - /// Finds the skin keys for a given slot. The results are added to the passed List(names). - /// The target slotIndex. To find the slot index, use or - /// Found skin key names will be added to this list. - public void FindNamesForSlot (int slotIndex, List names) { - if (names == null) throw new ArgumentNullException("names", "names cannot be null."); - foreach (AttachmentKeyTuple key in attachments.Keys) - if (key.slotIndex == slotIndex) names.Add(key.name); - } - - /// Finds the attachments for a given slot. The results are added to the passed List(Attachment). - /// The target slotIndex. To find the slot index, use or - /// Found Attachments will be added to this list. - public void FindAttachmentsForSlot (int slotIndex, List attachments) { - if (attachments == null) throw new ArgumentNullException("attachments", "attachments cannot be null."); - foreach (KeyValuePair entry in this.attachments) - if (entry.Key.slotIndex == slotIndex) attachments.Add(entry.Value); - } - - override public string ToString () { - return name; - } - - /// Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached. - internal void AttachAll (Skeleton skeleton, Skin oldSkin) { - foreach (KeyValuePair entry in oldSkin.attachments) { - int slotIndex = entry.Key.slotIndex; - Slot slot = skeleton.slots.Items[slotIndex]; - if (slot.Attachment == entry.Value) { - Attachment attachment = GetAttachment(slotIndex, entry.Key.name); - if (attachment != null) slot.Attachment = attachment; - } - } - } - - public struct AttachmentKeyTuple { - public readonly int slotIndex; - public readonly string name; - internal readonly int nameHashCode; - - public AttachmentKeyTuple (int slotIndex, string name) { - this.slotIndex = slotIndex; - this.name = name; - nameHashCode = this.name.GetHashCode(); - } - } - - // Avoids boxing in the dictionary. - class AttachmentKeyTupleComparer : IEqualityComparer { - internal static readonly AttachmentKeyTupleComparer Instance = new AttachmentKeyTupleComparer(); - - bool IEqualityComparer.Equals (AttachmentKeyTuple o1, AttachmentKeyTuple o2) { - return o1.slotIndex == o2.slotIndex && o1.nameHashCode == o2.nameHashCode && string.Equals(o1.name, o2.name, StringComparison.Ordinal); - } - - int IEqualityComparer.GetHashCode (AttachmentKeyTuple o) { - return o.slotIndex; - } - } - } +namespace Spine3_7_94 +{ + /// Stores attachments by slot index and attachment name. + /// See SkeletonData , Skeleton , and + /// Runtime skins in the Spine Runtimes Guide. + /// + public class Skin + { + internal string name; + private Dictionary attachments = + new Dictionary(AttachmentKeyTupleComparer.Instance); + + public string Name { get { return name; } } + public Dictionary Attachments { get { return attachments; } } + + public Skin(string name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + } + + /// 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. + public void AddAttachment(int slotIndex, string name, Attachment attachment) + { + if (attachment == null) throw new ArgumentNullException("attachment", "attachment cannot be null."); + attachments[new AttachmentKeyTuple(slotIndex, name)] = attachment; + } + + /// Returns the attachment for the specified slot index and name, or null. + /// May be null. + public Attachment GetAttachment(int slotIndex, string name) + { + Attachment attachment; + attachments.TryGetValue(new AttachmentKeyTuple(slotIndex, name), out attachment); + return attachment; + } + + /// Removes the attachment in the skin for the specified slot index and name, if any. + public void RemoveAttachment(int slotIndex, string name) + { + if (slotIndex < 0) throw new ArgumentOutOfRangeException("slotIndex", "slotIndex must be >= 0"); + attachments.Remove(new AttachmentKeyTuple(slotIndex, name)); + } + + /// Finds the skin keys for a given slot. The results are added to the passed List(names). + /// The target slotIndex. To find the slot index, use or + /// Found skin key names will be added to this list. + public void FindNamesForSlot(int slotIndex, List names) + { + if (names == null) throw new ArgumentNullException("names", "names cannot be null."); + foreach (AttachmentKeyTuple key in attachments.Keys) + if (key.slotIndex == slotIndex) names.Add(key.name); + } + + /// Finds the attachments for a given slot. The results are added to the passed List(Attachment). + /// The target slotIndex. To find the slot index, use or + /// Found Attachments will be added to this list. + public void FindAttachmentsForSlot(int slotIndex, List attachments) + { + if (attachments == null) throw new ArgumentNullException("attachments", "attachments cannot be null."); + foreach (KeyValuePair entry in this.attachments) + if (entry.Key.slotIndex == slotIndex) attachments.Add(entry.Value); + } + + override public string ToString() + { + return name; + } + + /// Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached. + internal void AttachAll(Skeleton skeleton, Skin oldSkin) + { + foreach (KeyValuePair entry in oldSkin.attachments) + { + int slotIndex = entry.Key.slotIndex; + Slot slot = skeleton.slots.Items[slotIndex]; + if (slot.Attachment == entry.Value) + { + Attachment attachment = GetAttachment(slotIndex, entry.Key.name); + if (attachment != null) slot.Attachment = attachment; + } + } + } + + public struct AttachmentKeyTuple + { + public readonly int slotIndex; + public readonly string name; + internal readonly int nameHashCode; + + public AttachmentKeyTuple(int slotIndex, string name) + { + this.slotIndex = slotIndex; + this.name = name; + nameHashCode = this.name.GetHashCode(); + } + } + + // Avoids boxing in the dictionary. + class AttachmentKeyTupleComparer : IEqualityComparer + { + internal static readonly AttachmentKeyTupleComparer Instance = new AttachmentKeyTupleComparer(); + + bool IEqualityComparer.Equals(AttachmentKeyTuple o1, AttachmentKeyTuple o2) + { + return o1.slotIndex == o2.slotIndex && o1.nameHashCode == o2.nameHashCode && string.Equals(o1.name, o2.name, StringComparison.Ordinal); + } + + int IEqualityComparer.GetHashCode(AttachmentKeyTuple o) + { + return o.slotIndex; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Slot.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Slot.cs index 8ec2d8e..a326ac7 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Slot.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Slot.cs @@ -29,153 +29,171 @@ using System; -namespace Spine3_7_94 { - - /// - /// Stores a slot's current pose. Slots organize attachments for {@link Skeleton#drawOrder} purposes and provide a place to store - /// state for an attachment.State cannot be stored in an attachment itself because attachments are stateless and may be shared - /// across multiple skeletons. - /// - public class Slot { - internal SlotData data; - internal Bone bone; - internal float r, g, b, a; - internal float r2, g2, b2; - internal bool hasSecondColor; - internal Attachment attachment; - internal float attachmentTime; - internal ExposedList attachmentVertices = new ExposedList(); - - public Slot (SlotData data, Bone bone) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); - this.data = data; - this.bone = bone; - - // darkColor = data.darkColor == null ? null : new Color(); - if (data.hasSecondColor) { - r2 = g2 = b2 = 0; - } - - SetToSetupPose(); - } - - /// Copy constructor. - public Slot(Slot slot, Bone bone) { - if (slot == null) throw new ArgumentNullException("slot", "slot cannot be null."); - if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); - data = slot.data; - this.bone = bone; - r = slot.r; - g = slot.g; - b = slot.b; - a = slot.a; - - // darkColor = slot.darkColor == null ? null : new Color(slot.darkColor); - if (slot.hasSecondColor) { - r2 = slot.r2; - g2 = slot.g2; - b2 = slot.b2; - } else { - r2 = g2 = b2 = 0; - } - hasSecondColor = slot.hasSecondColor; - - attachment = slot.attachment; - attachmentTime = slot.attachmentTime; - } - - /// The slot's setup pose data. - public SlotData Data { get { return data; } } - /// The bone this slot belongs to. - public Bone Bone { get { return bone; } } - /// The skeleton this slot belongs to. - public Skeleton Skeleton { get { return bone.skeleton; } } - /// The color used to tint the slot's attachment. If is set, this is used as the light color for two - /// color tinting. - public float R { get { return r; } set { r = value; } } - /// The color used to tint the slot's attachment. If is set, this is used as the light color for two - /// color tinting. - public float G { get { return g; } set { g = value; } } - /// The color used to tint the slot's attachment. If is set, this is used as the light color for two - /// color tinting. - public float B { get { return b; } set { b = value; } } - /// The color used to tint the slot's attachment. If is set, this is used as the light color for two - /// color tinting. - public float A { get { return a; } set { a = value; } } - - /// The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used. - /// - public float R2 { get { return r2; } set { r2 = value; } } - /// The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used. - /// - public float G2 { get { return g2; } set { g2 = value; } } - /// The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used. - /// - public float B2 { get { return b2; } set { b2 = value; } } - /// Whether R2 G2 B2 are used to tint the slot's attachment for two color tinting. False if two color tinting is not used. - public bool HasSecondColor { get { return data.hasSecondColor; } set { data.hasSecondColor = value; } } - - public Attachment Attachment { - /// The current attachment for the slot, or null if the slot has no attachment. - get { return attachment; } - /// - /// Sets the slot's attachment and, if the attachment changed, resets and clears - /// . - /// May be null. - set { - if (attachment == value) return; - attachment = value; - attachmentTime = bone.skeleton.time; - attachmentVertices.Clear(false); - } - } - - /// The time that has elapsed since the last time the attachment was set or cleared. Relies on Skeleton - /// - public float AttachmentTime { - get { return bone.skeleton.time - attachmentTime; } - set { attachmentTime = bone.skeleton.time - value; } - } - - /// Vertices to deform the slot's attachment. For an unweighted mesh, the entries are local positions for each vertex. For a - /// weighted mesh, the entries are an offset for each vertex which will be added to the mesh's local vertex positions. - /// - /// See and . - public ExposedList AttachmentVertices { - get { - return attachmentVertices; - } - set { - if (attachmentVertices == null) throw new ArgumentNullException("attachmentVertices", "attachmentVertices cannot be null."); - attachmentVertices = value; - } - } - - /// Sets this slot to the setup pose. - public void SetToSetupPose () { - r = data.r; - g = data.g; - b = data.b; - a = data.a; - - // if (darkColor != null) darkColor.set(data.darkColor); - if (HasSecondColor) { - r2 = data.r2; - g2 = data.g2; - b2 = data.b2; - } - - if (data.attachmentName == null) - Attachment = null; - else { - attachment = null; - Attachment = bone.skeleton.GetAttachment(data.index, data.attachmentName); - } - } - - override public string ToString () { - return data.name; - } - } +namespace Spine3_7_94 +{ + + /// + /// Stores a slot's current pose. Slots organize attachments for {@link Skeleton#drawOrder} purposes and provide a place to store + /// state for an attachment.State cannot be stored in an attachment itself because attachments are stateless and may be shared + /// across multiple skeletons. + /// + public class Slot + { + internal SlotData data; + internal Bone bone; + internal float r, g, b, a; + internal float r2, g2, b2; + internal bool hasSecondColor; + internal Attachment attachment; + internal float attachmentTime; + internal ExposedList attachmentVertices = new ExposedList(); + + public Slot(SlotData data, Bone bone) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); + this.data = data; + this.bone = bone; + + // darkColor = data.darkColor == null ? null : new Color(); + if (data.hasSecondColor) + { + r2 = g2 = b2 = 0; + } + + SetToSetupPose(); + } + + /// Copy constructor. + public Slot(Slot slot, Bone bone) + { + if (slot == null) throw new ArgumentNullException("slot", "slot cannot be null."); + if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); + data = slot.data; + this.bone = bone; + r = slot.r; + g = slot.g; + b = slot.b; + a = slot.a; + + // darkColor = slot.darkColor == null ? null : new Color(slot.darkColor); + if (slot.hasSecondColor) + { + r2 = slot.r2; + g2 = slot.g2; + b2 = slot.b2; + } + else + { + r2 = g2 = b2 = 0; + } + hasSecondColor = slot.hasSecondColor; + + attachment = slot.attachment; + attachmentTime = slot.attachmentTime; + } + + /// The slot's setup pose data. + public SlotData Data { get { return data; } } + /// The bone this slot belongs to. + public Bone Bone { get { return bone; } } + /// The skeleton this slot belongs to. + public Skeleton Skeleton { get { return bone.skeleton; } } + /// The color used to tint the slot's attachment. If is set, this is used as the light color for two + /// color tinting. + public float R { get { return r; } set { r = value; } } + /// The color used to tint the slot's attachment. If is set, this is used as the light color for two + /// color tinting. + public float G { get { return g; } set { g = value; } } + /// The color used to tint the slot's attachment. If is set, this is used as the light color for two + /// color tinting. + public float B { get { return b; } set { b = value; } } + /// The color used to tint the slot's attachment. If is set, this is used as the light color for two + /// color tinting. + public float A { get { return a; } set { a = value; } } + + /// The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used. + /// + public float R2 { get { return r2; } set { r2 = value; } } + /// The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used. + /// + public float G2 { get { return g2; } set { g2 = value; } } + /// The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used. + /// + public float B2 { get { return b2; } set { b2 = value; } } + /// Whether R2 G2 B2 are used to tint the slot's attachment for two color tinting. False if two color tinting is not used. + public bool HasSecondColor { get { return data.hasSecondColor; } set { data.hasSecondColor = value; } } + + public Attachment Attachment + { + /// The current attachment for the slot, or null if the slot has no attachment. + get { return attachment; } + /// + /// Sets the slot's attachment and, if the attachment changed, resets and clears + /// . + /// May be null. + set + { + if (attachment == value) return; + attachment = value; + attachmentTime = bone.skeleton.time; + attachmentVertices.Clear(false); + } + } + + /// The time that has elapsed since the last time the attachment was set or cleared. Relies on Skeleton + /// + public float AttachmentTime + { + get { return bone.skeleton.time - attachmentTime; } + set { attachmentTime = bone.skeleton.time - value; } + } + + /// Vertices to deform the slot's attachment. For an unweighted mesh, the entries are local positions for each vertex. For a + /// weighted mesh, the entries are an offset for each vertex which will be added to the mesh's local vertex positions. + /// + /// See and . + public ExposedList AttachmentVertices + { + get + { + return attachmentVertices; + } + set + { + if (attachmentVertices == null) throw new ArgumentNullException("attachmentVertices", "attachmentVertices cannot be null."); + attachmentVertices = value; + } + } + + /// Sets this slot to the setup pose. + public void SetToSetupPose() + { + r = data.r; + g = data.g; + b = data.b; + a = data.a; + + // if (darkColor != null) darkColor.set(data.darkColor); + if (HasSecondColor) + { + r2 = data.r2; + g2 = data.g2; + b2 = data.b2; + } + + if (data.attachmentName == null) + Attachment = null; + else + { + attachment = null; + Attachment = bone.skeleton.GetAttachment(data.index, data.attachmentName); + } + } + + override public string ToString() + { + return data.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/SlotData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/SlotData.cs index 022738d..761e73c 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/SlotData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/SlotData.cs @@ -29,45 +29,49 @@ using System; -namespace Spine3_7_94 { - public class SlotData { - internal int index; - internal string name; - internal BoneData boneData; - internal float r = 1, g = 1, b = 1, a = 1; - internal float r2 = 0, g2 = 0, b2 = 0; - internal bool hasSecondColor = false; - internal string attachmentName; - internal BlendMode blendMode; +namespace Spine3_7_94 +{ + public class SlotData + { + internal int index; + internal string name; + internal BoneData boneData; + internal float r = 1, g = 1, b = 1, a = 1; + internal float r2 = 0, g2 = 0, b2 = 0; + internal bool hasSecondColor = false; + internal string attachmentName; + internal BlendMode blendMode; - public int Index { get { return index; } } - public string Name { get { return name; } } - public BoneData BoneData { get { return boneData; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } + public int Index { get { return index; } } + public string Name { get { return name; } } + public BoneData BoneData { get { return boneData; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } - public float R2 { get { return r2; } set { r2 = value; } } - public float G2 { get { return g2; } set { g2 = value; } } - public float B2 { get { return b2; } set { b2 = value; } } - public bool HasSecondColor { get { return hasSecondColor; } set { hasSecondColor = value; } } + public float R2 { get { return r2; } set { r2 = value; } } + public float G2 { get { return g2; } set { g2 = value; } } + public float B2 { get { return b2; } set { b2 = value; } } + public bool HasSecondColor { get { return hasSecondColor; } set { hasSecondColor = value; } } - /// May be null. - public String AttachmentName { get { return attachmentName; } set { attachmentName = value; } } - public BlendMode BlendMode { get { return blendMode; } set { blendMode = value; } } + /// May be null. + public String AttachmentName { get { return attachmentName; } set { attachmentName = value; } } + public BlendMode BlendMode { get { return blendMode; } set { blendMode = value; } } - public SlotData (int index, String name, BoneData boneData) { - if (index < 0) throw new ArgumentException ("index must be >= 0.", "index"); - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - if (boneData == null) throw new ArgumentNullException("boneData", "boneData cannot be null."); - this.index = index; - this.name = name; - this.boneData = boneData; - } + public SlotData(int index, String name, BoneData boneData) + { + if (index < 0) throw new ArgumentException("index must be >= 0.", "index"); + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + if (boneData == null) throw new ArgumentNullException("boneData", "boneData cannot be null."); + this.index = index; + this.name = name; + this.boneData = boneData; + } - override public string ToString () { - return name; - } - } + override public string ToString() + { + return name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/TransformConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/TransformConstraint.cs index 8a2b970..1547ab4 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/TransformConstraint.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/TransformConstraint.cs @@ -29,283 +29,315 @@ using System; -namespace Spine3_7_94 { - /// - /// - /// Stores the current pose for a transform constraint. A transform constraint adjusts the world transform of the constrained - /// bones to match that of the target bone. - /// - /// See Transform constraints in the Spine User Guide. - /// - public class TransformConstraint : IConstraint { - internal TransformConstraintData data; - internal ExposedList bones; - internal Bone target; - internal float rotateMix, translateMix, scaleMix, shearMix; - - public TransformConstraint (TransformConstraintData data, Skeleton skeleton) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.data = data; - rotateMix = data.rotateMix; - translateMix = data.translateMix; - scaleMix = data.scaleMix; - shearMix = data.shearMix; - - bones = new ExposedList(); - foreach (BoneData boneData in data.bones) - bones.Add (skeleton.FindBone(boneData.name)); - - target = skeleton.FindBone(data.target.name); - } - - /// Copy constructor. - public TransformConstraint (TransformConstraint constraint, Skeleton skeleton) { - if (constraint == null) throw new ArgumentNullException("constraint cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); - data = constraint.data; - bones = new ExposedList(constraint.Bones.Count); - foreach (Bone bone in constraint.Bones) - bones.Add(skeleton.Bones.Items[bone.data.index]); - target = skeleton.Bones.Items[constraint.target.data.index]; - rotateMix = constraint.rotateMix; - translateMix = constraint.translateMix; - scaleMix = constraint.scaleMix; - shearMix = constraint.shearMix; - } - - /// Applies the constraint to the constrained bones. - public void Apply () { - Update(); - } - - public void Update () { - if (data.local) { - if (data.relative) - ApplyRelativeLocal(); - else - ApplyAbsoluteLocal(); - } else { - if (data.relative) - ApplyRelativeWorld(); - else - ApplyAbsoluteWorld(); - } - } - - void ApplyAbsoluteWorld () { - float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; - Bone target = this.target; - float ta = target.a, tb = target.b, tc = target.c, td = target.d; - float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad; - float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect; - var bones = this.bones; - for (int i = 0, n = bones.Count; i < n; i++) { - Bone bone = bones.Items[i]; - bool modified = false; - - if (rotateMix != 0) { - float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - float r = MathUtils.Atan2(tc, ta) - MathUtils.Atan2(c, a) + offsetRotation; - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) r += MathUtils.PI2; - r *= rotateMix; - float cos = MathUtils.Cos(r), sin = MathUtils.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; - modified = true; - } - - if (translateMix != 0) { - float tx, ty; //Vector2 temp = this.temp; - target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY)); - bone.worldX += (tx - bone.worldX) * translateMix; - bone.worldY += (ty - bone.worldY) * translateMix; - modified = true; - } - - if (scaleMix > 0) { - float s = (float)Math.Sqrt(bone.a * bone.a + bone.c * bone.c); - if (s != 0) s = (s + ((float)Math.Sqrt(ta * ta + tc * tc) - s + data.offsetScaleX) * scaleMix) / s; - bone.a *= s; - bone.c *= s; - s = (float)Math.Sqrt(bone.b * bone.b + bone.d * bone.d); - if (s != 0) s = (s + ((float)Math.Sqrt(tb * tb + td * td) - s + data.offsetScaleY) * scaleMix) / s; - bone.b *= s; - bone.d *= s; - modified = true; - } - - if (shearMix > 0) { - float b = bone.b, d = bone.d; - float by = MathUtils.Atan2(d, b); - float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta) - (by - MathUtils.Atan2(bone.c, bone.a)); - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) r += MathUtils.PI2; - r = by + (r + offsetShearY) * shearMix; - float s = (float)Math.Sqrt(b * b + d * d); - bone.b = MathUtils.Cos(r) * s; - bone.d = MathUtils.Sin(r) * s; - modified = true; - } - - if (modified) bone.appliedValid = false; - } - } - - void ApplyRelativeWorld () { - float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; - Bone target = this.target; - float ta = target.a, tb = target.b, tc = target.c, td = target.d; - float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad; - float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect; - var bones = this.bones; - for (int i = 0, n = bones.Count; i < n; i++) { - Bone bone = bones.Items[i]; - bool modified = false; - - if (rotateMix != 0) { - float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - float r = MathUtils.Atan2(tc, ta) + offsetRotation; - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) r += MathUtils.PI2; - r *= rotateMix; - float cos = MathUtils.Cos(r), sin = MathUtils.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; - modified = true; - } - - if (translateMix != 0) { - float tx, ty; //Vector2 temp = this.temp; - target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY)); - bone.worldX += tx * translateMix; - bone.worldY += ty * translateMix; - modified = true; - } - - if (scaleMix > 0) { - float s = ((float)Math.Sqrt(ta * ta + tc * tc) - 1 + data.offsetScaleX) * scaleMix + 1; - bone.a *= s; - bone.c *= s; - s = ((float)Math.Sqrt(tb * tb + td * td) - 1 + data.offsetScaleY) * scaleMix + 1; - bone.b *= s; - bone.d *= s; - modified = true; - } - - if (shearMix > 0) { - float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta); - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) r += MathUtils.PI2; - float b = bone.b, d = bone.d; - r = MathUtils.Atan2(d, b) + (r - MathUtils.PI / 2 + offsetShearY) * shearMix; - float s = (float)Math.Sqrt(b * b + d * d); - bone.b = MathUtils.Cos(r) * s; - bone.d = MathUtils.Sin(r) * s; - modified = true; - } - - if (modified) bone.appliedValid = false; - } - } - - void ApplyAbsoluteLocal () { - float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; - Bone target = this.target; - if (!target.appliedValid) target.UpdateAppliedTransform(); - var bonesItems = this.bones.Items; - for (int i = 0, n = this.bones.Count; i < n; i++) { - Bone bone = bonesItems[i]; - if (!bone.appliedValid) bone.UpdateAppliedTransform(); - - float rotation = bone.arotation; - if (rotateMix != 0) { - float r = target.arotation - rotation + data.offsetRotation; - r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; - rotation += r * rotateMix; - } - - float x = bone.ax, y = bone.ay; - if (translateMix != 0) { - x += (target.ax - x + data.offsetX) * translateMix; - y += (target.ay - y + data.offsetY) * translateMix; - } - - float scaleX = bone.ascaleX, scaleY = bone.ascaleY; - if (scaleMix != 0) { - if (scaleX != 0) scaleX = (scaleX + (target.ascaleX - scaleX + data.offsetScaleX) * scaleMix) / scaleX; - if (scaleY != 0) scaleY = (scaleY + (target.ascaleY - scaleY + data.offsetScaleY) * scaleMix) / scaleY; - } - - float shearY = bone.ashearY; - if (shearMix != 0) { - float r = target.ashearY - shearY + data.offsetShearY; - r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; - shearY += r * shearMix; - } - - bone.UpdateWorldTransform(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY); - } - } - - void ApplyRelativeLocal () { - float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; - Bone target = this.target; - if (!target.appliedValid) target.UpdateAppliedTransform(); - var bonesItems = this.bones.Items; - for (int i = 0, n = this.bones.Count; i < n; i++) { - Bone bone = bonesItems[i]; - if (!bone.appliedValid) bone.UpdateAppliedTransform(); - - float rotation = bone.arotation; - if (rotateMix != 0) rotation += (target.arotation + data.offsetRotation) * rotateMix; - - float x = bone.ax, y = bone.ay; - if (translateMix != 0) { - x += (target.ax + data.offsetX) * translateMix; - y += (target.ay + data.offsetY) * translateMix; - } - - float scaleX = bone.ascaleX, scaleY = bone.ascaleY; - if (scaleMix != 0) { - scaleX *= ((target.ascaleX - 1 + data.offsetScaleX) * scaleMix) + 1; - scaleY *= ((target.ascaleY - 1 + data.offsetScaleY) * scaleMix) + 1; - } - - float shearY = bone.ashearY; - if (shearMix != 0) shearY += (target.ashearY + data.offsetShearY) * shearMix; - - bone.UpdateWorldTransform(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY); - } - } - - public int Order { get { return data.order; } } - /// The bones that will be modified by this transform constraint. - public ExposedList Bones { get { return bones; } } - /// The target bone whose world transform will be copied to the constrained bones. - public Bone Target { get { return target; } set { target = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotations. - public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained translations. - public float TranslateMix { get { return translateMix; } set { translateMix = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained scales. - public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained scales. - public float ShearMix { get { return shearMix; } set { shearMix = value; } } - /// The transform constraint's setup pose data. - public TransformConstraintData Data { get { return data; } } - - override public string ToString () { - return data.name; - } - } +namespace Spine3_7_94 +{ + /// + /// + /// Stores the current pose for a transform constraint. A transform constraint adjusts the world transform of the constrained + /// bones to match that of the target bone. + /// + /// See Transform constraints in the Spine User Guide. + /// + public class TransformConstraint : IConstraint + { + internal TransformConstraintData data; + internal ExposedList bones; + internal Bone target; + internal float rotateMix, translateMix, scaleMix, shearMix; + + public TransformConstraint(TransformConstraintData data, Skeleton skeleton) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + rotateMix = data.rotateMix; + translateMix = data.translateMix; + scaleMix = data.scaleMix; + shearMix = data.shearMix; + + bones = new ExposedList(); + foreach (BoneData boneData in data.bones) + bones.Add(skeleton.FindBone(boneData.name)); + + target = skeleton.FindBone(data.target.name); + } + + /// Copy constructor. + public TransformConstraint(TransformConstraint constraint, Skeleton skeleton) + { + if (constraint == null) throw new ArgumentNullException("constraint cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); + data = constraint.data; + bones = new ExposedList(constraint.Bones.Count); + foreach (Bone bone in constraint.Bones) + bones.Add(skeleton.Bones.Items[bone.data.index]); + target = skeleton.Bones.Items[constraint.target.data.index]; + rotateMix = constraint.rotateMix; + translateMix = constraint.translateMix; + scaleMix = constraint.scaleMix; + shearMix = constraint.shearMix; + } + + /// Applies the constraint to the constrained bones. + public void Apply() + { + Update(); + } + + public void Update() + { + if (data.local) + { + if (data.relative) + ApplyRelativeLocal(); + else + ApplyAbsoluteLocal(); + } + else + { + if (data.relative) + ApplyRelativeWorld(); + else + ApplyAbsoluteWorld(); + } + } + + void ApplyAbsoluteWorld() + { + float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; + Bone target = this.target; + float ta = target.a, tb = target.b, tc = target.c, td = target.d; + float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad; + float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect; + var bones = this.bones; + for (int i = 0, n = bones.Count; i < n; i++) + { + Bone bone = bones.Items[i]; + bool modified = false; + + if (rotateMix != 0) + { + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + float r = MathUtils.Atan2(tc, ta) - MathUtils.Atan2(c, a) + offsetRotation; + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) r += MathUtils.PI2; + r *= rotateMix; + float cos = MathUtils.Cos(r), sin = MathUtils.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; + modified = true; + } + + if (translateMix != 0) + { + float tx, ty; //Vector2 temp = this.temp; + target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY)); + bone.worldX += (tx - bone.worldX) * translateMix; + bone.worldY += (ty - bone.worldY) * translateMix; + modified = true; + } + + if (scaleMix > 0) + { + float s = (float)Math.Sqrt(bone.a * bone.a + bone.c * bone.c); + if (s != 0) s = (s + ((float)Math.Sqrt(ta * ta + tc * tc) - s + data.offsetScaleX) * scaleMix) / s; + bone.a *= s; + bone.c *= s; + s = (float)Math.Sqrt(bone.b * bone.b + bone.d * bone.d); + if (s != 0) s = (s + ((float)Math.Sqrt(tb * tb + td * td) - s + data.offsetScaleY) * scaleMix) / s; + bone.b *= s; + bone.d *= s; + modified = true; + } + + if (shearMix > 0) + { + float b = bone.b, d = bone.d; + float by = MathUtils.Atan2(d, b); + float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta) - (by - MathUtils.Atan2(bone.c, bone.a)); + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) r += MathUtils.PI2; + r = by + (r + offsetShearY) * shearMix; + float s = (float)Math.Sqrt(b * b + d * d); + bone.b = MathUtils.Cos(r) * s; + bone.d = MathUtils.Sin(r) * s; + modified = true; + } + + if (modified) bone.appliedValid = false; + } + } + + void ApplyRelativeWorld() + { + float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; + Bone target = this.target; + float ta = target.a, tb = target.b, tc = target.c, td = target.d; + float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad; + float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect; + var bones = this.bones; + for (int i = 0, n = bones.Count; i < n; i++) + { + Bone bone = bones.Items[i]; + bool modified = false; + + if (rotateMix != 0) + { + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + float r = MathUtils.Atan2(tc, ta) + offsetRotation; + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) r += MathUtils.PI2; + r *= rotateMix; + float cos = MathUtils.Cos(r), sin = MathUtils.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; + modified = true; + } + + if (translateMix != 0) + { + float tx, ty; //Vector2 temp = this.temp; + target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY)); + bone.worldX += tx * translateMix; + bone.worldY += ty * translateMix; + modified = true; + } + + if (scaleMix > 0) + { + float s = ((float)Math.Sqrt(ta * ta + tc * tc) - 1 + data.offsetScaleX) * scaleMix + 1; + bone.a *= s; + bone.c *= s; + s = ((float)Math.Sqrt(tb * tb + td * td) - 1 + data.offsetScaleY) * scaleMix + 1; + bone.b *= s; + bone.d *= s; + modified = true; + } + + if (shearMix > 0) + { + float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta); + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) r += MathUtils.PI2; + float b = bone.b, d = bone.d; + r = MathUtils.Atan2(d, b) + (r - MathUtils.PI / 2 + offsetShearY) * shearMix; + float s = (float)Math.Sqrt(b * b + d * d); + bone.b = MathUtils.Cos(r) * s; + bone.d = MathUtils.Sin(r) * s; + modified = true; + } + + if (modified) bone.appliedValid = false; + } + } + + void ApplyAbsoluteLocal() + { + float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; + Bone target = this.target; + if (!target.appliedValid) target.UpdateAppliedTransform(); + var bonesItems = this.bones.Items; + for (int i = 0, n = this.bones.Count; i < n; i++) + { + Bone bone = bonesItems[i]; + if (!bone.appliedValid) bone.UpdateAppliedTransform(); + + float rotation = bone.arotation; + if (rotateMix != 0) + { + float r = target.arotation - rotation + data.offsetRotation; + r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; + rotation += r * rotateMix; + } + + float x = bone.ax, y = bone.ay; + if (translateMix != 0) + { + x += (target.ax - x + data.offsetX) * translateMix; + y += (target.ay - y + data.offsetY) * translateMix; + } + + float scaleX = bone.ascaleX, scaleY = bone.ascaleY; + if (scaleMix != 0) + { + if (scaleX != 0) scaleX = (scaleX + (target.ascaleX - scaleX + data.offsetScaleX) * scaleMix) / scaleX; + if (scaleY != 0) scaleY = (scaleY + (target.ascaleY - scaleY + data.offsetScaleY) * scaleMix) / scaleY; + } + + float shearY = bone.ashearY; + if (shearMix != 0) + { + float r = target.ashearY - shearY + data.offsetShearY; + r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; + shearY += r * shearMix; + } + + bone.UpdateWorldTransform(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY); + } + } + + void ApplyRelativeLocal() + { + float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; + Bone target = this.target; + if (!target.appliedValid) target.UpdateAppliedTransform(); + var bonesItems = this.bones.Items; + for (int i = 0, n = this.bones.Count; i < n; i++) + { + Bone bone = bonesItems[i]; + if (!bone.appliedValid) bone.UpdateAppliedTransform(); + + float rotation = bone.arotation; + if (rotateMix != 0) rotation += (target.arotation + data.offsetRotation) * rotateMix; + + float x = bone.ax, y = bone.ay; + if (translateMix != 0) + { + x += (target.ax + data.offsetX) * translateMix; + y += (target.ay + data.offsetY) * translateMix; + } + + float scaleX = bone.ascaleX, scaleY = bone.ascaleY; + if (scaleMix != 0) + { + scaleX *= ((target.ascaleX - 1 + data.offsetScaleX) * scaleMix) + 1; + scaleY *= ((target.ascaleY - 1 + data.offsetScaleY) * scaleMix) + 1; + } + + float shearY = bone.ashearY; + if (shearMix != 0) shearY += (target.ashearY + data.offsetShearY) * shearMix; + + bone.UpdateWorldTransform(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY); + } + } + + public int Order { get { return data.order; } } + /// The bones that will be modified by this transform constraint. + public ExposedList Bones { get { return bones; } } + /// The target bone whose world transform will be copied to the constrained bones. + public Bone Target { get { return target; } set { target = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotations. + public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained translations. + public float TranslateMix { get { return translateMix; } set { translateMix = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained scales. + public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained scales. + public float ShearMix { get { return shearMix; } set { shearMix = value; } } + /// The transform constraint's setup pose data. + public TransformConstraintData Data { get { return data; } } + + override public string ToString() + { + return data.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/TransformConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/TransformConstraintData.cs index 4705eee..fbc9013 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/TransformConstraintData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/TransformConstraintData.cs @@ -29,42 +29,46 @@ using System; -namespace Spine3_7_94 { - public class TransformConstraintData { - internal string name; - internal int order; - internal ExposedList bones = new ExposedList(); - internal BoneData target; - internal float rotateMix, translateMix, scaleMix, shearMix; - internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY; - internal bool relative, local; +namespace Spine3_7_94 +{ + public class TransformConstraintData + { + internal string name; + internal int order; + internal ExposedList bones = new ExposedList(); + internal BoneData target; + internal float rotateMix, translateMix, scaleMix, shearMix; + internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY; + internal bool relative, local; - public string Name { get { return name; } } - public int Order { get { return order; } set { order = value; } } - public ExposedList Bones { get { return bones; } } - public BoneData Target { get { return target; } set { target = value; } } - public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } - public float TranslateMix { get { return translateMix; } set { translateMix = value; } } - public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } } - public float ShearMix { get { return shearMix; } set { shearMix = value; } } + public string Name { get { return name; } } + public int Order { get { return order; } set { order = value; } } + public ExposedList Bones { get { return bones; } } + public BoneData Target { get { return target; } set { target = value; } } + public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } + public float TranslateMix { get { return translateMix; } set { translateMix = value; } } + public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } } + public float ShearMix { get { return shearMix; } set { shearMix = value; } } - public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } - public float OffsetX { get { return offsetX; } set { offsetX = value; } } - public float OffsetY { get { return offsetY; } set { offsetY = value; } } - public float OffsetScaleX { get { return offsetScaleX; } set { offsetScaleX = value; } } - public float OffsetScaleY { get { return offsetScaleY; } set { offsetScaleY = value; } } - public float OffsetShearY { get { return offsetShearY; } set { offsetShearY = value; } } + public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } + public float OffsetX { get { return offsetX; } set { offsetX = value; } } + public float OffsetY { get { return offsetY; } set { offsetY = value; } } + public float OffsetScaleX { get { return offsetScaleX; } set { offsetScaleX = value; } } + public float OffsetScaleY { get { return offsetScaleY; } set { offsetScaleY = value; } } + public float OffsetShearY { get { return offsetShearY; } set { offsetShearY = value; } } - public bool Relative { get { return relative; } set { relative = value; } } - public bool Local { get { return local; } set { local = value; } } + public bool Relative { get { return relative; } set { relative = value; } } + public bool Local { get { return local; } set { local = value; } } - public TransformConstraintData (string name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; - } + public TransformConstraintData(string name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + } - override public string ToString () { - return name; - } - } + override public string ToString() + { + return name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Triangulator.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Triangulator.cs index 13d41c7..c2b6a79 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Triangulator.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/Triangulator.cs @@ -29,249 +29,280 @@ using System; -namespace Spine3_7_94 { - public class Triangulator { - private readonly ExposedList> convexPolygons = new ExposedList>(); - private readonly ExposedList> convexPolygonsIndices = new ExposedList>(); - - private readonly ExposedList indicesArray = new ExposedList(); - private readonly ExposedList isConcaveArray = new ExposedList(); - private readonly ExposedList triangles = new ExposedList(); - - private readonly Pool> polygonPool = new Pool>(); - private readonly Pool> polygonIndicesPool = new Pool>(); - - public ExposedList Triangulate (ExposedList verticesArray) { - var vertices = verticesArray.Items; - int vertexCount = verticesArray.Count >> 1; - - var indicesArray = this.indicesArray; - indicesArray.Clear(); - int[] indices = indicesArray.Resize(vertexCount).Items; - for (int i = 0; i < vertexCount; i++) - indices[i] = i; - - var isConcaveArray = this.isConcaveArray; - bool[] isConcave = isConcaveArray.Resize(vertexCount).Items; - for (int i = 0, n = vertexCount; i < n; ++i) - isConcave[i] = IsConcave(i, vertexCount, vertices, indices); - - var triangles = this.triangles; - triangles.Clear(); - triangles.EnsureCapacity(Math.Max(0, vertexCount - 2) << 2); - - while (vertexCount > 3) { - // Find ear tip. - int previous = vertexCount - 1, i = 0, next = 1; - - // outer: - while (true) { - if (!isConcave[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 (int ii = (next + 1) % vertexCount; ii != previous; ii = (ii + 1) % vertexCount) { - if (!isConcave[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 (!isConcave[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]); - indicesArray.RemoveAt(i); - isConcaveArray.RemoveAt(i); - vertexCount--; - - int previousIndex = (vertexCount + i - 1) % vertexCount; - int nextIndex = i == vertexCount ? 0 : i; - isConcave[previousIndex] = IsConcave(previousIndex, vertexCount, vertices, indices); - isConcave[nextIndex] = IsConcave(nextIndex, vertexCount, vertices, indices); - } - - if (vertexCount == 3) { - triangles.Add(indices[2]); - triangles.Add(indices[0]); - triangles.Add(indices[1]); - } - - return triangles; - } - - public ExposedList> Decompose (ExposedList verticesArray, ExposedList triangles) { - var vertices = verticesArray.Items; - var convexPolygons = this.convexPolygons; - for (int i = 0, n = convexPolygons.Count; i < n; i++) { - polygonPool.Free(convexPolygons.Items[i]); - } - convexPolygons.Clear(); - - var convexPolygonsIndices = this.convexPolygonsIndices; - for (int i = 0, n = convexPolygonsIndices.Count; i < n; i++) { - polygonIndicesPool.Free(convexPolygonsIndices.Items[i]); - } - convexPolygonsIndices.Clear(); - - var polygonIndices = polygonIndicesPool.Obtain(); - polygonIndices.Clear(); - - var polygon = polygonPool.Obtain(); - polygon.Clear(); - - // Merge subsequent triangles if they form a triangle fan. - int fanBaseIndex = -1, lastWinding = 0; - int[] trianglesItems = triangles.Items; - for (int i = 0, n = triangles.Count; i < n; i += 3) { - int t1 = trianglesItems[i] << 1, t2 = trianglesItems[i + 1] << 1, t3 = trianglesItems[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). - var merged = false; - if (fanBaseIndex == t1) { - int o = polygon.Count - 4; - float[] p = polygon.Items; - 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.Count > 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.Count > 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 (int i = 0, n = convexPolygons.Count; i < n; i++) { - polygonIndices = convexPolygonsIndices.Items[i]; - if (polygonIndices.Count == 0) continue; - int firstIndex = polygonIndices.Items[0]; - int lastIndex = polygonIndices.Items[polygonIndices.Count - 1]; - - polygon = convexPolygons.Items[i]; - int o = polygon.Count - 4; - float[] p = polygon.Items; - 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 winding = Winding(prevPrevX, prevPrevY, prevX, prevY, firstX, firstY); - - for (int ii = 0; ii < n; ii++) { - if (ii == i) continue; - var otherIndices = convexPolygonsIndices.Items[ii]; - if (otherIndices.Count != 3) continue; - int otherFirstIndex = otherIndices.Items[0]; - int otherSecondIndex = otherIndices.Items[1]; - int otherLastIndex = otherIndices.Items[2]; - - var otherPoly = convexPolygons.Items[ii]; - float x3 = otherPoly.Items[otherPoly.Count - 2], y3 = otherPoly.Items[otherPoly.Count - 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 == winding && winding2 == winding) { - 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 = convexPolygons.Count - 1; i >= 0; i--) { - polygon = convexPolygons.Items[i]; - if (polygon.Count == 0) { - convexPolygons.RemoveAt(i); - polygonPool.Free(polygon); - polygonIndices = convexPolygonsIndices.Items[i]; - convexPolygonsIndices.RemoveAt(i); - polygonIndicesPool.Free(polygonIndices); - } - } - - return convexPolygons; - } - - static private bool IsConcave (int index, int vertexCount, float[] vertices, int[] 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]); - } - - static private bool PositiveArea (float p1x, float p1y, float p2x, float p2y, float p3x, float p3y) { - return p1x * (p3y - p2y) + p2x * (p1y - p3y) + p3x * (p2y - p1y) >= 0; - } - - static private int 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; - } - } +namespace Spine3_7_94 +{ + public class Triangulator + { + private readonly ExposedList> convexPolygons = new ExposedList>(); + private readonly ExposedList> convexPolygonsIndices = new ExposedList>(); + + private readonly ExposedList indicesArray = new ExposedList(); + private readonly ExposedList isConcaveArray = new ExposedList(); + private readonly ExposedList triangles = new ExposedList(); + + private readonly Pool> polygonPool = new Pool>(); + private readonly Pool> polygonIndicesPool = new Pool>(); + + public ExposedList Triangulate(ExposedList verticesArray) + { + var vertices = verticesArray.Items; + int vertexCount = verticesArray.Count >> 1; + + var indicesArray = this.indicesArray; + indicesArray.Clear(); + int[] indices = indicesArray.Resize(vertexCount).Items; + for (int i = 0; i < vertexCount; i++) + indices[i] = i; + + var isConcaveArray = this.isConcaveArray; + bool[] isConcave = isConcaveArray.Resize(vertexCount).Items; + for (int i = 0, n = vertexCount; i < n; ++i) + isConcave[i] = IsConcave(i, vertexCount, vertices, indices); + + var triangles = this.triangles; + triangles.Clear(); + triangles.EnsureCapacity(Math.Max(0, vertexCount - 2) << 2); + + while (vertexCount > 3) + { + // Find ear tip. + int previous = vertexCount - 1, i = 0, next = 1; + + // outer: + while (true) + { + if (!isConcave[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 (int ii = (next + 1) % vertexCount; ii != previous; ii = (ii + 1) % vertexCount) + { + if (!isConcave[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 (!isConcave[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]); + indicesArray.RemoveAt(i); + isConcaveArray.RemoveAt(i); + vertexCount--; + + int previousIndex = (vertexCount + i - 1) % vertexCount; + int nextIndex = i == vertexCount ? 0 : i; + isConcave[previousIndex] = IsConcave(previousIndex, vertexCount, vertices, indices); + isConcave[nextIndex] = IsConcave(nextIndex, vertexCount, vertices, indices); + } + + if (vertexCount == 3) + { + triangles.Add(indices[2]); + triangles.Add(indices[0]); + triangles.Add(indices[1]); + } + + return triangles; + } + + public ExposedList> Decompose(ExposedList verticesArray, ExposedList triangles) + { + var vertices = verticesArray.Items; + var convexPolygons = this.convexPolygons; + for (int i = 0, n = convexPolygons.Count; i < n; i++) + { + polygonPool.Free(convexPolygons.Items[i]); + } + convexPolygons.Clear(); + + var convexPolygonsIndices = this.convexPolygonsIndices; + for (int i = 0, n = convexPolygonsIndices.Count; i < n; i++) + { + polygonIndicesPool.Free(convexPolygonsIndices.Items[i]); + } + convexPolygonsIndices.Clear(); + + var polygonIndices = polygonIndicesPool.Obtain(); + polygonIndices.Clear(); + + var polygon = polygonPool.Obtain(); + polygon.Clear(); + + // Merge subsequent triangles if they form a triangle fan. + int fanBaseIndex = -1, lastWinding = 0; + int[] trianglesItems = triangles.Items; + for (int i = 0, n = triangles.Count; i < n; i += 3) + { + int t1 = trianglesItems[i] << 1, t2 = trianglesItems[i + 1] << 1, t3 = trianglesItems[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). + var merged = false; + if (fanBaseIndex == t1) + { + int o = polygon.Count - 4; + float[] p = polygon.Items; + 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.Count > 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.Count > 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 (int i = 0, n = convexPolygons.Count; i < n; i++) + { + polygonIndices = convexPolygonsIndices.Items[i]; + if (polygonIndices.Count == 0) continue; + int firstIndex = polygonIndices.Items[0]; + int lastIndex = polygonIndices.Items[polygonIndices.Count - 1]; + + polygon = convexPolygons.Items[i]; + int o = polygon.Count - 4; + float[] p = polygon.Items; + 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 winding = Winding(prevPrevX, prevPrevY, prevX, prevY, firstX, firstY); + + for (int ii = 0; ii < n; ii++) + { + if (ii == i) continue; + var otherIndices = convexPolygonsIndices.Items[ii]; + if (otherIndices.Count != 3) continue; + int otherFirstIndex = otherIndices.Items[0]; + int otherSecondIndex = otherIndices.Items[1]; + int otherLastIndex = otherIndices.Items[2]; + + var otherPoly = convexPolygons.Items[ii]; + float x3 = otherPoly.Items[otherPoly.Count - 2], y3 = otherPoly.Items[otherPoly.Count - 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 == winding && winding2 == winding) + { + 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 = convexPolygons.Count - 1; i >= 0; i--) + { + polygon = convexPolygons.Items[i]; + if (polygon.Count == 0) + { + convexPolygons.RemoveAt(i); + polygonPool.Free(polygon); + polygonIndices = convexPolygonsIndices.Items[i]; + convexPolygonsIndices.RemoveAt(i); + polygonIndicesPool.Free(polygonIndices); + } + } + + return convexPolygons; + } + + static private bool IsConcave(int index, int vertexCount, float[] vertices, int[] 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]); + } + + static private bool PositiveArea(float p1x, float p1y, float p2x, float p2y, float p3x, float p3y) + { + return p1x * (p3y - p2y) + p2x * (p1y - p3y) + p3x * (p2y - p1y) >= 0; + } + + static private int 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/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/XnaLoader/MeshBatcher.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/XnaLoader/MeshBatcher.cs index ff69638..5543ed1 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/XnaLoader/MeshBatcher.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/XnaLoader/MeshBatcher.cs @@ -29,156 +29,169 @@ using System; using System.Collections.Generic; -using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; -namespace Spine3_7_94 { - public struct VertexPositionColorTextureColor : IVertexType { - public Vector3 Position; - public Color Color; - public Vector2 TextureCoordinate; - public Color Color2; - - public readonly static VertexDeclaration VertexDeclaration = new VertexDeclaration - ( - new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0), - new VertexElement(12, VertexElementFormat.Color, VertexElementUsage.Color, 0), - new VertexElement(16, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0), - new VertexElement(24, VertexElementFormat.Color, VertexElementUsage.Color, 1) - ); - - VertexDeclaration IVertexType.VertexDeclaration { - get { return VertexDeclaration; } - } - } - - // #region License - // /* - // Microsoft Public License (Ms-PL) - // MonoGame - Copyright ? 2009 The MonoGame Team - // - // All rights reserved. - // - // This license governs use of the accompanying software. If you use the software, you accept this license. If you do not - // accept the license, do not use the software. - // - // 1. Definitions - // The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under - // U.S. copyright law. - // - // A "contribution" is the original software, or any additions or changes to the software. - // A "contributor" is any person that distributes its contribution under this license. - // "Licensed patents" are a contributor's patent claims that read directly on its contribution. - // - // 2. Grant of Rights - // (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, - // each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. - // (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, - // each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. - // - // 3. Conditions and Limitations - // (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. - // (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, - // your patent license from such contributor to the software ends automatically. - // (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution - // notices that are present in the software. - // (D) If you distribute any portion of the software in source code form, you may do so only under this license by including - // a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object - // code form, you may only do so under a license that complies with this license. - // (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees - // or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent - // permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular - // purpose and non-infringement. - // */ - // #endregion License - // - - /// Draws batched meshes. - public class MeshBatcher { - private readonly List items; - private readonly Queue freeItems; - private VertexPositionColorTextureColor[] vertexArray = { }; - private short[] triangles = { }; - - public MeshBatcher () { - items = new List(256); - freeItems = new Queue(256); - EnsureCapacity(256, 512); - } - - /// Returns a pooled MeshItem. - public MeshItem NextItem (int vertexCount, int triangleCount) { - MeshItem item = freeItems.Count > 0 ? freeItems.Dequeue() : new MeshItem(); - if (item.vertices.Length < vertexCount) item.vertices = new VertexPositionColorTextureColor[vertexCount]; - if (item.triangles.Length < triangleCount) item.triangles = new int[triangleCount]; - item.vertexCount = vertexCount; - item.triangleCount = triangleCount; - items.Add(item); - return item; - } - - private void EnsureCapacity (int vertexCount, int triangleCount) { - if (vertexArray.Length < vertexCount) vertexArray = new VertexPositionColorTextureColor[vertexCount]; - if (triangles.Length < triangleCount) triangles = new short[triangleCount]; - } - - public void Draw (GraphicsDevice device) { - if (items.Count == 0) return; - - int itemCount = items.Count; - int vertexCount = 0, triangleCount = 0; - for (int i = 0; i < itemCount; i++) { - MeshItem item = items[i]; - vertexCount += item.vertexCount; - triangleCount += item.triangleCount; - } - EnsureCapacity(vertexCount, triangleCount); - - vertexCount = 0; - triangleCount = 0; - Texture2D lastTexture = null; - for (int i = 0; i < itemCount; i++) { - MeshItem item = items[i]; - int itemVertexCount = item.vertexCount; - - if (item.texture != lastTexture || vertexCount + itemVertexCount > short.MaxValue) { - FlushVertexArray(device, vertexCount, triangleCount); - vertexCount = 0; - triangleCount = 0; - lastTexture = item.texture; - device.Textures[0] = lastTexture; - } - - int[] itemTriangles = item.triangles; - int itemTriangleCount = item.triangleCount; - for (int ii = 0, t = triangleCount; ii < itemTriangleCount; ii++, t++) - triangles[t] = (short)(itemTriangles[ii] + vertexCount); - triangleCount += itemTriangleCount; - - Array.Copy(item.vertices, 0, vertexArray, vertexCount, itemVertexCount); - vertexCount += itemVertexCount; - - item.texture = null; - freeItems.Enqueue(item); - } - FlushVertexArray(device, vertexCount, triangleCount); - items.Clear(); - } - - private void FlushVertexArray (GraphicsDevice device, int vertexCount, int triangleCount) { - if (vertexCount == 0) return; - device.DrawUserIndexedPrimitives( - PrimitiveType.TriangleList, - vertexArray, 0, vertexCount, - triangles, 0, triangleCount / 3, - VertexPositionColorTextureColor.VertexDeclaration); - } - } - - public class MeshItem { - public Texture2D texture; - public int vertexCount, triangleCount; - public VertexPositionColorTextureColor[] vertices = { }; - public int[] triangles = { }; - } +namespace Spine3_7_94 +{ + public struct VertexPositionColorTextureColor : IVertexType + { + public Vector3 Position; + public Color Color; + public Vector2 TextureCoordinate; + public Color Color2; + + public readonly static VertexDeclaration VertexDeclaration = new VertexDeclaration + ( + new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0), + new VertexElement(12, VertexElementFormat.Color, VertexElementUsage.Color, 0), + new VertexElement(16, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0), + new VertexElement(24, VertexElementFormat.Color, VertexElementUsage.Color, 1) + ); + + VertexDeclaration IVertexType.VertexDeclaration + { + get { return VertexDeclaration; } + } + } + + // #region License + // /* + // Microsoft Public License (Ms-PL) + // MonoGame - Copyright ? 2009 The MonoGame Team + // + // All rights reserved. + // + // This license governs use of the accompanying software. If you use the software, you accept this license. If you do not + // accept the license, do not use the software. + // + // 1. Definitions + // The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under + // U.S. copyright law. + // + // A "contribution" is the original software, or any additions or changes to the software. + // A "contributor" is any person that distributes its contribution under this license. + // "Licensed patents" are a contributor's patent claims that read directly on its contribution. + // + // 2. Grant of Rights + // (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, + // each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. + // (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, + // each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. + // + // 3. Conditions and Limitations + // (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. + // (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, + // your patent license from such contributor to the software ends automatically. + // (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution + // notices that are present in the software. + // (D) If you distribute any portion of the software in source code form, you may do so only under this license by including + // a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object + // code form, you may only do so under a license that complies with this license. + // (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees + // or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent + // permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular + // purpose and non-infringement. + // */ + // #endregion License + // + + /// Draws batched meshes. + public class MeshBatcher + { + private readonly List items; + private readonly Queue freeItems; + private VertexPositionColorTextureColor[] vertexArray = { }; + private short[] triangles = { }; + + public MeshBatcher() + { + items = new List(256); + freeItems = new Queue(256); + EnsureCapacity(256, 512); + } + + /// Returns a pooled MeshItem. + public MeshItem NextItem(int vertexCount, int triangleCount) + { + MeshItem item = freeItems.Count > 0 ? freeItems.Dequeue() : new MeshItem(); + if (item.vertices.Length < vertexCount) item.vertices = new VertexPositionColorTextureColor[vertexCount]; + if (item.triangles.Length < triangleCount) item.triangles = new int[triangleCount]; + item.vertexCount = vertexCount; + item.triangleCount = triangleCount; + items.Add(item); + return item; + } + + private void EnsureCapacity(int vertexCount, int triangleCount) + { + if (vertexArray.Length < vertexCount) vertexArray = new VertexPositionColorTextureColor[vertexCount]; + if (triangles.Length < triangleCount) triangles = new short[triangleCount]; + } + + public void Draw(GraphicsDevice device) + { + if (items.Count == 0) return; + + int itemCount = items.Count; + int vertexCount = 0, triangleCount = 0; + for (int i = 0; i < itemCount; i++) + { + MeshItem item = items[i]; + vertexCount += item.vertexCount; + triangleCount += item.triangleCount; + } + EnsureCapacity(vertexCount, triangleCount); + + vertexCount = 0; + triangleCount = 0; + Texture2D lastTexture = null; + for (int i = 0; i < itemCount; i++) + { + MeshItem item = items[i]; + int itemVertexCount = item.vertexCount; + + if (item.texture != lastTexture || vertexCount + itemVertexCount > short.MaxValue) + { + FlushVertexArray(device, vertexCount, triangleCount); + vertexCount = 0; + triangleCount = 0; + lastTexture = item.texture; + device.Textures[0] = lastTexture; + } + + int[] itemTriangles = item.triangles; + int itemTriangleCount = item.triangleCount; + for (int ii = 0, t = triangleCount; ii < itemTriangleCount; ii++, t++) + triangles[t] = (short)(itemTriangles[ii] + vertexCount); + triangleCount += itemTriangleCount; + + Array.Copy(item.vertices, 0, vertexArray, vertexCount, itemVertexCount); + vertexCount += itemVertexCount; + + item.texture = null; + freeItems.Enqueue(item); + } + FlushVertexArray(device, vertexCount, triangleCount); + items.Clear(); + } + + private void FlushVertexArray(GraphicsDevice device, int vertexCount, int triangleCount) + { + if (vertexCount == 0) return; + device.DrawUserIndexedPrimitives( + PrimitiveType.TriangleList, + vertexArray, 0, vertexCount, + triangles, 0, triangleCount / 3, + VertexPositionColorTextureColor.VertexDeclaration); + } + } + + public class MeshItem + { + public Texture2D texture; + public int vertexCount, triangleCount; + public VertexPositionColorTextureColor[] vertices = { }; + public int[] triangles = { }; + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/XnaLoader/ShapeRenderer.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/XnaLoader/ShapeRenderer.cs index 603c508..c1e09f5 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/XnaLoader/ShapeRenderer.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/XnaLoader/ShapeRenderer.cs @@ -27,140 +27,157 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Spine3_7_94 { - /// - /// Batch drawing of lines and shapes that can be derrived from lines. - /// - /// Call drawing methods in between Begin()/End() - /// - public class ShapeRenderer { - GraphicsDevice device; - List vertices = new List(); - Color color = Color.White; - BasicEffect effect; - public BasicEffect Effect { get { return effect; } set { effect = value; } } - - public ShapeRenderer(GraphicsDevice device) { - this.device = device; - this.effect = new BasicEffect(device); - effect.World = Matrix.Identity; - effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); - effect.TextureEnabled = false; - effect.VertexColorEnabled = true; - } - - public void SetColor(Color color) { - this.color = color; - } - - public void Begin() { - device.RasterizerState = new RasterizerState(); - device.BlendState = BlendState.AlphaBlend; - } - - public void Line(float x1, float y1, float x2, float y2) { - vertices.Add(new VertexPositionColor(new Vector3(x1, y1, 0), color)); - vertices.Add(new VertexPositionColor(new Vector3(x2, y2, 0), color)); - } - - /** Calls {@link #circle(float, float, float, int)} by estimating the number of segments needed for a smooth circle. */ - public void Circle(float x, float y, float radius) { - Circle(x, y, radius, Math.Max(1, (int)(6 * (float)Math.Pow(radius, 1.0f / 3.0f)))); - } - - /** Draws a circle using {@link ShapeType#Line} or {@link ShapeType#Filled}. */ - public void Circle(float x, float y, float radius, int segments) { - if (segments <= 0) throw new ArgumentException("segments must be > 0."); - float angle = 2 * MathUtils.PI / segments; - float cos = MathUtils.Cos(angle); - float sin = MathUtils.Sin(angle); - float cx = radius, cy = 0; - float temp = 0; - - for (int i = 0; i < segments; i++) { - vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); - temp = cx; - cx = cos * cx - sin * cy; - cy = sin * temp + cos * cy; - vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); - } - vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); - - temp = cx; - cx = radius; - cy = 0; - vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); - } - - public void Triangle(float x1, float y1, float x2, float y2, float x3, float y3) { - vertices.Add(new VertexPositionColor(new Vector3(x1, y1, 0), color)); - vertices.Add(new VertexPositionColor(new Vector3(x2, y2, 0), color)); - - vertices.Add(new VertexPositionColor(new Vector3(x2, y2, 0), color)); - vertices.Add(new VertexPositionColor(new Vector3(x3, y3, 0), color)); - - vertices.Add(new VertexPositionColor(new Vector3(x3, y3, 0), color)); - vertices.Add(new VertexPositionColor(new Vector3(x1, y1, 0), color)); - } - - public void X(float x, float y, float len) { - Line(x + len, y + len, x - len, y - len); - Line(x - len, y + len, x + len, y - len); - } - - public void Polygon(float[] polygonVertices, int offset, int count) { - if (count< 3) throw new ArgumentException("Polygon must contain at least 3 vertices"); - - offset <<= 1; - count <<= 1; - - var firstX = polygonVertices[offset]; - var firstY = polygonVertices[offset + 1]; - var last = offset + count; - - for (int i = offset, n = offset + count - 2; i= last) { - x2 = firstX; - y2 = firstY; - } else { - x2 = polygonVertices[i + 2]; - y2 = polygonVertices[i + 3]; - } - - Line(x1, y1, x2, y2); - } - } - - public void Rect(float x, float y, float width, float height) { - Line(x, y, x + width, y); - Line(x + width, y, x + width, y + height); - Line(x + width, y + height, x, y + height); - Line(x, y + height, x, y); - } - - public void End() { - if (vertices.Count == 0) return; - var verticesArray = vertices.ToArray(); - - foreach (EffectPass pass in effect.CurrentTechnique.Passes) { - pass.Apply(); - device.DrawUserPrimitives(PrimitiveType.LineList, verticesArray, 0, verticesArray.Length / 2); - } - - vertices.Clear(); - } - } +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Spine3_7_94 +{ + /// + /// Batch drawing of lines and shapes that can be derrived from lines. + /// + /// Call drawing methods in between Begin()/End() + /// + public class ShapeRenderer + { + GraphicsDevice device; + List vertices = new List(); + Color color = Color.White; + BasicEffect effect; + public BasicEffect Effect { get { return effect; } set { effect = value; } } + + public ShapeRenderer(GraphicsDevice device) + { + this.device = device; + this.effect = new BasicEffect(device); + effect.World = Matrix.Identity; + effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); + effect.TextureEnabled = false; + effect.VertexColorEnabled = true; + } + + public void SetColor(Color color) + { + this.color = color; + } + + public void Begin() + { + device.RasterizerState = new RasterizerState(); + device.BlendState = BlendState.AlphaBlend; + } + + public void Line(float x1, float y1, float x2, float y2) + { + vertices.Add(new VertexPositionColor(new Vector3(x1, y1, 0), color)); + vertices.Add(new VertexPositionColor(new Vector3(x2, y2, 0), color)); + } + + /** Calls {@link #circle(float, float, float, int)} by estimating the number of segments needed for a smooth circle. */ + public void Circle(float x, float y, float radius) + { + Circle(x, y, radius, Math.Max(1, (int)(6 * (float)Math.Pow(radius, 1.0f / 3.0f)))); + } + + /** Draws a circle using {@link ShapeType#Line} or {@link ShapeType#Filled}. */ + public void Circle(float x, float y, float radius, int segments) + { + if (segments <= 0) throw new ArgumentException("segments must be > 0."); + float angle = 2 * MathUtils.PI / segments; + float cos = MathUtils.Cos(angle); + float sin = MathUtils.Sin(angle); + float cx = radius, cy = 0; + float temp = 0; + + for (int i = 0; i < segments; i++) + { + vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); + temp = cx; + cx = cos * cx - sin * cy; + cy = sin * temp + cos * cy; + vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); + } + vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); + + temp = cx; + cx = radius; + cy = 0; + vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); + } + + public void Triangle(float x1, float y1, float x2, float y2, float x3, float y3) + { + vertices.Add(new VertexPositionColor(new Vector3(x1, y1, 0), color)); + vertices.Add(new VertexPositionColor(new Vector3(x2, y2, 0), color)); + + vertices.Add(new VertexPositionColor(new Vector3(x2, y2, 0), color)); + vertices.Add(new VertexPositionColor(new Vector3(x3, y3, 0), color)); + + vertices.Add(new VertexPositionColor(new Vector3(x3, y3, 0), color)); + vertices.Add(new VertexPositionColor(new Vector3(x1, y1, 0), color)); + } + + public void X(float x, float y, float len) + { + Line(x + len, y + len, x - len, y - len); + Line(x - len, y + len, x + len, y - len); + } + + public void Polygon(float[] polygonVertices, int offset, int count) + { + if (count < 3) throw new ArgumentException("Polygon must contain at least 3 vertices"); + + offset <<= 1; + count <<= 1; + + var firstX = polygonVertices[offset]; + var firstY = polygonVertices[offset + 1]; + var last = offset + count; + + for (int i = offset, n = offset + count - 2; i < n; i += 2) + { + var x1 = polygonVertices[i]; + var y1 = polygonVertices[i + 1]; + + var x2 = 0f; + var y2 = 0f; + + if (i + 2 >= last) + { + x2 = firstX; + y2 = firstY; + } + else + { + x2 = polygonVertices[i + 2]; + y2 = polygonVertices[i + 3]; + } + + Line(x1, y1, x2, y2); + } + } + + public void Rect(float x, float y, float width, float height) + { + Line(x, y, x + width, y); + Line(x + width, y, x + width, y + height); + Line(x + width, y + height, x, y + height); + Line(x, y + height, x, y); + } + + public void End() + { + if (vertices.Count == 0) return; + var verticesArray = vertices.ToArray(); + + foreach (EffectPass pass in effect.CurrentTechnique.Passes) + { + pass.Apply(); + device.DrawUserPrimitives(PrimitiveType.LineList, verticesArray, 0, verticesArray.Length / 2); + } + + vertices.Clear(); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/XnaLoader/SkeletonRenderer.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/XnaLoader/SkeletonRenderer.cs index 24bda39..747c5c5 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/XnaLoader/SkeletonRenderer.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/XnaLoader/SkeletonRenderer.cs @@ -27,119 +27,129 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; -using System.Collections.Generic; -using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; -namespace Spine3_7_94 { - /// Draws region and mesh attachments. - public class SkeletonRenderer { - private const int TL = 0; - private const int TR = 1; - private const int BL = 2; - private const int BR = 3; +namespace Spine3_7_94 +{ + /// Draws region and mesh attachments. + public class SkeletonRenderer + { + private const int TL = 0; + private const int TR = 1; + private const int BL = 2; + private const int BR = 3; - SkeletonClipping clipper = new SkeletonClipping(); - GraphicsDevice device; - MeshBatcher batcher; - public MeshBatcher Batcher { get { return batcher; } } - RasterizerState rasterizerState; - float[] vertices = new float[8]; - int[] quadTriangles = { 0, 1, 2, 2, 3, 0 }; - BlendState defaultBlendState; + SkeletonClipping clipper = new SkeletonClipping(); + GraphicsDevice device; + MeshBatcher batcher; + public MeshBatcher Batcher { get { return batcher; } } + RasterizerState rasterizerState; + float[] vertices = new float[8]; + int[] quadTriangles = { 0, 1, 2, 2, 3, 0 }; + BlendState defaultBlendState; - Effect effect; - public Effect Effect { get { return effect; } set { effect = value; } } - public IVertexEffect VertexEffect { get; set; } + Effect effect; + public Effect Effect { get { return effect; } set { effect = value; } } + public IVertexEffect VertexEffect { get; set; } - private bool premultipliedAlpha; - public bool PremultipliedAlpha { get { return premultipliedAlpha; } set { premultipliedAlpha = value; } } + private bool premultipliedAlpha; + public bool PremultipliedAlpha { get { return premultipliedAlpha; } set { premultipliedAlpha = value; } } - public SkeletonRenderer (GraphicsDevice device) { - this.device = device; + public SkeletonRenderer(GraphicsDevice device) + { + this.device = device; - batcher = new MeshBatcher(); + batcher = new MeshBatcher(); - var basicEffect = new BasicEffect(device); - basicEffect.World = Matrix.Identity; - basicEffect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); - basicEffect.TextureEnabled = true; - basicEffect.VertexColorEnabled = true; - effect = basicEffect; + var basicEffect = new BasicEffect(device); + basicEffect.World = Matrix.Identity; + basicEffect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); + basicEffect.TextureEnabled = true; + basicEffect.VertexColorEnabled = true; + effect = basicEffect; - rasterizerState = new RasterizerState(); - rasterizerState.CullMode = CullMode.None; + rasterizerState = new RasterizerState(); + rasterizerState.CullMode = CullMode.None; - Bone.yDown = true; - } + Bone.yDown = true; + } - public void Begin () { - defaultBlendState = premultipliedAlpha ? BlendState.AlphaBlend : BlendState.NonPremultiplied; + public void Begin() + { + defaultBlendState = premultipliedAlpha ? BlendState.AlphaBlend : BlendState.NonPremultiplied; - device.RasterizerState = rasterizerState; - device.BlendState = defaultBlendState; - } + device.RasterizerState = rasterizerState; + device.BlendState = defaultBlendState; + } - public void End () { - foreach (EffectPass pass in effect.CurrentTechnique.Passes) { - pass.Apply(); - batcher.Draw(device); - } - } + public void End() + { + foreach (EffectPass pass in effect.CurrentTechnique.Passes) + { + pass.Apply(); + batcher.Draw(device); + } + } - public void Draw(Skeleton skeleton) { - var drawOrder = skeleton.DrawOrder; - var drawOrderItems = skeleton.DrawOrder.Items; - float skeletonR = skeleton.R, skeletonG = skeleton.G, skeletonB = skeleton.B, skeletonA = skeleton.A; - Color color = new Color(); + public void Draw(Skeleton skeleton) + { + var drawOrder = skeleton.DrawOrder; + var drawOrderItems = skeleton.DrawOrder.Items; + float skeletonR = skeleton.R, skeletonG = skeleton.G, skeletonB = skeleton.B, skeletonA = skeleton.A; + Color color = new Color(); - if (VertexEffect != null) VertexEffect.Begin(skeleton); + if (VertexEffect != null) VertexEffect.Begin(skeleton); - for (int i = 0, n = drawOrder.Count; i < n; i++) { - Slot slot = drawOrderItems[i]; - Attachment attachment = slot.Attachment; + for (int i = 0, n = drawOrder.Count; i < n; i++) + { + Slot slot = drawOrderItems[i]; + Attachment attachment = slot.Attachment; - float attachmentColorR, attachmentColorG, attachmentColorB, attachmentColorA; - Texture2D texture = null; - int verticesCount = 0; - float[] vertices = this.vertices; - int indicesCount = 0; - int[] indices = null; - float[] uvs = null; + float attachmentColorR, attachmentColorG, attachmentColorB, attachmentColorA; + Texture2D texture = null; + int verticesCount = 0; + float[] vertices = this.vertices; + int indicesCount = 0; + int[] indices = null; + float[] uvs = null; - if (attachment is RegionAttachment) { - RegionAttachment regionAttachment = (RegionAttachment)attachment; - attachmentColorR = regionAttachment.R; attachmentColorG = regionAttachment.G; attachmentColorB = regionAttachment.B; attachmentColorA = regionAttachment.A; - AtlasRegion region = (AtlasRegion)regionAttachment.RendererObject; - texture = (Texture2D)region.page.rendererObject; - verticesCount = 4; - regionAttachment.ComputeWorldVertices(slot.Bone, vertices, 0, 2); - indicesCount = 6; - indices = quadTriangles; - uvs = regionAttachment.UVs; - } - else if (attachment is MeshAttachment) { - MeshAttachment mesh = (MeshAttachment)attachment; - attachmentColorR = mesh.R; attachmentColorG = mesh.G; attachmentColorB = mesh.B; attachmentColorA = mesh.A; - AtlasRegion region = (AtlasRegion)mesh.RendererObject; - texture = (Texture2D)region.page.rendererObject; - int vertexCount = mesh.WorldVerticesLength; - if (vertices.Length < vertexCount) vertices = new float[vertexCount]; - verticesCount = vertexCount >> 1; - mesh.ComputeWorldVertices(slot, vertices); - indicesCount = mesh.Triangles.Length; - indices = mesh.Triangles; - uvs = mesh.UVs; - } - else if (attachment is ClippingAttachment) { - ClippingAttachment clip = (ClippingAttachment)attachment; - clipper.ClipStart(slot, clip); - continue; - } - else { - continue; - } + if (attachment is RegionAttachment) + { + RegionAttachment regionAttachment = (RegionAttachment)attachment; + attachmentColorR = regionAttachment.R; attachmentColorG = regionAttachment.G; attachmentColorB = regionAttachment.B; attachmentColorA = regionAttachment.A; + AtlasRegion region = (AtlasRegion)regionAttachment.RendererObject; + texture = (Texture2D)region.page.rendererObject; + verticesCount = 4; + regionAttachment.ComputeWorldVertices(slot.Bone, vertices, 0, 2); + indicesCount = 6; + indices = quadTriangles; + uvs = regionAttachment.UVs; + } + else if (attachment is MeshAttachment) + { + MeshAttachment mesh = (MeshAttachment)attachment; + attachmentColorR = mesh.R; attachmentColorG = mesh.G; attachmentColorB = mesh.B; attachmentColorA = mesh.A; + AtlasRegion region = (AtlasRegion)mesh.RendererObject; + texture = (Texture2D)region.page.rendererObject; + int vertexCount = mesh.WorldVerticesLength; + if (vertices.Length < vertexCount) vertices = new float[vertexCount]; + verticesCount = vertexCount >> 1; + mesh.ComputeWorldVertices(slot, vertices); + indicesCount = mesh.Triangles.Length; + indices = mesh.Triangles; + uvs = mesh.UVs; + } + else if (attachment is ClippingAttachment) + { + ClippingAttachment clip = (ClippingAttachment)attachment; + clipper.ClipStart(slot, clip); + continue; + } + else + { + continue; + } // set blend state @@ -194,74 +204,84 @@ public void Draw(Skeleton skeleton) { break; } - if (device.BlendState != blendState) { + if (device.BlendState != blendState) + { End(); device.BlendState = blendState; } - + // calculate color float a = skeletonA * slot.A * attachmentColorA; - if (premultipliedAlpha) { - color = new Color( - skeletonR * slot.R * attachmentColorR * a, - skeletonG * slot.G * attachmentColorG * a, - skeletonB * slot.B * attachmentColorB * a, a); - } - else { - color = new Color( - skeletonR * slot.R * attachmentColorR, - skeletonG * slot.G * attachmentColorG, - skeletonB * slot.B * attachmentColorB, a); - } + if (premultipliedAlpha) + { + color = new Color( + skeletonR * slot.R * attachmentColorR * a, + skeletonG * slot.G * attachmentColorG * a, + skeletonB * slot.B * attachmentColorB * a, a); + } + else + { + color = new Color( + skeletonR * slot.R * attachmentColorR, + skeletonG * slot.G * attachmentColorG, + skeletonB * slot.B * attachmentColorB, a); + } - Color darkColor = new Color(); - if (slot.HasSecondColor) { - if (premultipliedAlpha) { - darkColor = new Color(slot.R2 * a, slot.G2 * a, slot.B2 * a); - } else { - darkColor = new Color(slot.R2 * a, slot.G2 * a, slot.B2 * a); - } - } - darkColor.A = premultipliedAlpha ? (byte)255 : (byte)0; + Color darkColor = new Color(); + if (slot.HasSecondColor) + { + if (premultipliedAlpha) + { + darkColor = new Color(slot.R2 * a, slot.G2 * a, slot.B2 * a); + } + else + { + darkColor = new Color(slot.R2 * a, slot.G2 * a, slot.B2 * a); + } + } + darkColor.A = premultipliedAlpha ? (byte)255 : (byte)0; - // clip - if (clipper.IsClipping) { - clipper.ClipTriangles(vertices, verticesCount << 1, indices, indicesCount, uvs); - vertices = clipper.ClippedVertices.Items; - verticesCount = clipper.ClippedVertices.Count >> 1; - indices = clipper.ClippedTriangles.Items; - indicesCount = clipper.ClippedTriangles.Count; - uvs = clipper.ClippedUVs.Items; - } + // clip + if (clipper.IsClipping) + { + clipper.ClipTriangles(vertices, verticesCount << 1, indices, indicesCount, uvs); + vertices = clipper.ClippedVertices.Items; + verticesCount = clipper.ClippedVertices.Count >> 1; + indices = clipper.ClippedTriangles.Items; + indicesCount = clipper.ClippedTriangles.Count; + uvs = clipper.ClippedUVs.Items; + } - if (verticesCount == 0 || indicesCount == 0) - continue; + if (verticesCount == 0 || indicesCount == 0) + continue; - // submit to batch - MeshItem item = batcher.NextItem(verticesCount, indicesCount); - item.texture = texture; - for (int ii = 0, nn = indicesCount; ii < nn; ii++) { - item.triangles[ii] = indices[ii]; - } - VertexPositionColorTextureColor[] itemVertices = item.vertices; - for (int ii = 0, v = 0, nn = verticesCount << 1; v < nn; ii++, v += 2) { - itemVertices[ii].Color = color; - itemVertices[ii].Color2 = darkColor; - itemVertices[ii].Position.X = vertices[v]; - itemVertices[ii].Position.Y = vertices[v + 1]; - itemVertices[ii].Position.Z = 0; - itemVertices[ii].TextureCoordinate.X = uvs[v]; - itemVertices[ii].TextureCoordinate.Y = uvs[v + 1]; - if (VertexEffect != null) VertexEffect.Transform(ref itemVertices[ii]); - } + // submit to batch + MeshItem item = batcher.NextItem(verticesCount, indicesCount); + item.texture = texture; + for (int ii = 0, nn = indicesCount; ii < nn; ii++) + { + item.triangles[ii] = indices[ii]; + } + VertexPositionColorTextureColor[] itemVertices = item.vertices; + for (int ii = 0, v = 0, nn = verticesCount << 1; v < nn; ii++, v += 2) + { + itemVertices[ii].Color = color; + itemVertices[ii].Color2 = darkColor; + itemVertices[ii].Position.X = vertices[v]; + itemVertices[ii].Position.Y = vertices[v + 1]; + itemVertices[ii].Position.Z = 0; + itemVertices[ii].TextureCoordinate.X = uvs[v]; + itemVertices[ii].TextureCoordinate.Y = uvs[v + 1]; + if (VertexEffect != null) VertexEffect.Transform(ref itemVertices[ii]); + } - clipper.ClipEnd(slot); - } - clipper.ClipEnd(); - if (VertexEffect != null) VertexEffect.End(); - } - } + clipper.ClipEnd(slot); + } + clipper.ClipEnd(); + if (VertexEffect != null) VertexEffect.End(); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/XnaLoader/VertexEffect.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/XnaLoader/VertexEffect.cs index 904d6bc..0b39e9e 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/XnaLoader/VertexEffect.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/XnaLoader/VertexEffect.cs @@ -28,70 +28,80 @@ *****************************************************************************/ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -namespace Spine3_7_94 { - public interface IVertexEffect { - void Begin(Skeleton skeleton); - void Transform(ref VertexPositionColorTextureColor vertex); - void End(); - } +namespace Spine3_7_94 +{ + public interface IVertexEffect + { + void Begin(Skeleton skeleton); + void Transform(ref VertexPositionColorTextureColor vertex); + void End(); + } - public class JitterEffect : IVertexEffect { - public float JitterX { get; set; } - public float JitterY { get; set; } + public class JitterEffect : IVertexEffect + { + public float JitterX { get; set; } + public float JitterY { get; set; } - public JitterEffect(float jitterX, float jitterY) { - JitterX = jitterX; - JitterY = jitterY; - } + public JitterEffect(float jitterX, float jitterY) + { + JitterX = jitterX; + JitterY = jitterY; + } - public void Begin(Skeleton skeleton) { - } + public void Begin(Skeleton skeleton) + { + } - public void End() { - } + public void End() + { + } - public void Transform(ref VertexPositionColorTextureColor vertex) { - vertex.Position.X += MathUtils.RandomTriangle(-JitterX, JitterY); - vertex.Position.Y += MathUtils.RandomTriangle(-JitterX, JitterY); - } - } + public void Transform(ref VertexPositionColorTextureColor vertex) + { + vertex.Position.X += MathUtils.RandomTriangle(-JitterX, JitterY); + vertex.Position.Y += MathUtils.RandomTriangle(-JitterX, JitterY); + } + } - public class SwirlEffect : IVertexEffect { - private float worldX, worldY, angle; + public class SwirlEffect : IVertexEffect + { + private float worldX, worldY, angle; - public float Radius { get; set; } - public float Angle { get { return angle; } set { angle = value * MathUtils.DegRad; } } - public float CenterX { get; set; } - public float CenterY { get; set; } - public IInterpolation Interpolation { get; set; } + public float Radius { get; set; } + public float Angle { get { return angle; } set { angle = value * MathUtils.DegRad; } } + public float CenterX { get; set; } + public float CenterY { get; set; } + public IInterpolation Interpolation { get; set; } - public SwirlEffect(float radius) { - Radius = radius; - Interpolation = IInterpolation.Pow2; - } + public SwirlEffect(float radius) + { + Radius = radius; + Interpolation = IInterpolation.Pow2; + } - public void Begin(Skeleton skeleton) { - worldX = skeleton.X + CenterX; - worldY = skeleton.Y + CenterY; - } + public void Begin(Skeleton skeleton) + { + worldX = skeleton.X + CenterX; + worldY = skeleton.Y + CenterY; + } - public void End() { - } + public void End() + { + } - public void Transform(ref VertexPositionColorTextureColor vertex) { - float x = vertex.Position.X - worldX; - float y = vertex.Position.Y - worldY; - float dist = (float)Math.Sqrt(x * x + y * y); - if (dist < Radius) { - float theta = Interpolation.Apply(0, angle, (Radius - dist) / Radius); - float cos = MathUtils.Cos(theta), sin = MathUtils.Sin(theta); - vertex.Position.X = cos * x - sin * y + worldX; - vertex.Position.Y = sin * x + cos * y + worldY; - } - } - } + public void Transform(ref VertexPositionColorTextureColor vertex) + { + float x = vertex.Position.X - worldX; + float y = vertex.Position.Y - worldY; + float dist = (float)Math.Sqrt(x * x + y * y); + if (dist < Radius) + { + float theta = Interpolation.Apply(0, angle, (Radius - dist) / Radius); + float cos = MathUtils.Cos(theta), sin = MathUtils.Sin(theta); + vertex.Position.X = cos * x - sin * y + worldX; + vertex.Position.Y = sin * x + cos * y + worldY; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/XnaLoader/XnaTextureLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/XnaLoader/XnaTextureLoader.cs index f29d540..96ab8cb 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/XnaLoader/XnaTextureLoader.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.7.94/XnaLoader/XnaTextureLoader.cs @@ -28,8 +28,6 @@ *****************************************************************************/ using System; -using System.IO; -using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; namespace Spine3_7_94 diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Animation.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Animation.cs index e151c47..c6048df 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Animation.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Animation.cs @@ -30,1790 +30,2079 @@ using System; using System.Collections.Generic; -namespace Spine3_8_95 { - - /// - /// A simple container for a list of timelines and a name. - public class Animation { - internal String name; - internal ExposedList timelines; - internal HashSet timelineIds; - internal float duration; - - public Animation (string name, ExposedList timelines, float duration) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - if (timelines == null) throw new ArgumentNullException("timelines", "timelines cannot be null."); - // Note: avoiding reallocations by adding all hash set entries at - // once (EnsureCapacity() is only available in newer .Net versions). - int[] propertyIDs = new int[timelines.Count]; - for (int i = 0; i < timelines.Count; ++i) { - propertyIDs[i] = timelines.Items[i].PropertyId; - } - this.timelineIds = new HashSet(propertyIDs); - this.name = name; - this.timelines = timelines; - this.duration = duration; - } - - public ExposedList Timelines { get { return timelines; } set { timelines = value; } } - - /// The duration of the animation in seconds, which is the highest time of all keys in the timeline. - public float Duration { get { return duration; } set { duration = value; } } - - /// The animation's name, which is unique across all animations in the skeleton. - public string Name { get { return name; } } - - /// Whether the timeline with the property id is contained in this animation. - public bool HasTimeline (int id) { - return timelineIds.Contains(id); - } - - /// Applies all the animation's timelines to the specified skeleton. - /// - public void Apply (Skeleton skeleton, float lastTime, float time, bool loop, ExposedList events, float alpha, MixBlend blend, - MixDirection direction) { - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - - if (loop && duration != 0) { - time %= duration; - if (lastTime > 0) lastTime %= duration; - } - - ExposedList timelines = this.timelines; - for (int i = 0, n = timelines.Count; i < n; i++) - timelines.Items[i].Apply(skeleton, lastTime, time, events, alpha, blend, direction); - } - - override public string ToString () { - return name; - } - - /// After the first and before the last entry. - /// Index of first value greater than the target. - internal static int BinarySearch (float[] values, float target, int step) { - int low = 0; - int high = values.Length / step - 2; - if (high == 0) return step; - int current = (int)((uint)high >> 1); - while (true) { - if (values[(current + 1) * step] <= target) - low = current + 1; - else - high = current; - if (low == high) return (low + 1) * step; - current = (int)((uint)(low + high) >> 1); - } - } - - /// After the first and before the last entry. - internal static int BinarySearch (float[] values, float target) { - int low = 0; - int high = values.Length - 2; - if (high == 0) return 1; - int current = (int)((uint)high >> 1); - while (true) { - if (values[current + 1] <= target) - low = current + 1; - else - high = current; - if (low == high) return (low + 1); - current = (int)((uint)(low + high) >> 1); - } - } - - internal static int LinearSearch (float[] values, float target, int step) { - for (int i = 0, last = values.Length - step; i <= last; i += step) - if (values[i] > target) return i; - return -1; - } - } - - /// - /// The interface for all timelines. - public interface Timeline { - /// Applies this timeline to the skeleton. - /// The skeleton the timeline is being applied to. This provides access to the bones, slots, and other - /// skeleton components the timeline may change. - /// The time this timeline was last applied. Timelines such as trigger only at specific - /// times rather than every frame. In that case, the timeline triggers everything between lastTime - /// (exclusive) and time (inclusive). - /// The time within the animation. Most timelines find the key before and the key after this time so they can - /// interpolate between the keys. - /// If any events are fired, they are added to this list. Can be null to ignore firing events or if the - /// timeline does not fire events. - /// 0 applies the current or setup value (depending on blend). 1 applies the timeline value. - /// Between 0 and 1 applies a value between the current or setup value 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). - /// Controls how mixing is applied when alpha < 1. - /// Indicates whether the timeline is mixing in or out. Used by timelines which perform instant transitions, - /// such as or , and other such as {@link ScaleTimeline}. - void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, MixBlend blend, MixDirection direction); - /// Uniquely encodes both the type of this timeline and the skeleton property that it affects. - int PropertyId { get; } - } - - /// - /// Controls how a timeline is mixed with the setup or current pose. - /// - public enum MixBlend { - - /// Transitions from the setup value to the timeline value (the current value is not used). Before the first key, the setup - /// value is set. - Setup, - - /// - /// - /// Transitions from the current value to the timeline value. Before the first key, transitions from the current value to - /// the setup value. Timelines which perform instant transitions, such as or - /// , use the setup value before the first key. - /// - /// First is intended for the first animations applied, not for animations layered on top of those. - /// - First, - - /// - /// - /// Transitions from the current value to the timeline value. No change is made before the first key (the current value is - /// kept until the first key). - /// - /// Replace is intended for animations layered on top of others, not for the first animations applied. - /// - Replace, - - /// - /// - /// Transitions from the current value to the current value plus the timeline value. No change is made before the first key - /// (the current value is kept until the first key). - /// - /// Add is intended for animations layered on top of others, not for the first animations applied. - /// - Add - } - - /// - /// Indicates whether a timeline's alpha is mixing out over time toward 0 (the setup or current pose value) or - /// mixing in toward 1 (the timeline's value). - /// - public enum MixDirection { - In, - Out - } - - internal enum TimelineType { - Rotate = 0, Translate, Scale, Shear, // - Attachment, Color, Deform, // - Event, DrawOrder, // - IkConstraint, TransformConstraint, // - PathConstraintPosition, PathConstraintSpacing, PathConstraintMix, // - TwoColor - } - - /// An interface for timelines which change the property of a bone. - public interface IBoneTimeline { - /// The index of the bone in that will be changed. - int BoneIndex { get; } - } - - /// An interface for timelines which change the property of a slot. - public interface ISlotTimeline { - /// The index of the slot in that will be changed. - int SlotIndex { get; } - } - - /// The base class for timelines that use interpolation between key frame values. - abstract public class CurveTimeline : Timeline { - protected const float LINEAR = 0, STEPPED = 1, BEZIER = 2; - protected const int BEZIER_SIZE = 10 * 2 - 1; - - internal float[] curves; // type, x, y, ... - /// The number of key frames for this timeline. - public int FrameCount { get { return curves.Length / BEZIER_SIZE + 1; } } - - public CurveTimeline (int frameCount) { - if (frameCount <= 0) throw new ArgumentOutOfRangeException("frameCount must be > 0: "); - curves = new float[(frameCount - 1) * BEZIER_SIZE]; - } - - abstract public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, MixDirection direction); - - abstract public int PropertyId { get; } - - /// Sets the specified key frame to linear interpolation. - public void SetLinear (int frameIndex) { - curves[frameIndex * BEZIER_SIZE] = LINEAR; - } - - /// Sets the specified key frame to stepped interpolation. - public void SetStepped (int frameIndex) { - curves[frameIndex * BEZIER_SIZE] = STEPPED; - } - - /// Returns the interpolation type for the specified key frame. - /// Linear is 0, stepped is 1, Bezier is 2. - public float GetCurveType (int frameIndex) { - int index = frameIndex * BEZIER_SIZE; - if (index == curves.Length) return LINEAR; - float type = curves[index]; - if (type == LINEAR) return LINEAR; - if (type == STEPPED) return STEPPED; - return BEZIER; - } - - /// Sets the specified key frame to Bezier interpolation. cx1 and cx2 are from 0 to 1, - /// representing the percent of time between the two key frames. cy1 and cy2 are the percent of the - /// difference between the key frame's values. - public void SetCurve (int frameIndex, float cx1, float cy1, float cx2, float cy2) { - float tmpx = (-cx1 * 2 + cx2) * 0.03f, tmpy = (-cy1 * 2 + cy2) * 0.03f; - float dddfx = ((cx1 - cx2) * 3 + 1) * 0.006f, dddfy = ((cy1 - cy2) * 3 + 1) * 0.006f; - float ddfx = tmpx * 2 + dddfx, ddfy = tmpy * 2 + dddfy; - float dfx = cx1 * 0.3f + tmpx + dddfx * 0.16666667f, dfy = cy1 * 0.3f + tmpy + dddfy * 0.16666667f; - - int i = frameIndex * BEZIER_SIZE; - float[] curves = this.curves; - curves[i++] = BEZIER; - - float x = dfx, y = dfy; - for (int n = i + BEZIER_SIZE - 1; i < n; i += 2) { - curves[i] = x; - curves[i + 1] = y; - dfx += ddfx; - dfy += ddfy; - ddfx += dddfx; - ddfy += dddfy; - x += dfx; - y += dfy; - } - } - - /// Returns the interpolated percentage for the specified key frame and linear percentage. - public float GetCurvePercent (int frameIndex, float percent) { - percent = MathUtils.Clamp (percent, 0, 1); - float[] curves = this.curves; - int i = frameIndex * BEZIER_SIZE; - float type = curves[i]; - if (type == LINEAR) return percent; - if (type == STEPPED) return 0; - i++; - float x = 0; - for (int start = i, n = i + BEZIER_SIZE - 1; i < n; i += 2) { - x = curves[i]; - if (x >= percent) { - if (i == start) return curves[i + 1] * percent / x; // First point is 0,0. - float prevX = curves[i - 2], prevY = curves[i - 1]; - return prevY + (curves[i + 1] - prevY) * (percent - prevX) / (x - prevX); - } - } - float y = curves[i - 1]; - return y + (1 - y) * (percent - x) / (1 - x); // Last point is 1,1. - } - } - - /// Changes a bone's local . - public class RotateTimeline : CurveTimeline, IBoneTimeline { - public const int ENTRIES = 2; - internal const int PREV_TIME = -2, PREV_ROTATION = -1; - internal const int ROTATION = 1; - - internal int boneIndex; - internal float[] frames; // time, degrees, ... - - public RotateTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount << 1]; - } - - override public int PropertyId { - get { return ((int)TimelineType.Rotate << 24) + boneIndex; } - } - /// The index of the bone in that will be changed. - public int BoneIndex { - set { - if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0."); - this.boneIndex = value; - } - get { - return boneIndex; - } - } - /// The time in seconds and rotation in degrees for each key frame. - public float[] Frames { get { return frames; } set { frames = value; } } - - /// Sets the time in seconds and the rotation in degrees for the specified key frame. - public void SetFrame (int frameIndex, float time, float degrees) { - frameIndex <<= 1; - frames[frameIndex] = time; - frames[frameIndex + ROTATION] = degrees; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - if (!bone.active) return; - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case MixBlend.Setup: - bone.rotation = bone.data.rotation; - return; - case MixBlend.First: - float r = bone.data.rotation - bone.rotation; - bone.rotation += (r - (16384 - (int)(16384.499999999996 - r / 360)) * 360) * alpha; - return; - } - return; - } - - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - float r = frames[frames.Length + PREV_ROTATION]; - switch (blend) { - case MixBlend.Setup: - bone.rotation = bone.data.rotation + r * alpha; - break; - case MixBlend.First: - case MixBlend.Replace: - r += bone.data.rotation - bone.rotation; - r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; - goto case MixBlend.Add; // Fall through. - - case MixBlend.Add: - bone.rotation += r * alpha; - break; - } - return; - } - - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - float prevRotation = frames[frame + PREV_ROTATION]; - float frameTime = frames[frame]; - float percent = GetCurvePercent((frame >> 1) - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - // scope for 'r' to prevent compile error. - { - float r = frames[frame + ROTATION] - prevRotation; - r = prevRotation + (r - (16384 - (int)(16384.499999999996 - r / 360)) * 360) * percent; - switch (blend) { - case MixBlend.Setup: - bone.rotation = bone.data.rotation + (r - (16384 - (int)(16384.499999999996 - r / 360)) * 360) * alpha; - break; - case MixBlend.First: - case MixBlend.Replace: - r += bone.data.rotation - bone.rotation; - goto case MixBlend.Add; // Fall through. - case MixBlend.Add: - bone.rotation += (r - (16384 - (int)(16384.499999999996 - r / 360)) * 360) * alpha; - break; - } - } - } - } - - /// Changes a bone's local and . - public class TranslateTimeline : CurveTimeline, IBoneTimeline { - public const int ENTRIES = 3; - protected const int PREV_TIME = -3, PREV_X = -2, PREV_Y = -1; - protected const int X = 1, Y = 2; - - internal int boneIndex; - internal float[] frames; // time, x, y, ... - - public TranslateTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - override public int PropertyId { - get { return ((int)TimelineType.Translate << 24) + boneIndex; } - } - - /// The index of the bone in that will be changed. - public int BoneIndex { - set { - if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0."); - this.boneIndex = value; - } - get { - return boneIndex; - } - } - /// The time in seconds, x, and y values for each key frame. - public float[] Frames { get { return frames; } set { frames = value; } } - - - /// Sets the time in seconds, x, and y values for the specified key frame. - public void SetFrame (int frameIndex, float time, float x, float y) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + X] = x; - frames[frameIndex + Y] = y; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - if (!bone.active) return; - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - 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; - return; - } - return; - } - - float x, y; - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - x = frames[frames.Length + PREV_X]; - y = frames[frames.Length + PREV_Y]; - } else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - x = frames[frame + PREV_X]; - y = frames[frame + PREV_Y]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - x += (frames[frame + X] - x) * percent; - y += (frames[frame + Y] - y) * percent; - } - 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; - break; - } - } - } - - /// Changes a bone's local and . - public class ScaleTimeline : TranslateTimeline, IBoneTimeline { - public ScaleTimeline (int frameCount) - : base(frameCount) { - } - - override public int PropertyId { - get { return ((int)TimelineType.Scale << 24) + boneIndex; } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - if (!bone.active) return; - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - 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; - return; - } - return; - } - - float x, y; - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - x = frames[frames.Length + PREV_X] * bone.data.scaleX; - y = frames[frames.Length + PREV_Y] * bone.data.scaleY; - } else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - x = frames[frame + PREV_X]; - y = frames[frame + PREV_Y]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - x = (x + (frames[frame + X] - x) * percent) * bone.data.scaleX; - y = (y + (frames[frame + Y] - y) * percent) * 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 { - // Mixing out uses sign of setup or current pose, else use sign of key. - float bx, by; - if (direction == MixDirection.Out) { - switch (blend) { - case MixBlend.Setup: - bx = bone.data.scaleX; - by = bone.data.scaleY; - bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha; - bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha; - break; - case MixBlend.First: - case MixBlend.Replace: - bx = bone.scaleX; - by = bone.scaleY; - bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha; - bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha; - break; - case MixBlend.Add: - bx = bone.scaleX; - by = bone.scaleY; - bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bone.data.scaleX) * alpha; - bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - bone.data.scaleY) * alpha; - break; - } - } else { - switch (blend) { - case MixBlend.Setup: - bx = Math.Abs(bone.data.scaleX) * Math.Sign(x); - by = Math.Abs(bone.data.scaleY) * Math.Sign(y); - bone.scaleX = bx + (x - bx) * alpha; - bone.scaleY = by + (y - by) * alpha; - break; - case MixBlend.First: - case MixBlend.Replace: - bx = Math.Abs(bone.scaleX) * Math.Sign(x); - by = Math.Abs(bone.scaleY) * Math.Sign(y); - bone.scaleX = bx + (x - bx) * alpha; - bone.scaleY = by + (y - by) * alpha; - break; - case MixBlend.Add: - bx = Math.Sign(x); - by = Math.Sign(y); - bone.scaleX = Math.Abs(bone.scaleX) * bx + (x - Math.Abs(bone.data.scaleX) * bx) * alpha; - bone.scaleY = Math.Abs(bone.scaleY) * by + (y - Math.Abs(bone.data.scaleY) * by) * alpha; - break; - } - } - } - } - } - - /// Changes a bone's local and . - public class ShearTimeline : TranslateTimeline, IBoneTimeline { - public ShearTimeline (int frameCount) - : base(frameCount) { - } - - override public int PropertyId { - get { return ((int)TimelineType.Shear << 24) + boneIndex; } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - if (!bone.active) return; - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - 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; - return; - } - return; - } - - float x, y; - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - x = frames[frames.Length + PREV_X]; - y = frames[frames.Length + PREV_Y]; - } else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - x = frames[frame + PREV_X]; - y = frames[frame + PREV_Y]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - x = x + (frames[frame + X] - x) * percent; - y = y + (frames[frame + Y] - y) * percent; - } - 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; - break; - } - } - } - - /// Changes a slot's . - public class ColorTimeline : CurveTimeline, ISlotTimeline { - public const int ENTRIES = 5; - protected const int PREV_TIME = -5, PREV_R = -4, PREV_G = -3, PREV_B = -2, PREV_A = -1; - protected const int R = 1, G = 2, B = 3, A = 4; - - internal int slotIndex; - internal float[] frames; // time, r, g, b, a, ... - - public ColorTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - override public int PropertyId { - get { return ((int)TimelineType.Color << 24) + slotIndex; } - } - - /// The index of the slot in that will be changed. - public int SlotIndex { - set { - if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0."); - this.slotIndex = value; - } - get { - return slotIndex; - } - } - /// The time in seconds, red, green, blue, and alpha values for each key frame. - public float[] Frames { get { return frames; } set { frames = value; } } - - /// Sets the time in seconds, red, green, blue, and alpha for the specified key frame. - public void SetFrame (int frameIndex, float time, float r, float g, float b, float a) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + R] = r; - frames[frameIndex + G] = g; - frames[frameIndex + B] = b; - frames[frameIndex + A] = a; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Slot slot = skeleton.slots.Items[slotIndex]; - if (!slot.bone.active) return; - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - var slotData = slot.data; - switch (blend) { - case MixBlend.Setup: - slot.r = slotData.r; - slot.g = slotData.g; - slot.b = slotData.b; - slot.a = slotData.a; - return; - case MixBlend.First: - slot.r += (slotData.r - slot.r) * alpha; - slot.g += (slotData.g - slot.g) * alpha; - slot.b += (slotData.b - slot.b) * alpha; - slot.a += (slotData.a - slot.a) * alpha; - slot.ClampColor(); - return; - } - return; - } - - float r, g, b, a; - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - int i = frames.Length; - r = frames[i + PREV_R]; - g = frames[i + PREV_G]; - b = frames[i + PREV_B]; - a = frames[i + PREV_A]; - } else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - r = frames[frame + PREV_R]; - g = frames[frame + PREV_G]; - b = frames[frame + PREV_B]; - a = frames[frame + PREV_A]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - r += (frames[frame + R] - r) * percent; - g += (frames[frame + G] - g) * percent; - b += (frames[frame + B] - b) * percent; - a += (frames[frame + A] - a) * percent; - } - if (alpha == 1) { - slot.r = r; - slot.g = g; - slot.b = b; - slot.a = a; - slot.ClampColor(); - } else { - float br, bg, bb, ba; - if (blend == MixBlend.Setup) { - br = slot.data.r; - bg = slot.data.g; - bb = slot.data.b; - ba = slot.data.a; - } else { - br = slot.r; - bg = slot.g; - bb = slot.b; - ba = slot.a; - } - slot.r = br + ((r - br) * alpha); - slot.g = bg + ((g - bg) * alpha); - slot.b = bb + ((b - bb) * alpha); - slot.a = ba + ((a - ba) * alpha); - slot.ClampColor(); - } - } - } - - /// Changes a slot's and for two color tinting. - public class TwoColorTimeline : CurveTimeline, ISlotTimeline { - public const int ENTRIES = 8; - protected const int PREV_TIME = -8, PREV_R = -7, PREV_G = -6, PREV_B = -5, PREV_A = -4; - protected const int PREV_R2 = -3, PREV_G2 = -2, PREV_B2 = -1; - protected const int R = 1, G = 2, B = 3, A = 4, R2 = 5, G2 = 6, B2 = 7; - - internal int slotIndex; - internal float[] frames; // time, r, g, b, a, r2, g2, b2, ... - - public TwoColorTimeline (int frameCount) : - base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - override public int PropertyId { - get { return ((int)TimelineType.TwoColor << 24) + slotIndex; } - } - - /// The index of the slot in that will be changed. - public int SlotIndex { - set { - if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0."); - this.slotIndex = value; - } - get { - return slotIndex; - } - } - /// The time in seconds, red, green, blue, and alpha values for each key frame. - public float[] Frames { get { return frames; } } - - /// Sets the time in seconds, light, and dark colors for the specified key frame.. - public void SetFrame (int frameIndex, float time, float r, float g, float b, float a, float r2, float g2, float b2) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + R] = r; - frames[frameIndex + G] = g; - frames[frameIndex + B] = b; - frames[frameIndex + A] = a; - frames[frameIndex + R2] = r2; - frames[frameIndex + G2] = g2; - frames[frameIndex + B2] = b2; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Slot slot = skeleton.slots.Items[slotIndex]; - if (!slot.bone.active) return; - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - var slotData = slot.data; - switch (blend) { - case MixBlend.Setup: - // slot.color.set(slot.data.color); - // slot.darkColor.set(slot.data.darkColor); - slot.r = slotData.r; - slot.g = slotData.g; - slot.b = slotData.b; - slot.a = slotData.a; - slot.ClampColor(); - slot.r2 = slotData.r2; - slot.g2 = slotData.g2; - slot.b2 = slotData.b2; - slot.ClampSecondColor(); - return; - case MixBlend.First: - slot.r += (slot.r - slotData.r) * alpha; - slot.g += (slot.g - slotData.g) * alpha; - slot.b += (slot.b - slotData.b) * alpha; - slot.a += (slot.a - slotData.a) * alpha; - slot.ClampColor(); - slot.r2 += (slot.r2 - slotData.r2) * alpha; - slot.g2 += (slot.g2 - slotData.g2) * alpha; - slot.b2 += (slot.b2 - slotData.b2) * alpha; - slot.ClampSecondColor(); - return; - } - return; - } - - float r, g, b, a, r2, g2, b2; - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - int i = frames.Length; - r = frames[i + PREV_R]; - g = frames[i + PREV_G]; - b = frames[i + PREV_B]; - a = frames[i + PREV_A]; - r2 = frames[i + PREV_R2]; - g2 = frames[i + PREV_G2]; - b2 = frames[i + PREV_B2]; - } else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - r = frames[frame + PREV_R]; - g = frames[frame + PREV_G]; - b = frames[frame + PREV_B]; - a = frames[frame + PREV_A]; - r2 = frames[frame + PREV_R2]; - g2 = frames[frame + PREV_G2]; - b2 = frames[frame + PREV_B2]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - r += (frames[frame + R] - r) * percent; - g += (frames[frame + G] - g) * percent; - b += (frames[frame + B] - b) * percent; - a += (frames[frame + A] - a) * percent; - r2 += (frames[frame + R2] - r2) * percent; - g2 += (frames[frame + G2] - g2) * percent; - b2 += (frames[frame + B2] - b2) * percent; - } - if (alpha == 1) { - slot.r = r; - slot.g = g; - slot.b = b; - slot.a = a; - slot.ClampColor(); - slot.r2 = r2; - slot.g2 = g2; - slot.b2 = b2; - slot.ClampSecondColor(); - } else { - float br, bg, bb, ba, br2, bg2, bb2; - if (blend == MixBlend.Setup) { - br = slot.data.r; - bg = slot.data.g; - bb = slot.data.b; - ba = slot.data.a; - br2 = slot.data.r2; - bg2 = slot.data.g2; - bb2 = slot.data.b2; - } else { - br = slot.r; - bg = slot.g; - bb = slot.b; - ba = slot.a; - br2 = slot.r2; - bg2 = slot.g2; - bb2 = slot.b2; - } - slot.r = br + ((r - br) * alpha); - slot.g = bg + ((g - bg) * alpha); - slot.b = bb + ((b - bb) * alpha); - slot.a = ba + ((a - ba) * alpha); - slot.ClampColor(); - slot.r2 = br2 + ((r2 - br2) * alpha); - slot.g2 = bg2 + ((g2 - bg2) * alpha); - slot.b2 = bb2 + ((b2 - bb2) * alpha); - slot.ClampSecondColor(); - } - } - - } - - /// Changes a slot's . - public class AttachmentTimeline : Timeline, ISlotTimeline { - internal int slotIndex; - internal float[] frames; // time, ... - internal string[] attachmentNames; - - public AttachmentTimeline (int frameCount) { - frames = new float[frameCount]; - attachmentNames = new String[frameCount]; - } - - public int PropertyId { - get { return ((int)TimelineType.Attachment << 24) + slotIndex; } - } - - /// The number of key frames for this timeline. - public int FrameCount { get { return frames.Length; } } - - /// The index of the slot in that will be changed. - public int SlotIndex { - set { - if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0."); - this.slotIndex = value; - } - get { - return slotIndex; - } - } - - /// The time in seconds for each key frame. - public float[] Frames { get { return frames; } set { frames = value; } } - - /// The attachment name for each key frame. May contain null values to clear the attachment. - public string[] AttachmentNames { get { return attachmentNames; } set { attachmentNames = value; } } - - /// Sets the time in seconds and the attachment name for the specified key frame. - public void SetFrame (int frameIndex, float time, String attachmentName) { - frames[frameIndex] = time; - attachmentNames[frameIndex] = attachmentName; - } - - public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Slot slot = skeleton.slots.Items[slotIndex]; - if (!slot.bone.active) return; - if (direction == MixDirection.Out) { - if (blend == MixBlend.Setup) SetAttachment(skeleton, slot, slot.data.attachmentName); - return; - } - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - if (blend == MixBlend.Setup || blend == MixBlend.First) SetAttachment(skeleton, slot, slot.data.attachmentName); - return; - } - - int frameIndex; - if (time >= frames[frames.Length - 1]) // Time is after last frame. - frameIndex = frames.Length - 1; - else - frameIndex = Animation.BinarySearch(frames, time) - 1; - - SetAttachment(skeleton, slot, attachmentNames[frameIndex]); - } - - private void SetAttachment (Skeleton skeleton, Slot slot, string attachmentName) { - slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); - } - } - - /// Changes a slot's to deform a . - public class DeformTimeline : CurveTimeline, ISlotTimeline { - internal int slotIndex; - internal VertexAttachment attachment; - internal float[] frames; // time, ... - internal float[][] frameVertices; - - public DeformTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount]; - frameVertices = new float[frameCount][]; - } - - override public int PropertyId { - get { return ((int)TimelineType.Deform << 27) + attachment.id + slotIndex; } - } - - /// The index of the slot in that will be changed. - public int SlotIndex { - set { - if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0."); - this.slotIndex = value; - } - get { - return slotIndex; - } - } - /// The attachment that will be deformed. - public VertexAttachment Attachment { get { return attachment; } set { attachment = value; } } - - /// The time in seconds for each key frame. - public float[] Frames { get { return frames; } set { frames = value; } } - - /// The vertices for each key frame. - public float[][] Vertices { get { return frameVertices; } set { frameVertices = value; } } - - - /// Sets the time in seconds and the vertices for the specified key frame. - /// Vertex positions for an unweighted VertexAttachment, or deform offsets if it has weights. - public void SetFrame (int frameIndex, float time, float[] vertices) { - frames[frameIndex] = time; - frameVertices[frameIndex] = vertices; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Slot slot = skeleton.slots.Items[slotIndex]; - if (!slot.bone.active) return; - VertexAttachment vertexAttachment = slot.attachment as VertexAttachment; - if (vertexAttachment == null || vertexAttachment.DeformAttachment != attachment) return; - - var deformArray = slot.Deform; - if (deformArray.Count == 0) blend = MixBlend.Setup; - - float[][] frameVertices = this.frameVertices; - int vertexCount = frameVertices[0].Length; - float[] frames = this.frames; - float[] deform; - - if (time < frames[0]) { // Time is before first frame. - - switch (blend) { - case MixBlend.Setup: - deformArray.Clear(); - return; - case MixBlend.First: - if (alpha == 1) { - deformArray.Clear(); - return; - } - - // deformArray.SetSize(vertexCount) // Ensure size and preemptively set count. - if (deformArray.Capacity < vertexCount) deformArray.Capacity = vertexCount; - deformArray.Count = vertexCount; - deform = deformArray.Items; - - if (vertexAttachment.bones == null) { - // Unweighted vertex positions. - float[] setupVertices = vertexAttachment.vertices; - for (int i = 0; i < vertexCount; i++) - deform[i] += (setupVertices[i] - deform[i]) * alpha; - } else { - // Weighted deform offsets. - alpha = 1 - alpha; - for (int i = 0; i < vertexCount; i++) - deform[i] *= alpha; - } - return; - default: - return; - } - - } - - // deformArray.SetSize(vertexCount) // Ensure size and preemptively set count. - if (deformArray.Capacity < vertexCount) deformArray.Capacity = vertexCount; - deformArray.Count = vertexCount; - deform = deformArray.Items; - - if (time >= frames[frames.Length - 1]) { // Time is after last frame. - - float[] lastVertices = frameVertices[frames.Length - 1]; - if (alpha == 1) { - if (blend == MixBlend.Add) { - if (vertexAttachment.bones == null) { - // Unweighted vertex positions, no alpha. - float[] setupVertices = vertexAttachment.vertices; - for (int i = 0; i < vertexCount; i++) - deform[i] += lastVertices[i] - setupVertices[i]; - } else { - // Weighted deform offsets, no alpha. - for (int i = 0; i < vertexCount; i++) - deform[i] += lastVertices[i]; - } - } else { - // Vertex positions or deform offsets, no alpha. - Array.Copy(lastVertices, 0, deform, 0, vertexCount); - } - } else { - switch (blend) { - case MixBlend.Setup: { - if (vertexAttachment.bones == null) { - // Unweighted vertex positions, with alpha. - float[] setupVertices = vertexAttachment.vertices; - for (int i = 0; i < vertexCount; i++) { - float setup = setupVertices[i]; - deform[i] = setup + (lastVertices[i] - setup) * alpha; - } - } else { - // Weighted deform offsets, with alpha. - for (int 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 (int i = 0; i < vertexCount; i++) - deform[i] += (lastVertices[i] - deform[i]) * alpha; - break; - case MixBlend.Add: - if (vertexAttachment.bones == null) { - // Unweighted vertex positions, no alpha. - float[] setupVertices = vertexAttachment.vertices; - for (int i = 0; i < vertexCount; i++) - deform[i] += (lastVertices[i] - setupVertices[i]) * alpha; - } else { - // Weighted deform offsets, alpha. - for (int i = 0; i < vertexCount; i++) - deform[i] += lastVertices[i] * alpha; - } - break; - } - } - return; - } - - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time); - float[] prevVertices = frameVertices[frame - 1]; - float[] nextVertices = frameVertices[frame]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame - 1, 1 - (time - frameTime) / (frames[frame - 1] - frameTime)); - - if (alpha == 1) { - if (blend == MixBlend.Add) { - if (vertexAttachment.bones == null) { - // Unweighted vertex positions, no alpha. - float[] setupVertices = vertexAttachment.vertices; - for (int 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 (int 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 (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - deform[i] = prev + (nextVertices[i] - prev) * percent; - } - } - } else { - switch (blend) { - case MixBlend.Setup: { - if (vertexAttachment.bones == null) { - // Unweighted vertex positions, with alpha. - float[] setupVertices = vertexAttachment.vertices; - for (int 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 (int 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 (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - deform[i] += (prev + (nextVertices[i] - prev) * percent - deform[i]) * alpha; - } - break; - } - case MixBlend.Add: { - if (vertexAttachment.bones == null) { - // Unweighted vertex positions, with alpha. - float[] setupVertices = vertexAttachment.vertices; - for (int 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 (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - deform[i] += (prev + (nextVertices[i] - prev) * percent) * alpha; - } - } - break; - } - } - } - } - } - - /// Fires an when specific animation times are reached. - public class EventTimeline : Timeline { - internal float[] frames; // time, ... - private Event[] events; - - public EventTimeline (int frameCount) { - frames = new float[frameCount]; - events = new Event[frameCount]; - } - - public int PropertyId { - get { return ((int)TimelineType.Event << 24); } - } - - /// The number of key frames for this timeline. - public int FrameCount { get { return frames.Length; } } - - /// The time in seconds for each key frame. - public float[] Frames { get { return frames; } set { frames = value; } } - - /// The event for each key frame. - public Event[] Events { get { return events; } set { events = value; } } - - /// Sets the time in seconds and the event for the specified key frame. - public void SetFrame (int frameIndex, Event e) { - frames[frameIndex] = e.Time; - events[frameIndex] = e; - } - - /// Fires events for frames > lastTime and <= time. - public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - if (firedEvents == null) return; - float[] frames = this.frames; - int frameCount = frames.Length; - - if (lastTime > time) { // Fire events after last time for looped animations. - Apply(skeleton, lastTime, int.MaxValue, firedEvents, alpha, blend, direction); - lastTime = -1f; - } else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame. - return; - if (time < frames[0]) return; // Time is before first frame. - - int frame; - if (lastTime < frames[0]) - frame = 0; - else { - frame = Animation.BinarySearch(frames, lastTime); - float frameTime = frames[frame]; - while (frame > 0) { // Fire multiple events with the same frame. - if (frames[frame - 1] != frameTime) break; - frame--; - } - } - for (; frame < frameCount && time >= frames[frame]; frame++) - firedEvents.Add(events[frame]); - } - } - - /// Changes a skeleton's . - public class DrawOrderTimeline : Timeline { - internal float[] frames; // time, ... - private int[][] drawOrders; - - public DrawOrderTimeline (int frameCount) { - frames = new float[frameCount]; - drawOrders = new int[frameCount][]; - } - - public int PropertyId { - get { return ((int)TimelineType.DrawOrder << 24); } - } - - /// The number of key frames for this timeline. - public int FrameCount { get { return frames.Length; } } - - /// The time in seconds for each key frame. - public float[] Frames { get { return frames; } set { frames = value; } } // time, ... - - /// The draw order for each key frame. - /// . - public int[][] DrawOrders { get { return drawOrders; } set { drawOrders = value; } } - - /// Sets the time in seconds and the draw order for the specified key frame. - /// For each slot in the index of the new draw order. May be null to use setup pose - /// draw order.. - public void SetFrame (int frameIndex, float time, int[] drawOrder) { - frames[frameIndex] = time; - drawOrders[frameIndex] = drawOrder; - } - - public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - ExposedList drawOrder = skeleton.drawOrder; - ExposedList slots = skeleton.slots; - if (direction == MixDirection.Out) { - if (blend == MixBlend.Setup) Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count); - return; - } - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - if (blend == MixBlend.Setup || blend == MixBlend.First) Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count); - return; - } - - int frame; - if (time >= frames[frames.Length - 1]) // Time is after last frame. - frame = frames.Length - 1; - else - frame = Animation.BinarySearch(frames, time) - 1; - - int[] drawOrderToSetupIndex = drawOrders[frame]; - if (drawOrderToSetupIndex == null) { - Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count); - } else { - var drawOrderItems = drawOrder.Items; - var slotsItems = slots.Items; - for (int i = 0, n = drawOrderToSetupIndex.Length; i < n; i++) - drawOrderItems[i] = slotsItems[drawOrderToSetupIndex[i]]; - } - } - } - - /// Changes an IK constraint's , , - /// , , and . - public class IkConstraintTimeline : CurveTimeline { - public const int ENTRIES = 6; - private const int PREV_TIME = -6, PREV_MIX = -5, PREV_SOFTNESS = -4, PREV_BEND_DIRECTION = -3, PREV_COMPRESS = -2, - PREV_STRETCH = -1; - private const int MIX = 1, SOFTNESS = 2, BEND_DIRECTION = 3, COMPRESS = 4, STRETCH = 5; - - internal int ikConstraintIndex; - internal float[] frames; // time, mix, softness, bendDirection, compress, stretch, ... - - public IkConstraintTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - override public int PropertyId { - get { return ((int)TimelineType.IkConstraint << 24) + ikConstraintIndex; } - } - - /// The index of the IK constraint slot in that will be changed. - public int IkConstraintIndex { - set { - if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0."); - this.ikConstraintIndex = value; - } - get { - return ikConstraintIndex; - } - } - - /// The time in seconds, mix, softness, bend direction, compress, and stretch for each key frame. - public float[] Frames { get { return frames; } set { frames = value; } } - - /// Sets the time in seconds, mix, softness, bend direction, compress, and stretch for the specified key frame. - public void SetFrame (int frameIndex, float time, float mix, float softness, int bendDirection, bool compress, - bool stretch) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + MIX] = mix; - frames[frameIndex + SOFTNESS] = softness; - frames[frameIndex + BEND_DIRECTION] = bendDirection; - frames[frameIndex + COMPRESS] = compress ? 1 : 0; - frames[frameIndex + STRETCH] = stretch ? 1 : 0; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - IkConstraint constraint = skeleton.ikConstraints.Items[ikConstraintIndex]; - if (!constraint.active) return; - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - 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; - } - return; - } - - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - if (blend == MixBlend.Setup) { - constraint.mix = constraint.data.mix + (frames[frames.Length + PREV_MIX] - constraint.data.mix) * alpha; - constraint.softness = constraint.data.softness - + (frames[frames.Length + PREV_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 = (int)frames[frames.Length + PREV_BEND_DIRECTION]; - constraint.compress = frames[frames.Length + PREV_COMPRESS] != 0; - constraint.stretch = frames[frames.Length + PREV_STRETCH] != 0; - } - } else { - constraint.mix += (frames[frames.Length + PREV_MIX] - constraint.mix) * alpha; - constraint.softness += (frames[frames.Length + PREV_SOFTNESS] - constraint.softness) * alpha; - if (direction == MixDirection.In) { - constraint.bendDirection = (int)frames[frames.Length + PREV_BEND_DIRECTION]; - constraint.compress = frames[frames.Length + PREV_COMPRESS] != 0; - constraint.stretch = frames[frames.Length + PREV_STRETCH] != 0; - } - } - return; - } - - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - float mix = frames[frame + PREV_MIX]; - float softness = frames[frame + PREV_SOFTNESS]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - if (blend == MixBlend.Setup) { - constraint.mix = constraint.data.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.data.mix) * alpha; - constraint.softness = constraint.data.softness - + (softness + (frames[frame + SOFTNESS] - softness) * percent - 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 = (int)frames[frame + PREV_BEND_DIRECTION]; - constraint.compress = frames[frame + PREV_COMPRESS] != 0; - constraint.stretch = frames[frame + PREV_STRETCH] != 0; - } - } else { - constraint.mix += (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha; - constraint.softness += (softness + (frames[frame + SOFTNESS] - softness) * percent - constraint.softness) * alpha; - if (direction == MixDirection.In) { - constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION]; - constraint.compress = frames[frame + PREV_COMPRESS] != 0; - constraint.stretch = frames[frame + PREV_STRETCH] != 0; - } - } - } - } - - /// Changes a transform constraint's mixes. - public class TransformConstraintTimeline : CurveTimeline { - public const int ENTRIES = 5; - private const int PREV_TIME = -5, PREV_ROTATE = -4, PREV_TRANSLATE = -3, PREV_SCALE = -2, PREV_SHEAR = -1; - private const int ROTATE = 1, TRANSLATE = 2, SCALE = 3, SHEAR = 4; - - internal int transformConstraintIndex; - internal float[] frames; // time, rotate mix, translate mix, scale mix, shear mix, ... - - public TransformConstraintTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - override public int PropertyId { - get { return ((int)TimelineType.TransformConstraint << 24) + transformConstraintIndex; } - } - - /// The index of the transform constraint slot in that will be changed. - public int TransformConstraintIndex { - set { - if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0."); - this.transformConstraintIndex = value; - } - get { - return transformConstraintIndex; - } - } - - /// The time in seconds, rotate mix, translate mix, scale mix, and shear mix for each key frame. - public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, scale mix, shear mix, ... - - /// The time in seconds, rotate mix, translate mix, scale mix, and shear mix for the specified key frame. - public void SetFrame (int frameIndex, float time, float rotateMix, float translateMix, float scaleMix, float shearMix) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + ROTATE] = rotateMix; - frames[frameIndex + TRANSLATE] = translateMix; - frames[frameIndex + SCALE] = scaleMix; - frames[frameIndex + SHEAR] = shearMix; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - TransformConstraint constraint = skeleton.transformConstraints.Items[transformConstraintIndex]; - if (!constraint.active) return; - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - TransformConstraintData data = constraint.data; - switch (blend) { - case MixBlend.Setup: - constraint.rotateMix = data.rotateMix; - constraint.translateMix = data.translateMix; - constraint.scaleMix = data.scaleMix; - constraint.shearMix = data.shearMix; - return; - case MixBlend.First: - constraint.rotateMix += (data.rotateMix - constraint.rotateMix) * alpha; - constraint.translateMix += (data.translateMix - constraint.translateMix) * alpha; - constraint.scaleMix += (data.scaleMix - constraint.scaleMix) * alpha; - constraint.shearMix += (data.shearMix - constraint.shearMix) * alpha; - return; - } - return; - } - - float rotate, translate, scale, shear; - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - int i = frames.Length; - rotate = frames[i + PREV_ROTATE]; - translate = frames[i + PREV_TRANSLATE]; - scale = frames[i + PREV_SCALE]; - shear = frames[i + PREV_SHEAR]; - } else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - rotate = frames[frame + PREV_ROTATE]; - translate = frames[frame + PREV_TRANSLATE]; - scale = frames[frame + PREV_SCALE]; - shear = frames[frame + PREV_SHEAR]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - rotate += (frames[frame + ROTATE] - rotate) * percent; - translate += (frames[frame + TRANSLATE] - translate) * percent; - scale += (frames[frame + SCALE] - scale) * percent; - shear += (frames[frame + SHEAR] - shear) * percent; - } - if (blend == MixBlend.Setup) { - TransformConstraintData data = constraint.data; - constraint.rotateMix = data.rotateMix + (rotate - data.rotateMix) * alpha; - constraint.translateMix = data.translateMix + (translate - data.translateMix) * alpha; - constraint.scaleMix = data.scaleMix + (scale - data.scaleMix) * alpha; - constraint.shearMix = data.shearMix + (shear - data.shearMix) * alpha; - } else { - constraint.rotateMix += (rotate - constraint.rotateMix) * alpha; - constraint.translateMix += (translate - constraint.translateMix) * alpha; - constraint.scaleMix += (scale - constraint.scaleMix) * alpha; - constraint.shearMix += (shear - constraint.shearMix) * alpha; - } - } - } - - /// Changes a path constraint's . - public class PathConstraintPositionTimeline : CurveTimeline { - public const int ENTRIES = 2; - protected const int PREV_TIME = -2, PREV_VALUE = -1; - protected const int VALUE = 1; - - internal int pathConstraintIndex; - internal float[] frames; // time, position, ... - - public PathConstraintPositionTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - override public int PropertyId { - get { return ((int)TimelineType.PathConstraintPosition << 24) + pathConstraintIndex; } - } - - /// The index of the path constraint slot in that will be changed. - public int PathConstraintIndex { - set { - if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0."); - this.pathConstraintIndex = value; - } - get { - return pathConstraintIndex; - } - } - - /// The time in seconds and path constraint position for each key frame. - public float[] Frames { get { return frames; } set { frames = value; } } // time, position, ... - - /// Sets the time in seconds and path constraint position for the specified key frame. - public void SetFrame (int frameIndex, float time, float position) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + VALUE] = position; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; - if (!constraint.active) return; - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case MixBlend.Setup: - constraint.position = constraint.data.position; - return; - case MixBlend.First: - constraint.position += (constraint.data.position - constraint.position) * alpha; - return; - } - return; - } - - float position; - if (time >= frames[frames.Length - ENTRIES]) // Time is after last frame. - position = frames[frames.Length + PREV_VALUE]; - else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - position = frames[frame + PREV_VALUE]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - position += (frames[frame + VALUE] - position) * percent; - } - if (blend == MixBlend.Setup) - constraint.position = constraint.data.position + (position - constraint.data.position) * alpha; - else - constraint.position += (position - constraint.position) * alpha; - } - } - - /// Changes a path constraint's . - public class PathConstraintSpacingTimeline : PathConstraintPositionTimeline { - public PathConstraintSpacingTimeline (int frameCount) - : base(frameCount) { - } - - override public int PropertyId { - get { return ((int)TimelineType.PathConstraintSpacing << 24) + pathConstraintIndex; } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, MixBlend blend, - MixDirection direction) { - PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; - if (!constraint.active) return; - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case MixBlend.Setup: - constraint.spacing = constraint.data.spacing; - return; - case MixBlend.First: - constraint.spacing += (constraint.data.spacing - constraint.spacing) * alpha; - return; - } - return; - } - - float spacing; - if (time >= frames[frames.Length - ENTRIES]) // Time is after last frame. - spacing = frames[frames.Length + PREV_VALUE]; - else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - spacing = frames[frame + PREV_VALUE]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - spacing += (frames[frame + VALUE] - spacing) * percent; - } - - if (blend == MixBlend.Setup) - constraint.spacing = constraint.data.spacing + (spacing - constraint.data.spacing) * alpha; - else - constraint.spacing += (spacing - constraint.spacing) * alpha; - } - } - - /// Changes a path constraint's mixes. - public class PathConstraintMixTimeline : CurveTimeline { - public const int ENTRIES = 3; - private const int PREV_TIME = -3, PREV_ROTATE = -2, PREV_TRANSLATE = -1; - private const int ROTATE = 1, TRANSLATE = 2; - - internal int pathConstraintIndex; - internal float[] frames; // time, rotate mix, translate mix, ... - - public PathConstraintMixTimeline (int frameCount) - : base(frameCount) { - frames = new float[frameCount * ENTRIES]; - } - - override public int PropertyId { - get { return ((int)TimelineType.PathConstraintMix << 24) + pathConstraintIndex; } - } - - /// The index of the path constraint slot in that will be changed. - public int PathConstraintIndex { - set { - if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0."); - this.pathConstraintIndex = value; - } - get { - return pathConstraintIndex; - } - } - - /// The time in seconds, rotate mix, and translate mix for each key frame. - public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, ... - - /// The time in seconds, rotate mix, and translate mix for the specified key frame. - public void SetFrame (int frameIndex, float time, float rotateMix, float translateMix) { - frameIndex *= ENTRIES; - frames[frameIndex] = time; - frames[frameIndex + ROTATE] = rotateMix; - frames[frameIndex + TRANSLATE] = translateMix; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; - if (!constraint.active) return; - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case MixBlend.Setup: - constraint.rotateMix = constraint.data.rotateMix; - constraint.translateMix = constraint.data.translateMix; - return; - case MixBlend.First: - constraint.rotateMix += (constraint.data.rotateMix - constraint.rotateMix) * alpha; - constraint.translateMix += (constraint.data.translateMix - constraint.translateMix) * alpha; - return; - } - return; - } - - float rotate, translate; - if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame. - rotate = frames[frames.Length + PREV_ROTATE]; - translate = frames[frames.Length + PREV_TRANSLATE]; - } else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, ENTRIES); - rotate = frames[frame + PREV_ROTATE]; - translate = frames[frame + PREV_TRANSLATE]; - float frameTime = frames[frame]; - float percent = GetCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - rotate += (frames[frame + ROTATE] - rotate) * percent; - translate += (frames[frame + TRANSLATE] - translate) * percent; - } - - if (blend == MixBlend.Setup) { - constraint.rotateMix = constraint.data.rotateMix + (rotate - constraint.data.rotateMix) * alpha; - constraint.translateMix = constraint.data.translateMix + (translate - constraint.data.translateMix) * alpha; - } else { - constraint.rotateMix += (rotate - constraint.rotateMix) * alpha; - constraint.translateMix += (translate - constraint.translateMix) * alpha; - } - } - } +namespace Spine3_8_95 +{ + + /// + /// A simple container for a list of timelines and a name. + public class Animation + { + internal String name; + internal ExposedList timelines; + internal HashSet timelineIds; + internal float duration; + + public Animation(string name, ExposedList timelines, float duration) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + if (timelines == null) throw new ArgumentNullException("timelines", "timelines cannot be null."); + // Note: avoiding reallocations by adding all hash set entries at + // once (EnsureCapacity() is only available in newer .Net versions). + int[] propertyIDs = new int[timelines.Count]; + for (int i = 0; i < timelines.Count; ++i) + { + propertyIDs[i] = timelines.Items[i].PropertyId; + } + this.timelineIds = new HashSet(propertyIDs); + this.name = name; + this.timelines = timelines; + this.duration = duration; + } + + public ExposedList Timelines { get { return timelines; } set { timelines = value; } } + + /// The duration of the animation in seconds, which is the highest time of all keys in the timeline. + public float Duration { get { return duration; } set { duration = value; } } + + /// The animation's name, which is unique across all animations in the skeleton. + public string Name { get { return name; } } + + /// Whether the timeline with the property id is contained in this animation. + public bool HasTimeline(int id) + { + return timelineIds.Contains(id); + } + + /// Applies all the animation's timelines to the specified skeleton. + /// + public void Apply(Skeleton skeleton, float lastTime, float time, bool loop, ExposedList events, float alpha, MixBlend blend, + MixDirection direction) + { + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + + if (loop && duration != 0) + { + time %= duration; + if (lastTime > 0) lastTime %= duration; + } + + ExposedList timelines = this.timelines; + for (int i = 0, n = timelines.Count; i < n; i++) + timelines.Items[i].Apply(skeleton, lastTime, time, events, alpha, blend, direction); + } + + override public string ToString() + { + return name; + } + + /// After the first and before the last entry. + /// Index of first value greater than the target. + internal static int BinarySearch(float[] values, float target, int step) + { + int low = 0; + int high = values.Length / step - 2; + if (high == 0) return step; + int current = (int)((uint)high >> 1); + while (true) + { + if (values[(current + 1) * step] <= target) + low = current + 1; + else + high = current; + if (low == high) return (low + 1) * step; + current = (int)((uint)(low + high) >> 1); + } + } + + /// After the first and before the last entry. + internal static int BinarySearch(float[] values, float target) + { + int low = 0; + int high = values.Length - 2; + if (high == 0) return 1; + int current = (int)((uint)high >> 1); + while (true) + { + if (values[current + 1] <= target) + low = current + 1; + else + high = current; + if (low == high) return (low + 1); + current = (int)((uint)(low + high) >> 1); + } + } + + internal static int LinearSearch(float[] values, float target, int step) + { + for (int i = 0, last = values.Length - step; i <= last; i += step) + if (values[i] > target) return i; + return -1; + } + } + + /// + /// The interface for all timelines. + public interface Timeline + { + /// Applies this timeline to the skeleton. + /// The skeleton the timeline is being applied to. This provides access to the bones, slots, and other + /// skeleton components the timeline may change. + /// The time this timeline was last applied. Timelines such as trigger only at specific + /// times rather than every frame. In that case, the timeline triggers everything between lastTime + /// (exclusive) and time (inclusive). + /// The time within the animation. Most timelines find the key before and the key after this time so they can + /// interpolate between the keys. + /// If any events are fired, they are added to this list. Can be null to ignore firing events or if the + /// timeline does not fire events. + /// 0 applies the current or setup value (depending on blend). 1 applies the timeline value. + /// Between 0 and 1 applies a value between the current or setup value 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). + /// Controls how mixing is applied when alpha < 1. + /// Indicates whether the timeline is mixing in or out. Used by timelines which perform instant transitions, + /// such as or , and other such as {@link ScaleTimeline}. + void Apply(Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, MixBlend blend, MixDirection direction); + /// Uniquely encodes both the type of this timeline and the skeleton property that it affects. + int PropertyId { get; } + } + + /// + /// Controls how a timeline is mixed with the setup or current pose. + /// + public enum MixBlend + { + + /// Transitions from the setup value to the timeline value (the current value is not used). Before the first key, the setup + /// value is set. + Setup, + + /// + /// + /// Transitions from the current value to the timeline value. Before the first key, transitions from the current value to + /// the setup value. Timelines which perform instant transitions, such as or + /// , use the setup value before the first key. + /// + /// First is intended for the first animations applied, not for animations layered on top of those. + /// + First, + + /// + /// + /// Transitions from the current value to the timeline value. No change is made before the first key (the current value is + /// kept until the first key). + /// + /// Replace is intended for animations layered on top of others, not for the first animations applied. + /// + Replace, + + /// + /// + /// Transitions from the current value to the current value plus the timeline value. No change is made before the first key + /// (the current value is kept until the first key). + /// + /// Add is intended for animations layered on top of others, not for the first animations applied. + /// + Add + } + + /// + /// Indicates whether a timeline's alpha is mixing out over time toward 0 (the setup or current pose value) or + /// mixing in toward 1 (the timeline's value). + /// + public enum MixDirection + { + In, + Out + } + + internal enum TimelineType + { + Rotate = 0, Translate, Scale, Shear, // + Attachment, Color, Deform, // + Event, DrawOrder, // + IkConstraint, TransformConstraint, // + PathConstraintPosition, PathConstraintSpacing, PathConstraintMix, // + TwoColor + } + + /// An interface for timelines which change the property of a bone. + public interface IBoneTimeline + { + /// The index of the bone in that will be changed. + int BoneIndex { get; } + } + + /// An interface for timelines which change the property of a slot. + public interface ISlotTimeline + { + /// The index of the slot in that will be changed. + int SlotIndex { get; } + } + + /// The base class for timelines that use interpolation between key frame values. + abstract public class CurveTimeline : Timeline + { + protected const float LINEAR = 0, STEPPED = 1, BEZIER = 2; + protected const int BEZIER_SIZE = 10 * 2 - 1; + + internal float[] curves; // type, x, y, ... + /// The number of key frames for this timeline. + public int FrameCount { get { return curves.Length / BEZIER_SIZE + 1; } } + + public CurveTimeline(int frameCount) + { + if (frameCount <= 0) throw new ArgumentOutOfRangeException("frameCount must be > 0: "); + curves = new float[(frameCount - 1) * BEZIER_SIZE]; + } + + abstract public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, MixDirection direction); + + abstract public int PropertyId { get; } + + /// Sets the specified key frame to linear interpolation. + public void SetLinear(int frameIndex) + { + curves[frameIndex * BEZIER_SIZE] = LINEAR; + } + + /// Sets the specified key frame to stepped interpolation. + public void SetStepped(int frameIndex) + { + curves[frameIndex * BEZIER_SIZE] = STEPPED; + } + + /// Returns the interpolation type for the specified key frame. + /// Linear is 0, stepped is 1, Bezier is 2. + public float GetCurveType(int frameIndex) + { + int index = frameIndex * BEZIER_SIZE; + if (index == curves.Length) return LINEAR; + float type = curves[index]; + if (type == LINEAR) return LINEAR; + if (type == STEPPED) return STEPPED; + return BEZIER; + } + + /// Sets the specified key frame to Bezier interpolation. cx1 and cx2 are from 0 to 1, + /// representing the percent of time between the two key frames. cy1 and cy2 are the percent of the + /// difference between the key frame's values. + public void SetCurve(int frameIndex, float cx1, float cy1, float cx2, float cy2) + { + float tmpx = (-cx1 * 2 + cx2) * 0.03f, tmpy = (-cy1 * 2 + cy2) * 0.03f; + float dddfx = ((cx1 - cx2) * 3 + 1) * 0.006f, dddfy = ((cy1 - cy2) * 3 + 1) * 0.006f; + float ddfx = tmpx * 2 + dddfx, ddfy = tmpy * 2 + dddfy; + float dfx = cx1 * 0.3f + tmpx + dddfx * 0.16666667f, dfy = cy1 * 0.3f + tmpy + dddfy * 0.16666667f; + + int i = frameIndex * BEZIER_SIZE; + float[] curves = this.curves; + curves[i++] = BEZIER; + + float x = dfx, y = dfy; + for (int n = i + BEZIER_SIZE - 1; i < n; i += 2) + { + curves[i] = x; + curves[i + 1] = y; + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + x += dfx; + y += dfy; + } + } + + /// Returns the interpolated percentage for the specified key frame and linear percentage. + public float GetCurvePercent(int frameIndex, float percent) + { + percent = MathUtils.Clamp(percent, 0, 1); + float[] curves = this.curves; + int i = frameIndex * BEZIER_SIZE; + float type = curves[i]; + if (type == LINEAR) return percent; + if (type == STEPPED) return 0; + i++; + float x = 0; + for (int start = i, n = i + BEZIER_SIZE - 1; i < n; i += 2) + { + x = curves[i]; + if (x >= percent) + { + if (i == start) return curves[i + 1] * percent / x; // First point is 0,0. + float prevX = curves[i - 2], prevY = curves[i - 1]; + return prevY + (curves[i + 1] - prevY) * (percent - prevX) / (x - prevX); + } + } + float y = curves[i - 1]; + return y + (1 - y) * (percent - x) / (1 - x); // Last point is 1,1. + } + } + + /// Changes a bone's local . + public class RotateTimeline : CurveTimeline, IBoneTimeline + { + public const int ENTRIES = 2; + internal const int PREV_TIME = -2, PREV_ROTATION = -1; + internal const int ROTATION = 1; + + internal int boneIndex; + internal float[] frames; // time, degrees, ... + + public RotateTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount << 1]; + } + + override public int PropertyId + { + get { return ((int)TimelineType.Rotate << 24) + boneIndex; } + } + /// The index of the bone in that will be changed. + public int BoneIndex + { + set + { + if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0."); + this.boneIndex = value; + } + get + { + return boneIndex; + } + } + /// The time in seconds and rotation in degrees for each key frame. + public float[] Frames { get { return frames; } set { frames = value; } } + + /// Sets the time in seconds and the rotation in degrees for the specified key frame. + public void SetFrame(int frameIndex, float time, float degrees) + { + frameIndex <<= 1; + frames[frameIndex] = time; + frames[frameIndex + ROTATION] = degrees; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + if (!bone.active) return; + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + switch (blend) + { + case MixBlend.Setup: + bone.rotation = bone.data.rotation; + return; + case MixBlend.First: + float r = bone.data.rotation - bone.rotation; + bone.rotation += (r - (16384 - (int)(16384.499999999996 - r / 360)) * 360) * alpha; + return; + } + return; + } + + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + float r = frames[frames.Length + PREV_ROTATION]; + switch (blend) + { + case MixBlend.Setup: + bone.rotation = bone.data.rotation + r * alpha; + break; + case MixBlend.First: + case MixBlend.Replace: + r += bone.data.rotation - bone.rotation; + r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; + goto case MixBlend.Add; // Fall through. + + case MixBlend.Add: + bone.rotation += r * alpha; + break; + } + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + float prevRotation = frames[frame + PREV_ROTATION]; + float frameTime = frames[frame]; + float percent = GetCurvePercent((frame >> 1) - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + // scope for 'r' to prevent compile error. + { + float r = frames[frame + ROTATION] - prevRotation; + r = prevRotation + (r - (16384 - (int)(16384.499999999996 - r / 360)) * 360) * percent; + switch (blend) + { + case MixBlend.Setup: + bone.rotation = bone.data.rotation + (r - (16384 - (int)(16384.499999999996 - r / 360)) * 360) * alpha; + break; + case MixBlend.First: + case MixBlend.Replace: + r += bone.data.rotation - bone.rotation; + goto case MixBlend.Add; // Fall through. + case MixBlend.Add: + bone.rotation += (r - (16384 - (int)(16384.499999999996 - r / 360)) * 360) * alpha; + break; + } + } + } + } + + /// Changes a bone's local and . + public class TranslateTimeline : CurveTimeline, IBoneTimeline + { + public const int ENTRIES = 3; + protected const int PREV_TIME = -3, PREV_X = -2, PREV_Y = -1; + protected const int X = 1, Y = 2; + + internal int boneIndex; + internal float[] frames; // time, x, y, ... + + public TranslateTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + override public int PropertyId + { + get { return ((int)TimelineType.Translate << 24) + boneIndex; } + } + + /// The index of the bone in that will be changed. + public int BoneIndex + { + set + { + if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0."); + this.boneIndex = value; + } + get + { + return boneIndex; + } + } + /// The time in seconds, x, and y values for each key frame. + public float[] Frames { get { return frames; } set { frames = value; } } + + + /// Sets the time in seconds, x, and y values for the specified key frame. + public void SetFrame(int frameIndex, float time, float x, float y) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + X] = x; + frames[frameIndex + Y] = y; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + if (!bone.active) return; + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + 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; + return; + } + return; + } + + float x, y; + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + x = frames[frames.Length + PREV_X]; + y = frames[frames.Length + PREV_Y]; + } + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + x = frames[frame + PREV_X]; + y = frames[frame + PREV_Y]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + x += (frames[frame + X] - x) * percent; + y += (frames[frame + Y] - y) * percent; + } + 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; + break; + } + } + } + + /// Changes a bone's local and . + public class ScaleTimeline : TranslateTimeline, IBoneTimeline + { + public ScaleTimeline(int frameCount) + : base(frameCount) + { + } + + override public int PropertyId + { + get { return ((int)TimelineType.Scale << 24) + boneIndex; } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + if (!bone.active) return; + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + 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; + return; + } + return; + } + + float x, y; + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + x = frames[frames.Length + PREV_X] * bone.data.scaleX; + y = frames[frames.Length + PREV_Y] * bone.data.scaleY; + } + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + x = frames[frame + PREV_X]; + y = frames[frame + PREV_Y]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + x = (x + (frames[frame + X] - x) * percent) * bone.data.scaleX; + y = (y + (frames[frame + Y] - y) * percent) * 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 + { + // Mixing out uses sign of setup or current pose, else use sign of key. + float bx, by; + if (direction == MixDirection.Out) + { + switch (blend) + { + case MixBlend.Setup: + bx = bone.data.scaleX; + by = bone.data.scaleY; + bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha; + bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha; + break; + case MixBlend.First: + case MixBlend.Replace: + bx = bone.scaleX; + by = bone.scaleY; + bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha; + bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha; + break; + case MixBlend.Add: + bx = bone.scaleX; + by = bone.scaleY; + bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bone.data.scaleX) * alpha; + bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - bone.data.scaleY) * alpha; + break; + } + } + else + { + switch (blend) + { + case MixBlend.Setup: + bx = Math.Abs(bone.data.scaleX) * Math.Sign(x); + by = Math.Abs(bone.data.scaleY) * Math.Sign(y); + bone.scaleX = bx + (x - bx) * alpha; + bone.scaleY = by + (y - by) * alpha; + break; + case MixBlend.First: + case MixBlend.Replace: + bx = Math.Abs(bone.scaleX) * Math.Sign(x); + by = Math.Abs(bone.scaleY) * Math.Sign(y); + bone.scaleX = bx + (x - bx) * alpha; + bone.scaleY = by + (y - by) * alpha; + break; + case MixBlend.Add: + bx = Math.Sign(x); + by = Math.Sign(y); + bone.scaleX = Math.Abs(bone.scaleX) * bx + (x - Math.Abs(bone.data.scaleX) * bx) * alpha; + bone.scaleY = Math.Abs(bone.scaleY) * by + (y - Math.Abs(bone.data.scaleY) * by) * alpha; + break; + } + } + } + } + } + + /// Changes a bone's local and . + public class ShearTimeline : TranslateTimeline, IBoneTimeline + { + public ShearTimeline(int frameCount) + : base(frameCount) + { + } + + override public int PropertyId + { + get { return ((int)TimelineType.Shear << 24) + boneIndex; } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + if (!bone.active) return; + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + 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; + return; + } + return; + } + + float x, y; + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + x = frames[frames.Length + PREV_X]; + y = frames[frames.Length + PREV_Y]; + } + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + x = frames[frame + PREV_X]; + y = frames[frame + PREV_Y]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + x = x + (frames[frame + X] - x) * percent; + y = y + (frames[frame + Y] - y) * percent; + } + 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; + break; + } + } + } + + /// Changes a slot's . + public class ColorTimeline : CurveTimeline, ISlotTimeline + { + public const int ENTRIES = 5; + protected const int PREV_TIME = -5, PREV_R = -4, PREV_G = -3, PREV_B = -2, PREV_A = -1; + protected const int R = 1, G = 2, B = 3, A = 4; + + internal int slotIndex; + internal float[] frames; // time, r, g, b, a, ... + + public ColorTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + override public int PropertyId + { + get { return ((int)TimelineType.Color << 24) + slotIndex; } + } + + /// The index of the slot in that will be changed. + public int SlotIndex + { + set + { + if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0."); + this.slotIndex = value; + } + get + { + return slotIndex; + } + } + /// The time in seconds, red, green, blue, and alpha values for each key frame. + public float[] Frames { get { return frames; } set { frames = value; } } + + /// Sets the time in seconds, red, green, blue, and alpha for the specified key frame. + public void SetFrame(int frameIndex, float time, float r, float g, float b, float a) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + R] = r; + frames[frameIndex + G] = g; + frames[frameIndex + B] = b; + frames[frameIndex + A] = a; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Slot slot = skeleton.slots.Items[slotIndex]; + if (!slot.bone.active) return; + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + var slotData = slot.data; + switch (blend) + { + case MixBlend.Setup: + slot.r = slotData.r; + slot.g = slotData.g; + slot.b = slotData.b; + slot.a = slotData.a; + return; + case MixBlend.First: + slot.r += (slotData.r - slot.r) * alpha; + slot.g += (slotData.g - slot.g) * alpha; + slot.b += (slotData.b - slot.b) * alpha; + slot.a += (slotData.a - slot.a) * alpha; + slot.ClampColor(); + return; + } + return; + } + + float r, g, b, a; + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + int i = frames.Length; + r = frames[i + PREV_R]; + g = frames[i + PREV_G]; + b = frames[i + PREV_B]; + a = frames[i + PREV_A]; + } + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + r = frames[frame + PREV_R]; + g = frames[frame + PREV_G]; + b = frames[frame + PREV_B]; + a = frames[frame + PREV_A]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + r += (frames[frame + R] - r) * percent; + g += (frames[frame + G] - g) * percent; + b += (frames[frame + B] - b) * percent; + a += (frames[frame + A] - a) * percent; + } + if (alpha == 1) + { + slot.r = r; + slot.g = g; + slot.b = b; + slot.a = a; + slot.ClampColor(); + } + else + { + float br, bg, bb, ba; + if (blend == MixBlend.Setup) + { + br = slot.data.r; + bg = slot.data.g; + bb = slot.data.b; + ba = slot.data.a; + } + else + { + br = slot.r; + bg = slot.g; + bb = slot.b; + ba = slot.a; + } + slot.r = br + ((r - br) * alpha); + slot.g = bg + ((g - bg) * alpha); + slot.b = bb + ((b - bb) * alpha); + slot.a = ba + ((a - ba) * alpha); + slot.ClampColor(); + } + } + } + + /// Changes a slot's and for two color tinting. + public class TwoColorTimeline : CurveTimeline, ISlotTimeline + { + public const int ENTRIES = 8; + protected const int PREV_TIME = -8, PREV_R = -7, PREV_G = -6, PREV_B = -5, PREV_A = -4; + protected const int PREV_R2 = -3, PREV_G2 = -2, PREV_B2 = -1; + protected const int R = 1, G = 2, B = 3, A = 4, R2 = 5, G2 = 6, B2 = 7; + + internal int slotIndex; + internal float[] frames; // time, r, g, b, a, r2, g2, b2, ... + + public TwoColorTimeline(int frameCount) : + base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + override public int PropertyId + { + get { return ((int)TimelineType.TwoColor << 24) + slotIndex; } + } + + /// The index of the slot in that will be changed. + public int SlotIndex + { + set + { + if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0."); + this.slotIndex = value; + } + get + { + return slotIndex; + } + } + /// The time in seconds, red, green, blue, and alpha values for each key frame. + public float[] Frames { get { return frames; } } + + /// Sets the time in seconds, light, and dark colors for the specified key frame.. + public void SetFrame(int frameIndex, float time, float r, float g, float b, float a, float r2, float g2, float b2) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + R] = r; + frames[frameIndex + G] = g; + frames[frameIndex + B] = b; + frames[frameIndex + A] = a; + frames[frameIndex + R2] = r2; + frames[frameIndex + G2] = g2; + frames[frameIndex + B2] = b2; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Slot slot = skeleton.slots.Items[slotIndex]; + if (!slot.bone.active) return; + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + var slotData = slot.data; + switch (blend) + { + case MixBlend.Setup: + // slot.color.set(slot.data.color); + // slot.darkColor.set(slot.data.darkColor); + slot.r = slotData.r; + slot.g = slotData.g; + slot.b = slotData.b; + slot.a = slotData.a; + slot.ClampColor(); + slot.r2 = slotData.r2; + slot.g2 = slotData.g2; + slot.b2 = slotData.b2; + slot.ClampSecondColor(); + return; + case MixBlend.First: + slot.r += (slot.r - slotData.r) * alpha; + slot.g += (slot.g - slotData.g) * alpha; + slot.b += (slot.b - slotData.b) * alpha; + slot.a += (slot.a - slotData.a) * alpha; + slot.ClampColor(); + slot.r2 += (slot.r2 - slotData.r2) * alpha; + slot.g2 += (slot.g2 - slotData.g2) * alpha; + slot.b2 += (slot.b2 - slotData.b2) * alpha; + slot.ClampSecondColor(); + return; + } + return; + } + + float r, g, b, a, r2, g2, b2; + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + int i = frames.Length; + r = frames[i + PREV_R]; + g = frames[i + PREV_G]; + b = frames[i + PREV_B]; + a = frames[i + PREV_A]; + r2 = frames[i + PREV_R2]; + g2 = frames[i + PREV_G2]; + b2 = frames[i + PREV_B2]; + } + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + r = frames[frame + PREV_R]; + g = frames[frame + PREV_G]; + b = frames[frame + PREV_B]; + a = frames[frame + PREV_A]; + r2 = frames[frame + PREV_R2]; + g2 = frames[frame + PREV_G2]; + b2 = frames[frame + PREV_B2]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + r += (frames[frame + R] - r) * percent; + g += (frames[frame + G] - g) * percent; + b += (frames[frame + B] - b) * percent; + a += (frames[frame + A] - a) * percent; + r2 += (frames[frame + R2] - r2) * percent; + g2 += (frames[frame + G2] - g2) * percent; + b2 += (frames[frame + B2] - b2) * percent; + } + if (alpha == 1) + { + slot.r = r; + slot.g = g; + slot.b = b; + slot.a = a; + slot.ClampColor(); + slot.r2 = r2; + slot.g2 = g2; + slot.b2 = b2; + slot.ClampSecondColor(); + } + else + { + float br, bg, bb, ba, br2, bg2, bb2; + if (blend == MixBlend.Setup) + { + br = slot.data.r; + bg = slot.data.g; + bb = slot.data.b; + ba = slot.data.a; + br2 = slot.data.r2; + bg2 = slot.data.g2; + bb2 = slot.data.b2; + } + else + { + br = slot.r; + bg = slot.g; + bb = slot.b; + ba = slot.a; + br2 = slot.r2; + bg2 = slot.g2; + bb2 = slot.b2; + } + slot.r = br + ((r - br) * alpha); + slot.g = bg + ((g - bg) * alpha); + slot.b = bb + ((b - bb) * alpha); + slot.a = ba + ((a - ba) * alpha); + slot.ClampColor(); + slot.r2 = br2 + ((r2 - br2) * alpha); + slot.g2 = bg2 + ((g2 - bg2) * alpha); + slot.b2 = bb2 + ((b2 - bb2) * alpha); + slot.ClampSecondColor(); + } + } + + } + + /// Changes a slot's . + public class AttachmentTimeline : Timeline, ISlotTimeline + { + internal int slotIndex; + internal float[] frames; // time, ... + internal string[] attachmentNames; + + public AttachmentTimeline(int frameCount) + { + frames = new float[frameCount]; + attachmentNames = new String[frameCount]; + } + + public int PropertyId + { + get { return ((int)TimelineType.Attachment << 24) + slotIndex; } + } + + /// The number of key frames for this timeline. + public int FrameCount { get { return frames.Length; } } + + /// The index of the slot in that will be changed. + public int SlotIndex + { + set + { + if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0."); + this.slotIndex = value; + } + get + { + return slotIndex; + } + } + + /// The time in seconds for each key frame. + public float[] Frames { get { return frames; } set { frames = value; } } + + /// The attachment name for each key frame. May contain null values to clear the attachment. + public string[] AttachmentNames { get { return attachmentNames; } set { attachmentNames = value; } } + + /// Sets the time in seconds and the attachment name for the specified key frame. + public void SetFrame(int frameIndex, float time, String attachmentName) + { + frames[frameIndex] = time; + attachmentNames[frameIndex] = attachmentName; + } + + public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Slot slot = skeleton.slots.Items[slotIndex]; + if (!slot.bone.active) return; + if (direction == MixDirection.Out) + { + if (blend == MixBlend.Setup) SetAttachment(skeleton, slot, slot.data.attachmentName); + return; + } + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + if (blend == MixBlend.Setup || blend == MixBlend.First) SetAttachment(skeleton, slot, slot.data.attachmentName); + return; + } + + int frameIndex; + if (time >= frames[frames.Length - 1]) // Time is after last frame. + frameIndex = frames.Length - 1; + else + frameIndex = Animation.BinarySearch(frames, time) - 1; + + SetAttachment(skeleton, slot, attachmentNames[frameIndex]); + } + + private void SetAttachment(Skeleton skeleton, Slot slot, string attachmentName) + { + slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); + } + } + + /// Changes a slot's to deform a . + public class DeformTimeline : CurveTimeline, ISlotTimeline + { + internal int slotIndex; + internal VertexAttachment attachment; + internal float[] frames; // time, ... + internal float[][] frameVertices; + + public DeformTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount]; + frameVertices = new float[frameCount][]; + } + + override public int PropertyId + { + get { return ((int)TimelineType.Deform << 27) + attachment.id + slotIndex; } + } + + /// The index of the slot in that will be changed. + public int SlotIndex + { + set + { + if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0."); + this.slotIndex = value; + } + get + { + return slotIndex; + } + } + /// The attachment that will be deformed. + public VertexAttachment Attachment { get { return attachment; } set { attachment = value; } } + + /// The time in seconds for each key frame. + public float[] Frames { get { return frames; } set { frames = value; } } + + /// The vertices for each key frame. + public float[][] Vertices { get { return frameVertices; } set { frameVertices = value; } } + + + /// Sets the time in seconds and the vertices for the specified key frame. + /// Vertex positions for an unweighted VertexAttachment, or deform offsets if it has weights. + public void SetFrame(int frameIndex, float time, float[] vertices) + { + frames[frameIndex] = time; + frameVertices[frameIndex] = vertices; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Slot slot = skeleton.slots.Items[slotIndex]; + if (!slot.bone.active) return; + VertexAttachment vertexAttachment = slot.attachment as VertexAttachment; + if (vertexAttachment == null || vertexAttachment.DeformAttachment != attachment) return; + + var deformArray = slot.Deform; + if (deformArray.Count == 0) blend = MixBlend.Setup; + + float[][] frameVertices = this.frameVertices; + int vertexCount = frameVertices[0].Length; + float[] frames = this.frames; + float[] deform; + + if (time < frames[0]) + { // Time is before first frame. + + switch (blend) + { + case MixBlend.Setup: + deformArray.Clear(); + return; + case MixBlend.First: + if (alpha == 1) + { + deformArray.Clear(); + return; + } + + // deformArray.SetSize(vertexCount) // Ensure size and preemptively set count. + if (deformArray.Capacity < vertexCount) deformArray.Capacity = vertexCount; + deformArray.Count = vertexCount; + deform = deformArray.Items; + + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions. + float[] setupVertices = vertexAttachment.vertices; + for (int i = 0; i < vertexCount; i++) + deform[i] += (setupVertices[i] - deform[i]) * alpha; + } + else + { + // Weighted deform offsets. + alpha = 1 - alpha; + for (int i = 0; i < vertexCount; i++) + deform[i] *= alpha; + } + return; + default: + return; + } + + } + + // deformArray.SetSize(vertexCount) // Ensure size and preemptively set count. + if (deformArray.Capacity < vertexCount) deformArray.Capacity = vertexCount; + deformArray.Count = vertexCount; + deform = deformArray.Items; + + if (time >= frames[frames.Length - 1]) + { // Time is after last frame. + + float[] lastVertices = frameVertices[frames.Length - 1]; + if (alpha == 1) + { + if (blend == MixBlend.Add) + { + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions, no alpha. + float[] setupVertices = vertexAttachment.vertices; + for (int i = 0; i < vertexCount; i++) + deform[i] += lastVertices[i] - setupVertices[i]; + } + else + { + // Weighted deform offsets, no alpha. + for (int i = 0; i < vertexCount; i++) + deform[i] += lastVertices[i]; + } + } + else + { + // Vertex positions or deform offsets, no alpha. + Array.Copy(lastVertices, 0, deform, 0, vertexCount); + } + } + else + { + switch (blend) + { + case MixBlend.Setup: + { + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions, with alpha. + float[] setupVertices = vertexAttachment.vertices; + for (int i = 0; i < vertexCount; i++) + { + float setup = setupVertices[i]; + deform[i] = setup + (lastVertices[i] - setup) * alpha; + } + } + else + { + // Weighted deform offsets, with alpha. + for (int 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 (int i = 0; i < vertexCount; i++) + deform[i] += (lastVertices[i] - deform[i]) * alpha; + break; + case MixBlend.Add: + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions, no alpha. + float[] setupVertices = vertexAttachment.vertices; + for (int i = 0; i < vertexCount; i++) + deform[i] += (lastVertices[i] - setupVertices[i]) * alpha; + } + else + { + // Weighted deform offsets, alpha. + for (int i = 0; i < vertexCount; i++) + deform[i] += lastVertices[i] * alpha; + } + break; + } + } + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time); + float[] prevVertices = frameVertices[frame - 1]; + float[] nextVertices = frameVertices[frame]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame - 1, 1 - (time - frameTime) / (frames[frame - 1] - frameTime)); + + if (alpha == 1) + { + if (blend == MixBlend.Add) + { + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions, no alpha. + float[] setupVertices = vertexAttachment.vertices; + for (int 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 (int 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 (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i]; + deform[i] = prev + (nextVertices[i] - prev) * percent; + } + } + } + else + { + switch (blend) + { + case MixBlend.Setup: + { + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions, with alpha. + float[] setupVertices = vertexAttachment.vertices; + for (int 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 (int 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 (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i]; + deform[i] += (prev + (nextVertices[i] - prev) * percent - deform[i]) * alpha; + } + break; + } + case MixBlend.Add: + { + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions, with alpha. + float[] setupVertices = vertexAttachment.vertices; + for (int 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 (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i]; + deform[i] += (prev + (nextVertices[i] - prev) * percent) * alpha; + } + } + break; + } + } + } + } + } + + /// Fires an when specific animation times are reached. + public class EventTimeline : Timeline + { + internal float[] frames; // time, ... + private Event[] events; + + public EventTimeline(int frameCount) + { + frames = new float[frameCount]; + events = new Event[frameCount]; + } + + public int PropertyId + { + get { return ((int)TimelineType.Event << 24); } + } + + /// The number of key frames for this timeline. + public int FrameCount { get { return frames.Length; } } + + /// The time in seconds for each key frame. + public float[] Frames { get { return frames; } set { frames = value; } } + + /// The event for each key frame. + public Event[] Events { get { return events; } set { events = value; } } + + /// Sets the time in seconds and the event for the specified key frame. + public void SetFrame(int frameIndex, Event e) + { + frames[frameIndex] = e.Time; + events[frameIndex] = e; + } + + /// Fires events for frames > lastTime and <= time. + public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + if (firedEvents == null) return; + float[] frames = this.frames; + int frameCount = frames.Length; + + if (lastTime > time) + { // Fire events after last time for looped animations. + Apply(skeleton, lastTime, int.MaxValue, firedEvents, alpha, blend, direction); + lastTime = -1f; + } + else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame. + return; + if (time < frames[0]) return; // Time is before first frame. + + int frame; + if (lastTime < frames[0]) + frame = 0; + else + { + frame = Animation.BinarySearch(frames, lastTime); + float frameTime = frames[frame]; + while (frame > 0) + { // Fire multiple events with the same frame. + if (frames[frame - 1] != frameTime) break; + frame--; + } + } + for (; frame < frameCount && time >= frames[frame]; frame++) + firedEvents.Add(events[frame]); + } + } + + /// Changes a skeleton's . + public class DrawOrderTimeline : Timeline + { + internal float[] frames; // time, ... + private int[][] drawOrders; + + public DrawOrderTimeline(int frameCount) + { + frames = new float[frameCount]; + drawOrders = new int[frameCount][]; + } + + public int PropertyId + { + get { return ((int)TimelineType.DrawOrder << 24); } + } + + /// The number of key frames for this timeline. + public int FrameCount { get { return frames.Length; } } + + /// The time in seconds for each key frame. + public float[] Frames { get { return frames; } set { frames = value; } } // time, ... + + /// The draw order for each key frame. + /// . + public int[][] DrawOrders { get { return drawOrders; } set { drawOrders = value; } } + + /// Sets the time in seconds and the draw order for the specified key frame. + /// For each slot in the index of the new draw order. May be null to use setup pose + /// draw order.. + public void SetFrame(int frameIndex, float time, int[] drawOrder) + { + frames[frameIndex] = time; + drawOrders[frameIndex] = drawOrder; + } + + public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + ExposedList drawOrder = skeleton.drawOrder; + ExposedList slots = skeleton.slots; + if (direction == MixDirection.Out) + { + if (blend == MixBlend.Setup) Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count); + return; + } + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + if (blend == MixBlend.Setup || blend == MixBlend.First) Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count); + return; + } + + int frame; + if (time >= frames[frames.Length - 1]) // Time is after last frame. + frame = frames.Length - 1; + else + frame = Animation.BinarySearch(frames, time) - 1; + + int[] drawOrderToSetupIndex = drawOrders[frame]; + if (drawOrderToSetupIndex == null) + { + Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count); + } + else + { + var drawOrderItems = drawOrder.Items; + var slotsItems = slots.Items; + for (int i = 0, n = drawOrderToSetupIndex.Length; i < n; i++) + drawOrderItems[i] = slotsItems[drawOrderToSetupIndex[i]]; + } + } + } + + /// Changes an IK constraint's , , + /// , , and . + public class IkConstraintTimeline : CurveTimeline + { + public const int ENTRIES = 6; + private const int PREV_TIME = -6, PREV_MIX = -5, PREV_SOFTNESS = -4, PREV_BEND_DIRECTION = -3, PREV_COMPRESS = -2, + PREV_STRETCH = -1; + private const int MIX = 1, SOFTNESS = 2, BEND_DIRECTION = 3, COMPRESS = 4, STRETCH = 5; + + internal int ikConstraintIndex; + internal float[] frames; // time, mix, softness, bendDirection, compress, stretch, ... + + public IkConstraintTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + override public int PropertyId + { + get { return ((int)TimelineType.IkConstraint << 24) + ikConstraintIndex; } + } + + /// The index of the IK constraint slot in that will be changed. + public int IkConstraintIndex + { + set + { + if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0."); + this.ikConstraintIndex = value; + } + get + { + return ikConstraintIndex; + } + } + + /// The time in seconds, mix, softness, bend direction, compress, and stretch for each key frame. + public float[] Frames { get { return frames; } set { frames = value; } } + + /// Sets the time in seconds, mix, softness, bend direction, compress, and stretch for the specified key frame. + public void SetFrame(int frameIndex, float time, float mix, float softness, int bendDirection, bool compress, + bool stretch) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + MIX] = mix; + frames[frameIndex + SOFTNESS] = softness; + frames[frameIndex + BEND_DIRECTION] = bendDirection; + frames[frameIndex + COMPRESS] = compress ? 1 : 0; + frames[frameIndex + STRETCH] = stretch ? 1 : 0; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + IkConstraint constraint = skeleton.ikConstraints.Items[ikConstraintIndex]; + if (!constraint.active) return; + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + 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; + } + return; + } + + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + if (blend == MixBlend.Setup) + { + constraint.mix = constraint.data.mix + (frames[frames.Length + PREV_MIX] - constraint.data.mix) * alpha; + constraint.softness = constraint.data.softness + + (frames[frames.Length + PREV_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 = (int)frames[frames.Length + PREV_BEND_DIRECTION]; + constraint.compress = frames[frames.Length + PREV_COMPRESS] != 0; + constraint.stretch = frames[frames.Length + PREV_STRETCH] != 0; + } + } + else + { + constraint.mix += (frames[frames.Length + PREV_MIX] - constraint.mix) * alpha; + constraint.softness += (frames[frames.Length + PREV_SOFTNESS] - constraint.softness) * alpha; + if (direction == MixDirection.In) + { + constraint.bendDirection = (int)frames[frames.Length + PREV_BEND_DIRECTION]; + constraint.compress = frames[frames.Length + PREV_COMPRESS] != 0; + constraint.stretch = frames[frames.Length + PREV_STRETCH] != 0; + } + } + return; + } + + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + float mix = frames[frame + PREV_MIX]; + float softness = frames[frame + PREV_SOFTNESS]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + if (blend == MixBlend.Setup) + { + constraint.mix = constraint.data.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.data.mix) * alpha; + constraint.softness = constraint.data.softness + + (softness + (frames[frame + SOFTNESS] - softness) * percent - 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 = (int)frames[frame + PREV_BEND_DIRECTION]; + constraint.compress = frames[frame + PREV_COMPRESS] != 0; + constraint.stretch = frames[frame + PREV_STRETCH] != 0; + } + } + else + { + constraint.mix += (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha; + constraint.softness += (softness + (frames[frame + SOFTNESS] - softness) * percent - constraint.softness) * alpha; + if (direction == MixDirection.In) + { + constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION]; + constraint.compress = frames[frame + PREV_COMPRESS] != 0; + constraint.stretch = frames[frame + PREV_STRETCH] != 0; + } + } + } + } + + /// Changes a transform constraint's mixes. + public class TransformConstraintTimeline : CurveTimeline + { + public const int ENTRIES = 5; + private const int PREV_TIME = -5, PREV_ROTATE = -4, PREV_TRANSLATE = -3, PREV_SCALE = -2, PREV_SHEAR = -1; + private const int ROTATE = 1, TRANSLATE = 2, SCALE = 3, SHEAR = 4; + + internal int transformConstraintIndex; + internal float[] frames; // time, rotate mix, translate mix, scale mix, shear mix, ... + + public TransformConstraintTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + override public int PropertyId + { + get { return ((int)TimelineType.TransformConstraint << 24) + transformConstraintIndex; } + } + + /// The index of the transform constraint slot in that will be changed. + public int TransformConstraintIndex + { + set + { + if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0."); + this.transformConstraintIndex = value; + } + get + { + return transformConstraintIndex; + } + } + + /// The time in seconds, rotate mix, translate mix, scale mix, and shear mix for each key frame. + public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, scale mix, shear mix, ... + + /// The time in seconds, rotate mix, translate mix, scale mix, and shear mix for the specified key frame. + public void SetFrame(int frameIndex, float time, float rotateMix, float translateMix, float scaleMix, float shearMix) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + ROTATE] = rotateMix; + frames[frameIndex + TRANSLATE] = translateMix; + frames[frameIndex + SCALE] = scaleMix; + frames[frameIndex + SHEAR] = shearMix; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + TransformConstraint constraint = skeleton.transformConstraints.Items[transformConstraintIndex]; + if (!constraint.active) return; + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + TransformConstraintData data = constraint.data; + switch (blend) + { + case MixBlend.Setup: + constraint.rotateMix = data.rotateMix; + constraint.translateMix = data.translateMix; + constraint.scaleMix = data.scaleMix; + constraint.shearMix = data.shearMix; + return; + case MixBlend.First: + constraint.rotateMix += (data.rotateMix - constraint.rotateMix) * alpha; + constraint.translateMix += (data.translateMix - constraint.translateMix) * alpha; + constraint.scaleMix += (data.scaleMix - constraint.scaleMix) * alpha; + constraint.shearMix += (data.shearMix - constraint.shearMix) * alpha; + return; + } + return; + } + + float rotate, translate, scale, shear; + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + int i = frames.Length; + rotate = frames[i + PREV_ROTATE]; + translate = frames[i + PREV_TRANSLATE]; + scale = frames[i + PREV_SCALE]; + shear = frames[i + PREV_SHEAR]; + } + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + rotate = frames[frame + PREV_ROTATE]; + translate = frames[frame + PREV_TRANSLATE]; + scale = frames[frame + PREV_SCALE]; + shear = frames[frame + PREV_SHEAR]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + rotate += (frames[frame + ROTATE] - rotate) * percent; + translate += (frames[frame + TRANSLATE] - translate) * percent; + scale += (frames[frame + SCALE] - scale) * percent; + shear += (frames[frame + SHEAR] - shear) * percent; + } + if (blend == MixBlend.Setup) + { + TransformConstraintData data = constraint.data; + constraint.rotateMix = data.rotateMix + (rotate - data.rotateMix) * alpha; + constraint.translateMix = data.translateMix + (translate - data.translateMix) * alpha; + constraint.scaleMix = data.scaleMix + (scale - data.scaleMix) * alpha; + constraint.shearMix = data.shearMix + (shear - data.shearMix) * alpha; + } + else + { + constraint.rotateMix += (rotate - constraint.rotateMix) * alpha; + constraint.translateMix += (translate - constraint.translateMix) * alpha; + constraint.scaleMix += (scale - constraint.scaleMix) * alpha; + constraint.shearMix += (shear - constraint.shearMix) * alpha; + } + } + } + + /// Changes a path constraint's . + public class PathConstraintPositionTimeline : CurveTimeline + { + public const int ENTRIES = 2; + protected const int PREV_TIME = -2, PREV_VALUE = -1; + protected const int VALUE = 1; + + internal int pathConstraintIndex; + internal float[] frames; // time, position, ... + + public PathConstraintPositionTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + override public int PropertyId + { + get { return ((int)TimelineType.PathConstraintPosition << 24) + pathConstraintIndex; } + } + + /// The index of the path constraint slot in that will be changed. + public int PathConstraintIndex + { + set + { + if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0."); + this.pathConstraintIndex = value; + } + get + { + return pathConstraintIndex; + } + } + + /// The time in seconds and path constraint position for each key frame. + public float[] Frames { get { return frames; } set { frames = value; } } // time, position, ... + + /// Sets the time in seconds and path constraint position for the specified key frame. + public void SetFrame(int frameIndex, float time, float position) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + VALUE] = position; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; + if (!constraint.active) return; + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + switch (blend) + { + case MixBlend.Setup: + constraint.position = constraint.data.position; + return; + case MixBlend.First: + constraint.position += (constraint.data.position - constraint.position) * alpha; + return; + } + return; + } + + float position; + if (time >= frames[frames.Length - ENTRIES]) // Time is after last frame. + position = frames[frames.Length + PREV_VALUE]; + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + position = frames[frame + PREV_VALUE]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + position += (frames[frame + VALUE] - position) * percent; + } + if (blend == MixBlend.Setup) + constraint.position = constraint.data.position + (position - constraint.data.position) * alpha; + else + constraint.position += (position - constraint.position) * alpha; + } + } + + /// Changes a path constraint's . + public class PathConstraintSpacingTimeline : PathConstraintPositionTimeline + { + public PathConstraintSpacingTimeline(int frameCount) + : base(frameCount) + { + } + + override public int PropertyId + { + get { return ((int)TimelineType.PathConstraintSpacing << 24) + pathConstraintIndex; } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, MixBlend blend, + MixDirection direction) + { + PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; + if (!constraint.active) return; + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + switch (blend) + { + case MixBlend.Setup: + constraint.spacing = constraint.data.spacing; + return; + case MixBlend.First: + constraint.spacing += (constraint.data.spacing - constraint.spacing) * alpha; + return; + } + return; + } + + float spacing; + if (time >= frames[frames.Length - ENTRIES]) // Time is after last frame. + spacing = frames[frames.Length + PREV_VALUE]; + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + spacing = frames[frame + PREV_VALUE]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + spacing += (frames[frame + VALUE] - spacing) * percent; + } + + if (blend == MixBlend.Setup) + constraint.spacing = constraint.data.spacing + (spacing - constraint.data.spacing) * alpha; + else + constraint.spacing += (spacing - constraint.spacing) * alpha; + } + } + + /// Changes a path constraint's mixes. + public class PathConstraintMixTimeline : CurveTimeline + { + public const int ENTRIES = 3; + private const int PREV_TIME = -3, PREV_ROTATE = -2, PREV_TRANSLATE = -1; + private const int ROTATE = 1, TRANSLATE = 2; + + internal int pathConstraintIndex; + internal float[] frames; // time, rotate mix, translate mix, ... + + public PathConstraintMixTimeline(int frameCount) + : base(frameCount) + { + frames = new float[frameCount * ENTRIES]; + } + + override public int PropertyId + { + get { return ((int)TimelineType.PathConstraintMix << 24) + pathConstraintIndex; } + } + + /// The index of the path constraint slot in that will be changed. + public int PathConstraintIndex + { + set + { + if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0."); + this.pathConstraintIndex = value; + } + get + { + return pathConstraintIndex; + } + } + + /// The time in seconds, rotate mix, and translate mix for each key frame. + public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, ... + + /// The time in seconds, rotate mix, and translate mix for the specified key frame. + public void SetFrame(int frameIndex, float time, float rotateMix, float translateMix) + { + frameIndex *= ENTRIES; + frames[frameIndex] = time; + frames[frameIndex + ROTATE] = rotateMix; + frames[frameIndex + TRANSLATE] = translateMix; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; + if (!constraint.active) return; + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + switch (blend) + { + case MixBlend.Setup: + constraint.rotateMix = constraint.data.rotateMix; + constraint.translateMix = constraint.data.translateMix; + return; + case MixBlend.First: + constraint.rotateMix += (constraint.data.rotateMix - constraint.rotateMix) * alpha; + constraint.translateMix += (constraint.data.translateMix - constraint.translateMix) * alpha; + return; + } + return; + } + + float rotate, translate; + if (time >= frames[frames.Length - ENTRIES]) + { // Time is after last frame. + rotate = frames[frames.Length + PREV_ROTATE]; + translate = frames[frames.Length + PREV_TRANSLATE]; + } + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, ENTRIES); + rotate = frames[frame + PREV_ROTATE]; + translate = frames[frame + PREV_TRANSLATE]; + float frameTime = frames[frame]; + float percent = GetCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + rotate += (frames[frame + ROTATE] - rotate) * percent; + translate += (frames[frame + TRANSLATE] - translate) * percent; + } + + if (blend == MixBlend.Setup) + { + constraint.rotateMix = constraint.data.rotateMix + (rotate - constraint.data.rotateMix) * alpha; + constraint.translateMix = constraint.data.translateMix + (translate - constraint.data.translateMix) * alpha; + } + else + { + constraint.rotateMix += (rotate - constraint.rotateMix) * alpha; + constraint.translateMix += (translate - constraint.translateMix) * alpha; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/AnimationState.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/AnimationState.cs index 03b7182..a3bef39 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/AnimationState.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/AnimationState.cs @@ -30,1296 +30,1442 @@ using System; using System.Collections.Generic; -namespace Spine3_8_95 { - - /// - /// - /// Applies animations over time, queues animations for later playback, mixes (crossfading) between animations, and applies - /// multiple animations on top of each other (layering). - /// - /// See Applying Animations in the Spine Runtimes Guide. - /// - public class AnimationState { - static readonly Animation EmptyAnimation = new Animation("", new ExposedList(), 0); - - /// 1) A previously applied timeline has set this property. - /// Result: Mix from the current pose to the timeline pose. - internal const int Subsequent = 0; - /// 1) This is the first timeline to set this property. - /// 2) The next track entry applied after this one does not have a timeline to set this property. - /// Result: Mix from the setup pose to the timeline pose. - internal const int First = 1; - /// 1) This is the first timeline to set this property. - /// 2) The next track entry to be applied does have a timeline to set this property. - /// 3) The next track entry after that one does not have a timeline to set this property. - /// Result: Mix from the setup pose to the timeline pose, but do not mix out. This avoids "dipping" when crossfading animations - /// that key the same property. A subsequent timeline will set this property using a mix. - internal const int Hold = 2; - /// 1) This is the first timeline to set this property. - /// 2) The next track entry to be applied does have a timeline to set this property. - /// 3) The next track entry after that one does have a timeline to set this property. - /// 4) timelineHoldMix stores the first subsequent track entry that does not have a timeline to set this property. - /// Result: The same as HOLD except the mix percentage from the timelineHoldMix track entry is used. This handles when more than - /// 2 track entries in a row have a timeline that sets the same property. - /// Eg, A -> B -> C -> D where A, B, and C have a timeline setting same property, but D does not. When A is applied, to avoid - /// "dipping" A is not mixed out, however D (the first entry that doesn't set the property) mixing in is used to mix out A - /// (which affects B and C). Without using D to mix out, A would be applied fully until mixing completes, then snap into - /// place. - internal const int HoldMix = 3; - - internal const int Setup = 1, Current = 2; - - protected AnimationStateData data; - private readonly ExposedList tracks = new ExposedList(); - private readonly ExposedList events = new ExposedList(); - // difference to libgdx reference: delegates are used for event callbacks instead of 'final SnapshotArray listeners'. - internal void OnStart (TrackEntry entry) { if (Start != null) Start(entry); } - internal void OnInterrupt (TrackEntry entry) { if (Interrupt != null) Interrupt(entry); } - internal void OnEnd (TrackEntry entry) { if (End != null) End(entry); } - internal void OnDispose (TrackEntry entry) { if (Dispose != null) Dispose(entry); } - internal void OnComplete (TrackEntry entry) { if (Complete != null) Complete(entry); } - internal void OnEvent (TrackEntry entry, Event e) { if (Event != null) Event(entry, e); } - - public delegate void TrackEntryDelegate (TrackEntry trackEntry); - public event TrackEntryDelegate Start, Interrupt, End, Dispose, Complete; - - public delegate void TrackEntryEventDelegate (TrackEntry trackEntry, Event e); - public event TrackEntryEventDelegate Event; - - public void AssignEventSubscribersFrom (AnimationState src) { - Event = src.Event; - Start = src.Start; - Interrupt = src.Interrupt; - End = src.End; - Dispose = src.Dispose; - Complete = src.Complete; - } - - public void AddEventSubscribersFrom (AnimationState src) { - Event += src.Event; - Start += src.Start; - Interrupt += src.Interrupt; - End += src.End; - Dispose += src.Dispose; - Complete += src.Complete; - } - - // end of difference - private readonly EventQueue queue; // Initialized by constructor. - private readonly HashSet propertyIDs = new HashSet(); - private bool animationsChanged; - private float timeScale = 1; - private int unkeyedState; - - private readonly Pool trackEntryPool = new Pool(); - - public AnimationState (AnimationStateData data) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - this.data = data; - this.queue = new EventQueue( - this, - delegate { this.animationsChanged = true; }, - trackEntryPool - ); - } - - /// - /// Increments the track entry , setting queued animations as current if needed. - /// delta time - public void Update (float delta) { - delta *= timeScale; - var tracksItems = tracks.Items; - for (int i = 0, n = tracks.Count; i < n; i++) { - TrackEntry current = tracksItems[i]; - if (current == null) continue; - - 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. - tracksItems[i] = null; - queue.End(current); - DisposeNext(current); - continue; - } - if (current.mixingFrom != null && UpdateMixingFrom(current, 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(); - } - - /// Returns true when all mixing from entries are complete. - private bool 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; - - // Require mixTime > 0 to ensure the mixing from entry was applied at least once. - if (to.mixTime > 0 && to.mixTime >= to.mixDuration) { - // Require totalAlpha == 0 to ensure mixing is complete, unless mixDuration == 0 (the transition is a single frame). - if (from.totalAlpha == 0 || to.mixDuration == 0) { - to.mixingFrom = from.mixingFrom; - if (from.mixingFrom != null) from.mixingFrom.mixingTo = to; - to.interruptAlpha = from.interruptAlpha; - queue.End(from); - } - return finished; - } - - from.trackTime += delta * from.timeScale; - to.mixTime += delta; - return false; - } - - /// - /// Poses the skeleton using the track entry animations. The animation state is not changed, so can be applied to multiple - /// skeletons to pose them identically. - /// True if any animations were applied. - public bool Apply (Skeleton skeleton) { - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - if (animationsChanged) AnimationsChanged(); - - var events = this.events; - bool applied = false; - var tracksItems = tracks.Items; - for (int i = 0, n = tracks.Count; i < n; i++) { - TrackEntry current = tracksItems[i]; - if (current == null || current.delay > 0) continue; - applied = true; - - // Track 0 animations aren't for layering, so do not show the previously applied animations before the first key. - MixBlend blend = i == 0 ? MixBlend.First : current.mixBlend; - - // Apply mixing from entries first. - float mix = current.alpha; - if (current.mixingFrom != null) - mix *= ApplyMixingFrom(current, skeleton, blend); - else if (current.trackTime >= current.trackEnd && current.next == null) // - mix = 0; // Set to setup pose the last time the entry will be applied. - - // Apply current entry. - float animationLast = current.animationLast, animationTime = current.AnimationTime; - int timelineCount = current.animation.timelines.Count; - var timelines = current.animation.timelines; - var timelinesItems = timelines.Items; - if ((i == 0 && mix == 1) || blend == MixBlend.Add) { - for (int ii = 0; ii < timelineCount; ii++) { - var timeline = timelinesItems[ii]; - if (timeline is AttachmentTimeline) - ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, animationTime, blend, true); - else - timeline.Apply(skeleton, animationLast, animationTime, events, mix, blend, MixDirection.In); - } - } else { - var timelineMode = current.timelineMode.Items; - - bool firstFrame = current.timelinesRotation.Count != timelineCount << 1; - if (firstFrame) current.timelinesRotation.Resize(timelines.Count << 1); - var timelinesRotation = current.timelinesRotation.Items; - - for (int ii = 0; ii < timelineCount; ii++) { - Timeline timeline = timelinesItems[ii]; - MixBlend timelineBlend = timelineMode[ii] == AnimationState.Subsequent ? blend : MixBlend.Setup; - var rotateTimeline = timeline as RotateTimeline; - if (rotateTimeline != null) - ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, mix, timelineBlend, timelinesRotation, - ii << 1, firstFrame); - else if (timeline is AttachmentTimeline) - ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, animationTime, blend, true); - else - timeline.Apply(skeleton, animationLast, animationTime, events, mix, timelineBlend, MixDirection.In); - } - } - QueueEvents(current, animationTime); - events.Clear(false); - current.nextAnimationLast = animationTime; - current.nextTrackLast = current.trackTime; - } - - // Set slots attachments to the setup pose, if needed. This occurs if an animation that is mixing out sets attachments so - // subsequent timelines see any deform, but the subsequent timelines don't set an attachment (eg they are also mixing out or - // the time is before the first key). - int setupState = unkeyedState + Setup; - var slots = skeleton.slots.Items; - for (int i = 0, n = skeleton.slots.Count; i < n; i++) { - Slot slot = (Slot)slots[i]; - if (slot.attachmentState == setupState) { - string attachmentName = slot.data.attachmentName; - slot.Attachment = (attachmentName == null ? null : skeleton.GetAttachment(slot.data.index, attachmentName)); - } - } - unkeyedState += 2; // Increasing after each use avoids the need to reset attachmentState for every slot. - - queue.Drain(); - return applied; - } - - private float 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; // Tracks > 0 are transparent and can't reset to setup pose. - } else { - mix = to.mixTime / to.mixDuration; - if (mix > 1) mix = 1; - if (blend != MixBlend.First) blend = from.mixBlend; // Track 0 ignores track mix blend. - } - - var eventBuffer = mix < from.eventThreshold ? this.events : null; - bool attachments = mix < from.attachmentThreshold, drawOrder = mix < from.drawOrderThreshold; - float animationLast = from.animationLast, animationTime = from.AnimationTime; - var timelines = from.animation.timelines; - int timelineCount = timelines.Count; - var timelinesItems = timelines.Items; - float alphaHold = from.alpha * to.interruptAlpha, alphaMix = alphaHold * (1 - mix); - - if (blend == MixBlend.Add) { - for (int i = 0; i < timelineCount; i++) - timelinesItems[i].Apply(skeleton, animationLast, animationTime, eventBuffer, alphaMix, blend, MixDirection.Out); - } else { - var timelineMode = from.timelineMode.Items; - var timelineHoldMix = from.timelineHoldMix.Items; - - bool firstFrame = from.timelinesRotation.Count != timelineCount << 1; - if (firstFrame) from.timelinesRotation.Resize(timelines.Count << 1); // from.timelinesRotation.setSize - var timelinesRotation = from.timelinesRotation.Items; - - from.totalAlpha = 0; - for (int i = 0; i < timelineCount; i++) { - Timeline timeline = timelinesItems[i]; - MixDirection direction = MixDirection.Out; - MixBlend timelineBlend; - float alpha; - switch (timelineMode[i]) { - case AnimationState.Subsequent: - if (!drawOrder && timeline is DrawOrderTimeline) continue; - timelineBlend = blend; - alpha = alphaMix; - break; - case AnimationState.First: - timelineBlend = MixBlend.Setup; - alpha = alphaMix; - break; - case AnimationState.Hold: - timelineBlend = MixBlend.Setup; - alpha = alphaHold; - break; - default: // HoldMix - timelineBlend = MixBlend.Setup; - TrackEntry holdMix = timelineHoldMix[i]; - alpha = alphaHold * Math.Max(0, 1 - holdMix.mixTime / holdMix.mixDuration); - break; - } - from.totalAlpha += alpha; - var rotateTimeline = timeline as RotateTimeline; - if (rotateTimeline != null) { - ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, alpha, timelineBlend, timelinesRotation, - i << 1, firstFrame); - } else if (timeline is AttachmentTimeline) { - ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, animationTime, timelineBlend, attachments); - } else { - if (drawOrder && timeline is DrawOrderTimeline && timelineBlend == MixBlend.Setup) - direction = MixDirection.In; - timeline.Apply(skeleton, animationLast, animationTime, eventBuffer, alpha, timelineBlend, direction); - } - } - } - - if (to.mixDuration > 0) QueueEvents(from, animationTime); - this.events.Clear(false); - from.nextAnimationLast = animationTime; - from.nextTrackLast = from.trackTime; - - return mix; - } - - /// Applies the attachment timeline and sets . - /// False when: 1) the attachment timeline is mixing out, 2) mix < attachmentThreshold, and 3) the timeline - /// is not the last timeline to set the slot's attachment. In that case the timeline is applied only so subsequent - /// timelines see any deform. - private void ApplyAttachmentTimeline (AttachmentTimeline timeline, Skeleton skeleton, float time, MixBlend blend, - bool attachments) { - - Slot slot = skeleton.slots.Items[timeline.slotIndex]; - if (!slot.bone.active) return; - - float[] frames = timeline.frames; - if (time < frames[0]) { // Time is before first frame. - if (blend == MixBlend.Setup || blend == MixBlend.First) - SetAttachment(skeleton, slot, slot.data.attachmentName, attachments); - } - else { - int frameIndex; - if (time >= frames[frames.Length - 1]) // Time is after last frame. - frameIndex = frames.Length - 1; - else - frameIndex = Animation.BinarySearch(frames, time) - 1; - SetAttachment(skeleton, slot, timeline.attachmentNames[frameIndex], attachments); - } - - // If an attachment wasn't set (ie before the first frame or attachments is false), set the setup attachment later. - if (slot.attachmentState <= unkeyedState) slot.attachmentState = unkeyedState + Setup; - } - - private void SetAttachment (Skeleton skeleton, Slot slot, String attachmentName, bool attachments) { - slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slot.data.index, attachmentName); - if (attachments) slot.attachmentState = unkeyedState + Current; - } - - /// - /// Applies the rotate timeline, mixing with the current pose while keeping the same rotation direction chosen as the shortest - /// the first time the mixing was applied. - static private void ApplyRotateTimeline (RotateTimeline timeline, Skeleton skeleton, float time, float alpha, MixBlend blend, - float[] timelinesRotation, int i, bool firstFrame) { - - if (firstFrame) timelinesRotation[i] = 0; - - if (alpha == 1) { - timeline.Apply(skeleton, 0, time, null, 1, blend, MixDirection.In); - return; - } - - Bone bone = skeleton.bones.Items[timeline.boneIndex]; - if (!bone.active) return; - - float[] frames = timeline.frames; - float r1, r2; - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case MixBlend.Setup: - bone.rotation = bone.data.rotation; - return; - default: - return; - case MixBlend.First: - r1 = bone.rotation; - r2 = bone.data.rotation; - break; - } - } else { - r1 = blend == MixBlend.Setup ? bone.data.rotation : bone.rotation; - if (time >= frames[frames.Length - RotateTimeline.ENTRIES]) // Time is after last frame. - r2 = bone.data.rotation + frames[frames.Length + RotateTimeline.PREV_ROTATION]; - else { - // Interpolate between the previous frame and the current frame. - int frame = Animation.BinarySearch(frames, time, RotateTimeline.ENTRIES); - float prevRotation = frames[frame + RotateTimeline.PREV_ROTATION]; - float frameTime = frames[frame]; - float percent = timeline.GetCurvePercent((frame >> 1) - 1, - 1 - (time - frameTime) / (frames[frame + RotateTimeline.PREV_TIME] - frameTime)); - - r2 = frames[frame + RotateTimeline.ROTATION] - prevRotation; - r2 -= (16384 - (int)(16384.499999999996 - r2 / 360)) * 360; - r2 = prevRotation + r2 * percent + bone.data.rotation; - r2 -= (16384 - (int)(16384.499999999996 - r2 / 360)) * 360; - } - } - - // Mix between rotations using the direction of the shortest route on the first frame. - float total, diff = r2 - r1; - diff -= (16384 - (int)(16384.499999999996 - diff / 360)) * 360; - if (diff == 0) { - total = timelinesRotation[i]; - } else { - float lastTotal, lastDiff; - if (firstFrame) { - lastTotal = 0; - lastDiff = diff; - } else { - lastTotal = timelinesRotation[i]; // Angle and direction of mix, including loops. - lastDiff = timelinesRotation[i + 1]; // Difference between bones. - } - bool current = diff > 0, dir = lastTotal >= 0; - // Detect cross at 0 (not 180). - if (Math.Sign(lastDiff) != Math.Sign(diff) && Math.Abs(lastDiff) <= 90) { - // A cross after a 360 rotation is a loop. - if (Math.Abs(lastTotal) > 180) lastTotal += 360 * Math.Sign(lastTotal); - dir = current; - } - total = diff + lastTotal - lastTotal % 360; // Store loops as part of lastTotal. - if (dir != current) total += 360 * Math.Sign(lastTotal); - timelinesRotation[i] = total; - } - timelinesRotation[i + 1] = diff; - r1 += total * alpha; - bone.rotation = r1 - (16384 - (int)(16384.499999999996 - r1 / 360)) * 360; - } - - private void QueueEvents (TrackEntry entry, float animationTime) { - float animationStart = entry.animationStart, animationEnd = entry.animationEnd; - float duration = animationEnd - animationStart; - float trackLastWrapped = entry.trackLast % duration; - - // Queue events before complete. - var events = this.events; - var eventsItems = events.Items; - int i = 0, n = events.Count; - for (; i < n; i++) { - Event e = eventsItems[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) - complete = duration == 0 || (trackLastWrapped > entry.trackTime % duration); - else - complete = animationTime >= animationEnd && entry.animationLast < animationEnd; - if (complete) queue.Complete(entry); - - // Queue events after complete. - for (; i < n; i++) { - Event e = eventsItems[i]; - if (e.time < animationStart) continue; // Discard events outside animation start/end. - queue.Event(entry, eventsItems[i]); - } - } - - /// - /// Removes all animations from all tracks, leaving skeletons in their current pose. - /// - /// It may be desired to use to mix the skeletons back to the setup pose, - /// rather than leaving them in their current pose. - /// - public void ClearTracks () { - bool oldDrainDisabled = queue.drainDisabled; - queue.drainDisabled = true; - for (int i = 0, n = tracks.Count; i < n; i++) { - ClearTrack(i); - } - tracks.Clear(); - queue.drainDisabled = oldDrainDisabled; - queue.Drain(); - } - - /// - /// Removes all animations from the track, leaving skeletons in their current pose. - /// - /// It may be desired to use to mix the skeletons back to the setup pose, - /// rather than leaving them in their current pose. - /// - public void ClearTrack (int trackIndex) { - if (trackIndex >= tracks.Count) return; - TrackEntry current = tracks.Items[trackIndex]; - if (current == null) return; - - queue.End(current); - - DisposeNext(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.Items[current.trackIndex] = null; - - queue.Drain(); - } - - /// Sets the active TrackEntry for a given track number. - private void SetCurrent (int index, TrackEntry current, bool interrupt) { - TrackEntry from = ExpandToIndex(index); - tracks.Items[index] = current; - - if (from != null) { - if (interrupt) queue.Interrupt(from); - current.mixingFrom = from; - from.mixingTo = current; - current.mixTime = 0; - - // Store the interrupted mix percentage. - if (from.mixingFrom != null && from.mixDuration > 0) - current.interruptAlpha *= Math.Min(1, from.mixTime / from.mixDuration); - - from.timelinesRotation.Clear(); // Reset rotation for mixing out, in case entry was mixed in. - } - - queue.Start(current); // triggers AnimationsChanged - } - - - /// Sets an animation by name. - public TrackEntry SetAnimation (int trackIndex, string animationName, bool loop) { - Animation animation = data.skeletonData.FindAnimation(animationName); - if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName"); - return SetAnimation(trackIndex, animation, loop); - } - - /// Sets the current animation for a track, discarding any queued animations. If the formerly current track entry was never - /// applied to a skeleton, it is replaced (not mixed from). - /// 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 determines when the track is cleared. - /// A track entry to allow further customization of animation playback. References to the track entry must not be kept - /// after the event occurs. - public TrackEntry SetAnimation (int trackIndex, Animation animation, bool loop) { - if (animation == null) throw new ArgumentNullException("animation", "animation cannot be 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.Items[trackIndex] = current.mixingFrom; - queue.Interrupt(current); - queue.End(current); - DisposeNext(current); - current = current.mixingFrom; - interrupt = false; // mixingFrom is current again, but don't interrupt it twice. - } else { - DisposeNext(current); - } - } - TrackEntry entry = NewTrackEntry(trackIndex, animation, loop, current); - SetCurrent(trackIndex, entry, interrupt); - queue.Drain(); - return entry; - } - - /// Queues an animation by name. - /// - public TrackEntry AddAnimation (int trackIndex, string animationName, bool loop, float delay) { - Animation animation = data.skeletonData.FindAnimation(animationName); - if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName"); - return AddAnimation(trackIndex, animation, loop, delay); - } - - /// Adds an animation to be played after the current or last queued animation for a track. If the track is empty, it is - /// equivalent to calling . - /// - /// If > 0, sets . If <= 0, the delay set is the duration of the previous track entry - /// minus any mix duration (from the {@link AnimationStateData}) plus the specified Delay (ie the mix - /// ends at (Delay = 0) or before (Delay < 0) the previous track entry duration). If the - /// previous entry is looping, its next loop completion is used instead of its duration. - /// - /// A track entry to allow further customization of animation playback. References to the track entry must not be kept - /// after the event occurs. - public TrackEntry AddAnimation (int trackIndex, Animation animation, bool loop, float delay) { - if (animation == null) throw new ArgumentNullException("animation", "animation cannot be 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(); - } else { - last.next = entry; - if (delay <= 0) { - float duration = last.animationEnd - last.animationStart; - if (duration != 0) { - if (last.loop) { - delay += duration * (1 + (int)(last.trackTime / duration)); // Completion of next loop. - } else { - delay += Math.Max(duration, last.trackTime); // After duration, else next update. - } - delay -= data.GetMix(last.animation, animation); - } else - delay = last.trackTime; // Next update. - } - } - - entry.delay = delay; - return entry; - } - - /// - /// Sets an empty animation for a track, discarding any queued animations, and sets the track entry's - /// . An empty animation has no timelines and serves as a placeholder for mixing in or out. - /// - /// Mixing out is done by setting an empty animation with a mix duration using either , - /// , or . Mixing to an empty animation causes - /// the previous animation to be applied less and less over the mix duration. Properties keyed in the previous animation - /// transition to the value from lower tracks or to the setup pose value if no lower tracks key the property. A mix duration of - /// 0 still mixes out over one frame. - /// - /// Mixing in is done by first setting an empty animation, then adding an animation using - /// and on the returned track entry, set the - /// . Mixing from an empty animation causes the new animation to be applied more and - /// more over the mix duration. Properties keyed in the new animation transition from the value from lower tracks or from the - /// setup pose value if no lower tracks key the property to the value keyed in the new animation. - /// - public TrackEntry SetEmptyAnimation (int trackIndex, float mixDuration) { - TrackEntry entry = SetAnimation(trackIndex, AnimationState.EmptyAnimation, false); - entry.mixDuration = mixDuration; - entry.trackEnd = mixDuration; - return entry; - } - - /// - /// Adds an empty animation to be played after the current or last queued animation for a track, and sets the track entry's - /// . If the track is empty, it is equivalent to calling - /// . - /// - /// Track number. - /// Mix duration. - /// If > 0, sets . If <= 0, the delay set is the duration of the previous track entry - /// minus any mix duration plus the specified Delay (ie the mix ends at (Delay = 0) or - /// before (Delay < 0) the previous track entry duration). If the previous entry is looping, its next - /// loop completion is used instead of its duration. - /// A track entry to allow further customization of animation playback. References to the track entry must not be kept - /// after the event occurs. - /// - public TrackEntry AddEmptyAnimation (int trackIndex, float mixDuration, float delay) { - if (delay <= 0) delay -= mixDuration; - TrackEntry entry = AddAnimation(trackIndex, AnimationState.EmptyAnimation, false, delay); - entry.mixDuration = mixDuration; - entry.trackEnd = mixDuration; - return entry; - } - - /// - /// Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix - /// duration. - public void SetEmptyAnimations (float mixDuration) { - bool oldDrainDisabled = queue.drainDisabled; - queue.drainDisabled = true; - for (int i = 0, n = tracks.Count; i < n; i++) { - TrackEntry current = tracks.Items[i]; - if (current != null) SetEmptyAnimation(current.trackIndex, mixDuration); - } - queue.drainDisabled = oldDrainDisabled; - queue.Drain(); - } - - private TrackEntry ExpandToIndex (int index) { - if (index < tracks.Count) return tracks.Items[index]; - tracks.Resize(index + 1); - return null; - } - - /// Object-pooling version of new TrackEntry. Obtain an unused TrackEntry from the pool and clear/initialize its values. - /// May be null. - private TrackEntry NewTrackEntry (int trackIndex, Animation animation, bool loop, TrackEntry last) { - TrackEntry entry = trackEntryPool.Obtain(); // Pooling - entry.trackIndex = trackIndex; - entry.animation = animation; - entry.loop = loop; - entry.holdPrevious = false; - - entry.eventThreshold = 0; - entry.attachmentThreshold = 0; - entry.drawOrderThreshold = 0; - - entry.animationStart = 0; - entry.animationEnd = animation.Duration; - 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 = float.MaxValue; // loop ? float.MaxValue : animation.Duration; - entry.timeScale = 1; - - entry.alpha = 1; - entry.interruptAlpha = 1; - entry.mixTime = 0; - entry.mixDuration = (last == null) ? 0 : data.GetMix(last.animation, animation); - return entry; - } - - /// Dispose all track entries queued after the given TrackEntry. - private void DisposeNext (TrackEntry entry) { - TrackEntry next = entry.next; - while (next != null) { - queue.Dispose(next); - next = next.next; - } - entry.next = null; - } - - private void AnimationsChanged () { - animationsChanged = false; - - // Process in the order that animations are applied. - propertyIDs.Clear(); - - var tracksItems = tracks.Items; - for (int i = 0, n = tracks.Count; i < n; i++) { - TrackEntry entry = tracksItems[i]; - if (entry == null) continue; - while (entry.mixingFrom != null) // Move to last entry, then iterate in reverse. - entry = entry.mixingFrom; - - do { - if (entry.mixingTo == null || entry.mixBlend != MixBlend.Add) ComputeHold(entry); - entry = entry.mixingTo; - } while (entry != null); - } - } - - private void ComputeHold (TrackEntry entry) { - TrackEntry to = entry.mixingTo; - var timelines = entry.animation.timelines.Items; - int timelinesCount = entry.animation.timelines.Count; - var timelineMode = entry.timelineMode.Resize(timelinesCount).Items; //timelineMode.setSize(timelinesCount); - entry.timelineHoldMix.Clear(); - var timelineHoldMix = entry.timelineHoldMix.Resize(timelinesCount).Items; //timelineHoldMix.setSize(timelinesCount); - var propertyIDs = this.propertyIDs; - - if (to != null && to.holdPrevious) { - for (int i = 0; i < timelinesCount; i++) { - propertyIDs.Add(timelines[i].PropertyId); - timelineMode[i] = AnimationState.Hold; - } - return; - } - - // outer: - for (int i = 0; i < timelinesCount; i++) { - Timeline timeline = timelines[i]; - int id = timeline.PropertyId; - if (!propertyIDs.Add(id)) - timelineMode[i] = AnimationState.Subsequent; - else if (to == null || timeline is AttachmentTimeline || timeline is DrawOrderTimeline - || timeline is EventTimeline || !to.animation.HasTimeline(id)) { - timelineMode[i] = AnimationState.First; - } else { - for (TrackEntry next = to.mixingTo; next != null; next = next.mixingTo) { - if (next.animation.HasTimeline(id)) continue; - if (next.mixDuration > 0) { - timelineMode[i] = AnimationState.HoldMix; - timelineHoldMix[i] = next; - goto continue_outer; // continue outer; - } - break; - } - timelineMode[i] = AnimationState.Hold; - } - continue_outer: {} - } - } - - /// The track entry for the animation currently playing on the track, or null if no animation is currently playing. - public TrackEntry GetCurrent (int trackIndex) { - if (trackIndex >= tracks.Count) return null; - return tracks.Items[trackIndex]; - } - - /// Discards all listener notifications that have not yet been delivered. This can be useful to call from an - /// AnimationState event subscriber when it is known that further notifications that may have been already queued for delivery - /// are not wanted because new animations are being set. - public void ClearListenerNotifications () { - queue.Clear(); - } - - /// - /// Multiplier for the delta time when the animation state is updated, causing time for all animations and mixes to play slower - /// or faster. Defaults to 1. - /// - /// See TrackEntry for affecting a single animation. - /// - public float TimeScale { get { return timeScale; } set { timeScale = value; } } - - /// The AnimationStateData to look up mix durations. - public AnimationStateData Data { - get { - return data; - } - set { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - this.data = value; - } - } - - /// A list of tracks that have animations, which may contain nulls. - public ExposedList Tracks { get { return tracks; } } - - override public string ToString () { - var buffer = new System.Text.StringBuilder(); - for (int i = 0, n = tracks.Count; i < n; i++) { - TrackEntry entry = tracks.Items[i]; - if (entry == null) continue; - if (buffer.Length > 0) buffer.Append(", "); - buffer.Append(entry.ToString()); - } - if (buffer.Length == 0) return ""; - return buffer.ToString(); - } - } - - /// - /// - /// Stores settings and other state for the playback of an animation on an track. - /// - /// References to a track entry must not be kept after the event occurs. - /// - public class TrackEntry : Pool.IPoolable { - internal Animation animation; - - internal TrackEntry next, mixingFrom, mixingTo; - // difference to libgdx reference: delegates are used for event callbacks instead of 'AnimationStateListener listener'. - public event AnimationState.TrackEntryDelegate Start, Interrupt, End, Dispose, Complete; - public event AnimationState.TrackEntryEventDelegate Event; - internal void OnStart () { if (Start != null) Start(this); } - internal void OnInterrupt () { if (Interrupt != null) Interrupt(this); } - internal void OnEnd () { if (End != null) End(this); } - internal void OnDispose () { if (Dispose != null) Dispose(this); } - internal void OnComplete () { if (Complete != null) Complete(this); } - internal void OnEvent (Event e) { if (Event != null) Event(this, e); } - - internal int trackIndex; - - internal bool loop, holdPrevious; - internal float eventThreshold, attachmentThreshold, drawOrderThreshold; - internal float animationStart, animationEnd, animationLast, nextAnimationLast; - internal float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale = 1f; - internal float alpha, mixTime, mixDuration, interruptAlpha, totalAlpha; - internal MixBlend mixBlend = MixBlend.Replace; - internal readonly ExposedList timelineMode = new ExposedList(); - internal readonly ExposedList timelineHoldMix = new ExposedList(); - internal readonly ExposedList timelinesRotation = new ExposedList(); - - // IPoolable.Reset() - public void Reset () { - next = null; - mixingFrom = null; - mixingTo = null; - animation = null; - // replaces 'listener = null;' since delegates are used for event callbacks - Start = null; - Interrupt = null; - End = null; - Dispose = null; - Complete = null; - Event = null; - timelineMode.Clear(); - timelineHoldMix.Clear(); - timelinesRotation.Clear(); - } - - /// The index of the track where this entry is either current or queued. - /// - public int TrackIndex { get { return trackIndex; } } - - /// The animation to apply for this track entry. - public Animation Animation { get { return animation; } } - - /// - /// If true, the animation will repeat. If false it will not, instead its last frame is applied if played beyond its - /// duration. - public bool Loop { get { return loop; } set { loop = value; } } - - /// - /// - /// Seconds to postpone playing the animation. When this track entry is the current track entry, Delay - /// postpones incrementing the . When this track entry is queued, Delay is the time from - /// the start of the previous animation to when this track entry will become the current track entry (ie when the previous - /// track entry >= this track entry's Delay). - /// - /// affects the delay. - /// - public float Delay { get { return delay; } set { delay = value; } } - - /// - /// Current time in seconds this track entry has been the current track entry. The track time determines - /// . The track time can be set to start the animation at a time other than 0, without affecting - /// looping. - public float TrackTime { get { return trackTime; } set { trackTime = value; } } - - /// - /// - /// The track time in seconds when this animation will be removed from the track. Defaults to the highest possible float - /// value, meaning the animation will be applied until a new animation is set or the track is cleared. If the track end time - /// is reached, no other animations are queued for playback, and mixing from any previous animations is complete, then the - /// properties keyed by the animation are set to the setup pose and the track is cleared. - /// - /// It may be desired to use rather than have the animation - /// abruptly cease being applied. - /// - public float TrackEnd { get { return trackEnd; } set { trackEnd = value; } } - - /// - /// - /// Seconds when this animation starts, both initially and after looping. Defaults to 0. - /// - /// When changing the AnimationStart time, it often makes sense to set to the same - /// value to prevent timeline keys before the start time from triggering. - /// - public float AnimationStart { get { return animationStart; } set { animationStart = value; } } - - /// - /// Seconds for the last frame of this animation. Non-looping animations won't play past this time. Looping animations will - /// loop back to at this time. Defaults to the animation . - /// - public float AnimationEnd { get { return animationEnd; } set { animationEnd = value; } } - - /// - /// 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 AnimationLast time (exclusive) and - /// AnimationTime (inclusive). Defaults to -1 to ensure triggers on frame 0 happen the first time this animation - /// is applied. - public float AnimationLast { - get { return animationLast; } - set { - animationLast = value; - nextAnimationLast = value; - } - } - - /// - /// Uses to compute the AnimationTime, which is between - /// and . When the TrackTime is 0, the AnimationTime is equal to the - /// AnimationStart time. - /// - public float AnimationTime { - get { - if (loop) { - float duration = animationEnd - animationStart; - if (duration == 0) return animationStart; - return (trackTime % duration) + animationStart; - } - return Math.Min(trackTime + animationStart, animationEnd); - } - } - - /// - /// - /// Multiplier for the delta time when this track entry is updated, causing time for this animation to pass slower or - /// faster. Defaults to 1. - /// - /// is not affected by track entry time scale, so may need to be adjusted to - /// match the animation speed. - /// - /// When using with a Delay <= 0, note the - /// { is set using the mix duration from the , assuming time scale to be 1. If - /// the time scale is not 1, the delay may need to be adjusted. - /// - /// See AnimationState for affecting all animations. - /// - public float TimeScale { get { return timeScale; } set { timeScale = value; } } - - /// - /// - /// Values < 1 mix this animation with the skeleton's current pose (usually the pose resulting from lower tracks). Defaults - /// to 1, which overwrites the skeleton's current pose with this animation. - /// - /// Typically track 0 is used to completely pose the skeleton, then alpha is 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. - /// - public float Alpha { get { return alpha; } set { alpha = value; } } - - /// - /// When the mix percentage ( / ) is less than the - /// EventThreshold, event timelines are applied while this animation is being mixed out. Defaults to 0, so event - /// timelines are not applied while this animation is being mixed out. - /// - public float EventThreshold { get { return eventThreshold; } set { eventThreshold = value; } } - - /// - /// When the mix percentage ( / ) is less than the - /// AttachmentThreshold, attachment timelines are applied while this animation is being mixed out. Defaults to - /// 0, so attachment timelines are not applied while this animation is being mixed out. - /// - public float AttachmentThreshold { get { return attachmentThreshold; } set { attachmentThreshold = value; } } - - /// - /// When the mix percentage ( / ) is less than the - /// DrawOrderThreshold, draw order timelines are applied while this animation is being mixed out. Defaults to 0, - /// so draw order timelines are not applied while this animation is being mixed out. - /// - public float DrawOrderThreshold { get { return drawOrderThreshold; } set { drawOrderThreshold = value; } } - - /// - /// The animation queued to start after this animation, or null. Next makes up a linked list. - public TrackEntry Next { get { return next; } } - - /// - /// Returns true if at least one loop has been completed. - /// - public bool IsComplete { - get { return trackTime >= animationEnd - animationStart; } - } - - /// - /// Seconds from 0 to the when mixing from the previous animation to this animation. May be - /// slightly more than MixDuration when the mix is complete. - public float MixTime { get { return mixTime; } set { mixTime = value; } } - - /// - /// - /// 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 MixDuration can be set manually rather than use the value from - /// . In that case, the MixDuration can be set for a new - /// track entry only before is first called. - /// - /// When using with a Delay <= 0, note the - /// is set using the mix duration from the , not a mix duration set - /// afterward. - /// - public float MixDuration { get { return mixDuration; } set { mixDuration = value; } } - - /// - /// - /// Controls how properties keyed in the animation are mixed with lower tracks. Defaults to , which - /// replaces the values from the lower tracks with the animation values. adds the animation values to - /// the values from the lower tracks. - /// - /// The MixBlend can be set for a new track entry only before is first - /// called. - /// - public MixBlend MixBlend { get { return mixBlend; } set { mixBlend = value; } } - - /// - /// 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 linked list. - public TrackEntry MixingFrom { get { return mixingFrom; } } - - /// - /// The track entry for the next animation when mixing from this animation to the next animation, or null if no mixing is - /// currently occuring. When mixing to multiple animations, MixingTo makes up a linked list. - public TrackEntry MixingTo { get { return mixingTo; } } - - /// - /// - /// 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. - /// - public bool HoldPrevious { get { return holdPrevious; } set { holdPrevious = value; } } - - /// - /// - /// 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 and starting animations on other tracks. - /// - /// Mixing with 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. - /// - public void ResetRotationDirections () { - timelinesRotation.Clear(); - } - - override public string ToString () { - return animation == null ? "" : animation.name; - } - } - - class EventQueue { - private readonly List eventQueueEntries = new List(); - internal bool drainDisabled; - - private readonly AnimationState state; - private readonly Pool trackEntryPool; - internal event Action AnimationsChanged; - - internal EventQueue (AnimationState state, Action HandleAnimationsChanged, Pool trackEntryPool) { - this.state = state; - this.AnimationsChanged += HandleAnimationsChanged; - this.trackEntryPool = trackEntryPool; - } - - struct EventQueueEntry { - public EventType type; - public TrackEntry entry; - public Event e; - - public EventQueueEntry (EventType eventType, TrackEntry trackEntry, Event e = null) { - this.type = eventType; - this.entry = trackEntry; - this.e = e; - } - } - - enum EventType { - Start, Interrupt, End, Dispose, Complete, Event - } - - internal void Start (TrackEntry entry) { - eventQueueEntries.Add(new EventQueueEntry(EventType.Start, entry)); - if (AnimationsChanged != null) AnimationsChanged(); - } - - internal void Interrupt (TrackEntry entry) { - eventQueueEntries.Add(new EventQueueEntry(EventType.Interrupt, entry)); - } - - internal void End (TrackEntry entry) { - eventQueueEntries.Add(new EventQueueEntry(EventType.End, entry)); - if (AnimationsChanged != null) AnimationsChanged(); - } - - internal void Dispose (TrackEntry entry) { - eventQueueEntries.Add(new EventQueueEntry(EventType.Dispose, entry)); - } - - internal void Complete (TrackEntry entry) { - eventQueueEntries.Add(new EventQueueEntry(EventType.Complete, entry)); - } - - internal void Event (TrackEntry entry, Event e) { - eventQueueEntries.Add(new EventQueueEntry(EventType.Event, entry, e)); - } - - /// Raises all events in the queue and drains the queue. - internal void Drain () { - if (drainDisabled) return; - drainDisabled = true; - - var entries = this.eventQueueEntries; - AnimationState state = this.state; - - // Don't cache entries.Count so callbacks can queue their own events (eg, call SetAnimation in AnimationState_Complete). - for (int i = 0; i < entries.Count; i++) { - var queueEntry = entries[i]; - TrackEntry trackEntry = queueEntry.entry; - - switch (queueEntry.type) { - case EventType.Start: - trackEntry.OnStart(); - state.OnStart(trackEntry); - break; - case EventType.Interrupt: - trackEntry.OnInterrupt(); - state.OnInterrupt(trackEntry); - break; - case EventType.End: - trackEntry.OnEnd(); - state.OnEnd(trackEntry); - goto case EventType.Dispose; // Fall through. (C#) - case EventType.Dispose: - trackEntry.OnDispose(); - state.OnDispose(trackEntry); - trackEntryPool.Free(trackEntry); // Pooling - break; - case EventType.Complete: - trackEntry.OnComplete(); - state.OnComplete(trackEntry); - break; - case EventType.Event: - trackEntry.OnEvent(queueEntry.e); - state.OnEvent(trackEntry, queueEntry.e); - break; - } - } - eventQueueEntries.Clear(); - - drainDisabled = false; - } - - internal void Clear () { - eventQueueEntries.Clear(); - } - } - - public class Pool where T : class, new() { - public readonly int max; - readonly Stack freeObjects; - - public int Count { get { return freeObjects.Count; } } - public int Peak { get; private set; } - - public Pool (int initialCapacity = 16, int max = int.MaxValue) { - freeObjects = new Stack(initialCapacity); - this.max = max; - } - - public T Obtain () { - return freeObjects.Count == 0 ? new T() : freeObjects.Pop(); - } - - public void Free (T obj) { - if (obj == null) throw new ArgumentNullException("obj", "obj cannot be null"); - if (freeObjects.Count < max) { - freeObjects.Push(obj); - Peak = Math.Max(Peak, freeObjects.Count); - } - Reset(obj); - } - -// protected void FreeAll (List objects) { -// if (objects == null) throw new ArgumentNullException("objects", "objects cannot be null."); -// var freeObjects = this.freeObjects; -// int max = this.max; -// for (int i = 0; i < objects.Count; i++) { -// T obj = objects[i]; -// if (obj == null) continue; -// if (freeObjects.Count < max) freeObjects.Push(obj); -// Reset(obj); -// } -// Peak = Math.Max(Peak, freeObjects.Count); -// } - - public void Clear () { - freeObjects.Clear(); - } - - protected void Reset (T obj) { - var poolable = obj as IPoolable; - if (poolable != null) poolable.Reset(); - } - - public interface IPoolable { - void Reset (); - } - } +namespace Spine3_8_95 +{ + + /// + /// + /// Applies animations over time, queues animations for later playback, mixes (crossfading) between animations, and applies + /// multiple animations on top of each other (layering). + /// + /// See Applying Animations in the Spine Runtimes Guide. + /// + public class AnimationState + { + static readonly Animation EmptyAnimation = new Animation("", new ExposedList(), 0); + + /// 1) A previously applied timeline has set this property. + /// Result: Mix from the current pose to the timeline pose. + internal const int Subsequent = 0; + /// 1) This is the first timeline to set this property. + /// 2) The next track entry applied after this one does not have a timeline to set this property. + /// Result: Mix from the setup pose to the timeline pose. + internal const int First = 1; + /// 1) This is the first timeline to set this property. + /// 2) The next track entry to be applied does have a timeline to set this property. + /// 3) The next track entry after that one does not have a timeline to set this property. + /// Result: Mix from the setup pose to the timeline pose, but do not mix out. This avoids "dipping" when crossfading animations + /// that key the same property. A subsequent timeline will set this property using a mix. + internal const int Hold = 2; + /// 1) This is the first timeline to set this property. + /// 2) The next track entry to be applied does have a timeline to set this property. + /// 3) The next track entry after that one does have a timeline to set this property. + /// 4) timelineHoldMix stores the first subsequent track entry that does not have a timeline to set this property. + /// Result: The same as HOLD except the mix percentage from the timelineHoldMix track entry is used. This handles when more than + /// 2 track entries in a row have a timeline that sets the same property. + /// Eg, A -> B -> C -> D where A, B, and C have a timeline setting same property, but D does not. When A is applied, to avoid + /// "dipping" A is not mixed out, however D (the first entry that doesn't set the property) mixing in is used to mix out A + /// (which affects B and C). Without using D to mix out, A would be applied fully until mixing completes, then snap into + /// place. + internal const int HoldMix = 3; + + internal const int Setup = 1, Current = 2; + + protected AnimationStateData data; + private readonly ExposedList tracks = new ExposedList(); + private readonly ExposedList events = new ExposedList(); + // difference to libgdx reference: delegates are used for event callbacks instead of 'final SnapshotArray listeners'. + internal void OnStart(TrackEntry entry) { if (Start != null) Start(entry); } + internal void OnInterrupt(TrackEntry entry) { if (Interrupt != null) Interrupt(entry); } + internal void OnEnd(TrackEntry entry) { if (End != null) End(entry); } + internal void OnDispose(TrackEntry entry) { if (Dispose != null) Dispose(entry); } + internal void OnComplete(TrackEntry entry) { if (Complete != null) Complete(entry); } + internal void OnEvent(TrackEntry entry, Event e) { if (Event != null) Event(entry, e); } + + public delegate void TrackEntryDelegate(TrackEntry trackEntry); + public event TrackEntryDelegate Start, Interrupt, End, Dispose, Complete; + + public delegate void TrackEntryEventDelegate(TrackEntry trackEntry, Event e); + public event TrackEntryEventDelegate Event; + + public void AssignEventSubscribersFrom(AnimationState src) + { + Event = src.Event; + Start = src.Start; + Interrupt = src.Interrupt; + End = src.End; + Dispose = src.Dispose; + Complete = src.Complete; + } + + public void AddEventSubscribersFrom(AnimationState src) + { + Event += src.Event; + Start += src.Start; + Interrupt += src.Interrupt; + End += src.End; + Dispose += src.Dispose; + Complete += src.Complete; + } + + // end of difference + private readonly EventQueue queue; // Initialized by constructor. + private readonly HashSet propertyIDs = new HashSet(); + private bool animationsChanged; + private float timeScale = 1; + private int unkeyedState; + + private readonly Pool trackEntryPool = new Pool(); + + public AnimationState(AnimationStateData data) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + this.data = data; + this.queue = new EventQueue( + this, + delegate { this.animationsChanged = true; }, + trackEntryPool + ); + } + + /// + /// Increments the track entry , setting queued animations as current if needed. + /// delta time + public void Update(float delta) + { + delta *= timeScale; + var tracksItems = tracks.Items; + for (int i = 0, n = tracks.Count; i < n; i++) + { + TrackEntry current = tracksItems[i]; + if (current == null) continue; + + 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. + tracksItems[i] = null; + queue.End(current); + DisposeNext(current); + continue; + } + if (current.mixingFrom != null && UpdateMixingFrom(current, 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(); + } + + /// Returns true when all mixing from entries are complete. + private bool 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; + + // Require mixTime > 0 to ensure the mixing from entry was applied at least once. + if (to.mixTime > 0 && to.mixTime >= to.mixDuration) + { + // Require totalAlpha == 0 to ensure mixing is complete, unless mixDuration == 0 (the transition is a single frame). + if (from.totalAlpha == 0 || to.mixDuration == 0) + { + to.mixingFrom = from.mixingFrom; + if (from.mixingFrom != null) from.mixingFrom.mixingTo = to; + to.interruptAlpha = from.interruptAlpha; + queue.End(from); + } + return finished; + } + + from.trackTime += delta * from.timeScale; + to.mixTime += delta; + return false; + } + + /// + /// Poses the skeleton using the track entry animations. The animation state is not changed, so can be applied to multiple + /// skeletons to pose them identically. + /// True if any animations were applied. + public bool Apply(Skeleton skeleton) + { + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + if (animationsChanged) AnimationsChanged(); + + var events = this.events; + bool applied = false; + var tracksItems = tracks.Items; + for (int i = 0, n = tracks.Count; i < n; i++) + { + TrackEntry current = tracksItems[i]; + if (current == null || current.delay > 0) continue; + applied = true; + + // Track 0 animations aren't for layering, so do not show the previously applied animations before the first key. + MixBlend blend = i == 0 ? MixBlend.First : current.mixBlend; + + // Apply mixing from entries first. + float mix = current.alpha; + if (current.mixingFrom != null) + mix *= ApplyMixingFrom(current, skeleton, blend); + else if (current.trackTime >= current.trackEnd && current.next == null) // + mix = 0; // Set to setup pose the last time the entry will be applied. + + // Apply current entry. + float animationLast = current.animationLast, animationTime = current.AnimationTime; + int timelineCount = current.animation.timelines.Count; + var timelines = current.animation.timelines; + var timelinesItems = timelines.Items; + if ((i == 0 && mix == 1) || blend == MixBlend.Add) + { + for (int ii = 0; ii < timelineCount; ii++) + { + var timeline = timelinesItems[ii]; + if (timeline is AttachmentTimeline) + ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, animationTime, blend, true); + else + timeline.Apply(skeleton, animationLast, animationTime, events, mix, blend, MixDirection.In); + } + } + else + { + var timelineMode = current.timelineMode.Items; + + bool firstFrame = current.timelinesRotation.Count != timelineCount << 1; + if (firstFrame) current.timelinesRotation.Resize(timelines.Count << 1); + var timelinesRotation = current.timelinesRotation.Items; + + for (int ii = 0; ii < timelineCount; ii++) + { + Timeline timeline = timelinesItems[ii]; + MixBlend timelineBlend = timelineMode[ii] == AnimationState.Subsequent ? blend : MixBlend.Setup; + var rotateTimeline = timeline as RotateTimeline; + if (rotateTimeline != null) + ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, mix, timelineBlend, timelinesRotation, + ii << 1, firstFrame); + else if (timeline is AttachmentTimeline) + ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, animationTime, blend, true); + else + timeline.Apply(skeleton, animationLast, animationTime, events, mix, timelineBlend, MixDirection.In); + } + } + QueueEvents(current, animationTime); + events.Clear(false); + current.nextAnimationLast = animationTime; + current.nextTrackLast = current.trackTime; + } + + // Set slots attachments to the setup pose, if needed. This occurs if an animation that is mixing out sets attachments so + // subsequent timelines see any deform, but the subsequent timelines don't set an attachment (eg they are also mixing out or + // the time is before the first key). + int setupState = unkeyedState + Setup; + var slots = skeleton.slots.Items; + for (int i = 0, n = skeleton.slots.Count; i < n; i++) + { + Slot slot = slots[i]; + if (slot.attachmentState == setupState) + { + string attachmentName = slot.data.attachmentName; + slot.Attachment = (attachmentName == null ? null : skeleton.GetAttachment(slot.data.index, attachmentName)); + } + } + unkeyedState += 2; // Increasing after each use avoids the need to reset attachmentState for every slot. + + queue.Drain(); + return applied; + } + + private float 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; // Tracks > 0 are transparent and can't reset to setup pose. + } + else + { + mix = to.mixTime / to.mixDuration; + if (mix > 1) mix = 1; + if (blend != MixBlend.First) blend = from.mixBlend; // Track 0 ignores track mix blend. + } + + var eventBuffer = mix < from.eventThreshold ? this.events : null; + bool attachments = mix < from.attachmentThreshold, drawOrder = mix < from.drawOrderThreshold; + float animationLast = from.animationLast, animationTime = from.AnimationTime; + var timelines = from.animation.timelines; + int timelineCount = timelines.Count; + var timelinesItems = timelines.Items; + float alphaHold = from.alpha * to.interruptAlpha, alphaMix = alphaHold * (1 - mix); + + if (blend == MixBlend.Add) + { + for (int i = 0; i < timelineCount; i++) + timelinesItems[i].Apply(skeleton, animationLast, animationTime, eventBuffer, alphaMix, blend, MixDirection.Out); + } + else + { + var timelineMode = from.timelineMode.Items; + var timelineHoldMix = from.timelineHoldMix.Items; + + bool firstFrame = from.timelinesRotation.Count != timelineCount << 1; + if (firstFrame) from.timelinesRotation.Resize(timelines.Count << 1); // from.timelinesRotation.setSize + var timelinesRotation = from.timelinesRotation.Items; + + from.totalAlpha = 0; + for (int i = 0; i < timelineCount; i++) + { + Timeline timeline = timelinesItems[i]; + MixDirection direction = MixDirection.Out; + MixBlend timelineBlend; + float alpha; + switch (timelineMode[i]) + { + case AnimationState.Subsequent: + if (!drawOrder && timeline is DrawOrderTimeline) continue; + timelineBlend = blend; + alpha = alphaMix; + break; + case AnimationState.First: + timelineBlend = MixBlend.Setup; + alpha = alphaMix; + break; + case AnimationState.Hold: + timelineBlend = MixBlend.Setup; + alpha = alphaHold; + break; + default: // HoldMix + timelineBlend = MixBlend.Setup; + TrackEntry holdMix = timelineHoldMix[i]; + alpha = alphaHold * Math.Max(0, 1 - holdMix.mixTime / holdMix.mixDuration); + break; + } + from.totalAlpha += alpha; + var rotateTimeline = timeline as RotateTimeline; + if (rotateTimeline != null) + { + ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, alpha, timelineBlend, timelinesRotation, + i << 1, firstFrame); + } + else if (timeline is AttachmentTimeline) + { + ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, animationTime, timelineBlend, attachments); + } + else + { + if (drawOrder && timeline is DrawOrderTimeline && timelineBlend == MixBlend.Setup) + direction = MixDirection.In; + timeline.Apply(skeleton, animationLast, animationTime, eventBuffer, alpha, timelineBlend, direction); + } + } + } + + if (to.mixDuration > 0) QueueEvents(from, animationTime); + this.events.Clear(false); + from.nextAnimationLast = animationTime; + from.nextTrackLast = from.trackTime; + + return mix; + } + + /// Applies the attachment timeline and sets . + /// False when: 1) the attachment timeline is mixing out, 2) mix < attachmentThreshold, and 3) the timeline + /// is not the last timeline to set the slot's attachment. In that case the timeline is applied only so subsequent + /// timelines see any deform. + private void ApplyAttachmentTimeline(AttachmentTimeline timeline, Skeleton skeleton, float time, MixBlend blend, + bool attachments) + { + + Slot slot = skeleton.slots.Items[timeline.slotIndex]; + if (!slot.bone.active) return; + + float[] frames = timeline.frames; + if (time < frames[0]) + { // Time is before first frame. + if (blend == MixBlend.Setup || blend == MixBlend.First) + SetAttachment(skeleton, slot, slot.data.attachmentName, attachments); + } + else + { + int frameIndex; + if (time >= frames[frames.Length - 1]) // Time is after last frame. + frameIndex = frames.Length - 1; + else + frameIndex = Animation.BinarySearch(frames, time) - 1; + SetAttachment(skeleton, slot, timeline.attachmentNames[frameIndex], attachments); + } + + // If an attachment wasn't set (ie before the first frame or attachments is false), set the setup attachment later. + if (slot.attachmentState <= unkeyedState) slot.attachmentState = unkeyedState + Setup; + } + + private void SetAttachment(Skeleton skeleton, Slot slot, String attachmentName, bool attachments) + { + slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slot.data.index, attachmentName); + if (attachments) slot.attachmentState = unkeyedState + Current; + } + + /// + /// Applies the rotate timeline, mixing with the current pose while keeping the same rotation direction chosen as the shortest + /// the first time the mixing was applied. + static private void ApplyRotateTimeline(RotateTimeline timeline, Skeleton skeleton, float time, float alpha, MixBlend blend, + float[] timelinesRotation, int i, bool firstFrame) + { + + if (firstFrame) timelinesRotation[i] = 0; + + if (alpha == 1) + { + timeline.Apply(skeleton, 0, time, null, 1, blend, MixDirection.In); + return; + } + + Bone bone = skeleton.bones.Items[timeline.boneIndex]; + if (!bone.active) return; + + float[] frames = timeline.frames; + float r1, r2; + if (time < frames[0]) + { // Time is before first frame. + switch (blend) + { + case MixBlend.Setup: + bone.rotation = bone.data.rotation; + return; + default: + return; + case MixBlend.First: + r1 = bone.rotation; + r2 = bone.data.rotation; + break; + } + } + else + { + r1 = blend == MixBlend.Setup ? bone.data.rotation : bone.rotation; + if (time >= frames[frames.Length - RotateTimeline.ENTRIES]) // Time is after last frame. + r2 = bone.data.rotation + frames[frames.Length + RotateTimeline.PREV_ROTATION]; + else + { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, RotateTimeline.ENTRIES); + float prevRotation = frames[frame + RotateTimeline.PREV_ROTATION]; + float frameTime = frames[frame]; + float percent = timeline.GetCurvePercent((frame >> 1) - 1, + 1 - (time - frameTime) / (frames[frame + RotateTimeline.PREV_TIME] - frameTime)); + + r2 = frames[frame + RotateTimeline.ROTATION] - prevRotation; + r2 -= (16384 - (int)(16384.499999999996 - r2 / 360)) * 360; + r2 = prevRotation + r2 * percent + bone.data.rotation; + r2 -= (16384 - (int)(16384.499999999996 - r2 / 360)) * 360; + } + } + + // Mix between rotations using the direction of the shortest route on the first frame. + float total, diff = r2 - r1; + diff -= (16384 - (int)(16384.499999999996 - diff / 360)) * 360; + if (diff == 0) + { + total = timelinesRotation[i]; + } + else + { + float lastTotal, lastDiff; + if (firstFrame) + { + lastTotal = 0; + lastDiff = diff; + } + else + { + lastTotal = timelinesRotation[i]; // Angle and direction of mix, including loops. + lastDiff = timelinesRotation[i + 1]; // Difference between bones. + } + bool current = diff > 0, dir = lastTotal >= 0; + // Detect cross at 0 (not 180). + if (Math.Sign(lastDiff) != Math.Sign(diff) && Math.Abs(lastDiff) <= 90) + { + // A cross after a 360 rotation is a loop. + if (Math.Abs(lastTotal) > 180) lastTotal += 360 * Math.Sign(lastTotal); + dir = current; + } + total = diff + lastTotal - lastTotal % 360; // Store loops as part of lastTotal. + if (dir != current) total += 360 * Math.Sign(lastTotal); + timelinesRotation[i] = total; + } + timelinesRotation[i + 1] = diff; + r1 += total * alpha; + bone.rotation = r1 - (16384 - (int)(16384.499999999996 - r1 / 360)) * 360; + } + + private void QueueEvents(TrackEntry entry, float animationTime) + { + float animationStart = entry.animationStart, animationEnd = entry.animationEnd; + float duration = animationEnd - animationStart; + float trackLastWrapped = entry.trackLast % duration; + + // Queue events before complete. + var events = this.events; + var eventsItems = events.Items; + int i = 0, n = events.Count; + for (; i < n; i++) + { + Event e = eventsItems[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) + complete = duration == 0 || (trackLastWrapped > entry.trackTime % duration); + else + complete = animationTime >= animationEnd && entry.animationLast < animationEnd; + if (complete) queue.Complete(entry); + + // Queue events after complete. + for (; i < n; i++) + { + Event e = eventsItems[i]; + if (e.time < animationStart) continue; // Discard events outside animation start/end. + queue.Event(entry, eventsItems[i]); + } + } + + /// + /// Removes all animations from all tracks, leaving skeletons in their current pose. + /// + /// It may be desired to use to mix the skeletons back to the setup pose, + /// rather than leaving them in their current pose. + /// + public void ClearTracks() + { + bool oldDrainDisabled = queue.drainDisabled; + queue.drainDisabled = true; + for (int i = 0, n = tracks.Count; i < n; i++) + { + ClearTrack(i); + } + tracks.Clear(); + queue.drainDisabled = oldDrainDisabled; + queue.Drain(); + } + + /// + /// Removes all animations from the track, leaving skeletons in their current pose. + /// + /// It may be desired to use to mix the skeletons back to the setup pose, + /// rather than leaving them in their current pose. + /// + public void ClearTrack(int trackIndex) + { + if (trackIndex >= tracks.Count) return; + TrackEntry current = tracks.Items[trackIndex]; + if (current == null) return; + + queue.End(current); + + DisposeNext(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.Items[current.trackIndex] = null; + + queue.Drain(); + } + + /// Sets the active TrackEntry for a given track number. + private void SetCurrent(int index, TrackEntry current, bool interrupt) + { + TrackEntry from = ExpandToIndex(index); + tracks.Items[index] = current; + + if (from != null) + { + if (interrupt) queue.Interrupt(from); + current.mixingFrom = from; + from.mixingTo = current; + current.mixTime = 0; + + // Store the interrupted mix percentage. + if (from.mixingFrom != null && from.mixDuration > 0) + current.interruptAlpha *= Math.Min(1, from.mixTime / from.mixDuration); + + from.timelinesRotation.Clear(); // Reset rotation for mixing out, in case entry was mixed in. + } + + queue.Start(current); // triggers AnimationsChanged + } + + + /// Sets an animation by name. + public TrackEntry SetAnimation(int trackIndex, string animationName, bool loop) + { + Animation animation = data.skeletonData.FindAnimation(animationName); + if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName"); + return SetAnimation(trackIndex, animation, loop); + } + + /// Sets the current animation for a track, discarding any queued animations. If the formerly current track entry was never + /// applied to a skeleton, it is replaced (not mixed from). + /// 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 determines when the track is cleared. + /// A track entry to allow further customization of animation playback. References to the track entry must not be kept + /// after the event occurs. + public TrackEntry SetAnimation(int trackIndex, Animation animation, bool loop) + { + if (animation == null) throw new ArgumentNullException("animation", "animation cannot be 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.Items[trackIndex] = current.mixingFrom; + queue.Interrupt(current); + queue.End(current); + DisposeNext(current); + current = current.mixingFrom; + interrupt = false; // mixingFrom is current again, but don't interrupt it twice. + } + else + { + DisposeNext(current); + } + } + TrackEntry entry = NewTrackEntry(trackIndex, animation, loop, current); + SetCurrent(trackIndex, entry, interrupt); + queue.Drain(); + return entry; + } + + /// Queues an animation by name. + /// + public TrackEntry AddAnimation(int trackIndex, string animationName, bool loop, float delay) + { + Animation animation = data.skeletonData.FindAnimation(animationName); + if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName"); + return AddAnimation(trackIndex, animation, loop, delay); + } + + /// Adds an animation to be played after the current or last queued animation for a track. If the track is empty, it is + /// equivalent to calling . + /// + /// If > 0, sets . If <= 0, the delay set is the duration of the previous track entry + /// minus any mix duration (from the {@link AnimationStateData}) plus the specified Delay (ie the mix + /// ends at (Delay = 0) or before (Delay < 0) the previous track entry duration). If the + /// previous entry is looping, its next loop completion is used instead of its duration. + /// + /// A track entry to allow further customization of animation playback. References to the track entry must not be kept + /// after the event occurs. + public TrackEntry AddAnimation(int trackIndex, Animation animation, bool loop, float delay) + { + if (animation == null) throw new ArgumentNullException("animation", "animation cannot be 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(); + } + else + { + last.next = entry; + if (delay <= 0) + { + float duration = last.animationEnd - last.animationStart; + if (duration != 0) + { + if (last.loop) + { + delay += duration * (1 + (int)(last.trackTime / duration)); // Completion of next loop. + } + else + { + delay += Math.Max(duration, last.trackTime); // After duration, else next update. + } + delay -= data.GetMix(last.animation, animation); + } + else + delay = last.trackTime; // Next update. + } + } + + entry.delay = delay; + return entry; + } + + /// + /// Sets an empty animation for a track, discarding any queued animations, and sets the track entry's + /// . An empty animation has no timelines and serves as a placeholder for mixing in or out. + /// + /// Mixing out is done by setting an empty animation with a mix duration using either , + /// , or . Mixing to an empty animation causes + /// the previous animation to be applied less and less over the mix duration. Properties keyed in the previous animation + /// transition to the value from lower tracks or to the setup pose value if no lower tracks key the property. A mix duration of + /// 0 still mixes out over one frame. + /// + /// Mixing in is done by first setting an empty animation, then adding an animation using + /// and on the returned track entry, set the + /// . Mixing from an empty animation causes the new animation to be applied more and + /// more over the mix duration. Properties keyed in the new animation transition from the value from lower tracks or from the + /// setup pose value if no lower tracks key the property to the value keyed in the new animation. + /// + public TrackEntry SetEmptyAnimation(int trackIndex, float mixDuration) + { + TrackEntry entry = SetAnimation(trackIndex, AnimationState.EmptyAnimation, false); + entry.mixDuration = mixDuration; + entry.trackEnd = mixDuration; + return entry; + } + + /// + /// Adds an empty animation to be played after the current or last queued animation for a track, and sets the track entry's + /// . If the track is empty, it is equivalent to calling + /// . + /// + /// Track number. + /// Mix duration. + /// If > 0, sets . If <= 0, the delay set is the duration of the previous track entry + /// minus any mix duration plus the specified Delay (ie the mix ends at (Delay = 0) or + /// before (Delay < 0) the previous track entry duration). If the previous entry is looping, its next + /// loop completion is used instead of its duration. + /// A track entry to allow further customization of animation playback. References to the track entry must not be kept + /// after the event occurs. + /// + public TrackEntry AddEmptyAnimation(int trackIndex, float mixDuration, float delay) + { + if (delay <= 0) delay -= mixDuration; + TrackEntry entry = AddAnimation(trackIndex, AnimationState.EmptyAnimation, false, delay); + entry.mixDuration = mixDuration; + entry.trackEnd = mixDuration; + return entry; + } + + /// + /// Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix + /// duration. + public void SetEmptyAnimations(float mixDuration) + { + bool oldDrainDisabled = queue.drainDisabled; + queue.drainDisabled = true; + for (int i = 0, n = tracks.Count; i < n; i++) + { + TrackEntry current = tracks.Items[i]; + if (current != null) SetEmptyAnimation(current.trackIndex, mixDuration); + } + queue.drainDisabled = oldDrainDisabled; + queue.Drain(); + } + + private TrackEntry ExpandToIndex(int index) + { + if (index < tracks.Count) return tracks.Items[index]; + tracks.Resize(index + 1); + return null; + } + + /// Object-pooling version of new TrackEntry. Obtain an unused TrackEntry from the pool and clear/initialize its values. + /// May be null. + private TrackEntry NewTrackEntry(int trackIndex, Animation animation, bool loop, TrackEntry last) + { + TrackEntry entry = trackEntryPool.Obtain(); // Pooling + entry.trackIndex = trackIndex; + entry.animation = animation; + entry.loop = loop; + entry.holdPrevious = false; + + entry.eventThreshold = 0; + entry.attachmentThreshold = 0; + entry.drawOrderThreshold = 0; + + entry.animationStart = 0; + entry.animationEnd = animation.Duration; + 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 = float.MaxValue; // loop ? float.MaxValue : animation.Duration; + entry.timeScale = 1; + + entry.alpha = 1; + entry.interruptAlpha = 1; + entry.mixTime = 0; + entry.mixDuration = (last == null) ? 0 : data.GetMix(last.animation, animation); + return entry; + } + + /// Dispose all track entries queued after the given TrackEntry. + private void DisposeNext(TrackEntry entry) + { + TrackEntry next = entry.next; + while (next != null) + { + queue.Dispose(next); + next = next.next; + } + entry.next = null; + } + + private void AnimationsChanged() + { + animationsChanged = false; + + // Process in the order that animations are applied. + propertyIDs.Clear(); + + var tracksItems = tracks.Items; + for (int i = 0, n = tracks.Count; i < n; i++) + { + TrackEntry entry = tracksItems[i]; + if (entry == null) continue; + while (entry.mixingFrom != null) // Move to last entry, then iterate in reverse. + entry = entry.mixingFrom; + + do + { + if (entry.mixingTo == null || entry.mixBlend != MixBlend.Add) ComputeHold(entry); + entry = entry.mixingTo; + } while (entry != null); + } + } + + private void ComputeHold(TrackEntry entry) + { + TrackEntry to = entry.mixingTo; + var timelines = entry.animation.timelines.Items; + int timelinesCount = entry.animation.timelines.Count; + var timelineMode = entry.timelineMode.Resize(timelinesCount).Items; //timelineMode.setSize(timelinesCount); + entry.timelineHoldMix.Clear(); + var timelineHoldMix = entry.timelineHoldMix.Resize(timelinesCount).Items; //timelineHoldMix.setSize(timelinesCount); + var propertyIDs = this.propertyIDs; + + if (to != null && to.holdPrevious) + { + for (int i = 0; i < timelinesCount; i++) + { + propertyIDs.Add(timelines[i].PropertyId); + timelineMode[i] = AnimationState.Hold; + } + return; + } + + // outer: + for (int i = 0; i < timelinesCount; i++) + { + Timeline timeline = timelines[i]; + int id = timeline.PropertyId; + if (!propertyIDs.Add(id)) + timelineMode[i] = AnimationState.Subsequent; + else if (to == null || timeline is AttachmentTimeline || timeline is DrawOrderTimeline + || timeline is EventTimeline || !to.animation.HasTimeline(id)) + { + timelineMode[i] = AnimationState.First; + } + else + { + for (TrackEntry next = to.mixingTo; next != null; next = next.mixingTo) + { + if (next.animation.HasTimeline(id)) continue; + if (next.mixDuration > 0) + { + timelineMode[i] = AnimationState.HoldMix; + timelineHoldMix[i] = next; + goto continue_outer; // continue outer; + } + break; + } + timelineMode[i] = AnimationState.Hold; + } + continue_outer: { } + } + } + + /// The track entry for the animation currently playing on the track, or null if no animation is currently playing. + public TrackEntry GetCurrent(int trackIndex) + { + if (trackIndex >= tracks.Count) return null; + return tracks.Items[trackIndex]; + } + + /// Discards all listener notifications that have not yet been delivered. This can be useful to call from an + /// AnimationState event subscriber when it is known that further notifications that may have been already queued for delivery + /// are not wanted because new animations are being set. + public void ClearListenerNotifications() + { + queue.Clear(); + } + + /// + /// Multiplier for the delta time when the animation state is updated, causing time for all animations and mixes to play slower + /// or faster. Defaults to 1. + /// + /// See TrackEntry for affecting a single animation. + /// + public float TimeScale { get { return timeScale; } set { timeScale = value; } } + + /// The AnimationStateData to look up mix durations. + public AnimationStateData Data + { + get + { + return data; + } + set + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + this.data = value; + } + } + + /// A list of tracks that have animations, which may contain nulls. + public ExposedList Tracks { get { return tracks; } } + + override public string ToString() + { + var buffer = new System.Text.StringBuilder(); + for (int i = 0, n = tracks.Count; i < n; i++) + { + TrackEntry entry = tracks.Items[i]; + if (entry == null) continue; + if (buffer.Length > 0) buffer.Append(", "); + buffer.Append(entry.ToString()); + } + if (buffer.Length == 0) return ""; + return buffer.ToString(); + } + } + + /// + /// + /// Stores settings and other state for the playback of an animation on an track. + /// + /// References to a track entry must not be kept after the event occurs. + /// + public class TrackEntry : Pool.IPoolable + { + internal Animation animation; + + internal TrackEntry next, mixingFrom, mixingTo; + // difference to libgdx reference: delegates are used for event callbacks instead of 'AnimationStateListener listener'. + public event AnimationState.TrackEntryDelegate Start, Interrupt, End, Dispose, Complete; + public event AnimationState.TrackEntryEventDelegate Event; + internal void OnStart() { if (Start != null) Start(this); } + internal void OnInterrupt() { if (Interrupt != null) Interrupt(this); } + internal void OnEnd() { if (End != null) End(this); } + internal void OnDispose() { if (Dispose != null) Dispose(this); } + internal void OnComplete() { if (Complete != null) Complete(this); } + internal void OnEvent(Event e) { if (Event != null) Event(this, e); } + + internal int trackIndex; + + internal bool loop, holdPrevious; + internal float eventThreshold, attachmentThreshold, drawOrderThreshold; + internal float animationStart, animationEnd, animationLast, nextAnimationLast; + internal float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale = 1f; + internal float alpha, mixTime, mixDuration, interruptAlpha, totalAlpha; + internal MixBlend mixBlend = MixBlend.Replace; + internal readonly ExposedList timelineMode = new ExposedList(); + internal readonly ExposedList timelineHoldMix = new ExposedList(); + internal readonly ExposedList timelinesRotation = new ExposedList(); + + // IPoolable.Reset() + public void Reset() + { + next = null; + mixingFrom = null; + mixingTo = null; + animation = null; + // replaces 'listener = null;' since delegates are used for event callbacks + Start = null; + Interrupt = null; + End = null; + Dispose = null; + Complete = null; + Event = null; + timelineMode.Clear(); + timelineHoldMix.Clear(); + timelinesRotation.Clear(); + } + + /// The index of the track where this entry is either current or queued. + /// + public int TrackIndex { get { return trackIndex; } } + + /// The animation to apply for this track entry. + public Animation Animation { get { return animation; } } + + /// + /// If true, the animation will repeat. If false it will not, instead its last frame is applied if played beyond its + /// duration. + public bool Loop { get { return loop; } set { loop = value; } } + + /// + /// + /// Seconds to postpone playing the animation. When this track entry is the current track entry, Delay + /// postpones incrementing the . When this track entry is queued, Delay is the time from + /// the start of the previous animation to when this track entry will become the current track entry (ie when the previous + /// track entry >= this track entry's Delay). + /// + /// affects the delay. + /// + public float Delay { get { return delay; } set { delay = value; } } + + /// + /// Current time in seconds this track entry has been the current track entry. The track time determines + /// . The track time can be set to start the animation at a time other than 0, without affecting + /// looping. + public float TrackTime { get { return trackTime; } set { trackTime = value; } } + + /// + /// + /// The track time in seconds when this animation will be removed from the track. Defaults to the highest possible float + /// value, meaning the animation will be applied until a new animation is set or the track is cleared. If the track end time + /// is reached, no other animations are queued for playback, and mixing from any previous animations is complete, then the + /// properties keyed by the animation are set to the setup pose and the track is cleared. + /// + /// It may be desired to use rather than have the animation + /// abruptly cease being applied. + /// + public float TrackEnd { get { return trackEnd; } set { trackEnd = value; } } + + /// + /// + /// Seconds when this animation starts, both initially and after looping. Defaults to 0. + /// + /// When changing the AnimationStart time, it often makes sense to set to the same + /// value to prevent timeline keys before the start time from triggering. + /// + public float AnimationStart { get { return animationStart; } set { animationStart = value; } } + + /// + /// Seconds for the last frame of this animation. Non-looping animations won't play past this time. Looping animations will + /// loop back to at this time. Defaults to the animation . + /// + public float AnimationEnd { get { return animationEnd; } set { animationEnd = value; } } + + /// + /// 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 AnimationLast time (exclusive) and + /// AnimationTime (inclusive). Defaults to -1 to ensure triggers on frame 0 happen the first time this animation + /// is applied. + public float AnimationLast + { + get { return animationLast; } + set + { + animationLast = value; + nextAnimationLast = value; + } + } + + /// + /// Uses to compute the AnimationTime, which is between + /// and . When the TrackTime is 0, the AnimationTime is equal to the + /// AnimationStart time. + /// + public float AnimationTime + { + get + { + if (loop) + { + float duration = animationEnd - animationStart; + if (duration == 0) return animationStart; + return (trackTime % duration) + animationStart; + } + return Math.Min(trackTime + animationStart, animationEnd); + } + } + + /// + /// + /// Multiplier for the delta time when this track entry is updated, causing time for this animation to pass slower or + /// faster. Defaults to 1. + /// + /// is not affected by track entry time scale, so may need to be adjusted to + /// match the animation speed. + /// + /// When using with a Delay <= 0, note the + /// { is set using the mix duration from the , assuming time scale to be 1. If + /// the time scale is not 1, the delay may need to be adjusted. + /// + /// See AnimationState for affecting all animations. + /// + public float TimeScale { get { return timeScale; } set { timeScale = value; } } + + /// + /// + /// Values < 1 mix this animation with the skeleton's current pose (usually the pose resulting from lower tracks). Defaults + /// to 1, which overwrites the skeleton's current pose with this animation. + /// + /// Typically track 0 is used to completely pose the skeleton, then alpha is 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. + /// + public float Alpha { get { return alpha; } set { alpha = value; } } + + /// + /// When the mix percentage ( / ) is less than the + /// EventThreshold, event timelines are applied while this animation is being mixed out. Defaults to 0, so event + /// timelines are not applied while this animation is being mixed out. + /// + public float EventThreshold { get { return eventThreshold; } set { eventThreshold = value; } } + + /// + /// When the mix percentage ( / ) is less than the + /// AttachmentThreshold, attachment timelines are applied while this animation is being mixed out. Defaults to + /// 0, so attachment timelines are not applied while this animation is being mixed out. + /// + public float AttachmentThreshold { get { return attachmentThreshold; } set { attachmentThreshold = value; } } + + /// + /// When the mix percentage ( / ) is less than the + /// DrawOrderThreshold, draw order timelines are applied while this animation is being mixed out. Defaults to 0, + /// so draw order timelines are not applied while this animation is being mixed out. + /// + public float DrawOrderThreshold { get { return drawOrderThreshold; } set { drawOrderThreshold = value; } } + + /// + /// The animation queued to start after this animation, or null. Next makes up a linked list. + public TrackEntry Next { get { return next; } } + + /// + /// Returns true if at least one loop has been completed. + /// + public bool IsComplete + { + get { return trackTime >= animationEnd - animationStart; } + } + + /// + /// Seconds from 0 to the when mixing from the previous animation to this animation. May be + /// slightly more than MixDuration when the mix is complete. + public float MixTime { get { return mixTime; } set { mixTime = value; } } + + /// + /// + /// 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 MixDuration can be set manually rather than use the value from + /// . In that case, the MixDuration can be set for a new + /// track entry only before is first called. + /// + /// When using with a Delay <= 0, note the + /// is set using the mix duration from the , not a mix duration set + /// afterward. + /// + public float MixDuration { get { return mixDuration; } set { mixDuration = value; } } + + /// + /// + /// Controls how properties keyed in the animation are mixed with lower tracks. Defaults to , which + /// replaces the values from the lower tracks with the animation values. adds the animation values to + /// the values from the lower tracks. + /// + /// The MixBlend can be set for a new track entry only before is first + /// called. + /// + public MixBlend MixBlend { get { return mixBlend; } set { mixBlend = value; } } + + /// + /// 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 linked list. + public TrackEntry MixingFrom { get { return mixingFrom; } } + + /// + /// The track entry for the next animation when mixing from this animation to the next animation, or null if no mixing is + /// currently occuring. When mixing to multiple animations, MixingTo makes up a linked list. + public TrackEntry MixingTo { get { return mixingTo; } } + + /// + /// + /// 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. + /// + public bool HoldPrevious { get { return holdPrevious; } set { holdPrevious = value; } } + + /// + /// + /// 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 and starting animations on other tracks. + /// + /// Mixing with 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. + /// + public void ResetRotationDirections() + { + timelinesRotation.Clear(); + } + + override public string ToString() + { + return animation == null ? "" : animation.name; + } + } + + class EventQueue + { + private readonly List eventQueueEntries = new List(); + internal bool drainDisabled; + + private readonly AnimationState state; + private readonly Pool trackEntryPool; + internal event Action AnimationsChanged; + + internal EventQueue(AnimationState state, Action HandleAnimationsChanged, Pool trackEntryPool) + { + this.state = state; + this.AnimationsChanged += HandleAnimationsChanged; + this.trackEntryPool = trackEntryPool; + } + + struct EventQueueEntry + { + public EventType type; + public TrackEntry entry; + public Event e; + + public EventQueueEntry(EventType eventType, TrackEntry trackEntry, Event e = null) + { + this.type = eventType; + this.entry = trackEntry; + this.e = e; + } + } + + enum EventType + { + Start, Interrupt, End, Dispose, Complete, Event + } + + internal void Start(TrackEntry entry) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.Start, entry)); + if (AnimationsChanged != null) AnimationsChanged(); + } + + internal void Interrupt(TrackEntry entry) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.Interrupt, entry)); + } + + internal void End(TrackEntry entry) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.End, entry)); + if (AnimationsChanged != null) AnimationsChanged(); + } + + internal void Dispose(TrackEntry entry) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.Dispose, entry)); + } + + internal void Complete(TrackEntry entry) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.Complete, entry)); + } + + internal void Event(TrackEntry entry, Event e) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.Event, entry, e)); + } + + /// Raises all events in the queue and drains the queue. + internal void Drain() + { + if (drainDisabled) return; + drainDisabled = true; + + var entries = this.eventQueueEntries; + AnimationState state = this.state; + + // Don't cache entries.Count so callbacks can queue their own events (eg, call SetAnimation in AnimationState_Complete). + for (int i = 0; i < entries.Count; i++) + { + var queueEntry = entries[i]; + TrackEntry trackEntry = queueEntry.entry; + + switch (queueEntry.type) + { + case EventType.Start: + trackEntry.OnStart(); + state.OnStart(trackEntry); + break; + case EventType.Interrupt: + trackEntry.OnInterrupt(); + state.OnInterrupt(trackEntry); + break; + case EventType.End: + trackEntry.OnEnd(); + state.OnEnd(trackEntry); + goto case EventType.Dispose; // Fall through. (C#) + case EventType.Dispose: + trackEntry.OnDispose(); + state.OnDispose(trackEntry); + trackEntryPool.Free(trackEntry); // Pooling + break; + case EventType.Complete: + trackEntry.OnComplete(); + state.OnComplete(trackEntry); + break; + case EventType.Event: + trackEntry.OnEvent(queueEntry.e); + state.OnEvent(trackEntry, queueEntry.e); + break; + } + } + eventQueueEntries.Clear(); + + drainDisabled = false; + } + + internal void Clear() + { + eventQueueEntries.Clear(); + } + } + + public class Pool where T : class, new() + { + public readonly int max; + readonly Stack freeObjects; + + public int Count { get { return freeObjects.Count; } } + public int Peak { get; private set; } + + public Pool(int initialCapacity = 16, int max = int.MaxValue) + { + freeObjects = new Stack(initialCapacity); + this.max = max; + } + + public T Obtain() + { + return freeObjects.Count == 0 ? new T() : freeObjects.Pop(); + } + + public void Free(T obj) + { + if (obj == null) throw new ArgumentNullException("obj", "obj cannot be null"); + if (freeObjects.Count < max) + { + freeObjects.Push(obj); + Peak = Math.Max(Peak, freeObjects.Count); + } + Reset(obj); + } + + // protected void FreeAll (List objects) { + // if (objects == null) throw new ArgumentNullException("objects", "objects cannot be null."); + // var freeObjects = this.freeObjects; + // int max = this.max; + // for (int i = 0; i < objects.Count; i++) { + // T obj = objects[i]; + // if (obj == null) continue; + // if (freeObjects.Count < max) freeObjects.Push(obj); + // Reset(obj); + // } + // Peak = Math.Max(Peak, freeObjects.Count); + // } + + public void Clear() + { + freeObjects.Clear(); + } + + protected void Reset(T obj) + { + var poolable = obj as IPoolable; + if (poolable != null) poolable.Reset(); + } + + public interface IPoolable + { + void Reset(); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/AnimationStateData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/AnimationStateData.cs index 3356dc0..7252f50 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/AnimationStateData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/AnimationStateData.cs @@ -30,85 +30,97 @@ using System; using System.Collections.Generic; -namespace Spine3_8_95 { +namespace Spine3_8_95 +{ - /// Stores mix (crossfade) durations to be applied when AnimationState animations are changed. - public class AnimationStateData { - internal SkeletonData skeletonData; - readonly Dictionary animationToMixTime = new Dictionary(AnimationPairComparer.Instance); - internal float defaultMix; + /// Stores mix (crossfade) durations to be applied when AnimationState animations are changed. + public class AnimationStateData + { + internal SkeletonData skeletonData; + readonly Dictionary animationToMixTime = new Dictionary(AnimationPairComparer.Instance); + internal float defaultMix; - /// The SkeletonData to look up animations when they are specified by name. - public SkeletonData SkeletonData { get { return skeletonData; } } + /// The SkeletonData to look up animations when they are specified by name. + public SkeletonData SkeletonData { get { return skeletonData; } } - /// - /// The mix duration to use when no mix duration has been specifically defined between two animations. - public float DefaultMix { get { return defaultMix; } set { defaultMix = value; } } + /// + /// The mix duration to use when no mix duration has been specifically defined between two animations. + public float DefaultMix { get { return defaultMix; } set { defaultMix = value; } } - public AnimationStateData (SkeletonData skeletonData) { - if (skeletonData == null) throw new ArgumentException("skeletonData cannot be null.", "skeletonData"); - this.skeletonData = skeletonData; - } + public AnimationStateData(SkeletonData skeletonData) + { + if (skeletonData == null) throw new ArgumentException("skeletonData cannot be null.", "skeletonData"); + this.skeletonData = skeletonData; + } - /// Sets a mix duration by animation names. - public void SetMix (string fromName, string toName, float duration) { - Animation from = skeletonData.FindAnimation(fromName); - if (from == null) throw new ArgumentException("Animation not found: " + fromName, "fromName"); - Animation to = skeletonData.FindAnimation(toName); - if (to == null) throw new ArgumentException("Animation not found: " + toName, "toName"); - SetMix(from, to, duration); - } + /// Sets a mix duration by animation names. + public void SetMix(string fromName, string toName, float duration) + { + Animation from = skeletonData.FindAnimation(fromName); + if (from == null) throw new ArgumentException("Animation not found: " + fromName, "fromName"); + Animation to = skeletonData.FindAnimation(toName); + if (to == null) throw new ArgumentException("Animation not found: " + toName, "toName"); + SetMix(from, to, duration); + } - /// Sets a mix duration when changing from the specified animation to the other. - /// See TrackEntry.MixDuration. - public void SetMix (Animation from, Animation to, float duration) { - if (from == null) throw new ArgumentNullException("from", "from cannot be null."); - if (to == null) throw new ArgumentNullException("to", "to cannot be null."); - AnimationPair key = new AnimationPair(from, to); - animationToMixTime.Remove(key); - animationToMixTime.Add(key, duration); - } + /// Sets a mix duration when changing from the specified animation to the other. + /// See TrackEntry.MixDuration. + public void SetMix(Animation from, Animation to, float duration) + { + if (from == null) throw new ArgumentNullException("from", "from cannot be null."); + if (to == null) throw new ArgumentNullException("to", "to cannot be null."); + AnimationPair key = new AnimationPair(from, to); + animationToMixTime.Remove(key); + animationToMixTime.Add(key, 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. - /// - public float GetMix (Animation from, Animation to) { - if (from == null) throw new ArgumentNullException("from", "from cannot be null."); - if (to == null) throw new ArgumentNullException("to", "to cannot be null."); - AnimationPair key = new AnimationPair(from, to); - float duration; - if (animationToMixTime.TryGetValue(key, out duration)) return duration; - return defaultMix; - } + /// + /// The mix duration to use when changing from the specified animation to the other, + /// or the DefaultMix if no mix duration has been set. + /// + public float GetMix(Animation from, Animation to) + { + if (from == null) throw new ArgumentNullException("from", "from cannot be null."); + if (to == null) throw new ArgumentNullException("to", "to cannot be null."); + AnimationPair key = new AnimationPair(from, to); + float duration; + if (animationToMixTime.TryGetValue(key, out duration)) return duration; + return defaultMix; + } - public struct AnimationPair { - public readonly Animation a1; - public readonly Animation a2; + public struct AnimationPair + { + public readonly Animation a1; + public readonly Animation a2; - public AnimationPair (Animation a1, Animation a2) { - this.a1 = a1; - this.a2 = a2; - } + public AnimationPair(Animation a1, Animation a2) + { + this.a1 = a1; + this.a2 = a2; + } - public override string ToString () { - return a1.name + "->" + a2.name; - } - } + public override string ToString() + { + return a1.name + "->" + a2.name; + } + } - // Avoids boxing in the dictionary. - public class AnimationPairComparer : IEqualityComparer { - public static readonly AnimationPairComparer Instance = new AnimationPairComparer(); + // Avoids boxing in the dictionary. + public class AnimationPairComparer : IEqualityComparer + { + public static readonly AnimationPairComparer Instance = new AnimationPairComparer(); - bool IEqualityComparer.Equals (AnimationPair x, AnimationPair y) { - return ReferenceEquals(x.a1, y.a1) && ReferenceEquals(x.a2, y.a2); - } + bool IEqualityComparer.Equals(AnimationPair x, AnimationPair y) + { + return ReferenceEquals(x.a1, y.a1) && ReferenceEquals(x.a2, y.a2); + } - int IEqualityComparer.GetHashCode (AnimationPair obj) { - // from Tuple.CombineHashCodes // return (((h1 << 5) + h1) ^ h2); - int h1 = obj.a1.GetHashCode(); - return (((h1 << 5) + h1) ^ obj.a2.GetHashCode()); - } - } - } + int IEqualityComparer.GetHashCode(AnimationPair obj) + { + // from Tuple.CombineHashCodes // return (((h1 << 5) + h1) ^ h2); + int h1 = obj.a1.GetHashCode(); + return (((h1 << 5) + h1) ^ obj.a2.GetHashCode()); + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Atlas.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Atlas.cs index cf0c359..0d5316e 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Atlas.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Atlas.cs @@ -35,31 +35,34 @@ using System.Collections.Generic; using System.Globalization; using System.IO; -using System.Reflection; #if WINDOWS_STOREAPP using System.Threading.Tasks; using Windows.Storage; #endif -namespace Spine3_8_95 { - public class Atlas : IEnumerable { - readonly List pages = new List(); - List regions = new List(); - TextureLoader textureLoader; - - #region IEnumerable implementation - public IEnumerator GetEnumerator () { - return regions.GetEnumerator(); - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () { - return regions.GetEnumerator(); - } - #endregion - - #if !(IS_UNITY) - #if WINDOWS_STOREAPP +namespace Spine3_8_95 +{ + public class Atlas : IEnumerable + { + readonly List pages = new List(); + List regions = new List(); + TextureLoader textureLoader; + + #region IEnumerable implementation + public IEnumerator GetEnumerator() + { + return regions.GetEnumerator(); + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return regions.GetEnumerator(); + } + #endregion + +#if !(IS_UNITY) +#if WINDOWS_STOREAPP private async Task ReadFile(string path, TextureLoader textureLoader) { var folder = Windows.ApplicationModel.Package.Current.InstalledLocation; var file = await folder.GetFileAsync(path).AsTask().ConfigureAwait(false); @@ -75,253 +78,286 @@ private async Task ReadFile(string path, TextureLoader textureLoader) { public Atlas(string path, TextureLoader textureLoader) { this.ReadFile(path, textureLoader).Wait(); } - #else +#else - public Atlas (string path, TextureLoader textureLoader) { + public Atlas(string path, TextureLoader textureLoader) + { - #if WINDOWS_PHONE +#if WINDOWS_PHONE Stream stream = Microsoft.Xna.Framework.TitleContainer.OpenStream(path); using (StreamReader reader = new StreamReader(stream)) { - #else - using (StreamReader reader = new StreamReader(path)) { - #endif // WINDOWS_PHONE - - try { - Load(reader, Path.GetDirectoryName(path), textureLoader); - } catch (Exception ex) { - throw new Exception("Error reading atlas file: " + path, ex); - } - - } - } - #endif // WINDOWS_STOREAPP - - #endif - - public Atlas (TextReader reader, string dir, TextureLoader textureLoader) { - Load(reader, dir, textureLoader); - } - - public Atlas (List pages, List regions) { - this.pages = pages; - this.regions = regions; - this.textureLoader = null; - } - - private void Load (TextReader reader, string imagesDir, TextureLoader textureLoader) { - if (textureLoader == null) throw new ArgumentNullException("textureLoader", "textureLoader cannot be null."); - this.textureLoader = textureLoader; - - string[] tuple = new string[4]; - AtlasPage page = null; - while (true) { - string line = reader.ReadLine(); - if (line == null) break; - if (line.Trim().Length == 0) - page = null; - else if (page == null) { - page = new AtlasPage(); - page.name = line; - - if (ReadTuple(reader, tuple) == 2) { // size is only optional for an atlas packed with an old TexturePacker. - page.width = int.Parse(tuple[0], CultureInfo.InvariantCulture); - page.height = int.Parse(tuple[1], CultureInfo.InvariantCulture); - ReadTuple(reader, tuple); - } - page.format = (Format)Enum.Parse(typeof(Format), tuple[0], false); - - ReadTuple(reader, tuple); - page.minFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[0], false); - page.magFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[1], false); - - string direction = ReadValue(reader); - page.uWrap = TextureWrap.ClampToEdge; - page.vWrap = TextureWrap.ClampToEdge; - if (direction == "x") - page.uWrap = TextureWrap.Repeat; - else if (direction == "y") - page.vWrap = TextureWrap.Repeat; - else if (direction == "xy") - page.uWrap = page.vWrap = TextureWrap.Repeat; - - textureLoader.Load(page, Path.Combine(imagesDir, line)); - - pages.Add(page); - - } else { - AtlasRegion region = new AtlasRegion(); - region.name = line; - region.page = page; - - string rotateValue = ReadValue(reader); - if (rotateValue == "true") - region.degrees = 90; - else if (rotateValue == "false") - region.degrees = 0; - else - region.degrees = int.Parse(rotateValue); - region.rotate = region.degrees == 90; - - ReadTuple(reader, tuple); - int x = int.Parse(tuple[0], CultureInfo.InvariantCulture); - int y = int.Parse(tuple[1], CultureInfo.InvariantCulture); - - ReadTuple(reader, tuple); - int width = int.Parse(tuple[0], CultureInfo.InvariantCulture); - int height = int.Parse(tuple[1], CultureInfo.InvariantCulture); - - region.u = x / (float)page.width; - region.v = y / (float)page.height; - if (region.rotate) { - region.u2 = (x + height) / (float)page.width; - region.v2 = (y + width) / (float)page.height; - } else { - region.u2 = (x + width) / (float)page.width; - region.v2 = (y + height) / (float)page.height; - } - region.x = x; - region.y = y; - region.width = Math.Abs(width); - region.height = Math.Abs(height); - - if (ReadTuple(reader, tuple) == 4) { // split is optional - region.splits = new [] {int.Parse(tuple[0], CultureInfo.InvariantCulture), - int.Parse(tuple[1], CultureInfo.InvariantCulture), - int.Parse(tuple[2], CultureInfo.InvariantCulture), - int.Parse(tuple[3], CultureInfo.InvariantCulture)}; - - if (ReadTuple(reader, tuple) == 4) { // pad is optional, but only present with splits - region.pads = new [] {int.Parse(tuple[0], CultureInfo.InvariantCulture), - int.Parse(tuple[1], CultureInfo.InvariantCulture), - int.Parse(tuple[2], CultureInfo.InvariantCulture), - int.Parse(tuple[3], CultureInfo.InvariantCulture)}; - - ReadTuple(reader, tuple); - } - } - - region.originalWidth = int.Parse(tuple[0], CultureInfo.InvariantCulture); - region.originalHeight = int.Parse(tuple[1], CultureInfo.InvariantCulture); - - ReadTuple(reader, tuple); - region.offsetX = int.Parse(tuple[0], CultureInfo.InvariantCulture); - region.offsetY = int.Parse(tuple[1], CultureInfo.InvariantCulture); - - region.index = int.Parse(ReadValue(reader), CultureInfo.InvariantCulture); - - regions.Add(region); - } - } - } - - static string ReadValue (TextReader reader) { - string line = reader.ReadLine(); - int colon = line.IndexOf(':'); - if (colon == -1) throw new Exception("Invalid line: " + line); - return line.Substring(colon + 1).Trim(); - } - - /// Returns the number of tuple values read (1, 2 or 4). - static int ReadTuple (TextReader reader, string[] tuple) { - string line = reader.ReadLine(); - int colon = line.IndexOf(':'); - if (colon == -1) throw new Exception("Invalid line: " + line); - int i = 0, lastMatch = colon + 1; - for (; i < 3; i++) { - int comma = line.IndexOf(',', lastMatch); - if (comma == -1) break; - tuple[i] = line.Substring(lastMatch, comma - lastMatch).Trim(); - lastMatch = comma + 1; - } - tuple[i] = line.Substring(lastMatch).Trim(); - return i + 1; - } +#else + using (StreamReader reader = new StreamReader(path)) + { +#endif // WINDOWS_PHONE + + try + { + Load(reader, Path.GetDirectoryName(path), textureLoader); + } + catch (Exception ex) + { + throw new Exception("Error reading atlas file: " + path, ex); + } + + } + } +#endif // WINDOWS_STOREAPP - public void FlipV () { - for (int i = 0, n = regions.Count; i < n; i++) { - AtlasRegion region = regions[i]; - region.v = 1 - region.v; - region.v2 = 1 - region.v2; - } - } - - /// 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. - /// The region, or null. - public AtlasRegion FindRegion (string name) { - for (int i = 0, n = regions.Count; i < n; i++) - if (regions[i].name == name) return regions[i]; - return null; - } - - public void Dispose () { - if (textureLoader == null) return; - for (int i = 0, n = pages.Count; i < n; i++) - textureLoader.Unload(pages[i].rendererObject); - } - } - - public enum Format { - Alpha, - Intensity, - LuminanceAlpha, - RGB565, - RGBA4444, - RGB888, - RGBA8888 - } - - public enum TextureFilter { - Nearest, - Linear, - MipMap, - MipMapNearestNearest, - MipMapLinearNearest, - MipMapNearestLinear, - MipMapLinearLinear - } - - public enum TextureWrap { - MirroredRepeat, - ClampToEdge, - Repeat - } - - public class AtlasPage { - public string name; - public Format format; - public TextureFilter minFilter; - public TextureFilter magFilter; - public TextureWrap uWrap; - public TextureWrap vWrap; - public object rendererObject; - public int width, height; - - public AtlasPage Clone () { - return MemberwiseClone() as AtlasPage; - } - } - - public class AtlasRegion { - public AtlasPage page; - public string name; - public int x, y, width, height; - public float u, v, u2, v2; - public float offsetX, offsetY; - public int originalWidth, originalHeight; - public int index; - public bool rotate; - public int degrees; - public int[] splits; - public int[] pads; - - public AtlasRegion Clone () { - return MemberwiseClone() as AtlasRegion; - } - } +#endif - public interface TextureLoader { - void Load (AtlasPage page, string path); - void Unload (Object texture); - } + public Atlas(TextReader reader, string dir, TextureLoader textureLoader) + { + Load(reader, dir, textureLoader); + } + + public Atlas(List pages, List regions) + { + this.pages = pages; + this.regions = regions; + this.textureLoader = null; + } + + private void Load(TextReader reader, string imagesDir, TextureLoader textureLoader) + { + if (textureLoader == null) throw new ArgumentNullException("textureLoader", "textureLoader cannot be null."); + this.textureLoader = textureLoader; + + string[] tuple = new string[4]; + AtlasPage page = null; + while (true) + { + string line = reader.ReadLine(); + if (line == null) break; + if (line.Trim().Length == 0) + page = null; + else if (page == null) + { + page = new AtlasPage(); + page.name = line; + + if (ReadTuple(reader, tuple) == 2) + { // size is only optional for an atlas packed with an old TexturePacker. + page.width = int.Parse(tuple[0], CultureInfo.InvariantCulture); + page.height = int.Parse(tuple[1], CultureInfo.InvariantCulture); + ReadTuple(reader, tuple); + } + page.format = (Format)Enum.Parse(typeof(Format), tuple[0], false); + + ReadTuple(reader, tuple); + page.minFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[0], false); + page.magFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[1], false); + + string direction = ReadValue(reader); + page.uWrap = TextureWrap.ClampToEdge; + page.vWrap = TextureWrap.ClampToEdge; + if (direction == "x") + page.uWrap = TextureWrap.Repeat; + else if (direction == "y") + page.vWrap = TextureWrap.Repeat; + else if (direction == "xy") + page.uWrap = page.vWrap = TextureWrap.Repeat; + + textureLoader.Load(page, Path.Combine(imagesDir, line)); + + pages.Add(page); + + } + else + { + AtlasRegion region = new AtlasRegion(); + region.name = line; + region.page = page; + + string rotateValue = ReadValue(reader); + if (rotateValue == "true") + region.degrees = 90; + else if (rotateValue == "false") + region.degrees = 0; + else + region.degrees = int.Parse(rotateValue); + region.rotate = region.degrees == 90; + + ReadTuple(reader, tuple); + int x = int.Parse(tuple[0], CultureInfo.InvariantCulture); + int y = int.Parse(tuple[1], CultureInfo.InvariantCulture); + + ReadTuple(reader, tuple); + int width = int.Parse(tuple[0], CultureInfo.InvariantCulture); + int height = int.Parse(tuple[1], CultureInfo.InvariantCulture); + + region.u = x / (float)page.width; + region.v = y / (float)page.height; + if (region.rotate) + { + region.u2 = (x + height) / (float)page.width; + region.v2 = (y + width) / (float)page.height; + } + else + { + region.u2 = (x + width) / (float)page.width; + region.v2 = (y + height) / (float)page.height; + } + region.x = x; + region.y = y; + region.width = Math.Abs(width); + region.height = Math.Abs(height); + + if (ReadTuple(reader, tuple) == 4) + { // split is optional + region.splits = new[] {int.Parse(tuple[0], CultureInfo.InvariantCulture), + int.Parse(tuple[1], CultureInfo.InvariantCulture), + int.Parse(tuple[2], CultureInfo.InvariantCulture), + int.Parse(tuple[3], CultureInfo.InvariantCulture)}; + + if (ReadTuple(reader, tuple) == 4) + { // pad is optional, but only present with splits + region.pads = new[] {int.Parse(tuple[0], CultureInfo.InvariantCulture), + int.Parse(tuple[1], CultureInfo.InvariantCulture), + int.Parse(tuple[2], CultureInfo.InvariantCulture), + int.Parse(tuple[3], CultureInfo.InvariantCulture)}; + + ReadTuple(reader, tuple); + } + } + + region.originalWidth = int.Parse(tuple[0], CultureInfo.InvariantCulture); + region.originalHeight = int.Parse(tuple[1], CultureInfo.InvariantCulture); + + ReadTuple(reader, tuple); + region.offsetX = int.Parse(tuple[0], CultureInfo.InvariantCulture); + region.offsetY = int.Parse(tuple[1], CultureInfo.InvariantCulture); + + region.index = int.Parse(ReadValue(reader), CultureInfo.InvariantCulture); + + regions.Add(region); + } + } + } + + static string ReadValue(TextReader reader) + { + string line = reader.ReadLine(); + int colon = line.IndexOf(':'); + if (colon == -1) throw new Exception("Invalid line: " + line); + return line.Substring(colon + 1).Trim(); + } + + /// Returns the number of tuple values read (1, 2 or 4). + static int ReadTuple(TextReader reader, string[] tuple) + { + string line = reader.ReadLine(); + int colon = line.IndexOf(':'); + if (colon == -1) throw new Exception("Invalid line: " + line); + int i = 0, lastMatch = colon + 1; + for (; i < 3; i++) + { + int comma = line.IndexOf(',', lastMatch); + if (comma == -1) break; + tuple[i] = line.Substring(lastMatch, comma - lastMatch).Trim(); + lastMatch = comma + 1; + } + tuple[i] = line.Substring(lastMatch).Trim(); + return i + 1; + } + + public void FlipV() + { + for (int i = 0, n = regions.Count; i < n; i++) + { + AtlasRegion region = regions[i]; + region.v = 1 - region.v; + region.v2 = 1 - region.v2; + } + } + + /// 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. + /// The region, or null. + public AtlasRegion FindRegion(string name) + { + for (int i = 0, n = regions.Count; i < n; i++) + if (regions[i].name == name) return regions[i]; + return null; + } + + public void Dispose() + { + if (textureLoader == null) return; + for (int i = 0, n = pages.Count; i < n; i++) + textureLoader.Unload(pages[i].rendererObject); + } + } + + public enum Format + { + Alpha, + Intensity, + LuminanceAlpha, + RGB565, + RGBA4444, + RGB888, + RGBA8888 + } + + public enum TextureFilter + { + Nearest, + Linear, + MipMap, + MipMapNearestNearest, + MipMapLinearNearest, + MipMapNearestLinear, + MipMapLinearLinear + } + + public enum TextureWrap + { + MirroredRepeat, + ClampToEdge, + Repeat + } + + public class AtlasPage + { + public string name; + public Format format; + public TextureFilter minFilter; + public TextureFilter magFilter; + public TextureWrap uWrap; + public TextureWrap vWrap; + public object rendererObject; + public int width, height; + + public AtlasPage Clone() + { + return MemberwiseClone() as AtlasPage; + } + } + + public class AtlasRegion + { + public AtlasPage page; + public string name; + public int x, y, width, height; + public float u, v, u2, v2; + public float offsetX, offsetY; + public int originalWidth, originalHeight; + public int index; + public bool rotate; + public int degrees; + public int[] splits; + public int[] pads; + + public AtlasRegion Clone() + { + return MemberwiseClone() as AtlasRegion; + } + } + + public interface TextureLoader + { + void Load(AtlasPage page, string path); + void Unload(Object texture); + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Attachments/AtlasAttachmentLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Attachments/AtlasAttachmentLoader.cs index 7d8c3c0..2718d2f 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Attachments/AtlasAttachmentLoader.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Attachments/AtlasAttachmentLoader.cs @@ -29,81 +29,92 @@ using System; -namespace Spine3_8_95 { +namespace Spine3_8_95 +{ - /// - /// An AttachmentLoader that configures attachments using texture regions from an Atlas. - /// See Loading Skeleton Data in the Spine Runtimes Guide. - /// - public class AtlasAttachmentLoader : AttachmentLoader { - private Atlas[] atlasArray; + /// + /// An AttachmentLoader that configures attachments using texture regions from an Atlas. + /// See Loading Skeleton Data in the Spine Runtimes Guide. + /// + public class AtlasAttachmentLoader : AttachmentLoader + { + private Atlas[] atlasArray; - public AtlasAttachmentLoader (params Atlas[] atlasArray) { - if (atlasArray == null) throw new ArgumentNullException("atlas array cannot be null."); - this.atlasArray = atlasArray; - } + public AtlasAttachmentLoader(params Atlas[] atlasArray) + { + if (atlasArray == null) throw new ArgumentNullException("atlas array cannot be null."); + this.atlasArray = atlasArray; + } - public RegionAttachment NewRegionAttachment (Skin skin, string name, string path) { - AtlasRegion region = FindRegion(path); - if (region == null) throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name)); - RegionAttachment attachment = new RegionAttachment(name); - attachment.RendererObject = region; - attachment.SetUVs(region.u, region.v, region.u2, region.v2, region.rotate); - attachment.regionOffsetX = region.offsetX; - attachment.regionOffsetY = region.offsetY; - attachment.regionWidth = region.width; - attachment.regionHeight = region.height; - attachment.regionOriginalWidth = region.originalWidth; - attachment.regionOriginalHeight = region.originalHeight; - return attachment; - } + public RegionAttachment NewRegionAttachment(Skin skin, string name, string path) + { + AtlasRegion region = FindRegion(path); + if (region == null) throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name)); + RegionAttachment attachment = new RegionAttachment(name); + attachment.RendererObject = region; + attachment.SetUVs(region.u, region.v, region.u2, region.v2, region.rotate); + attachment.regionOffsetX = region.offsetX; + attachment.regionOffsetY = region.offsetY; + attachment.regionWidth = region.width; + attachment.regionHeight = region.height; + attachment.regionOriginalWidth = region.originalWidth; + attachment.regionOriginalHeight = region.originalHeight; + return attachment; + } - public MeshAttachment NewMeshAttachment (Skin skin, string name, string path) { - AtlasRegion region = FindRegion(path); - if (region == null) throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name)); - MeshAttachment attachment = new MeshAttachment(name); - attachment.RendererObject = region; - attachment.RegionU = region.u; - attachment.RegionV = region.v; - attachment.RegionU2 = region.u2; - attachment.RegionV2 = region.v2; - attachment.RegionRotate = region.rotate; - attachment.RegionDegrees = region.degrees; - attachment.regionOffsetX = region.offsetX; - attachment.regionOffsetY = region.offsetY; - attachment.regionWidth = region.width; - attachment.regionHeight = region.height; - attachment.regionOriginalWidth = region.originalWidth; - attachment.regionOriginalHeight = region.originalHeight; - return attachment; - } + public MeshAttachment NewMeshAttachment(Skin skin, string name, string path) + { + AtlasRegion region = FindRegion(path); + if (region == null) throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name)); + MeshAttachment attachment = new MeshAttachment(name); + attachment.RendererObject = region; + attachment.RegionU = region.u; + attachment.RegionV = region.v; + attachment.RegionU2 = region.u2; + attachment.RegionV2 = region.v2; + attachment.RegionRotate = region.rotate; + attachment.RegionDegrees = region.degrees; + attachment.regionOffsetX = region.offsetX; + attachment.regionOffsetY = region.offsetY; + attachment.regionWidth = region.width; + attachment.regionHeight = region.height; + attachment.regionOriginalWidth = region.originalWidth; + attachment.regionOriginalHeight = region.originalHeight; + return attachment; + } - public BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, string name) { - return new BoundingBoxAttachment(name); - } + public BoundingBoxAttachment NewBoundingBoxAttachment(Skin skin, string name) + { + return new BoundingBoxAttachment(name); + } - public PathAttachment NewPathAttachment (Skin skin, string name) { - return new PathAttachment(name); - } + public PathAttachment NewPathAttachment(Skin skin, string name) + { + return new PathAttachment(name); + } - public PointAttachment NewPointAttachment (Skin skin, string name) { - return new PointAttachment(name); - } + public PointAttachment NewPointAttachment(Skin skin, string name) + { + return new PointAttachment(name); + } - public ClippingAttachment NewClippingAttachment(Skin skin, string name) { - return new ClippingAttachment(name); - } + public ClippingAttachment NewClippingAttachment(Skin skin, string name) + { + return new ClippingAttachment(name); + } - public AtlasRegion FindRegion (string name) { - AtlasRegion region; + public AtlasRegion FindRegion(string name) + { + AtlasRegion region; - for (int i = 0; i < atlasArray.Length; i++) { - region = atlasArray[i].FindRegion(name); - if (region != null) - return region; - } + for (int i = 0; i < atlasArray.Length; i++) + { + region = atlasArray[i].FindRegion(name); + if (region != null) + return region; + } - return null; - } - } + return null; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Attachments/Attachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Attachments/Attachment.cs index 0d833c4..1d46756 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Attachments/Attachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Attachments/Attachment.cs @@ -29,24 +29,29 @@ using System; -namespace Spine3_8_95 { - abstract public class Attachment { - public string Name { get; private set; } +namespace Spine3_8_95 +{ + abstract public class Attachment + { + public string Name { get; private set; } - protected Attachment (string name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null"); - Name = name; - } + protected Attachment(string name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null"); + Name = name; + } - override public string ToString () { - return Name; - } + override public string ToString() + { + return Name; + } - ///Returns a copy of the attachment. - public abstract Attachment Copy (); - } + ///Returns a copy of the attachment. + public abstract Attachment Copy(); + } - public interface IHasRendererObject { - object RendererObject { get; set; } - } + public interface IHasRendererObject + { + object RendererObject { get; set; } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Attachments/AttachmentLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Attachments/AttachmentLoader.cs index 0bdd538..1ff8e4e 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Attachments/AttachmentLoader.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Attachments/AttachmentLoader.cs @@ -27,22 +27,24 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine3_8_95 { - public interface AttachmentLoader { - /// May be null to not load any attachment. - RegionAttachment NewRegionAttachment (Skin skin, string name, string path); +namespace Spine3_8_95 +{ + public interface AttachmentLoader + { + /// May be null to not load any attachment. + RegionAttachment NewRegionAttachment(Skin skin, string name, string path); - /// May be null to not load any attachment. - MeshAttachment NewMeshAttachment (Skin skin, string name, string path); + /// May be null to not load any attachment. + MeshAttachment NewMeshAttachment(Skin skin, string name, string path); - /// May be null to not load any attachment. - BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, string name); + /// May be null to not load any attachment. + BoundingBoxAttachment NewBoundingBoxAttachment(Skin skin, string name); - /// May be null to not load any attachment - PathAttachment NewPathAttachment (Skin skin, string name); + /// May be null to not load any attachment + PathAttachment NewPathAttachment(Skin skin, string name); - PointAttachment NewPointAttachment (Skin skin, string name); + PointAttachment NewPointAttachment(Skin skin, string name); - ClippingAttachment NewClippingAttachment (Skin skin, string name); - } + ClippingAttachment NewClippingAttachment(Skin skin, string name); + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Attachments/AttachmentType.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Attachments/AttachmentType.cs index 2551941..60fedd3 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Attachments/AttachmentType.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Attachments/AttachmentType.cs @@ -27,8 +27,10 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine3_8_95 { - public enum AttachmentType { - Region, Boundingbox, Mesh, Linkedmesh, Path, Point, Clipping - } +namespace Spine3_8_95 +{ + public enum AttachmentType + { + Region, Boundingbox, Mesh, Linkedmesh, Path, Point, Clipping + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Attachments/BoundingBoxAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Attachments/BoundingBoxAttachment.cs index 4d2fb0c..2edc1a8 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Attachments/BoundingBoxAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Attachments/BoundingBoxAttachment.cs @@ -27,19 +27,21 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; +namespace Spine3_8_95 +{ + /// Attachment that has a polygon for bounds checking. + public class BoundingBoxAttachment : VertexAttachment + { + public BoundingBoxAttachment(string name) + : base(name) + { + } -namespace Spine3_8_95 { - /// Attachment that has a polygon for bounds checking. - public class BoundingBoxAttachment : VertexAttachment { - public BoundingBoxAttachment (string name) - : base(name) { - } - - public override Attachment Copy () { - BoundingBoxAttachment copy = new BoundingBoxAttachment(this.Name); - CopyTo(copy); - return copy; - } - } + public override Attachment Copy() + { + BoundingBoxAttachment copy = new BoundingBoxAttachment(this.Name); + CopyTo(copy); + return copy; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Attachments/ClippingAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Attachments/ClippingAttachment.cs index fd2d0b0..d955f66 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Attachments/ClippingAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Attachments/ClippingAttachment.cs @@ -27,22 +27,24 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; +namespace Spine3_8_95 +{ + public class ClippingAttachment : VertexAttachment + { + internal SlotData endSlot; -namespace Spine3_8_95 { - public class ClippingAttachment : VertexAttachment { - internal SlotData endSlot; + public SlotData EndSlot { get { return endSlot; } set { endSlot = value; } } - public SlotData EndSlot { get { return endSlot; } set { endSlot = value; } } + public ClippingAttachment(string name) : base(name) + { + } - public ClippingAttachment(string name) : base(name) { - } - - public override Attachment Copy () { - ClippingAttachment copy = new ClippingAttachment(this.Name); - CopyTo(copy); - copy.endSlot = endSlot; - return copy; - } - } + public override Attachment Copy() + { + ClippingAttachment copy = new ClippingAttachment(this.Name); + CopyTo(copy); + copy.endSlot = endSlot; + return copy; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Attachments/MeshAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Attachments/MeshAttachment.cs index 09ff43a..6c0560d 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Attachments/MeshAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Attachments/MeshAttachment.cs @@ -29,196 +29,217 @@ using System; -namespace Spine3_8_95 { - /// Attachment that displays a texture region using a mesh. - public class MeshAttachment : VertexAttachment, IHasRendererObject { - internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; - private MeshAttachment parentMesh; - internal float[] uvs, regionUVs; - internal int[] triangles; - internal float r = 1, g = 1, b = 1, a = 1; - internal int hulllength; - - public int HullLength { get { return hulllength; } set { hulllength = value; } } - public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } } - /// The UV pair for each vertex, normalized within the entire texture. - public float[] UVs { get { return uvs; } set { uvs = value; } } - public int[] Triangles { get { return triangles; } set { triangles = value; } } - - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } - - public string Path { get; set; } - public object RendererObject { get; set; } - public float RegionU { get; set; } - public float RegionV { get; set; } - public float RegionU2 { get; set; } - public float RegionV2 { get; set; } - public bool RegionRotate { get; set; } - public int RegionDegrees { get; set; } - public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } - public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. - public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } - public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. - public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } - public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. - - public MeshAttachment ParentMesh { - get { return parentMesh; } - set { - parentMesh = value; - if (value != null) { - bones = value.bones; - vertices = value.vertices; - worldVerticesLength = value.worldVerticesLength; - regionUVs = value.regionUVs; - triangles = value.triangles; - HullLength = value.HullLength; - Edges = value.Edges; - Width = value.Width; - Height = value.Height; - } - } - } - - // Nonessential. - public int[] Edges { get; set; } - public float Width { get; set; } - public float Height { get; set; } - - public MeshAttachment (string name) - : base(name) { - } - - public void UpdateUVs () { - float[] regionUVs = this.regionUVs; - if (this.uvs == null || this.uvs.Length != regionUVs.Length) this.uvs = new float[regionUVs.Length]; - float[] uvs = this.uvs; - float u = RegionU, v = RegionV, width = 0, height = 0; - - if (RegionDegrees == 90) { - float textureHeight = this.regionWidth / (RegionV2 - RegionV); - float textureWidth = this.regionHeight / (RegionU2 - RegionU); - u -= (RegionOriginalHeight - RegionOffsetY - RegionHeight) / textureWidth; - v -= (RegionOriginalWidth - RegionOffsetX - RegionWidth) / textureHeight; - width = RegionOriginalHeight / textureWidth; - height = RegionOriginalWidth / textureHeight; - - for (int i = 0, n = uvs.Length; i < n; i += 2) { - uvs[i] = u + regionUVs[i + 1] * width; - uvs[i + 1] = v + (1 - regionUVs[i]) * height; - } - } else if (RegionDegrees == 180) { - float textureWidth = this.regionWidth / (RegionU2 - RegionU); - float textureHeight = this.regionHeight / (RegionV2 - RegionV); - u -= (RegionOriginalWidth - RegionOffsetX - RegionWidth) / textureWidth; - v -= RegionOffsetY / textureHeight; - width = RegionOriginalWidth / textureWidth; - height = RegionOriginalHeight / textureHeight; - - for (int i = 0, n = uvs.Length; i < n; i += 2) { - uvs[i] = u + (1 - regionUVs[i]) * width; - uvs[i + 1] = v + (1 - regionUVs[i + 1]) * height; - } - } else if (RegionDegrees == 270) { - float textureWidth = this.regionWidth / (RegionU2 - RegionU); - float textureHeight = this.regionHeight / (RegionV2 - RegionV); - u -= RegionOffsetY / textureWidth; - v -= RegionOffsetX / textureHeight; - width = RegionOriginalHeight / textureWidth; - height = RegionOriginalWidth / textureHeight; - - for (int i = 0, n = uvs.Length; iReturns a new mesh with this mesh set as the . - public MeshAttachment NewLinkedMesh () { - MeshAttachment mesh = new MeshAttachment(Name); - mesh.RendererObject = RendererObject; - mesh.regionOffsetX = regionOffsetX; - mesh.regionOffsetY = regionOffsetY; - mesh.regionWidth = regionWidth; - mesh.regionHeight = regionHeight; - mesh.regionOriginalWidth = regionOriginalWidth; - mesh.regionOriginalHeight = regionOriginalHeight; - mesh.RegionDegrees = RegionDegrees; - mesh.RegionRotate = RegionRotate; - mesh.RegionU = RegionU; - mesh.RegionV = RegionV; - mesh.RegionU2 = RegionU2; - mesh.RegionV2 = RegionV2; - - mesh.Path = Path; - mesh.r = r; - mesh.g = g; - mesh.b = b; - mesh.a = a; - - mesh.deformAttachment = deformAttachment; - mesh.ParentMesh = parentMesh != null ? parentMesh : this; - mesh.UpdateUVs(); - return mesh; - } - } +namespace Spine3_8_95 +{ + /// Attachment that displays a texture region using a mesh. + public class MeshAttachment : VertexAttachment, IHasRendererObject + { + internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; + private MeshAttachment parentMesh; + internal float[] uvs, regionUVs; + internal int[] triangles; + internal float r = 1, g = 1, b = 1, a = 1; + internal int hulllength; + + public int HullLength { get { return hulllength; } set { hulllength = value; } } + public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } } + /// The UV pair for each vertex, normalized within the entire texture. + public float[] UVs { get { return uvs; } set { uvs = value; } } + public int[] Triangles { get { return triangles; } set { triangles = value; } } + + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } + + public string Path { get; set; } + public object RendererObject { get; set; } + public float RegionU { get; set; } + public float RegionV { get; set; } + public float RegionU2 { get; set; } + public float RegionV2 { get; set; } + public bool RegionRotate { get; set; } + public int RegionDegrees { get; set; } + public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } + public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. + public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } + public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. + public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } + public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. + + public MeshAttachment ParentMesh + { + get { return parentMesh; } + set + { + parentMesh = value; + if (value != null) + { + bones = value.bones; + vertices = value.vertices; + worldVerticesLength = value.worldVerticesLength; + regionUVs = value.regionUVs; + triangles = value.triangles; + HullLength = value.HullLength; + Edges = value.Edges; + Width = value.Width; + Height = value.Height; + } + } + } + + // Nonessential. + public int[] Edges { get; set; } + public float Width { get; set; } + public float Height { get; set; } + + public MeshAttachment(string name) + : base(name) + { + } + + public void UpdateUVs() + { + float[] regionUVs = this.regionUVs; + if (this.uvs == null || this.uvs.Length != regionUVs.Length) this.uvs = new float[regionUVs.Length]; + float[] uvs = this.uvs; + float u = RegionU, v = RegionV, width = 0, height = 0; + + if (RegionDegrees == 90) + { + float textureHeight = this.regionWidth / (RegionV2 - RegionV); + float textureWidth = this.regionHeight / (RegionU2 - RegionU); + u -= (RegionOriginalHeight - RegionOffsetY - RegionHeight) / textureWidth; + v -= (RegionOriginalWidth - RegionOffsetX - RegionWidth) / textureHeight; + width = RegionOriginalHeight / textureWidth; + height = RegionOriginalWidth / textureHeight; + + for (int i = 0, n = uvs.Length; i < n; i += 2) + { + uvs[i] = u + regionUVs[i + 1] * width; + uvs[i + 1] = v + (1 - regionUVs[i]) * height; + } + } + else if (RegionDegrees == 180) + { + float textureWidth = this.regionWidth / (RegionU2 - RegionU); + float textureHeight = this.regionHeight / (RegionV2 - RegionV); + u -= (RegionOriginalWidth - RegionOffsetX - RegionWidth) / textureWidth; + v -= RegionOffsetY / textureHeight; + width = RegionOriginalWidth / textureWidth; + height = RegionOriginalHeight / textureHeight; + + for (int i = 0, n = uvs.Length; i < n; i += 2) + { + uvs[i] = u + (1 - regionUVs[i]) * width; + uvs[i + 1] = v + (1 - regionUVs[i + 1]) * height; + } + } + else if (RegionDegrees == 270) + { + float textureWidth = this.regionWidth / (RegionU2 - RegionU); + float textureHeight = this.regionHeight / (RegionV2 - RegionV); + u -= RegionOffsetY / textureWidth; + v -= RegionOffsetX / textureHeight; + width = RegionOriginalHeight / textureWidth; + height = RegionOriginalWidth / textureHeight; + + for (int i = 0, n = uvs.Length; i < n; i += 2) + { + uvs[i] = u + (1 - regionUVs[i + 1]) * width; + uvs[i + 1] = v + regionUVs[i] * height; + } + } + else + { + float textureWidth = this.regionWidth / (RegionU2 - RegionU); + float textureHeight = this.regionHeight / (RegionV2 - RegionV); + u -= RegionOffsetX / textureWidth; + v -= (RegionOriginalHeight - RegionOffsetY - RegionHeight) / textureHeight; + width = RegionOriginalWidth / textureWidth; + height = RegionOriginalHeight / textureHeight; + + for (int i = 0, n = uvs.Length; i < n; i += 2) + { + uvs[i] = u + regionUVs[i] * width; + uvs[i + 1] = v + regionUVs[i + 1] * height; + } + } + } + + public override Attachment Copy() + { + if (parentMesh != null) return NewLinkedMesh(); + + MeshAttachment copy = new MeshAttachment(this.Name); + copy.RendererObject = RendererObject; + copy.regionOffsetX = regionOffsetX; + copy.regionOffsetY = regionOffsetY; + copy.regionWidth = regionWidth; + copy.regionHeight = regionHeight; + copy.regionOriginalWidth = regionOriginalWidth; + copy.regionOriginalHeight = regionOriginalHeight; + copy.RegionRotate = RegionRotate; + copy.RegionDegrees = RegionDegrees; + copy.RegionU = RegionU; + copy.RegionV = RegionV; + copy.RegionU2 = RegionU2; + copy.RegionV2 = RegionV2; + + copy.Path = Path; + copy.r = r; + copy.g = g; + copy.b = b; + copy.a = a; + + CopyTo(copy); + copy.regionUVs = new float[regionUVs.Length]; + Array.Copy(regionUVs, 0, copy.regionUVs, 0, regionUVs.Length); + copy.uvs = new float[uvs.Length]; + Array.Copy(uvs, 0, copy.uvs, 0, uvs.Length); + copy.triangles = new int[triangles.Length]; + Array.Copy(triangles, 0, copy.triangles, 0, triangles.Length); + copy.HullLength = HullLength; + + // Nonessential. + if (Edges != null) + { + copy.Edges = new int[Edges.Length]; + Array.Copy(Edges, 0, copy.Edges, 0, Edges.Length); + } + copy.Width = Width; + copy.Height = Height; + return copy; + } + + ///Returns a new mesh with this mesh set as the . + public MeshAttachment NewLinkedMesh() + { + MeshAttachment mesh = new MeshAttachment(Name); + mesh.RendererObject = RendererObject; + mesh.regionOffsetX = regionOffsetX; + mesh.regionOffsetY = regionOffsetY; + mesh.regionWidth = regionWidth; + mesh.regionHeight = regionHeight; + mesh.regionOriginalWidth = regionOriginalWidth; + mesh.regionOriginalHeight = regionOriginalHeight; + mesh.RegionDegrees = RegionDegrees; + mesh.RegionRotate = RegionRotate; + mesh.RegionU = RegionU; + mesh.RegionV = RegionV; + mesh.RegionU2 = RegionU2; + mesh.RegionV2 = RegionV2; + + mesh.Path = Path; + mesh.r = r; + mesh.g = g; + mesh.b = b; + mesh.a = a; + + mesh.deformAttachment = deformAttachment; + mesh.ParentMesh = parentMesh != null ? parentMesh : this; + mesh.UpdateUVs(); + return mesh; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Attachments/PathAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Attachments/PathAttachment.cs index d81ebf6..3d21be2 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Attachments/PathAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Attachments/PathAttachment.cs @@ -28,30 +28,33 @@ *****************************************************************************/ using System; -using System.Collections.Generic; -namespace Spine3_8_95 { - public class PathAttachment : VertexAttachment { - internal float[] lengths; - internal bool closed, constantSpeed; +namespace Spine3_8_95 +{ + public class PathAttachment : VertexAttachment + { + internal float[] lengths; + internal bool closed, constantSpeed; - /// The length in the setup pose from the start of the path to the end of each curve. - public float[] Lengths { get { return lengths; } set { lengths = value; } } - public bool Closed { get { return closed; } set { closed = value; } } - public bool ConstantSpeed { get { return constantSpeed; } set { constantSpeed = value; } } + /// The length in the setup pose from the start of the path to the end of each curve. + public float[] Lengths { get { return lengths; } set { lengths = value; } } + public bool Closed { get { return closed; } set { closed = value; } } + public bool ConstantSpeed { get { return constantSpeed; } set { constantSpeed = value; } } - public PathAttachment (String name) - : base(name) { - } + public PathAttachment(String name) + : base(name) + { + } - public override Attachment Copy () { - PathAttachment copy = new PathAttachment(this.Name); - CopyTo(copy); - copy.lengths = new float[lengths.Length]; - Array.Copy(lengths, 0, copy.lengths, 0, lengths.Length); - copy.closed = closed; - copy.constantSpeed = constantSpeed; - return copy; - } - } + public override Attachment Copy() + { + PathAttachment copy = new PathAttachment(this.Name); + CopyTo(copy); + copy.lengths = new float[lengths.Length]; + Array.Copy(lengths, 0, copy.lengths, 0, lengths.Length); + copy.closed = closed; + copy.constantSpeed = constantSpeed; + return copy; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Attachments/PointAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Attachments/PointAttachment.cs index d55a7d7..836a0d4 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Attachments/PointAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Attachments/PointAttachment.cs @@ -27,41 +27,47 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine3_8_95 { - /// - /// 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 Point Attachments in the Spine User Guide. - ///

- public class PointAttachment : Attachment { - internal float x, y, rotation; - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public float Rotation { get { return rotation; } set { rotation = value; } } +namespace Spine3_8_95 +{ + /// + /// 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 Point Attachments in the Spine User Guide. + ///

+ public class PointAttachment : Attachment + { + internal float x, y, rotation; + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public float Rotation { get { return rotation; } set { rotation = value; } } - public PointAttachment (string name) - : base(name) { - } + public PointAttachment(string name) + : base(name) + { + } - public void ComputeWorldPosition (Bone bone, out float ox, out float oy) { - bone.LocalToWorld(this.x, this.y, out ox, out oy); - } + public void ComputeWorldPosition(Bone bone, out float ox, out float oy) + { + bone.LocalToWorld(this.x, this.y, out ox, out oy); + } - public float ComputeWorldRotation (Bone bone) { - float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation); - float ix = cos * bone.a + sin * bone.b; - float iy = cos * bone.c + sin * bone.d; - return MathUtils.Atan2(iy, ix) * MathUtils.RadDeg; - } + public float ComputeWorldRotation(Bone bone) + { + float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation); + float ix = cos * bone.a + sin * bone.b; + float iy = cos * bone.c + sin * bone.d; + return MathUtils.Atan2(iy, ix) * MathUtils.RadDeg; + } - public override Attachment Copy () { - PointAttachment copy = new PointAttachment(this.Name); - copy.x = x; - copy.y = y; - copy.rotation = rotation; - return copy; - } - } + public override Attachment Copy() + { + PointAttachment copy = new PointAttachment(this.Name); + copy.x = x; + copy.y = y; + copy.rotation = rotation; + return copy; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Attachments/RegionAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Attachments/RegionAttachment.cs index ec9c7d7..2a02e38 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Attachments/RegionAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Attachments/RegionAttachment.cs @@ -29,180 +29,191 @@ using System; -namespace Spine3_8_95 { - /// Attachment that displays a texture region. - public class RegionAttachment : Attachment, IHasRendererObject { - public const int BLX = 0; - public const int BLY = 1; - public const int ULX = 2; - public const int ULY = 3; - public const int URX = 4; - public const int URY = 5; - public const int BRX = 6; - public const int BRY = 7; - - internal float x, y, rotation, scaleX = 1, scaleY = 1, width, height; - internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; - internal float[] offset = new float[8], uvs = new float[8]; - internal float r = 1, g = 1, b = 1, a = 1; - - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public float Rotation { get { return rotation; } set { rotation = value; } } - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - public float ScaleY { get { return scaleY; } set { scaleY = value; } } - public float Width { get { return width; } set { width = value; } } - public float Height { get { return height; } set { height = value; } } - - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } - - public string Path { get; set; } - public object RendererObject { get; set; } - public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } - public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. - public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } - public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. - public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } - public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. - - public float[] Offset { get { return offset; } } - public float[] UVs { get { return uvs; } } - - public RegionAttachment (string name) - : base(name) { - } - - public void UpdateOffset () { - float width = this.width; - float height = this.height; - float localX2 = width * 0.5f; - float localY2 = height * 0.5f; - float localX = -localX2; - float localY = -localY2; - if (regionOriginalWidth != 0) { // if (region != null) - localX += regionOffsetX / regionOriginalWidth * width; - localY += regionOffsetY / regionOriginalHeight * height; - localX2 -= (regionOriginalWidth - regionOffsetX - regionWidth) / regionOriginalWidth * width; - localY2 -= (regionOriginalHeight - regionOffsetY - regionHeight) / regionOriginalHeight * height; - } - float scaleX = this.scaleX; - float scaleY = this.scaleY; - localX *= scaleX; - localY *= scaleY; - localX2 *= scaleX; - localY2 *= scaleY; - float rotation = this.rotation; - float cos = MathUtils.CosDeg(rotation); - float sin = MathUtils.SinDeg(rotation); - float x = this.x; - float y = this.y; - 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; - float[] offset = this.offset; - offset[BLX] = localXCos - localYSin; - offset[BLY] = localYCos + localXSin; - offset[ULX] = localXCos - localY2Sin; - offset[ULY] = localY2Cos + localXSin; - offset[URX] = localX2Cos - localY2Sin; - offset[URY] = localY2Cos + localX2Sin; - offset[BRX] = localX2Cos - localYSin; - offset[BRY] = localYCos + localX2Sin; - } - - public void SetUVs (float u, float v, float u2, float v2, bool rotate) { - float[] uvs = this.uvs; - // UV values differ from RegionAttachment.java - if (rotate) { - uvs[URX] = u; - uvs[URY] = v2; - uvs[BRX] = u; - uvs[BRY] = v; - uvs[BLX] = u2; - uvs[BLY] = v; - uvs[ULX] = u2; - uvs[ULY] = v2; - } else { - uvs[ULX] = u; - uvs[ULY] = v2; - uvs[URX] = u; - uvs[URY] = v; - uvs[BRX] = u2; - uvs[BRY] = v; - uvs[BLX] = u2; - uvs[BLY] = v2; - } - } - - /// Transforms the attachment's four vertices to world coordinates. - /// The parent bone. - /// The output world vertices. Must have a length greater than or equal to offset + 8. - /// The worldVertices index to begin writing values. - /// The number of worldVertices entries between the value pairs written. - public void ComputeWorldVertices (Bone bone, float[] worldVertices, int offset, int stride = 2) { - float[] vertexOffset = this.offset; - float bwx = bone.worldX, bwy = bone.worldY; - float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - float offsetX, offsetY; - - // Vertex order is different from RegionAttachment.java - offsetX = vertexOffset[BRX]; // 0 - offsetY = vertexOffset[BRY]; // 1 - worldVertices[offset] = offsetX * a + offsetY * b + bwx; // bl - worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; - offset += stride; - - offsetX = vertexOffset[BLX]; // 2 - offsetY = vertexOffset[BLY]; // 3 - worldVertices[offset] = offsetX * a + offsetY * b + bwx; // ul - worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; - offset += stride; - - offsetX = vertexOffset[ULX]; // 4 - offsetY = vertexOffset[ULY]; // 5 - worldVertices[offset] = offsetX * a + offsetY * b + bwx; // ur - worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; - offset += stride; - - offsetX = vertexOffset[URX]; // 6 - offsetY = vertexOffset[URY]; // 7 - worldVertices[offset] = offsetX * a + offsetY * b + bwx; // br - worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; - //offset += stride; - } - - public override Attachment Copy () { - RegionAttachment copy = new RegionAttachment(this.Name); - copy.RendererObject = RendererObject; - copy.regionOffsetX = regionOffsetX; - copy.regionOffsetY = regionOffsetY; - copy.regionWidth = regionWidth; - copy.regionHeight = regionHeight; - copy.regionOriginalWidth = regionOriginalWidth; - copy.regionOriginalHeight = regionOriginalHeight; - copy.Path = Path; - copy.x = x; - copy.y = y; - copy.scaleX = scaleX; - copy.scaleY = scaleY; - copy.rotation = rotation; - copy.width = width; - copy.height = height; - Array.Copy(uvs, 0, copy.uvs, 0, 8); - Array.Copy(offset, 0, copy.offset, 0, 8); - copy.r = r; - copy.g = g; - copy.b = b; - copy.a = a; - return copy; - } - } +namespace Spine3_8_95 +{ + /// Attachment that displays a texture region. + public class RegionAttachment : Attachment, IHasRendererObject + { + public const int BLX = 0; + public const int BLY = 1; + public const int ULX = 2; + public const int ULY = 3; + public const int URX = 4; + public const int URY = 5; + public const int BRX = 6; + public const int BRY = 7; + + internal float x, y, rotation, scaleX = 1, scaleY = 1, width, height; + internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; + internal float[] offset = new float[8], uvs = new float[8]; + internal float r = 1, g = 1, b = 1, a = 1; + + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public float Rotation { get { return rotation; } set { rotation = value; } } + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + public float Width { get { return width; } set { width = value; } } + public float Height { get { return height; } set { height = value; } } + + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } + + public string Path { get; set; } + public object RendererObject { get; set; } + public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } + public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. + public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } + public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. + public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } + public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. + + public float[] Offset { get { return offset; } } + public float[] UVs { get { return uvs; } } + + public RegionAttachment(string name) + : base(name) + { + } + + public void UpdateOffset() + { + float width = this.width; + float height = this.height; + float localX2 = width * 0.5f; + float localY2 = height * 0.5f; + float localX = -localX2; + float localY = -localY2; + if (regionOriginalWidth != 0) + { // if (region != null) + localX += regionOffsetX / regionOriginalWidth * width; + localY += regionOffsetY / regionOriginalHeight * height; + localX2 -= (regionOriginalWidth - regionOffsetX - regionWidth) / regionOriginalWidth * width; + localY2 -= (regionOriginalHeight - regionOffsetY - regionHeight) / regionOriginalHeight * height; + } + float scaleX = this.scaleX; + float scaleY = this.scaleY; + localX *= scaleX; + localY *= scaleY; + localX2 *= scaleX; + localY2 *= scaleY; + float rotation = this.rotation; + float cos = MathUtils.CosDeg(rotation); + float sin = MathUtils.SinDeg(rotation); + float x = this.x; + float y = this.y; + 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; + float[] offset = this.offset; + offset[BLX] = localXCos - localYSin; + offset[BLY] = localYCos + localXSin; + offset[ULX] = localXCos - localY2Sin; + offset[ULY] = localY2Cos + localXSin; + offset[URX] = localX2Cos - localY2Sin; + offset[URY] = localY2Cos + localX2Sin; + offset[BRX] = localX2Cos - localYSin; + offset[BRY] = localYCos + localX2Sin; + } + + public void SetUVs(float u, float v, float u2, float v2, bool rotate) + { + float[] uvs = this.uvs; + // UV values differ from RegionAttachment.java + if (rotate) + { + uvs[URX] = u; + uvs[URY] = v2; + uvs[BRX] = u; + uvs[BRY] = v; + uvs[BLX] = u2; + uvs[BLY] = v; + uvs[ULX] = u2; + uvs[ULY] = v2; + } + else + { + uvs[ULX] = u; + uvs[ULY] = v2; + uvs[URX] = u; + uvs[URY] = v; + uvs[BRX] = u2; + uvs[BRY] = v; + uvs[BLX] = u2; + uvs[BLY] = v2; + } + } + + /// Transforms the attachment's four vertices to world coordinates. + /// The parent bone. + /// The output world vertices. Must have a length greater than or equal to offset + 8. + /// The worldVertices index to begin writing values. + /// The number of worldVertices entries between the value pairs written. + public void ComputeWorldVertices(Bone bone, float[] worldVertices, int offset, int stride = 2) + { + float[] vertexOffset = this.offset; + float bwx = bone.worldX, bwy = bone.worldY; + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + float offsetX, offsetY; + + // Vertex order is different from RegionAttachment.java + offsetX = vertexOffset[BRX]; // 0 + offsetY = vertexOffset[BRY]; // 1 + worldVertices[offset] = offsetX * a + offsetY * b + bwx; // bl + worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; + offset += stride; + + offsetX = vertexOffset[BLX]; // 2 + offsetY = vertexOffset[BLY]; // 3 + worldVertices[offset] = offsetX * a + offsetY * b + bwx; // ul + worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; + offset += stride; + + offsetX = vertexOffset[ULX]; // 4 + offsetY = vertexOffset[ULY]; // 5 + worldVertices[offset] = offsetX * a + offsetY * b + bwx; // ur + worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; + offset += stride; + + offsetX = vertexOffset[URX]; // 6 + offsetY = vertexOffset[URY]; // 7 + worldVertices[offset] = offsetX * a + offsetY * b + bwx; // br + worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; + //offset += stride; + } + + public override Attachment Copy() + { + RegionAttachment copy = new RegionAttachment(this.Name); + copy.RendererObject = RendererObject; + copy.regionOffsetX = regionOffsetX; + copy.regionOffsetY = regionOffsetY; + copy.regionWidth = regionWidth; + copy.regionHeight = regionHeight; + copy.regionOriginalWidth = regionOriginalWidth; + copy.regionOriginalHeight = regionOriginalHeight; + copy.Path = Path; + copy.x = x; + copy.y = y; + copy.scaleX = scaleX; + copy.scaleY = scaleY; + copy.rotation = rotation; + copy.width = width; + copy.height = height; + Array.Copy(uvs, 0, copy.uvs, 0, 8); + Array.Copy(offset, 0, copy.offset, 0, 8); + copy.r = r; + copy.g = g; + copy.b = b; + copy.a = a; + return copy; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Attachments/VertexAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Attachments/VertexAttachment.cs index e009302..c466a3e 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Attachments/VertexAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Attachments/VertexAttachment.cs @@ -29,128 +29,147 @@ using System; -namespace Spine3_8_95 { - /// >An attachment with vertices that are transformed by one or more bones and can be deformed by a slot's - /// . - public abstract class VertexAttachment : Attachment { - static int nextID = 0; - static readonly Object nextIdLock = new Object(); +namespace Spine3_8_95 +{ + /// >An attachment with vertices that are transformed by one or more bones and can be deformed by a slot's + /// . + public abstract class VertexAttachment : Attachment + { + static int nextID = 0; + static readonly Object nextIdLock = new Object(); - internal readonly int id; - internal int[] bones; - internal float[] vertices; - internal int worldVerticesLength; - internal VertexAttachment deformAttachment; + internal readonly int id; + internal int[] bones; + internal float[] vertices; + internal int worldVerticesLength; + internal VertexAttachment deformAttachment; - /// Gets a unique ID for this attachment. - public int Id { get { return id; } } - public int[] Bones { get { return bones; } set { bones = value; } } - public float[] Vertices { get { return vertices; } set { vertices = value; } } - public int WorldVerticesLength { get { return worldVerticesLength; } set { worldVerticesLength = value; } } - ///Deform keys for the deform attachment are also applied to this attachment. - /// May be null if no deform keys should be applied. - public VertexAttachment DeformAttachment { get { return deformAttachment; } set { deformAttachment = value; } } + /// Gets a unique ID for this attachment. + public int Id { get { return id; } } + public int[] Bones { get { return bones; } set { bones = value; } } + public float[] Vertices { get { return vertices; } set { vertices = value; } } + public int WorldVerticesLength { get { return worldVerticesLength; } set { worldVerticesLength = value; } } + ///Deform keys for the deform attachment are also applied to this attachment. + /// May be null if no deform keys should be applied. + public VertexAttachment DeformAttachment { get { return deformAttachment; } set { deformAttachment = value; } } - public VertexAttachment (string name) - : base(name) { + public VertexAttachment(string name) + : base(name) + { - deformAttachment = this; - lock (VertexAttachment.nextIdLock) { - id = (VertexAttachment.nextID++ & 65535) << 11; - } - } + deformAttachment = this; + lock (VertexAttachment.nextIdLock) + { + id = (VertexAttachment.nextID++ & 65535) << 11; + } + } - public void ComputeWorldVertices (Slot slot, float[] worldVertices) { - ComputeWorldVertices(slot, 0, worldVerticesLength, worldVertices, 0); - } + public void ComputeWorldVertices(Slot slot, float[] worldVertices) + { + ComputeWorldVertices(slot, 0, worldVerticesLength, worldVertices, 0); + } - /// - /// Transforms the attachment's local to world coordinates. If the slot's is - /// not empty, it is used to deform the vertices. - /// - /// See World transforms in the Spine - /// Runtimes Guide. - /// - /// The index of the first value to transform. Each vertex has 2 values, x and y. - /// The number of world vertex values to output. Must be less than or equal to - start. - /// The output world vertices. Must have a length greater than or equal to + . - /// The index to begin writing values. - /// The number of entries between the value pairs written. - public void ComputeWorldVertices (Slot slot, int start, int count, float[] worldVertices, int offset, int stride = 2) { - count = offset + (count >> 1) * stride; - Skeleton skeleton = slot.bone.skeleton; - var deformArray = slot.deform; - float[] vertices = this.vertices; - int[] bones = this.bones; - if (bones == null) { - if (deformArray.Count > 0) vertices = deformArray.Items; - Bone bone = slot.bone; - float x = bone.worldX, y = bone.worldY; - float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - for (int vv = start, w = offset; w < count; vv += 2, w += stride) { - float vx = vertices[vv], 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 (int i = 0; i < start; i += 2) { - int n = bones[v]; - v += n + 1; - skip += n; - } - var skeletonBones = skeleton.bones.Items; - if (deformArray.Count == 0) { - for (int w = offset, b = skip * 3; w < count; w += stride) { - float wx = 0, wy = 0; - int n = bones[v++]; - n += v; - for (; v < n; v++, b += 3) { - Bone bone = skeletonBones[bones[v]]; - float vx = vertices[b], vy = vertices[b + 1], 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 { - float[] deform = deformArray.Items; - for (int w = offset, b = skip * 3, f = skip << 1; w < count; w += stride) { - float wx = 0, wy = 0; - int n = bones[v++]; - n += v; - for (; v < n; v++, b += 3, f += 2) { - Bone bone = skeletonBones[bones[v]]; - float vx = vertices[b] + deform[f], vy = vertices[b + 1] + deform[f + 1], 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; - } - } - } + /// + /// Transforms the attachment's local to world coordinates. If the slot's is + /// not empty, it is used to deform the vertices. + /// + /// See World transforms in the Spine + /// Runtimes Guide. + /// + /// The index of the first value to transform. Each vertex has 2 values, x and y. + /// The number of world vertex values to output. Must be less than or equal to - start. + /// The output world vertices. Must have a length greater than or equal to + . + /// The index to begin writing values. + /// The number of entries between the value pairs written. + public void ComputeWorldVertices(Slot slot, int start, int count, float[] worldVertices, int offset, int stride = 2) + { + count = offset + (count >> 1) * stride; + Skeleton skeleton = slot.bone.skeleton; + var deformArray = slot.deform; + float[] vertices = this.vertices; + int[] bones = this.bones; + if (bones == null) + { + if (deformArray.Count > 0) vertices = deformArray.Items; + Bone bone = slot.bone; + float x = bone.worldX, y = bone.worldY; + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + for (int vv = start, w = offset; w < count; vv += 2, w += stride) + { + float vx = vertices[vv], 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 (int i = 0; i < start; i += 2) + { + int n = bones[v]; + v += n + 1; + skip += n; + } + var skeletonBones = skeleton.bones.Items; + if (deformArray.Count == 0) + { + for (int w = offset, b = skip * 3; w < count; w += stride) + { + float wx = 0, wy = 0; + int n = bones[v++]; + n += v; + for (; v < n; v++, b += 3) + { + Bone bone = skeletonBones[bones[v]]; + float vx = vertices[b], vy = vertices[b + 1], 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 + { + float[] deform = deformArray.Items; + for (int w = offset, b = skip * 3, f = skip << 1; w < count; w += stride) + { + float wx = 0, wy = 0; + int n = bones[v++]; + n += v; + for (; v < n; v++, b += 3, f += 2) + { + Bone bone = skeletonBones[bones[v]]; + float vx = vertices[b] + deform[f], vy = vertices[b + 1] + deform[f + 1], 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; + } + } + } - ///Does not copy id (generated) or name (set on construction). - internal void CopyTo (VertexAttachment attachment) { - if (bones != null) { - attachment.bones = new int[bones.Length]; - Array.Copy(bones, 0, attachment.bones, 0, bones.Length); - } - else - attachment.bones = null; + ///Does not copy id (generated) or name (set on construction). + internal void CopyTo(VertexAttachment attachment) + { + if (bones != null) + { + attachment.bones = new int[bones.Length]; + Array.Copy(bones, 0, attachment.bones, 0, bones.Length); + } + else + attachment.bones = null; - if (vertices != null) { - attachment.vertices = new float[vertices.Length]; - Array.Copy(vertices, 0, attachment.vertices, 0, vertices.Length); - } - else - attachment.vertices = null; + if (vertices != null) + { + attachment.vertices = new float[vertices.Length]; + Array.Copy(vertices, 0, attachment.vertices, 0, vertices.Length); + } + else + attachment.vertices = null; - attachment.worldVerticesLength = worldVerticesLength; - attachment.deformAttachment = deformAttachment; - } - } + attachment.worldVerticesLength = worldVerticesLength; + attachment.deformAttachment = deformAttachment; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/BlendMode.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/BlendMode.cs index 6f5b95d..ca9b057 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/BlendMode.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/BlendMode.cs @@ -27,8 +27,10 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine3_8_95 { - public enum BlendMode { - Normal, Additive, Multiply, Screen - } +namespace Spine3_8_95 +{ + public enum BlendMode + { + Normal, Additive, Multiply, Screen + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Bone.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Bone.cs index a4826c7..ff31946 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Bone.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Bone.cs @@ -29,338 +29,369 @@ using System; -namespace Spine3_8_95 { - /// - /// 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. - /// - /// - public class Bone : IUpdatable { - static public bool yDown; - - internal BoneData data; - internal Skeleton skeleton; - internal Bone parent; - internal ExposedList children = new ExposedList(); - internal float x, y, rotation, scaleX, scaleY, shearX, shearY; - internal float ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY; - internal bool appliedValid; - - internal float a, b, worldX; - internal float c, d, worldY; - - internal bool sorted, active; - - public BoneData Data { get { return data; } } - public Skeleton Skeleton { get { return skeleton; } } - public Bone Parent { get { return parent; } } - public ExposedList Children { get { return children; } } - /// Returns false when the bone has not been computed because is true and the - /// active skin does not contain this bone. - public bool Active { get { return active; } } - /// The local X translation. - public float X { get { return x; } set { x = value; } } - /// The local Y translation. - public float Y { get { return y; } set { y = value; } } - /// The local rotation. - public float Rotation { get { return rotation; } set { rotation = value; } } - - /// The local scaleX. - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - - /// The local scaleY. - public float ScaleY { get { return scaleY; } set { scaleY = value; } } - - /// The local shearX. - public float ShearX { get { return shearX; } set { shearX = value; } } - - /// The local shearY. - public float ShearY { get { return shearY; } set { shearY = value; } } - - /// The rotation, as calculated by any constraints. - public float AppliedRotation { get { return arotation; } set { arotation = value; } } - - /// The applied local x translation. - public float AX { get { return ax; } set { ax = value; } } - - /// The applied local y translation. - public float AY { get { return ay; } set { ay = value; } } - - /// The applied local scaleX. - public float AScaleX { get { return ascaleX; } set { ascaleX = value; } } - - /// The applied local scaleY. - public float AScaleY { get { return ascaleY; } set { ascaleY = value; } } - - /// The applied local shearX. - public float AShearX { get { return ashearX; } set { ashearX = value; } } - - /// The applied local shearY. - public float AShearY { get { return ashearY; } set { ashearY = value; } } - - public float A { get { return a; } } - public float B { get { return b; } } - public float C { get { return c; } } - public float D { get { return d; } } - - public float WorldX { get { return worldX; } } - public float WorldY { get { return worldY; } } - public float WorldRotationX { get { return MathUtils.Atan2(c, a) * MathUtils.RadDeg; } } - public float WorldRotationY { get { return MathUtils.Atan2(d, b) * MathUtils.RadDeg; } } - - /// Returns the magnitide (always positive) of the world scale X. - public float WorldScaleX { get { return (float)Math.Sqrt(a * a + c * c); } } - /// Returns the magnitide (always positive) of the world scale Y. - public float WorldScaleY { get { return (float)Math.Sqrt(b * b + d * d); } } - - /// May be null. - public Bone (BoneData data, Skeleton skeleton, Bone parent) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.data = data; - this.skeleton = skeleton; - this.parent = parent; - SetToSetupPose(); - } - - /// Same as . This method exists for Bone to implement . - public void Update () { - UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); - } - - /// Computes the world transform using the parent bone and this bone's local transform. - public void UpdateWorldTransform () { - UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); - } - - /// Computes the world transform using the parent bone and the specified local transform. - public void UpdateWorldTransform (float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY) { - ax = x; - ay = y; - arotation = rotation; - ascaleX = scaleX; - ascaleY = scaleY; - ashearX = shearX; - ashearY = shearY; - appliedValid = true; - Skeleton skeleton = this.skeleton; - - Bone parent = this.parent; - if (parent == null) { // Root bone. - float rotationY = rotation + 90 + shearY, sx = skeleton.ScaleX, sy = skeleton.ScaleY; - a = MathUtils.CosDeg(rotation + shearX) * scaleX * sx; - b = MathUtils.CosDeg(rotationY) * scaleY * sx; - c = MathUtils.SinDeg(rotation + shearX) * scaleX * sy; - d = MathUtils.SinDeg(rotationY) * scaleY * sy; - worldX = x * sx + skeleton.x; - worldY = y * sy + skeleton.y; - return; - } - - float 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 (data.transformMode) { - case TransformMode.Normal: { - float rotationY = rotation + 90 + shearY; - float la = MathUtils.CosDeg(rotation + shearX) * scaleX; - float lb = MathUtils.CosDeg(rotationY) * scaleY; - float lc = MathUtils.SinDeg(rotation + shearX) * scaleX; - float ld = MathUtils.SinDeg(rotationY) * scaleY; - a = pa * la + pb * lc; - b = pa * lb + pb * ld; - c = pc * la + pd * lc; - d = pc * lb + pd * ld; - return; - } - case TransformMode.OnlyTranslation: { - float rotationY = rotation + 90 + shearY; - a = MathUtils.CosDeg(rotation + shearX) * scaleX; - b = MathUtils.CosDeg(rotationY) * scaleY; - c = MathUtils.SinDeg(rotation + shearX) * scaleX; - d = MathUtils.SinDeg(rotationY) * scaleY; - break; - } - case TransformMode.NoRotationOrReflection: { - float s = pa * pa + pc * pc, prx; - if (s > 0.0001f) { - s = Math.Abs(pa * pd - pb * pc) / s; - pa /= skeleton.ScaleX; - pc /= skeleton.ScaleY; - pb = pc * s; - pd = pa * s; - prx = MathUtils.Atan2(pc, pa) * MathUtils.RadDeg; - } else { - pa = 0; - pc = 0; - prx = 90 - MathUtils.Atan2(pd, pb) * MathUtils.RadDeg; - } - float rx = rotation + shearX - prx; - float ry = rotation + shearY - prx + 90; - float la = MathUtils.CosDeg(rx) * scaleX; - float lb = MathUtils.CosDeg(ry) * scaleY; - float lc = MathUtils.SinDeg(rx) * scaleX; - float ld = MathUtils.SinDeg(ry) * scaleY; - a = pa * la - pb * lc; - b = pa * lb - pb * ld; - c = pc * la + pd * lc; - d = pc * lb + pd * ld; - break; - } - case TransformMode.NoScale: - case TransformMode.NoScaleOrReflection: { - float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation); - float za = (pa * cos + pb * sin) / skeleton.ScaleX; - float zc = (pc * cos + pd * sin) / skeleton.ScaleY; - float s = (float)Math.Sqrt(za * za + zc * zc); - if (s > 0.00001f) s = 1 / s; - za *= s; - zc *= s; - s = (float)Math.Sqrt(za * za + zc * zc); - if (data.transformMode == TransformMode.NoScale - && (pa * pd - pb * pc < 0) != (skeleton.ScaleX < 0 != skeleton.ScaleY < 0)) s = -s; - - float r = MathUtils.PI / 2 + MathUtils.Atan2(zc, za); - float zb = MathUtils.Cos(r) * s; - float zd = MathUtils.Sin(r) * s; - float la = MathUtils.CosDeg(shearX) * scaleX; - float lb = MathUtils.CosDeg(90 + shearY) * scaleY; - float lc = MathUtils.SinDeg(shearX) * scaleX; - float ld = MathUtils.SinDeg(90 + shearY) * scaleY; - a = za * la + zb * lc; - b = za * lb + zb * ld; - c = zc * la + zd * lc; - d = zc * lb + zd * ld; - break; - } - } - - a *= skeleton.ScaleX; - b *= skeleton.ScaleX; - c *= skeleton.ScaleY; - d *= skeleton.ScaleY; - } - - public void SetToSetupPose () { - BoneData data = this.data; - x = data.x; - y = data.y; - rotation = data.rotation; - scaleX = data.scaleX; - scaleY = data.scaleY; - shearX = data.shearX; - shearY = data.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. - /// - internal void UpdateAppliedTransform () { - appliedValid = true; - Bone parent = this.parent; - if (parent == null) { - ax = worldX; - ay = worldY; - arotation = MathUtils.Atan2(c, a) * MathUtils.RadDeg; - ascaleX = (float)Math.Sqrt(a * a + c * c); - ascaleY = (float)Math.Sqrt(b * b + d * d); - ashearX = 0; - ashearY = MathUtils.Atan2(a * b + c * d, a * d - b * c) * MathUtils.RadDeg; - return; - } - float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d; - float pid = 1 / (pa * pd - pb * pc); - float dx = worldX - parent.worldX, dy = worldY - parent.worldY; - ax = (dx * pd * pid - dy * pb * pid); - ay = (dy * pa * pid - dx * pc * pid); - float ia = pid * pd; - float id = pid * pa; - float ib = pid * pb; - float ic = pid * pc; - float ra = ia * a - ib * c; - float rb = ia * b - ib * d; - float rc = id * c - ic * a; - float rd = id * d - ic * b; - ashearX = 0; - ascaleX = (float)Math.Sqrt(ra * ra + rc * rc); - if (ascaleX > 0.0001f) { - float det = ra * rd - rb * rc; - ascaleY = det / ascaleX; - ashearY = MathUtils.Atan2(ra * rb + rc * rd, det) * MathUtils.RadDeg; - arotation = MathUtils.Atan2(rc, ra) * MathUtils.RadDeg; - } else { - ascaleX = 0; - ascaleY = (float)Math.Sqrt(rb * rb + rd * rd); - ashearY = 0; - arotation = 90 - MathUtils.Atan2(rd, rb) * MathUtils.RadDeg; - } - } - - public void WorldToLocal (float worldX, float worldY, out float localX, out float localY) { - float a = this.a, b = this.b, c = this.c, d = this.d; - float invDet = 1 / (a * d - b * c); - float x = worldX - this.worldX, y = worldY - this.worldY; - localX = (x * d * invDet - y * b * invDet); - localY = (y * a * invDet - x * c * invDet); - } - - public void LocalToWorld (float localX, float localY, out float worldX, out float worldY) { - worldX = localX * a + localY * b + this.worldX; - worldY = localX * c + localY * d + this.worldY; - } - - public float WorldToLocalRotationX { - get { - Bone parent = this.parent; - if (parent == null) return arotation; - float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, a = this.a, c = this.c; - return MathUtils.Atan2(pa * c - pc * a, pd * a - pb * c) * MathUtils.RadDeg; - } - } - - public float WorldToLocalRotationY { - get { - Bone parent = this.parent; - if (parent == null) return arotation; - float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, b = this.b, d = this.d; - return MathUtils.Atan2(pa * d - pc * b, pd * b - pb * d) * MathUtils.RadDeg; - } - } - - public float WorldToLocalRotation (float worldRotation) { - float sin = MathUtils.SinDeg(worldRotation), cos = MathUtils.CosDeg(worldRotation); - return MathUtils.Atan2(a * sin - c * cos, d * cos - b * sin) * MathUtils.RadDeg + rotation - shearX; - } - - public float LocalToWorldRotation (float localRotation) { - localRotation -= rotation - shearX; - float sin = MathUtils.SinDeg(localRotation), cos = MathUtils.CosDeg(localRotation); - return MathUtils.Atan2(cos * c + sin * d, cos * a + sin * b) * MathUtils.RadDeg; - } - - /// - /// Rotates the world transform the specified amount and sets isAppliedValid to false. - /// - /// Degrees. - public void RotateWorld (float degrees) { - float a = this.a, b = this.b, c = this.c, d = this.d; - float cos = MathUtils.CosDeg(degrees), sin = MathUtils.SinDeg(degrees); - this.a = cos * a - sin * c; - this.b = cos * b - sin * d; - this.c = sin * a + cos * c; - this.d = sin * b + cos * d; - appliedValid = false; - } - - override public string ToString () { - return data.name; - } - } +namespace Spine3_8_95 +{ + /// + /// 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. + /// + /// + public class Bone : IUpdatable + { + static public bool yDown; + + internal BoneData data; + internal Skeleton skeleton; + internal Bone parent; + internal ExposedList children = new ExposedList(); + internal float x, y, rotation, scaleX, scaleY, shearX, shearY; + internal float ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY; + internal bool appliedValid; + + internal float a, b, worldX; + internal float c, d, worldY; + + internal bool sorted, active; + + public BoneData Data { get { return data; } } + public Skeleton Skeleton { get { return skeleton; } } + public Bone Parent { get { return parent; } } + public ExposedList Children { get { return children; } } + /// Returns false when the bone has not been computed because is true and the + /// active skin does not contain this bone. + public bool Active { get { return active; } } + /// The local X translation. + public float X { get { return x; } set { x = value; } } + /// The local Y translation. + public float Y { get { return y; } set { y = value; } } + /// The local rotation. + public float Rotation { get { return rotation; } set { rotation = value; } } + + /// The local scaleX. + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + + /// The local scaleY. + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + + /// The local shearX. + public float ShearX { get { return shearX; } set { shearX = value; } } + + /// The local shearY. + public float ShearY { get { return shearY; } set { shearY = value; } } + + /// The rotation, as calculated by any constraints. + public float AppliedRotation { get { return arotation; } set { arotation = value; } } + + /// The applied local x translation. + public float AX { get { return ax; } set { ax = value; } } + + /// The applied local y translation. + public float AY { get { return ay; } set { ay = value; } } + + /// The applied local scaleX. + public float AScaleX { get { return ascaleX; } set { ascaleX = value; } } + + /// The applied local scaleY. + public float AScaleY { get { return ascaleY; } set { ascaleY = value; } } + + /// The applied local shearX. + public float AShearX { get { return ashearX; } set { ashearX = value; } } + + /// The applied local shearY. + public float AShearY { get { return ashearY; } set { ashearY = value; } } + + public float A { get { return a; } } + public float B { get { return b; } } + public float C { get { return c; } } + public float D { get { return d; } } + + public float WorldX { get { return worldX; } } + public float WorldY { get { return worldY; } } + public float WorldRotationX { get { return MathUtils.Atan2(c, a) * MathUtils.RadDeg; } } + public float WorldRotationY { get { return MathUtils.Atan2(d, b) * MathUtils.RadDeg; } } + + /// Returns the magnitide (always positive) of the world scale X. + public float WorldScaleX { get { return (float)Math.Sqrt(a * a + c * c); } } + /// Returns the magnitide (always positive) of the world scale Y. + public float WorldScaleY { get { return (float)Math.Sqrt(b * b + d * d); } } + + /// May be null. + public Bone(BoneData data, Skeleton skeleton, Bone parent) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + this.skeleton = skeleton; + this.parent = parent; + SetToSetupPose(); + } + + /// Same as . This method exists for Bone to implement . + public void Update() + { + UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); + } + + /// Computes the world transform using the parent bone and this bone's local transform. + public void UpdateWorldTransform() + { + UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); + } + + /// Computes the world transform using the parent bone and the specified local transform. + public void UpdateWorldTransform(float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY) + { + ax = x; + ay = y; + arotation = rotation; + ascaleX = scaleX; + ascaleY = scaleY; + ashearX = shearX; + ashearY = shearY; + appliedValid = true; + Skeleton skeleton = this.skeleton; + + Bone parent = this.parent; + if (parent == null) + { // Root bone. + float rotationY = rotation + 90 + shearY, sx = skeleton.ScaleX, sy = skeleton.ScaleY; + a = MathUtils.CosDeg(rotation + shearX) * scaleX * sx; + b = MathUtils.CosDeg(rotationY) * scaleY * sx; + c = MathUtils.SinDeg(rotation + shearX) * scaleX * sy; + d = MathUtils.SinDeg(rotationY) * scaleY * sy; + worldX = x * sx + skeleton.x; + worldY = y * sy + skeleton.y; + return; + } + + float 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 (data.transformMode) + { + case TransformMode.Normal: + { + float rotationY = rotation + 90 + shearY; + float la = MathUtils.CosDeg(rotation + shearX) * scaleX; + float lb = MathUtils.CosDeg(rotationY) * scaleY; + float lc = MathUtils.SinDeg(rotation + shearX) * scaleX; + float ld = MathUtils.SinDeg(rotationY) * scaleY; + a = pa * la + pb * lc; + b = pa * lb + pb * ld; + c = pc * la + pd * lc; + d = pc * lb + pd * ld; + return; + } + case TransformMode.OnlyTranslation: + { + float rotationY = rotation + 90 + shearY; + a = MathUtils.CosDeg(rotation + shearX) * scaleX; + b = MathUtils.CosDeg(rotationY) * scaleY; + c = MathUtils.SinDeg(rotation + shearX) * scaleX; + d = MathUtils.SinDeg(rotationY) * scaleY; + break; + } + case TransformMode.NoRotationOrReflection: + { + float s = pa * pa + pc * pc, prx; + if (s > 0.0001f) + { + s = Math.Abs(pa * pd - pb * pc) / s; + pa /= skeleton.ScaleX; + pc /= skeleton.ScaleY; + pb = pc * s; + pd = pa * s; + prx = MathUtils.Atan2(pc, pa) * MathUtils.RadDeg; + } + else + { + pa = 0; + pc = 0; + prx = 90 - MathUtils.Atan2(pd, pb) * MathUtils.RadDeg; + } + float rx = rotation + shearX - prx; + float ry = rotation + shearY - prx + 90; + float la = MathUtils.CosDeg(rx) * scaleX; + float lb = MathUtils.CosDeg(ry) * scaleY; + float lc = MathUtils.SinDeg(rx) * scaleX; + float ld = MathUtils.SinDeg(ry) * scaleY; + a = pa * la - pb * lc; + b = pa * lb - pb * ld; + c = pc * la + pd * lc; + d = pc * lb + pd * ld; + break; + } + case TransformMode.NoScale: + case TransformMode.NoScaleOrReflection: + { + float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation); + float za = (pa * cos + pb * sin) / skeleton.ScaleX; + float zc = (pc * cos + pd * sin) / skeleton.ScaleY; + float s = (float)Math.Sqrt(za * za + zc * zc); + if (s > 0.00001f) s = 1 / s; + za *= s; + zc *= s; + s = (float)Math.Sqrt(za * za + zc * zc); + if (data.transformMode == TransformMode.NoScale + && (pa * pd - pb * pc < 0) != (skeleton.ScaleX < 0 != skeleton.ScaleY < 0)) s = -s; + + float r = MathUtils.PI / 2 + MathUtils.Atan2(zc, za); + float zb = MathUtils.Cos(r) * s; + float zd = MathUtils.Sin(r) * s; + float la = MathUtils.CosDeg(shearX) * scaleX; + float lb = MathUtils.CosDeg(90 + shearY) * scaleY; + float lc = MathUtils.SinDeg(shearX) * scaleX; + float ld = MathUtils.SinDeg(90 + shearY) * scaleY; + a = za * la + zb * lc; + b = za * lb + zb * ld; + c = zc * la + zd * lc; + d = zc * lb + zd * ld; + break; + } + } + + a *= skeleton.ScaleX; + b *= skeleton.ScaleX; + c *= skeleton.ScaleY; + d *= skeleton.ScaleY; + } + + public void SetToSetupPose() + { + BoneData data = this.data; + x = data.x; + y = data.y; + rotation = data.rotation; + scaleX = data.scaleX; + scaleY = data.scaleY; + shearX = data.shearX; + shearY = data.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. + /// + internal void UpdateAppliedTransform() + { + appliedValid = true; + Bone parent = this.parent; + if (parent == null) + { + ax = worldX; + ay = worldY; + arotation = MathUtils.Atan2(c, a) * MathUtils.RadDeg; + ascaleX = (float)Math.Sqrt(a * a + c * c); + ascaleY = (float)Math.Sqrt(b * b + d * d); + ashearX = 0; + ashearY = MathUtils.Atan2(a * b + c * d, a * d - b * c) * MathUtils.RadDeg; + return; + } + float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d; + float pid = 1 / (pa * pd - pb * pc); + float dx = worldX - parent.worldX, dy = worldY - parent.worldY; + ax = (dx * pd * pid - dy * pb * pid); + ay = (dy * pa * pid - dx * pc * pid); + float ia = pid * pd; + float id = pid * pa; + float ib = pid * pb; + float ic = pid * pc; + float ra = ia * a - ib * c; + float rb = ia * b - ib * d; + float rc = id * c - ic * a; + float rd = id * d - ic * b; + ashearX = 0; + ascaleX = (float)Math.Sqrt(ra * ra + rc * rc); + if (ascaleX > 0.0001f) + { + float det = ra * rd - rb * rc; + ascaleY = det / ascaleX; + ashearY = MathUtils.Atan2(ra * rb + rc * rd, det) * MathUtils.RadDeg; + arotation = MathUtils.Atan2(rc, ra) * MathUtils.RadDeg; + } + else + { + ascaleX = 0; + ascaleY = (float)Math.Sqrt(rb * rb + rd * rd); + ashearY = 0; + arotation = 90 - MathUtils.Atan2(rd, rb) * MathUtils.RadDeg; + } + } + + public void WorldToLocal(float worldX, float worldY, out float localX, out float localY) + { + float a = this.a, b = this.b, c = this.c, d = this.d; + float invDet = 1 / (a * d - b * c); + float x = worldX - this.worldX, y = worldY - this.worldY; + localX = (x * d * invDet - y * b * invDet); + localY = (y * a * invDet - x * c * invDet); + } + + public void LocalToWorld(float localX, float localY, out float worldX, out float worldY) + { + worldX = localX * a + localY * b + this.worldX; + worldY = localX * c + localY * d + this.worldY; + } + + public float WorldToLocalRotationX + { + get + { + Bone parent = this.parent; + if (parent == null) return arotation; + float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, a = this.a, c = this.c; + return MathUtils.Atan2(pa * c - pc * a, pd * a - pb * c) * MathUtils.RadDeg; + } + } + + public float WorldToLocalRotationY + { + get + { + Bone parent = this.parent; + if (parent == null) return arotation; + float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, b = this.b, d = this.d; + return MathUtils.Atan2(pa * d - pc * b, pd * b - pb * d) * MathUtils.RadDeg; + } + } + + public float WorldToLocalRotation(float worldRotation) + { + float sin = MathUtils.SinDeg(worldRotation), cos = MathUtils.CosDeg(worldRotation); + return MathUtils.Atan2(a * sin - c * cos, d * cos - b * sin) * MathUtils.RadDeg + rotation - shearX; + } + + public float LocalToWorldRotation(float localRotation) + { + localRotation -= rotation - shearX; + float sin = MathUtils.SinDeg(localRotation), cos = MathUtils.CosDeg(localRotation); + return MathUtils.Atan2(cos * c + sin * d, cos * a + sin * b) * MathUtils.RadDeg; + } + + /// + /// Rotates the world transform the specified amount and sets isAppliedValid to false. + /// + /// Degrees. + public void RotateWorld(float degrees) + { + float a = this.a, b = this.b, c = this.c, d = this.d; + float cos = MathUtils.CosDeg(degrees), sin = MathUtils.SinDeg(degrees); + this.a = cos * a - sin * c; + this.b = cos * b - sin * d; + this.c = sin * a + cos * c; + this.d = sin * b + cos * d; + appliedValid = false; + } + + override public string ToString() + { + return data.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/BoneData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/BoneData.cs index 7523ad6..6227586 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/BoneData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/BoneData.cs @@ -29,77 +29,82 @@ using System; -namespace Spine3_8_95 { - public class BoneData { - internal int index; - internal string name; - internal BoneData parent; - internal float length; - internal float x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY; - internal TransformMode transformMode = TransformMode.Normal; - internal bool skinRequired; - - /// The index of the bone in Skeleton.Bones - public int Index { get { return index; } } - - /// The name of the bone, which is unique across all bones in the skeleton. - public string Name { get { return name; } } - - /// May be null. - public BoneData Parent { get { return parent; } } - - public float Length { get { return length; } set { length = value; } } - - /// Local X translation. - public float X { get { return x; } set { x = value; } } - - /// Local Y translation. - public float Y { get { return y; } set { y = value; } } - - /// Local rotation. - public float Rotation { get { return rotation; } set { rotation = value; } } - - /// Local scaleX. - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - - /// Local scaleY. - public float ScaleY { get { return scaleY; } set { scaleY = value; } } - - /// Local shearX. - public float ShearX { get { return shearX; } set { shearX = value; } } - - /// Local shearY. - public float ShearY { get { return shearY; } set { shearY = value; } } - - /// The transform mode for how parent world transforms affect this bone. - public TransformMode TransformMode { get { return transformMode; } set { transformMode = value; } } - - ///When true, only updates this bone if the contains this - /// bone. - /// - public bool SkinRequired { get { return skinRequired; } set { skinRequired = value; } } - - /// May be null. - public BoneData (int index, string name, BoneData parent) { - if (index < 0) throw new ArgumentException("index must be >= 0", "index"); - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.index = index; - this.name = name; - this.parent = parent; - } - - override public string ToString () { - return name; - } - } - - [Flags] - public enum TransformMode { - //0000 0 Flip Scale Rotation - Normal = 0, // 0000 - OnlyTranslation = 7, // 0111 - NoRotationOrReflection = 1, // 0001 - NoScale = 2, // 0010 - NoScaleOrReflection = 6, // 0110 - } +namespace Spine3_8_95 +{ + public class BoneData + { + internal int index; + internal string name; + internal BoneData parent; + internal float length; + internal float x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY; + internal TransformMode transformMode = TransformMode.Normal; + internal bool skinRequired; + + /// The index of the bone in Skeleton.Bones + public int Index { get { return index; } } + + /// The name of the bone, which is unique across all bones in the skeleton. + public string Name { get { return name; } } + + /// May be null. + public BoneData Parent { get { return parent; } } + + public float Length { get { return length; } set { length = value; } } + + /// Local X translation. + public float X { get { return x; } set { x = value; } } + + /// Local Y translation. + public float Y { get { return y; } set { y = value; } } + + /// Local rotation. + public float Rotation { get { return rotation; } set { rotation = value; } } + + /// Local scaleX. + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + + /// Local scaleY. + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + + /// Local shearX. + public float ShearX { get { return shearX; } set { shearX = value; } } + + /// Local shearY. + public float ShearY { get { return shearY; } set { shearY = value; } } + + /// The transform mode for how parent world transforms affect this bone. + public TransformMode TransformMode { get { return transformMode; } set { transformMode = value; } } + + ///When true, only updates this bone if the contains this + /// bone. + /// + public bool SkinRequired { get { return skinRequired; } set { skinRequired = value; } } + + /// May be null. + public BoneData(int index, string name, BoneData parent) + { + if (index < 0) throw new ArgumentException("index must be >= 0", "index"); + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.index = index; + this.name = name; + this.parent = parent; + } + + override public string ToString() + { + return name; + } + } + + [Flags] + public enum TransformMode + { + //0000 0 Flip Scale Rotation + Normal = 0, // 0000 + OnlyTranslation = 7, // 0111 + NoRotationOrReflection = 1, // 0001 + NoScale = 2, // 0010 + NoScaleOrReflection = 6, // 0110 + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Collections/OrderedDictionary.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Collections/OrderedDictionary.cs index 6af4cc4..b478ebc 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Collections/OrderedDictionary.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Collections/OrderedDictionary.cs @@ -41,593 +41,687 @@ namespace Spine.Collections { - /// - /// Represents a dictionary that tracks the order that items were added. - /// - /// The type of the dictionary keys. - /// The type of the dictionary values. - /// - /// This dictionary makes it possible to get the index of a key and a key based on an index. - /// It can be costly to find the index of a key because it must be searched for linearly. - /// It can be costly to insert a key/value pair because other key's indexes must be adjusted. - /// It can be costly to remove a key/value pair because other keys' indexes must be adjusted. - /// - [DebuggerDisplay("Count = {Count}")] - [DebuggerTypeProxy(typeof(OrderedDictionaryDebugView<,>))] - public sealed class OrderedDictionary : IDictionary, IList> - { - private readonly Dictionary dictionary; - private readonly List keys; - private readonly List values; - private int version; - - private const string CollectionModifiedMessage = "Collection was modified; enumeration operation may not execute."; - private const string EditReadOnlyListMessage = "An attempt was made to edit a read-only list."; - private const string IndexOutOfRangeMessage = "The index is negative or outside the bounds of the collection."; - - /// - /// Initializes a new instance of an OrderedDictionary. - /// - public OrderedDictionary () - : this(0, null) { - } - - /// - /// Initializes a new instance of an OrderedDictionary. - /// - /// The initial capacity of the dictionary. - /// The capacity is less than zero. - public OrderedDictionary (int capacity) - : this(capacity, null) { - } - - /// - /// Initializes a new instance of an OrderedDictionary. - /// - /// The equality comparer to use to compare keys. - public OrderedDictionary (IEqualityComparer comparer) - : this(0, comparer) { - } - - /// - /// Initializes a new instance of an OrderedDictionary. - /// - /// The initial capacity of the dictionary. - /// The equality comparer to use to compare keys. - public OrderedDictionary (int capacity, IEqualityComparer comparer) { - dictionary = new Dictionary(capacity, comparer ?? EqualityComparer.Default); - keys = new List(capacity); - values = new List(capacity); - } - - /// - /// Gets the equality comparer used to compare keys. - /// - public IEqualityComparer Comparer { - get { - return dictionary.Comparer; - } - } - - /// - /// Adds the given key/value pair to the dictionary. - /// - /// The key to add to the dictionary. - /// The value to associated with the key. - /// The given key already exists in the dictionary. - /// The key is null. - public void Add (TKey key, TValue value) { - dictionary.Add(key, values.Count); - keys.Add(key); - values.Add(value); - ++version; - } - - /// - /// Inserts the given key/value pair at the specified index. - /// - /// The index to insert the key/value pair. - /// The key to insert. - /// The value to insert. - /// The given key already exists in the dictionary. - /// The key is null. - /// The index is negative -or- larger than the size of the dictionary. - public void Insert (int index, TKey key, TValue value) { - if (index < 0 || index > values.Count) { - throw new ArgumentOutOfRangeException("index", index, IndexOutOfRangeMessage); - } - dictionary.Add(key, index); - for (int keyIndex = index; keyIndex != keys.Count; ++keyIndex) { - var otherKey = keys[keyIndex]; - dictionary[otherKey] += 1; - } - keys.Insert(index, key); - values.Insert(index, value); - ++version; - } - - /// - /// Determines whether the given key exists in the dictionary. - /// - /// The key to look for. - /// True if the key exists in the dictionary; otherwise, false. - /// The key is null. - public bool ContainsKey (TKey key) { - return dictionary.ContainsKey(key); - } - - /// - /// Gets the key at the given index. - /// - /// The index of the key to get. - /// The key at the given index. - /// The index is negative -or- larger than the number of keys. - public TKey GetKey (int index) { - return keys[index]; - } - - /// - /// Gets the index of the given key. - /// - /// The key to get the index of. - /// The index of the key in the dictionary -or- -1 if the key is not found. - /// The operation runs in O(n). - public int IndexOf (TKey key) { - int index; - if (dictionary.TryGetValue(key, out index)) { - return index; - } - return -1; - } - - /// - /// Gets the keys in the dictionary in the order they were added. - /// - public KeyCollection Keys { - get { - return new KeyCollection(this.dictionary); - } - } - - /// - /// Removes the key/value pair with the given key from the dictionary. - /// - /// The key of the pair to remove. - /// True if the key was found and the pair removed; otherwise, false. - /// The key is null. - public bool Remove (TKey key) { - int index; - if (dictionary.TryGetValue(key, out index)) { - RemoveAt(index); - return true; - } - return false; - } - - /// - /// Removes the key/value pair at the given index. - /// - /// The index of the key/value pair to remove. - /// The index is negative -or- larger than the size of the dictionary. - public void RemoveAt (int index) { - var key = keys[index]; - for (int keyIndex = index + 1; keyIndex < keys.Count; ++keyIndex) { - var otherKey = keys[keyIndex]; - dictionary[otherKey] -= 1; - } - dictionary.Remove(key); - keys.RemoveAt(index); - values.RemoveAt(index); - ++version; - } - - /// - /// Tries to get the value associated with the given key. If the key is not found, - /// default(TValue) value is stored in the value. - /// - /// The key to get the value for. - /// The value used to hold the results. - /// True if the key was found; otherwise, false. - /// The key is null. - public bool TryGetValue (TKey key, out TValue value) { - int index; - if (dictionary.TryGetValue(key, out index)) { - value = values[index]; - return true; - } - value = default(TValue); - return false; - } - - /// - /// Gets the values in the dictionary. - /// - public ValueCollection Values { - get { - return new ValueCollection(values); - } - } - - /// - /// Gets or sets the value at the given index. - /// - /// The index of the value to get. - /// The value at the given index. - /// The index is negative -or- beyond the length of the dictionary. - public TValue this[int index] { - get { - return values[index]; - } - - set { - values[index] = value; - } - } - - /// - /// Gets or sets the value associated with the given key. - /// - /// The key to get the associated value by or to associate with the value. - /// The value associated with the given key. - /// The key is null. - /// The key is not in the dictionary. - public TValue this[TKey key] { - get { - return values[dictionary[key]]; - } - set { - int index; - if (dictionary.TryGetValue(key, out index)) { - keys[index] = key; - values[index] = value; - } - else { - Add(key, value); - } - } - } - - /// - /// Removes all key/value pairs from the dictionary. - /// - public void Clear () { - dictionary.Clear(); - keys.Clear(); - values.Clear(); - ++version; - } - - /// - /// Gets the number of key/value pairs in the dictionary. - /// - public int Count { - get { - return dictionary.Count; - } - } - - /// - /// Gets the key/value pairs in the dictionary in the order they were added. - /// - /// An enumerator over the key/value pairs in the dictionary. - public IEnumerator> GetEnumerator () { - int startVersion = version; - for (int index = 0; index != keys.Count; ++index) { - var key = keys[index]; - var value = values[index]; - yield return new KeyValuePair(key, value); - if (version != startVersion) { - throw new InvalidOperationException(CollectionModifiedMessage); - } - } - } - - int IList>.IndexOf (KeyValuePair item) { - int index; - if (dictionary.TryGetValue(item.Key, out index) && Equals(values[index], item.Value)) { - return index; - } - return -1; - } - - void IList>.Insert (int index, KeyValuePair item) { - Insert(index, item.Key, item.Value); - } - - KeyValuePair IList>.this[int index] { - get { - TKey key = keys[index]; - TValue value = values[index]; - return new KeyValuePair(key, value); - } - set { - TKey key = keys[index]; - if (dictionary.Comparer.Equals(key, value.Key)) { - dictionary[value.Key] = index; - } - else { - dictionary.Add(value.Key, index); // will throw if key already exists - dictionary.Remove(key); - } - keys[index] = value.Key; - values[index] = value.Value; - } - } - - ICollection IDictionary.Keys { - get { - return Keys; - } - } - - ICollection IDictionary.Values { - get { - return Values; - } - } - - void ICollection>.Add (KeyValuePair item) { - Add(item.Key, item.Value); - } - - bool ICollection>.Contains (KeyValuePair item) { - int index; - if (dictionary.TryGetValue(item.Key, out index) && Equals(values[index], item.Value)) { - return true; - } - return false; - } - - void ICollection>.CopyTo (KeyValuePair[] array, int arrayIndex) { - if (array == null) { - throw new ArgumentNullException("array"); - } - if (arrayIndex < 0) { - throw new ArgumentOutOfRangeException("arrayIndex", arrayIndex, IndexOutOfRangeMessage); - } - for (int index = 0; index != keys.Count && arrayIndex < array.Length; ++index, ++arrayIndex) { - var key = keys[index]; - var value = values[index]; - array[arrayIndex] = new KeyValuePair(key, value); - } - } - - bool ICollection>.IsReadOnly { - get { - return false; - } - } - - bool ICollection>.Remove (KeyValuePair item) { - ICollection> self = this; - if (self.Contains(item)) { - return Remove(item.Key); - } - return false; - } - - IEnumerator IEnumerable.GetEnumerator () { - return GetEnumerator(); - } - - /// - /// Wraps the keys in an OrderDictionary. - /// - public sealed class KeyCollection : ICollection - { - private readonly Dictionary dictionary; - - /// - /// Initializes a new instance of a KeyCollection. - /// - /// The OrderedDictionary whose keys to wrap. - /// The dictionary is null. - internal KeyCollection (Dictionary dictionary) { - this.dictionary = dictionary; - } - - /// - /// Copies the keys from the OrderedDictionary to the given array, starting at the given index. - /// - /// The array to copy the keys to. - /// The index into the array to start copying the keys. - /// The array is null. - /// The arrayIndex is negative. - /// The array, starting at the given index, is not large enough to contain all the keys. - public void CopyTo (TKey[] array, int arrayIndex) { - dictionary.Keys.CopyTo(array, arrayIndex); - } - - /// - /// Gets the number of keys in the OrderedDictionary. - /// - public int Count { - get { - return dictionary.Count; - } - } - - /// - /// Gets an enumerator over the keys in the OrderedDictionary. - /// - /// The enumerator. - public IEnumerator GetEnumerator () { - return dictionary.Keys.GetEnumerator(); - } - - [EditorBrowsable(EditorBrowsableState.Never)] - bool ICollection.Contains (TKey item) { - return dictionary.ContainsKey(item); - } - - [EditorBrowsable(EditorBrowsableState.Never)] - void ICollection.Add (TKey item) { - throw new NotSupportedException(EditReadOnlyListMessage); - } - - [EditorBrowsable(EditorBrowsableState.Never)] - void ICollection.Clear () { - throw new NotSupportedException(EditReadOnlyListMessage); - } - - [EditorBrowsable(EditorBrowsableState.Never)] - bool ICollection.IsReadOnly { - get { - return true; - } - } - - [EditorBrowsable(EditorBrowsableState.Never)] - bool ICollection.Remove (TKey item) { - throw new NotSupportedException(EditReadOnlyListMessage); - } - - IEnumerator IEnumerable.GetEnumerator () { - return GetEnumerator(); - } - } - - /// - /// Wraps the keys in an OrderDictionary. - /// - public sealed class ValueCollection : ICollection - { - private readonly List values; - - /// - /// Initializes a new instance of a ValueCollection. - /// - /// The OrderedDictionary whose keys to wrap. - /// The dictionary is null. - internal ValueCollection (List values) { - this.values = values; - } - - /// - /// Copies the values from the OrderedDictionary to the given array, starting at the given index. - /// - /// The array to copy the values to. - /// The index into the array to start copying the values. - /// The array is null. - /// The arrayIndex is negative. - /// The array, starting at the given index, is not large enough to contain all the values. - public void CopyTo (TValue[] array, int arrayIndex) { - values.CopyTo(array, arrayIndex); - } - - /// - /// Gets the number of values in the OrderedDictionary. - /// - public int Count { - get { - return values.Count; - } - } - - /// - /// Gets an enumerator over the values in the OrderedDictionary. - /// - /// The enumerator. - public IEnumerator GetEnumerator () { - return values.GetEnumerator(); - } - - [EditorBrowsable(EditorBrowsableState.Never)] - bool ICollection.Contains (TValue item) { - return values.Contains(item); - } - - [EditorBrowsable(EditorBrowsableState.Never)] - void ICollection.Add (TValue item) { - throw new NotSupportedException(EditReadOnlyListMessage); - } - - [EditorBrowsable(EditorBrowsableState.Never)] - void ICollection.Clear () { - throw new NotSupportedException(EditReadOnlyListMessage); - } - - [EditorBrowsable(EditorBrowsableState.Never)] - bool ICollection.IsReadOnly { - get { - return true; - } - } - - [EditorBrowsable(EditorBrowsableState.Never)] - bool ICollection.Remove (TValue item) { - throw new NotSupportedException(EditReadOnlyListMessage); - } - - IEnumerator IEnumerable.GetEnumerator () { - return GetEnumerator(); - } - } - } - - internal class OrderedDictionaryDebugView - { - private readonly OrderedDictionary dictionary; - - public OrderedDictionaryDebugView (OrderedDictionary dictionary) { - this.dictionary = dictionary; - } - - [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] - public KeyValuePair[] Items { - get { - return dictionary.ToArray(); - } - } - } - - /// - /// Provides extensions methods for constructing instances of . - /// - public static class CollectionExtensions - { - #region ToOrderedDictionary - - /// - /// Creates a new OrderedDictionary from the given collection, using the key selector to extract the key. - /// - /// The type of the items in the collection. - /// The type of the key. - /// The items to created the OrderedDictionary from. - /// A delegate that can extract a key from an item in the collection. - /// An OrderedDictionary mapping the extracted keys to their values. - public static OrderedDictionary ToOrderedDictionary (this IEnumerable source, Func keySelector) { - return ToOrderedDictionary(source, keySelector, null); - } - - /// - /// Creates a new OrderedDictionary from the given collection, using the key selector to extract the key. - /// The key comparer is passed to the OrderedDictionary for comparing the extracted keys. - /// - /// The type of the items in the collection. - /// The type of the key. - /// The items to created the OrderedDictionary from. - /// A delegate that can extract a key from an item in the collection. - /// The key equality comparer to use to compare keys in the dictionary. - /// An OrderedDictionary mapping the extracted keys to their values. - public static OrderedDictionary ToOrderedDictionary ( - this IEnumerable source, - Func keySelector, - IEqualityComparer comparer) { - if (source == null) { - throw new ArgumentNullException("source"); - } - if (keySelector == null) { - throw new ArgumentNullException("keySelector"); - } - var dictionary = new OrderedDictionary(comparer); - foreach (TSource item in source) { - TKey key = keySelector(item); - dictionary.Add(key, item); - } - return dictionary; - } - - #endregion - } + /// + /// Represents a dictionary that tracks the order that items were added. + /// + /// The type of the dictionary keys. + /// The type of the dictionary values. + /// + /// This dictionary makes it possible to get the index of a key and a key based on an index. + /// It can be costly to find the index of a key because it must be searched for linearly. + /// It can be costly to insert a key/value pair because other key's indexes must be adjusted. + /// It can be costly to remove a key/value pair because other keys' indexes must be adjusted. + /// + [DebuggerDisplay("Count = {Count}")] + [DebuggerTypeProxy(typeof(OrderedDictionaryDebugView<,>))] + public sealed class OrderedDictionary : IDictionary, IList> + { + private readonly Dictionary dictionary; + private readonly List keys; + private readonly List values; + private int version; + + private const string CollectionModifiedMessage = "Collection was modified; enumeration operation may not execute."; + private const string EditReadOnlyListMessage = "An attempt was made to edit a read-only list."; + private const string IndexOutOfRangeMessage = "The index is negative or outside the bounds of the collection."; + + /// + /// Initializes a new instance of an OrderedDictionary. + /// + public OrderedDictionary() + : this(0, null) + { + } + + /// + /// Initializes a new instance of an OrderedDictionary. + /// + /// The initial capacity of the dictionary. + /// The capacity is less than zero. + public OrderedDictionary(int capacity) + : this(capacity, null) + { + } + + /// + /// Initializes a new instance of an OrderedDictionary. + /// + /// The equality comparer to use to compare keys. + public OrderedDictionary(IEqualityComparer comparer) + : this(0, comparer) + { + } + + /// + /// Initializes a new instance of an OrderedDictionary. + /// + /// The initial capacity of the dictionary. + /// The equality comparer to use to compare keys. + public OrderedDictionary(int capacity, IEqualityComparer comparer) + { + dictionary = new Dictionary(capacity, comparer ?? EqualityComparer.Default); + keys = new List(capacity); + values = new List(capacity); + } + + /// + /// Gets the equality comparer used to compare keys. + /// + public IEqualityComparer Comparer + { + get + { + return dictionary.Comparer; + } + } + + /// + /// Adds the given key/value pair to the dictionary. + /// + /// The key to add to the dictionary. + /// The value to associated with the key. + /// The given key already exists in the dictionary. + /// The key is null. + public void Add(TKey key, TValue value) + { + dictionary.Add(key, values.Count); + keys.Add(key); + values.Add(value); + ++version; + } + + /// + /// Inserts the given key/value pair at the specified index. + /// + /// The index to insert the key/value pair. + /// The key to insert. + /// The value to insert. + /// The given key already exists in the dictionary. + /// The key is null. + /// The index is negative -or- larger than the size of the dictionary. + public void Insert(int index, TKey key, TValue value) + { + if (index < 0 || index > values.Count) + { + throw new ArgumentOutOfRangeException("index", index, IndexOutOfRangeMessage); + } + dictionary.Add(key, index); + for (int keyIndex = index; keyIndex != keys.Count; ++keyIndex) + { + var otherKey = keys[keyIndex]; + dictionary[otherKey] += 1; + } + keys.Insert(index, key); + values.Insert(index, value); + ++version; + } + + /// + /// Determines whether the given key exists in the dictionary. + /// + /// The key to look for. + /// True if the key exists in the dictionary; otherwise, false. + /// The key is null. + public bool ContainsKey(TKey key) + { + return dictionary.ContainsKey(key); + } + + /// + /// Gets the key at the given index. + /// + /// The index of the key to get. + /// The key at the given index. + /// The index is negative -or- larger than the number of keys. + public TKey GetKey(int index) + { + return keys[index]; + } + + /// + /// Gets the index of the given key. + /// + /// The key to get the index of. + /// The index of the key in the dictionary -or- -1 if the key is not found. + /// The operation runs in O(n). + public int IndexOf(TKey key) + { + int index; + if (dictionary.TryGetValue(key, out index)) + { + return index; + } + return -1; + } + + /// + /// Gets the keys in the dictionary in the order they were added. + /// + public KeyCollection Keys + { + get + { + return new KeyCollection(this.dictionary); + } + } + + /// + /// Removes the key/value pair with the given key from the dictionary. + /// + /// The key of the pair to remove. + /// True if the key was found and the pair removed; otherwise, false. + /// The key is null. + public bool Remove(TKey key) + { + int index; + if (dictionary.TryGetValue(key, out index)) + { + RemoveAt(index); + return true; + } + return false; + } + + /// + /// Removes the key/value pair at the given index. + /// + /// The index of the key/value pair to remove. + /// The index is negative -or- larger than the size of the dictionary. + public void RemoveAt(int index) + { + var key = keys[index]; + for (int keyIndex = index + 1; keyIndex < keys.Count; ++keyIndex) + { + var otherKey = keys[keyIndex]; + dictionary[otherKey] -= 1; + } + dictionary.Remove(key); + keys.RemoveAt(index); + values.RemoveAt(index); + ++version; + } + + /// + /// Tries to get the value associated with the given key. If the key is not found, + /// default(TValue) value is stored in the value. + /// + /// The key to get the value for. + /// The value used to hold the results. + /// True if the key was found; otherwise, false. + /// The key is null. + public bool TryGetValue(TKey key, out TValue value) + { + int index; + if (dictionary.TryGetValue(key, out index)) + { + value = values[index]; + return true; + } + value = default(TValue); + return false; + } + + /// + /// Gets the values in the dictionary. + /// + public ValueCollection Values + { + get + { + return new ValueCollection(values); + } + } + + /// + /// Gets or sets the value at the given index. + /// + /// The index of the value to get. + /// The value at the given index. + /// The index is negative -or- beyond the length of the dictionary. + public TValue this[int index] + { + get + { + return values[index]; + } + + set + { + values[index] = value; + } + } + + /// + /// Gets or sets the value associated with the given key. + /// + /// The key to get the associated value by or to associate with the value. + /// The value associated with the given key. + /// The key is null. + /// The key is not in the dictionary. + public TValue this[TKey key] + { + get + { + return values[dictionary[key]]; + } + set + { + int index; + if (dictionary.TryGetValue(key, out index)) + { + keys[index] = key; + values[index] = value; + } + else + { + Add(key, value); + } + } + } + + /// + /// Removes all key/value pairs from the dictionary. + /// + public void Clear() + { + dictionary.Clear(); + keys.Clear(); + values.Clear(); + ++version; + } + + /// + /// Gets the number of key/value pairs in the dictionary. + /// + public int Count + { + get + { + return dictionary.Count; + } + } + + /// + /// Gets the key/value pairs in the dictionary in the order they were added. + /// + /// An enumerator over the key/value pairs in the dictionary. + public IEnumerator> GetEnumerator() + { + int startVersion = version; + for (int index = 0; index != keys.Count; ++index) + { + var key = keys[index]; + var value = values[index]; + yield return new KeyValuePair(key, value); + if (version != startVersion) + { + throw new InvalidOperationException(CollectionModifiedMessage); + } + } + } + + int IList>.IndexOf(KeyValuePair item) + { + int index; + if (dictionary.TryGetValue(item.Key, out index) && Equals(values[index], item.Value)) + { + return index; + } + return -1; + } + + void IList>.Insert(int index, KeyValuePair item) + { + Insert(index, item.Key, item.Value); + } + + KeyValuePair IList>.this[int index] + { + get + { + TKey key = keys[index]; + TValue value = values[index]; + return new KeyValuePair(key, value); + } + set + { + TKey key = keys[index]; + if (dictionary.Comparer.Equals(key, value.Key)) + { + dictionary[value.Key] = index; + } + else + { + dictionary.Add(value.Key, index); // will throw if key already exists + dictionary.Remove(key); + } + keys[index] = value.Key; + values[index] = value.Value; + } + } + + ICollection IDictionary.Keys + { + get + { + return Keys; + } + } + + ICollection IDictionary.Values + { + get + { + return Values; + } + } + + void ICollection>.Add(KeyValuePair item) + { + Add(item.Key, item.Value); + } + + bool ICollection>.Contains(KeyValuePair item) + { + int index; + if (dictionary.TryGetValue(item.Key, out index) && Equals(values[index], item.Value)) + { + return true; + } + return false; + } + + void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) + { + if (array == null) + { + throw new ArgumentNullException("array"); + } + if (arrayIndex < 0) + { + throw new ArgumentOutOfRangeException("arrayIndex", arrayIndex, IndexOutOfRangeMessage); + } + for (int index = 0; index != keys.Count && arrayIndex < array.Length; ++index, ++arrayIndex) + { + var key = keys[index]; + var value = values[index]; + array[arrayIndex] = new KeyValuePair(key, value); + } + } + + bool ICollection>.IsReadOnly + { + get + { + return false; + } + } + + bool ICollection>.Remove(KeyValuePair item) + { + ICollection> self = this; + if (self.Contains(item)) + { + return Remove(item.Key); + } + return false; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + /// + /// Wraps the keys in an OrderDictionary. + /// + public sealed class KeyCollection : ICollection + { + private readonly Dictionary dictionary; + + /// + /// Initializes a new instance of a KeyCollection. + /// + /// The OrderedDictionary whose keys to wrap. + /// The dictionary is null. + internal KeyCollection(Dictionary dictionary) + { + this.dictionary = dictionary; + } + + /// + /// Copies the keys from the OrderedDictionary to the given array, starting at the given index. + /// + /// The array to copy the keys to. + /// The index into the array to start copying the keys. + /// The array is null. + /// The arrayIndex is negative. + /// The array, starting at the given index, is not large enough to contain all the keys. + public void CopyTo(TKey[] array, int arrayIndex) + { + dictionary.Keys.CopyTo(array, arrayIndex); + } + + /// + /// Gets the number of keys in the OrderedDictionary. + /// + public int Count + { + get + { + return dictionary.Count; + } + } + + /// + /// Gets an enumerator over the keys in the OrderedDictionary. + /// + /// The enumerator. + public IEnumerator GetEnumerator() + { + return dictionary.Keys.GetEnumerator(); + } + + [EditorBrowsable(EditorBrowsableState.Never)] + bool ICollection.Contains(TKey item) + { + return dictionary.ContainsKey(item); + } + + [EditorBrowsable(EditorBrowsableState.Never)] + void ICollection.Add(TKey item) + { + throw new NotSupportedException(EditReadOnlyListMessage); + } + + [EditorBrowsable(EditorBrowsableState.Never)] + void ICollection.Clear() + { + throw new NotSupportedException(EditReadOnlyListMessage); + } + + [EditorBrowsable(EditorBrowsableState.Never)] + bool ICollection.IsReadOnly + { + get + { + return true; + } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + bool ICollection.Remove(TKey item) + { + throw new NotSupportedException(EditReadOnlyListMessage); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + + /// + /// Wraps the keys in an OrderDictionary. + /// + public sealed class ValueCollection : ICollection + { + private readonly List values; + + /// + /// Initializes a new instance of a ValueCollection. + /// + /// The OrderedDictionary whose keys to wrap. + /// The dictionary is null. + internal ValueCollection(List values) + { + this.values = values; + } + + /// + /// Copies the values from the OrderedDictionary to the given array, starting at the given index. + /// + /// The array to copy the values to. + /// The index into the array to start copying the values. + /// The array is null. + /// The arrayIndex is negative. + /// The array, starting at the given index, is not large enough to contain all the values. + public void CopyTo(TValue[] array, int arrayIndex) + { + values.CopyTo(array, arrayIndex); + } + + /// + /// Gets the number of values in the OrderedDictionary. + /// + public int Count + { + get + { + return values.Count; + } + } + + /// + /// Gets an enumerator over the values in the OrderedDictionary. + /// + /// The enumerator. + public IEnumerator GetEnumerator() + { + return values.GetEnumerator(); + } + + [EditorBrowsable(EditorBrowsableState.Never)] + bool ICollection.Contains(TValue item) + { + return values.Contains(item); + } + + [EditorBrowsable(EditorBrowsableState.Never)] + void ICollection.Add(TValue item) + { + throw new NotSupportedException(EditReadOnlyListMessage); + } + + [EditorBrowsable(EditorBrowsableState.Never)] + void ICollection.Clear() + { + throw new NotSupportedException(EditReadOnlyListMessage); + } + + [EditorBrowsable(EditorBrowsableState.Never)] + bool ICollection.IsReadOnly + { + get + { + return true; + } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + bool ICollection.Remove(TValue item) + { + throw new NotSupportedException(EditReadOnlyListMessage); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + } + + internal class OrderedDictionaryDebugView + { + private readonly OrderedDictionary dictionary; + + public OrderedDictionaryDebugView(OrderedDictionary dictionary) + { + this.dictionary = dictionary; + } + + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public KeyValuePair[] Items + { + get + { + return dictionary.ToArray(); + } + } + } + + /// + /// Provides extensions methods for constructing instances of . + /// + public static class CollectionExtensions + { + #region ToOrderedDictionary + + /// + /// Creates a new OrderedDictionary from the given collection, using the key selector to extract the key. + /// + /// The type of the items in the collection. + /// The type of the key. + /// The items to created the OrderedDictionary from. + /// A delegate that can extract a key from an item in the collection. + /// An OrderedDictionary mapping the extracted keys to their values. + public static OrderedDictionary ToOrderedDictionary(this IEnumerable source, Func keySelector) + { + return ToOrderedDictionary(source, keySelector, null); + } + + /// + /// Creates a new OrderedDictionary from the given collection, using the key selector to extract the key. + /// The key comparer is passed to the OrderedDictionary for comparing the extracted keys. + /// + /// The type of the items in the collection. + /// The type of the key. + /// The items to created the OrderedDictionary from. + /// A delegate that can extract a key from an item in the collection. + /// The key equality comparer to use to compare keys in the dictionary. + /// An OrderedDictionary mapping the extracted keys to their values. + public static OrderedDictionary ToOrderedDictionary( + this IEnumerable source, + Func keySelector, + IEqualityComparer comparer) + { + if (source == null) + { + throw new ArgumentNullException("source"); + } + if (keySelector == null) + { + throw new ArgumentNullException("keySelector"); + } + var dictionary = new OrderedDictionary(comparer); + foreach (TSource item in source) + { + TKey key = keySelector(item); + dictionary.Add(key, item); + } + return dictionary; + } + + #endregion + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/ConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/ConstraintData.cs index 40463ee..b7844c8 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/ConstraintData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/ConstraintData.cs @@ -28,35 +28,37 @@ *****************************************************************************/ using System; -using System.Collections.Generic; namespace Spine3_8_95 { - /// The base class for all constraint datas. - public abstract class ConstraintData { - internal readonly string name; - internal int order; - internal bool skinRequired; + /// The base class for all constraint datas. + public abstract class ConstraintData + { + internal readonly string name; + internal int order; + internal bool skinRequired; - public ConstraintData (string name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; - } + public ConstraintData(string name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + } - /// The constraint's name, which is unique across all constraints in the skeleton of the same type. - public string Name { get { return name; } } + /// The constraint's name, which is unique across all constraints in the skeleton of the same type. + public string Name { get { return name; } } - ///The ordinal of this constraint for the order a skeleton's constraints will be applied by - /// . - public int Order { get { return order; } set { order = value; } } + ///The ordinal of this constraint for the order a skeleton's constraints will be applied by + /// . + public int Order { get { return order; } set { order = value; } } - ///When true, only updates this constraint if the contains - /// this constraint. - /// - public bool SkinRequired { get { return skinRequired; } set { skinRequired = value; } } + ///When true, only updates this constraint if the contains + /// this constraint. + /// + public bool SkinRequired { get { return skinRequired; } set { skinRequired = value; } } - override public string ToString () { - return name; - } - } + override public string ToString() + { + return name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Event.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Event.cs index ac99fef..eaeadb0 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Event.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Event.cs @@ -29,36 +29,40 @@ using System; -namespace Spine3_8_95 { - /// Stores the current pose values for an Event. - public class Event { - internal readonly EventData data; - internal readonly float time; - internal int intValue; - internal float floatValue; - internal string stringValue; - internal float volume; - internal float balance; +namespace Spine3_8_95 +{ + /// Stores the current pose values for an Event. + public class Event + { + internal readonly EventData data; + internal readonly float time; + internal int intValue; + internal float floatValue; + internal string stringValue; + internal float volume; + internal float balance; - public EventData Data { get { return data; } } - /// The animation time this event was keyed. - public float Time { get { return time; } } + public EventData Data { get { return data; } } + /// The animation time this event was keyed. + public float Time { get { return time; } } - public int Int { get { return intValue; } set { intValue = value; } } - public float Float { get { return floatValue; } set { floatValue = value; } } - public string String { get { return stringValue; } set { stringValue = value; } } + public int Int { get { return intValue; } set { intValue = value; } } + public float Float { get { return floatValue; } set { floatValue = value; } } + public string String { get { return stringValue; } set { stringValue = value; } } - public float Volume { get { return volume; } set { volume = value; } } - public float Balance { get { return balance; } set { balance = value; } } + public float Volume { get { return volume; } set { volume = value; } } + public float Balance { get { return balance; } set { balance = value; } } - public Event (float time, EventData data) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - this.time = time; - this.data = data; - } + public Event(float time, EventData data) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + this.time = time; + this.data = data; + } - override public string ToString () { - return this.data.Name; - } - } + override public string ToString() + { + return this.data.Name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/EventData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/EventData.cs index e85007f..98514c3 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/EventData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/EventData.cs @@ -29,28 +29,32 @@ using System; -namespace Spine3_8_95 { - /// Stores the setup pose values for an Event. - public class EventData { - internal string name; +namespace Spine3_8_95 +{ + /// Stores the setup pose values for an Event. + public class EventData + { + internal string name; - /// The name of the event, which is unique across all events in the skeleton. - public string Name { get { return name; } } - public int Int { get; set; } - public float Float { get; set; } - public string @String { get; set; } + /// The name of the event, which is unique across all events in the skeleton. + public string Name { get { return name; } } + public int Int { get; set; } + public float Float { get; set; } + public string @String { get; set; } - public string AudioPath { get; set; } - public float Volume { get; set; } - public float Balance { get; set; } + public string AudioPath { get; set; } + public float Volume { get; set; } + public float Balance { get; set; } - public EventData (string name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; - } + public EventData(string name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + } - override public string ToString () { - return Name; - } - } + override public string ToString() + { + return Name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/ExposedList.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/ExposedList.cs index 20ad6d9..f38a547 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/ExposedList.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/ExposedList.cs @@ -35,607 +35,704 @@ using System.Collections.Generic; using System.Diagnostics; -namespace Spine3_8_95 { - [DebuggerDisplay("Count={Count}")] - public class ExposedList : IEnumerable { - public T[] Items; - public int Count; - private const int DefaultCapacity = 4; - private static readonly T[] EmptyArray = new T[0]; - private int version; - - public ExposedList () { - Items = EmptyArray; - } - - public ExposedList (IEnumerable collection) { - CheckCollection(collection); - - // initialize to needed size (if determinable) - ICollection c = collection as ICollection; - if (c == null) { - Items = EmptyArray; - AddEnumerable(collection); - } else { - Items = new T[c.Count]; - AddCollection(c); - } - } - - public ExposedList (int capacity) { - if (capacity < 0) - throw new ArgumentOutOfRangeException("capacity"); - Items = new T[capacity]; - } - - internal ExposedList (T[] data, int size) { - Items = data; - Count = size; - } - - public void Add (T item) { - // If we check to see if we need to grow before trying to grow - // we can speed things up by 25% - if (Count == Items.Length) - GrowIfNeeded(1); - Items[Count++] = item; - version++; - } - - public void GrowIfNeeded (int addedCount) { - int minimumSize = Count + addedCount; - if (minimumSize > Items.Length) - Capacity = Math.Max(Math.Max(Capacity * 2, DefaultCapacity), minimumSize); - } - - public ExposedList Resize (int newSize) { - int itemsLength = Items.Length; - var oldItems = Items; - if (newSize > itemsLength) { - Array.Resize(ref Items, newSize); -// var newItems = new T[newSize]; -// Array.Copy(oldItems, newItems, Count); -// Items = newItems; - } else if (newSize < itemsLength) { - // Allow nulling of T reference type to allow GC. - for (int i = newSize; i < itemsLength; i++) - oldItems[i] = default(T); - } - Count = newSize; - return this; - } - - public void EnsureCapacity (int min) { - if (Items.Length < min) { - int newCapacity = Items.Length == 0 ? DefaultCapacity : Items.Length * 2; - //if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength; - if (newCapacity < min) newCapacity = min; - Capacity = newCapacity; - } - } - - private void CheckRange (int index, int count) { - if (index < 0) - throw new ArgumentOutOfRangeException("index"); - - if (count < 0) - throw new ArgumentOutOfRangeException("count"); - - if ((uint)index + (uint)count > (uint)Count) - throw new ArgumentException("index and count exceed length of list"); - } - - private void AddCollection (ICollection collection) { - int collectionCount = collection.Count; - if (collectionCount == 0) - return; - - GrowIfNeeded(collectionCount); - collection.CopyTo(Items, Count); - Count += collectionCount; - } - - private void AddEnumerable (IEnumerable enumerable) { - foreach (T t in enumerable) { - Add(t); - } - } - - // Additional overload provided because ExposedList only implements IEnumerable, - // leading to sub-optimal behavior: It grows multiple times as it assumes not - // to know the final size ahead of insertion. - public void AddRange (ExposedList list) { - CheckCollection(list); - - int collectionCount = list.Count; - if (collectionCount == 0) - return; - - GrowIfNeeded(collectionCount); - list.CopyTo(Items, Count); - Count += collectionCount; - - version++; - } - - public void AddRange (IEnumerable collection) { - CheckCollection(collection); - - ICollection c = collection as ICollection; - if (c != null) - AddCollection(c); - else - AddEnumerable(collection); - version++; - } - - public int BinarySearch (T item) { - return Array.BinarySearch(Items, 0, Count, item); - } - - public int BinarySearch (T item, IComparer comparer) { - return Array.BinarySearch(Items, 0, Count, item, comparer); - } - - public int BinarySearch (int index, int count, T item, IComparer comparer) { - CheckRange(index, count); - return Array.BinarySearch(Items, index, count, item, comparer); - } - - public void Clear (bool clearArray = true) { - if (clearArray) - Array.Clear(Items, 0, Items.Length); - - Count = 0; - version++; - } - - public bool Contains (T item) { - return Array.IndexOf(Items, item, 0, Count) != -1; - } - - public ExposedList ConvertAll (Converter converter) { - if (converter == null) - throw new ArgumentNullException("converter"); - ExposedList u = new ExposedList(Count); - for (int i = 0; i < Count; i++) - u.Items[i] = converter(Items[i]); - - u.Count = Count; - return u; - } - - public void CopyTo (T[] array) { - Array.Copy(Items, 0, array, 0, Count); - } - - public void CopyTo (T[] array, int arrayIndex) { - Array.Copy(Items, 0, array, arrayIndex, Count); - } - - public void CopyTo (int index, T[] array, int arrayIndex, int count) { - CheckRange(index, count); - Array.Copy(Items, index, array, arrayIndex, count); - } - - - - public bool Exists (Predicate match) { - CheckMatch(match); - return GetIndex(0, Count, match) != -1; - } - - public T Find (Predicate match) { - CheckMatch(match); - int i = GetIndex(0, Count, match); - return (i != -1) ? Items[i] : default(T); - } - - private static void CheckMatch (Predicate match) { - if (match == null) - throw new ArgumentNullException("match"); - } - - public ExposedList FindAll (Predicate match) { - CheckMatch(match); - return FindAllList(match); - } - - private ExposedList FindAllList (Predicate match) { - ExposedList results = new ExposedList(); - for (int i = 0; i < Count; i++) - if (match(Items[i])) - results.Add(Items[i]); - - return results; - } - - public int FindIndex (Predicate match) { - CheckMatch(match); - return GetIndex(0, Count, match); - } - - public int FindIndex (int startIndex, Predicate match) { - CheckMatch(match); - CheckIndex(startIndex); - return GetIndex(startIndex, Count - startIndex, match); - } - - public int FindIndex (int startIndex, int count, Predicate match) { - CheckMatch(match); - CheckRange(startIndex, count); - return GetIndex(startIndex, count, match); - } - - private int GetIndex (int startIndex, int count, Predicate match) { - int end = startIndex + count; - for (int i = startIndex; i < end; i++) - if (match(Items[i])) - return i; - - return -1; - } - - public T FindLast (Predicate match) { - CheckMatch(match); - int i = GetLastIndex(0, Count, match); - return i == -1 ? default(T) : Items[i]; - } - - public int FindLastIndex (Predicate match) { - CheckMatch(match); - return GetLastIndex(0, Count, match); - } - - public int FindLastIndex (int startIndex, Predicate match) { - CheckMatch(match); - CheckIndex(startIndex); - return GetLastIndex(0, startIndex + 1, match); - } - - public int FindLastIndex (int startIndex, int count, Predicate match) { - CheckMatch(match); - int start = startIndex - count + 1; - CheckRange(start, count); - return GetLastIndex(start, count, match); - } - - private int GetLastIndex (int startIndex, int count, Predicate match) { - // unlike FindLastIndex, takes regular params for search range - for (int i = startIndex + count; i != startIndex; ) - if (match(Items[--i])) - return i; - return -1; - } - - public void ForEach (Action action) { - if (action == null) - throw new ArgumentNullException("action"); - for (int i = 0; i < Count; i++) - action(Items[i]); - } - - public Enumerator GetEnumerator () { - return new Enumerator(this); - } - - public ExposedList GetRange (int index, int count) { - CheckRange(index, count); - T[] tmpArray = new T[count]; - Array.Copy(Items, index, tmpArray, 0, count); - return new ExposedList(tmpArray, count); - } - - public int IndexOf (T item) { - return Array.IndexOf(Items, item, 0, Count); - } - - public int IndexOf (T item, int index) { - CheckIndex(index); - return Array.IndexOf(Items, item, index, Count - index); - } - - public int IndexOf (T item, int index, int count) { - if (index < 0) - throw new ArgumentOutOfRangeException("index"); - - if (count < 0) - throw new ArgumentOutOfRangeException("count"); - - if ((uint)index + (uint)count > (uint)Count) - throw new ArgumentOutOfRangeException("index and count exceed length of list"); - - return Array.IndexOf(Items, item, index, count); - } - - private void Shift (int start, int delta) { - if (delta < 0) - start -= delta; - - if (start < Count) - Array.Copy(Items, start, Items, start + delta, Count - start); - - Count += delta; - - if (delta < 0) - Array.Clear(Items, Count, -delta); - } - - private void CheckIndex (int index) { - if (index < 0 || (uint)index > (uint)Count) - throw new ArgumentOutOfRangeException("index"); - } - - public void Insert (int index, T item) { - CheckIndex(index); - if (Count == Items.Length) - GrowIfNeeded(1); - Shift(index, 1); - Items[index] = item; - version++; - } - - private void CheckCollection (IEnumerable collection) { - if (collection == null) - throw new ArgumentNullException("collection"); - } - - public void InsertRange (int index, IEnumerable collection) { - CheckCollection(collection); - CheckIndex(index); - if (collection == this) { - T[] buffer = new T[Count]; - CopyTo(buffer, 0); - GrowIfNeeded(Count); - Shift(index, buffer.Length); - Array.Copy(buffer, 0, Items, index, buffer.Length); - } else { - ICollection c = collection as ICollection; - if (c != null) - InsertCollection(index, c); - else - InsertEnumeration(index, collection); - } - version++; - } - - private void InsertCollection (int index, ICollection collection) { - int collectionCount = collection.Count; - GrowIfNeeded(collectionCount); - - Shift(index, collectionCount); - collection.CopyTo(Items, index); - } - - private void InsertEnumeration (int index, IEnumerable enumerable) { - foreach (T t in enumerable) - Insert(index++, t); - } - - public int LastIndexOf (T item) { - return Array.LastIndexOf(Items, item, Count - 1, Count); - } - - public int LastIndexOf (T item, int index) { - CheckIndex(index); - return Array.LastIndexOf(Items, item, index, index + 1); - } - - public int LastIndexOf (T item, int index, int count) { - if (index < 0) - throw new ArgumentOutOfRangeException("index", index, "index is negative"); - - if (count < 0) - throw new ArgumentOutOfRangeException("count", count, "count is negative"); - - if (index - count + 1 < 0) - throw new ArgumentOutOfRangeException("count", count, "count is too large"); - - return Array.LastIndexOf(Items, item, index, count); - } - - public bool Remove (T item) { - int loc = IndexOf(item); - if (loc != -1) - RemoveAt(loc); - - return loc != -1; - } - - public int RemoveAll (Predicate match) { - CheckMatch(match); - int i = 0; - int j = 0; - - // Find the first item to remove - for (i = 0; i < Count; i++) - if (match(Items[i])) - break; - - if (i == Count) - return 0; - - version++; - - // Remove any additional items - for (j = i + 1; j < Count; j++) { - if (!match(Items[j])) - Items[i++] = Items[j]; - } - if (j - i > 0) - Array.Clear(Items, i, j - i); - - Count = i; - return (j - i); - } - - public void RemoveAt (int index) { - if (index < 0 || (uint)index >= (uint)Count) - throw new ArgumentOutOfRangeException("index"); - Shift(index, -1); - Array.Clear(Items, Count, 1); - version++; - } - - // Spine Added Method - // Based on Stack.Pop(); https://referencesource.microsoft.com/#mscorlib/system/collections/stack.cs - /// Pops the last item of the list. If the list is empty, Pop throws an InvalidOperationException. - public T Pop () { - if (Count == 0) - throw new InvalidOperationException("List is empty. Nothing to pop."); - - int i = Count - 1; - T item = Items[i]; - Items[i] = default(T); - Count--; - version++; - return item; - } - - public void RemoveRange (int index, int count) { - CheckRange(index, count); - if (count > 0) { - Shift(index, -count); - Array.Clear(Items, Count, count); - version++; - } - } - - public void Reverse () { - Array.Reverse(Items, 0, Count); - version++; - } - - public void Reverse (int index, int count) { - CheckRange(index, count); - Array.Reverse(Items, index, count); - version++; - } - - public void Sort () { - Array.Sort(Items, 0, Count, Comparer.Default); - version++; - } - - public void Sort (IComparer comparer) { - Array.Sort(Items, 0, Count, comparer); - version++; - } - - public void Sort (Comparison comparison) { - Array.Sort(Items, comparison); - version++; - } - - public void Sort (int index, int count, IComparer comparer) { - CheckRange(index, count); - Array.Sort(Items, index, count, comparer); - version++; - } - - public T[] ToArray () { - T[] t = new T[Count]; - Array.Copy(Items, t, Count); - - return t; - } - - public void TrimExcess () { - Capacity = Count; - } - - public bool TrueForAll (Predicate match) { - CheckMatch(match); - - for (int i = 0; i < Count; i++) - if (!match(Items[i])) - return false; - - return true; - } - - public int Capacity { - get { - return Items.Length; - } - set { - if ((uint)value < (uint)Count) - throw new ArgumentOutOfRangeException(); - - Array.Resize(ref Items, value); - } - } - - #region Interface implementations. - - IEnumerator IEnumerable.GetEnumerator () { - return GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator () { - return GetEnumerator(); - } - - #endregion - - public struct Enumerator : IEnumerator, IDisposable { - private ExposedList l; - private int next; - private int ver; - private T current; - - internal Enumerator (ExposedList l) - : this() { - this.l = l; - ver = l.version; - } - - public void Dispose () { - l = null; - } - - private void VerifyState () { - if (l == null) - throw new ObjectDisposedException(GetType().FullName); - if (ver != l.version) - throw new InvalidOperationException( - "Collection was modified; enumeration operation may not execute."); - } - - public bool MoveNext () { - VerifyState(); - - if (next < 0) - return false; - - if (next < l.Count) { - current = l.Items[next++]; - return true; - } - - next = -1; - return false; - } - - public T Current { - get { - return current; - } - } - - void IEnumerator.Reset () { - VerifyState(); - next = 0; - } - - object IEnumerator.Current { - get { - VerifyState(); - if (next <= 0) - throw new InvalidOperationException(); - return current; - } - } - } - } +namespace Spine3_8_95 +{ + [DebuggerDisplay("Count={Count}")] + public class ExposedList : IEnumerable + { + public T[] Items; + public int Count; + private const int DefaultCapacity = 4; + private static readonly T[] EmptyArray = new T[0]; + private int version; + + public ExposedList() + { + Items = EmptyArray; + } + + public ExposedList(IEnumerable collection) + { + CheckCollection(collection); + + // initialize to needed size (if determinable) + ICollection c = collection as ICollection; + if (c == null) + { + Items = EmptyArray; + AddEnumerable(collection); + } + else + { + Items = new T[c.Count]; + AddCollection(c); + } + } + + public ExposedList(int capacity) + { + if (capacity < 0) + throw new ArgumentOutOfRangeException("capacity"); + Items = new T[capacity]; + } + + internal ExposedList(T[] data, int size) + { + Items = data; + Count = size; + } + + public void Add(T item) + { + // If we check to see if we need to grow before trying to grow + // we can speed things up by 25% + if (Count == Items.Length) + GrowIfNeeded(1); + Items[Count++] = item; + version++; + } + + public void GrowIfNeeded(int addedCount) + { + int minimumSize = Count + addedCount; + if (minimumSize > Items.Length) + Capacity = Math.Max(Math.Max(Capacity * 2, DefaultCapacity), minimumSize); + } + + public ExposedList Resize(int newSize) + { + int itemsLength = Items.Length; + var oldItems = Items; + if (newSize > itemsLength) + { + Array.Resize(ref Items, newSize); + // var newItems = new T[newSize]; + // Array.Copy(oldItems, newItems, Count); + // Items = newItems; + } + else if (newSize < itemsLength) + { + // Allow nulling of T reference type to allow GC. + for (int i = newSize; i < itemsLength; i++) + oldItems[i] = default(T); + } + Count = newSize; + return this; + } + + public void EnsureCapacity(int min) + { + if (Items.Length < min) + { + int newCapacity = Items.Length == 0 ? DefaultCapacity : Items.Length * 2; + //if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength; + if (newCapacity < min) newCapacity = min; + Capacity = newCapacity; + } + } + + private void CheckRange(int index, int count) + { + if (index < 0) + throw new ArgumentOutOfRangeException("index"); + + if (count < 0) + throw new ArgumentOutOfRangeException("count"); + + if ((uint)index + (uint)count > (uint)Count) + throw new ArgumentException("index and count exceed length of list"); + } + + private void AddCollection(ICollection collection) + { + int collectionCount = collection.Count; + if (collectionCount == 0) + return; + + GrowIfNeeded(collectionCount); + collection.CopyTo(Items, Count); + Count += collectionCount; + } + + private void AddEnumerable(IEnumerable enumerable) + { + foreach (T t in enumerable) + { + Add(t); + } + } + + // Additional overload provided because ExposedList only implements IEnumerable, + // leading to sub-optimal behavior: It grows multiple times as it assumes not + // to know the final size ahead of insertion. + public void AddRange(ExposedList list) + { + CheckCollection(list); + + int collectionCount = list.Count; + if (collectionCount == 0) + return; + + GrowIfNeeded(collectionCount); + list.CopyTo(Items, Count); + Count += collectionCount; + + version++; + } + + public void AddRange(IEnumerable collection) + { + CheckCollection(collection); + + ICollection c = collection as ICollection; + if (c != null) + AddCollection(c); + else + AddEnumerable(collection); + version++; + } + + public int BinarySearch(T item) + { + return Array.BinarySearch(Items, 0, Count, item); + } + + public int BinarySearch(T item, IComparer comparer) + { + return Array.BinarySearch(Items, 0, Count, item, comparer); + } + + public int BinarySearch(int index, int count, T item, IComparer comparer) + { + CheckRange(index, count); + return Array.BinarySearch(Items, index, count, item, comparer); + } + + public void Clear(bool clearArray = true) + { + if (clearArray) + Array.Clear(Items, 0, Items.Length); + + Count = 0; + version++; + } + + public bool Contains(T item) + { + return Array.IndexOf(Items, item, 0, Count) != -1; + } + + public ExposedList ConvertAll(Converter converter) + { + if (converter == null) + throw new ArgumentNullException("converter"); + ExposedList u = new ExposedList(Count); + for (int i = 0; i < Count; i++) + u.Items[i] = converter(Items[i]); + + u.Count = Count; + return u; + } + + public void CopyTo(T[] array) + { + Array.Copy(Items, 0, array, 0, Count); + } + + public void CopyTo(T[] array, int arrayIndex) + { + Array.Copy(Items, 0, array, arrayIndex, Count); + } + + public void CopyTo(int index, T[] array, int arrayIndex, int count) + { + CheckRange(index, count); + Array.Copy(Items, index, array, arrayIndex, count); + } + + + + public bool Exists(Predicate match) + { + CheckMatch(match); + return GetIndex(0, Count, match) != -1; + } + + public T Find(Predicate match) + { + CheckMatch(match); + int i = GetIndex(0, Count, match); + return (i != -1) ? Items[i] : default(T); + } + + private static void CheckMatch(Predicate match) + { + if (match == null) + throw new ArgumentNullException("match"); + } + + public ExposedList FindAll(Predicate match) + { + CheckMatch(match); + return FindAllList(match); + } + + private ExposedList FindAllList(Predicate match) + { + ExposedList results = new ExposedList(); + for (int i = 0; i < Count; i++) + if (match(Items[i])) + results.Add(Items[i]); + + return results; + } + + public int FindIndex(Predicate match) + { + CheckMatch(match); + return GetIndex(0, Count, match); + } + + public int FindIndex(int startIndex, Predicate match) + { + CheckMatch(match); + CheckIndex(startIndex); + return GetIndex(startIndex, Count - startIndex, match); + } + + public int FindIndex(int startIndex, int count, Predicate match) + { + CheckMatch(match); + CheckRange(startIndex, count); + return GetIndex(startIndex, count, match); + } + + private int GetIndex(int startIndex, int count, Predicate match) + { + int end = startIndex + count; + for (int i = startIndex; i < end; i++) + if (match(Items[i])) + return i; + + return -1; + } + + public T FindLast(Predicate match) + { + CheckMatch(match); + int i = GetLastIndex(0, Count, match); + return i == -1 ? default(T) : Items[i]; + } + + public int FindLastIndex(Predicate match) + { + CheckMatch(match); + return GetLastIndex(0, Count, match); + } + + public int FindLastIndex(int startIndex, Predicate match) + { + CheckMatch(match); + CheckIndex(startIndex); + return GetLastIndex(0, startIndex + 1, match); + } + + public int FindLastIndex(int startIndex, int count, Predicate match) + { + CheckMatch(match); + int start = startIndex - count + 1; + CheckRange(start, count); + return GetLastIndex(start, count, match); + } + + private int GetLastIndex(int startIndex, int count, Predicate match) + { + // unlike FindLastIndex, takes regular params for search range + for (int i = startIndex + count; i != startIndex;) + if (match(Items[--i])) + return i; + return -1; + } + + public void ForEach(Action action) + { + if (action == null) + throw new ArgumentNullException("action"); + for (int i = 0; i < Count; i++) + action(Items[i]); + } + + public Enumerator GetEnumerator() + { + return new Enumerator(this); + } + + public ExposedList GetRange(int index, int count) + { + CheckRange(index, count); + T[] tmpArray = new T[count]; + Array.Copy(Items, index, tmpArray, 0, count); + return new ExposedList(tmpArray, count); + } + + public int IndexOf(T item) + { + return Array.IndexOf(Items, item, 0, Count); + } + + public int IndexOf(T item, int index) + { + CheckIndex(index); + return Array.IndexOf(Items, item, index, Count - index); + } + + public int IndexOf(T item, int index, int count) + { + if (index < 0) + throw new ArgumentOutOfRangeException("index"); + + if (count < 0) + throw new ArgumentOutOfRangeException("count"); + + if ((uint)index + (uint)count > (uint)Count) + throw new ArgumentOutOfRangeException("index and count exceed length of list"); + + return Array.IndexOf(Items, item, index, count); + } + + private void Shift(int start, int delta) + { + if (delta < 0) + start -= delta; + + if (start < Count) + Array.Copy(Items, start, Items, start + delta, Count - start); + + Count += delta; + + if (delta < 0) + Array.Clear(Items, Count, -delta); + } + + private void CheckIndex(int index) + { + if (index < 0 || (uint)index > (uint)Count) + throw new ArgumentOutOfRangeException("index"); + } + + public void Insert(int index, T item) + { + CheckIndex(index); + if (Count == Items.Length) + GrowIfNeeded(1); + Shift(index, 1); + Items[index] = item; + version++; + } + + private void CheckCollection(IEnumerable collection) + { + if (collection == null) + throw new ArgumentNullException("collection"); + } + + public void InsertRange(int index, IEnumerable collection) + { + CheckCollection(collection); + CheckIndex(index); + if (collection == this) + { + T[] buffer = new T[Count]; + CopyTo(buffer, 0); + GrowIfNeeded(Count); + Shift(index, buffer.Length); + Array.Copy(buffer, 0, Items, index, buffer.Length); + } + else + { + ICollection c = collection as ICollection; + if (c != null) + InsertCollection(index, c); + else + InsertEnumeration(index, collection); + } + version++; + } + + private void InsertCollection(int index, ICollection collection) + { + int collectionCount = collection.Count; + GrowIfNeeded(collectionCount); + + Shift(index, collectionCount); + collection.CopyTo(Items, index); + } + + private void InsertEnumeration(int index, IEnumerable enumerable) + { + foreach (T t in enumerable) + Insert(index++, t); + } + + public int LastIndexOf(T item) + { + return Array.LastIndexOf(Items, item, Count - 1, Count); + } + + public int LastIndexOf(T item, int index) + { + CheckIndex(index); + return Array.LastIndexOf(Items, item, index, index + 1); + } + + public int LastIndexOf(T item, int index, int count) + { + if (index < 0) + throw new ArgumentOutOfRangeException("index", index, "index is negative"); + + if (count < 0) + throw new ArgumentOutOfRangeException("count", count, "count is negative"); + + if (index - count + 1 < 0) + throw new ArgumentOutOfRangeException("count", count, "count is too large"); + + return Array.LastIndexOf(Items, item, index, count); + } + + public bool Remove(T item) + { + int loc = IndexOf(item); + if (loc != -1) + RemoveAt(loc); + + return loc != -1; + } + + public int RemoveAll(Predicate match) + { + CheckMatch(match); + int i = 0; + int j = 0; + + // Find the first item to remove + for (i = 0; i < Count; i++) + if (match(Items[i])) + break; + + if (i == Count) + return 0; + + version++; + + // Remove any additional items + for (j = i + 1; j < Count; j++) + { + if (!match(Items[j])) + Items[i++] = Items[j]; + } + if (j - i > 0) + Array.Clear(Items, i, j - i); + + Count = i; + return (j - i); + } + + public void RemoveAt(int index) + { + if (index < 0 || (uint)index >= (uint)Count) + throw new ArgumentOutOfRangeException("index"); + Shift(index, -1); + Array.Clear(Items, Count, 1); + version++; + } + + // Spine Added Method + // Based on Stack.Pop(); https://referencesource.microsoft.com/#mscorlib/system/collections/stack.cs + /// Pops the last item of the list. If the list is empty, Pop throws an InvalidOperationException. + public T Pop() + { + if (Count == 0) + throw new InvalidOperationException("List is empty. Nothing to pop."); + + int i = Count - 1; + T item = Items[i]; + Items[i] = default(T); + Count--; + version++; + return item; + } + + public void RemoveRange(int index, int count) + { + CheckRange(index, count); + if (count > 0) + { + Shift(index, -count); + Array.Clear(Items, Count, count); + version++; + } + } + + public void Reverse() + { + Array.Reverse(Items, 0, Count); + version++; + } + + public void Reverse(int index, int count) + { + CheckRange(index, count); + Array.Reverse(Items, index, count); + version++; + } + + public void Sort() + { + Array.Sort(Items, 0, Count, Comparer.Default); + version++; + } + + public void Sort(IComparer comparer) + { + Array.Sort(Items, 0, Count, comparer); + version++; + } + + public void Sort(Comparison comparison) + { + Array.Sort(Items, comparison); + version++; + } + + public void Sort(int index, int count, IComparer comparer) + { + CheckRange(index, count); + Array.Sort(Items, index, count, comparer); + version++; + } + + public T[] ToArray() + { + T[] t = new T[Count]; + Array.Copy(Items, t, Count); + + return t; + } + + public void TrimExcess() + { + Capacity = Count; + } + + public bool TrueForAll(Predicate match) + { + CheckMatch(match); + + for (int i = 0; i < Count; i++) + if (!match(Items[i])) + return false; + + return true; + } + + public int Capacity + { + get + { + return Items.Length; + } + set + { + if ((uint)value < (uint)Count) + throw new ArgumentOutOfRangeException(); + + Array.Resize(ref Items, value); + } + } + + #region Interface implementations. + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion + + public struct Enumerator : IEnumerator, IDisposable + { + private ExposedList l; + private int next; + private int ver; + private T current; + + internal Enumerator(ExposedList l) + : this() + { + this.l = l; + ver = l.version; + } + + public void Dispose() + { + l = null; + } + + private void VerifyState() + { + if (l == null) + throw new ObjectDisposedException(GetType().FullName); + if (ver != l.version) + throw new InvalidOperationException( + "Collection was modified; enumeration operation may not execute."); + } + + public bool MoveNext() + { + VerifyState(); + + if (next < 0) + return false; + + if (next < l.Count) + { + current = l.Items[next++]; + return true; + } + + next = -1; + return false; + } + + public T Current + { + get + { + return current; + } + } + + void IEnumerator.Reset() + { + VerifyState(); + next = 0; + } + + object IEnumerator.Current + { + get + { + VerifyState(); + if (next <= 0) + throw new InvalidOperationException(); + return current; + } + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/IUpdatable.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/IUpdatable.cs index d4527cf..7f29428 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/IUpdatable.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/IUpdatable.cs @@ -27,16 +27,18 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine3_8_95 { +namespace Spine3_8_95 +{ - ///The interface for items updated by . - public interface IUpdatable { - void Update (); + ///The interface for items updated by . + public interface IUpdatable + { + void Update(); - ///Returns false when this item has not been updated because a skin is required and the active - /// skin does not contain this item. - /// - /// - bool Active { get; } - } + ///Returns false when this item has not been updated because a skin is required and the active + /// skin does not contain this item. + /// + /// + bool Active { get; } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/IkConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/IkConstraint.cs index a04b50e..ceb233b 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/IkConstraint.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/IkConstraint.cs @@ -29,337 +29,387 @@ using System; -namespace Spine3_8_95 { - /// - /// - /// Stores the current pose for an IK constraint. An IK constraint adjusts the rotation of 1 or 2 constrained bones so the tip of - /// the last bone is as close to the target bone as possible. - /// - /// See IK constraints in the Spine User Guide. - /// - public class IkConstraint : IUpdatable { - internal IkConstraintData data; - internal ExposedList bones = new ExposedList(); - internal Bone target; - internal int bendDirection; - internal bool compress, stretch; - internal float mix = 1, softness; +namespace Spine3_8_95 +{ + /// + /// + /// Stores the current pose for an IK constraint. An IK constraint adjusts the rotation of 1 or 2 constrained bones so the tip of + /// the last bone is as close to the target bone as possible. + /// + /// See IK constraints in the Spine User Guide. + /// + public class IkConstraint : IUpdatable + { + internal IkConstraintData data; + internal ExposedList bones = new ExposedList(); + internal Bone target; + internal int bendDirection; + internal bool compress, stretch; + internal float mix = 1, softness; - internal bool active; + internal bool active; - public IkConstraint (IkConstraintData data, Skeleton skeleton) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.data = data; - mix = data.mix; - softness = data.softness; - bendDirection = data.bendDirection; - compress = data.compress; - stretch = data.stretch; + public IkConstraint(IkConstraintData data, Skeleton skeleton) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + mix = data.mix; + softness = data.softness; + bendDirection = data.bendDirection; + compress = data.compress; + stretch = data.stretch; - bones = new ExposedList(data.bones.Count); - foreach (BoneData boneData in data.bones) - bones.Add(skeleton.FindBone(boneData.name)); - target = skeleton.FindBone(data.target.name); - } + bones = new ExposedList(data.bones.Count); + foreach (BoneData boneData in data.bones) + bones.Add(skeleton.FindBone(boneData.name)); + target = skeleton.FindBone(data.target.name); + } - /// Copy constructor. - public IkConstraint (IkConstraint constraint, Skeleton skeleton) { - if (constraint == null) throw new ArgumentNullException("constraint cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); - data = constraint.data; - bones = new ExposedList(constraint.Bones.Count); - foreach (Bone bone in constraint.Bones) - bones.Add(skeleton.Bones.Items[bone.data.index]); - target = skeleton.Bones.Items[constraint.target.data.index]; - mix = constraint.mix; - softness = constraint.softness; - bendDirection = constraint.bendDirection; - compress = constraint.compress; - stretch = constraint.stretch; - } + /// Copy constructor. + public IkConstraint(IkConstraint constraint, Skeleton skeleton) + { + if (constraint == null) throw new ArgumentNullException("constraint cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); + data = constraint.data; + bones = new ExposedList(constraint.Bones.Count); + foreach (Bone bone in constraint.Bones) + bones.Add(skeleton.Bones.Items[bone.data.index]); + target = skeleton.Bones.Items[constraint.target.data.index]; + mix = constraint.mix; + softness = constraint.softness; + bendDirection = constraint.bendDirection; + compress = constraint.compress; + stretch = constraint.stretch; + } - /// Applies the constraint to the constrained bones. - public void Apply () { - Update(); - } + /// Applies the constraint to the constrained bones. + public void Apply() + { + Update(); + } - public void Update () { - Bone target = this.target; - ExposedList bones = this.bones; - switch (bones.Count) { - case 1: - Apply(bones.Items[0], target.worldX, target.worldY, compress, stretch, data.uniform, mix); - break; - case 2: - Apply(bones.Items[0], bones.Items[1], target.worldX, target.worldY, bendDirection, stretch, softness, mix); - break; - } - } + public void Update() + { + Bone target = this.target; + ExposedList bones = this.bones; + switch (bones.Count) + { + case 1: + Apply(bones.Items[0], target.worldX, target.worldY, compress, stretch, data.uniform, mix); + break; + case 2: + Apply(bones.Items[0], bones.Items[1], target.worldX, target.worldY, bendDirection, stretch, softness, mix); + break; + } + } - /// The bones that will be modified by this IK constraint. - public ExposedList Bones { - get { return bones; } - } + /// The bones that will be modified by this IK constraint. + public ExposedList Bones + { + get { return bones; } + } - /// The bone that is the IK target. - public Bone Target { - get { return target; } - set { target = value; } - } + /// The bone that is the IK target. + public Bone Target + { + get { return target; } + set { target = value; } + } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotations. - public float Mix { - get { return mix; } - set { mix = value; } - } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotations. + public float Mix + { + get { return mix; } + set { mix = value; } + } - ///For two bone IK, the distance from the maximum reach of the bones that rotation will slow. - public float Softness { - get { return softness; } - set { softness = value; } - } + ///For two bone IK, the distance from the maximum reach of the bones that rotation will slow. + public float Softness + { + get { return softness; } + set { softness = value; } + } - /// Controls the bend direction of the IK bones, either 1 or -1. - public int BendDirection { - get { return bendDirection; } - set { bendDirection = value; } - } + /// Controls the bend direction of the IK bones, either 1 or -1. + public int BendDirection + { + get { return bendDirection; } + set { bendDirection = value; } + } - /// - /// When true and only a single bone is being constrained, if the target is too close, the bone is scaled to reach it. - public bool Compress { - get { return compress; } - set { compress = value; } - } + /// + /// When true and only a single bone is being constrained, if the target is too close, the bone is scaled to reach it. + public bool Compress + { + get { return compress; } + set { compress = value; } + } - /// - /// When true, if the target is out of range, the parent bone is scaled to reach it. If more than one bone is being constrained - /// and the parent bone has local nonuniform scale, stretch is not applied. - public bool Stretch { - get { return stretch; } - set { stretch = value; } - } + /// + /// When true, if the target is out of range, the parent bone is scaled to reach it. If more than one bone is being constrained + /// and the parent bone has local nonuniform scale, stretch is not applied. + public bool Stretch + { + get { return stretch; } + set { stretch = value; } + } - public bool Active { - get { return active; } - } + public bool Active + { + get { return active; } + } - /// The IK constraint's setup pose data. - public IkConstraintData Data { - get { return data; } - } + /// The IK constraint's setup pose data. + public IkConstraintData Data + { + get { return data; } + } - override public string ToString () { - return data.name; - } + override public string ToString() + { + return data.name; + } - /// Applies 1 bone IK. The target is specified in the world coordinate system. - static public void Apply (Bone bone, float targetX, float targetY, bool compress, bool stretch, bool uniform, - float alpha) { - if (!bone.appliedValid) bone.UpdateAppliedTransform(); - Bone p = bone.parent; + /// Applies 1 bone IK. The target is specified in the world coordinate system. + static public void Apply(Bone bone, float targetX, float targetY, bool compress, bool stretch, bool uniform, + float alpha) + { + if (!bone.appliedValid) bone.UpdateAppliedTransform(); + Bone p = bone.parent; - float pa = p.a, pb = p.b, pc = p.c, pd = p.d; - float rotationIK = -bone.ashearX - bone.arotation; - float tx = 0, ty = 0; + 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.data.transformMode) { - case TransformMode.OnlyTranslation: - tx = targetX - bone.worldX; - ty = targetY - bone.worldY; - break; - case TransformMode.NoRotationOrReflection: { - float s = Math.Abs(pa * pd - pb * pc) / (pa * pa + pc * pc); - float sa = pa / bone.skeleton.ScaleX; - float sc = pc / bone.skeleton.ScaleY; - pb = -sc * s * bone.skeleton.ScaleX; - pd = sa * s * bone.skeleton.ScaleY; - rotationIK += (float)Math.Atan2(pc, pa) * MathUtils.RadDeg; - goto default; // Fall through. - } - default: { - float x = targetX - p.worldX, y = targetY - p.worldY; - float d = pa * pd - pb * pc; - tx = (x * pd - y * pb) / d - bone.ax; - ty = (y * pa - x * pc) / d - bone.ay; + switch (bone.data.transformMode) + { + case TransformMode.OnlyTranslation: + tx = targetX - bone.worldX; + ty = targetY - bone.worldY; break; - } - } + case TransformMode.NoRotationOrReflection: + { + float s = Math.Abs(pa * pd - pb * pc) / (pa * pa + pc * pc); + float sa = pa / bone.skeleton.ScaleX; + float sc = pc / bone.skeleton.ScaleY; + pb = -sc * s * bone.skeleton.ScaleX; + pd = sa * s * bone.skeleton.ScaleY; + rotationIK += (float)Math.Atan2(pc, pa) * MathUtils.RadDeg; + goto default; // Fall through. + } + default: + { + float x = targetX - p.worldX, y = targetY - p.worldY; + float d = pa * pd - pb * pc; + tx = (x * pd - y * pb) / d - bone.ax; + ty = (y * pa - x * pc) / d - bone.ay; + break; + } + } - rotationIK += (float)Math.Atan2(ty, tx) * MathUtils.RadDeg; - if (bone.ascaleX < 0) rotationIK += 180; - if (rotationIK > 180) - rotationIK -= 360; - else if (rotationIK < -180) // - rotationIK += 360; + rotationIK += (float)Math.Atan2(ty, tx) * MathUtils.RadDeg; + if (bone.ascaleX < 0) rotationIK += 180; + if (rotationIK > 180) + rotationIK -= 360; + else if (rotationIK < -180) // + rotationIK += 360; - float sx = bone.ascaleX, sy = bone.ascaleY; - if (compress || stretch) { - switch (bone.data.transformMode) { - case TransformMode.NoScale: + float sx = bone.ascaleX, sy = bone.ascaleY; + if (compress || stretch) + { + switch (bone.data.transformMode) + { + case TransformMode.NoScale: tx = targetX - bone.worldX; ty = targetY - bone.worldY; break; case TransformMode.NoScaleOrReflection: - tx = targetX - bone.worldX; - ty = targetY - bone.worldY; + tx = targetX - bone.worldX; + ty = targetY - bone.worldY; break; - } - float b = bone.data.length * sx, dd = (float)Math.Sqrt(tx * tx + ty * ty); - if ((compress && dd < b) || (stretch && dd > b) && b > 0.0001f) { - float s = (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); - } + } + float b = bone.data.length * sx, dd = (float)Math.Sqrt(tx * tx + ty * ty); + if ((compress && dd < b) || (stretch && dd > b) && b > 0.0001f) + { + float s = (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); + } - /// Applies 2 bone IK. The target is specified in the world coordinate system. - /// A direct descendant of the parent bone. - static public void Apply (Bone parent, Bone child, float targetX, float targetY, int bendDir, bool stretch, float softness, - float alpha) { - if (alpha == 0) { - child.UpdateWorldTransform(); - return; - } - if (!parent.appliedValid) parent.UpdateAppliedTransform(); - if (!child.appliedValid) child.UpdateAppliedTransform(); - float px = parent.ax, py = parent.ay, psx = parent.ascaleX, sx = psx, psy = parent.ascaleY, csx = child.ascaleX; - int os1, os2, s2; - if (psx < 0) { - psx = -psx; - os1 = 180; - s2 = -1; - } else { - os1 = 0; - s2 = 1; - } - if (psy < 0) { - psy = -psy; - s2 = -s2; - } - if (csx < 0) { - csx = -csx; - os2 = 180; - } else - os2 = 0; - float cx = child.ax, cy, cwx, cwy, a = parent.a, b = parent.b, c = parent.c, d = parent.d; - bool u = Math.Abs(psx - psy) <= 0.0001f; - if (!u) { - cy = 0; - cwx = a * cx + parent.worldX; - cwy = c * cx + parent.worldY; - } else { - cy = child.ay; - cwx = a * cx + b * cy + parent.worldX; - cwy = c * cx + d * cy + parent.worldY; - } - Bone pp = parent.parent; - a = pp.a; - b = pp.b; - c = pp.c; - d = pp.d; - float id = 1 / (a * d - b * c), x = cwx - pp.worldX, y = cwy - pp.worldY; - float dx = (x * d - y * b) * id - px, dy = (y * a - x * c) * id - py; - float l1 = (float)Math.Sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2; - if (l1 < 0.0001f) { - 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; - float tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py; - float dd = tx * tx + ty * ty; - if (softness != 0) { - softness *= psx * (csx + 1) / 2; - float td = (float)Math.Sqrt(dd), sd = td - l1 - l2 * psx + softness; - if (sd > 0) { - float p = Math.Min(1, sd / (softness * 2)) - 1; - p = (sd - softness * (1 - p * p)) / td; - tx -= p * tx; - ty -= p * ty; - dd = tx * tx + ty * ty; - } - } - if (u) { - l2 *= psx; - float cos = (dd - l1 * l1 - l2 * l2) / (2 * l1 * l2); - if (cos < -1) - cos = -1; - else if (cos > 1) { - cos = 1; - if (stretch) sx *= ((float)Math.Sqrt(dd) / (l1 + l2) - 1) * alpha + 1; - } - a2 = (float)Math.Acos(cos) * bendDir; - a = l1 + l2 * cos; - b = l2 * (float)Math.Sin(a2); - a1 = (float)Math.Atan2(ty * a - tx * b, tx * a + ty * b); - } else { - a = psx * l2; - b = psy * l2; - float aa = a * a, bb = b * b, ta = (float)Math.Atan2(ty, tx); - c = bb * l1 * l1 + aa * dd - aa * bb; - float c1 = -2 * bb * l1, c2 = bb - aa; - d = c1 * c1 - 4 * c2 * c; - if (d >= 0) { - float q = (float)Math.Sqrt(d); - if (c1 < 0) q = -q; - q = -(c1 + q) / 2; - float r0 = q / c2, r1 = c / q; - float r = Math.Abs(r0) < Math.Abs(r1) ? r0 : r1; - if (r * r <= dd) { - y = (float)Math.Sqrt(dd - r * r) * bendDir; - a1 = ta - (float)Math.Atan2(y, r); - a2 = (float)Math.Atan2(y / psy, (r - l1) / psx); - goto break_outer; // break outer; - } - } - float minAngle = MathUtils.PI, minX = l1 - a, minDist = minX * minX, minY = 0; - float maxAngle = 0, maxX = l1 + a, maxDist = maxX * maxX, maxY = 0; - c = -a * l1 / (aa - bb); - if (c >= -1 && c <= 1) { - c = (float)Math.Acos(c); - x = a * (float)Math.Cos(c) + l1; - y = b * (float)Math.Sin(c); - d = x * x + y * y; - if (d < minDist) { - minAngle = c; - minDist = d; - minX = x; - minY = y; - } - if (d > maxDist) { - maxAngle = c; - maxDist = d; - maxX = x; - maxY = y; - } - } - if (dd <= (minDist + maxDist) / 2) { - a1 = ta - (float)Math.Atan2(minY * bendDir, minX); - a2 = minAngle * bendDir; - } else { - a1 = ta - (float)Math.Atan2(maxY * bendDir, maxX); - a2 = maxAngle * bendDir; - } - } - break_outer: - float os = (float)Math.Atan2(cy, cx) * s2; - float rotation = parent.arotation; - a1 = (a1 - os) * MathUtils.RadDeg + os1 - rotation; - if (a1 > 180) - a1 -= 360; - else if (a1 < -180) a1 += 360; - parent.UpdateWorldTransform(px, py, rotation + a1 * alpha, sx, parent.ascaleY, 0, 0); - rotation = child.arotation; - a2 = ((a2 + os) * MathUtils.RadDeg - child.ashearX) * s2 + os2 - rotation; - if (a2 > 180) - a2 -= 360; - else if (a2 < -180) a2 += 360; - child.UpdateWorldTransform(cx, cy, rotation + a2 * alpha, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY); - } - } + /// Applies 2 bone IK. The target is specified in the world coordinate system. + /// A direct descendant of the parent bone. + static public void Apply(Bone parent, Bone child, float targetX, float targetY, int bendDir, bool stretch, float softness, + float alpha) + { + if (alpha == 0) + { + child.UpdateWorldTransform(); + return; + } + if (!parent.appliedValid) parent.UpdateAppliedTransform(); + if (!child.appliedValid) child.UpdateAppliedTransform(); + float px = parent.ax, py = parent.ay, psx = parent.ascaleX, sx = psx, psy = parent.ascaleY, csx = child.ascaleX; + int os1, os2, s2; + if (psx < 0) + { + psx = -psx; + os1 = 180; + s2 = -1; + } + else + { + os1 = 0; + s2 = 1; + } + if (psy < 0) + { + psy = -psy; + s2 = -s2; + } + if (csx < 0) + { + csx = -csx; + os2 = 180; + } + else + os2 = 0; + float cx = child.ax, cy, cwx, cwy, a = parent.a, b = parent.b, c = parent.c, d = parent.d; + bool u = Math.Abs(psx - psy) <= 0.0001f; + if (!u) + { + cy = 0; + cwx = a * cx + parent.worldX; + cwy = c * cx + parent.worldY; + } + else + { + cy = child.ay; + cwx = a * cx + b * cy + parent.worldX; + cwy = c * cx + d * cy + parent.worldY; + } + Bone pp = parent.parent; + a = pp.a; + b = pp.b; + c = pp.c; + d = pp.d; + float id = 1 / (a * d - b * c), x = cwx - pp.worldX, y = cwy - pp.worldY; + float dx = (x * d - y * b) * id - px, dy = (y * a - x * c) * id - py; + float l1 = (float)Math.Sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2; + if (l1 < 0.0001f) + { + 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; + float tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py; + float dd = tx * tx + ty * ty; + if (softness != 0) + { + softness *= psx * (csx + 1) / 2; + float td = (float)Math.Sqrt(dd), sd = td - l1 - l2 * psx + softness; + if (sd > 0) + { + float p = Math.Min(1, sd / (softness * 2)) - 1; + p = (sd - softness * (1 - p * p)) / td; + tx -= p * tx; + ty -= p * ty; + dd = tx * tx + ty * ty; + } + } + if (u) + { + l2 *= psx; + float cos = (dd - l1 * l1 - l2 * l2) / (2 * l1 * l2); + if (cos < -1) + cos = -1; + else if (cos > 1) + { + cos = 1; + if (stretch) sx *= ((float)Math.Sqrt(dd) / (l1 + l2) - 1) * alpha + 1; + } + a2 = (float)Math.Acos(cos) * bendDir; + a = l1 + l2 * cos; + b = l2 * (float)Math.Sin(a2); + a1 = (float)Math.Atan2(ty * a - tx * b, tx * a + ty * b); + } + else + { + a = psx * l2; + b = psy * l2; + float aa = a * a, bb = b * b, ta = (float)Math.Atan2(ty, tx); + c = bb * l1 * l1 + aa * dd - aa * bb; + float c1 = -2 * bb * l1, c2 = bb - aa; + d = c1 * c1 - 4 * c2 * c; + if (d >= 0) + { + float q = (float)Math.Sqrt(d); + if (c1 < 0) q = -q; + q = -(c1 + q) / 2; + float r0 = q / c2, r1 = c / q; + float r = Math.Abs(r0) < Math.Abs(r1) ? r0 : r1; + if (r * r <= dd) + { + y = (float)Math.Sqrt(dd - r * r) * bendDir; + a1 = ta - (float)Math.Atan2(y, r); + a2 = (float)Math.Atan2(y / psy, (r - l1) / psx); + goto break_outer; // break outer; + } + } + float minAngle = MathUtils.PI, minX = l1 - a, minDist = minX * minX, minY = 0; + float maxAngle = 0, maxX = l1 + a, maxDist = maxX * maxX, maxY = 0; + c = -a * l1 / (aa - bb); + if (c >= -1 && c <= 1) + { + c = (float)Math.Acos(c); + x = a * (float)Math.Cos(c) + l1; + y = b * (float)Math.Sin(c); + d = x * x + y * y; + if (d < minDist) + { + minAngle = c; + minDist = d; + minX = x; + minY = y; + } + if (d > maxDist) + { + maxAngle = c; + maxDist = d; + maxX = x; + maxY = y; + } + } + if (dd <= (minDist + maxDist) / 2) + { + a1 = ta - (float)Math.Atan2(minY * bendDir, minX); + a2 = minAngle * bendDir; + } + else + { + a1 = ta - (float)Math.Atan2(maxY * bendDir, maxX); + a2 = maxAngle * bendDir; + } + } + break_outer: + float os = (float)Math.Atan2(cy, cx) * s2; + float rotation = parent.arotation; + a1 = (a1 - os) * MathUtils.RadDeg + os1 - rotation; + if (a1 > 180) + a1 -= 360; + else if (a1 < -180) a1 += 360; + parent.UpdateWorldTransform(px, py, rotation + a1 * alpha, sx, parent.ascaleY, 0, 0); + rotation = child.arotation; + a2 = ((a2 + os) * MathUtils.RadDeg - child.ashearX) * s2 + os2 - rotation; + if (a2 > 180) + a2 -= 360; + else if (a2 < -180) a2 += 360; + child.UpdateWorldTransform(cx, cy, rotation + a2 * alpha, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/IkConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/IkConstraintData.cs index bec157d..879bd89 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/IkConstraintData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/IkConstraintData.cs @@ -27,73 +27,81 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; -using System.Collections.Generic; +namespace Spine3_8_95 +{ + /// Stores the setup pose for an IkConstraint. + public class IkConstraintData : ConstraintData + { + internal ExposedList bones = new ExposedList(); + internal BoneData target; + internal int bendDirection = 1; + internal bool compress, stretch, uniform; + internal float mix = 1, softness; -namespace Spine3_8_95 { - /// Stores the setup pose for an IkConstraint. - public class IkConstraintData : ConstraintData { - internal ExposedList bones = new ExposedList(); - internal BoneData target; - internal int bendDirection = 1; - internal bool compress, stretch, uniform; - internal float mix = 1, softness; + public IkConstraintData(string name) : base(name) + { + } - public IkConstraintData (string name) : base(name) { - } + /// The bones that are constrained by this IK Constraint. + public ExposedList Bones + { + get { return bones; } + } - /// The bones that are constrained by this IK Constraint. - public ExposedList Bones { - get { return bones; } - } + /// The bone that is the IK target. + public BoneData Target + { + get { return target; } + set { target = value; } + } - /// The bone that is the IK target. - public BoneData Target { - get { return target; } - set { target = value; } - } + /// + /// A percentage (0-1) that controls the mix between the constraint and unconstrained rotations. + public float Mix + { + get { return mix; } + set { mix = value; } + } - /// - /// A percentage (0-1) that controls the mix between the constraint and unconstrained rotations. - public float Mix { - get { return mix; } - set { mix = value; } - } + ///For two bone IK, the distance from the maximum reach of the bones that rotation will slow. + public float Softness + { + get { return softness; } + set { softness = value; } + } - ///For two bone IK, the distance from the maximum reach of the bones that rotation will slow. - public float Softness { - get { return softness; } - set { softness = value; } - } + /// Controls the bend direction of the IK bones, either 1 or -1. + public int BendDirection + { + get { return bendDirection; } + set { bendDirection = value; } + } - /// Controls the bend direction of the IK bones, either 1 or -1. - public int BendDirection { - get { return bendDirection; } - set { bendDirection = value; } - } + /// + /// When true, and only a single bone is being constrained, + /// if the target is too close, the bone is scaled to reach it. + public bool Compress + { + get { return compress; } + set { compress = value; } + } - /// - /// When true, and only a single bone is being constrained, - /// if the target is too close, the bone is scaled to reach it. - public bool Compress { - get { return compress; } - set { compress = value; } - } + /// + /// When true, if the target is out of range, the parent bone is scaled on the X axis to reach it. + /// If the bone has local nonuniform scale, stretching is not applied. + public bool Stretch + { + get { return stretch; } + set { stretch = value; } + } - /// - /// When true, if the target is out of range, the parent bone is scaled on the X axis to reach it. - /// If the bone has local nonuniform scale, stretching is not applied. - public bool Stretch { - get { return stretch; } - set { stretch = value; } - } - - /// - /// When true, only a single bone is being constrained and Compress or Stretch is used, - /// the bone is scaled both on the X and Y axes. - public bool Uniform { - get { return uniform; } - set { uniform = value; } - } - } + /// + /// When true, only a single bone is being constrained and Compress or Stretch is used, + /// the bone is scaled both on the X and Y axes. + public bool Uniform + { + get { return uniform; } + set { uniform = value; } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Json.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Json.cs index a784b17..2d8ad1d 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Json.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Json.cs @@ -28,20 +28,22 @@ *****************************************************************************/ using System; +using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Text; -using System.Collections; -using System.Globalization; -using System.Collections.Generic; -namespace Spine3_8_95 { - public static class Json { - public static object Deserialize (TextReader text) { - var parser = new SharpJson.JsonDecoder(); - parser.parseNumbersAsFloat = true; - return parser.Decode(text.ReadToEnd()); - } - } +namespace Spine3_8_95 +{ + public static class Json + { + public static object Deserialize(TextReader text) + { + var parser = new SharpJson.JsonDecoder(); + parser.parseNumbersAsFloat = true; + return parser.Decode(text.ReadToEnd()); + } + } } /** @@ -72,460 +74,502 @@ public static object Deserialize (TextReader text) { */ namespace Spine3_8_95 { - class Lexer - { - public enum Token { - None, - Null, - True, - False, - Colon, - Comma, - String, - Number, - CurlyOpen, - CurlyClose, - SquaredOpen, - SquaredClose, - }; - - public bool hasError { - get { - return !success; - } - } - - public int lineNumber { - get; - private set; - } - - public bool parseNumbersAsFloat { - get; - set; - } - - char[] json; - int index = 0; - bool success = true; - char[] stringBuffer = new char[4096]; - - public Lexer(string text) - { - Reset(); - - json = text.ToCharArray(); - parseNumbersAsFloat = false; - } - - public void Reset() - { - index = 0; - lineNumber = 1; - success = true; - } - - public string ParseString() - { - int idx = 0; - StringBuilder builder = null; - - SkipWhiteSpaces(); - - // " - char c = json[index++]; - - bool failed = false; - bool complete = false; - - while (!complete && !failed) { - if (index == json.Length) - break; - - c = json[index++]; - if (c == '"') { - complete = true; - break; - } else if (c == '\\') { - if (index == json.Length) - break; - - c = json[index++]; - - switch (c) { - case '"': - stringBuffer[idx++] = '"'; - break; - case '\\': - stringBuffer[idx++] = '\\'; - break; - case '/': - stringBuffer[idx++] = '/'; - break; - case 'b': - stringBuffer[idx++] = '\b'; - break; - case'f': - stringBuffer[idx++] = '\f'; - break; - case 'n': - stringBuffer[idx++] = '\n'; - break; - case 'r': - stringBuffer[idx++] = '\r'; - break; - case 't': - stringBuffer[idx++] = '\t'; - break; - case 'u': - int remainingLength = json.Length - index; - if (remainingLength >= 4) { - var hex = new string(json, index, 4); - - // XXX: handle UTF - stringBuffer[idx++] = (char) Convert.ToInt32(hex, 16); - - // skip 4 chars - index += 4; - } else { - failed = true; - } - break; - } - } else { - stringBuffer[idx++] = c; - } - - if (idx >= stringBuffer.Length) { - if (builder == null) - builder = new StringBuilder(); - - builder.Append(stringBuffer, 0, idx); - idx = 0; - } - } - - if (!complete) { - success = false; - return null; - } - - if (builder != null) - return builder.ToString (); - else - return new string (stringBuffer, 0, idx); - } - - string GetNumberString() - { - SkipWhiteSpaces(); - - int lastIndex = GetLastIndexOfNumber(index); - int charLength = (lastIndex - index) + 1; - - var result = new string (json, index, charLength); - - index = lastIndex + 1; - - return result; - } - - public float ParseFloatNumber() - { - float number; - var str = GetNumberString (); - - if (!float.TryParse (str, NumberStyles.Float, CultureInfo.InvariantCulture, out number)) - return 0; - - return number; - } - - public double ParseDoubleNumber() - { - double number; - var str = GetNumberString (); - - if (!double.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out number)) - return 0; - - return number; - } - - int GetLastIndexOfNumber(int index) - { - int lastIndex; - - for (lastIndex = index; lastIndex < json.Length; lastIndex++) { - char ch = json[lastIndex]; - - if ((ch < '0' || ch > '9') && ch != '+' && ch != '-' - && ch != '.' && ch != 'e' && ch != 'E') - break; - } - - return lastIndex - 1; - } - - void SkipWhiteSpaces() - { - for (; index < json.Length; index++) { - char ch = json[index]; - - if (ch == '\n') - lineNumber++; - - if (!char.IsWhiteSpace(json[index])) - break; - } - } - - public Token LookAhead() - { - SkipWhiteSpaces(); - - int savedIndex = index; - return NextToken(json, ref savedIndex); - } - - public Token NextToken() - { - SkipWhiteSpaces(); - return NextToken(json, ref index); - } - - static Token NextToken(char[] json, ref int index) - { - if (index == json.Length) - return Token.None; - - char c = json[index++]; - - switch (c) { - case '{': - return Token.CurlyOpen; - case '}': - return Token.CurlyClose; - case '[': - return Token.SquaredOpen; - case ']': - return Token.SquaredClose; - case ',': - return Token.Comma; - case '"': - return Token.String; - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - case '-': - return Token.Number; - case ':': - return Token.Colon; - } - - index--; - - int remainingLength = json.Length - index; - - // false - if (remainingLength >= 5) { - if (json[index] == 'f' && - json[index + 1] == 'a' && - json[index + 2] == 'l' && - json[index + 3] == 's' && - json[index + 4] == 'e') { - index += 5; - return Token.False; - } - } - - // true - if (remainingLength >= 4) { - if (json[index] == 't' && - json[index + 1] == 'r' && - json[index + 2] == 'u' && - json[index + 3] == 'e') { - index += 4; - return Token.True; - } - } - - // null - if (remainingLength >= 4) { - if (json[index] == 'n' && - json[index + 1] == 'u' && - json[index + 2] == 'l' && - json[index + 3] == 'l') { - index += 4; - return Token.Null; - } - } - - return Token.None; - } - } - - public class JsonDecoder - { - public string errorMessage { - get; - private set; - } - - public bool parseNumbersAsFloat { - get; - set; - } - - Lexer lexer; - - public JsonDecoder() - { - errorMessage = null; - parseNumbersAsFloat = false; - } - - public object Decode(string text) - { - errorMessage = null; - - lexer = new Lexer(text); - lexer.parseNumbersAsFloat = parseNumbersAsFloat; - - return ParseValue(); - } - - public static object DecodeText(string text) - { - var builder = new JsonDecoder(); - return builder.Decode(text); - } - - IDictionary ParseObject() - { - var table = new Dictionary(); - - // { - lexer.NextToken(); - - while (true) { - var token = lexer.LookAhead(); - - switch (token) { - case Lexer.Token.None: - TriggerError("Invalid token"); - return null; - case Lexer.Token.Comma: - lexer.NextToken(); - break; - case Lexer.Token.CurlyClose: - lexer.NextToken(); - return table; - default: - // name - string name = EvalLexer(lexer.ParseString()); - - if (errorMessage != null) - return null; - - // : - token = lexer.NextToken(); - - if (token != Lexer.Token.Colon) { - TriggerError("Invalid token; expected ':'"); - return null; - } - - // value - object value = ParseValue(); - - if (errorMessage != null) - return null; - - table[name] = value; - break; - } - } - - //return null; // Unreachable code - } - - IList ParseArray() - { - var array = new List(); - - // [ - lexer.NextToken(); - - while (true) { - var token = lexer.LookAhead(); - - switch (token) { - case Lexer.Token.None: - TriggerError("Invalid token"); - return null; - case Lexer.Token.Comma: - lexer.NextToken(); - break; - case Lexer.Token.SquaredClose: - lexer.NextToken(); - return array; - default: - object value = ParseValue(); - - if (errorMessage != null) - return null; - - array.Add(value); - break; - } - } - - //return null; // Unreachable code - } - - object ParseValue() - { - switch (lexer.LookAhead()) { - case Lexer.Token.String: - return EvalLexer(lexer.ParseString()); - case Lexer.Token.Number: - if (parseNumbersAsFloat) - return EvalLexer(lexer.ParseFloatNumber()); - else - return EvalLexer(lexer.ParseDoubleNumber()); - case Lexer.Token.CurlyOpen: - return ParseObject(); - case Lexer.Token.SquaredOpen: - return ParseArray(); - case Lexer.Token.True: - lexer.NextToken(); - return true; - case Lexer.Token.False: - lexer.NextToken(); - return false; - case Lexer.Token.Null: - lexer.NextToken(); - return null; - case Lexer.Token.None: - break; - } - - TriggerError("Unable to parse value"); - return null; - } - - void TriggerError(string message) - { - errorMessage = string.Format("Error: '{0}' at line {1}", - message, lexer.lineNumber); - } - - T EvalLexer(T value) - { - if (lexer.hasError) - TriggerError("Lexical error ocurred"); - - return value; - } - } + class Lexer + { + public enum Token + { + None, + Null, + True, + False, + Colon, + Comma, + String, + Number, + CurlyOpen, + CurlyClose, + SquaredOpen, + SquaredClose, + }; + + public bool hasError + { + get + { + return !success; + } + } + + public int lineNumber + { + get; + private set; + } + + public bool parseNumbersAsFloat + { + get; + set; + } + + char[] json; + int index = 0; + bool success = true; + char[] stringBuffer = new char[4096]; + + public Lexer(string text) + { + Reset(); + + json = text.ToCharArray(); + parseNumbersAsFloat = false; + } + + public void Reset() + { + index = 0; + lineNumber = 1; + success = true; + } + + public string ParseString() + { + int idx = 0; + StringBuilder builder = null; + + SkipWhiteSpaces(); + + // " + char c = json[index++]; + + bool failed = false; + bool complete = false; + + while (!complete && !failed) + { + if (index == json.Length) + break; + + c = json[index++]; + if (c == '"') + { + complete = true; + break; + } + else if (c == '\\') + { + if (index == json.Length) + break; + + c = json[index++]; + + switch (c) + { + case '"': + stringBuffer[idx++] = '"'; + break; + case '\\': + stringBuffer[idx++] = '\\'; + break; + case '/': + stringBuffer[idx++] = '/'; + break; + case 'b': + stringBuffer[idx++] = '\b'; + break; + case 'f': + stringBuffer[idx++] = '\f'; + break; + case 'n': + stringBuffer[idx++] = '\n'; + break; + case 'r': + stringBuffer[idx++] = '\r'; + break; + case 't': + stringBuffer[idx++] = '\t'; + break; + case 'u': + int remainingLength = json.Length - index; + if (remainingLength >= 4) + { + var hex = new string(json, index, 4); + + // XXX: handle UTF + stringBuffer[idx++] = (char)Convert.ToInt32(hex, 16); + + // skip 4 chars + index += 4; + } + else + { + failed = true; + } + break; + } + } + else + { + stringBuffer[idx++] = c; + } + + if (idx >= stringBuffer.Length) + { + if (builder == null) + builder = new StringBuilder(); + + builder.Append(stringBuffer, 0, idx); + idx = 0; + } + } + + if (!complete) + { + success = false; + return null; + } + + if (builder != null) + return builder.ToString(); + else + return new string(stringBuffer, 0, idx); + } + + string GetNumberString() + { + SkipWhiteSpaces(); + + int lastIndex = GetLastIndexOfNumber(index); + int charLength = (lastIndex - index) + 1; + + var result = new string(json, index, charLength); + + index = lastIndex + 1; + + return result; + } + + public float ParseFloatNumber() + { + float number; + var str = GetNumberString(); + + if (!float.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out number)) + return 0; + + return number; + } + + public double ParseDoubleNumber() + { + double number; + var str = GetNumberString(); + + if (!double.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out number)) + return 0; + + return number; + } + + int GetLastIndexOfNumber(int index) + { + int lastIndex; + + for (lastIndex = index; lastIndex < json.Length; lastIndex++) + { + char ch = json[lastIndex]; + + if ((ch < '0' || ch > '9') && ch != '+' && ch != '-' + && ch != '.' && ch != 'e' && ch != 'E') + break; + } + + return lastIndex - 1; + } + + void SkipWhiteSpaces() + { + for (; index < json.Length; index++) + { + char ch = json[index]; + + if (ch == '\n') + lineNumber++; + + if (!char.IsWhiteSpace(json[index])) + break; + } + } + + public Token LookAhead() + { + SkipWhiteSpaces(); + + int savedIndex = index; + return NextToken(json, ref savedIndex); + } + + public Token NextToken() + { + SkipWhiteSpaces(); + return NextToken(json, ref index); + } + + static Token NextToken(char[] json, ref int index) + { + if (index == json.Length) + return Token.None; + + char c = json[index++]; + + switch (c) + { + case '{': + return Token.CurlyOpen; + case '}': + return Token.CurlyClose; + case '[': + return Token.SquaredOpen; + case ']': + return Token.SquaredClose; + case ',': + return Token.Comma; + case '"': + return Token.String; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + return Token.Number; + case ':': + return Token.Colon; + } + + index--; + + int remainingLength = json.Length - index; + + // false + if (remainingLength >= 5) + { + if (json[index] == 'f' && + json[index + 1] == 'a' && + json[index + 2] == 'l' && + json[index + 3] == 's' && + json[index + 4] == 'e') + { + index += 5; + return Token.False; + } + } + + // true + if (remainingLength >= 4) + { + if (json[index] == 't' && + json[index + 1] == 'r' && + json[index + 2] == 'u' && + json[index + 3] == 'e') + { + index += 4; + return Token.True; + } + } + + // null + if (remainingLength >= 4) + { + if (json[index] == 'n' && + json[index + 1] == 'u' && + json[index + 2] == 'l' && + json[index + 3] == 'l') + { + index += 4; + return Token.Null; + } + } + + return Token.None; + } + } + + public class JsonDecoder + { + public string errorMessage + { + get; + private set; + } + + public bool parseNumbersAsFloat + { + get; + set; + } + + Lexer lexer; + + public JsonDecoder() + { + errorMessage = null; + parseNumbersAsFloat = false; + } + + public object Decode(string text) + { + errorMessage = null; + + lexer = new Lexer(text); + lexer.parseNumbersAsFloat = parseNumbersAsFloat; + + return ParseValue(); + } + + public static object DecodeText(string text) + { + var builder = new JsonDecoder(); + return builder.Decode(text); + } + + IDictionary ParseObject() + { + var table = new Dictionary(); + + // { + lexer.NextToken(); + + while (true) + { + var token = lexer.LookAhead(); + + switch (token) + { + case Lexer.Token.None: + TriggerError("Invalid token"); + return null; + case Lexer.Token.Comma: + lexer.NextToken(); + break; + case Lexer.Token.CurlyClose: + lexer.NextToken(); + return table; + default: + // name + string name = EvalLexer(lexer.ParseString()); + + if (errorMessage != null) + return null; + + // : + token = lexer.NextToken(); + + if (token != Lexer.Token.Colon) + { + TriggerError("Invalid token; expected ':'"); + return null; + } + + // value + object value = ParseValue(); + + if (errorMessage != null) + return null; + + table[name] = value; + break; + } + } + + //return null; // Unreachable code + } + + IList ParseArray() + { + var array = new List(); + + // [ + lexer.NextToken(); + + while (true) + { + var token = lexer.LookAhead(); + + switch (token) + { + case Lexer.Token.None: + TriggerError("Invalid token"); + return null; + case Lexer.Token.Comma: + lexer.NextToken(); + break; + case Lexer.Token.SquaredClose: + lexer.NextToken(); + return array; + default: + object value = ParseValue(); + + if (errorMessage != null) + return null; + + array.Add(value); + break; + } + } + + //return null; // Unreachable code + } + + object ParseValue() + { + switch (lexer.LookAhead()) + { + case Lexer.Token.String: + return EvalLexer(lexer.ParseString()); + case Lexer.Token.Number: + if (parseNumbersAsFloat) + return EvalLexer(lexer.ParseFloatNumber()); + else + return EvalLexer(lexer.ParseDoubleNumber()); + case Lexer.Token.CurlyOpen: + return ParseObject(); + case Lexer.Token.SquaredOpen: + return ParseArray(); + case Lexer.Token.True: + lexer.NextToken(); + return true; + case Lexer.Token.False: + lexer.NextToken(); + return false; + case Lexer.Token.Null: + lexer.NextToken(); + return null; + case Lexer.Token.None: + break; + } + + TriggerError("Unable to parse value"); + return null; + } + + void TriggerError(string message) + { + errorMessage = string.Format("Error: '{0}' at line {1}", + message, lexer.lineNumber); + } + + T EvalLexer(T value) + { + if (lexer.hasError) + TriggerError("Lexical error ocurred"); + + return value; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/MathUtils.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/MathUtils.cs index 542f88e..96bd9ff 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/MathUtils.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/MathUtils.cs @@ -31,16 +31,18 @@ using System; -namespace Spine3_8_95 { - public static class MathUtils { - public const float PI = 3.1415927f; - public const float PI2 = PI * 2; - public const float RadDeg = 180f / PI; - public const float DegRad = PI / 180; - - static Random random = new Random(); - - #if USE_FAST_SIN_COS_ATAN2_APPROXIMATIONS +namespace Spine3_8_95 +{ + public static class MathUtils + { + public const float PI = 3.1415927f; + public const float PI2 = PI * 2; + public const float RadDeg = 180f / PI; + public const float DegRad = PI / 180; + + static Random random = new Random(); + +#if USE_FAST_SIN_COS_ATAN2_APPROXIMATIONS const int SIN_BITS = 14; // 16KB. Adjust for accuracy. const int SIN_MASK = ~(-1 << SIN_BITS); const int SIN_COUNT = SIN_MASK + 1; @@ -94,80 +96,96 @@ static public float Atan2 (float y, float x) { atan = PI / 2 - z / (z * z + 0.28f); return y < 0f ? atan - PI : atan; } - #else - /// Returns the sine of a given angle in radians. - static public float Sin (float radians) { - return (float)Math.Sin(radians); - } - - /// Returns the cosine of a given angle in radians. - static public float Cos (float radians) { - return (float)Math.Cos(radians); - } - - /// Returns the sine of a given angle in degrees. - static public float SinDeg (float degrees) { - return (float)Math.Sin(degrees * DegRad); - } - - /// Returns the cosine of a given angle in degrees. - static public float CosDeg (float degrees) { - return (float)Math.Cos(degrees * DegRad); - } - - /// Returns the atan2 using Math.Atan2. - static public float Atan2 (float y, float x) { - return (float)Math.Atan2(y, x); - } - #endif - static public float Clamp (float value, float min, float max) { - if (value < min) return min; - if (value > max) return max; - return value; - } - - static public float RandomTriangle(float min, float max) { - return RandomTriangle(min, max, (min + max) * 0.5f); - } - - static public float RandomTriangle(float min, float max, float mode) { - float u = (float)random.NextDouble(); - float d = max - min; - if (u <= (mode - min) / d) return min + (float)Math.Sqrt(u * d * (mode - min)); - return max - (float)Math.Sqrt((1 - u) * d * (max - mode)); - } - } - - public abstract class IInterpolation { - public static IInterpolation Pow2 = new Pow(2); - public static IInterpolation Pow2Out = new PowOut(2); - - protected abstract float Apply(float a); - - public float Apply(float start, float end, float a) { - return start + (end - start) * Apply(a); - } - } - - public class Pow: IInterpolation { - public float Power { get; set; } - - public Pow(float power) { - Power = power; - } - - protected override float Apply(float a) { - if (a <= 0.5f) return (float)Math.Pow(a * 2, Power) / 2; - return (float)Math.Pow((a - 1) * 2, Power) / (Power % 2 == 0 ? -2 : 2) + 1; - } - } - - public class PowOut : Pow { - public PowOut(float power) : base(power) { - } - - protected override float Apply(float a) { - return (float)Math.Pow(a - 1, Power) * (Power % 2 == 0 ? -1 : 1) + 1; - } - } +#else + /// Returns the sine of a given angle in radians. + static public float Sin(float radians) + { + return (float)Math.Sin(radians); + } + + /// Returns the cosine of a given angle in radians. + static public float Cos(float radians) + { + return (float)Math.Cos(radians); + } + + /// Returns the sine of a given angle in degrees. + static public float SinDeg(float degrees) + { + return (float)Math.Sin(degrees * DegRad); + } + + /// Returns the cosine of a given angle in degrees. + static public float CosDeg(float degrees) + { + return (float)Math.Cos(degrees * DegRad); + } + + /// Returns the atan2 using Math.Atan2. + static public float Atan2(float y, float x) + { + return (float)Math.Atan2(y, x); + } +#endif + static public float Clamp(float value, float min, float max) + { + if (value < min) return min; + if (value > max) return max; + return value; + } + + static public float RandomTriangle(float min, float max) + { + return RandomTriangle(min, max, (min + max) * 0.5f); + } + + static public float RandomTriangle(float min, float max, float mode) + { + float u = (float)random.NextDouble(); + float d = max - min; + if (u <= (mode - min) / d) return min + (float)Math.Sqrt(u * d * (mode - min)); + return max - (float)Math.Sqrt((1 - u) * d * (max - mode)); + } + } + + public abstract class IInterpolation + { + public static IInterpolation Pow2 = new Pow(2); + public static IInterpolation Pow2Out = new PowOut(2); + + protected abstract float Apply(float a); + + public float Apply(float start, float end, float a) + { + return start + (end - start) * Apply(a); + } + } + + public class Pow : IInterpolation + { + public float Power { get; set; } + + public Pow(float power) + { + Power = power; + } + + protected override float Apply(float a) + { + if (a <= 0.5f) return (float)Math.Pow(a * 2, Power) / 2; + return (float)Math.Pow((a - 1) * 2, Power) / (Power % 2 == 0 ? -2 : 2) + 1; + } + } + + public class PowOut : Pow + { + public PowOut(float power) : base(power) + { + } + + protected override float Apply(float a) + { + return (float)Math.Pow(a - 1, Power) * (Power % 2 == 0 ? -1 : 1) + 1; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/PathConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/PathConstraint.cs index b6087b2..b5b6c48 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/PathConstraint.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/PathConstraint.cs @@ -29,439 +29,502 @@ using System; -namespace Spine3_8_95 { +namespace Spine3_8_95 +{ - /// - /// - /// Stores the current pose for a path constraint. A path constraint adjusts the rotation, translation, and scale of the - /// constrained bones so they follow a {@link PathAttachment}. - /// - /// See Path constraints in the Spine User Guide. - /// - public class PathConstraint : IUpdatable { - const int NONE = -1, BEFORE = -2, AFTER = -3; - const float Epsilon = 0.00001f; + /// + /// + /// Stores the current pose for a path constraint. A path constraint adjusts the rotation, translation, and scale of the + /// constrained bones so they follow a {@link PathAttachment}. + /// + /// See Path constraints in the Spine User Guide. + /// + public class PathConstraint : IUpdatable + { + const int NONE = -1, BEFORE = -2, AFTER = -3; + const float Epsilon = 0.00001f; - internal PathConstraintData data; - internal ExposedList bones; - internal Slot target; - internal float position, spacing, rotateMix, translateMix; + internal PathConstraintData data; + internal ExposedList bones; + internal Slot target; + internal float position, spacing, rotateMix, translateMix; - internal bool active; + internal bool active; - internal ExposedList spaces = new ExposedList(), positions = new ExposedList(); - internal ExposedList world = new ExposedList(), curves = new ExposedList(), lengths = new ExposedList(); - internal float[] segments = new float[10]; + internal ExposedList spaces = new ExposedList(), positions = new ExposedList(); + internal ExposedList world = new ExposedList(), curves = new ExposedList(), lengths = new ExposedList(); + internal float[] segments = new float[10]; - public PathConstraint (PathConstraintData data, Skeleton skeleton) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.data = data; - bones = new ExposedList(data.Bones.Count); - foreach (BoneData boneData in data.bones) - bones.Add(skeleton.FindBone(boneData.name)); - target = skeleton.FindSlot(data.target.name); - position = data.position; - spacing = data.spacing; - rotateMix = data.rotateMix; - translateMix = data.translateMix; - } + public PathConstraint(PathConstraintData data, Skeleton skeleton) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + bones = new ExposedList(data.Bones.Count); + foreach (BoneData boneData in data.bones) + bones.Add(skeleton.FindBone(boneData.name)); + target = skeleton.FindSlot(data.target.name); + position = data.position; + spacing = data.spacing; + rotateMix = data.rotateMix; + translateMix = data.translateMix; + } - /// Copy constructor. - public PathConstraint (PathConstraint constraint, Skeleton skeleton) { - if (constraint == null) throw new ArgumentNullException("constraint cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); - data = constraint.data; - bones = new ExposedList(constraint.Bones.Count); - foreach (Bone bone in constraint.Bones) - bones.Add(skeleton.Bones.Items[bone.data.index]); - target = skeleton.slots.Items[constraint.target.data.index]; - position = constraint.position; - spacing = constraint.spacing; - rotateMix = constraint.rotateMix; - translateMix = constraint.translateMix; - } + /// Copy constructor. + public PathConstraint(PathConstraint constraint, Skeleton skeleton) + { + if (constraint == null) throw new ArgumentNullException("constraint cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); + data = constraint.data; + bones = new ExposedList(constraint.Bones.Count); + foreach (Bone bone in constraint.Bones) + bones.Add(skeleton.Bones.Items[bone.data.index]); + target = skeleton.slots.Items[constraint.target.data.index]; + position = constraint.position; + spacing = constraint.spacing; + rotateMix = constraint.rotateMix; + translateMix = constraint.translateMix; + } - /// Applies the constraint to the constrained bones. - public void Apply () { - Update(); - } + /// Applies the constraint to the constrained bones. + public void Apply() + { + Update(); + } - public void Update () { - PathAttachment attachment = target.Attachment as PathAttachment; - if (attachment == null) return; + public void Update() + { + PathAttachment attachment = target.Attachment as PathAttachment; + if (attachment == null) return; - float rotateMix = this.rotateMix, translateMix = this.translateMix; - bool translate = translateMix > 0, rotate = rotateMix > 0; - if (!translate && !rotate) return; + float rotateMix = this.rotateMix, translateMix = this.translateMix; + bool translate = translateMix > 0, rotate = rotateMix > 0; + if (!translate && !rotate) return; - PathConstraintData data = this.data; - bool percentSpacing = data.spacingMode == SpacingMode.Percent; - RotateMode rotateMode = data.rotateMode; - bool tangents = rotateMode == RotateMode.Tangent, scale = rotateMode == RotateMode.ChainScale; - int boneCount = this.bones.Count, spacesCount = tangents ? boneCount : boneCount + 1; - Bone[] bonesItems = this.bones.Items; - ExposedList spaces = this.spaces.Resize(spacesCount), lengths = null; - float spacing = this.spacing; - if (scale || !percentSpacing) { - if (scale) lengths = this.lengths.Resize(boneCount); - bool lengthSpacing = data.spacingMode == SpacingMode.Length; - for (int i = 0, n = spacesCount - 1; i < n;) { - Bone bone = bonesItems[i]; - float setupLength = bone.data.length; - if (setupLength < PathConstraint.Epsilon) { - if (scale) lengths.Items[i] = 0; - spaces.Items[++i] = 0; - } else if (percentSpacing) { - if (scale) { - float x = setupLength * bone.a, y = setupLength * bone.c; - float length = (float)Math.Sqrt(x * x + y * y); - lengths.Items[i] = length; - } - spaces.Items[++i] = spacing; - } else { - float x = setupLength * bone.a, y = setupLength * bone.c; - float length = (float)Math.Sqrt(x * x + y * y); - if (scale) lengths.Items[i] = length; - spaces.Items[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length / setupLength; - } - } - } else { - for (int i = 1; i < spacesCount; i++) - spaces.Items[i] = spacing; - } + PathConstraintData data = this.data; + bool percentSpacing = data.spacingMode == SpacingMode.Percent; + RotateMode rotateMode = data.rotateMode; + bool tangents = rotateMode == RotateMode.Tangent, scale = rotateMode == RotateMode.ChainScale; + int boneCount = this.bones.Count, spacesCount = tangents ? boneCount : boneCount + 1; + Bone[] bonesItems = this.bones.Items; + ExposedList spaces = this.spaces.Resize(spacesCount), lengths = null; + float spacing = this.spacing; + if (scale || !percentSpacing) + { + if (scale) lengths = this.lengths.Resize(boneCount); + bool lengthSpacing = data.spacingMode == SpacingMode.Length; + for (int i = 0, n = spacesCount - 1; i < n;) + { + Bone bone = bonesItems[i]; + float setupLength = bone.data.length; + if (setupLength < PathConstraint.Epsilon) + { + if (scale) lengths.Items[i] = 0; + spaces.Items[++i] = 0; + } + else if (percentSpacing) + { + if (scale) + { + float x = setupLength * bone.a, y = setupLength * bone.c; + float length = (float)Math.Sqrt(x * x + y * y); + lengths.Items[i] = length; + } + spaces.Items[++i] = spacing; + } + else + { + float x = setupLength * bone.a, y = setupLength * bone.c; + float length = (float)Math.Sqrt(x * x + y * y); + if (scale) lengths.Items[i] = length; + spaces.Items[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length / setupLength; + } + } + } + else + { + for (int i = 1; i < spacesCount; i++) + spaces.Items[i] = spacing; + } - float[] positions = ComputeWorldPositions(attachment, spacesCount, tangents, - data.positionMode == PositionMode.Percent, percentSpacing); - float boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation; - bool tip; - if (offsetRotation == 0) { - tip = rotateMode == RotateMode.Chain; - } else { - tip = false; - Bone p = target.bone; - offsetRotation *= p.a * p.d - p.b * p.c > 0 ? MathUtils.DegRad : -MathUtils.DegRad; - } - for (int i = 0, p = 3; i < boneCount; i++, p += 3) { - Bone bone = bonesItems[i]; - bone.worldX += (boneX - bone.worldX) * translateMix; - bone.worldY += (boneY - bone.worldY) * translateMix; - float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY; - if (scale) { - float length = lengths.Items[i]; - if (length >= PathConstraint.Epsilon) { - float s = ((float)Math.Sqrt(dx * dx + dy * dy) / length - 1) * rotateMix + 1; - bone.a *= s; - bone.c *= s; - } - } - boneX = x; - boneY = y; - if (rotate) { - 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.Items[i + 1] < PathConstraint.Epsilon) - r = positions[p + 2]; - else - r = MathUtils.Atan2(dy, dx); - r -= MathUtils.Atan2(c, a); - if (tip) { - cos = MathUtils.Cos(r); - sin = MathUtils.Sin(r); - float length = bone.data.length; - boneX += (length * (cos * a - sin * c) - dx) * rotateMix; - boneY += (length * (sin * a + cos * c) - dy) * rotateMix; - } else - r += offsetRotation; - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) // - r += MathUtils.PI2; - r *= rotateMix; - cos = MathUtils.Cos(r); - sin = MathUtils.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.appliedValid = false; - } - } + float[] positions = ComputeWorldPositions(attachment, spacesCount, tangents, + data.positionMode == PositionMode.Percent, percentSpacing); + float boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation; + bool tip; + if (offsetRotation == 0) + { + tip = rotateMode == RotateMode.Chain; + } + else + { + tip = false; + Bone p = target.bone; + offsetRotation *= p.a * p.d - p.b * p.c > 0 ? MathUtils.DegRad : -MathUtils.DegRad; + } + for (int i = 0, p = 3; i < boneCount; i++, p += 3) + { + Bone bone = bonesItems[i]; + bone.worldX += (boneX - bone.worldX) * translateMix; + bone.worldY += (boneY - bone.worldY) * translateMix; + float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY; + if (scale) + { + float length = lengths.Items[i]; + if (length >= PathConstraint.Epsilon) + { + float s = ((float)Math.Sqrt(dx * dx + dy * dy) / length - 1) * rotateMix + 1; + bone.a *= s; + bone.c *= s; + } + } + boneX = x; + boneY = y; + if (rotate) + { + 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.Items[i + 1] < PathConstraint.Epsilon) + r = positions[p + 2]; + else + r = MathUtils.Atan2(dy, dx); + r -= MathUtils.Atan2(c, a); + if (tip) + { + cos = MathUtils.Cos(r); + sin = MathUtils.Sin(r); + float length = bone.data.length; + boneX += (length * (cos * a - sin * c) - dx) * rotateMix; + boneY += (length * (sin * a + cos * c) - dy) * rotateMix; + } + else + r += offsetRotation; + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) // + r += MathUtils.PI2; + r *= rotateMix; + cos = MathUtils.Cos(r); + sin = MathUtils.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.appliedValid = false; + } + } - float[] ComputeWorldPositions (PathAttachment path, int spacesCount, bool tangents, bool percentPosition, - bool percentSpacing) { + float[] ComputeWorldPositions(PathAttachment path, int spacesCount, bool tangents, bool percentPosition, + bool percentSpacing) + { - Slot target = this.target; - float position = this.position; - float[] spacesItems = this.spaces.Items, output = this.positions.Resize(spacesCount * 3 + 2).Items, world; - bool closed = path.Closed; - int verticesLength = path.WorldVerticesLength, curveCount = verticesLength / 6, prevCurve = NONE; - float pathLength = 0; + Slot target = this.target; + float position = this.position; + float[] spacesItems = this.spaces.Items, output = this.positions.Resize(spacesCount * 3 + 2).Items, world; + bool closed = path.Closed; + int verticesLength = path.WorldVerticesLength, curveCount = verticesLength / 6, prevCurve = NONE; + float pathLength = 0; - if (!path.ConstantSpeed) { - float[] lengths = path.Lengths; - curveCount -= closed ? 1 : 2; - pathLength = lengths[curveCount]; - if (percentPosition) position *= pathLength; - if (percentSpacing) { - for (int i = 1; i < spacesCount; i++) - spacesItems[i] *= pathLength; - } - world = this.world.Resize(8).Items; - for (int i = 0, o = 0, curve = 0; i < spacesCount; i++, o += 3) { - float space = spacesItems[i]; - position += space; - float p = position; + if (!path.ConstantSpeed) + { + float[] lengths = path.Lengths; + curveCount -= closed ? 1 : 2; + pathLength = lengths[curveCount]; + if (percentPosition) position *= pathLength; + if (percentSpacing) + { + for (int i = 1; i < spacesCount; i++) + spacesItems[i] *= pathLength; + } + world = this.world.Resize(8).Items; + for (int i = 0, o = 0, curve = 0; i < spacesCount; i++, o += 3) + { + float space = spacesItems[i]; + position += space; + float p = position; - if (closed) { - 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, 2); - } - AddBeforePosition(p, world, 0, output, o); - continue; - } else if (p > pathLength) { - if (prevCurve != AFTER) { - prevCurve = AFTER; - path.ComputeWorldVertices(target, verticesLength - 6, 4, world, 0, 2); - } - AddAfterPosition(p - pathLength, world, 0, output, o); - continue; - } + if (closed) + { + 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, 2); + } + AddBeforePosition(p, world, 0, output, o); + continue; + } + else if (p > pathLength) + { + if (prevCurve != AFTER) + { + prevCurve = AFTER; + path.ComputeWorldVertices(target, verticesLength - 6, 4, world, 0, 2); + } + AddAfterPosition(p - pathLength, world, 0, output, 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, 2); - path.ComputeWorldVertices(target, 0, 4, world, 4, 2); - } else - path.ComputeWorldVertices(target, curve * 6 + 2, 8, world, 0, 2); - } - AddCurvePosition(p, world[0], world[1], world[2], world[3], world[4], world[5], world[6], world[7], output, o, - tangents || (i > 0 && space < PathConstraint.Epsilon)); - } - return output; - } + // 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, 2); + path.ComputeWorldVertices(target, 0, 4, world, 4, 2); + } + else + path.ComputeWorldVertices(target, curve * 6 + 2, 8, world, 0, 2); + } + AddCurvePosition(p, world[0], world[1], world[2], world[3], world[4], world[5], world[6], world[7], output, o, + tangents || (i > 0 && space < PathConstraint.Epsilon)); + } + return output; + } - // World vertices. - if (closed) { - verticesLength += 2; - world = this.world.Resize(verticesLength).Items; - path.ComputeWorldVertices(target, 2, verticesLength - 4, world, 0, 2); - path.ComputeWorldVertices(target, 0, 2, world, verticesLength - 4, 2); - world[verticesLength - 2] = world[0]; - world[verticesLength - 1] = world[1]; - } else { - curveCount--; - verticesLength -= 4; - world = this.world.Resize(verticesLength).Items; - path.ComputeWorldVertices(target, 2, verticesLength, world, 0, 2); - } + // World vertices. + if (closed) + { + verticesLength += 2; + world = this.world.Resize(verticesLength).Items; + path.ComputeWorldVertices(target, 2, verticesLength - 4, world, 0, 2); + path.ComputeWorldVertices(target, 0, 2, world, verticesLength - 4, 2); + world[verticesLength - 2] = world[0]; + world[verticesLength - 1] = world[1]; + } + else + { + curveCount--; + verticesLength -= 4; + world = this.world.Resize(verticesLength).Items; + path.ComputeWorldVertices(target, 2, verticesLength, world, 0, 2); + } - // Curve lengths. - float[] curves = this.curves.Resize(curveCount).Items; - 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 += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - dfx += ddfx; - dfy += ddfy; - ddfx += dddfx; - ddfy += dddfy; - pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - dfx += ddfx; - dfy += ddfy; - pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - dfx += ddfx + dddfx; - dfy += ddfy + dddfy; - pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - curves[i] = pathLength; - x1 = x2; - y1 = y2; - } - if (percentPosition) - position *= pathLength; - else - position *= pathLength / path.lengths[curveCount - 1]; + // Curve lengths. + float[] curves = this.curves.Resize(curveCount).Items; + 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 += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx; + dfy += ddfy; + pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx + dddfx; + dfy += ddfy + dddfy; + pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + curves[i] = pathLength; + x1 = x2; + y1 = y2; + } + if (percentPosition) + position *= pathLength; + else + position *= pathLength / path.lengths[curveCount - 1]; - if (percentSpacing) { - for (int i = 1; i < spacesCount; i++) - spacesItems[i] *= pathLength; - } + if (percentSpacing) + { + for (int i = 1; i < spacesCount; i++) + spacesItems[i] *= pathLength; + } - float[] segments = this.segments; - float curveLength = 0; - for (int i = 0, o = 0, curve = 0, segment = 0; i < spacesCount; i++, o += 3) { - float space = spacesItems[i]; - position += space; - float p = position; + float[] segments = this.segments; + float curveLength = 0; + for (int i = 0, o = 0, curve = 0, segment = 0; i < spacesCount; i++, o += 3) + { + float space = spacesItems[i]; + position += space; + float p = position; - if (closed) { - p %= pathLength; - if (p < 0) p += pathLength; - curve = 0; - } else if (p < 0) { - AddBeforePosition(p, world, 0, output, o); - continue; - } else if (p > pathLength) { - AddAfterPosition(p - pathLength, world, verticesLength - 4, output, o); - continue; - } + if (closed) + { + p %= pathLength; + if (p < 0) p += pathLength; + curve = 0; + } + else if (p < 0) + { + AddBeforePosition(p, world, 0, output, o); + continue; + } + else if (p > pathLength) + { + AddAfterPosition(p - pathLength, world, verticesLength - 4, output, 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; - } + // 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 = (float)Math.Sqrt(dfx * dfx + dfy * dfy); - segments[0] = curveLength; - for (ii = 1; ii < 8; ii++) { - dfx += ddfx; - dfy += ddfy; - ddfx += dddfx; - ddfy += dddfy; - curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - segments[ii] = curveLength; - } - dfx += ddfx; - dfy += ddfy; - curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - segments[8] = curveLength; - dfx += ddfx + dddfx; - dfy += ddfy + dddfy; - curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - segments[9] = curveLength; - segment = 0; - } + // 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 = (float)Math.Sqrt(dfx * dfx + dfy * dfy); + segments[0] = curveLength; + for (ii = 1; ii < 8; ii++) + { + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + segments[ii] = curveLength; + } + dfx += ddfx; + dfy += ddfy; + curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + segments[8] = curveLength; + dfx += ddfx + dddfx; + dfy += ddfy + dddfy; + curveLength += (float)Math.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, output, o, tangents || (i > 0 && space < PathConstraint.Epsilon)); - } - return output; - } + // 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, output, o, tangents || (i > 0 && space < PathConstraint.Epsilon)); + } + return output; + } - static void AddBeforePosition (float p, float[] temp, int i, float[] output, int o) { - float x1 = temp[i], y1 = temp[i + 1], dx = temp[i + 2] - x1, dy = temp[i + 3] - y1, r = MathUtils.Atan2(dy, dx); - output[o] = x1 + p * MathUtils.Cos(r); - output[o + 1] = y1 + p * MathUtils.Sin(r); - output[o + 2] = r; - } + static void AddBeforePosition(float p, float[] temp, int i, float[] output, int o) + { + float x1 = temp[i], y1 = temp[i + 1], dx = temp[i + 2] - x1, dy = temp[i + 3] - y1, r = MathUtils.Atan2(dy, dx); + output[o] = x1 + p * MathUtils.Cos(r); + output[o + 1] = y1 + p * MathUtils.Sin(r); + output[o + 2] = r; + } - static void AddAfterPosition (float p, float[] temp, int i, float[] output, int o) { - float x1 = temp[i + 2], y1 = temp[i + 3], dx = x1 - temp[i], dy = y1 - temp[i + 1], r = MathUtils.Atan2(dy, dx); - output[o] = x1 + p * MathUtils.Cos(r); - output[o + 1] = y1 + p * MathUtils.Sin(r); - output[o + 2] = r; - } + static void AddAfterPosition(float p, float[] temp, int i, float[] output, int o) + { + float x1 = temp[i + 2], y1 = temp[i + 3], dx = x1 - temp[i], dy = y1 - temp[i + 1], r = MathUtils.Atan2(dy, dx); + output[o] = x1 + p * MathUtils.Cos(r); + output[o + 1] = y1 + p * MathUtils.Sin(r); + output[o + 2] = r; + } - static void AddCurvePosition (float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2, - float[] output, int o, bool tangents) { - if (p < PathConstraint.Epsilon || float.IsNaN(p)) { - output[o] = x1; - output[o + 1] = y1; - output[o + 2] = (float)Math.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.001f) - output[o + 2] = (float)Math.Atan2(cy1 - y1, cx1 - x1); - else - output[o + 2] = (float)Math.Atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt)); - } - } + static void AddCurvePosition(float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2, + float[] output, int o, bool tangents) + { + if (p < PathConstraint.Epsilon || float.IsNaN(p)) + { + output[o] = x1; + output[o + 1] = y1; + output[o + 2] = (float)Math.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.001f) + output[o + 2] = (float)Math.Atan2(cy1 - y1, cx1 - x1); + else + output[o + 2] = (float)Math.Atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt)); + } + } - /// The position along the path. - public float Position { get { return position; } set { position = value; } } - /// The spacing between bones. - public float Spacing { get { return spacing; } set { spacing = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotations. - public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained translations. - public float TranslateMix { get { return translateMix; } set { translateMix = value; } } - /// The bones that will be modified by this path constraint. - public ExposedList Bones { get { return bones; } } - /// The slot whose path attachment will be used to constrained the bones. - public Slot Target { get { return target; } set { target = value; } } - public bool Active { get { return active; } } - /// The path constraint's setup pose data. - public PathConstraintData Data { get { return data; } } - } + /// The position along the path. + public float Position { get { return position; } set { position = value; } } + /// The spacing between bones. + public float Spacing { get { return spacing; } set { spacing = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotations. + public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained translations. + public float TranslateMix { get { return translateMix; } set { translateMix = value; } } + /// The bones that will be modified by this path constraint. + public ExposedList Bones { get { return bones; } } + /// The slot whose path attachment will be used to constrained the bones. + public Slot Target { get { return target; } set { target = value; } } + public bool Active { get { return active; } } + /// The path constraint's setup pose data. + public PathConstraintData Data { get { return data; } } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/PathConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/PathConstraintData.cs index 55ac0b8..69be760 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/PathConstraintData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/PathConstraintData.cs @@ -27,42 +27,46 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; +namespace Spine3_8_95 +{ + public class PathConstraintData : ConstraintData + { + internal ExposedList bones = new ExposedList(); + internal SlotData target; + internal PositionMode positionMode; + internal SpacingMode spacingMode; + internal RotateMode rotateMode; + internal float offsetRotation; + internal float position, spacing, rotateMix, translateMix; -namespace Spine3_8_95 { - public class PathConstraintData : ConstraintData { - internal ExposedList bones = new ExposedList(); - internal SlotData target; - internal PositionMode positionMode; - internal SpacingMode spacingMode; - internal RotateMode rotateMode; - internal float offsetRotation; - internal float position, spacing, rotateMix, translateMix; + public PathConstraintData(string name) : base(name) + { + } - public PathConstraintData (string name) : base(name) { - } + public ExposedList Bones { get { return bones; } } + public SlotData Target { get { return target; } set { target = value; } } + public PositionMode PositionMode { get { return positionMode; } set { positionMode = value; } } + public SpacingMode SpacingMode { get { return spacingMode; } set { spacingMode = value; } } + public RotateMode RotateMode { get { return rotateMode; } set { rotateMode = value; } } + public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } + public float Position { get { return position; } set { position = value; } } + public float Spacing { get { return spacing; } set { spacing = value; } } + public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } + public float TranslateMix { get { return translateMix; } set { translateMix = value; } } + } - public ExposedList Bones { get { return bones; } } - public SlotData Target { get { return target; } set { target = value; } } - public PositionMode PositionMode { get { return positionMode; } set { positionMode = value; } } - public SpacingMode SpacingMode { get { return spacingMode; } set { spacingMode = value; } } - public RotateMode RotateMode { get { return rotateMode; } set { rotateMode = value; } } - public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } - public float Position { get { return position; } set { position = value; } } - public float Spacing { get { return spacing; } set { spacing = value; } } - public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } - public float TranslateMix { get { return translateMix; } set { translateMix = value; } } - } + public enum PositionMode + { + Fixed, Percent + } - public enum PositionMode { - Fixed, Percent - } + public enum SpacingMode + { + Length, Fixed, Percent + } - public enum SpacingMode { - Length, Fixed, Percent - } - - public enum RotateMode { - Tangent, Chain, ChainScale - } + public enum RotateMode + { + Tangent, Chain, ChainScale + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Skeleton.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Skeleton.cs index 23502a5..acc93c0 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Skeleton.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Skeleton.cs @@ -28,608 +28,689 @@ *****************************************************************************/ using System; -using System.Collections.Generic; - -namespace Spine3_8_95 { - public class Skeleton { - internal SkeletonData data; - internal ExposedList bones; - internal ExposedList slots; - internal ExposedList drawOrder; - internal ExposedList ikConstraints; - internal ExposedList transformConstraints; - internal ExposedList pathConstraints; - internal ExposedList updateCache = new ExposedList(); - internal ExposedList updateCacheReset = new ExposedList(); - internal Skin skin; - internal float r = 1, g = 1, b = 1, a = 1; - internal float time; - private float scaleX = 1, scaleY = 1; - internal float x, y; - - public SkeletonData Data { get { return data; } } - public ExposedList Bones { get { return bones; } } - public ExposedList UpdateCacheList { get { return updateCache; } } - public ExposedList Slots { get { return slots; } } - public ExposedList DrawOrder { get { return drawOrder; } } - public ExposedList IkConstraints { get { return ikConstraints; } } - public ExposedList PathConstraints { get { return pathConstraints; } } - public ExposedList TransformConstraints { get { return transformConstraints; } } - public Skin Skin { get { return skin; } set { SetSkin(value); } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } - public float Time { get { return time; } set { time = value; } } - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - public float ScaleY { get { return scaleY * (Bone.yDown ? -1 : 1); } set { scaleY = value; } } - - [Obsolete("Use ScaleX instead. FlipX is when ScaleX is negative.")] - public bool FlipX { get { return scaleX < 0; } set { scaleX = value ? -1f : 1f; } } - - [Obsolete("Use ScaleY instead. FlipY is when ScaleY is negative.")] - public bool FlipY { get { return scaleY < 0; } set { scaleY = value ? -1f : 1f; } } - - public Bone RootBone { - get { return bones.Count == 0 ? null : bones.Items[0]; } - } - - public Skeleton (SkeletonData data) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - this.data = data; - - bones = new ExposedList(data.bones.Count); - foreach (BoneData boneData in data.bones) { - Bone bone; - if (boneData.parent == null) { - bone = new Bone(boneData, this, null); - } else { - Bone parent = bones.Items[boneData.parent.index]; - bone = new Bone(boneData, this, parent); - parent.children.Add(bone); - } - bones.Add(bone); - } - - slots = new ExposedList(data.slots.Count); - drawOrder = new ExposedList(data.slots.Count); - foreach (SlotData slotData in data.slots) { - Bone bone = bones.Items[slotData.boneData.index]; - Slot slot = new Slot(slotData, bone); - slots.Add(slot); - drawOrder.Add(slot); - } - - ikConstraints = new ExposedList(data.ikConstraints.Count); - foreach (IkConstraintData ikConstraintData in data.ikConstraints) - ikConstraints.Add(new IkConstraint(ikConstraintData, this)); - - transformConstraints = new ExposedList(data.transformConstraints.Count); - foreach (TransformConstraintData transformConstraintData in data.transformConstraints) - transformConstraints.Add(new TransformConstraint(transformConstraintData, this)); - - pathConstraints = new ExposedList (data.pathConstraints.Count); - foreach (PathConstraintData pathConstraintData in data.pathConstraints) - pathConstraints.Add(new PathConstraint(pathConstraintData, this)); - - UpdateCache(); - UpdateWorldTransform(); - } - - /// Caches information about bones and constraints. Must be called if the is modified or if bones, constraints, or - /// constraints, or weighted path attachments are added or removed. - public void UpdateCache () { - var updateCache = this.updateCache; - updateCache.Clear(); - this.updateCacheReset.Clear(); - - int boneCount = this.bones.Items.Length; - var bones = this.bones; - for (int i = 0; i < boneCount; i++) { - Bone bone = bones.Items[i]; - bone.sorted = bone.data.skinRequired; - bone.active = !bone.sorted; - } - if (skin != null) { - Object[] skinBones = skin.bones.Items; - for (int i = 0, n = skin.bones.Count; i < n; i++) { - Bone bone = (Bone)bones.Items[((BoneData)skinBones[i]).index]; - do { - bone.sorted = false; - bone.active = true; - bone = bone.parent; - } while (bone != null); - } - } - - int ikCount = this.ikConstraints.Count, transformCount = this.transformConstraints.Count, pathCount = this.pathConstraints.Count; - var ikConstraints = this.ikConstraints; - var transformConstraints = this.transformConstraints; - var pathConstraints = this.pathConstraints; - int constraintCount = ikCount + transformCount + pathCount; - //outer: - for (int i = 0; i < constraintCount; i++) { - for (int ii = 0; ii < ikCount; ii++) { - IkConstraint constraint = ikConstraints.Items[ii]; - if (constraint.data.order == i) { - SortIkConstraint(constraint); - goto continue_outer; //continue outer; - } - } - for (int ii = 0; ii < transformCount; ii++) { - TransformConstraint constraint = transformConstraints.Items[ii]; - if (constraint.data.order == i) { - SortTransformConstraint(constraint); - goto continue_outer; //continue outer; - } - } - for (int ii = 0; ii < pathCount; ii++) { - PathConstraint constraint = pathConstraints.Items[ii]; - if (constraint.data.order == i) { - SortPathConstraint(constraint); - goto continue_outer; //continue outer; - } - } - continue_outer: {} - } - - for (int i = 0; i < boneCount; i++) - SortBone(bones.Items[i]); - } - - private void SortIkConstraint (IkConstraint constraint) { - constraint.active = constraint.target.active - && (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data))); - if (!constraint.active) return; - - Bone target = constraint.target; - SortBone(target); - - var constrained = constraint.bones; - Bone parent = constrained.Items[0]; - SortBone(parent); - - if (constrained.Count > 1) { - Bone child = constrained.Items[constrained.Count - 1]; - if (!updateCache.Contains(child)) - updateCacheReset.Add(child); - } - - updateCache.Add(constraint); - - SortReset(parent.children); - constrained.Items[constrained.Count - 1].sorted = true; - } - - private void SortPathConstraint (PathConstraint constraint) { - constraint.active = constraint.target.bone.active - && (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data))); - if (!constraint.active) return; - - Slot slot = constraint.target; - int slotIndex = slot.data.index; - Bone slotBone = slot.bone; - if (skin != null) SortPathConstraintAttachment(skin, slotIndex, slotBone); - if (data.defaultSkin != null && data.defaultSkin != skin) - SortPathConstraintAttachment(data.defaultSkin, slotIndex, slotBone); - - Attachment attachment = slot.attachment; - if (attachment is PathAttachment) SortPathConstraintAttachment(attachment, slotBone); - - var constrained = constraint.bones; - int boneCount = constrained.Count; - for (int i = 0; i < boneCount; i++) - SortBone(constrained.Items[i]); - - updateCache.Add(constraint); - - for (int i = 0; i < boneCount; i++) - SortReset(constrained.Items[i].children); - for (int i = 0; i < boneCount; i++) - constrained.Items[i].sorted = true; - } - - private void SortTransformConstraint (TransformConstraint constraint) { - constraint.active = constraint.target.active - && (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data))); - if (!constraint.active) return; - - SortBone(constraint.target); - - var constrained = constraint.bones; - int boneCount = constrained.Count; - if (constraint.data.local) { - for (int i = 0; i < boneCount; i++) { - Bone child = constrained.Items[i]; - SortBone(child.parent); - if (!updateCache.Contains(child)) updateCacheReset.Add(child); - } - } else { - for (int i = 0; i < boneCount; i++) - SortBone(constrained.Items[i]); - } - - updateCache.Add(constraint); - - for (int i = 0; i < boneCount; i++) - SortReset(constrained.Items[i].children); - for (int i = 0; i < boneCount; i++) - constrained.Items[i].sorted = true; - } - - private void SortPathConstraintAttachment (Skin skin, int slotIndex, Bone slotBone) { - foreach (var entryObj in skin.Attachments.Keys) { - var entry = (Skin.SkinEntry)entryObj; - if (entry.SlotIndex == slotIndex) SortPathConstraintAttachment(entry.Attachment, slotBone); - } - } - - private void SortPathConstraintAttachment (Attachment attachment, Bone slotBone) { - if (!(attachment is PathAttachment)) return; - int[] pathBones = ((PathAttachment)attachment).bones; - if (pathBones == null) - SortBone(slotBone); - else { - var bones = this.bones; - for (int i = 0, n = pathBones.Length; i < n;) { - int nn = pathBones[i++]; - nn += i; - while (i < nn) - SortBone(bones.Items[pathBones[i++]]); - } - } - } - - private void SortBone (Bone bone) { - if (bone.sorted) return; - Bone parent = bone.parent; - if (parent != null) SortBone(parent); - bone.sorted = true; - updateCache.Add(bone); - } - - private static void SortReset (ExposedList bones) { - var bonesItems = bones.Items; - for (int i = 0, n = bones.Count; i < n; i++) { - Bone bone = bonesItems[i]; - if (!bone.active) continue; - if (bone.sorted) SortReset(bone.children); - bone.sorted = false; - } - } - - /// Updates the world transform for each bone and applies constraints. - public void UpdateWorldTransform () { - var updateCacheReset = this.updateCacheReset; - var updateCacheResetItems = updateCacheReset.Items; - for (int i = 0, n = updateCacheReset.Count; i < n; i++) { - Bone bone = updateCacheResetItems[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; - bone.appliedValid = true; - } - var updateItems = this.updateCache.Items; - for (int i = 0, n = updateCache.Count; i < n; i++) - updateItems[i].Update(); - } - - /// - /// Temporarily sets the root bone as a child of the specified bone, then updates the world transform for each bone and applies - /// all constraints. - /// - public void UpdateWorldTransform (Bone parent) { - // This partial update avoids computing the world transform for constrained bones when 1) the bone is not updated - // before the constraint, 2) the constraint only needs to access the applied local transform, and 3) the constraint calls - // updateWorldTransform. - var updateCacheReset = this.updateCacheReset; - var updateCacheResetItems = updateCacheReset.Items; - for (int i = 0, n = updateCacheReset.Count; i < n; i++) { - Bone bone = updateCacheResetItems[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; - bone.appliedValid = true; - } - - // Apply the parent bone transform to the root bone. The root bone always inherits scale, rotation and reflection. - Bone rootBone = this.RootBone; - 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 rotationY = rootBone.rotation + 90 + rootBone.shearY; - float la = MathUtils.CosDeg(rootBone.rotation + rootBone.shearX) * rootBone.scaleX; - float lb = MathUtils.CosDeg(rotationY) * rootBone.scaleY; - float lc = MathUtils.SinDeg(rootBone.rotation + rootBone.shearX) * rootBone.scaleX; - float ld = MathUtils.SinDeg(rotationY) * 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. - var updateCache = this.updateCache; - var updateCacheItems = updateCache.Items; - for (int i = 0, n = updateCache.Count; i < n; i++) { - var updatable = updateCacheItems[i]; - if (updatable != rootBone) - updatable.Update(); - } - } - - /// Sets the bones, constraints, and slots to their setup pose values. - public void SetToSetupPose () { - SetBonesToSetupPose(); - SetSlotsToSetupPose(); - } - - /// Sets the bones and constraints to their setup pose values. - public void SetBonesToSetupPose () { - var bonesItems = this.bones.Items; - for (int i = 0, n = bones.Count; i < n; i++) - bonesItems[i].SetToSetupPose(); - - var ikConstraintsItems = this.ikConstraints.Items; - for (int i = 0, n = ikConstraints.Count; i < n; i++) { - IkConstraint constraint = ikConstraintsItems[i]; - 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; - } - - var transformConstraintsItems = this.transformConstraints.Items; - for (int i = 0, n = transformConstraints.Count; i < n; i++) { - TransformConstraint constraint = transformConstraintsItems[i]; - TransformConstraintData constraintData = constraint.data; - constraint.rotateMix = constraintData.rotateMix; - constraint.translateMix = constraintData.translateMix; - constraint.scaleMix = constraintData.scaleMix; - constraint.shearMix = constraintData.shearMix; - } - - var pathConstraintItems = this.pathConstraints.Items; - for (int i = 0, n = pathConstraints.Count; i < n; i++) { - PathConstraint constraint = pathConstraintItems[i]; - PathConstraintData constraintData = constraint.data; - constraint.position = constraintData.position; - constraint.spacing = constraintData.spacing; - constraint.rotateMix = constraintData.rotateMix; - constraint.translateMix = constraintData.translateMix; - } - } - - public void SetSlotsToSetupPose () { - var slots = this.slots; - var slotsItems = slots.Items; - drawOrder.Clear(); - for (int i = 0, n = slots.Count; i < n; i++) - drawOrder.Add(slotsItems[i]); - - for (int i = 0, n = slots.Count; i < n; i++) - slotsItems[i].SetToSetupPose(); - } - - /// May be null. - public Bone FindBone (string boneName) { - if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); - var bones = this.bones; - var bonesItems = bones.Items; - for (int i = 0, n = bones.Count; i < n; i++) { - Bone bone = bonesItems[i]; - if (bone.data.name == boneName) return bone; - } - return null; - } - - /// -1 if the bone was not found. - public int FindBoneIndex (string boneName) { - if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); - var bones = this.bones; - var bonesItems = bones.Items; - for (int i = 0, n = bones.Count; i < n; i++) - if (bonesItems[i].data.name == boneName) return i; - return -1; - } - - /// May be null. - public Slot FindSlot (string slotName) { - if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); - var slots = this.slots; - var slotsItems = slots.Items; - for (int i = 0, n = slots.Count; i < n; i++) { - Slot slot = slotsItems[i]; - if (slot.data.name == slotName) return slot; - } - return null; - } - - /// -1 if the bone was not found. - public int FindSlotIndex (string slotName) { - if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); - var slots = this.slots; - var slotsItems = slots.Items; - for (int i = 0, n = slots.Count; i < n; i++) - if (slotsItems[i].data.name.Equals(slotName)) return i; - return -1; - } - - /// Sets a skin by name (see SetSkin). - public void SetSkin (string skinName) { - Skin foundSkin = data.FindSkin(skinName); - if (foundSkin == null) throw new ArgumentException("Skin not found: " + skinName, "skinName"); - SetSkin(foundSkin); - } - - /// - /// Sets the skin used to look up attachments before looking in the . If the - /// skin is changed, is called. - /// - /// 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 - /// . - /// Also, often 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. - /// - /// May be null. - public void SetSkin (Skin newSkin) { - if (newSkin == skin) return; - if (newSkin != null) { - if (skin != null) - newSkin.AttachAll(this, skin); - else { - ExposedList slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) { - Slot slot = slots.Items[i]; - string name = slot.data.attachmentName; - if (name != null) { - Attachment attachment = newSkin.GetAttachment(i, name); - if (attachment != null) slot.Attachment = attachment; - } - } - } - } - skin = newSkin; - UpdateCache(); - } - - /// Finds an attachment by looking in the {@link #skin} and {@link SkeletonData#defaultSkin} using the slot name and attachment name. - /// May be null. - public Attachment GetAttachment (string slotName, string attachmentName) { - return GetAttachment(data.FindSlotIndex(slotName), attachmentName); - } - - /// Finds an attachment by looking in the skin and skeletonData.defaultSkin using the slot index and attachment name.First the skin is checked and if the attachment was not found, the default skin is checked. - /// May be null. - public Attachment GetAttachment (int slotIndex, string attachmentName) { - if (attachmentName == null) throw new ArgumentNullException("attachmentName", "attachmentName cannot be null."); - if (skin != null) { - Attachment attachment = skin.GetAttachment(slotIndex, attachmentName); - if (attachment != null) return attachment; - } - return data.defaultSkin != null ? data.defaultSkin.GetAttachment(slotIndex, attachmentName) : null; - } - - /// A convenience method to set an attachment by finding the slot with FindSlot, finding the attachment with GetAttachment, then setting the slot's slot.Attachment. - /// May be null to clear the slot's attachment. - public void SetAttachment (string slotName, string attachmentName) { - if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); - ExposedList slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) { - Slot slot = slots.Items[i]; - if (slot.data.name == slotName) { - Attachment attachment = null; - if (attachmentName != null) { - attachment = GetAttachment(i, attachmentName); - if (attachment == null) throw new Exception("Attachment not found: " + attachmentName + ", for slot: " + slotName); - } - slot.Attachment = attachment; - return; - } - } - throw new Exception("Slot not found: " + slotName); - } - - /// May be null. - public IkConstraint FindIkConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - ExposedList ikConstraints = this.ikConstraints; - for (int i = 0, n = ikConstraints.Count; i < n; i++) { - IkConstraint ikConstraint = ikConstraints.Items[i]; - if (ikConstraint.data.name == constraintName) return ikConstraint; - } - return null; - } - - /// May be null. - public TransformConstraint FindTransformConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - ExposedList transformConstraints = this.transformConstraints; - for (int i = 0, n = transformConstraints.Count; i < n; i++) { - TransformConstraint transformConstraint = transformConstraints.Items[i]; - if (transformConstraint.data.Name == constraintName) return transformConstraint; - } - return null; - } - - /// May be null. - public PathConstraint FindPathConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - ExposedList pathConstraints = this.pathConstraints; - for (int i = 0, n = pathConstraints.Count; i < n; i++) { - PathConstraint constraint = pathConstraints.Items[i]; - if (constraint.data.Name.Equals(constraintName)) return constraint; - } - return null; - } - - public void Update (float delta) { - time += delta; - } - - /// Returns the axis aligned bounding box (AABB) of the region and mesh attachments for the current pose. - /// The horizontal distance between the skeleton origin and the left side of the AABB. - /// The vertical distance between the skeleton origin and the bottom side of the AABB. - /// The width of the AABB - /// The height of the AABB. - /// Reference to hold a float[]. May be a null reference. This method will assign it a new float[] with the appropriate size as needed. - public void GetBounds (out float x, out float y, out float width, out float height, ref float[] vertexBuffer) { - float[] temp = vertexBuffer; - temp = temp ?? new float[8]; - var drawOrderItems = this.drawOrder.Items; - float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; - for (int i = 0, n = drawOrderItems.Length; i < n; i++) { - Slot slot = drawOrderItems[i]; - if (!slot.bone.active) continue; - int verticesLength = 0; - float[] vertices = null; - Attachment attachment = slot.attachment; - var regionAttachment = attachment as RegionAttachment; - if (regionAttachment != null) { - verticesLength = 8; - vertices = temp; - if (vertices.Length < 8) vertices = temp = new float[8]; - regionAttachment.ComputeWorldVertices(slot.bone, temp, 0); - } else { - var meshAttachment = attachment as MeshAttachment; - if (meshAttachment != null) { - MeshAttachment mesh = meshAttachment; - verticesLength = mesh.WorldVerticesLength; - vertices = temp; - if (vertices.Length < verticesLength) vertices = temp = new float[verticesLength]; - mesh.ComputeWorldVertices(slot, 0, verticesLength, temp, 0); - } - } - - if (vertices != null) { - for (int ii = 0; ii < verticesLength; ii += 2) { - float vx = vertices[ii], vy = vertices[ii + 1]; - minX = Math.Min(minX, vx); - minY = Math.Min(minY, vy); - maxX = Math.Max(maxX, vx); - maxY = Math.Max(maxY, vy); - } - } - } - x = minX; - y = minY; - width = maxX - minX; - height = maxY - minY; - vertexBuffer = temp; - } - } + +namespace Spine3_8_95 +{ + public class Skeleton + { + internal SkeletonData data; + internal ExposedList bones; + internal ExposedList slots; + internal ExposedList drawOrder; + internal ExposedList ikConstraints; + internal ExposedList transformConstraints; + internal ExposedList pathConstraints; + internal ExposedList updateCache = new ExposedList(); + internal ExposedList updateCacheReset = new ExposedList(); + internal Skin skin; + internal float r = 1, g = 1, b = 1, a = 1; + internal float time; + private float scaleX = 1, scaleY = 1; + internal float x, y; + + public SkeletonData Data { get { return data; } } + public ExposedList Bones { get { return bones; } } + public ExposedList UpdateCacheList { get { return updateCache; } } + public ExposedList Slots { get { return slots; } } + public ExposedList DrawOrder { get { return drawOrder; } } + public ExposedList IkConstraints { get { return ikConstraints; } } + public ExposedList PathConstraints { get { return pathConstraints; } } + public ExposedList TransformConstraints { get { return transformConstraints; } } + public Skin Skin { get { return skin; } set { SetSkin(value); } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } + public float Time { get { return time; } set { time = value; } } + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + public float ScaleY { get { return scaleY * (Bone.yDown ? -1 : 1); } set { scaleY = value; } } + + [Obsolete("Use ScaleX instead. FlipX is when ScaleX is negative.")] + public bool FlipX { get { return scaleX < 0; } set { scaleX = value ? -1f : 1f; } } + + [Obsolete("Use ScaleY instead. FlipY is when ScaleY is negative.")] + public bool FlipY { get { return scaleY < 0; } set { scaleY = value ? -1f : 1f; } } + + public Bone RootBone + { + get { return bones.Count == 0 ? null : bones.Items[0]; } + } + + public Skeleton(SkeletonData data) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + this.data = data; + + bones = new ExposedList(data.bones.Count); + foreach (BoneData boneData in data.bones) + { + Bone bone; + if (boneData.parent == null) + { + bone = new Bone(boneData, this, null); + } + else + { + Bone parent = bones.Items[boneData.parent.index]; + bone = new Bone(boneData, this, parent); + parent.children.Add(bone); + } + bones.Add(bone); + } + + slots = new ExposedList(data.slots.Count); + drawOrder = new ExposedList(data.slots.Count); + foreach (SlotData slotData in data.slots) + { + Bone bone = bones.Items[slotData.boneData.index]; + Slot slot = new Slot(slotData, bone); + slots.Add(slot); + drawOrder.Add(slot); + } + + ikConstraints = new ExposedList(data.ikConstraints.Count); + foreach (IkConstraintData ikConstraintData in data.ikConstraints) + ikConstraints.Add(new IkConstraint(ikConstraintData, this)); + + transformConstraints = new ExposedList(data.transformConstraints.Count); + foreach (TransformConstraintData transformConstraintData in data.transformConstraints) + transformConstraints.Add(new TransformConstraint(transformConstraintData, this)); + + pathConstraints = new ExposedList(data.pathConstraints.Count); + foreach (PathConstraintData pathConstraintData in data.pathConstraints) + pathConstraints.Add(new PathConstraint(pathConstraintData, this)); + + UpdateCache(); + UpdateWorldTransform(); + } + + /// Caches information about bones and constraints. Must be called if the is modified or if bones, constraints, or + /// constraints, or weighted path attachments are added or removed. + public void UpdateCache() + { + var updateCache = this.updateCache; + updateCache.Clear(); + this.updateCacheReset.Clear(); + + int boneCount = this.bones.Items.Length; + var bones = this.bones; + for (int i = 0; i < boneCount; i++) + { + Bone bone = bones.Items[i]; + bone.sorted = bone.data.skinRequired; + bone.active = !bone.sorted; + } + if (skin != null) + { + Object[] skinBones = skin.bones.Items; + for (int i = 0, n = skin.bones.Count; i < n; i++) + { + Bone bone = bones.Items[((BoneData)skinBones[i]).index]; + do + { + bone.sorted = false; + bone.active = true; + bone = bone.parent; + } while (bone != null); + } + } + + int ikCount = this.ikConstraints.Count, transformCount = this.transformConstraints.Count, pathCount = this.pathConstraints.Count; + var ikConstraints = this.ikConstraints; + var transformConstraints = this.transformConstraints; + var pathConstraints = this.pathConstraints; + int constraintCount = ikCount + transformCount + pathCount; + //outer: + for (int i = 0; i < constraintCount; i++) + { + for (int ii = 0; ii < ikCount; ii++) + { + IkConstraint constraint = ikConstraints.Items[ii]; + if (constraint.data.order == i) + { + SortIkConstraint(constraint); + goto continue_outer; //continue outer; + } + } + for (int ii = 0; ii < transformCount; ii++) + { + TransformConstraint constraint = transformConstraints.Items[ii]; + if (constraint.data.order == i) + { + SortTransformConstraint(constraint); + goto continue_outer; //continue outer; + } + } + for (int ii = 0; ii < pathCount; ii++) + { + PathConstraint constraint = pathConstraints.Items[ii]; + if (constraint.data.order == i) + { + SortPathConstraint(constraint); + goto continue_outer; //continue outer; + } + } + continue_outer: { } + } + + for (int i = 0; i < boneCount; i++) + SortBone(bones.Items[i]); + } + + private void SortIkConstraint(IkConstraint constraint) + { + constraint.active = constraint.target.active + && (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data))); + if (!constraint.active) return; + + Bone target = constraint.target; + SortBone(target); + + var constrained = constraint.bones; + Bone parent = constrained.Items[0]; + SortBone(parent); + + if (constrained.Count > 1) + { + Bone child = constrained.Items[constrained.Count - 1]; + if (!updateCache.Contains(child)) + updateCacheReset.Add(child); + } + + updateCache.Add(constraint); + + SortReset(parent.children); + constrained.Items[constrained.Count - 1].sorted = true; + } + + private void SortPathConstraint(PathConstraint constraint) + { + constraint.active = constraint.target.bone.active + && (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data))); + if (!constraint.active) return; + + Slot slot = constraint.target; + int slotIndex = slot.data.index; + Bone slotBone = slot.bone; + if (skin != null) SortPathConstraintAttachment(skin, slotIndex, slotBone); + if (data.defaultSkin != null && data.defaultSkin != skin) + SortPathConstraintAttachment(data.defaultSkin, slotIndex, slotBone); + + Attachment attachment = slot.attachment; + if (attachment is PathAttachment) SortPathConstraintAttachment(attachment, slotBone); + + var constrained = constraint.bones; + int boneCount = constrained.Count; + for (int i = 0; i < boneCount; i++) + SortBone(constrained.Items[i]); + + updateCache.Add(constraint); + + for (int i = 0; i < boneCount; i++) + SortReset(constrained.Items[i].children); + for (int i = 0; i < boneCount; i++) + constrained.Items[i].sorted = true; + } + + private void SortTransformConstraint(TransformConstraint constraint) + { + constraint.active = constraint.target.active + && (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data))); + if (!constraint.active) return; + + SortBone(constraint.target); + + var constrained = constraint.bones; + int boneCount = constrained.Count; + if (constraint.data.local) + { + for (int i = 0; i < boneCount; i++) + { + Bone child = constrained.Items[i]; + SortBone(child.parent); + if (!updateCache.Contains(child)) updateCacheReset.Add(child); + } + } + else + { + for (int i = 0; i < boneCount; i++) + SortBone(constrained.Items[i]); + } + + updateCache.Add(constraint); + + for (int i = 0; i < boneCount; i++) + SortReset(constrained.Items[i].children); + for (int i = 0; i < boneCount; i++) + constrained.Items[i].sorted = true; + } + + private void SortPathConstraintAttachment(Skin skin, int slotIndex, Bone slotBone) + { + foreach (var entryObj in skin.Attachments.Keys) + { + var entry = entryObj; + if (entry.SlotIndex == slotIndex) SortPathConstraintAttachment(entry.Attachment, slotBone); + } + } + + private void SortPathConstraintAttachment(Attachment attachment, Bone slotBone) + { + if (!(attachment is PathAttachment)) return; + int[] pathBones = ((PathAttachment)attachment).bones; + if (pathBones == null) + SortBone(slotBone); + else + { + var bones = this.bones; + for (int i = 0, n = pathBones.Length; i < n;) + { + int nn = pathBones[i++]; + nn += i; + while (i < nn) + SortBone(bones.Items[pathBones[i++]]); + } + } + } + + private void SortBone(Bone bone) + { + if (bone.sorted) return; + Bone parent = bone.parent; + if (parent != null) SortBone(parent); + bone.sorted = true; + updateCache.Add(bone); + } + + private static void SortReset(ExposedList bones) + { + var bonesItems = bones.Items; + for (int i = 0, n = bones.Count; i < n; i++) + { + Bone bone = bonesItems[i]; + if (!bone.active) continue; + if (bone.sorted) SortReset(bone.children); + bone.sorted = false; + } + } + + /// Updates the world transform for each bone and applies constraints. + public void UpdateWorldTransform() + { + var updateCacheReset = this.updateCacheReset; + var updateCacheResetItems = updateCacheReset.Items; + for (int i = 0, n = updateCacheReset.Count; i < n; i++) + { + Bone bone = updateCacheResetItems[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; + bone.appliedValid = true; + } + var updateItems = this.updateCache.Items; + for (int i = 0, n = updateCache.Count; i < n; i++) + updateItems[i].Update(); + } + + /// + /// Temporarily sets the root bone as a child of the specified bone, then updates the world transform for each bone and applies + /// all constraints. + /// + public void UpdateWorldTransform(Bone parent) + { + // This partial update avoids computing the world transform for constrained bones when 1) the bone is not updated + // before the constraint, 2) the constraint only needs to access the applied local transform, and 3) the constraint calls + // updateWorldTransform. + var updateCacheReset = this.updateCacheReset; + var updateCacheResetItems = updateCacheReset.Items; + for (int i = 0, n = updateCacheReset.Count; i < n; i++) + { + Bone bone = updateCacheResetItems[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; + bone.appliedValid = true; + } + + // Apply the parent bone transform to the root bone. The root bone always inherits scale, rotation and reflection. + Bone rootBone = this.RootBone; + 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 rotationY = rootBone.rotation + 90 + rootBone.shearY; + float la = MathUtils.CosDeg(rootBone.rotation + rootBone.shearX) * rootBone.scaleX; + float lb = MathUtils.CosDeg(rotationY) * rootBone.scaleY; + float lc = MathUtils.SinDeg(rootBone.rotation + rootBone.shearX) * rootBone.scaleX; + float ld = MathUtils.SinDeg(rotationY) * 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. + var updateCache = this.updateCache; + var updateCacheItems = updateCache.Items; + for (int i = 0, n = updateCache.Count; i < n; i++) + { + var updatable = updateCacheItems[i]; + if (updatable != rootBone) + updatable.Update(); + } + } + + /// Sets the bones, constraints, and slots to their setup pose values. + public void SetToSetupPose() + { + SetBonesToSetupPose(); + SetSlotsToSetupPose(); + } + + /// Sets the bones and constraints to their setup pose values. + public void SetBonesToSetupPose() + { + var bonesItems = this.bones.Items; + for (int i = 0, n = bones.Count; i < n; i++) + bonesItems[i].SetToSetupPose(); + + var ikConstraintsItems = this.ikConstraints.Items; + for (int i = 0, n = ikConstraints.Count; i < n; i++) + { + IkConstraint constraint = ikConstraintsItems[i]; + 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; + } + + var transformConstraintsItems = this.transformConstraints.Items; + for (int i = 0, n = transformConstraints.Count; i < n; i++) + { + TransformConstraint constraint = transformConstraintsItems[i]; + TransformConstraintData constraintData = constraint.data; + constraint.rotateMix = constraintData.rotateMix; + constraint.translateMix = constraintData.translateMix; + constraint.scaleMix = constraintData.scaleMix; + constraint.shearMix = constraintData.shearMix; + } + + var pathConstraintItems = this.pathConstraints.Items; + for (int i = 0, n = pathConstraints.Count; i < n; i++) + { + PathConstraint constraint = pathConstraintItems[i]; + PathConstraintData constraintData = constraint.data; + constraint.position = constraintData.position; + constraint.spacing = constraintData.spacing; + constraint.rotateMix = constraintData.rotateMix; + constraint.translateMix = constraintData.translateMix; + } + } + + public void SetSlotsToSetupPose() + { + var slots = this.slots; + var slotsItems = slots.Items; + drawOrder.Clear(); + for (int i = 0, n = slots.Count; i < n; i++) + drawOrder.Add(slotsItems[i]); + + for (int i = 0, n = slots.Count; i < n; i++) + slotsItems[i].SetToSetupPose(); + } + + /// May be null. + public Bone FindBone(string boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); + var bones = this.bones; + var bonesItems = bones.Items; + for (int i = 0, n = bones.Count; i < n; i++) + { + Bone bone = bonesItems[i]; + if (bone.data.name == boneName) return bone; + } + return null; + } + + /// -1 if the bone was not found. + public int FindBoneIndex(string boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); + var bones = this.bones; + var bonesItems = bones.Items; + for (int i = 0, n = bones.Count; i < n; i++) + if (bonesItems[i].data.name == boneName) return i; + return -1; + } + + /// May be null. + public Slot FindSlot(string slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + var slots = this.slots; + var slotsItems = slots.Items; + for (int i = 0, n = slots.Count; i < n; i++) + { + Slot slot = slotsItems[i]; + if (slot.data.name == slotName) return slot; + } + return null; + } + + /// -1 if the bone was not found. + public int FindSlotIndex(string slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + var slots = this.slots; + var slotsItems = slots.Items; + for (int i = 0, n = slots.Count; i < n; i++) + if (slotsItems[i].data.name.Equals(slotName)) return i; + return -1; + } + + /// Sets a skin by name (see SetSkin). + public void SetSkin(string skinName) + { + Skin foundSkin = data.FindSkin(skinName); + if (foundSkin == null) throw new ArgumentException("Skin not found: " + skinName, "skinName"); + SetSkin(foundSkin); + } + + /// + /// Sets the skin used to look up attachments before looking in the . If the + /// skin is changed, is called. + /// + /// 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 + /// . + /// Also, often 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. + /// + /// May be null. + public void SetSkin(Skin newSkin) + { + if (newSkin == skin) return; + if (newSkin != null) + { + if (skin != null) + newSkin.AttachAll(this, skin); + else + { + ExposedList slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + { + Slot slot = slots.Items[i]; + string name = slot.data.attachmentName; + if (name != null) + { + Attachment attachment = newSkin.GetAttachment(i, name); + if (attachment != null) slot.Attachment = attachment; + } + } + } + } + skin = newSkin; + UpdateCache(); + } + + /// Finds an attachment by looking in the {@link #skin} and {@link SkeletonData#defaultSkin} using the slot name and attachment name. + /// May be null. + public Attachment GetAttachment(string slotName, string attachmentName) + { + return GetAttachment(data.FindSlotIndex(slotName), attachmentName); + } + + /// Finds an attachment by looking in the skin and skeletonData.defaultSkin using the slot index and attachment name.First the skin is checked and if the attachment was not found, the default skin is checked. + /// May be null. + public Attachment GetAttachment(int slotIndex, string attachmentName) + { + if (attachmentName == null) throw new ArgumentNullException("attachmentName", "attachmentName cannot be null."); + if (skin != null) + { + Attachment attachment = skin.GetAttachment(slotIndex, attachmentName); + if (attachment != null) return attachment; + } + return data.defaultSkin != null ? data.defaultSkin.GetAttachment(slotIndex, attachmentName) : null; + } + + /// A convenience method to set an attachment by finding the slot with FindSlot, finding the attachment with GetAttachment, then setting the slot's slot.Attachment. + /// May be null to clear the slot's attachment. + public void SetAttachment(string slotName, string attachmentName) + { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + ExposedList slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + { + Slot slot = slots.Items[i]; + if (slot.data.name == slotName) + { + Attachment attachment = null; + if (attachmentName != null) + { + attachment = GetAttachment(i, attachmentName); + if (attachment == null) throw new Exception("Attachment not found: " + attachmentName + ", for slot: " + slotName); + } + slot.Attachment = attachment; + return; + } + } + throw new Exception("Slot not found: " + slotName); + } + + /// May be null. + public IkConstraint FindIkConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + ExposedList ikConstraints = this.ikConstraints; + for (int i = 0, n = ikConstraints.Count; i < n; i++) + { + IkConstraint ikConstraint = ikConstraints.Items[i]; + if (ikConstraint.data.name == constraintName) return ikConstraint; + } + return null; + } + + /// May be null. + public TransformConstraint FindTransformConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + ExposedList transformConstraints = this.transformConstraints; + for (int i = 0, n = transformConstraints.Count; i < n; i++) + { + TransformConstraint transformConstraint = transformConstraints.Items[i]; + if (transformConstraint.data.Name == constraintName) return transformConstraint; + } + return null; + } + + /// May be null. + public PathConstraint FindPathConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + ExposedList pathConstraints = this.pathConstraints; + for (int i = 0, n = pathConstraints.Count; i < n; i++) + { + PathConstraint constraint = pathConstraints.Items[i]; + if (constraint.data.Name.Equals(constraintName)) return constraint; + } + return null; + } + + public void Update(float delta) + { + time += delta; + } + + /// Returns the axis aligned bounding box (AABB) of the region and mesh attachments for the current pose. + /// The horizontal distance between the skeleton origin and the left side of the AABB. + /// The vertical distance between the skeleton origin and the bottom side of the AABB. + /// The width of the AABB + /// The height of the AABB. + /// Reference to hold a float[]. May be a null reference. This method will assign it a new float[] with the appropriate size as needed. + public void GetBounds(out float x, out float y, out float width, out float height, ref float[] vertexBuffer) + { + float[] temp = vertexBuffer; + temp = temp ?? new float[8]; + var drawOrderItems = this.drawOrder.Items; + float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; + for (int i = 0, n = drawOrderItems.Length; i < n; i++) + { + Slot slot = drawOrderItems[i]; + if (!slot.bone.active) continue; + int verticesLength = 0; + float[] vertices = null; + Attachment attachment = slot.attachment; + var regionAttachment = attachment as RegionAttachment; + if (regionAttachment != null) + { + verticesLength = 8; + vertices = temp; + if (vertices.Length < 8) vertices = temp = new float[8]; + regionAttachment.ComputeWorldVertices(slot.bone, temp, 0); + } + else + { + var meshAttachment = attachment as MeshAttachment; + if (meshAttachment != null) + { + MeshAttachment mesh = meshAttachment; + verticesLength = mesh.WorldVerticesLength; + vertices = temp; + if (vertices.Length < verticesLength) vertices = temp = new float[verticesLength]; + mesh.ComputeWorldVertices(slot, 0, verticesLength, temp, 0); + } + } + + if (vertices != null) + { + for (int ii = 0; ii < verticesLength; ii += 2) + { + float vx = vertices[ii], vy = vertices[ii + 1]; + minX = Math.Min(minX, vx); + minY = Math.Min(minY, vy); + maxX = Math.Max(maxX, vx); + maxY = Math.Max(maxY, vy); + } + } + } + x = minX; + y = minY; + width = maxX - minX; + height = maxY - minY; + vertexBuffer = temp; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/SkeletonBinary.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/SkeletonBinary.cs index e3c3fcc..a7f8601 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/SkeletonBinary.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/SkeletonBinary.cs @@ -32,49 +32,53 @@ #endif using System; -using System.IO; using System.Collections.Generic; +using System.IO; #if WINDOWS_STOREAPP using System.Threading.Tasks; using Windows.Storage; #endif -namespace Spine3_8_95 { - public class SkeletonBinary { - public const int BONE_ROTATE = 0; - public const int BONE_TRANSLATE = 1; - public const int BONE_SCALE = 2; - public const int BONE_SHEAR = 3; - - public const int SLOT_ATTACHMENT = 0; - public const int SLOT_COLOR = 1; - public const int SLOT_TWO_COLOR = 2; - - public const int PATH_POSITION = 0; - public const int PATH_SPACING = 1; - public const int PATH_MIX = 2; - - public const int CURVE_LINEAR = 0; - public const int CURVE_STEPPED = 1; - public const int CURVE_BEZIER = 2; - - public float Scale { get; set; } - - private AttachmentLoader attachmentLoader; - private List linkedMeshes = new List(); - - public SkeletonBinary (params Atlas[] atlasArray) - : this(new AtlasAttachmentLoader(atlasArray)) { - } - - public SkeletonBinary (AttachmentLoader attachmentLoader) { - if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader"); - this.attachmentLoader = attachmentLoader; - Scale = 1; - } - - #if !ISUNITY && WINDOWS_STOREAPP +namespace Spine3_8_95 +{ + public class SkeletonBinary + { + public const int BONE_ROTATE = 0; + public const int BONE_TRANSLATE = 1; + public const int BONE_SCALE = 2; + public const int BONE_SHEAR = 3; + + public const int SLOT_ATTACHMENT = 0; + public const int SLOT_COLOR = 1; + public const int SLOT_TWO_COLOR = 2; + + public const int PATH_POSITION = 0; + public const int PATH_SPACING = 1; + public const int PATH_MIX = 2; + + public const int CURVE_LINEAR = 0; + public const int CURVE_STEPPED = 1; + public const int CURVE_BEZIER = 2; + + public float Scale { get; set; } + + private AttachmentLoader attachmentLoader; + private List linkedMeshes = new List(); + + public SkeletonBinary(params Atlas[] atlasArray) + : this(new AtlasAttachmentLoader(atlasArray)) + { + } + + public SkeletonBinary(AttachmentLoader attachmentLoader) + { + if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader"); + this.attachmentLoader = attachmentLoader; + Scale = 1; + } + +#if !ISUNITY && WINDOWS_STOREAPP private async Task ReadFile(string path) { var folder = Windows.ApplicationModel.Package.Current.InstalledLocation; using (var input = new BufferedStream(await folder.GetFileAsync(path).AsTask().ConfigureAwait(false))) { @@ -87,911 +91,1025 @@ private async Task ReadFile(string path) { public SkeletonData ReadSkeletonData (String path) { return this.ReadFile(path).Result; } - #else - public SkeletonData ReadSkeletonData (String path) { - #if WINDOWS_PHONE +#else + public SkeletonData ReadSkeletonData(String path) + { +#if WINDOWS_PHONE using (var input = new BufferedStream(Microsoft.Xna.Framework.TitleContainer.OpenStream(path))) { - #else - using (var input = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) { - #endif - SkeletonData skeletonData = ReadSkeletonData(input); - skeletonData.name = Path.GetFileNameWithoutExtension(path); - return skeletonData; - } - } - #endif // WINDOWS_STOREAPP - - public static readonly TransformMode[] TransformModeValues = { - TransformMode.Normal, - TransformMode.OnlyTranslation, - TransformMode.NoRotationOrReflection, - TransformMode.NoScale, - TransformMode.NoScaleOrReflection - }; - - /// Returns the version string of binary skeleton data. - public static string GetVersionString (Stream file) { - if (file == null) throw new ArgumentNullException("file"); - - SkeletonInput input = new SkeletonInput(file); - return input.GetVersionString(); - } - - public SkeletonData ReadSkeletonData (Stream file) { - if (file == null) throw new ArgumentNullException("file"); - float scale = Scale; - - var skeletonData = new SkeletonData(); - SkeletonInput input = new SkeletonInput(file); - - skeletonData.hash = input.ReadString(); - if (skeletonData.hash.Length == 0) skeletonData.hash = null; - skeletonData.version = input.ReadString(); - if (skeletonData.version.Length == 0) skeletonData.version = null; - if ("3.8.75" == skeletonData.version) - throw new Exception("Unsupported skeleton data, please export with a newer version of Spine."); - skeletonData.x = input.ReadFloat(); - skeletonData.y = input.ReadFloat(); - skeletonData.width = input.ReadFloat(); - skeletonData.height = input.ReadFloat(); - - bool nonessential = input.ReadBoolean(); - - if (nonessential) { - skeletonData.fps = input.ReadFloat(); - - skeletonData.imagesPath = input.ReadString(); - if (string.IsNullOrEmpty(skeletonData.imagesPath)) skeletonData.imagesPath = null; - - skeletonData.audioPath = input.ReadString(); - if (string.IsNullOrEmpty(skeletonData.audioPath)) skeletonData.audioPath = null; - } - - int n; - Object[] o; - - // Strings. - input.strings = new ExposedList(n = input.ReadInt(true)); - o = input.strings.Resize(n).Items; - for (int i = 0; i < n; i++) - o[i] = input.ReadString(); - - // Bones. - o = skeletonData.bones.Resize(n = input.ReadInt(true)).Items; - for (int i = 0; i < n; i++) { - String name = input.ReadString(); - BoneData parent = i == 0 ? null : skeletonData.bones.Items[input.ReadInt(true)]; - BoneData data = new BoneData(i, name, parent); - data.rotation = input.ReadFloat(); - data.x = input.ReadFloat() * scale; - data.y = input.ReadFloat() * scale; - data.scaleX = input.ReadFloat(); - data.scaleY = input.ReadFloat(); - data.shearX = input.ReadFloat(); - data.shearY = input.ReadFloat(); - data.length = input.ReadFloat() * scale; - data.transformMode = TransformModeValues[input.ReadInt(true)]; - data.skinRequired = input.ReadBoolean(); - if (nonessential) input.ReadInt(); // Skip bone color. - o[i] = data; - } - - // Slots. - o = skeletonData.slots.Resize(n = input.ReadInt(true)).Items; - for (int i = 0; i < n; i++) { - String slotName = input.ReadString(); - BoneData boneData = skeletonData.bones.Items[input.ReadInt(true)]; - SlotData slotData = new SlotData(i, slotName, boneData); - int color = input.ReadInt(); - slotData.r = ((color & 0xff000000) >> 24) / 255f; - slotData.g = ((color & 0x00ff0000) >> 16) / 255f; - slotData.b = ((color & 0x0000ff00) >> 8) / 255f; - slotData.a = ((color & 0x000000ff)) / 255f; - - int darkColor = input.ReadInt(); // 0x00rrggbb - if (darkColor != -1) { - slotData.hasSecondColor = true; - slotData.r2 = ((darkColor & 0x00ff0000) >> 16) / 255f; - slotData.g2 = ((darkColor & 0x0000ff00) >> 8) / 255f; - slotData.b2 = ((darkColor & 0x000000ff)) / 255f; - } - - slotData.attachmentName = input.ReadStringRef(); - slotData.blendMode = (BlendMode)input.ReadInt(true); - o[i] = slotData; - } - - // IK constraints. - o = skeletonData.ikConstraints.Resize(n = input.ReadInt(true)).Items; - for (int i = 0, nn; i < n; i++) { - IkConstraintData data = new IkConstraintData(input.ReadString()); - data.order = input.ReadInt(true); - data.skinRequired = input.ReadBoolean(); - Object[] bones = data.bones.Resize(nn = input.ReadInt(true)).Items; - for (int ii = 0; ii < nn; ii++) - bones[ii] = skeletonData.bones.Items[input.ReadInt(true)]; - data.target = skeletonData.bones.Items[input.ReadInt(true)]; - data.mix = input.ReadFloat(); - data.softness = input.ReadFloat() * scale; - data.bendDirection = input.ReadSByte(); - data.compress = input.ReadBoolean(); - data.stretch = input.ReadBoolean(); - data.uniform = input.ReadBoolean(); - o[i] = data; - } - - // Transform constraints. - o = skeletonData.transformConstraints.Resize(n = input.ReadInt(true)).Items; - for (int i = 0, nn; i < n; i++) { - TransformConstraintData data = new TransformConstraintData(input.ReadString()); - data.order = input.ReadInt(true); - data.skinRequired = input.ReadBoolean(); - Object[] bones = data.bones.Resize(nn = input.ReadInt(true)).Items; - for (int ii = 0; ii < nn; ii++) - bones[ii] = skeletonData.bones.Items[input.ReadInt(true)]; - data.target = skeletonData.bones.Items[input.ReadInt(true)]; - data.local = input.ReadBoolean(); - data.relative = input.ReadBoolean(); - data.offsetRotation = input.ReadFloat(); - data.offsetX = input.ReadFloat() * scale; - data.offsetY = input.ReadFloat() * scale; - data.offsetScaleX = input.ReadFloat(); - data.offsetScaleY = input.ReadFloat(); - data.offsetShearY = input.ReadFloat(); - data.rotateMix = input.ReadFloat(); - data.translateMix = input.ReadFloat(); - data.scaleMix = input.ReadFloat(); - data.shearMix = input.ReadFloat(); - o[i] = data; - } - - // Path constraints - o = skeletonData.pathConstraints.Resize(n = input.ReadInt(true)).Items; - for (int i = 0, nn; i < n; i++) { - PathConstraintData data = new PathConstraintData(input.ReadString()); - data.order = input.ReadInt(true); - data.skinRequired = input.ReadBoolean(); - Object[] bones = data.bones.Resize(nn = input.ReadInt(true)).Items; - for (int ii = 0; ii < nn; ii++) - bones[ii] = skeletonData.bones.Items[input.ReadInt(true)]; - data.target = skeletonData.slots.Items[input.ReadInt(true)]; - data.positionMode = (PositionMode)Enum.GetValues(typeof(PositionMode)).GetValue(input.ReadInt(true)); - data.spacingMode = (SpacingMode)Enum.GetValues(typeof(SpacingMode)).GetValue(input.ReadInt(true)); - data.rotateMode = (RotateMode)Enum.GetValues(typeof(RotateMode)).GetValue(input.ReadInt(true)); - data.offsetRotation = input.ReadFloat(); - data.position = input.ReadFloat(); - if (data.positionMode == PositionMode.Fixed) data.position *= scale; - data.spacing = input.ReadFloat(); - if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale; - data.rotateMix = input.ReadFloat(); - data.translateMix = input.ReadFloat(); - o[i] = data; - } - - // Default skin. - Skin defaultSkin = ReadSkin(input, skeletonData, true, nonessential); - if (defaultSkin != null) { - skeletonData.defaultSkin = defaultSkin; - skeletonData.skins.Add(defaultSkin); - } - - // Skins. - { - int i = skeletonData.skins.Count; - o = skeletonData.skins.Resize(n = i + input.ReadInt(true)).Items; - for (; i < n; i++) - o[i] = ReadSkin(input, skeletonData, false, nonessential); - } - - // Linked meshes. - n = linkedMeshes.Count; - for (int i = 0; i < n; i++) { - SkeletonJson.LinkedMesh linkedMesh = linkedMeshes[i]; - Skin skin = linkedMesh.skin == null ? skeletonData.DefaultSkin : skeletonData.FindSkin(linkedMesh.skin); - if (skin == null) throw new Exception("Skin not found: " + linkedMesh.skin); - Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); - if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); - linkedMesh.mesh.DeformAttachment = linkedMesh.inheritDeform ? (VertexAttachment)parent : linkedMesh.mesh; - linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; - linkedMesh.mesh.UpdateUVs(); - } - linkedMeshes.Clear(); - - // Events. - o = skeletonData.events.Resize(n = input.ReadInt(true)).Items; - for (int i = 0; i < n; i++) { - EventData data = new EventData(input.ReadStringRef()); - data.Int = input.ReadInt(false); - data.Float = input.ReadFloat(); - data.String = input.ReadString(); - data.AudioPath = input.ReadString(); - if (data.AudioPath != null) { - data.Volume = input.ReadFloat(); - data.Balance = input.ReadFloat(); - } - o[i] = data; - } - - // Animations. - o = skeletonData.animations.Resize(n = input.ReadInt(true)).Items; - for (int i = 0; i < n; i++) - o[i] = ReadAnimation(input.ReadString(), input, skeletonData); - - return skeletonData; - } - - - /// May be null. - private Skin ReadSkin (SkeletonInput input, SkeletonData skeletonData, bool defaultSkin, bool nonessential) { - - Skin skin; - int slotCount; - - if (defaultSkin) { - slotCount = input.ReadInt(true); - if (slotCount == 0) return null; - skin = new Skin("default"); - } else { - skin = new Skin(input.ReadStringRef()); - Object[] bones = skin.bones.Resize(input.ReadInt(true)).Items; - for (int i = 0, n = skin.bones.Count; i < n; i++) - bones[i] = skeletonData.bones.Items[input.ReadInt(true)]; - - for (int i = 0, n = input.ReadInt(true); i < n; i++) - skin.constraints.Add(skeletonData.ikConstraints.Items[input.ReadInt(true)]); - for (int i = 0, n = input.ReadInt(true); i < n; i++) - skin.constraints.Add(skeletonData.transformConstraints.Items[input.ReadInt(true)]); - for (int i = 0, n = input.ReadInt(true); i < n; i++) - skin.constraints.Add(skeletonData.pathConstraints.Items[input.ReadInt(true)]); - skin.constraints.TrimExcess(); - slotCount = input.ReadInt(true); - } - for (int i = 0; i < slotCount; i++) { - int slotIndex = input.ReadInt(true); - for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) { - String name = input.ReadStringRef(); - Attachment attachment = ReadAttachment(input, skeletonData, skin, slotIndex, name, nonessential); - if (attachment != null) skin.SetAttachment(slotIndex, name, attachment); - } - } - return skin; - } - - private Attachment ReadAttachment (SkeletonInput input, SkeletonData skeletonData, Skin skin, int slotIndex, - String attachmentName, bool nonessential) { - - float scale = Scale; - - String name = input.ReadStringRef(); - if (name == null) name = attachmentName; - - AttachmentType type = (AttachmentType)input.ReadByte(); - switch (type) { - case AttachmentType.Region: { - String path = input.ReadStringRef(); - float rotation = input.ReadFloat(); - float x = input.ReadFloat(); - float y = input.ReadFloat(); - float scaleX = input.ReadFloat(); - float scaleY = input.ReadFloat(); - float width = input.ReadFloat(); - float height = input.ReadFloat(); - int color = input.ReadInt(); - - if (path == null) path = name; - RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); - if (region == null) return null; - region.Path = path; - region.x = x * scale; - region.y = y * scale; - region.scaleX = scaleX; - region.scaleY = scaleY; - region.rotation = rotation; - region.width = width * scale; - region.height = height * scale; - region.r = ((color & 0xff000000) >> 24) / 255f; - region.g = ((color & 0x00ff0000) >> 16) / 255f; - region.b = ((color & 0x0000ff00) >> 8) / 255f; - region.a = ((color & 0x000000ff)) / 255f; - region.UpdateOffset(); - return region; - } - case AttachmentType.Boundingbox: { - int vertexCount = input.ReadInt(true); - Vertices vertices = ReadVertices(input, vertexCount); - if (nonessential) input.ReadInt(); //int color = nonessential ? input.ReadInt() : 0; // Avoid unused local warning. - - BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); - if (box == null) return null; - box.worldVerticesLength = vertexCount << 1; - box.vertices = vertices.vertices; - box.bones = vertices.bones; - // skipped porting: if (nonessential) Color.rgba8888ToColor(box.getColor(), color); - return box; - } - case AttachmentType.Mesh: { - String path = input.ReadStringRef(); - int color = input.ReadInt(); - int vertexCount = input.ReadInt(true); - float[] uvs = ReadFloatArray(input, vertexCount << 1, 1); - int[] triangles = ReadShortArray(input); - Vertices vertices = ReadVertices(input, vertexCount); - int hullLength = input.ReadInt(true); - int[] edges = null; - float width = 0, height = 0; - if (nonessential) { - edges = ReadShortArray(input); - width = input.ReadFloat(); - height = input.ReadFloat(); - } - - if (path == null) path = name; - MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); - if (mesh == null) return null; - mesh.Path = path; - mesh.r = ((color & 0xff000000) >> 24) / 255f; - mesh.g = ((color & 0x00ff0000) >> 16) / 255f; - mesh.b = ((color & 0x0000ff00) >> 8) / 255f; - mesh.a = ((color & 0x000000ff)) / 255f; - mesh.bones = vertices.bones; - mesh.vertices = vertices.vertices; - mesh.WorldVerticesLength = vertexCount << 1; - mesh.triangles = triangles; - mesh.regionUVs = uvs; - mesh.UpdateUVs(); - mesh.HullLength = hullLength << 1; - if (nonessential) { - mesh.Edges = edges; - mesh.Width = width * scale; - mesh.Height = height * scale; - } - return mesh; - } - case AttachmentType.Linkedmesh: { - String path = input.ReadStringRef(); - int color = input.ReadInt(); - String skinName = input.ReadStringRef(); - String parent = input.ReadStringRef(); - bool inheritDeform = input.ReadBoolean(); - float width = 0, height = 0; - if (nonessential) { - width = input.ReadFloat(); - height = input.ReadFloat(); - } - - if (path == null) path = name; - MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); - if (mesh == null) return null; - mesh.Path = path; - mesh.r = ((color & 0xff000000) >> 24) / 255f; - mesh.g = ((color & 0x00ff0000) >> 16) / 255f; - mesh.b = ((color & 0x0000ff00) >> 8) / 255f; - mesh.a = ((color & 0x000000ff)) / 255f; - if (nonessential) { - mesh.Width = width * scale; - mesh.Height = height * scale; - } - linkedMeshes.Add(new SkeletonJson.LinkedMesh(mesh, skinName, slotIndex, parent, inheritDeform)); - return mesh; - } - case AttachmentType.Path: { - bool closed = input.ReadBoolean(); - bool constantSpeed = input.ReadBoolean(); - int vertexCount = input.ReadInt(true); - Vertices vertices = ReadVertices(input, vertexCount); - float[] lengths = new float[vertexCount / 3]; - for (int i = 0, n = lengths.Length; i < n; i++) - lengths[i] = input.ReadFloat() * scale; - if (nonessential) input.ReadInt(); //int color = nonessential ? input.ReadInt() : 0; - - PathAttachment path = attachmentLoader.NewPathAttachment(skin, name); - if (path == null) return null; - path.closed = closed; - path.constantSpeed = constantSpeed; - path.worldVerticesLength = vertexCount << 1; - path.vertices = vertices.vertices; - path.bones = vertices.bones; - path.lengths = lengths; - // skipped porting: if (nonessential) Color.rgba8888ToColor(path.getColor(), color); - return path; - } - case AttachmentType.Point: { - float rotation = input.ReadFloat(); - float x = input.ReadFloat(); - float y = input.ReadFloat(); - if (nonessential) input.ReadInt(); //int color = nonessential ? input.ReadInt() : 0; - - PointAttachment point = attachmentLoader.NewPointAttachment(skin, name); - if (point == null) return null; - point.x = x * scale; - point.y = y * scale; - point.rotation = rotation; - // skipped porting: if (nonessential) point.color = color; - return point; - } - case AttachmentType.Clipping: { - int endSlotIndex = input.ReadInt(true); - int vertexCount = input.ReadInt(true); - Vertices vertices = ReadVertices(input, vertexCount); - if (nonessential) input.ReadInt(); - - ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name); - if (clip == null) return null; - clip.EndSlot = skeletonData.slots.Items[endSlotIndex]; - clip.worldVerticesLength = vertexCount << 1; - clip.vertices = vertices.vertices; - clip.bones = vertices.bones; - // skipped porting: if (nonessential) Color.rgba8888ToColor(clip.getColor(), color); - return clip; - } - } - return null; - } - - private Vertices ReadVertices (SkeletonInput input, int vertexCount) { - float scale = Scale; - int verticesLength = vertexCount << 1; - Vertices vertices = new Vertices(); - if(!input.ReadBoolean()) { - vertices.vertices = ReadFloatArray(input, verticesLength, scale); - return vertices; - } - var weights = new ExposedList(verticesLength * 3 * 3); - var bonesArray = new ExposedList(verticesLength * 3); - for (int i = 0; i < vertexCount; i++) { - int boneCount = input.ReadInt(true); - bonesArray.Add(boneCount); - for (int ii = 0; ii < boneCount; ii++) { - bonesArray.Add(input.ReadInt(true)); - weights.Add(input.ReadFloat() * scale); - weights.Add(input.ReadFloat() * scale); - weights.Add(input.ReadFloat()); - } - } - - vertices.vertices = weights.ToArray(); - vertices.bones = bonesArray.ToArray(); - return vertices; - } - - private float[] ReadFloatArray (SkeletonInput input, int n, float scale) { - float[] array = new float[n]; - if (scale == 1) { - for (int i = 0; i < n; i++) - array[i] = input.ReadFloat(); - } else { - for (int i = 0; i < n; i++) - array[i] = input.ReadFloat() * scale; - } - return array; - } - - private int[] ReadShortArray (SkeletonInput input) { - int n = input.ReadInt(true); - int[] array = new int[n]; - for (int i = 0; i < n; i++) - array[i] = (input.ReadByte() << 8) | input.ReadByte(); - return array; - } - - private Animation ReadAnimation (String name, SkeletonInput input, SkeletonData skeletonData) { - var timelines = new ExposedList(32); - float scale = Scale; - float duration = 0; - - // Slot timelines. - for (int i = 0, n = input.ReadInt(true); i < n; i++) { - int slotIndex = input.ReadInt(true); - for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) { - int timelineType = input.ReadByte(); - int frameCount = input.ReadInt(true); - switch (timelineType) { - case SLOT_ATTACHMENT: { - AttachmentTimeline timeline = new AttachmentTimeline(frameCount); - timeline.slotIndex = slotIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) - timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadStringRef()); - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[frameCount - 1]); - break; - } - case SLOT_COLOR: { - ColorTimeline timeline = new ColorTimeline(frameCount); - timeline.slotIndex = slotIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - float time = input.ReadFloat(); - int color = input.ReadInt(); - float r = ((color & 0xff000000) >> 24) / 255f; - float g = ((color & 0x00ff0000) >> 16) / 255f; - float b = ((color & 0x0000ff00) >> 8) / 255f; - float a = ((color & 0x000000ff)) / 255f; - timeline.SetFrame(frameIndex, time, r, g, b, a); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(frameCount - 1) * ColorTimeline.ENTRIES]); - break; - } - case SLOT_TWO_COLOR: { - TwoColorTimeline timeline = new TwoColorTimeline(frameCount); - timeline.slotIndex = slotIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - float time = input.ReadFloat(); - int color = input.ReadInt(); - float r = ((color & 0xff000000) >> 24) / 255f; - float g = ((color & 0x00ff0000) >> 16) / 255f; - float b = ((color & 0x0000ff00) >> 8) / 255f; - float a = ((color & 0x000000ff)) / 255f; - int color2 = input.ReadInt(); // 0x00rrggbb - float r2 = ((color2 & 0x00ff0000) >> 16) / 255f; - float g2 = ((color2 & 0x0000ff00) >> 8) / 255f; - float b2 = ((color2 & 0x000000ff)) / 255f; - - timeline.SetFrame(frameIndex, time, r, g, b, a, r2, g2, b2); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TwoColorTimeline.ENTRIES]); - break; - } - } - } - } - - // Bone timelines. - for (int i = 0, n = input.ReadInt(true); i < n; i++) { - int boneIndex = input.ReadInt(true); - for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) { - int timelineType = input.ReadByte(); - int frameCount = input.ReadInt(true); - switch (timelineType) { - case BONE_ROTATE: { - RotateTimeline timeline = new RotateTimeline(frameCount); - timeline.boneIndex = boneIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadFloat()); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(frameCount - 1) * RotateTimeline.ENTRIES]); - break; - } - case BONE_TRANSLATE: - case BONE_SCALE: - case BONE_SHEAR: { - TranslateTimeline timeline; - float timelineScale = 1; - if (timelineType == BONE_SCALE) - timeline = new ScaleTimeline(frameCount); - else if (timelineType == BONE_SHEAR) - timeline = new ShearTimeline(frameCount); - else { - timeline = new TranslateTimeline(frameCount); - timelineScale = scale; - } - timeline.boneIndex = boneIndex; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadFloat() * timelineScale, - input.ReadFloat() * timelineScale); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TranslateTimeline.ENTRIES]); - break; - } - } - } - } - - // IK constraint timelines. - for (int i = 0, n = input.ReadInt(true); i < n; i++) { - int index = input.ReadInt(true); - int frameCount = input.ReadInt(true); - IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount) { - ikConstraintIndex = index - }; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadFloat(), input.ReadFloat() * scale, input.ReadSByte(), input.ReadBoolean(), - input.ReadBoolean()); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(frameCount - 1) * IkConstraintTimeline.ENTRIES]); - } - - // Transform constraint timelines. - for (int i = 0, n = input.ReadInt(true); i < n; i++) { - int index = input.ReadInt(true); - int frameCount = input.ReadInt(true); - TransformConstraintTimeline timeline = new TransformConstraintTimeline(frameCount); - timeline.transformConstraintIndex = index; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadFloat(), input.ReadFloat(), input.ReadFloat(), - input.ReadFloat()); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TransformConstraintTimeline.ENTRIES]); - } - - // Path constraint timelines. - for (int i = 0, n = input.ReadInt(true); i < n; i++) { - int index = input.ReadInt(true); - PathConstraintData data = skeletonData.pathConstraints.Items[index]; - for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) { - int timelineType = input.ReadSByte(); - int frameCount = input.ReadInt(true); - switch(timelineType) { - case PATH_POSITION: - case PATH_SPACING: { - PathConstraintPositionTimeline timeline; - float timelineScale = 1; - if (timelineType == PATH_SPACING) { - timeline = new PathConstraintSpacingTimeline(frameCount); - if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale; - } else { - timeline = new PathConstraintPositionTimeline(frameCount); - if (data.positionMode == PositionMode.Fixed) timelineScale = scale; - } - timeline.pathConstraintIndex = index; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadFloat() * timelineScale); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintPositionTimeline.ENTRIES]); - break; - } - case PATH_MIX: { - PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(frameCount); - timeline.pathConstraintIndex = index; - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadFloat(), input.ReadFloat()); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintMixTimeline.ENTRIES]); - break; - } - } - } - } - - // Deform timelines. - for (int i = 0, n = input.ReadInt(true); i < n; i++) { - Skin skin = skeletonData.skins.Items[input.ReadInt(true)]; - for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) { - int slotIndex = input.ReadInt(true); - for (int iii = 0, nnn = input.ReadInt(true); iii < nnn; iii++) { - VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, input.ReadStringRef()); - bool weighted = attachment.bones != null; - float[] vertices = attachment.vertices; - int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length; - - int frameCount = input.ReadInt(true); - DeformTimeline timeline = new DeformTimeline(frameCount); - timeline.slotIndex = slotIndex; - timeline.attachment = attachment; - - for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) { - float time = input.ReadFloat(); - float[] deform; - int end = input.ReadInt(true); - if (end == 0) - deform = weighted ? new float[deformLength] : vertices; - else { - deform = new float[deformLength]; - int start = input.ReadInt(true); - end += start; - if (scale == 1) { - for (int v = start; v < end; v++) - deform[v] = input.ReadFloat(); - } else { - for (int v = start; v < end; v++) - deform[v] = input.ReadFloat() * scale; - } - if (!weighted) { - for (int v = 0, vn = deform.Length; v < vn; v++) - deform[v] += vertices[v]; - } - } - - timeline.SetFrame(frameIndex, time, deform); - if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[frameCount - 1]); - } - } - } - - // Draw order timeline. - int drawOrderCount = input.ReadInt(true); - if (drawOrderCount > 0) { - DrawOrderTimeline timeline = new DrawOrderTimeline(drawOrderCount); - int slotCount = skeletonData.slots.Count; - for (int i = 0; i < drawOrderCount; i++) { - float time = input.ReadFloat(); - int offsetCount = input.ReadInt(true); - int[] drawOrder = new int[slotCount]; - for (int ii = slotCount - 1; ii >= 0; ii--) - drawOrder[ii] = -1; - int[] unchanged = new int[slotCount - offsetCount]; - int originalIndex = 0, unchangedIndex = 0; - for (int ii = 0; ii < offsetCount; ii++) { - int slotIndex = input.ReadInt(true); - // Collect unchanged items. - while (originalIndex != slotIndex) - unchanged[unchangedIndex++] = originalIndex++; - // Set changed items. - drawOrder[originalIndex + input.ReadInt(true)] = originalIndex++; - } - // Collect remaining unchanged items. - while (originalIndex < slotCount) - unchanged[unchangedIndex++] = originalIndex++; - // Fill in unchanged items. - for (int ii = slotCount - 1; ii >= 0; ii--) - if (drawOrder[ii] == -1) drawOrder[ii] = unchanged[--unchangedIndex]; - timeline.SetFrame(i, time, drawOrder); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[drawOrderCount - 1]); - } - - // Event timeline. - int eventCount = input.ReadInt(true); - if (eventCount > 0) { - EventTimeline timeline = new EventTimeline(eventCount); - for (int i = 0; i < eventCount; i++) { - float time = input.ReadFloat(); - EventData eventData = skeletonData.events.Items[input.ReadInt(true)]; - Event e = new Event(time, eventData) { - Int = input.ReadInt(false), - Float = input.ReadFloat(), - String = input.ReadBoolean() ? input.ReadString() : eventData.String - }; - if (e.data.AudioPath != null) { - e.volume = input.ReadFloat(); - e.balance = input.ReadFloat(); - } - timeline.SetFrame(i, e); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[eventCount - 1]); - } - - timelines.TrimExcess(); - return new Animation(name, timelines, duration); - } - - private void ReadCurve (SkeletonInput input, int frameIndex, CurveTimeline timeline) { - switch (input.ReadByte()) { - case CURVE_STEPPED: - timeline.SetStepped(frameIndex); - break; - case CURVE_BEZIER: - timeline.SetCurve(frameIndex, input.ReadFloat(), input.ReadFloat(), input.ReadFloat(), input.ReadFloat()); - break; - } - } - - internal class Vertices - { - public int[] bones; - public float[] vertices; - } - - internal class SkeletonInput { - private byte[] chars = new byte[32]; - private byte[] bytesBigEndian = new byte[4]; - internal ExposedList strings; - Stream input; - - public SkeletonInput (Stream input) { - this.input = input; - } - - public byte ReadByte () { - return (byte)input.ReadByte(); - } - - public sbyte ReadSByte () { - int value = input.ReadByte(); - if (value == -1) throw new EndOfStreamException(); - return (sbyte)value; - } - - public bool ReadBoolean () { - return input.ReadByte() != 0; - } - - public float ReadFloat () { - input.Read(bytesBigEndian, 0, 4); - chars[3] = bytesBigEndian[0]; - chars[2] = bytesBigEndian[1]; - chars[1] = bytesBigEndian[2]; - chars[0] = bytesBigEndian[3]; - return BitConverter.ToSingle(chars, 0); - } - - public int ReadInt () { - input.Read(bytesBigEndian, 0, 4); - return (bytesBigEndian[0] << 24) - + (bytesBigEndian[1] << 16) - + (bytesBigEndian[2] << 8) - + bytesBigEndian[3]; - } - - public int ReadInt (bool optimizePositive) { - int b = input.ReadByte(); - int result = b & 0x7F; - if ((b & 0x80) != 0) { - b = input.ReadByte(); - result |= (b & 0x7F) << 7; - if ((b & 0x80) != 0) { - b = input.ReadByte(); - result |= (b & 0x7F) << 14; - if ((b & 0x80) != 0) { - b = input.ReadByte(); - result |= (b & 0x7F) << 21; - if ((b & 0x80) != 0) result |= (input.ReadByte() & 0x7F) << 28; - } - } - } - return optimizePositive ? result : ((result >> 1) ^ -(result & 1)); - } - - public string ReadString () { - int byteCount = ReadInt(true); - switch (byteCount) { - case 0: - return null; - case 1: - return ""; - } - byteCount--; - byte[] buffer = this.chars; - if (buffer.Length < byteCount) buffer = new byte[byteCount]; - ReadFully(buffer, 0, byteCount); - return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount); - } - - ///May be null. - public String ReadStringRef () { - int index = ReadInt(true); - return index == 0 ? null : strings.Items[index - 1]; - } - - public void ReadFully (byte[] buffer, int offset, int length) { - while (length > 0) { - int count = input.Read(buffer, offset, length); - if (count <= 0) throw new EndOfStreamException(); - offset += count; - length -= count; - } - } - - /// Returns the version string of binary skeleton data. - public string GetVersionString () { - try { - // Hash. - int byteCount = ReadInt(true); - if (byteCount > 1) input.Position += byteCount - 1; - - // Version. - byteCount = ReadInt(true); - if (byteCount > 1) { - byteCount--; - var buffer = new byte[byteCount]; - ReadFully(buffer, 0, byteCount); - return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount); - } - - throw new ArgumentException("Stream does not contain a valid binary Skeleton Data.", "input"); - } catch (Exception e) { - throw new ArgumentException("Stream does not contain a valid binary Skeleton Data.\n" + e, "input"); - } - } - } - } +#else + using (var input = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) + { +#endif + SkeletonData skeletonData = ReadSkeletonData(input); + skeletonData.name = Path.GetFileNameWithoutExtension(path); + return skeletonData; + } + } +#endif // WINDOWS_STOREAPP + + public static readonly TransformMode[] TransformModeValues = { + TransformMode.Normal, + TransformMode.OnlyTranslation, + TransformMode.NoRotationOrReflection, + TransformMode.NoScale, + TransformMode.NoScaleOrReflection + }; + + /// Returns the version string of binary skeleton data. + public static string GetVersionString(Stream file) + { + if (file == null) throw new ArgumentNullException("file"); + + SkeletonInput input = new SkeletonInput(file); + return input.GetVersionString(); + } + + public SkeletonData ReadSkeletonData(Stream file) + { + if (file == null) throw new ArgumentNullException("file"); + float scale = Scale; + + var skeletonData = new SkeletonData(); + SkeletonInput input = new SkeletonInput(file); + + skeletonData.hash = input.ReadString(); + if (skeletonData.hash.Length == 0) skeletonData.hash = null; + skeletonData.version = input.ReadString(); + if (skeletonData.version.Length == 0) skeletonData.version = null; + if ("3.8.75" == skeletonData.version) + throw new Exception("Unsupported skeleton data, please export with a newer version of Spine."); + skeletonData.x = input.ReadFloat(); + skeletonData.y = input.ReadFloat(); + skeletonData.width = input.ReadFloat(); + skeletonData.height = input.ReadFloat(); + + bool nonessential = input.ReadBoolean(); + + if (nonessential) + { + skeletonData.fps = input.ReadFloat(); + + skeletonData.imagesPath = input.ReadString(); + if (string.IsNullOrEmpty(skeletonData.imagesPath)) skeletonData.imagesPath = null; + + skeletonData.audioPath = input.ReadString(); + if (string.IsNullOrEmpty(skeletonData.audioPath)) skeletonData.audioPath = null; + } + + int n; + Object[] o; + + // Strings. + input.strings = new ExposedList(n = input.ReadInt(true)); + o = input.strings.Resize(n).Items; + for (int i = 0; i < n; i++) + o[i] = input.ReadString(); + + // Bones. + o = skeletonData.bones.Resize(n = input.ReadInt(true)).Items; + for (int i = 0; i < n; i++) + { + String name = input.ReadString(); + BoneData parent = i == 0 ? null : skeletonData.bones.Items[input.ReadInt(true)]; + BoneData data = new BoneData(i, name, parent); + data.rotation = input.ReadFloat(); + data.x = input.ReadFloat() * scale; + data.y = input.ReadFloat() * scale; + data.scaleX = input.ReadFloat(); + data.scaleY = input.ReadFloat(); + data.shearX = input.ReadFloat(); + data.shearY = input.ReadFloat(); + data.length = input.ReadFloat() * scale; + data.transformMode = TransformModeValues[input.ReadInt(true)]; + data.skinRequired = input.ReadBoolean(); + if (nonessential) input.ReadInt(); // Skip bone color. + o[i] = data; + } + + // Slots. + o = skeletonData.slots.Resize(n = input.ReadInt(true)).Items; + for (int i = 0; i < n; i++) + { + String slotName = input.ReadString(); + BoneData boneData = skeletonData.bones.Items[input.ReadInt(true)]; + SlotData slotData = new SlotData(i, slotName, boneData); + int color = input.ReadInt(); + slotData.r = ((color & 0xff000000) >> 24) / 255f; + slotData.g = ((color & 0x00ff0000) >> 16) / 255f; + slotData.b = ((color & 0x0000ff00) >> 8) / 255f; + slotData.a = ((color & 0x000000ff)) / 255f; + + int darkColor = input.ReadInt(); // 0x00rrggbb + if (darkColor != -1) + { + slotData.hasSecondColor = true; + slotData.r2 = ((darkColor & 0x00ff0000) >> 16) / 255f; + slotData.g2 = ((darkColor & 0x0000ff00) >> 8) / 255f; + slotData.b2 = ((darkColor & 0x000000ff)) / 255f; + } + + slotData.attachmentName = input.ReadStringRef(); + slotData.blendMode = (BlendMode)input.ReadInt(true); + o[i] = slotData; + } + + // IK constraints. + o = skeletonData.ikConstraints.Resize(n = input.ReadInt(true)).Items; + for (int i = 0, nn; i < n; i++) + { + IkConstraintData data = new IkConstraintData(input.ReadString()); + data.order = input.ReadInt(true); + data.skinRequired = input.ReadBoolean(); + Object[] bones = data.bones.Resize(nn = input.ReadInt(true)).Items; + for (int ii = 0; ii < nn; ii++) + bones[ii] = skeletonData.bones.Items[input.ReadInt(true)]; + data.target = skeletonData.bones.Items[input.ReadInt(true)]; + data.mix = input.ReadFloat(); + data.softness = input.ReadFloat() * scale; + data.bendDirection = input.ReadSByte(); + data.compress = input.ReadBoolean(); + data.stretch = input.ReadBoolean(); + data.uniform = input.ReadBoolean(); + o[i] = data; + } + + // Transform constraints. + o = skeletonData.transformConstraints.Resize(n = input.ReadInt(true)).Items; + for (int i = 0, nn; i < n; i++) + { + TransformConstraintData data = new TransformConstraintData(input.ReadString()); + data.order = input.ReadInt(true); + data.skinRequired = input.ReadBoolean(); + Object[] bones = data.bones.Resize(nn = input.ReadInt(true)).Items; + for (int ii = 0; ii < nn; ii++) + bones[ii] = skeletonData.bones.Items[input.ReadInt(true)]; + data.target = skeletonData.bones.Items[input.ReadInt(true)]; + data.local = input.ReadBoolean(); + data.relative = input.ReadBoolean(); + data.offsetRotation = input.ReadFloat(); + data.offsetX = input.ReadFloat() * scale; + data.offsetY = input.ReadFloat() * scale; + data.offsetScaleX = input.ReadFloat(); + data.offsetScaleY = input.ReadFloat(); + data.offsetShearY = input.ReadFloat(); + data.rotateMix = input.ReadFloat(); + data.translateMix = input.ReadFloat(); + data.scaleMix = input.ReadFloat(); + data.shearMix = input.ReadFloat(); + o[i] = data; + } + + // Path constraints + o = skeletonData.pathConstraints.Resize(n = input.ReadInt(true)).Items; + for (int i = 0, nn; i < n; i++) + { + PathConstraintData data = new PathConstraintData(input.ReadString()); + data.order = input.ReadInt(true); + data.skinRequired = input.ReadBoolean(); + Object[] bones = data.bones.Resize(nn = input.ReadInt(true)).Items; + for (int ii = 0; ii < nn; ii++) + bones[ii] = skeletonData.bones.Items[input.ReadInt(true)]; + data.target = skeletonData.slots.Items[input.ReadInt(true)]; + data.positionMode = (PositionMode)Enum.GetValues(typeof(PositionMode)).GetValue(input.ReadInt(true)); + data.spacingMode = (SpacingMode)Enum.GetValues(typeof(SpacingMode)).GetValue(input.ReadInt(true)); + data.rotateMode = (RotateMode)Enum.GetValues(typeof(RotateMode)).GetValue(input.ReadInt(true)); + data.offsetRotation = input.ReadFloat(); + data.position = input.ReadFloat(); + if (data.positionMode == PositionMode.Fixed) data.position *= scale; + data.spacing = input.ReadFloat(); + if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale; + data.rotateMix = input.ReadFloat(); + data.translateMix = input.ReadFloat(); + o[i] = data; + } + + // Default skin. + Skin defaultSkin = ReadSkin(input, skeletonData, true, nonessential); + if (defaultSkin != null) + { + skeletonData.defaultSkin = defaultSkin; + skeletonData.skins.Add(defaultSkin); + } + + // Skins. + { + int i = skeletonData.skins.Count; + o = skeletonData.skins.Resize(n = i + input.ReadInt(true)).Items; + for (; i < n; i++) + o[i] = ReadSkin(input, skeletonData, false, nonessential); + } + + // Linked meshes. + n = linkedMeshes.Count; + for (int i = 0; i < n; i++) + { + SkeletonJson.LinkedMesh linkedMesh = linkedMeshes[i]; + Skin skin = linkedMesh.skin == null ? skeletonData.DefaultSkin : skeletonData.FindSkin(linkedMesh.skin); + if (skin == null) throw new Exception("Skin not found: " + linkedMesh.skin); + Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); + if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); + linkedMesh.mesh.DeformAttachment = linkedMesh.inheritDeform ? (VertexAttachment)parent : linkedMesh.mesh; + linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; + linkedMesh.mesh.UpdateUVs(); + } + linkedMeshes.Clear(); + + // Events. + o = skeletonData.events.Resize(n = input.ReadInt(true)).Items; + for (int i = 0; i < n; i++) + { + EventData data = new EventData(input.ReadStringRef()); + data.Int = input.ReadInt(false); + data.Float = input.ReadFloat(); + data.String = input.ReadString(); + data.AudioPath = input.ReadString(); + if (data.AudioPath != null) + { + data.Volume = input.ReadFloat(); + data.Balance = input.ReadFloat(); + } + o[i] = data; + } + + // Animations. + o = skeletonData.animations.Resize(n = input.ReadInt(true)).Items; + for (int i = 0; i < n; i++) + o[i] = ReadAnimation(input.ReadString(), input, skeletonData); + + return skeletonData; + } + + + /// May be null. + private Skin ReadSkin(SkeletonInput input, SkeletonData skeletonData, bool defaultSkin, bool nonessential) + { + + Skin skin; + int slotCount; + + if (defaultSkin) + { + slotCount = input.ReadInt(true); + if (slotCount == 0) return null; + skin = new Skin("default"); + } + else + { + skin = new Skin(input.ReadStringRef()); + Object[] bones = skin.bones.Resize(input.ReadInt(true)).Items; + for (int i = 0, n = skin.bones.Count; i < n; i++) + bones[i] = skeletonData.bones.Items[input.ReadInt(true)]; + + for (int i = 0, n = input.ReadInt(true); i < n; i++) + skin.constraints.Add(skeletonData.ikConstraints.Items[input.ReadInt(true)]); + for (int i = 0, n = input.ReadInt(true); i < n; i++) + skin.constraints.Add(skeletonData.transformConstraints.Items[input.ReadInt(true)]); + for (int i = 0, n = input.ReadInt(true); i < n; i++) + skin.constraints.Add(skeletonData.pathConstraints.Items[input.ReadInt(true)]); + skin.constraints.TrimExcess(); + slotCount = input.ReadInt(true); + } + for (int i = 0; i < slotCount; i++) + { + int slotIndex = input.ReadInt(true); + for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) + { + String name = input.ReadStringRef(); + Attachment attachment = ReadAttachment(input, skeletonData, skin, slotIndex, name, nonessential); + if (attachment != null) skin.SetAttachment(slotIndex, name, attachment); + } + } + return skin; + } + + private Attachment ReadAttachment(SkeletonInput input, SkeletonData skeletonData, Skin skin, int slotIndex, + String attachmentName, bool nonessential) + { + + float scale = Scale; + + String name = input.ReadStringRef(); + if (name == null) name = attachmentName; + + AttachmentType type = (AttachmentType)input.ReadByte(); + switch (type) + { + case AttachmentType.Region: + { + String path = input.ReadStringRef(); + float rotation = input.ReadFloat(); + float x = input.ReadFloat(); + float y = input.ReadFloat(); + float scaleX = input.ReadFloat(); + float scaleY = input.ReadFloat(); + float width = input.ReadFloat(); + float height = input.ReadFloat(); + int color = input.ReadInt(); + + if (path == null) path = name; + RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); + if (region == null) return null; + region.Path = path; + region.x = x * scale; + region.y = y * scale; + region.scaleX = scaleX; + region.scaleY = scaleY; + region.rotation = rotation; + region.width = width * scale; + region.height = height * scale; + region.r = ((color & 0xff000000) >> 24) / 255f; + region.g = ((color & 0x00ff0000) >> 16) / 255f; + region.b = ((color & 0x0000ff00) >> 8) / 255f; + region.a = ((color & 0x000000ff)) / 255f; + region.UpdateOffset(); + return region; + } + case AttachmentType.Boundingbox: + { + int vertexCount = input.ReadInt(true); + Vertices vertices = ReadVertices(input, vertexCount); + if (nonessential) input.ReadInt(); //int color = nonessential ? input.ReadInt() : 0; // Avoid unused local warning. + + BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); + if (box == null) return null; + box.worldVerticesLength = vertexCount << 1; + box.vertices = vertices.vertices; + box.bones = vertices.bones; + // skipped porting: if (nonessential) Color.rgba8888ToColor(box.getColor(), color); + return box; + } + case AttachmentType.Mesh: + { + String path = input.ReadStringRef(); + int color = input.ReadInt(); + int vertexCount = input.ReadInt(true); + float[] uvs = ReadFloatArray(input, vertexCount << 1, 1); + int[] triangles = ReadShortArray(input); + Vertices vertices = ReadVertices(input, vertexCount); + int hullLength = input.ReadInt(true); + int[] edges = null; + float width = 0, height = 0; + if (nonessential) + { + edges = ReadShortArray(input); + width = input.ReadFloat(); + height = input.ReadFloat(); + } + + if (path == null) path = name; + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); + if (mesh == null) return null; + mesh.Path = path; + mesh.r = ((color & 0xff000000) >> 24) / 255f; + mesh.g = ((color & 0x00ff0000) >> 16) / 255f; + mesh.b = ((color & 0x0000ff00) >> 8) / 255f; + mesh.a = ((color & 0x000000ff)) / 255f; + mesh.bones = vertices.bones; + mesh.vertices = vertices.vertices; + mesh.WorldVerticesLength = vertexCount << 1; + mesh.triangles = triangles; + mesh.regionUVs = uvs; + mesh.UpdateUVs(); + mesh.HullLength = hullLength << 1; + if (nonessential) + { + mesh.Edges = edges; + mesh.Width = width * scale; + mesh.Height = height * scale; + } + return mesh; + } + case AttachmentType.Linkedmesh: + { + String path = input.ReadStringRef(); + int color = input.ReadInt(); + String skinName = input.ReadStringRef(); + String parent = input.ReadStringRef(); + bool inheritDeform = input.ReadBoolean(); + float width = 0, height = 0; + if (nonessential) + { + width = input.ReadFloat(); + height = input.ReadFloat(); + } + + if (path == null) path = name; + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); + if (mesh == null) return null; + mesh.Path = path; + mesh.r = ((color & 0xff000000) >> 24) / 255f; + mesh.g = ((color & 0x00ff0000) >> 16) / 255f; + mesh.b = ((color & 0x0000ff00) >> 8) / 255f; + mesh.a = ((color & 0x000000ff)) / 255f; + if (nonessential) + { + mesh.Width = width * scale; + mesh.Height = height * scale; + } + linkedMeshes.Add(new SkeletonJson.LinkedMesh(mesh, skinName, slotIndex, parent, inheritDeform)); + return mesh; + } + case AttachmentType.Path: + { + bool closed = input.ReadBoolean(); + bool constantSpeed = input.ReadBoolean(); + int vertexCount = input.ReadInt(true); + Vertices vertices = ReadVertices(input, vertexCount); + float[] lengths = new float[vertexCount / 3]; + for (int i = 0, n = lengths.Length; i < n; i++) + lengths[i] = input.ReadFloat() * scale; + if (nonessential) input.ReadInt(); //int color = nonessential ? input.ReadInt() : 0; + + PathAttachment path = attachmentLoader.NewPathAttachment(skin, name); + if (path == null) return null; + path.closed = closed; + path.constantSpeed = constantSpeed; + path.worldVerticesLength = vertexCount << 1; + path.vertices = vertices.vertices; + path.bones = vertices.bones; + path.lengths = lengths; + // skipped porting: if (nonessential) Color.rgba8888ToColor(path.getColor(), color); + return path; + } + case AttachmentType.Point: + { + float rotation = input.ReadFloat(); + float x = input.ReadFloat(); + float y = input.ReadFloat(); + if (nonessential) input.ReadInt(); //int color = nonessential ? input.ReadInt() : 0; + + PointAttachment point = attachmentLoader.NewPointAttachment(skin, name); + if (point == null) return null; + point.x = x * scale; + point.y = y * scale; + point.rotation = rotation; + // skipped porting: if (nonessential) point.color = color; + return point; + } + case AttachmentType.Clipping: + { + int endSlotIndex = input.ReadInt(true); + int vertexCount = input.ReadInt(true); + Vertices vertices = ReadVertices(input, vertexCount); + if (nonessential) input.ReadInt(); + + ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name); + if (clip == null) return null; + clip.EndSlot = skeletonData.slots.Items[endSlotIndex]; + clip.worldVerticesLength = vertexCount << 1; + clip.vertices = vertices.vertices; + clip.bones = vertices.bones; + // skipped porting: if (nonessential) Color.rgba8888ToColor(clip.getColor(), color); + return clip; + } + } + return null; + } + + private Vertices ReadVertices(SkeletonInput input, int vertexCount) + { + float scale = Scale; + int verticesLength = vertexCount << 1; + Vertices vertices = new Vertices(); + if (!input.ReadBoolean()) + { + vertices.vertices = ReadFloatArray(input, verticesLength, scale); + return vertices; + } + var weights = new ExposedList(verticesLength * 3 * 3); + var bonesArray = new ExposedList(verticesLength * 3); + for (int i = 0; i < vertexCount; i++) + { + int boneCount = input.ReadInt(true); + bonesArray.Add(boneCount); + for (int ii = 0; ii < boneCount; ii++) + { + bonesArray.Add(input.ReadInt(true)); + weights.Add(input.ReadFloat() * scale); + weights.Add(input.ReadFloat() * scale); + weights.Add(input.ReadFloat()); + } + } + + vertices.vertices = weights.ToArray(); + vertices.bones = bonesArray.ToArray(); + return vertices; + } + + private float[] ReadFloatArray(SkeletonInput input, int n, float scale) + { + float[] array = new float[n]; + if (scale == 1) + { + for (int i = 0; i < n; i++) + array[i] = input.ReadFloat(); + } + else + { + for (int i = 0; i < n; i++) + array[i] = input.ReadFloat() * scale; + } + return array; + } + + private int[] ReadShortArray(SkeletonInput input) + { + int n = input.ReadInt(true); + int[] array = new int[n]; + for (int i = 0; i < n; i++) + array[i] = (input.ReadByte() << 8) | input.ReadByte(); + return array; + } + + private Animation ReadAnimation(String name, SkeletonInput input, SkeletonData skeletonData) + { + var timelines = new ExposedList(32); + float scale = Scale; + float duration = 0; + + // Slot timelines. + for (int i = 0, n = input.ReadInt(true); i < n; i++) + { + int slotIndex = input.ReadInt(true); + for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) + { + int timelineType = input.ReadByte(); + int frameCount = input.ReadInt(true); + switch (timelineType) + { + case SLOT_ATTACHMENT: + { + AttachmentTimeline timeline = new AttachmentTimeline(frameCount); + timeline.slotIndex = slotIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadStringRef()); + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[frameCount - 1]); + break; + } + case SLOT_COLOR: + { + ColorTimeline timeline = new ColorTimeline(frameCount); + timeline.slotIndex = slotIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + float time = input.ReadFloat(); + int color = input.ReadInt(); + float r = ((color & 0xff000000) >> 24) / 255f; + float g = ((color & 0x00ff0000) >> 16) / 255f; + float b = ((color & 0x0000ff00) >> 8) / 255f; + float a = ((color & 0x000000ff)) / 255f; + timeline.SetFrame(frameIndex, time, r, g, b, a); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(frameCount - 1) * ColorTimeline.ENTRIES]); + break; + } + case SLOT_TWO_COLOR: + { + TwoColorTimeline timeline = new TwoColorTimeline(frameCount); + timeline.slotIndex = slotIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + float time = input.ReadFloat(); + int color = input.ReadInt(); + float r = ((color & 0xff000000) >> 24) / 255f; + float g = ((color & 0x00ff0000) >> 16) / 255f; + float b = ((color & 0x0000ff00) >> 8) / 255f; + float a = ((color & 0x000000ff)) / 255f; + int color2 = input.ReadInt(); // 0x00rrggbb + float r2 = ((color2 & 0x00ff0000) >> 16) / 255f; + float g2 = ((color2 & 0x0000ff00) >> 8) / 255f; + float b2 = ((color2 & 0x000000ff)) / 255f; + + timeline.SetFrame(frameIndex, time, r, g, b, a, r2, g2, b2); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TwoColorTimeline.ENTRIES]); + break; + } + } + } + } + + // Bone timelines. + for (int i = 0, n = input.ReadInt(true); i < n; i++) + { + int boneIndex = input.ReadInt(true); + for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) + { + int timelineType = input.ReadByte(); + int frameCount = input.ReadInt(true); + switch (timelineType) + { + case BONE_ROTATE: + { + RotateTimeline timeline = new RotateTimeline(frameCount); + timeline.boneIndex = boneIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadFloat()); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(frameCount - 1) * RotateTimeline.ENTRIES]); + break; + } + case BONE_TRANSLATE: + case BONE_SCALE: + case BONE_SHEAR: + { + TranslateTimeline timeline; + float timelineScale = 1; + if (timelineType == BONE_SCALE) + timeline = new ScaleTimeline(frameCount); + else if (timelineType == BONE_SHEAR) + timeline = new ShearTimeline(frameCount); + else + { + timeline = new TranslateTimeline(frameCount); + timelineScale = scale; + } + timeline.boneIndex = boneIndex; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadFloat() * timelineScale, + input.ReadFloat() * timelineScale); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TranslateTimeline.ENTRIES]); + break; + } + } + } + } + + // IK constraint timelines. + for (int i = 0, n = input.ReadInt(true); i < n; i++) + { + int index = input.ReadInt(true); + int frameCount = input.ReadInt(true); + IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount) + { + ikConstraintIndex = index + }; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadFloat(), input.ReadFloat() * scale, input.ReadSByte(), input.ReadBoolean(), + input.ReadBoolean()); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(frameCount - 1) * IkConstraintTimeline.ENTRIES]); + } + + // Transform constraint timelines. + for (int i = 0, n = input.ReadInt(true); i < n; i++) + { + int index = input.ReadInt(true); + int frameCount = input.ReadInt(true); + TransformConstraintTimeline timeline = new TransformConstraintTimeline(frameCount); + timeline.transformConstraintIndex = index; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadFloat(), input.ReadFloat(), input.ReadFloat(), + input.ReadFloat()); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TransformConstraintTimeline.ENTRIES]); + } + + // Path constraint timelines. + for (int i = 0, n = input.ReadInt(true); i < n; i++) + { + int index = input.ReadInt(true); + PathConstraintData data = skeletonData.pathConstraints.Items[index]; + for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) + { + int timelineType = input.ReadSByte(); + int frameCount = input.ReadInt(true); + switch (timelineType) + { + case PATH_POSITION: + case PATH_SPACING: + { + PathConstraintPositionTimeline timeline; + float timelineScale = 1; + if (timelineType == PATH_SPACING) + { + timeline = new PathConstraintSpacingTimeline(frameCount); + if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale; + } + else + { + timeline = new PathConstraintPositionTimeline(frameCount); + if (data.positionMode == PositionMode.Fixed) timelineScale = scale; + } + timeline.pathConstraintIndex = index; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadFloat() * timelineScale); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintPositionTimeline.ENTRIES]); + break; + } + case PATH_MIX: + { + PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(frameCount); + timeline.pathConstraintIndex = index; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadFloat(), input.ReadFloat()); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintMixTimeline.ENTRIES]); + break; + } + } + } + } + + // Deform timelines. + for (int i = 0, n = input.ReadInt(true); i < n; i++) + { + Skin skin = skeletonData.skins.Items[input.ReadInt(true)]; + for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) + { + int slotIndex = input.ReadInt(true); + for (int iii = 0, nnn = input.ReadInt(true); iii < nnn; iii++) + { + VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, input.ReadStringRef()); + bool weighted = attachment.bones != null; + float[] vertices = attachment.vertices; + int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length; + + int frameCount = input.ReadInt(true); + DeformTimeline timeline = new DeformTimeline(frameCount); + timeline.slotIndex = slotIndex; + timeline.attachment = attachment; + + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + float time = input.ReadFloat(); + float[] deform; + int end = input.ReadInt(true); + if (end == 0) + deform = weighted ? new float[deformLength] : vertices; + else + { + deform = new float[deformLength]; + int start = input.ReadInt(true); + end += start; + if (scale == 1) + { + for (int v = start; v < end; v++) + deform[v] = input.ReadFloat(); + } + else + { + for (int v = start; v < end; v++) + deform[v] = input.ReadFloat() * scale; + } + if (!weighted) + { + for (int v = 0, vn = deform.Length; v < vn; v++) + deform[v] += vertices[v]; + } + } + + timeline.SetFrame(frameIndex, time, deform); + if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[frameCount - 1]); + } + } + } + + // Draw order timeline. + int drawOrderCount = input.ReadInt(true); + if (drawOrderCount > 0) + { + DrawOrderTimeline timeline = new DrawOrderTimeline(drawOrderCount); + int slotCount = skeletonData.slots.Count; + for (int i = 0; i < drawOrderCount; i++) + { + float time = input.ReadFloat(); + int offsetCount = input.ReadInt(true); + int[] drawOrder = new int[slotCount]; + for (int ii = slotCount - 1; ii >= 0; ii--) + drawOrder[ii] = -1; + int[] unchanged = new int[slotCount - offsetCount]; + int originalIndex = 0, unchangedIndex = 0; + for (int ii = 0; ii < offsetCount; ii++) + { + int slotIndex = input.ReadInt(true); + // Collect unchanged items. + while (originalIndex != slotIndex) + unchanged[unchangedIndex++] = originalIndex++; + // Set changed items. + drawOrder[originalIndex + input.ReadInt(true)] = originalIndex++; + } + // Collect remaining unchanged items. + while (originalIndex < slotCount) + unchanged[unchangedIndex++] = originalIndex++; + // Fill in unchanged items. + for (int ii = slotCount - 1; ii >= 0; ii--) + if (drawOrder[ii] == -1) drawOrder[ii] = unchanged[--unchangedIndex]; + timeline.SetFrame(i, time, drawOrder); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[drawOrderCount - 1]); + } + + // Event timeline. + int eventCount = input.ReadInt(true); + if (eventCount > 0) + { + EventTimeline timeline = new EventTimeline(eventCount); + for (int i = 0; i < eventCount; i++) + { + float time = input.ReadFloat(); + EventData eventData = skeletonData.events.Items[input.ReadInt(true)]; + Event e = new Event(time, eventData) + { + Int = input.ReadInt(false), + Float = input.ReadFloat(), + String = input.ReadBoolean() ? input.ReadString() : eventData.String + }; + if (e.data.AudioPath != null) + { + e.volume = input.ReadFloat(); + e.balance = input.ReadFloat(); + } + timeline.SetFrame(i, e); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[eventCount - 1]); + } + + timelines.TrimExcess(); + return new Animation(name, timelines, duration); + } + + private void ReadCurve(SkeletonInput input, int frameIndex, CurveTimeline timeline) + { + switch (input.ReadByte()) + { + case CURVE_STEPPED: + timeline.SetStepped(frameIndex); + break; + case CURVE_BEZIER: + timeline.SetCurve(frameIndex, input.ReadFloat(), input.ReadFloat(), input.ReadFloat(), input.ReadFloat()); + break; + } + } + + internal class Vertices + { + public int[] bones; + public float[] vertices; + } + + internal class SkeletonInput + { + private byte[] chars = new byte[32]; + private byte[] bytesBigEndian = new byte[4]; + internal ExposedList strings; + Stream input; + + public SkeletonInput(Stream input) + { + this.input = input; + } + + public byte ReadByte() + { + return (byte)input.ReadByte(); + } + + public sbyte ReadSByte() + { + int value = input.ReadByte(); + if (value == -1) throw new EndOfStreamException(); + return (sbyte)value; + } + + public bool ReadBoolean() + { + return input.ReadByte() != 0; + } + + public float ReadFloat() + { + input.Read(bytesBigEndian, 0, 4); + chars[3] = bytesBigEndian[0]; + chars[2] = bytesBigEndian[1]; + chars[1] = bytesBigEndian[2]; + chars[0] = bytesBigEndian[3]; + return BitConverter.ToSingle(chars, 0); + } + + public int ReadInt() + { + input.Read(bytesBigEndian, 0, 4); + return (bytesBigEndian[0] << 24) + + (bytesBigEndian[1] << 16) + + (bytesBigEndian[2] << 8) + + bytesBigEndian[3]; + } + + public int ReadInt(bool optimizePositive) + { + int b = input.ReadByte(); + int result = b & 0x7F; + if ((b & 0x80) != 0) + { + b = input.ReadByte(); + result |= (b & 0x7F) << 7; + if ((b & 0x80) != 0) + { + b = input.ReadByte(); + result |= (b & 0x7F) << 14; + if ((b & 0x80) != 0) + { + b = input.ReadByte(); + result |= (b & 0x7F) << 21; + if ((b & 0x80) != 0) result |= (input.ReadByte() & 0x7F) << 28; + } + } + } + return optimizePositive ? result : ((result >> 1) ^ -(result & 1)); + } + + public string ReadString() + { + int byteCount = ReadInt(true); + switch (byteCount) + { + case 0: + return null; + case 1: + return ""; + } + byteCount--; + byte[] buffer = this.chars; + if (buffer.Length < byteCount) buffer = new byte[byteCount]; + ReadFully(buffer, 0, byteCount); + return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount); + } + + ///May be null. + public String ReadStringRef() + { + int index = ReadInt(true); + return index == 0 ? null : strings.Items[index - 1]; + } + + public void ReadFully(byte[] buffer, int offset, int length) + { + while (length > 0) + { + int count = input.Read(buffer, offset, length); + if (count <= 0) throw new EndOfStreamException(); + offset += count; + length -= count; + } + } + + /// Returns the version string of binary skeleton data. + public string GetVersionString() + { + try + { + // Hash. + int byteCount = ReadInt(true); + if (byteCount > 1) input.Position += byteCount - 1; + + // Version. + byteCount = ReadInt(true); + if (byteCount > 1) + { + byteCount--; + var buffer = new byte[byteCount]; + ReadFully(buffer, 0, byteCount); + return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount); + } + + throw new ArgumentException("Stream does not contain a valid binary Skeleton Data.", "input"); + } + catch (Exception e) + { + throw new ArgumentException("Stream does not contain a valid binary Skeleton Data.\n" + e, "input"); + } + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/SkeletonBounds.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/SkeletonBounds.cs index f87a03b..ad91ae7 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/SkeletonBounds.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/SkeletonBounds.cs @@ -29,206 +29,233 @@ using System; -namespace Spine3_8_95 { - - /// - /// 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. - /// - public class SkeletonBounds { - private ExposedList polygonPool = new ExposedList(); - private float minX, minY, maxX, maxY; - - public ExposedList BoundingBoxes { get; private set; } - public ExposedList Polygons { get; private set; } - public float MinX { get { return minX; } set { minX = value; } } - public float MinY { get { return minY; } set { minY = value; } } - public float MaxX { get { return maxX; } set { maxX = value; } } - public float MaxY { get { return maxY; } set { maxY = value; } } - public float Width { get { return maxX - minX; } } - public float Height { get { return maxY - minY; } } - - public SkeletonBounds () { - BoundingBoxes = new ExposedList(); - Polygons = new ExposedList(); - } - - /// - /// Clears any previous polygons, finds all visible bounding box attachments, - /// and computes the world vertices for each bounding box's polygon. - /// The skeleton. - /// - /// If true, the axis aligned bounding box containing all the polygons is computed. - /// If false, the SkeletonBounds AABB methods will always return true. - /// - public void Update (Skeleton skeleton, bool updateAabb) { - ExposedList boundingBoxes = BoundingBoxes; - ExposedList polygons = Polygons; - ExposedList slots = skeleton.slots; - int slotCount = slots.Count; - - boundingBoxes.Clear(); - for (int i = 0, n = polygons.Count; i < n; i++) - polygonPool.Add(polygons.Items[i]); - polygons.Clear(); - - for (int i = 0; i < slotCount; i++) { - Slot slot = slots.Items[i]; - if (!slot.bone.active) continue; - BoundingBoxAttachment boundingBox = slot.attachment as BoundingBoxAttachment; - if (boundingBox == null) continue; - boundingBoxes.Add(boundingBox); - - Polygon polygon = null; - int poolCount = polygonPool.Count; - if (poolCount > 0) { - polygon = polygonPool.Items[poolCount - 1]; - polygonPool.RemoveAt(poolCount - 1); - } else - polygon = new Polygon(); - polygons.Add(polygon); - - int count = boundingBox.worldVerticesLength; - polygon.Count = count; - if (polygon.Vertices.Length < count) polygon.Vertices = new float[count]; - boundingBox.ComputeWorldVertices(slot, polygon.Vertices); - } - - if (updateAabb) { - AabbCompute(); - } else { - minX = int.MinValue; - minY = int.MinValue; - maxX = int.MaxValue; - maxY = int.MaxValue; - } - } - - private void AabbCompute () { - float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; - ExposedList polygons = Polygons; - for (int i = 0, n = polygons.Count; i < n; i++) { - Polygon polygon = polygons.Items[i]; - float[] vertices = polygon.Vertices; - for (int ii = 0, nn = polygon.Count; ii < nn; ii += 2) { - float x = vertices[ii]; - float y = vertices[ii + 1]; - minX = Math.Min(minX, x); - minY = Math.Min(minY, y); - maxX = Math.Max(maxX, x); - maxY = Math.Max(maxY, y); - } - } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; - } - - - /// Returns true if the axis aligned bounding box contains the point. - public bool AabbContainsPoint (float x, float y) { - return x >= minX && x <= maxX && y >= minY && y <= maxY; - } - - /// Returns true if the axis aligned bounding box intersects the line segment. - public bool AabbIntersectsSegment (float x1, float y1, float x2, float y2) { - float minX = this.minX; - float minY = this.minY; - float maxX = this.maxX; - float maxY = this.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; - } - - /// Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds. - public bool AabbIntersectsSkeleton (SkeletonBounds bounds) { - return minX < bounds.maxX && maxX > bounds.minX && minY < bounds.maxY && maxY > bounds.minY; - } - - /// Returns true if the polygon contains the point. - public bool ContainsPoint (Polygon polygon, float x, float y) { - float[] 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; - } - - /// 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. - public BoundingBoxAttachment ContainsPoint (float x, float y) { - ExposedList polygons = Polygons; - for (int i = 0, n = polygons.Count; i < n; i++) - if (ContainsPoint(polygons.Items[i], x, y)) return BoundingBoxes.Items[i]; - return null; - } - - /// 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. - public BoundingBoxAttachment IntersectsSegment (float x1, float y1, float x2, float y2) { - ExposedList polygons = Polygons; - for (int i = 0, n = polygons.Count; i < n; i++) - if (IntersectsSegment(polygons.Items[i], x1, y1, x2, y2)) return BoundingBoxes.Items[i]; - return null; - } - - /// Returns true if the polygon contains the line segment. - public bool IntersectsSegment (Polygon polygon, float x1, float y1, float x2, float y2) { - float[] vertices = polygon.Vertices; - int 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 (int 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; - } - - public Polygon GetPolygon (BoundingBoxAttachment attachment) { - int index = BoundingBoxes.IndexOf(attachment); - return index == -1 ? null : Polygons.Items[index]; - } - } - - public class Polygon { - public float[] Vertices { get; set; } - public int Count { get; set; } - - public Polygon () { - Vertices = new float[16]; - } - } +namespace Spine3_8_95 +{ + + /// + /// 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. + /// + public class SkeletonBounds + { + private ExposedList polygonPool = new ExposedList(); + private float minX, minY, maxX, maxY; + + public ExposedList BoundingBoxes { get; private set; } + public ExposedList Polygons { get; private set; } + public float MinX { get { return minX; } set { minX = value; } } + public float MinY { get { return minY; } set { minY = value; } } + public float MaxX { get { return maxX; } set { maxX = value; } } + public float MaxY { get { return maxY; } set { maxY = value; } } + public float Width { get { return maxX - minX; } } + public float Height { get { return maxY - minY; } } + + public SkeletonBounds() + { + BoundingBoxes = new ExposedList(); + Polygons = new ExposedList(); + } + + /// + /// Clears any previous polygons, finds all visible bounding box attachments, + /// and computes the world vertices for each bounding box's polygon. + /// The skeleton. + /// + /// If true, the axis aligned bounding box containing all the polygons is computed. + /// If false, the SkeletonBounds AABB methods will always return true. + /// + public void Update(Skeleton skeleton, bool updateAabb) + { + ExposedList boundingBoxes = BoundingBoxes; + ExposedList polygons = Polygons; + ExposedList slots = skeleton.slots; + int slotCount = slots.Count; + + boundingBoxes.Clear(); + for (int i = 0, n = polygons.Count; i < n; i++) + polygonPool.Add(polygons.Items[i]); + polygons.Clear(); + + for (int i = 0; i < slotCount; i++) + { + Slot slot = slots.Items[i]; + if (!slot.bone.active) continue; + BoundingBoxAttachment boundingBox = slot.attachment as BoundingBoxAttachment; + if (boundingBox == null) continue; + boundingBoxes.Add(boundingBox); + + Polygon polygon = null; + int poolCount = polygonPool.Count; + if (poolCount > 0) + { + polygon = polygonPool.Items[poolCount - 1]; + polygonPool.RemoveAt(poolCount - 1); + } + else + polygon = new Polygon(); + polygons.Add(polygon); + + int count = boundingBox.worldVerticesLength; + polygon.Count = count; + if (polygon.Vertices.Length < count) polygon.Vertices = new float[count]; + boundingBox.ComputeWorldVertices(slot, polygon.Vertices); + } + + if (updateAabb) + { + AabbCompute(); + } + else + { + minX = int.MinValue; + minY = int.MinValue; + maxX = int.MaxValue; + maxY = int.MaxValue; + } + } + + private void AabbCompute() + { + float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; + ExposedList polygons = Polygons; + for (int i = 0, n = polygons.Count; i < n; i++) + { + Polygon polygon = polygons.Items[i]; + float[] vertices = polygon.Vertices; + for (int ii = 0, nn = polygon.Count; ii < nn; ii += 2) + { + float x = vertices[ii]; + float y = vertices[ii + 1]; + minX = Math.Min(minX, x); + minY = Math.Min(minY, y); + maxX = Math.Max(maxX, x); + maxY = Math.Max(maxY, y); + } + } + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + + /// Returns true if the axis aligned bounding box contains the point. + public bool AabbContainsPoint(float x, float y) + { + return x >= minX && x <= maxX && y >= minY && y <= maxY; + } + + /// Returns true if the axis aligned bounding box intersects the line segment. + public bool AabbIntersectsSegment(float x1, float y1, float x2, float y2) + { + float minX = this.minX; + float minY = this.minY; + float maxX = this.maxX; + float maxY = this.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; + } + + /// Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds. + public bool AabbIntersectsSkeleton(SkeletonBounds bounds) + { + return minX < bounds.maxX && maxX > bounds.minX && minY < bounds.maxY && maxY > bounds.minY; + } + + /// Returns true if the polygon contains the point. + public bool ContainsPoint(Polygon polygon, float x, float y) + { + float[] 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; + } + + /// 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. + public BoundingBoxAttachment ContainsPoint(float x, float y) + { + ExposedList polygons = Polygons; + for (int i = 0, n = polygons.Count; i < n; i++) + if (ContainsPoint(polygons.Items[i], x, y)) return BoundingBoxes.Items[i]; + return null; + } + + /// 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. + public BoundingBoxAttachment IntersectsSegment(float x1, float y1, float x2, float y2) + { + ExposedList polygons = Polygons; + for (int i = 0, n = polygons.Count; i < n; i++) + if (IntersectsSegment(polygons.Items[i], x1, y1, x2, y2)) return BoundingBoxes.Items[i]; + return null; + } + + /// Returns true if the polygon contains the line segment. + public bool IntersectsSegment(Polygon polygon, float x1, float y1, float x2, float y2) + { + float[] vertices = polygon.Vertices; + int 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 (int 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; + } + + public Polygon GetPolygon(BoundingBoxAttachment attachment) + { + int index = BoundingBoxes.IndexOf(attachment); + return index == -1 ? null : Polygons.Items[index]; + } + } + + public class Polygon + { + public float[] Vertices { get; set; } + public int Count { get; set; } + + public Polygon() + { + Vertices = new float[16]; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/SkeletonClipping.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/SkeletonClipping.cs index 40d2fe4..9275a42 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/SkeletonClipping.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/SkeletonClipping.cs @@ -29,268 +29,304 @@ using System; -namespace Spine3_8_95 { - public class SkeletonClipping { - internal readonly Triangulator triangulator = new Triangulator(); - internal readonly ExposedList clippingPolygon = new ExposedList(); - internal readonly ExposedList clipOutput = new ExposedList(128); - internal readonly ExposedList clippedVertices = new ExposedList(128); - internal readonly ExposedList clippedTriangles = new ExposedList(128); - internal readonly ExposedList clippedUVs = new ExposedList(128); - internal readonly ExposedList scratch = new ExposedList(); - - internal ClippingAttachment clipAttachment; - internal ExposedList> clippingPolygons; - - public ExposedList ClippedVertices { get { return clippedVertices; } } - public ExposedList ClippedTriangles { get { return clippedTriangles; } } - public ExposedList ClippedUVs { get { return clippedUVs; } } - - public bool IsClipping { get { return clipAttachment != null; } } - - public int ClipStart (Slot slot, ClippingAttachment clip) { - if (clipAttachment != null) return 0; - clipAttachment = clip; - - int n = clip.worldVerticesLength; - float[] vertices = clippingPolygon.Resize(n).Items; - clip.ComputeWorldVertices(slot, 0, n, vertices, 0, 2); - MakeClockwise(clippingPolygon); - clippingPolygons = triangulator.Decompose(clippingPolygon, triangulator.Triangulate(clippingPolygon)); - foreach (var polygon in clippingPolygons) { - MakeClockwise(polygon); - polygon.Add(polygon.Items[0]); - polygon.Add(polygon.Items[1]); - } - return clippingPolygons.Count; - } - - public void ClipEnd (Slot slot) { - if (clipAttachment != null && clipAttachment.endSlot == slot.data) ClipEnd(); - } - - public void ClipEnd () { - if (clipAttachment == null) return; - clipAttachment = null; - clippingPolygons = null; - clippedVertices.Clear(); - clippedTriangles.Clear(); - clippingPolygon.Clear(); - } - - public void ClipTriangles (float[] vertices, int verticesLength, int[] triangles, int trianglesLength, float[] uvs) { - ExposedList clipOutput = this.clipOutput, clippedVertices = this.clippedVertices; - var clippedTriangles = this.clippedTriangles; - var polygons = clippingPolygons.Items; - int polygonsCount = clippingPolygons.Count; - - int index = 0; - clippedVertices.Clear(); - clippedUVs.Clear(); - clippedTriangles.Clear(); - //outer: - for (int i = 0; i < trianglesLength; i += 3) { - int vertexOffset = triangles[i] << 1; - float x1 = vertices[vertexOffset], y1 = vertices[vertexOffset + 1]; - float u1 = uvs[vertexOffset], v1 = uvs[vertexOffset + 1]; - - vertexOffset = triangles[i + 1] << 1; - float x2 = vertices[vertexOffset], y2 = vertices[vertexOffset + 1]; - float u2 = uvs[vertexOffset], v2 = uvs[vertexOffset + 1]; - - vertexOffset = triangles[i + 2] << 1; - float x3 = vertices[vertexOffset], y3 = vertices[vertexOffset + 1]; - float u3 = uvs[vertexOffset], v3 = uvs[vertexOffset + 1]; - - for (int p = 0; p < polygonsCount; p++) { - int s = clippedVertices.Count; - if (Clip(x1, y1, x2, y2, x3, y3, polygons[p], clipOutput)) { - int clipOutputLength = clipOutput.Count; - 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)); - - int clipOutputCount = clipOutputLength >> 1; - float[] clipOutputItems = clipOutput.Items; - float[] clippedVerticesItems = clippedVertices.Resize(s + clipOutputCount * 2).Items; - float[] clippedUVsItems = clippedUVs.Resize(s + clipOutputCount * 2).Items; - for (int ii = 0; ii < clipOutputLength; ii += 2) { - float x = clipOutputItems[ii], y = clipOutputItems[ii + 1]; - clippedVerticesItems[s] = x; - clippedVerticesItems[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; - clippedUVsItems[s] = u1 * a + u2 * b + u3 * c; - clippedUVsItems[s + 1] = v1 * a + v2 * b + v3 * c; - s += 2; - } - - s = clippedTriangles.Count; - int[] clippedTrianglesItems = clippedTriangles.Resize(s + 3 * (clipOutputCount - 2)).Items; - clipOutputCount--; - for (int ii = 1; ii < clipOutputCount; ii++) { - clippedTrianglesItems[s] = index; - clippedTrianglesItems[s + 1] = index + ii; - clippedTrianglesItems[s + 2] = index + ii + 1; - s += 3; - } - index += clipOutputCount + 1; - } - else { - float[] clippedVerticesItems = clippedVertices.Resize(s + 3 * 2).Items; - float[] clippedUVsItems = clippedUVs.Resize(s + 3 * 2).Items; - clippedVerticesItems[s] = x1; - clippedVerticesItems[s + 1] = y1; - clippedVerticesItems[s + 2] = x2; - clippedVerticesItems[s + 3] = y2; - clippedVerticesItems[s + 4] = x3; - clippedVerticesItems[s + 5] = y3; - - clippedUVsItems[s] = u1; - clippedUVsItems[s + 1] = v1; - clippedUVsItems[s + 2] = u2; - clippedUVsItems[s + 3] = v2; - clippedUVsItems[s + 4] = u3; - clippedUVsItems[s + 5] = v3; - - s = clippedTriangles.Count; - int[] clippedTrianglesItems = clippedTriangles.Resize(s + 3).Items; - clippedTrianglesItems[s] = index; - clippedTrianglesItems[s + 1] = index + 1; - clippedTrianglesItems[s + 2] = index + 2; - index += 3; - break; //continue outer; - } - } - } - - } - - /** Clips the input triangle against the convex, clockwise clipping area. If the triangle lies entirely within the clipping +namespace Spine3_8_95 +{ + public class SkeletonClipping + { + internal readonly Triangulator triangulator = new Triangulator(); + internal readonly ExposedList clippingPolygon = new ExposedList(); + internal readonly ExposedList clipOutput = new ExposedList(128); + internal readonly ExposedList clippedVertices = new ExposedList(128); + internal readonly ExposedList clippedTriangles = new ExposedList(128); + internal readonly ExposedList clippedUVs = new ExposedList(128); + internal readonly ExposedList scratch = new ExposedList(); + + internal ClippingAttachment clipAttachment; + internal ExposedList> clippingPolygons; + + public ExposedList ClippedVertices { get { return clippedVertices; } } + public ExposedList ClippedTriangles { get { return clippedTriangles; } } + public ExposedList ClippedUVs { get { return clippedUVs; } } + + public bool IsClipping { get { return clipAttachment != null; } } + + public int ClipStart(Slot slot, ClippingAttachment clip) + { + if (clipAttachment != null) return 0; + clipAttachment = clip; + + int n = clip.worldVerticesLength; + float[] vertices = clippingPolygon.Resize(n).Items; + clip.ComputeWorldVertices(slot, 0, n, vertices, 0, 2); + MakeClockwise(clippingPolygon); + clippingPolygons = triangulator.Decompose(clippingPolygon, triangulator.Triangulate(clippingPolygon)); + foreach (var polygon in clippingPolygons) + { + MakeClockwise(polygon); + polygon.Add(polygon.Items[0]); + polygon.Add(polygon.Items[1]); + } + return clippingPolygons.Count; + } + + public void ClipEnd(Slot slot) + { + if (clipAttachment != null && clipAttachment.endSlot == slot.data) ClipEnd(); + } + + public void ClipEnd() + { + if (clipAttachment == null) return; + clipAttachment = null; + clippingPolygons = null; + clippedVertices.Clear(); + clippedTriangles.Clear(); + clippingPolygon.Clear(); + } + + public void ClipTriangles(float[] vertices, int verticesLength, int[] triangles, int trianglesLength, float[] uvs) + { + ExposedList clipOutput = this.clipOutput, clippedVertices = this.clippedVertices; + var clippedTriangles = this.clippedTriangles; + var polygons = clippingPolygons.Items; + int polygonsCount = clippingPolygons.Count; + + int index = 0; + clippedVertices.Clear(); + clippedUVs.Clear(); + clippedTriangles.Clear(); + //outer: + for (int i = 0; i < trianglesLength; i += 3) + { + int vertexOffset = triangles[i] << 1; + float x1 = vertices[vertexOffset], y1 = vertices[vertexOffset + 1]; + float u1 = uvs[vertexOffset], v1 = uvs[vertexOffset + 1]; + + vertexOffset = triangles[i + 1] << 1; + float x2 = vertices[vertexOffset], y2 = vertices[vertexOffset + 1]; + float u2 = uvs[vertexOffset], v2 = uvs[vertexOffset + 1]; + + vertexOffset = triangles[i + 2] << 1; + float x3 = vertices[vertexOffset], y3 = vertices[vertexOffset + 1]; + float u3 = uvs[vertexOffset], v3 = uvs[vertexOffset + 1]; + + for (int p = 0; p < polygonsCount; p++) + { + int s = clippedVertices.Count; + if (Clip(x1, y1, x2, y2, x3, y3, polygons[p], clipOutput)) + { + int clipOutputLength = clipOutput.Count; + 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)); + + int clipOutputCount = clipOutputLength >> 1; + float[] clipOutputItems = clipOutput.Items; + float[] clippedVerticesItems = clippedVertices.Resize(s + clipOutputCount * 2).Items; + float[] clippedUVsItems = clippedUVs.Resize(s + clipOutputCount * 2).Items; + for (int ii = 0; ii < clipOutputLength; ii += 2) + { + float x = clipOutputItems[ii], y = clipOutputItems[ii + 1]; + clippedVerticesItems[s] = x; + clippedVerticesItems[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; + clippedUVsItems[s] = u1 * a + u2 * b + u3 * c; + clippedUVsItems[s + 1] = v1 * a + v2 * b + v3 * c; + s += 2; + } + + s = clippedTriangles.Count; + int[] clippedTrianglesItems = clippedTriangles.Resize(s + 3 * (clipOutputCount - 2)).Items; + clipOutputCount--; + for (int ii = 1; ii < clipOutputCount; ii++) + { + clippedTrianglesItems[s] = index; + clippedTrianglesItems[s + 1] = index + ii; + clippedTrianglesItems[s + 2] = index + ii + 1; + s += 3; + } + index += clipOutputCount + 1; + } + else + { + float[] clippedVerticesItems = clippedVertices.Resize(s + 3 * 2).Items; + float[] clippedUVsItems = clippedUVs.Resize(s + 3 * 2).Items; + clippedVerticesItems[s] = x1; + clippedVerticesItems[s + 1] = y1; + clippedVerticesItems[s + 2] = x2; + clippedVerticesItems[s + 3] = y2; + clippedVerticesItems[s + 4] = x3; + clippedVerticesItems[s + 5] = y3; + + clippedUVsItems[s] = u1; + clippedUVsItems[s + 1] = v1; + clippedUVsItems[s + 2] = u2; + clippedUVsItems[s + 3] = v2; + clippedUVsItems[s + 4] = u3; + clippedUVsItems[s + 5] = v3; + + s = clippedTriangles.Count; + int[] clippedTrianglesItems = clippedTriangles.Resize(s + 3).Items; + clippedTrianglesItems[s] = index; + clippedTrianglesItems[s + 1] = index + 1; + clippedTrianglesItems[s + 2] = index + 2; + index += 3; + break; //continue outer; + } + } + } + + } + + /** 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. */ - internal bool Clip (float x1, float y1, float x2, float y2, float x3, float y3, ExposedList clippingArea, ExposedList output) { - var originalOutput = output; - var clipped = false; - - // Avoid copy at the end. - ExposedList input = null; - if (clippingArea.Count % 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(); - - float[] clippingVertices = clippingArea.Items; - int clippingVerticesLast = clippingArea.Count - 4; - for (int i = 0; ; i += 2) { - float edgeX = clippingVertices[i], edgeY = clippingVertices[i + 1]; - float edgeX2 = clippingVertices[i + 2], edgeY2 = clippingVertices[i + 3]; - float deltaX = edgeX - edgeX2, deltaY = edgeY - edgeY2; - - float[] inputVertices = input.Items; - int inputVerticesLength = input.Count - 2, outputStart = output.Count; - for (int ii = 0; ii < inputVerticesLength; ii += 2) { - float inputX = inputVertices[ii], inputY = inputVertices[ii + 1]; - float inputX2 = inputVertices[ii + 2], inputY2 = inputVertices[ii + 3]; - bool side2 = deltaX * (inputY2 - edgeY2) - deltaY * (inputX2 - edgeX2) > 0; - if (deltaX * (inputY - edgeY2) - deltaY * (inputX - edgeX2) > 0) { - if (side2) { // v1 inside, v2 inside - output.Add(inputX2); - output.Add(inputY2); - continue; - } - // v1 inside, v2 outside - float c0 = inputY2 - inputY, c2 = inputX2 - inputX; - float s = c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY); - if (Math.Abs(s) > 0.000001f) { - float ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / s; - output.Add(edgeX + (edgeX2 - edgeX) * ua); - output.Add(edgeY + (edgeY2 - edgeY) * ua); - } else { - output.Add(edgeX); - output.Add(edgeY); - } - } - else if (side2) { // v1 outside, v2 inside - float c0 = inputY2 - inputY, c2 = inputX2 - inputX; - float s = c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY); - if (Math.Abs(s) > 0.000001f) { - float ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / s; - output.Add(edgeX + (edgeX2 - edgeX) * ua); - output.Add(edgeY + (edgeY2 - edgeY) * ua); - } else { - output.Add(edgeX); - output.Add(edgeY); - } - output.Add(inputX2); - output.Add(inputY2); - } - clipped = true; - } - - if (outputStart == output.Count) { // All edges outside. - originalOutput.Clear(); - return true; - } - - output.Add(output.Items[0]); - output.Add(output.Items[1]); - - if (i == clippingVerticesLast) break; - var temp = output; - output = input; - output.Clear(); - input = temp; - } - - if (originalOutput != output) { - originalOutput.Clear(); - for (int i = 0, n = output.Count - 2; i < n; i++) { - originalOutput.Add(output.Items[i]); - } - } else { - originalOutput.Resize(originalOutput.Count - 2); - } - - return clipped; - } - - public static void MakeClockwise (ExposedList polygon) { - float[] vertices = polygon.Items; - int verticeslength = polygon.Count; - - float area = vertices[verticeslength - 2] * vertices[1] - vertices[0] * vertices[verticeslength - 1], p1x, p1y, p2x, p2y; - for (int i = 0, n = verticeslength - 3; i < n; i += 2) { - p1x = vertices[i]; - p1y = vertices[i + 1]; - p2x = vertices[i + 2]; - p2y = vertices[i + 3]; - area += p1x * p2y - p2x * p1y; - } - if (area < 0) return; - - for (int i = 0, lastX = verticeslength - 2, n = verticeslength >> 1; i < n; i += 2) { - float x = vertices[i], y = vertices[i + 1]; - int other = lastX - i; - vertices[i] = vertices[other]; - vertices[i + 1] = vertices[other + 1]; - vertices[other] = x; - vertices[other + 1] = y; - } - } - } + internal bool Clip(float x1, float y1, float x2, float y2, float x3, float y3, ExposedList clippingArea, ExposedList output) + { + var originalOutput = output; + var clipped = false; + + // Avoid copy at the end. + ExposedList input = null; + if (clippingArea.Count % 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(); + + float[] clippingVertices = clippingArea.Items; + int clippingVerticesLast = clippingArea.Count - 4; + for (int i = 0; ; i += 2) + { + float edgeX = clippingVertices[i], edgeY = clippingVertices[i + 1]; + float edgeX2 = clippingVertices[i + 2], edgeY2 = clippingVertices[i + 3]; + float deltaX = edgeX - edgeX2, deltaY = edgeY - edgeY2; + + float[] inputVertices = input.Items; + int inputVerticesLength = input.Count - 2, outputStart = output.Count; + for (int ii = 0; ii < inputVerticesLength; ii += 2) + { + float inputX = inputVertices[ii], inputY = inputVertices[ii + 1]; + float inputX2 = inputVertices[ii + 2], inputY2 = inputVertices[ii + 3]; + bool side2 = deltaX * (inputY2 - edgeY2) - deltaY * (inputX2 - edgeX2) > 0; + if (deltaX * (inputY - edgeY2) - deltaY * (inputX - edgeX2) > 0) + { + if (side2) + { // v1 inside, v2 inside + output.Add(inputX2); + output.Add(inputY2); + continue; + } + // v1 inside, v2 outside + float c0 = inputY2 - inputY, c2 = inputX2 - inputX; + float s = c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY); + if (Math.Abs(s) > 0.000001f) + { + float ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / s; + output.Add(edgeX + (edgeX2 - edgeX) * ua); + output.Add(edgeY + (edgeY2 - edgeY) * ua); + } + else + { + output.Add(edgeX); + output.Add(edgeY); + } + } + else if (side2) + { // v1 outside, v2 inside + float c0 = inputY2 - inputY, c2 = inputX2 - inputX; + float s = c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY); + if (Math.Abs(s) > 0.000001f) + { + float ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / s; + output.Add(edgeX + (edgeX2 - edgeX) * ua); + output.Add(edgeY + (edgeY2 - edgeY) * ua); + } + else + { + output.Add(edgeX); + output.Add(edgeY); + } + output.Add(inputX2); + output.Add(inputY2); + } + clipped = true; + } + + if (outputStart == output.Count) + { // All edges outside. + originalOutput.Clear(); + return true; + } + + output.Add(output.Items[0]); + output.Add(output.Items[1]); + + if (i == clippingVerticesLast) break; + var temp = output; + output = input; + output.Clear(); + input = temp; + } + + if (originalOutput != output) + { + originalOutput.Clear(); + for (int i = 0, n = output.Count - 2; i < n; i++) + { + originalOutput.Add(output.Items[i]); + } + } + else + { + originalOutput.Resize(originalOutput.Count - 2); + } + + return clipped; + } + + public static void MakeClockwise(ExposedList polygon) + { + float[] vertices = polygon.Items; + int verticeslength = polygon.Count; + + float area = vertices[verticeslength - 2] * vertices[1] - vertices[0] * vertices[verticeslength - 1], p1x, p1y, p2x, p2y; + for (int i = 0, n = verticeslength - 3; i < n; i += 2) + { + p1x = vertices[i]; + p1y = vertices[i + 1]; + p2x = vertices[i + 2]; + p2y = vertices[i + 3]; + area += p1x * p2y - p2x * p1y; + } + if (area < 0) return; + + for (int i = 0, lastX = verticeslength - 2, n = verticeslength >> 1; i < n; i += 2) + { + float x = vertices[i], y = vertices[i + 1]; + int other = lastX - i; + vertices[i] = vertices[other]; + vertices[i + 1] = vertices[other + 1]; + vertices[other] = x; + vertices[other + 1] = y; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/SkeletonData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/SkeletonData.cs index 4c1d832..e5aa89d 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/SkeletonData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/SkeletonData.cs @@ -29,202 +29,222 @@ using System; -namespace Spine3_8_95 { - - /// Stores the setup pose and all of the stateless data for a skeleton. - public class SkeletonData { - internal string name; - internal ExposedList bones = new ExposedList(); // Ordered parents first - internal ExposedList slots = new ExposedList(); // Setup pose draw order. - internal ExposedList skins = new ExposedList(); - internal Skin defaultSkin; - internal ExposedList events = new ExposedList(); - internal ExposedList animations = new ExposedList(); - internal ExposedList ikConstraints = new ExposedList(); - internal ExposedList transformConstraints = new ExposedList(); - internal ExposedList pathConstraints = new ExposedList(); - internal float x , y, width, height; - internal string version, hash; - - // Nonessential. - internal float fps; - internal string imagesPath, audioPath; - - public string Name { get { return name; } set { name = value; } } - - /// The skeleton's bones, sorted parent first. The root bone is always the first bone. - public ExposedList Bones { get { return bones; } } - - public ExposedList Slots { get { return slots; } } - - /// All skins, including the default skin. - public ExposedList Skins { get { return skins; } set { skins = value; } } - - /// - /// The skeleton's default skin. - /// By default this skin contains all attachments that were not in a skin in Spine. - /// - /// May be null. - public Skin DefaultSkin { get { return defaultSkin; } set { defaultSkin = value; } } - - public ExposedList Events { get { return events; } set { events = value; } } - public ExposedList Animations { get { return animations; } set { animations = value; } } - public ExposedList IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } } - public ExposedList TransformConstraints { get { return transformConstraints; } set { transformConstraints = value; } } - public ExposedList PathConstraints { get { return pathConstraints; } set { pathConstraints = value; } } - - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public float Width { get { return width; } set { width = value; } } - public float Height { get { return height; } set { height = value; } } - /// The Spine version used to export this data, or null. - public string Version { get { return version; } set { version = value; } } - public string Hash { get { return hash; } set { hash = value; } } - - /// The path to the images directory as defined in Spine. Available only when nonessential data was exported. May be null - public string ImagesPath { get { return imagesPath; } set { imagesPath = value; } } - - /// The path to the audio directory defined in Spine. Available only when nonessential data was exported. May be null. - public string AudioPath { get { return audioPath; } set { audioPath = value; } } - - /// - /// The dopesheet FPS in Spine. Available only when nonessential data was exported. - public float Fps { get { return fps; } set { fps = value; } } - - // --- Bones. - - /// - /// 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. - /// May be null. - public BoneData FindBone (string boneName) { - if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); - var bones = this.bones; - var bonesItems = bones.Items; - for (int i = 0, n = bones.Count; i < n; i++) { - BoneData bone = bonesItems[i]; - if (bone.name == boneName) return bone; - } - return null; - } - - /// -1 if the bone was not found. - public int FindBoneIndex (string boneName) { - if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); - var bones = this.bones; - var bonesItems = bones.Items; - for (int i = 0, n = bones.Count; i < n; i++) - if (bonesItems[i].name == boneName) return i; - return -1; - } - - // --- Slots. - - /// May be null. - public SlotData FindSlot (string slotName) { - if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); - ExposedList slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) { - SlotData slot = slots.Items[i]; - if (slot.name == slotName) return slot; - } - return null; - } - - /// -1 if the slot was not found. - public int FindSlotIndex (string slotName) { - if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); - ExposedList slots = this.slots; - for (int i = 0, n = slots.Count; i < n; i++) - if (slots.Items[i].name == slotName) return i; - return -1; - } - - // --- Skins. - - /// May be null. - public Skin FindSkin (string skinName) { - if (skinName == null) throw new ArgumentNullException("skinName", "skinName cannot be null."); - foreach (Skin skin in skins) - if (skin.name == skinName) return skin; - return null; - } - - // --- Events. - - /// May be null. - public EventData FindEvent (string eventDataName) { - if (eventDataName == null) throw new ArgumentNullException("eventDataName", "eventDataName cannot be null."); - foreach (EventData eventData in events) - if (eventData.name == eventDataName) return eventData; - return null; - } - - // --- Animations. - - /// May be null. - public Animation FindAnimation (string animationName) { - if (animationName == null) throw new ArgumentNullException("animationName", "animationName cannot be null."); - ExposedList animations = this.animations; - for (int i = 0, n = animations.Count; i < n; i++) { - Animation animation = animations.Items[i]; - if (animation.name == animationName) return animation; - } - return null; - } - - // --- IK constraints. - - /// May be null. - public IkConstraintData FindIkConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - ExposedList ikConstraints = this.ikConstraints; - for (int i = 0, n = ikConstraints.Count; i < n; i++) { - IkConstraintData ikConstraint = ikConstraints.Items[i]; - if (ikConstraint.name == constraintName) return ikConstraint; - } - return null; - } - - // --- Transform constraints. - - /// May be null. - public TransformConstraintData FindTransformConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - ExposedList transformConstraints = this.transformConstraints; - for (int i = 0, n = transformConstraints.Count; i < n; i++) { - TransformConstraintData transformConstraint = transformConstraints.Items[i]; - if (transformConstraint.name == constraintName) return transformConstraint; - } - return null; - } - - // --- Path constraints. - - /// May be null. - public PathConstraintData FindPathConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - ExposedList pathConstraints = this.pathConstraints; - for (int i = 0, n = pathConstraints.Count; i < n; i++) { - PathConstraintData constraint = pathConstraints.Items[i]; - if (constraint.name.Equals(constraintName)) return constraint; - } - return null; - } - - /// -1 if the path constraint was not found. - public int FindPathConstraintIndex (string pathConstraintName) { - if (pathConstraintName == null) throw new ArgumentNullException("pathConstraintName", "pathConstraintName cannot be null."); - ExposedList pathConstraints = this.pathConstraints; - for (int i = 0, n = pathConstraints.Count; i < n; i++) - if (pathConstraints.Items[i].name.Equals(pathConstraintName)) return i; - return -1; - } - - // --- - - override public string ToString () { - return name ?? base.ToString(); - } - } +namespace Spine3_8_95 +{ + + /// Stores the setup pose and all of the stateless data for a skeleton. + public class SkeletonData + { + internal string name; + internal ExposedList bones = new ExposedList(); // Ordered parents first + internal ExposedList slots = new ExposedList(); // Setup pose draw order. + internal ExposedList skins = new ExposedList(); + internal Skin defaultSkin; + internal ExposedList events = new ExposedList(); + internal ExposedList animations = new ExposedList(); + internal ExposedList ikConstraints = new ExposedList(); + internal ExposedList transformConstraints = new ExposedList(); + internal ExposedList pathConstraints = new ExposedList(); + internal float x, y, width, height; + internal string version, hash; + + // Nonessential. + internal float fps; + internal string imagesPath, audioPath; + + public string Name { get { return name; } set { name = value; } } + + /// The skeleton's bones, sorted parent first. The root bone is always the first bone. + public ExposedList Bones { get { return bones; } } + + public ExposedList Slots { get { return slots; } } + + /// All skins, including the default skin. + public ExposedList Skins { get { return skins; } set { skins = value; } } + + /// + /// The skeleton's default skin. + /// By default this skin contains all attachments that were not in a skin in Spine. + /// + /// May be null. + public Skin DefaultSkin { get { return defaultSkin; } set { defaultSkin = value; } } + + public ExposedList Events { get { return events; } set { events = value; } } + public ExposedList Animations { get { return animations; } set { animations = value; } } + public ExposedList IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } } + public ExposedList TransformConstraints { get { return transformConstraints; } set { transformConstraints = value; } } + public ExposedList PathConstraints { get { return pathConstraints; } set { pathConstraints = value; } } + + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public float Width { get { return width; } set { width = value; } } + public float Height { get { return height; } set { height = value; } } + /// The Spine version used to export this data, or null. + public string Version { get { return version; } set { version = value; } } + public string Hash { get { return hash; } set { hash = value; } } + + /// The path to the images directory as defined in Spine. Available only when nonessential data was exported. May be null + public string ImagesPath { get { return imagesPath; } set { imagesPath = value; } } + + /// The path to the audio directory defined in Spine. Available only when nonessential data was exported. May be null. + public string AudioPath { get { return audioPath; } set { audioPath = value; } } + + /// + /// The dopesheet FPS in Spine. Available only when nonessential data was exported. + public float Fps { get { return fps; } set { fps = value; } } + + // --- Bones. + + /// + /// 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. + /// May be null. + public BoneData FindBone(string boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); + var bones = this.bones; + var bonesItems = bones.Items; + for (int i = 0, n = bones.Count; i < n; i++) + { + BoneData bone = bonesItems[i]; + if (bone.name == boneName) return bone; + } + return null; + } + + /// -1 if the bone was not found. + public int FindBoneIndex(string boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); + var bones = this.bones; + var bonesItems = bones.Items; + for (int i = 0, n = bones.Count; i < n; i++) + if (bonesItems[i].name == boneName) return i; + return -1; + } + + // --- Slots. + + /// May be null. + public SlotData FindSlot(string slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + ExposedList slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + { + SlotData slot = slots.Items[i]; + if (slot.name == slotName) return slot; + } + return null; + } + + /// -1 if the slot was not found. + public int FindSlotIndex(string slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + ExposedList slots = this.slots; + for (int i = 0, n = slots.Count; i < n; i++) + if (slots.Items[i].name == slotName) return i; + return -1; + } + + // --- Skins. + + /// May be null. + public Skin FindSkin(string skinName) + { + if (skinName == null) throw new ArgumentNullException("skinName", "skinName cannot be null."); + foreach (Skin skin in skins) + if (skin.name == skinName) return skin; + return null; + } + + // --- Events. + + /// May be null. + public EventData FindEvent(string eventDataName) + { + if (eventDataName == null) throw new ArgumentNullException("eventDataName", "eventDataName cannot be null."); + foreach (EventData eventData in events) + if (eventData.name == eventDataName) return eventData; + return null; + } + + // --- Animations. + + /// May be null. + public Animation FindAnimation(string animationName) + { + if (animationName == null) throw new ArgumentNullException("animationName", "animationName cannot be null."); + ExposedList animations = this.animations; + for (int i = 0, n = animations.Count; i < n; i++) + { + Animation animation = animations.Items[i]; + if (animation.name == animationName) return animation; + } + return null; + } + + // --- IK constraints. + + /// May be null. + public IkConstraintData FindIkConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + ExposedList ikConstraints = this.ikConstraints; + for (int i = 0, n = ikConstraints.Count; i < n; i++) + { + IkConstraintData ikConstraint = ikConstraints.Items[i]; + if (ikConstraint.name == constraintName) return ikConstraint; + } + return null; + } + + // --- Transform constraints. + + /// May be null. + public TransformConstraintData FindTransformConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + ExposedList transformConstraints = this.transformConstraints; + for (int i = 0, n = transformConstraints.Count; i < n; i++) + { + TransformConstraintData transformConstraint = transformConstraints.Items[i]; + if (transformConstraint.name == constraintName) return transformConstraint; + } + return null; + } + + // --- Path constraints. + + /// May be null. + public PathConstraintData FindPathConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + ExposedList pathConstraints = this.pathConstraints; + for (int i = 0, n = pathConstraints.Count; i < n; i++) + { + PathConstraintData constraint = pathConstraints.Items[i]; + if (constraint.name.Equals(constraintName)) return constraint; + } + return null; + } + + /// -1 if the path constraint was not found. + public int FindPathConstraintIndex(string pathConstraintName) + { + if (pathConstraintName == null) throw new ArgumentNullException("pathConstraintName", "pathConstraintName cannot be null."); + ExposedList pathConstraints = this.pathConstraints; + for (int i = 0, n = pathConstraints.Count; i < n; i++) + if (pathConstraints.Items[i].name.Equals(pathConstraintName)) return i; + return -1; + } + + // --- + + override public string ToString() + { + return name ?? base.ToString(); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/SkeletonJson.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/SkeletonJson.cs index 17103a9..38a9adf 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/SkeletonJson.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/SkeletonJson.cs @@ -32,32 +32,36 @@ #endif using System; -using System.IO; using System.Collections.Generic; +using System.IO; #if WINDOWS_STOREAPP using System.Threading.Tasks; using Windows.Storage; #endif -namespace Spine3_8_95 { - public class SkeletonJson { - public float Scale { get; set; } +namespace Spine3_8_95 +{ + public class SkeletonJson + { + public float Scale { get; set; } - private AttachmentLoader attachmentLoader; - private List linkedMeshes = new List(); + private AttachmentLoader attachmentLoader; + private List linkedMeshes = new List(); - public SkeletonJson (params Atlas[] atlasArray) - : this(new AtlasAttachmentLoader(atlasArray)) { - } + public SkeletonJson(params Atlas[] atlasArray) + : this(new AtlasAttachmentLoader(atlasArray)) + { + } - public SkeletonJson (AttachmentLoader attachmentLoader) { - if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader", "attachmentLoader cannot be null."); - this.attachmentLoader = attachmentLoader; - Scale = 1; - } + public SkeletonJson(AttachmentLoader attachmentLoader) + { + if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader", "attachmentLoader cannot be null."); + this.attachmentLoader = attachmentLoader; + Scale = 1; + } - #if !IS_UNITY && WINDOWS_STOREAPP +#if !IS_UNITY && WINDOWS_STOREAPP private async Task ReadFile(string path) { var folder = Windows.ApplicationModel.Package.Current.InstalledLocation; var file = await folder.GetFileAsync(path).AsTask().ConfigureAwait(false); @@ -71,846 +75,980 @@ private async Task ReadFile(string path) { public SkeletonData ReadSkeletonData (string path) { return this.ReadFile(path).Result; } - #else - public SkeletonData ReadSkeletonData (string path) { - #if WINDOWS_PHONE +#else + public SkeletonData ReadSkeletonData(string path) + { +#if WINDOWS_PHONE using (var reader = new StreamReader(Microsoft.Xna.Framework.TitleContainer.OpenStream(path))) { - #else - using (var reader = new StreamReader(new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))) { - #endif - SkeletonData skeletonData = ReadSkeletonData(reader); - skeletonData.name = Path.GetFileNameWithoutExtension(path); - return skeletonData; - } - } - #endif - - public SkeletonData ReadSkeletonData (TextReader reader) { - if (reader == null) throw new ArgumentNullException("reader", "reader cannot be null."); - - float scale = this.Scale; - var skeletonData = new SkeletonData(); - - var root = Json.Deserialize(reader) as Dictionary; - if (root == null) throw new Exception("Invalid JSON."); - - // Skeleton. - if (root.ContainsKey("skeleton")) { - var skeletonMap = (Dictionary)root["skeleton"]; - skeletonData.hash = (string)skeletonMap["hash"]; - skeletonData.version = (string)skeletonMap["spine"]; - skeletonData.x = GetFloat(skeletonMap, "x", 0); - skeletonData.y = GetFloat(skeletonMap, "y", 0); - skeletonData.width = GetFloat(skeletonMap, "width", 0); - skeletonData.height = GetFloat(skeletonMap, "height", 0); - skeletonData.fps = GetFloat(skeletonMap, "fps", 30); - skeletonData.imagesPath = GetString(skeletonMap, "images", null); - skeletonData.audioPath = GetString(skeletonMap, "audio", null); - } - - // Bones. - if (root.ContainsKey("bones")) { - foreach (Dictionary boneMap in (List)root["bones"]) { - BoneData parent = null; - if (boneMap.ContainsKey("parent")) { - parent = skeletonData.FindBone((string)boneMap["parent"]); - if (parent == null) - throw new Exception("Parent bone not found: " + boneMap["parent"]); - } - var data = new BoneData(skeletonData.Bones.Count, (string)boneMap["name"], parent); - data.length = GetFloat(boneMap, "length", 0) * scale; - data.x = GetFloat(boneMap, "x", 0) * scale; - data.y = GetFloat(boneMap, "y", 0) * scale; - data.rotation = GetFloat(boneMap, "rotation", 0); - data.scaleX = GetFloat(boneMap, "scaleX", 1); - data.scaleY = GetFloat(boneMap, "scaleY", 1); - data.shearX = GetFloat(boneMap, "shearX", 0); - data.shearY = GetFloat(boneMap, "shearY", 0); - - string tm = GetString(boneMap, "transform", TransformMode.Normal.ToString()); - data.transformMode = (TransformMode)Enum.Parse(typeof(TransformMode), tm, true); - data.skinRequired = GetBoolean(boneMap, "skin", false); - - skeletonData.bones.Add(data); - } - } - - // Slots. - if (root.ContainsKey("slots")) { - foreach (Dictionary slotMap in (List)root["slots"]) { - var slotName = (string)slotMap["name"]; - var boneName = (string)slotMap["bone"]; - BoneData boneData = skeletonData.FindBone(boneName); - if (boneData == null) throw new Exception("Slot bone not found: " + boneName); - var data = new SlotData(skeletonData.Slots.Count, slotName, boneData); - - if (slotMap.ContainsKey("color")) { - string color = (string)slotMap["color"]; - data.r = ToColor(color, 0); - data.g = ToColor(color, 1); - data.b = ToColor(color, 2); - data.a = ToColor(color, 3); - } - - if (slotMap.ContainsKey("dark")) { - var color2 = (string)slotMap["dark"]; - data.r2 = ToColor(color2, 0, 6); // expectedLength = 6. ie. "RRGGBB" - data.g2 = ToColor(color2, 1, 6); - data.b2 = ToColor(color2, 2, 6); - data.hasSecondColor = true; - } - - data.attachmentName = GetString(slotMap, "attachment", null); - if (slotMap.ContainsKey("blend")) - data.blendMode = (BlendMode)Enum.Parse(typeof(BlendMode), (string)slotMap["blend"], true); - else - data.blendMode = BlendMode.Normal; - skeletonData.slots.Add(data); - } - } - - // IK constraints. - if (root.ContainsKey("ik")) { - foreach (Dictionary constraintMap in (List)root["ik"]) { - IkConstraintData data = new IkConstraintData((string)constraintMap["name"]); - data.order = GetInt(constraintMap, "order", 0); - data.skinRequired = GetBoolean(constraintMap,"skin", false); - - if (constraintMap.ContainsKey("bones")) { - foreach (string boneName in (List)constraintMap["bones"]) { - BoneData bone = skeletonData.FindBone(boneName); - if (bone == null) throw new Exception("IK bone not found: " + boneName); - data.bones.Add(bone); - } - } - - string targetName = (string)constraintMap["target"]; - data.target = skeletonData.FindBone(targetName); - if (data.target == null) throw new Exception("IK target bone not found: " + targetName); - data.mix = GetFloat(constraintMap, "mix", 1); - data.softness = GetFloat(constraintMap, "softness", 0) * scale; - data.bendDirection = GetBoolean(constraintMap, "bendPositive", true) ? 1 : -1; - data.compress = GetBoolean(constraintMap, "compress", false); - data.stretch = GetBoolean(constraintMap, "stretch", false); - data.uniform = GetBoolean(constraintMap, "uniform", false); - - skeletonData.ikConstraints.Add(data); - } - } - - // Transform constraints. - if (root.ContainsKey("transform")) { - foreach (Dictionary constraintMap in (List)root["transform"]) { - TransformConstraintData data = new TransformConstraintData((string)constraintMap["name"]); - data.order = GetInt(constraintMap, "order", 0); - data.skinRequired = GetBoolean(constraintMap,"skin", false); - - if (constraintMap.ContainsKey("bones")) { - foreach (string boneName in (List)constraintMap["bones"]) { - BoneData bone = skeletonData.FindBone(boneName); - if (bone == null) throw new Exception("Transform constraint bone not found: " + boneName); - data.bones.Add(bone); - } - } - - string targetName = (string)constraintMap["target"]; - data.target = skeletonData.FindBone(targetName); - if (data.target == null) throw new Exception("Transform constraint target bone not found: " + targetName); - - data.local = GetBoolean(constraintMap, "local", false); - data.relative = GetBoolean(constraintMap, "relative", false); - - data.offsetRotation = GetFloat(constraintMap, "rotation", 0); - data.offsetX = GetFloat(constraintMap, "x", 0) * scale; - data.offsetY = GetFloat(constraintMap, "y", 0) * scale; - data.offsetScaleX = GetFloat(constraintMap, "scaleX", 0); - data.offsetScaleY = GetFloat(constraintMap, "scaleY", 0); - data.offsetShearY = GetFloat(constraintMap, "shearY", 0); - - data.rotateMix = GetFloat(constraintMap, "rotateMix", 1); - data.translateMix = GetFloat(constraintMap, "translateMix", 1); - data.scaleMix = GetFloat(constraintMap, "scaleMix", 1); - data.shearMix = GetFloat(constraintMap, "shearMix", 1); - - skeletonData.transformConstraints.Add(data); - } - } - - // Path constraints. - if(root.ContainsKey("path")) { - foreach (Dictionary constraintMap in (List)root["path"]) { - PathConstraintData data = new PathConstraintData((string)constraintMap["name"]); - data.order = GetInt(constraintMap, "order", 0); - data.skinRequired = GetBoolean(constraintMap,"skin", false); - - if (constraintMap.ContainsKey("bones")) { - foreach (string boneName in (List)constraintMap["bones"]) { - BoneData bone = skeletonData.FindBone(boneName); - if (bone == null) throw new Exception("Path bone not found: " + boneName); - data.bones.Add(bone); - } - } - - string targetName = (string)constraintMap["target"]; - data.target = skeletonData.FindSlot(targetName); - if (data.target == null) throw new Exception("Path target slot not found: " + targetName); - - data.positionMode = (PositionMode)Enum.Parse(typeof(PositionMode), GetString(constraintMap, "positionMode", "percent"), true); - data.spacingMode = (SpacingMode)Enum.Parse(typeof(SpacingMode), GetString(constraintMap, "spacingMode", "length"), true); - data.rotateMode = (RotateMode)Enum.Parse(typeof(RotateMode), GetString(constraintMap, "rotateMode", "tangent"), true); - data.offsetRotation = GetFloat(constraintMap, "rotation", 0); - data.position = GetFloat(constraintMap, "position", 0); - if (data.positionMode == PositionMode.Fixed) data.position *= scale; - data.spacing = GetFloat(constraintMap, "spacing", 0); - if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale; - data.rotateMix = GetFloat(constraintMap, "rotateMix", 1); - data.translateMix = GetFloat(constraintMap, "translateMix", 1); - - skeletonData.pathConstraints.Add(data); - } - } - - // Skins. - if (root.ContainsKey("skins")) { - foreach (Dictionary skinMap in (List)root["skins"]) { - Skin skin = new Skin((string)skinMap["name"]); - if (skinMap.ContainsKey("bones")) { - foreach (string entryName in (List)skinMap["bones"]) { - BoneData bone = skeletonData.FindBone(entryName); - if (bone == null) throw new Exception("Skin bone not found: " + entryName); - skin.bones.Add(bone); - } - } - if (skinMap.ContainsKey("ik")) { - foreach (string entryName in (List)skinMap["ik"]) { - IkConstraintData constraint = skeletonData.FindIkConstraint(entryName); - if (constraint == null) throw new Exception("Skin IK constraint not found: " + entryName); - skin.constraints.Add(constraint); - } - } - if (skinMap.ContainsKey("transform")) { - foreach (string entryName in (List)skinMap["transform"]) { - TransformConstraintData constraint = skeletonData.FindTransformConstraint(entryName); - if (constraint == null) throw new Exception("Skin transform constraint not found: " + entryName); - skin.constraints.Add(constraint); - } - } - if (skinMap.ContainsKey("path")) { - foreach (string entryName in (List)skinMap["path"]) { - PathConstraintData constraint = skeletonData.FindPathConstraint(entryName); - if (constraint == null) throw new Exception("Skin path constraint not found: " + entryName); - skin.constraints.Add(constraint); - } - } - if (skinMap.ContainsKey("attachments")) { - foreach (KeyValuePair slotEntry in (Dictionary)skinMap["attachments"]) { - int slotIndex = skeletonData.FindSlotIndex(slotEntry.Key); - foreach (KeyValuePair entry in ((Dictionary)slotEntry.Value)) { - try { - Attachment attachment = ReadAttachment((Dictionary)entry.Value, skin, slotIndex, entry.Key, skeletonData); - if (attachment != null) skin.SetAttachment(slotIndex, entry.Key, attachment); - } catch (Exception e) { - throw new Exception("Error reading attachment: " + entry.Key + ", skin: " + skin, e); - } - } - } - } - skeletonData.skins.Add(skin); - if (skin.name == "default") skeletonData.defaultSkin = skin; - } - } - - // Linked meshes. - for (int i = 0, n = linkedMeshes.Count; i < n; i++) { - LinkedMesh linkedMesh = linkedMeshes[i]; - Skin skin = linkedMesh.skin == null ? skeletonData.defaultSkin : skeletonData.FindSkin(linkedMesh.skin); - if (skin == null) throw new Exception("Slot not found: " + linkedMesh.skin); - Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); - if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); - linkedMesh.mesh.DeformAttachment = linkedMesh.inheritDeform ? (VertexAttachment)parent : linkedMesh.mesh; - linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; - linkedMesh.mesh.UpdateUVs(); - } - linkedMeshes.Clear(); - - // Events. - if (root.ContainsKey("events")) { - foreach (KeyValuePair entry in (Dictionary)root["events"]) { - var entryMap = (Dictionary)entry.Value; - var data = new EventData(entry.Key); - data.Int = GetInt(entryMap, "int", 0); - data.Float = GetFloat(entryMap, "float", 0); - data.String = GetString(entryMap, "string", string.Empty); - data.AudioPath = GetString(entryMap, "audio", null); - if (data.AudioPath != null) { - data.Volume = GetFloat(entryMap, "volume", 1); - data.Balance = GetFloat(entryMap, "balance", 0); - } - skeletonData.events.Add(data); - } - } - - // Animations. - if (root.ContainsKey("animations")) { - foreach (KeyValuePair entry in (Dictionary)root["animations"]) { - try { - ReadAnimation((Dictionary)entry.Value, entry.Key, skeletonData); - } catch (Exception e) { - throw new Exception("Error reading animation: " + entry.Key, e); - } - } - } - - skeletonData.bones.TrimExcess(); - skeletonData.slots.TrimExcess(); - skeletonData.skins.TrimExcess(); - skeletonData.events.TrimExcess(); - skeletonData.animations.TrimExcess(); - skeletonData.ikConstraints.TrimExcess(); - return skeletonData; - } - - private Attachment ReadAttachment (Dictionary map, Skin skin, int slotIndex, string name, SkeletonData skeletonData) { - float scale = this.Scale; - name = GetString(map, "name", name); - - var typeName = GetString(map, "type", "region"); - var type = (AttachmentType)Enum.Parse(typeof(AttachmentType), typeName, true); - - string path = GetString(map, "path", name); - - switch (type) { - case AttachmentType.Region: - RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); - if (region == null) return null; - region.Path = path; - region.x = GetFloat(map, "x", 0) * scale; - region.y = GetFloat(map, "y", 0) * scale; - region.scaleX = GetFloat(map, "scaleX", 1); - region.scaleY = GetFloat(map, "scaleY", 1); - region.rotation = GetFloat(map, "rotation", 0); - region.width = GetFloat(map, "width", 32) * scale; - region.height = GetFloat(map, "height", 32) * scale; - - if (map.ContainsKey("color")) { - var color = (string)map["color"]; - region.r = ToColor(color, 0); - region.g = ToColor(color, 1); - region.b = ToColor(color, 2); - region.a = ToColor(color, 3); - } - - region.UpdateOffset(); - return region; - case AttachmentType.Boundingbox: - BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); - if (box == null) return null; - ReadVertices(map, box, GetInt(map, "vertexCount", 0) << 1); - return box; - case AttachmentType.Mesh: - case AttachmentType.Linkedmesh: { - MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); - if (mesh == null) return null; - mesh.Path = path; - - if (map.ContainsKey("color")) { - var color = (string)map["color"]; - mesh.r = ToColor(color, 0); - mesh.g = ToColor(color, 1); - mesh.b = ToColor(color, 2); - mesh.a = ToColor(color, 3); - } - - mesh.Width = GetFloat(map, "width", 0) * scale; - mesh.Height = GetFloat(map, "height", 0) * scale; - - string parent = GetString(map, "parent", null); - if (parent != null) { - linkedMeshes.Add(new LinkedMesh(mesh, GetString(map, "skin", null), slotIndex, parent, GetBoolean(map, "deform", true))); - return mesh; - } - - float[] uvs = GetFloatArray(map, "uvs", 1); - ReadVertices(map, mesh, uvs.Length); - mesh.triangles = GetIntArray(map, "triangles"); - mesh.regionUVs = uvs; - mesh.UpdateUVs(); - - if (map.ContainsKey("hull")) mesh.HullLength = GetInt(map, "hull", 0) * 2; - if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges"); - return mesh; - } - case AttachmentType.Path: { - PathAttachment pathAttachment = attachmentLoader.NewPathAttachment(skin, name); - if (pathAttachment == null) return null; - pathAttachment.closed = GetBoolean(map, "closed", false); - pathAttachment.constantSpeed = GetBoolean(map, "constantSpeed", true); - - int vertexCount = GetInt(map, "vertexCount", 0); - ReadVertices(map, pathAttachment, vertexCount << 1); - - // potential BOZO see Java impl - pathAttachment.lengths = GetFloatArray(map, "lengths", scale); - return pathAttachment; - } - case AttachmentType.Point: { - PointAttachment point = attachmentLoader.NewPointAttachment(skin, name); - if (point == null) return null; - point.x = GetFloat(map, "x", 0) * scale; - point.y = GetFloat(map, "y", 0) * scale; - point.rotation = GetFloat(map, "rotation", 0); - - //string color = GetString(map, "color", null); - //if (color != null) point.color = color; - return point; - } - case AttachmentType.Clipping: { - ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name); - if (clip == null) return null; - - string end = GetString(map, "end", null); - if (end != null) { - SlotData slot = skeletonData.FindSlot(end); - if (slot == null) throw new Exception("Clipping end slot not found: " + end); - clip.EndSlot = slot; - } - - ReadVertices(map, clip, GetInt(map, "vertexCount", 0) << 1); - - //string color = GetString(map, "color", null); - // if (color != null) clip.color = color; - return clip; - } - } - return null; - } - - private void ReadVertices (Dictionary map, VertexAttachment attachment, int verticesLength) { - attachment.WorldVerticesLength = verticesLength; - float[] vertices = GetFloatArray(map, "vertices", 1); - float scale = Scale; - if (verticesLength == vertices.Length) { - if (scale != 1) { - for (int i = 0; i < vertices.Length; i++) { - vertices[i] *= scale; - } - } - attachment.vertices = vertices; - return; - } - ExposedList weights = new ExposedList(verticesLength * 3 * 3); - ExposedList bones = new ExposedList(verticesLength * 3); - for (int i = 0, n = vertices.Length; i < n;) { - int boneCount = (int)vertices[i++]; - bones.Add(boneCount); - for (int nn = i + boneCount * 4; i < nn; i += 4) { - bones.Add((int)vertices[i]); - weights.Add(vertices[i + 1] * this.Scale); - weights.Add(vertices[i + 2] * this.Scale); - weights.Add(vertices[i + 3]); - } - } - attachment.bones = bones.ToArray(); - attachment.vertices = weights.ToArray(); - } - - private void ReadAnimation (Dictionary map, string name, SkeletonData skeletonData) { - var scale = this.Scale; - var timelines = new ExposedList(); - float duration = 0; - - // Slot timelines. - if (map.ContainsKey("slots")) { - foreach (KeyValuePair entry in (Dictionary)map["slots"]) { - string slotName = entry.Key; - int slotIndex = skeletonData.FindSlotIndex(slotName); - var timelineMap = (Dictionary)entry.Value; - foreach (KeyValuePair timelineEntry in timelineMap) { - var values = (List)timelineEntry.Value; - var timelineName = (string)timelineEntry.Key; - if (timelineName == "attachment") { - var timeline = new AttachmentTimeline(values.Count); - timeline.slotIndex = slotIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = GetFloat(valueMap, "time", 0); - timeline.SetFrame(frameIndex++, time, (string)valueMap["name"]); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); - - } else if (timelineName == "color") { - var timeline = new ColorTimeline(values.Count); - timeline.slotIndex = slotIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = GetFloat(valueMap, "time", 0); - string c = (string)valueMap["color"]; - timeline.SetFrame(frameIndex, time, ToColor(c, 0), ToColor(c, 1), ToColor(c, 2), ToColor(c, 3)); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * ColorTimeline.ENTRIES]); - - } else if (timelineName == "twoColor") { - var timeline = new TwoColorTimeline(values.Count); - timeline.slotIndex = slotIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = GetFloat(valueMap, "time", 0); - string light = (string)valueMap["light"]; - string dark = (string)valueMap["dark"]; - timeline.SetFrame(frameIndex, time, ToColor(light, 0), ToColor(light, 1), ToColor(light, 2), ToColor(light, 3), - ToColor(dark, 0, 6), ToColor(dark, 1, 6), ToColor(dark, 2, 6)); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TwoColorTimeline.ENTRIES]); - - } else - throw new Exception("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")"); - } - } - } - - // Bone timelines. - if (map.ContainsKey("bones")) { - foreach (KeyValuePair entry in (Dictionary)map["bones"]) { - string boneName = entry.Key; - int boneIndex = skeletonData.FindBoneIndex(boneName); - if (boneIndex == -1) throw new Exception("Bone not found: " + boneName); - var timelineMap = (Dictionary)entry.Value; - foreach (KeyValuePair timelineEntry in timelineMap) { - var values = (List)timelineEntry.Value; - var timelineName = (string)timelineEntry.Key; - if (timelineName == "rotate") { - var timeline = new RotateTimeline(values.Count); - timeline.boneIndex = boneIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), GetFloat(valueMap, "angle", 0)); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * RotateTimeline.ENTRIES]); - - } else if (timelineName == "translate" || timelineName == "scale" || timelineName == "shear") { - TranslateTimeline timeline; - float timelineScale = 1, defaultValue = 0; - if (timelineName == "scale") { - timeline = new ScaleTimeline(values.Count); - defaultValue = 1; - } - else if (timelineName == "shear") - timeline = new ShearTimeline(values.Count); - else { - timeline = new TranslateTimeline(values.Count); - timelineScale = scale; - } - timeline.boneIndex = boneIndex; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float time = GetFloat(valueMap, "time", 0); - float x = GetFloat(valueMap, "x", defaultValue); - float y = GetFloat(valueMap, "y", defaultValue); - timeline.SetFrame(frameIndex, time, x * timelineScale, y * timelineScale); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TranslateTimeline.ENTRIES]); - - } else - throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")"); - } - } - } - - // IK constraint timelines. - if (map.ContainsKey("ik")) { - foreach (KeyValuePair constraintMap in (Dictionary)map["ik"]) { - IkConstraintData constraint = skeletonData.FindIkConstraint(constraintMap.Key); - var values = (List)constraintMap.Value; - var timeline = new IkConstraintTimeline(values.Count); - timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(constraint); - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), GetFloat(valueMap, "mix", 1), - GetFloat(valueMap, "softness", 0) * scale, GetBoolean(valueMap, "bendPositive", true) ? 1 : -1, - GetBoolean(valueMap, "compress", false), GetBoolean(valueMap, "stretch", false)); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * IkConstraintTimeline.ENTRIES]); - } - } - - // Transform constraint timelines. - if (map.ContainsKey("transform")) { - foreach (KeyValuePair constraintMap in (Dictionary)map["transform"]) { - TransformConstraintData constraint = skeletonData.FindTransformConstraint(constraintMap.Key); - var values = (List)constraintMap.Value; - var timeline = new TransformConstraintTimeline(values.Count); - timeline.transformConstraintIndex = skeletonData.transformConstraints.IndexOf(constraint); - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), GetFloat(valueMap, "rotateMix", 1), - GetFloat(valueMap, "translateMix", 1), GetFloat(valueMap, "scaleMix", 1), GetFloat(valueMap, "shearMix", 1)); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TransformConstraintTimeline.ENTRIES]); - } - } - - // Path constraint timelines. - if (map.ContainsKey("path")) { - foreach (KeyValuePair constraintMap in (Dictionary)map["path"]) { - int index = skeletonData.FindPathConstraintIndex(constraintMap.Key); - if (index == -1) throw new Exception("Path constraint not found: " + constraintMap.Key); - PathConstraintData data = skeletonData.pathConstraints.Items[index]; - var timelineMap = (Dictionary)constraintMap.Value; - foreach (KeyValuePair timelineEntry in timelineMap) { - var values = (List)timelineEntry.Value; - var timelineName = (string)timelineEntry.Key; - if (timelineName == "position" || timelineName == "spacing") { - PathConstraintPositionTimeline timeline; - float timelineScale = 1; - if (timelineName == "spacing") { - timeline = new PathConstraintSpacingTimeline(values.Count); - if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale; - } - else { - timeline = new PathConstraintPositionTimeline(values.Count); - if (data.positionMode == PositionMode.Fixed) timelineScale = scale; - } - timeline.pathConstraintIndex = index; - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), GetFloat(valueMap, timelineName, 0) * timelineScale); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintPositionTimeline.ENTRIES]); - } - else if (timelineName == "mix") { - PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(values.Count); - timeline.pathConstraintIndex = index; - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), GetFloat(valueMap, "rotateMix", 1), - GetFloat(valueMap, "translateMix", 1)); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintMixTimeline.ENTRIES]); - } - } - } - } - - // Deform timelines. - if (map.ContainsKey("deform")) { - foreach (KeyValuePair deformMap in (Dictionary)map["deform"]) { - Skin skin = skeletonData.FindSkin(deformMap.Key); - foreach (KeyValuePair slotMap in (Dictionary)deformMap.Value) { - int slotIndex = skeletonData.FindSlotIndex(slotMap.Key); - if (slotIndex == -1) throw new Exception("Slot not found: " + slotMap.Key); - foreach (KeyValuePair timelineMap in (Dictionary)slotMap.Value) { - var values = (List)timelineMap.Value; - VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, timelineMap.Key); - if (attachment == null) throw new Exception("Deform attachment not found: " + timelineMap.Key); - bool weighted = attachment.bones != null; - float[] vertices = attachment.vertices; - int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length; - - var timeline = new DeformTimeline(values.Count); - timeline.slotIndex = slotIndex; - timeline.attachment = attachment; - - int frameIndex = 0; - foreach (Dictionary valueMap in values) { - float[] deform; - if (!valueMap.ContainsKey("vertices")) { - deform = weighted ? new float[deformLength] : vertices; - } else { - deform = new float[deformLength]; - int start = GetInt(valueMap, "offset", 0); - float[] verticesValue = GetFloatArray(valueMap, "vertices", 1); - Array.Copy(verticesValue, 0, deform, start, verticesValue.Length); - if (scale != 1) { - for (int i = start, n = i + verticesValue.Length; i < n; i++) - deform[i] *= scale; - } - - if (!weighted) { - for (int i = 0; i < deformLength; i++) - deform[i] += vertices[i]; - } - } - - timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), deform); - ReadCurve(valueMap, timeline, frameIndex); - frameIndex++; - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); - } - } - } - } - - // Draw order timeline. - if (map.ContainsKey("drawOrder") || map.ContainsKey("draworder")) { - var values = (List)map[map.ContainsKey("drawOrder") ? "drawOrder" : "draworder"]; - var timeline = new DrawOrderTimeline(values.Count); - int slotCount = skeletonData.slots.Count; - int frameIndex = 0; - foreach (Dictionary drawOrderMap in values) { - int[] drawOrder = null; - if (drawOrderMap.ContainsKey("offsets")) { - drawOrder = new int[slotCount]; - for (int i = slotCount - 1; i >= 0; i--) - drawOrder[i] = -1; - var offsets = (List)drawOrderMap["offsets"]; - int[] unchanged = new int[slotCount - offsets.Count]; - int originalIndex = 0, unchangedIndex = 0; - foreach (Dictionary offsetMap in offsets) { - int slotIndex = skeletonData.FindSlotIndex((string)offsetMap["slot"]); - if (slotIndex == -1) throw new Exception("Slot not found: " + offsetMap["slot"]); - // Collect unchanged items. - while (originalIndex != slotIndex) - unchanged[unchangedIndex++] = originalIndex++; - // Set changed items. - int index = originalIndex + (int)(float)offsetMap["offset"]; - drawOrder[index] = originalIndex++; - } - // Collect remaining unchanged items. - while (originalIndex < slotCount) - unchanged[unchangedIndex++] = originalIndex++; - // Fill in unchanged items. - for (int i = slotCount - 1; i >= 0; i--) - if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex]; - } - timeline.SetFrame(frameIndex++, GetFloat(drawOrderMap, "time", 0), drawOrder); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); - } - - // Event timeline. - if (map.ContainsKey("events")) { - var eventsMap = (List)map["events"]; - var timeline = new EventTimeline(eventsMap.Count); - int frameIndex = 0; - foreach (Dictionary eventMap in eventsMap) { - EventData eventData = skeletonData.FindEvent((string)eventMap["name"]); - if (eventData == null) throw new Exception("Event not found: " + eventMap["name"]); - var e = new Event(GetFloat(eventMap, "time", 0), eventData) { - intValue = GetInt(eventMap, "int", eventData.Int), - floatValue = GetFloat(eventMap, "float", eventData.Float), - stringValue = GetString(eventMap, "string", eventData.String) - }; - if (e.data.AudioPath != null) { - e.volume = GetFloat(eventMap, "volume", eventData.Volume); - e.balance = GetFloat(eventMap, "balance", eventData.Balance); - } - timeline.SetFrame(frameIndex++, e); - } - timelines.Add(timeline); - duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); - } - - timelines.TrimExcess(); - skeletonData.animations.Add(new Animation(name, timelines, duration)); - } - - static void ReadCurve (Dictionary valueMap, CurveTimeline timeline, int frameIndex) { - if (!valueMap.ContainsKey("curve")) - return; - Object curveObject = valueMap["curve"]; - if (curveObject is string) - timeline.SetStepped(frameIndex); - else - timeline.SetCurve(frameIndex, (float)curveObject, GetFloat(valueMap, "c2", 0), GetFloat(valueMap, "c3", 1), GetFloat(valueMap, "c4", 1)); - } - - internal class LinkedMesh { - internal string parent, skin; - internal int slotIndex; - internal MeshAttachment mesh; - internal bool inheritDeform; - - public LinkedMesh (MeshAttachment mesh, string skin, int slotIndex, string parent, bool inheritDeform) { - this.mesh = mesh; - this.skin = skin; - this.slotIndex = slotIndex; - this.parent = parent; - this.inheritDeform = inheritDeform; - } - } - - static float[] GetFloatArray(Dictionary map, string name, float scale) { - var list = (List)map[name]; - var values = new float[list.Count]; - if (scale == 1) { - for (int i = 0, n = list.Count; i < n; i++) - values[i] = (float)list[i]; - } else { - for (int i = 0, n = list.Count; i < n; i++) - values[i] = (float)list[i] * scale; - } - return values; - } - - static int[] GetIntArray(Dictionary map, string name) { - var list = (List)map[name]; - var values = new int[list.Count]; - for (int i = 0, n = list.Count; i < n; i++) - values[i] = (int)(float)list[i]; - return values; - } - - static float GetFloat(Dictionary map, string name, float defaultValue) { - if (!map.ContainsKey(name)) - return defaultValue; - return (float)map[name]; - } - - static int GetInt(Dictionary map, string name, int defaultValue) { - if (!map.ContainsKey(name)) - return defaultValue; - return (int)(float)map[name]; - } - - static bool GetBoolean(Dictionary map, string name, bool defaultValue) { - if (!map.ContainsKey(name)) - return defaultValue; - return (bool)map[name]; - } - - static string GetString(Dictionary map, string name, string defaultValue) { - if (!map.ContainsKey(name)) - return defaultValue; - return (string)map[name]; - } +#else + using (var reader = new StreamReader(new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))) + { +#endif + SkeletonData skeletonData = ReadSkeletonData(reader); + skeletonData.name = Path.GetFileNameWithoutExtension(path); + return skeletonData; + } + } +#endif - static float ToColor(string hexString, int colorIndex, int expectedLength = 8) { - if (hexString.Length != expectedLength) - throw new ArgumentException("Color hexidecimal length must be " + expectedLength + ", recieved: " + hexString, "hexString"); - return Convert.ToInt32(hexString.Substring(colorIndex * 2, 2), 16) / (float)255; - } - } + public SkeletonData ReadSkeletonData(TextReader reader) + { + if (reader == null) throw new ArgumentNullException("reader", "reader cannot be null."); + + float scale = this.Scale; + var skeletonData = new SkeletonData(); + + var root = Json.Deserialize(reader) as Dictionary; + if (root == null) throw new Exception("Invalid JSON."); + + // Skeleton. + if (root.ContainsKey("skeleton")) + { + var skeletonMap = (Dictionary)root["skeleton"]; + skeletonData.hash = (string)skeletonMap["hash"]; + skeletonData.version = (string)skeletonMap["spine"]; + skeletonData.x = GetFloat(skeletonMap, "x", 0); + skeletonData.y = GetFloat(skeletonMap, "y", 0); + skeletonData.width = GetFloat(skeletonMap, "width", 0); + skeletonData.height = GetFloat(skeletonMap, "height", 0); + skeletonData.fps = GetFloat(skeletonMap, "fps", 30); + skeletonData.imagesPath = GetString(skeletonMap, "images", null); + skeletonData.audioPath = GetString(skeletonMap, "audio", null); + } + + // Bones. + if (root.ContainsKey("bones")) + { + foreach (Dictionary boneMap in (List)root["bones"]) + { + BoneData parent = null; + if (boneMap.ContainsKey("parent")) + { + parent = skeletonData.FindBone((string)boneMap["parent"]); + if (parent == null) + throw new Exception("Parent bone not found: " + boneMap["parent"]); + } + var data = new BoneData(skeletonData.Bones.Count, (string)boneMap["name"], parent); + data.length = GetFloat(boneMap, "length", 0) * scale; + data.x = GetFloat(boneMap, "x", 0) * scale; + data.y = GetFloat(boneMap, "y", 0) * scale; + data.rotation = GetFloat(boneMap, "rotation", 0); + data.scaleX = GetFloat(boneMap, "scaleX", 1); + data.scaleY = GetFloat(boneMap, "scaleY", 1); + data.shearX = GetFloat(boneMap, "shearX", 0); + data.shearY = GetFloat(boneMap, "shearY", 0); + + string tm = GetString(boneMap, "transform", TransformMode.Normal.ToString()); + data.transformMode = (TransformMode)Enum.Parse(typeof(TransformMode), tm, true); + data.skinRequired = GetBoolean(boneMap, "skin", false); + + skeletonData.bones.Add(data); + } + } + + // Slots. + if (root.ContainsKey("slots")) + { + foreach (Dictionary slotMap in (List)root["slots"]) + { + var slotName = (string)slotMap["name"]; + var boneName = (string)slotMap["bone"]; + BoneData boneData = skeletonData.FindBone(boneName); + if (boneData == null) throw new Exception("Slot bone not found: " + boneName); + var data = new SlotData(skeletonData.Slots.Count, slotName, boneData); + + if (slotMap.ContainsKey("color")) + { + string color = (string)slotMap["color"]; + data.r = ToColor(color, 0); + data.g = ToColor(color, 1); + data.b = ToColor(color, 2); + data.a = ToColor(color, 3); + } + + if (slotMap.ContainsKey("dark")) + { + var color2 = (string)slotMap["dark"]; + data.r2 = ToColor(color2, 0, 6); // expectedLength = 6. ie. "RRGGBB" + data.g2 = ToColor(color2, 1, 6); + data.b2 = ToColor(color2, 2, 6); + data.hasSecondColor = true; + } + + data.attachmentName = GetString(slotMap, "attachment", null); + if (slotMap.ContainsKey("blend")) + data.blendMode = (BlendMode)Enum.Parse(typeof(BlendMode), (string)slotMap["blend"], true); + else + data.blendMode = BlendMode.Normal; + skeletonData.slots.Add(data); + } + } + + // IK constraints. + if (root.ContainsKey("ik")) + { + foreach (Dictionary constraintMap in (List)root["ik"]) + { + IkConstraintData data = new IkConstraintData((string)constraintMap["name"]); + data.order = GetInt(constraintMap, "order", 0); + data.skinRequired = GetBoolean(constraintMap, "skin", false); + + if (constraintMap.ContainsKey("bones")) + { + foreach (string boneName in (List)constraintMap["bones"]) + { + BoneData bone = skeletonData.FindBone(boneName); + if (bone == null) throw new Exception("IK bone not found: " + boneName); + data.bones.Add(bone); + } + } + + string targetName = (string)constraintMap["target"]; + data.target = skeletonData.FindBone(targetName); + if (data.target == null) throw new Exception("IK target bone not found: " + targetName); + data.mix = GetFloat(constraintMap, "mix", 1); + data.softness = GetFloat(constraintMap, "softness", 0) * scale; + data.bendDirection = GetBoolean(constraintMap, "bendPositive", true) ? 1 : -1; + data.compress = GetBoolean(constraintMap, "compress", false); + data.stretch = GetBoolean(constraintMap, "stretch", false); + data.uniform = GetBoolean(constraintMap, "uniform", false); + + skeletonData.ikConstraints.Add(data); + } + } + + // Transform constraints. + if (root.ContainsKey("transform")) + { + foreach (Dictionary constraintMap in (List)root["transform"]) + { + TransformConstraintData data = new TransformConstraintData((string)constraintMap["name"]); + data.order = GetInt(constraintMap, "order", 0); + data.skinRequired = GetBoolean(constraintMap, "skin", false); + + if (constraintMap.ContainsKey("bones")) + { + foreach (string boneName in (List)constraintMap["bones"]) + { + BoneData bone = skeletonData.FindBone(boneName); + if (bone == null) throw new Exception("Transform constraint bone not found: " + boneName); + data.bones.Add(bone); + } + } + + string targetName = (string)constraintMap["target"]; + data.target = skeletonData.FindBone(targetName); + if (data.target == null) throw new Exception("Transform constraint target bone not found: " + targetName); + + data.local = GetBoolean(constraintMap, "local", false); + data.relative = GetBoolean(constraintMap, "relative", false); + + data.offsetRotation = GetFloat(constraintMap, "rotation", 0); + data.offsetX = GetFloat(constraintMap, "x", 0) * scale; + data.offsetY = GetFloat(constraintMap, "y", 0) * scale; + data.offsetScaleX = GetFloat(constraintMap, "scaleX", 0); + data.offsetScaleY = GetFloat(constraintMap, "scaleY", 0); + data.offsetShearY = GetFloat(constraintMap, "shearY", 0); + + data.rotateMix = GetFloat(constraintMap, "rotateMix", 1); + data.translateMix = GetFloat(constraintMap, "translateMix", 1); + data.scaleMix = GetFloat(constraintMap, "scaleMix", 1); + data.shearMix = GetFloat(constraintMap, "shearMix", 1); + + skeletonData.transformConstraints.Add(data); + } + } + + // Path constraints. + if (root.ContainsKey("path")) + { + foreach (Dictionary constraintMap in (List)root["path"]) + { + PathConstraintData data = new PathConstraintData((string)constraintMap["name"]); + data.order = GetInt(constraintMap, "order", 0); + data.skinRequired = GetBoolean(constraintMap, "skin", false); + + if (constraintMap.ContainsKey("bones")) + { + foreach (string boneName in (List)constraintMap["bones"]) + { + BoneData bone = skeletonData.FindBone(boneName); + if (bone == null) throw new Exception("Path bone not found: " + boneName); + data.bones.Add(bone); + } + } + + string targetName = (string)constraintMap["target"]; + data.target = skeletonData.FindSlot(targetName); + if (data.target == null) throw new Exception("Path target slot not found: " + targetName); + + data.positionMode = (PositionMode)Enum.Parse(typeof(PositionMode), GetString(constraintMap, "positionMode", "percent"), true); + data.spacingMode = (SpacingMode)Enum.Parse(typeof(SpacingMode), GetString(constraintMap, "spacingMode", "length"), true); + data.rotateMode = (RotateMode)Enum.Parse(typeof(RotateMode), GetString(constraintMap, "rotateMode", "tangent"), true); + data.offsetRotation = GetFloat(constraintMap, "rotation", 0); + data.position = GetFloat(constraintMap, "position", 0); + if (data.positionMode == PositionMode.Fixed) data.position *= scale; + data.spacing = GetFloat(constraintMap, "spacing", 0); + if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale; + data.rotateMix = GetFloat(constraintMap, "rotateMix", 1); + data.translateMix = GetFloat(constraintMap, "translateMix", 1); + + skeletonData.pathConstraints.Add(data); + } + } + + // Skins. + if (root.ContainsKey("skins")) + { + foreach (Dictionary skinMap in (List)root["skins"]) + { + Skin skin = new Skin((string)skinMap["name"]); + if (skinMap.ContainsKey("bones")) + { + foreach (string entryName in (List)skinMap["bones"]) + { + BoneData bone = skeletonData.FindBone(entryName); + if (bone == null) throw new Exception("Skin bone not found: " + entryName); + skin.bones.Add(bone); + } + } + if (skinMap.ContainsKey("ik")) + { + foreach (string entryName in (List)skinMap["ik"]) + { + IkConstraintData constraint = skeletonData.FindIkConstraint(entryName); + if (constraint == null) throw new Exception("Skin IK constraint not found: " + entryName); + skin.constraints.Add(constraint); + } + } + if (skinMap.ContainsKey("transform")) + { + foreach (string entryName in (List)skinMap["transform"]) + { + TransformConstraintData constraint = skeletonData.FindTransformConstraint(entryName); + if (constraint == null) throw new Exception("Skin transform constraint not found: " + entryName); + skin.constraints.Add(constraint); + } + } + if (skinMap.ContainsKey("path")) + { + foreach (string entryName in (List)skinMap["path"]) + { + PathConstraintData constraint = skeletonData.FindPathConstraint(entryName); + if (constraint == null) throw new Exception("Skin path constraint not found: " + entryName); + skin.constraints.Add(constraint); + } + } + if (skinMap.ContainsKey("attachments")) + { + foreach (KeyValuePair slotEntry in (Dictionary)skinMap["attachments"]) + { + int slotIndex = skeletonData.FindSlotIndex(slotEntry.Key); + foreach (KeyValuePair entry in ((Dictionary)slotEntry.Value)) + { + try + { + Attachment attachment = ReadAttachment((Dictionary)entry.Value, skin, slotIndex, entry.Key, skeletonData); + if (attachment != null) skin.SetAttachment(slotIndex, entry.Key, attachment); + } + catch (Exception e) + { + throw new Exception("Error reading attachment: " + entry.Key + ", skin: " + skin, e); + } + } + } + } + skeletonData.skins.Add(skin); + if (skin.name == "default") skeletonData.defaultSkin = skin; + } + } + + // Linked meshes. + for (int i = 0, n = linkedMeshes.Count; i < n; i++) + { + LinkedMesh linkedMesh = linkedMeshes[i]; + Skin skin = linkedMesh.skin == null ? skeletonData.defaultSkin : skeletonData.FindSkin(linkedMesh.skin); + if (skin == null) throw new Exception("Slot not found: " + linkedMesh.skin); + Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); + if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); + linkedMesh.mesh.DeformAttachment = linkedMesh.inheritDeform ? (VertexAttachment)parent : linkedMesh.mesh; + linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; + linkedMesh.mesh.UpdateUVs(); + } + linkedMeshes.Clear(); + + // Events. + if (root.ContainsKey("events")) + { + foreach (KeyValuePair entry in (Dictionary)root["events"]) + { + var entryMap = (Dictionary)entry.Value; + var data = new EventData(entry.Key); + data.Int = GetInt(entryMap, "int", 0); + data.Float = GetFloat(entryMap, "float", 0); + data.String = GetString(entryMap, "string", string.Empty); + data.AudioPath = GetString(entryMap, "audio", null); + if (data.AudioPath != null) + { + data.Volume = GetFloat(entryMap, "volume", 1); + data.Balance = GetFloat(entryMap, "balance", 0); + } + skeletonData.events.Add(data); + } + } + + // Animations. + if (root.ContainsKey("animations")) + { + foreach (KeyValuePair entry in (Dictionary)root["animations"]) + { + try + { + ReadAnimation((Dictionary)entry.Value, entry.Key, skeletonData); + } + catch (Exception e) + { + throw new Exception("Error reading animation: " + entry.Key, e); + } + } + } + + skeletonData.bones.TrimExcess(); + skeletonData.slots.TrimExcess(); + skeletonData.skins.TrimExcess(); + skeletonData.events.TrimExcess(); + skeletonData.animations.TrimExcess(); + skeletonData.ikConstraints.TrimExcess(); + return skeletonData; + } + + private Attachment ReadAttachment(Dictionary map, Skin skin, int slotIndex, string name, SkeletonData skeletonData) + { + float scale = this.Scale; + name = GetString(map, "name", name); + + var typeName = GetString(map, "type", "region"); + var type = (AttachmentType)Enum.Parse(typeof(AttachmentType), typeName, true); + + string path = GetString(map, "path", name); + + switch (type) + { + case AttachmentType.Region: + RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); + if (region == null) return null; + region.Path = path; + region.x = GetFloat(map, "x", 0) * scale; + region.y = GetFloat(map, "y", 0) * scale; + region.scaleX = GetFloat(map, "scaleX", 1); + region.scaleY = GetFloat(map, "scaleY", 1); + region.rotation = GetFloat(map, "rotation", 0); + region.width = GetFloat(map, "width", 32) * scale; + region.height = GetFloat(map, "height", 32) * scale; + + if (map.ContainsKey("color")) + { + var color = (string)map["color"]; + region.r = ToColor(color, 0); + region.g = ToColor(color, 1); + region.b = ToColor(color, 2); + region.a = ToColor(color, 3); + } + + region.UpdateOffset(); + return region; + case AttachmentType.Boundingbox: + BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); + if (box == null) return null; + ReadVertices(map, box, GetInt(map, "vertexCount", 0) << 1); + return box; + case AttachmentType.Mesh: + case AttachmentType.Linkedmesh: + { + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); + if (mesh == null) return null; + mesh.Path = path; + + if (map.ContainsKey("color")) + { + var color = (string)map["color"]; + mesh.r = ToColor(color, 0); + mesh.g = ToColor(color, 1); + mesh.b = ToColor(color, 2); + mesh.a = ToColor(color, 3); + } + + mesh.Width = GetFloat(map, "width", 0) * scale; + mesh.Height = GetFloat(map, "height", 0) * scale; + + string parent = GetString(map, "parent", null); + if (parent != null) + { + linkedMeshes.Add(new LinkedMesh(mesh, GetString(map, "skin", null), slotIndex, parent, GetBoolean(map, "deform", true))); + return mesh; + } + + float[] uvs = GetFloatArray(map, "uvs", 1); + ReadVertices(map, mesh, uvs.Length); + mesh.triangles = GetIntArray(map, "triangles"); + mesh.regionUVs = uvs; + mesh.UpdateUVs(); + + if (map.ContainsKey("hull")) mesh.HullLength = GetInt(map, "hull", 0) * 2; + if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges"); + return mesh; + } + case AttachmentType.Path: + { + PathAttachment pathAttachment = attachmentLoader.NewPathAttachment(skin, name); + if (pathAttachment == null) return null; + pathAttachment.closed = GetBoolean(map, "closed", false); + pathAttachment.constantSpeed = GetBoolean(map, "constantSpeed", true); + + int vertexCount = GetInt(map, "vertexCount", 0); + ReadVertices(map, pathAttachment, vertexCount << 1); + + // potential BOZO see Java impl + pathAttachment.lengths = GetFloatArray(map, "lengths", scale); + return pathAttachment; + } + case AttachmentType.Point: + { + PointAttachment point = attachmentLoader.NewPointAttachment(skin, name); + if (point == null) return null; + point.x = GetFloat(map, "x", 0) * scale; + point.y = GetFloat(map, "y", 0) * scale; + point.rotation = GetFloat(map, "rotation", 0); + + //string color = GetString(map, "color", null); + //if (color != null) point.color = color; + return point; + } + case AttachmentType.Clipping: + { + ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name); + if (clip == null) return null; + + string end = GetString(map, "end", null); + if (end != null) + { + SlotData slot = skeletonData.FindSlot(end); + if (slot == null) throw new Exception("Clipping end slot not found: " + end); + clip.EndSlot = slot; + } + + ReadVertices(map, clip, GetInt(map, "vertexCount", 0) << 1); + + //string color = GetString(map, "color", null); + // if (color != null) clip.color = color; + return clip; + } + } + return null; + } + + private void ReadVertices(Dictionary map, VertexAttachment attachment, int verticesLength) + { + attachment.WorldVerticesLength = verticesLength; + float[] vertices = GetFloatArray(map, "vertices", 1); + float scale = Scale; + if (verticesLength == vertices.Length) + { + if (scale != 1) + { + for (int i = 0; i < vertices.Length; i++) + { + vertices[i] *= scale; + } + } + attachment.vertices = vertices; + return; + } + ExposedList weights = new ExposedList(verticesLength * 3 * 3); + ExposedList bones = new ExposedList(verticesLength * 3); + for (int i = 0, n = vertices.Length; i < n;) + { + int boneCount = (int)vertices[i++]; + bones.Add(boneCount); + for (int nn = i + boneCount * 4; i < nn; i += 4) + { + bones.Add((int)vertices[i]); + weights.Add(vertices[i + 1] * this.Scale); + weights.Add(vertices[i + 2] * this.Scale); + weights.Add(vertices[i + 3]); + } + } + attachment.bones = bones.ToArray(); + attachment.vertices = weights.ToArray(); + } + + private void ReadAnimation(Dictionary map, string name, SkeletonData skeletonData) + { + var scale = this.Scale; + var timelines = new ExposedList(); + float duration = 0; + + // Slot timelines. + if (map.ContainsKey("slots")) + { + foreach (KeyValuePair entry in (Dictionary)map["slots"]) + { + string slotName = entry.Key; + int slotIndex = skeletonData.FindSlotIndex(slotName); + var timelineMap = (Dictionary)entry.Value; + foreach (KeyValuePair timelineEntry in timelineMap) + { + var values = (List)timelineEntry.Value; + var timelineName = timelineEntry.Key; + if (timelineName == "attachment") + { + var timeline = new AttachmentTimeline(values.Count); + timeline.slotIndex = slotIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = GetFloat(valueMap, "time", 0); + timeline.SetFrame(frameIndex++, time, (string)valueMap["name"]); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); + + } + else if (timelineName == "color") + { + var timeline = new ColorTimeline(values.Count); + timeline.slotIndex = slotIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = GetFloat(valueMap, "time", 0); + string c = (string)valueMap["color"]; + timeline.SetFrame(frameIndex, time, ToColor(c, 0), ToColor(c, 1), ToColor(c, 2), ToColor(c, 3)); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * ColorTimeline.ENTRIES]); + + } + else if (timelineName == "twoColor") + { + var timeline = new TwoColorTimeline(values.Count); + timeline.slotIndex = slotIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = GetFloat(valueMap, "time", 0); + string light = (string)valueMap["light"]; + string dark = (string)valueMap["dark"]; + timeline.SetFrame(frameIndex, time, ToColor(light, 0), ToColor(light, 1), ToColor(light, 2), ToColor(light, 3), + ToColor(dark, 0, 6), ToColor(dark, 1, 6), ToColor(dark, 2, 6)); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TwoColorTimeline.ENTRIES]); + + } + else + throw new Exception("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")"); + } + } + } + + // Bone timelines. + if (map.ContainsKey("bones")) + { + foreach (KeyValuePair entry in (Dictionary)map["bones"]) + { + string boneName = entry.Key; + int boneIndex = skeletonData.FindBoneIndex(boneName); + if (boneIndex == -1) throw new Exception("Bone not found: " + boneName); + var timelineMap = (Dictionary)entry.Value; + foreach (KeyValuePair timelineEntry in timelineMap) + { + var values = (List)timelineEntry.Value; + var timelineName = timelineEntry.Key; + if (timelineName == "rotate") + { + var timeline = new RotateTimeline(values.Count); + timeline.boneIndex = boneIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), GetFloat(valueMap, "angle", 0)); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * RotateTimeline.ENTRIES]); + + } + else if (timelineName == "translate" || timelineName == "scale" || timelineName == "shear") + { + TranslateTimeline timeline; + float timelineScale = 1, defaultValue = 0; + if (timelineName == "scale") + { + timeline = new ScaleTimeline(values.Count); + defaultValue = 1; + } + else if (timelineName == "shear") + timeline = new ShearTimeline(values.Count); + else + { + timeline = new TranslateTimeline(values.Count); + timelineScale = scale; + } + timeline.boneIndex = boneIndex; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float time = GetFloat(valueMap, "time", 0); + float x = GetFloat(valueMap, "x", defaultValue); + float y = GetFloat(valueMap, "y", defaultValue); + timeline.SetFrame(frameIndex, time, x * timelineScale, y * timelineScale); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TranslateTimeline.ENTRIES]); + + } + else + throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")"); + } + } + } + + // IK constraint timelines. + if (map.ContainsKey("ik")) + { + foreach (KeyValuePair constraintMap in (Dictionary)map["ik"]) + { + IkConstraintData constraint = skeletonData.FindIkConstraint(constraintMap.Key); + var values = (List)constraintMap.Value; + var timeline = new IkConstraintTimeline(values.Count); + timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(constraint); + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), GetFloat(valueMap, "mix", 1), + GetFloat(valueMap, "softness", 0) * scale, GetBoolean(valueMap, "bendPositive", true) ? 1 : -1, + GetBoolean(valueMap, "compress", false), GetBoolean(valueMap, "stretch", false)); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * IkConstraintTimeline.ENTRIES]); + } + } + + // Transform constraint timelines. + if (map.ContainsKey("transform")) + { + foreach (KeyValuePair constraintMap in (Dictionary)map["transform"]) + { + TransformConstraintData constraint = skeletonData.FindTransformConstraint(constraintMap.Key); + var values = (List)constraintMap.Value; + var timeline = new TransformConstraintTimeline(values.Count); + timeline.transformConstraintIndex = skeletonData.transformConstraints.IndexOf(constraint); + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), GetFloat(valueMap, "rotateMix", 1), + GetFloat(valueMap, "translateMix", 1), GetFloat(valueMap, "scaleMix", 1), GetFloat(valueMap, "shearMix", 1)); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TransformConstraintTimeline.ENTRIES]); + } + } + + // Path constraint timelines. + if (map.ContainsKey("path")) + { + foreach (KeyValuePair constraintMap in (Dictionary)map["path"]) + { + int index = skeletonData.FindPathConstraintIndex(constraintMap.Key); + if (index == -1) throw new Exception("Path constraint not found: " + constraintMap.Key); + PathConstraintData data = skeletonData.pathConstraints.Items[index]; + var timelineMap = (Dictionary)constraintMap.Value; + foreach (KeyValuePair timelineEntry in timelineMap) + { + var values = (List)timelineEntry.Value; + var timelineName = timelineEntry.Key; + if (timelineName == "position" || timelineName == "spacing") + { + PathConstraintPositionTimeline timeline; + float timelineScale = 1; + if (timelineName == "spacing") + { + timeline = new PathConstraintSpacingTimeline(values.Count); + if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale; + } + else + { + timeline = new PathConstraintPositionTimeline(values.Count); + if (data.positionMode == PositionMode.Fixed) timelineScale = scale; + } + timeline.pathConstraintIndex = index; + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), GetFloat(valueMap, timelineName, 0) * timelineScale); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintPositionTimeline.ENTRIES]); + } + else if (timelineName == "mix") + { + PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(values.Count); + timeline.pathConstraintIndex = index; + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), GetFloat(valueMap, "rotateMix", 1), + GetFloat(valueMap, "translateMix", 1)); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintMixTimeline.ENTRIES]); + } + } + } + } + + // Deform timelines. + if (map.ContainsKey("deform")) + { + foreach (KeyValuePair deformMap in (Dictionary)map["deform"]) + { + Skin skin = skeletonData.FindSkin(deformMap.Key); + foreach (KeyValuePair slotMap in (Dictionary)deformMap.Value) + { + int slotIndex = skeletonData.FindSlotIndex(slotMap.Key); + if (slotIndex == -1) throw new Exception("Slot not found: " + slotMap.Key); + foreach (KeyValuePair timelineMap in (Dictionary)slotMap.Value) + { + var values = (List)timelineMap.Value; + VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, timelineMap.Key); + if (attachment == null) throw new Exception("Deform attachment not found: " + timelineMap.Key); + bool weighted = attachment.bones != null; + float[] vertices = attachment.vertices; + int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length; + + var timeline = new DeformTimeline(values.Count); + timeline.slotIndex = slotIndex; + timeline.attachment = attachment; + + int frameIndex = 0; + foreach (Dictionary valueMap in values) + { + float[] deform; + if (!valueMap.ContainsKey("vertices")) + { + deform = weighted ? new float[deformLength] : vertices; + } + else + { + deform = new float[deformLength]; + int start = GetInt(valueMap, "offset", 0); + float[] verticesValue = GetFloatArray(valueMap, "vertices", 1); + Array.Copy(verticesValue, 0, deform, start, verticesValue.Length); + if (scale != 1) + { + for (int i = start, n = i + verticesValue.Length; i < n; i++) + deform[i] *= scale; + } + + if (!weighted) + { + for (int i = 0; i < deformLength; i++) + deform[i] += vertices[i]; + } + } + + timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), deform); + ReadCurve(valueMap, timeline, frameIndex); + frameIndex++; + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); + } + } + } + } + + // Draw order timeline. + if (map.ContainsKey("drawOrder") || map.ContainsKey("draworder")) + { + var values = (List)map[map.ContainsKey("drawOrder") ? "drawOrder" : "draworder"]; + var timeline = new DrawOrderTimeline(values.Count); + int slotCount = skeletonData.slots.Count; + int frameIndex = 0; + foreach (Dictionary drawOrderMap in values) + { + int[] drawOrder = null; + if (drawOrderMap.ContainsKey("offsets")) + { + drawOrder = new int[slotCount]; + for (int i = slotCount - 1; i >= 0; i--) + drawOrder[i] = -1; + var offsets = (List)drawOrderMap["offsets"]; + int[] unchanged = new int[slotCount - offsets.Count]; + int originalIndex = 0, unchangedIndex = 0; + foreach (Dictionary offsetMap in offsets) + { + int slotIndex = skeletonData.FindSlotIndex((string)offsetMap["slot"]); + if (slotIndex == -1) throw new Exception("Slot not found: " + offsetMap["slot"]); + // Collect unchanged items. + while (originalIndex != slotIndex) + unchanged[unchangedIndex++] = originalIndex++; + // Set changed items. + int index = originalIndex + (int)(float)offsetMap["offset"]; + drawOrder[index] = originalIndex++; + } + // Collect remaining unchanged items. + while (originalIndex < slotCount) + unchanged[unchangedIndex++] = originalIndex++; + // Fill in unchanged items. + for (int i = slotCount - 1; i >= 0; i--) + if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex]; + } + timeline.SetFrame(frameIndex++, GetFloat(drawOrderMap, "time", 0), drawOrder); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); + } + + // Event timeline. + if (map.ContainsKey("events")) + { + var eventsMap = (List)map["events"]; + var timeline = new EventTimeline(eventsMap.Count); + int frameIndex = 0; + foreach (Dictionary eventMap in eventsMap) + { + EventData eventData = skeletonData.FindEvent((string)eventMap["name"]); + if (eventData == null) throw new Exception("Event not found: " + eventMap["name"]); + var e = new Event(GetFloat(eventMap, "time", 0), eventData) + { + intValue = GetInt(eventMap, "int", eventData.Int), + floatValue = GetFloat(eventMap, "float", eventData.Float), + stringValue = GetString(eventMap, "string", eventData.String) + }; + if (e.data.AudioPath != null) + { + e.volume = GetFloat(eventMap, "volume", eventData.Volume); + e.balance = GetFloat(eventMap, "balance", eventData.Balance); + } + timeline.SetFrame(frameIndex++, e); + } + timelines.Add(timeline); + duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); + } + + timelines.TrimExcess(); + skeletonData.animations.Add(new Animation(name, timelines, duration)); + } + + static void ReadCurve(Dictionary valueMap, CurveTimeline timeline, int frameIndex) + { + if (!valueMap.ContainsKey("curve")) + return; + Object curveObject = valueMap["curve"]; + if (curveObject is string) + timeline.SetStepped(frameIndex); + else + timeline.SetCurve(frameIndex, (float)curveObject, GetFloat(valueMap, "c2", 0), GetFloat(valueMap, "c3", 1), GetFloat(valueMap, "c4", 1)); + } + + internal class LinkedMesh + { + internal string parent, skin; + internal int slotIndex; + internal MeshAttachment mesh; + internal bool inheritDeform; + + public LinkedMesh(MeshAttachment mesh, string skin, int slotIndex, string parent, bool inheritDeform) + { + this.mesh = mesh; + this.skin = skin; + this.slotIndex = slotIndex; + this.parent = parent; + this.inheritDeform = inheritDeform; + } + } + + static float[] GetFloatArray(Dictionary map, string name, float scale) + { + var list = (List)map[name]; + var values = new float[list.Count]; + if (scale == 1) + { + for (int i = 0, n = list.Count; i < n; i++) + values[i] = (float)list[i]; + } + else + { + for (int i = 0, n = list.Count; i < n; i++) + values[i] = (float)list[i] * scale; + } + return values; + } + + static int[] GetIntArray(Dictionary map, string name) + { + var list = (List)map[name]; + var values = new int[list.Count]; + for (int i = 0, n = list.Count; i < n; i++) + values[i] = (int)(float)list[i]; + return values; + } + + static float GetFloat(Dictionary map, string name, float defaultValue) + { + if (!map.ContainsKey(name)) + return defaultValue; + return (float)map[name]; + } + + static int GetInt(Dictionary map, string name, int defaultValue) + { + if (!map.ContainsKey(name)) + return defaultValue; + return (int)(float)map[name]; + } + + static bool GetBoolean(Dictionary map, string name, bool defaultValue) + { + if (!map.ContainsKey(name)) + return defaultValue; + return (bool)map[name]; + } + + static string GetString(Dictionary map, string name, string defaultValue) + { + if (!map.ContainsKey(name)) + return defaultValue; + return (string)map[name]; + } + + static float ToColor(string hexString, int colorIndex, int expectedLength = 8) + { + if (hexString.Length != expectedLength) + throw new ArgumentException("Color hexidecimal length must be " + expectedLength + ", recieved: " + hexString, "hexString"); + return Convert.ToInt32(hexString.Substring(colorIndex * 2, 2), 16) / (float)255; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Skin.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Skin.cs index 2c6b995..3d5d8ba 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Skin.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Skin.cs @@ -28,166 +28,192 @@ *****************************************************************************/ using System; -using System.Collections; using System.Collections.Generic; using Spine.Collections; -namespace Spine3_8_95 { - /// Stores attachments by slot index and attachment name. - /// See SkeletonData , Skeleton , and - /// Runtime skins in the Spine Runtimes Guide. - /// - public class Skin { - internal string name; - private OrderedDictionary attachments = new OrderedDictionary(SkinEntryComparer.Instance); - internal readonly ExposedList bones = new ExposedList(); - internal readonly ExposedList constraints = new ExposedList(); - - public string Name { get { return name; } } - public OrderedDictionary Attachments { get { return attachments; } } - public ExposedList Bones { get { return bones; } } - public ExposedList Constraints { get { return constraints; } } - - public Skin (string name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; - } - - /// 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. - public void SetAttachment (int slotIndex, string name, Attachment attachment) { - if (attachment == null) throw new ArgumentNullException("attachment", "attachment cannot be null."); - if (slotIndex < 0) throw new ArgumentNullException("slotIndex", "slotIndex must be >= 0."); - attachments[new SkinEntry(slotIndex, name, attachment)] = attachment; - } - - ///Adds all attachments, bones, and constraints from the specified skin to this skin. - public void AddSkin (Skin skin) { - foreach (BoneData data in skin.bones) - if (!bones.Contains(data)) bones.Add(data); - - foreach (ConstraintData data in skin.constraints) - if (!constraints.Contains(data)) constraints.Add(data); - - foreach (SkinEntry entry in skin.attachments.Keys) - SetAttachment(entry.SlotIndex, entry.Name, entry.Attachment); - } - - ///Adds all attachments from the specified skin to this skin. Attachments are deep copied. - public void CopySkin (Skin skin) { - foreach (BoneData data in skin.bones) - if (!bones.Contains(data)) bones.Add(data); - - foreach (ConstraintData data in skin.constraints) - if (!constraints.Contains(data)) constraints.Add(data); - - foreach (SkinEntry entry in skin.attachments.Keys) { - if (entry.Attachment is MeshAttachment) - SetAttachment(entry.SlotIndex, entry.Name, - entry.Attachment != null ? ((MeshAttachment)entry.Attachment).NewLinkedMesh() : null); - else - SetAttachment(entry.SlotIndex, entry.Name, entry.Attachment != null ? entry.Attachment.Copy() : null); - } - } - - /// Returns the attachment for the specified slot index and name, or null. - /// May be null. - public Attachment GetAttachment (int slotIndex, string name) { - var lookup = new SkinEntry(slotIndex, name, null); - Attachment attachment = null; - bool containsKey = attachments.TryGetValue(lookup, out attachment); - return containsKey ? attachment : null; - } - - /// Removes the attachment in the skin for the specified slot index and name, if any. - public void RemoveAttachment (int slotIndex, string name) { - if (slotIndex < 0) throw new ArgumentOutOfRangeException("slotIndex", "slotIndex must be >= 0"); - var lookup = new SkinEntry(slotIndex, name, null); - attachments.Remove(lookup); - } - - ///Returns all attachments contained in this skin. - public ICollection GetAttachments () { - return this.attachments.Keys; - } - - /// Returns all attachments in this skin for the specified slot index. - /// The target slotIndex. To find the slot index, use or - public void GetAttachments (int slotIndex, List attachments) { - foreach (SkinEntry entry in this.attachments.Keys) - if (entry.SlotIndex == slotIndex) attachments.Add(entry); - } - - ///Clears all attachments, bones, and constraints. - public void Clear () { - attachments.Clear(); - bones.Clear(); - constraints.Clear(); - } - - override public string ToString () { - return name; - } - - /// Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached. - internal void AttachAll (Skeleton skeleton, Skin oldSkin) { - foreach (SkinEntry entry in oldSkin.attachments.Keys) { - int slotIndex = entry.SlotIndex; - Slot slot = skeleton.slots.Items[slotIndex]; - if (slot.Attachment == entry.Attachment) { - Attachment attachment = GetAttachment(slotIndex, entry.Name); - if (attachment != null) slot.Attachment = attachment; - } - } - } - - /// Stores an entry in the skin consisting of the slot index, name, and attachment. - public struct SkinEntry { - private readonly int slotIndex; - private readonly string name; - private readonly Attachment attachment; - internal readonly int hashCode; - - public SkinEntry (int slotIndex, string name, Attachment attachment) { - this.slotIndex = slotIndex; - this.name = name; - this.attachment = attachment; - this.hashCode = this.name.GetHashCode() + this.slotIndex * 37; - } - - public int SlotIndex { - get { - return slotIndex; - } - } - - /// The name the attachment is associated with, equivalent to the skin placeholder name in the Spine editor. - public String Name { - get { - return name; - } - } - - public Attachment Attachment { - get { - return attachment; - } - } - } - - // Avoids boxing in the dictionary and is necessary to omit entry.attachment in the comparison. - class SkinEntryComparer : IEqualityComparer { - internal static readonly SkinEntryComparer Instance = new SkinEntryComparer(); - - bool IEqualityComparer.Equals (SkinEntry e1, SkinEntry e2) { - if (e1.SlotIndex != e2.SlotIndex) return false; - if (!string.Equals(e1.Name, e2.Name, StringComparison.Ordinal)) return false; - return true; - } - - int IEqualityComparer.GetHashCode (SkinEntry e) { - return e.Name.GetHashCode() + e.SlotIndex * 37; - } - } - } +namespace Spine3_8_95 +{ + /// Stores attachments by slot index and attachment name. + /// See SkeletonData , Skeleton , and + /// Runtime skins in the Spine Runtimes Guide. + /// + public class Skin + { + internal string name; + private OrderedDictionary attachments = new OrderedDictionary(SkinEntryComparer.Instance); + internal readonly ExposedList bones = new ExposedList(); + internal readonly ExposedList constraints = new ExposedList(); + + public string Name { get { return name; } } + public OrderedDictionary Attachments { get { return attachments; } } + public ExposedList Bones { get { return bones; } } + public ExposedList Constraints { get { return constraints; } } + + public Skin(string name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + } + + /// 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. + public void SetAttachment(int slotIndex, string name, Attachment attachment) + { + if (attachment == null) throw new ArgumentNullException("attachment", "attachment cannot be null."); + if (slotIndex < 0) throw new ArgumentNullException("slotIndex", "slotIndex must be >= 0."); + attachments[new SkinEntry(slotIndex, name, attachment)] = attachment; + } + + ///Adds all attachments, bones, and constraints from the specified skin to this skin. + public void AddSkin(Skin skin) + { + foreach (BoneData data in skin.bones) + if (!bones.Contains(data)) bones.Add(data); + + foreach (ConstraintData data in skin.constraints) + if (!constraints.Contains(data)) constraints.Add(data); + + foreach (SkinEntry entry in skin.attachments.Keys) + SetAttachment(entry.SlotIndex, entry.Name, entry.Attachment); + } + + ///Adds all attachments from the specified skin to this skin. Attachments are deep copied. + public void CopySkin(Skin skin) + { + foreach (BoneData data in skin.bones) + if (!bones.Contains(data)) bones.Add(data); + + foreach (ConstraintData data in skin.constraints) + if (!constraints.Contains(data)) constraints.Add(data); + + foreach (SkinEntry entry in skin.attachments.Keys) + { + if (entry.Attachment is MeshAttachment) + SetAttachment(entry.SlotIndex, entry.Name, + entry.Attachment != null ? ((MeshAttachment)entry.Attachment).NewLinkedMesh() : null); + else + SetAttachment(entry.SlotIndex, entry.Name, entry.Attachment != null ? entry.Attachment.Copy() : null); + } + } + + /// Returns the attachment for the specified slot index and name, or null. + /// May be null. + public Attachment GetAttachment(int slotIndex, string name) + { + var lookup = new SkinEntry(slotIndex, name, null); + Attachment attachment = null; + bool containsKey = attachments.TryGetValue(lookup, out attachment); + return containsKey ? attachment : null; + } + + /// Removes the attachment in the skin for the specified slot index and name, if any. + public void RemoveAttachment(int slotIndex, string name) + { + if (slotIndex < 0) throw new ArgumentOutOfRangeException("slotIndex", "slotIndex must be >= 0"); + var lookup = new SkinEntry(slotIndex, name, null); + attachments.Remove(lookup); + } + + ///Returns all attachments contained in this skin. + public ICollection GetAttachments() + { + return this.attachments.Keys; + } + + /// Returns all attachments in this skin for the specified slot index. + /// The target slotIndex. To find the slot index, use or + public void GetAttachments(int slotIndex, List attachments) + { + foreach (SkinEntry entry in this.attachments.Keys) + if (entry.SlotIndex == slotIndex) attachments.Add(entry); + } + + ///Clears all attachments, bones, and constraints. + public void Clear() + { + attachments.Clear(); + bones.Clear(); + constraints.Clear(); + } + + override public string ToString() + { + return name; + } + + /// Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached. + internal void AttachAll(Skeleton skeleton, Skin oldSkin) + { + foreach (SkinEntry entry in oldSkin.attachments.Keys) + { + int slotIndex = entry.SlotIndex; + Slot slot = skeleton.slots.Items[slotIndex]; + if (slot.Attachment == entry.Attachment) + { + Attachment attachment = GetAttachment(slotIndex, entry.Name); + if (attachment != null) slot.Attachment = attachment; + } + } + } + + /// Stores an entry in the skin consisting of the slot index, name, and attachment. + public struct SkinEntry + { + private readonly int slotIndex; + private readonly string name; + private readonly Attachment attachment; + internal readonly int hashCode; + + public SkinEntry(int slotIndex, string name, Attachment attachment) + { + this.slotIndex = slotIndex; + this.name = name; + this.attachment = attachment; + this.hashCode = this.name.GetHashCode() + this.slotIndex * 37; + } + + public int SlotIndex + { + get + { + return slotIndex; + } + } + + /// The name the attachment is associated with, equivalent to the skin placeholder name in the Spine editor. + public String Name + { + get + { + return name; + } + } + + public Attachment Attachment + { + get + { + return attachment; + } + } + } + + // Avoids boxing in the dictionary and is necessary to omit entry.attachment in the comparison. + class SkinEntryComparer : IEqualityComparer + { + internal static readonly SkinEntryComparer Instance = new SkinEntryComparer(); + + bool IEqualityComparer.Equals(SkinEntry e1, SkinEntry e2) + { + if (e1.SlotIndex != e2.SlotIndex) return false; + if (!string.Equals(e1.Name, e2.Name, StringComparison.Ordinal)) return false; + return true; + } + + int IEqualityComparer.GetHashCode(SkinEntry e) + { + return e.Name.GetHashCode() + e.SlotIndex * 37; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Slot.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Slot.cs index 7c60e4f..fe452dd 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Slot.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Slot.cs @@ -29,168 +29,188 @@ using System; -namespace Spine3_8_95 { - - /// - /// Stores a slot's current pose. Slots organize attachments for {@link Skeleton#drawOrder} purposes and provide a place to store - /// state for an attachment.State cannot be stored in an attachment itself because attachments are stateless and may be shared - /// across multiple skeletons. - /// - public class Slot { - internal SlotData data; - internal Bone bone; - internal float r, g, b, a; - internal float r2, g2, b2; - internal bool hasSecondColor; - internal Attachment attachment; - internal float attachmentTime; - internal ExposedList deform = new ExposedList(); - internal int attachmentState; - - public Slot (SlotData data, Bone bone) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); - this.data = data; - this.bone = bone; - - // darkColor = data.darkColor == null ? null : new Color(); - if (data.hasSecondColor) { - r2 = g2 = b2 = 0; - } - - SetToSetupPose(); - } - - /// Copy constructor. - public Slot(Slot slot, Bone bone) { - if (slot == null) throw new ArgumentNullException("slot", "slot cannot be null."); - if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); - data = slot.data; - this.bone = bone; - r = slot.r; - g = slot.g; - b = slot.b; - a = slot.a; - - // darkColor = slot.darkColor == null ? null : new Color(slot.darkColor); - if (slot.hasSecondColor) { - r2 = slot.r2; - g2 = slot.g2; - b2 = slot.b2; - } else { - r2 = g2 = b2 = 0; - } - hasSecondColor = slot.hasSecondColor; - - attachment = slot.attachment; - attachmentTime = slot.attachmentTime; - deform.AddRange(slot.deform); - } - - /// The slot's setup pose data. - public SlotData Data { get { return data; } } - /// The bone this slot belongs to. - public Bone Bone { get { return bone; } } - /// The skeleton this slot belongs to. - public Skeleton Skeleton { get { return bone.skeleton; } } - /// The color used to tint the slot's attachment. If is set, this is used as the light color for two - /// color tinting. - public float R { get { return r; } set { r = value; } } - /// The color used to tint the slot's attachment. If is set, this is used as the light color for two - /// color tinting. - public float G { get { return g; } set { g = value; } } - /// The color used to tint the slot's attachment. If is set, this is used as the light color for two - /// color tinting. - public float B { get { return b; } set { b = value; } } - /// The color used to tint the slot's attachment. If is set, this is used as the light color for two - /// color tinting. - public float A { get { return a; } set { a = value; } } - - public void ClampColor() { - r = MathUtils.Clamp(r, 0, 1); - g = MathUtils.Clamp(g, 0, 1); - b = MathUtils.Clamp(b, 0, 1); - a = MathUtils.Clamp(a, 0, 1); - } - - /// The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used. - /// - public float R2 { get { return r2; } set { r2 = value; } } - /// The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used. - /// - public float G2 { get { return g2; } set { g2 = value; } } - /// The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used. - /// - public float B2 { get { return b2; } set { b2 = value; } } - /// Whether R2 G2 B2 are used to tint the slot's attachment for two color tinting. False if two color tinting is not used. - public bool HasSecondColor { get { return data.hasSecondColor; } set { data.hasSecondColor = value; } } - - public void ClampSecondColor () { - r2 = MathUtils.Clamp(r2, 0, 1); - g2 = MathUtils.Clamp(g2, 0, 1); - b2 = MathUtils.Clamp(b2, 0, 1); - } - - public Attachment Attachment { - /// The current attachment for the slot, or null if the slot has no attachment. - get { return attachment; } - /// - /// Sets the slot's attachment and, if the attachment changed, resets and clears - /// . - /// May be null. - set { - if (attachment == value) return; - attachment = value; - attachmentTime = bone.skeleton.time; - deform.Clear(false); - } - } - - /// The time that has elapsed since the last time the attachment was set or cleared. Relies on Skeleton - /// - public float AttachmentTime { - get { return bone.skeleton.time - attachmentTime; } - set { attachmentTime = bone.skeleton.time - value; } - } - - /// Vertices to deform the slot's attachment. For an unweighted mesh, the entries are local positions for each vertex. For a - /// weighted mesh, the entries are an offset for each vertex which will be added to the mesh's local vertex positions. - /// - /// See and . - public ExposedList Deform { - get { - return deform; - } - set { - if (deform == null) throw new ArgumentNullException("deform", "deform cannot be null."); - deform = value; - } - } - - /// Sets this slot to the setup pose. - public void SetToSetupPose () { - r = data.r; - g = data.g; - b = data.b; - a = data.a; - - // if (darkColor != null) darkColor.set(data.darkColor); - if (HasSecondColor) { - r2 = data.r2; - g2 = data.g2; - b2 = data.b2; - } - - if (data.attachmentName == null) - Attachment = null; - else { - attachment = null; - Attachment = bone.skeleton.GetAttachment(data.index, data.attachmentName); - } - } - - override public string ToString () { - return data.name; - } - } +namespace Spine3_8_95 +{ + + /// + /// Stores a slot's current pose. Slots organize attachments for {@link Skeleton#drawOrder} purposes and provide a place to store + /// state for an attachment.State cannot be stored in an attachment itself because attachments are stateless and may be shared + /// across multiple skeletons. + /// + public class Slot + { + internal SlotData data; + internal Bone bone; + internal float r, g, b, a; + internal float r2, g2, b2; + internal bool hasSecondColor; + internal Attachment attachment; + internal float attachmentTime; + internal ExposedList deform = new ExposedList(); + internal int attachmentState; + + public Slot(SlotData data, Bone bone) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); + this.data = data; + this.bone = bone; + + // darkColor = data.darkColor == null ? null : new Color(); + if (data.hasSecondColor) + { + r2 = g2 = b2 = 0; + } + + SetToSetupPose(); + } + + /// Copy constructor. + public Slot(Slot slot, Bone bone) + { + if (slot == null) throw new ArgumentNullException("slot", "slot cannot be null."); + if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); + data = slot.data; + this.bone = bone; + r = slot.r; + g = slot.g; + b = slot.b; + a = slot.a; + + // darkColor = slot.darkColor == null ? null : new Color(slot.darkColor); + if (slot.hasSecondColor) + { + r2 = slot.r2; + g2 = slot.g2; + b2 = slot.b2; + } + else + { + r2 = g2 = b2 = 0; + } + hasSecondColor = slot.hasSecondColor; + + attachment = slot.attachment; + attachmentTime = slot.attachmentTime; + deform.AddRange(slot.deform); + } + + /// The slot's setup pose data. + public SlotData Data { get { return data; } } + /// The bone this slot belongs to. + public Bone Bone { get { return bone; } } + /// The skeleton this slot belongs to. + public Skeleton Skeleton { get { return bone.skeleton; } } + /// The color used to tint the slot's attachment. If is set, this is used as the light color for two + /// color tinting. + public float R { get { return r; } set { r = value; } } + /// The color used to tint the slot's attachment. If is set, this is used as the light color for two + /// color tinting. + public float G { get { return g; } set { g = value; } } + /// The color used to tint the slot's attachment. If is set, this is used as the light color for two + /// color tinting. + public float B { get { return b; } set { b = value; } } + /// The color used to tint the slot's attachment. If is set, this is used as the light color for two + /// color tinting. + public float A { get { return a; } set { a = value; } } + + public void ClampColor() + { + r = MathUtils.Clamp(r, 0, 1); + g = MathUtils.Clamp(g, 0, 1); + b = MathUtils.Clamp(b, 0, 1); + a = MathUtils.Clamp(a, 0, 1); + } + + /// The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used. + /// + public float R2 { get { return r2; } set { r2 = value; } } + /// The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used. + /// + public float G2 { get { return g2; } set { g2 = value; } } + /// The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used. + /// + public float B2 { get { return b2; } set { b2 = value; } } + /// Whether R2 G2 B2 are used to tint the slot's attachment for two color tinting. False if two color tinting is not used. + public bool HasSecondColor { get { return data.hasSecondColor; } set { data.hasSecondColor = value; } } + + public void ClampSecondColor() + { + r2 = MathUtils.Clamp(r2, 0, 1); + g2 = MathUtils.Clamp(g2, 0, 1); + b2 = MathUtils.Clamp(b2, 0, 1); + } + + public Attachment Attachment + { + /// The current attachment for the slot, or null if the slot has no attachment. + get { return attachment; } + /// + /// Sets the slot's attachment and, if the attachment changed, resets and clears + /// . + /// May be null. + set + { + if (attachment == value) return; + attachment = value; + attachmentTime = bone.skeleton.time; + deform.Clear(false); + } + } + + /// The time that has elapsed since the last time the attachment was set or cleared. Relies on Skeleton + /// + public float AttachmentTime + { + get { return bone.skeleton.time - attachmentTime; } + set { attachmentTime = bone.skeleton.time - value; } + } + + /// Vertices to deform the slot's attachment. For an unweighted mesh, the entries are local positions for each vertex. For a + /// weighted mesh, the entries are an offset for each vertex which will be added to the mesh's local vertex positions. + /// + /// See and . + public ExposedList Deform + { + get + { + return deform; + } + set + { + if (deform == null) throw new ArgumentNullException("deform", "deform cannot be null."); + deform = value; + } + } + + /// Sets this slot to the setup pose. + public void SetToSetupPose() + { + r = data.r; + g = data.g; + b = data.b; + a = data.a; + + // if (darkColor != null) darkColor.set(data.darkColor); + if (HasSecondColor) + { + r2 = data.r2; + g2 = data.g2; + b2 = data.b2; + } + + if (data.attachmentName == null) + Attachment = null; + else + { + attachment = null; + Attachment = bone.skeleton.GetAttachment(data.index, data.attachmentName); + } + } + + override public string ToString() + { + return data.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/SlotData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/SlotData.cs index 8fbdf9f..46986e8 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/SlotData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/SlotData.cs @@ -29,49 +29,53 @@ using System; -namespace Spine3_8_95 { - public class SlotData { - internal int index; - internal string name; - internal BoneData boneData; - internal float r = 1, g = 1, b = 1, a = 1; - internal float r2 = 0, g2 = 0, b2 = 0; - internal bool hasSecondColor = false; - internal string attachmentName; - internal BlendMode blendMode; +namespace Spine3_8_95 +{ + public class SlotData + { + internal int index; + internal string name; + internal BoneData boneData; + internal float r = 1, g = 1, b = 1, a = 1; + internal float r2 = 0, g2 = 0, b2 = 0; + internal bool hasSecondColor = false; + internal string attachmentName; + internal BlendMode blendMode; - /// The index of the slot in . - public int Index { get { return index; } } - /// The name of the slot, which is unique across all slots in the skeleton. - public string Name { get { return name; } } - /// The bone this slot belongs to. - public BoneData BoneData { get { return boneData; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } + /// The index of the slot in . + public int Index { get { return index; } } + /// The name of the slot, which is unique across all slots in the skeleton. + public string Name { get { return name; } } + /// The bone this slot belongs to. + public BoneData BoneData { get { return boneData; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } - public float R2 { get { return r2; } set { r2 = value; } } - public float G2 { get { return g2; } set { g2 = value; } } - public float B2 { get { return b2; } set { b2 = value; } } - public bool HasSecondColor { get { return hasSecondColor; } set { hasSecondColor = value; } } + public float R2 { get { return r2; } set { r2 = value; } } + public float G2 { get { return g2; } set { g2 = value; } } + public float B2 { get { return b2; } set { b2 = value; } } + public bool HasSecondColor { get { return hasSecondColor; } set { hasSecondColor = value; } } - /// The name of the attachment that is visible for this slot in the setup pose, or null if no attachment is visible. - public String AttachmentName { get { return attachmentName; } set { attachmentName = value; } } - /// The blend mode for drawing the slot's attachment. - public BlendMode BlendMode { get { return blendMode; } set { blendMode = value; } } + /// The name of the attachment that is visible for this slot in the setup pose, or null if no attachment is visible. + public String AttachmentName { get { return attachmentName; } set { attachmentName = value; } } + /// The blend mode for drawing the slot's attachment. + public BlendMode BlendMode { get { return blendMode; } set { blendMode = value; } } - public SlotData (int index, String name, BoneData boneData) { - if (index < 0) throw new ArgumentException ("index must be >= 0.", "index"); - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - if (boneData == null) throw new ArgumentNullException("boneData", "boneData cannot be null."); - this.index = index; - this.name = name; - this.boneData = boneData; - } + public SlotData(int index, String name, BoneData boneData) + { + if (index < 0) throw new ArgumentException("index must be >= 0.", "index"); + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + if (boneData == null) throw new ArgumentNullException("boneData", "boneData cannot be null."); + this.index = index; + this.name = name; + this.boneData = boneData; + } - override public string ToString () { - return name; - } - } + override public string ToString() + { + return name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/TransformConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/TransformConstraint.cs index 3c18afc..529e181 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/TransformConstraint.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/TransformConstraint.cs @@ -29,285 +29,317 @@ using System; -namespace Spine3_8_95 { - /// - /// - /// Stores the current pose for a transform constraint. A transform constraint adjusts the world transform of the constrained - /// bones to match that of the target bone. - /// - /// See Transform constraints in the Spine User Guide. - /// - public class TransformConstraint : IUpdatable { - internal TransformConstraintData data; - internal ExposedList bones; - internal Bone target; - internal float rotateMix, translateMix, scaleMix, shearMix; - - internal bool active; - - public TransformConstraint (TransformConstraintData data, Skeleton skeleton) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.data = data; - rotateMix = data.rotateMix; - translateMix = data.translateMix; - scaleMix = data.scaleMix; - shearMix = data.shearMix; - - bones = new ExposedList(); - foreach (BoneData boneData in data.bones) - bones.Add (skeleton.FindBone(boneData.name)); - - target = skeleton.FindBone(data.target.name); - } - - /// Copy constructor. - public TransformConstraint (TransformConstraint constraint, Skeleton skeleton) { - if (constraint == null) throw new ArgumentNullException("constraint cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); - data = constraint.data; - bones = new ExposedList(constraint.Bones.Count); - foreach (Bone bone in constraint.Bones) - bones.Add(skeleton.Bones.Items[bone.data.index]); - target = skeleton.Bones.Items[constraint.target.data.index]; - rotateMix = constraint.rotateMix; - translateMix = constraint.translateMix; - scaleMix = constraint.scaleMix; - shearMix = constraint.shearMix; - } - - /// Applies the constraint to the constrained bones. - public void Apply () { - Update(); - } - - public void Update () { - if (data.local) { - if (data.relative) - ApplyRelativeLocal(); - else - ApplyAbsoluteLocal(); - } else { - if (data.relative) - ApplyRelativeWorld(); - else - ApplyAbsoluteWorld(); - } - } - - void ApplyAbsoluteWorld () { - float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; - Bone target = this.target; - float ta = target.a, tb = target.b, tc = target.c, td = target.d; - float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad; - float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect; - var bones = this.bones; - for (int i = 0, n = bones.Count; i < n; i++) { - Bone bone = bones.Items[i]; - bool modified = false; - - if (rotateMix != 0) { - float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - float r = MathUtils.Atan2(tc, ta) - MathUtils.Atan2(c, a) + offsetRotation; - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) r += MathUtils.PI2; - r *= rotateMix; - float cos = MathUtils.Cos(r), sin = MathUtils.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; - modified = true; - } - - if (translateMix != 0) { - float tx, ty; //Vector2 temp = this.temp; - target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY)); - bone.worldX += (tx - bone.worldX) * translateMix; - bone.worldY += (ty - bone.worldY) * translateMix; - modified = true; - } - - if (scaleMix > 0) { - float s = (float)Math.Sqrt(bone.a * bone.a + bone.c * bone.c); - if (s != 0) s = (s + ((float)Math.Sqrt(ta * ta + tc * tc) - s + data.offsetScaleX) * scaleMix) / s; - bone.a *= s; - bone.c *= s; - s = (float)Math.Sqrt(bone.b * bone.b + bone.d * bone.d); - if (s != 0) s = (s + ((float)Math.Sqrt(tb * tb + td * td) - s + data.offsetScaleY) * scaleMix) / s; - bone.b *= s; - bone.d *= s; - modified = true; - } - - if (shearMix > 0) { - float b = bone.b, d = bone.d; - float by = MathUtils.Atan2(d, b); - float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta) - (by - MathUtils.Atan2(bone.c, bone.a)); - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) r += MathUtils.PI2; - r = by + (r + offsetShearY) * shearMix; - float s = (float)Math.Sqrt(b * b + d * d); - bone.b = MathUtils.Cos(r) * s; - bone.d = MathUtils.Sin(r) * s; - modified = true; - } - - if (modified) bone.appliedValid = false; - } - } - - void ApplyRelativeWorld () { - float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; - Bone target = this.target; - float ta = target.a, tb = target.b, tc = target.c, td = target.d; - float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad; - float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect; - var bones = this.bones; - for (int i = 0, n = bones.Count; i < n; i++) { - Bone bone = bones.Items[i]; - bool modified = false; - - if (rotateMix != 0) { - float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - float r = MathUtils.Atan2(tc, ta) + offsetRotation; - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) r += MathUtils.PI2; - r *= rotateMix; - float cos = MathUtils.Cos(r), sin = MathUtils.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; - modified = true; - } - - if (translateMix != 0) { - float tx, ty; //Vector2 temp = this.temp; - target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY)); - bone.worldX += tx * translateMix; - bone.worldY += ty * translateMix; - modified = true; - } - - if (scaleMix > 0) { - float s = ((float)Math.Sqrt(ta * ta + tc * tc) - 1 + data.offsetScaleX) * scaleMix + 1; - bone.a *= s; - bone.c *= s; - s = ((float)Math.Sqrt(tb * tb + td * td) - 1 + data.offsetScaleY) * scaleMix + 1; - bone.b *= s; - bone.d *= s; - modified = true; - } - - if (shearMix > 0) { - float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta); - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) r += MathUtils.PI2; - float b = bone.b, d = bone.d; - r = MathUtils.Atan2(d, b) + (r - MathUtils.PI / 2 + offsetShearY) * shearMix; - float s = (float)Math.Sqrt(b * b + d * d); - bone.b = MathUtils.Cos(r) * s; - bone.d = MathUtils.Sin(r) * s; - modified = true; - } - - if (modified) bone.appliedValid = false; - } - } - - void ApplyAbsoluteLocal () { - float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; - Bone target = this.target; - if (!target.appliedValid) target.UpdateAppliedTransform(); - var bonesItems = this.bones.Items; - for (int i = 0, n = this.bones.Count; i < n; i++) { - Bone bone = bonesItems[i]; - if (!bone.appliedValid) bone.UpdateAppliedTransform(); - - float rotation = bone.arotation; - if (rotateMix != 0) { - float r = target.arotation - rotation + data.offsetRotation; - r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; - rotation += r * rotateMix; - } - - float x = bone.ax, y = bone.ay; - if (translateMix != 0) { - x += (target.ax - x + data.offsetX) * translateMix; - y += (target.ay - y + data.offsetY) * translateMix; - } - - float scaleX = bone.ascaleX, scaleY = bone.ascaleY; - if (scaleMix != 0) { - if (scaleX != 0) scaleX = (scaleX + (target.ascaleX - scaleX + data.offsetScaleX) * scaleMix) / scaleX; - if (scaleY != 0) scaleY = (scaleY + (target.ascaleY - scaleY + data.offsetScaleY) * scaleMix) / scaleY; - } - - float shearY = bone.ashearY; - if (shearMix != 0) { - float r = target.ashearY - shearY + data.offsetShearY; - r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; - shearY += r * shearMix; - } - - bone.UpdateWorldTransform(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY); - } - } - - void ApplyRelativeLocal () { - float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; - Bone target = this.target; - if (!target.appliedValid) target.UpdateAppliedTransform(); - var bonesItems = this.bones.Items; - for (int i = 0, n = this.bones.Count; i < n; i++) { - Bone bone = bonesItems[i]; - if (!bone.appliedValid) bone.UpdateAppliedTransform(); - - float rotation = bone.arotation; - if (rotateMix != 0) rotation += (target.arotation + data.offsetRotation) * rotateMix; - - float x = bone.ax, y = bone.ay; - if (translateMix != 0) { - x += (target.ax + data.offsetX) * translateMix; - y += (target.ay + data.offsetY) * translateMix; - } - - float scaleX = bone.ascaleX, scaleY = bone.ascaleY; - if (scaleMix != 0) { - scaleX *= ((target.ascaleX - 1 + data.offsetScaleX) * scaleMix) + 1; - scaleY *= ((target.ascaleY - 1 + data.offsetScaleY) * scaleMix) + 1; - } - - float shearY = bone.ashearY; - if (shearMix != 0) shearY += (target.ashearY + data.offsetShearY) * shearMix; - - bone.UpdateWorldTransform(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY); - } - } - - /// The bones that will be modified by this transform constraint. - public ExposedList Bones { get { return bones; } } - /// The target bone whose world transform will be copied to the constrained bones. - public Bone Target { get { return target; } set { target = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotations. - public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained translations. - public float TranslateMix { get { return translateMix; } set { translateMix = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained scales. - public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained scales. - public float ShearMix { get { return shearMix; } set { shearMix = value; } } - public bool Active { get { return active; } } - /// The transform constraint's setup pose data. - public TransformConstraintData Data { get { return data; } } - - override public string ToString () { - return data.name; - } - } +namespace Spine3_8_95 +{ + /// + /// + /// Stores the current pose for a transform constraint. A transform constraint adjusts the world transform of the constrained + /// bones to match that of the target bone. + /// + /// See Transform constraints in the Spine User Guide. + /// + public class TransformConstraint : IUpdatable + { + internal TransformConstraintData data; + internal ExposedList bones; + internal Bone target; + internal float rotateMix, translateMix, scaleMix, shearMix; + + internal bool active; + + public TransformConstraint(TransformConstraintData data, Skeleton skeleton) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + rotateMix = data.rotateMix; + translateMix = data.translateMix; + scaleMix = data.scaleMix; + shearMix = data.shearMix; + + bones = new ExposedList(); + foreach (BoneData boneData in data.bones) + bones.Add(skeleton.FindBone(boneData.name)); + + target = skeleton.FindBone(data.target.name); + } + + /// Copy constructor. + public TransformConstraint(TransformConstraint constraint, Skeleton skeleton) + { + if (constraint == null) throw new ArgumentNullException("constraint cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); + data = constraint.data; + bones = new ExposedList(constraint.Bones.Count); + foreach (Bone bone in constraint.Bones) + bones.Add(skeleton.Bones.Items[bone.data.index]); + target = skeleton.Bones.Items[constraint.target.data.index]; + rotateMix = constraint.rotateMix; + translateMix = constraint.translateMix; + scaleMix = constraint.scaleMix; + shearMix = constraint.shearMix; + } + + /// Applies the constraint to the constrained bones. + public void Apply() + { + Update(); + } + + public void Update() + { + if (data.local) + { + if (data.relative) + ApplyRelativeLocal(); + else + ApplyAbsoluteLocal(); + } + else + { + if (data.relative) + ApplyRelativeWorld(); + else + ApplyAbsoluteWorld(); + } + } + + void ApplyAbsoluteWorld() + { + float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; + Bone target = this.target; + float ta = target.a, tb = target.b, tc = target.c, td = target.d; + float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad; + float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect; + var bones = this.bones; + for (int i = 0, n = bones.Count; i < n; i++) + { + Bone bone = bones.Items[i]; + bool modified = false; + + if (rotateMix != 0) + { + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + float r = MathUtils.Atan2(tc, ta) - MathUtils.Atan2(c, a) + offsetRotation; + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) r += MathUtils.PI2; + r *= rotateMix; + float cos = MathUtils.Cos(r), sin = MathUtils.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; + modified = true; + } + + if (translateMix != 0) + { + float tx, ty; //Vector2 temp = this.temp; + target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY)); + bone.worldX += (tx - bone.worldX) * translateMix; + bone.worldY += (ty - bone.worldY) * translateMix; + modified = true; + } + + if (scaleMix > 0) + { + float s = (float)Math.Sqrt(bone.a * bone.a + bone.c * bone.c); + if (s != 0) s = (s + ((float)Math.Sqrt(ta * ta + tc * tc) - s + data.offsetScaleX) * scaleMix) / s; + bone.a *= s; + bone.c *= s; + s = (float)Math.Sqrt(bone.b * bone.b + bone.d * bone.d); + if (s != 0) s = (s + ((float)Math.Sqrt(tb * tb + td * td) - s + data.offsetScaleY) * scaleMix) / s; + bone.b *= s; + bone.d *= s; + modified = true; + } + + if (shearMix > 0) + { + float b = bone.b, d = bone.d; + float by = MathUtils.Atan2(d, b); + float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta) - (by - MathUtils.Atan2(bone.c, bone.a)); + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) r += MathUtils.PI2; + r = by + (r + offsetShearY) * shearMix; + float s = (float)Math.Sqrt(b * b + d * d); + bone.b = MathUtils.Cos(r) * s; + bone.d = MathUtils.Sin(r) * s; + modified = true; + } + + if (modified) bone.appliedValid = false; + } + } + + void ApplyRelativeWorld() + { + float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; + Bone target = this.target; + float ta = target.a, tb = target.b, tc = target.c, td = target.d; + float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad; + float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect; + var bones = this.bones; + for (int i = 0, n = bones.Count; i < n; i++) + { + Bone bone = bones.Items[i]; + bool modified = false; + + if (rotateMix != 0) + { + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + float r = MathUtils.Atan2(tc, ta) + offsetRotation; + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) r += MathUtils.PI2; + r *= rotateMix; + float cos = MathUtils.Cos(r), sin = MathUtils.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; + modified = true; + } + + if (translateMix != 0) + { + float tx, ty; //Vector2 temp = this.temp; + target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY)); + bone.worldX += tx * translateMix; + bone.worldY += ty * translateMix; + modified = true; + } + + if (scaleMix > 0) + { + float s = ((float)Math.Sqrt(ta * ta + tc * tc) - 1 + data.offsetScaleX) * scaleMix + 1; + bone.a *= s; + bone.c *= s; + s = ((float)Math.Sqrt(tb * tb + td * td) - 1 + data.offsetScaleY) * scaleMix + 1; + bone.b *= s; + bone.d *= s; + modified = true; + } + + if (shearMix > 0) + { + float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta); + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) r += MathUtils.PI2; + float b = bone.b, d = bone.d; + r = MathUtils.Atan2(d, b) + (r - MathUtils.PI / 2 + offsetShearY) * shearMix; + float s = (float)Math.Sqrt(b * b + d * d); + bone.b = MathUtils.Cos(r) * s; + bone.d = MathUtils.Sin(r) * s; + modified = true; + } + + if (modified) bone.appliedValid = false; + } + } + + void ApplyAbsoluteLocal() + { + float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; + Bone target = this.target; + if (!target.appliedValid) target.UpdateAppliedTransform(); + var bonesItems = this.bones.Items; + for (int i = 0, n = this.bones.Count; i < n; i++) + { + Bone bone = bonesItems[i]; + if (!bone.appliedValid) bone.UpdateAppliedTransform(); + + float rotation = bone.arotation; + if (rotateMix != 0) + { + float r = target.arotation - rotation + data.offsetRotation; + r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; + rotation += r * rotateMix; + } + + float x = bone.ax, y = bone.ay; + if (translateMix != 0) + { + x += (target.ax - x + data.offsetX) * translateMix; + y += (target.ay - y + data.offsetY) * translateMix; + } + + float scaleX = bone.ascaleX, scaleY = bone.ascaleY; + if (scaleMix != 0) + { + if (scaleX != 0) scaleX = (scaleX + (target.ascaleX - scaleX + data.offsetScaleX) * scaleMix) / scaleX; + if (scaleY != 0) scaleY = (scaleY + (target.ascaleY - scaleY + data.offsetScaleY) * scaleMix) / scaleY; + } + + float shearY = bone.ashearY; + if (shearMix != 0) + { + float r = target.ashearY - shearY + data.offsetShearY; + r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; + shearY += r * shearMix; + } + + bone.UpdateWorldTransform(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY); + } + } + + void ApplyRelativeLocal() + { + float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix; + Bone target = this.target; + if (!target.appliedValid) target.UpdateAppliedTransform(); + var bonesItems = this.bones.Items; + for (int i = 0, n = this.bones.Count; i < n; i++) + { + Bone bone = bonesItems[i]; + if (!bone.appliedValid) bone.UpdateAppliedTransform(); + + float rotation = bone.arotation; + if (rotateMix != 0) rotation += (target.arotation + data.offsetRotation) * rotateMix; + + float x = bone.ax, y = bone.ay; + if (translateMix != 0) + { + x += (target.ax + data.offsetX) * translateMix; + y += (target.ay + data.offsetY) * translateMix; + } + + float scaleX = bone.ascaleX, scaleY = bone.ascaleY; + if (scaleMix != 0) + { + scaleX *= ((target.ascaleX - 1 + data.offsetScaleX) * scaleMix) + 1; + scaleY *= ((target.ascaleY - 1 + data.offsetScaleY) * scaleMix) + 1; + } + + float shearY = bone.ashearY; + if (shearMix != 0) shearY += (target.ashearY + data.offsetShearY) * shearMix; + + bone.UpdateWorldTransform(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY); + } + } + + /// The bones that will be modified by this transform constraint. + public ExposedList Bones { get { return bones; } } + /// The target bone whose world transform will be copied to the constrained bones. + public Bone Target { get { return target; } set { target = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotations. + public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained translations. + public float TranslateMix { get { return translateMix; } set { translateMix = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained scales. + public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained scales. + public float ShearMix { get { return shearMix; } set { shearMix = value; } } + public bool Active { get { return active; } } + /// The transform constraint's setup pose data. + public TransformConstraintData Data { get { return data; } } + + override public string ToString() + { + return data.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/TransformConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/TransformConstraintData.cs index 7f7743e..e6790cc 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/TransformConstraintData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/TransformConstraintData.cs @@ -27,34 +27,35 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; +namespace Spine3_8_95 +{ + public class TransformConstraintData : ConstraintData + { + internal ExposedList bones = new ExposedList(); + internal BoneData target; + internal float rotateMix, translateMix, scaleMix, shearMix; + internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY; + internal bool relative, local; -namespace Spine3_8_95 { - public class TransformConstraintData : ConstraintData { - internal ExposedList bones = new ExposedList(); - internal BoneData target; - internal float rotateMix, translateMix, scaleMix, shearMix; - internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY; - internal bool relative, local; + public ExposedList Bones { get { return bones; } } + public BoneData Target { get { return target; } set { target = value; } } + public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } + public float TranslateMix { get { return translateMix; } set { translateMix = value; } } + public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } } + public float ShearMix { get { return shearMix; } set { shearMix = value; } } - public ExposedList Bones { get { return bones; } } - public BoneData Target { get { return target; } set { target = value; } } - public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } - public float TranslateMix { get { return translateMix; } set { translateMix = value; } } - public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } } - public float ShearMix { get { return shearMix; } set { shearMix = value; } } + public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } + public float OffsetX { get { return offsetX; } set { offsetX = value; } } + public float OffsetY { get { return offsetY; } set { offsetY = value; } } + public float OffsetScaleX { get { return offsetScaleX; } set { offsetScaleX = value; } } + public float OffsetScaleY { get { return offsetScaleY; } set { offsetScaleY = value; } } + public float OffsetShearY { get { return offsetShearY; } set { offsetShearY = value; } } - public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } - public float OffsetX { get { return offsetX; } set { offsetX = value; } } - public float OffsetY { get { return offsetY; } set { offsetY = value; } } - public float OffsetScaleX { get { return offsetScaleX; } set { offsetScaleX = value; } } - public float OffsetScaleY { get { return offsetScaleY; } set { offsetScaleY = value; } } - public float OffsetShearY { get { return offsetShearY; } set { offsetShearY = value; } } + public bool Relative { get { return relative; } set { relative = value; } } + public bool Local { get { return local; } set { local = value; } } - public bool Relative { get { return relative; } set { relative = value; } } - public bool Local { get { return local; } set { local = value; } } - - public TransformConstraintData (string name) : base(name) { - } - } + public TransformConstraintData(string name) : base(name) + { + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Triangulator.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Triangulator.cs index 793f04e..74f44a4 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Triangulator.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/Triangulator.cs @@ -29,249 +29,280 @@ using System; -namespace Spine3_8_95 { - public class Triangulator { - private readonly ExposedList> convexPolygons = new ExposedList>(); - private readonly ExposedList> convexPolygonsIndices = new ExposedList>(); - - private readonly ExposedList indicesArray = new ExposedList(); - private readonly ExposedList isConcaveArray = new ExposedList(); - private readonly ExposedList triangles = new ExposedList(); - - private readonly Pool> polygonPool = new Pool>(); - private readonly Pool> polygonIndicesPool = new Pool>(); - - public ExposedList Triangulate (ExposedList verticesArray) { - var vertices = verticesArray.Items; - int vertexCount = verticesArray.Count >> 1; - - var indicesArray = this.indicesArray; - indicesArray.Clear(); - int[] indices = indicesArray.Resize(vertexCount).Items; - for (int i = 0; i < vertexCount; i++) - indices[i] = i; - - var isConcaveArray = this.isConcaveArray; - bool[] isConcave = isConcaveArray.Resize(vertexCount).Items; - for (int i = 0, n = vertexCount; i < n; ++i) - isConcave[i] = IsConcave(i, vertexCount, vertices, indices); - - var triangles = this.triangles; - triangles.Clear(); - triangles.EnsureCapacity(Math.Max(0, vertexCount - 2) << 2); - - while (vertexCount > 3) { - // Find ear tip. - int previous = vertexCount - 1, i = 0, next = 1; - - // outer: - while (true) { - if (!isConcave[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 (int ii = (next + 1) % vertexCount; ii != previous; ii = (ii + 1) % vertexCount) { - if (!isConcave[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 (!isConcave[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]); - indicesArray.RemoveAt(i); - isConcaveArray.RemoveAt(i); - vertexCount--; - - int previousIndex = (vertexCount + i - 1) % vertexCount; - int nextIndex = i == vertexCount ? 0 : i; - isConcave[previousIndex] = IsConcave(previousIndex, vertexCount, vertices, indices); - isConcave[nextIndex] = IsConcave(nextIndex, vertexCount, vertices, indices); - } - - if (vertexCount == 3) { - triangles.Add(indices[2]); - triangles.Add(indices[0]); - triangles.Add(indices[1]); - } - - return triangles; - } - - public ExposedList> Decompose (ExposedList verticesArray, ExposedList triangles) { - var vertices = verticesArray.Items; - var convexPolygons = this.convexPolygons; - for (int i = 0, n = convexPolygons.Count; i < n; i++) { - polygonPool.Free(convexPolygons.Items[i]); - } - convexPolygons.Clear(); - - var convexPolygonsIndices = this.convexPolygonsIndices; - for (int i = 0, n = convexPolygonsIndices.Count; i < n; i++) { - polygonIndicesPool.Free(convexPolygonsIndices.Items[i]); - } - convexPolygonsIndices.Clear(); - - var polygonIndices = polygonIndicesPool.Obtain(); - polygonIndices.Clear(); - - var polygon = polygonPool.Obtain(); - polygon.Clear(); - - // Merge subsequent triangles if they form a triangle fan. - int fanBaseIndex = -1, lastWinding = 0; - int[] trianglesItems = triangles.Items; - for (int i = 0, n = triangles.Count; i < n; i += 3) { - int t1 = trianglesItems[i] << 1, t2 = trianglesItems[i + 1] << 1, t3 = trianglesItems[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). - var merged = false; - if (fanBaseIndex == t1) { - int o = polygon.Count - 4; - float[] p = polygon.Items; - 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.Count > 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.Count > 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 (int i = 0, n = convexPolygons.Count; i < n; i++) { - polygonIndices = convexPolygonsIndices.Items[i]; - if (polygonIndices.Count == 0) continue; - int firstIndex = polygonIndices.Items[0]; - int lastIndex = polygonIndices.Items[polygonIndices.Count - 1]; - - polygon = convexPolygons.Items[i]; - int o = polygon.Count - 4; - float[] p = polygon.Items; - 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 winding = Winding(prevPrevX, prevPrevY, prevX, prevY, firstX, firstY); - - for (int ii = 0; ii < n; ii++) { - if (ii == i) continue; - var otherIndices = convexPolygonsIndices.Items[ii]; - if (otherIndices.Count != 3) continue; - int otherFirstIndex = otherIndices.Items[0]; - int otherSecondIndex = otherIndices.Items[1]; - int otherLastIndex = otherIndices.Items[2]; - - var otherPoly = convexPolygons.Items[ii]; - float x3 = otherPoly.Items[otherPoly.Count - 2], y3 = otherPoly.Items[otherPoly.Count - 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 == winding && winding2 == winding) { - 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 = convexPolygons.Count - 1; i >= 0; i--) { - polygon = convexPolygons.Items[i]; - if (polygon.Count == 0) { - convexPolygons.RemoveAt(i); - polygonPool.Free(polygon); - polygonIndices = convexPolygonsIndices.Items[i]; - convexPolygonsIndices.RemoveAt(i); - polygonIndicesPool.Free(polygonIndices); - } - } - - return convexPolygons; - } - - static private bool IsConcave (int index, int vertexCount, float[] vertices, int[] 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]); - } - - static private bool PositiveArea (float p1x, float p1y, float p2x, float p2y, float p3x, float p3y) { - return p1x * (p3y - p2y) + p2x * (p1y - p3y) + p3x * (p2y - p1y) >= 0; - } - - static private int 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; - } - } +namespace Spine3_8_95 +{ + public class Triangulator + { + private readonly ExposedList> convexPolygons = new ExposedList>(); + private readonly ExposedList> convexPolygonsIndices = new ExposedList>(); + + private readonly ExposedList indicesArray = new ExposedList(); + private readonly ExposedList isConcaveArray = new ExposedList(); + private readonly ExposedList triangles = new ExposedList(); + + private readonly Pool> polygonPool = new Pool>(); + private readonly Pool> polygonIndicesPool = new Pool>(); + + public ExposedList Triangulate(ExposedList verticesArray) + { + var vertices = verticesArray.Items; + int vertexCount = verticesArray.Count >> 1; + + var indicesArray = this.indicesArray; + indicesArray.Clear(); + int[] indices = indicesArray.Resize(vertexCount).Items; + for (int i = 0; i < vertexCount; i++) + indices[i] = i; + + var isConcaveArray = this.isConcaveArray; + bool[] isConcave = isConcaveArray.Resize(vertexCount).Items; + for (int i = 0, n = vertexCount; i < n; ++i) + isConcave[i] = IsConcave(i, vertexCount, vertices, indices); + + var triangles = this.triangles; + triangles.Clear(); + triangles.EnsureCapacity(Math.Max(0, vertexCount - 2) << 2); + + while (vertexCount > 3) + { + // Find ear tip. + int previous = vertexCount - 1, i = 0, next = 1; + + // outer: + while (true) + { + if (!isConcave[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 (int ii = (next + 1) % vertexCount; ii != previous; ii = (ii + 1) % vertexCount) + { + if (!isConcave[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 (!isConcave[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]); + indicesArray.RemoveAt(i); + isConcaveArray.RemoveAt(i); + vertexCount--; + + int previousIndex = (vertexCount + i - 1) % vertexCount; + int nextIndex = i == vertexCount ? 0 : i; + isConcave[previousIndex] = IsConcave(previousIndex, vertexCount, vertices, indices); + isConcave[nextIndex] = IsConcave(nextIndex, vertexCount, vertices, indices); + } + + if (vertexCount == 3) + { + triangles.Add(indices[2]); + triangles.Add(indices[0]); + triangles.Add(indices[1]); + } + + return triangles; + } + + public ExposedList> Decompose(ExposedList verticesArray, ExposedList triangles) + { + var vertices = verticesArray.Items; + var convexPolygons = this.convexPolygons; + for (int i = 0, n = convexPolygons.Count; i < n; i++) + { + polygonPool.Free(convexPolygons.Items[i]); + } + convexPolygons.Clear(); + + var convexPolygonsIndices = this.convexPolygonsIndices; + for (int i = 0, n = convexPolygonsIndices.Count; i < n; i++) + { + polygonIndicesPool.Free(convexPolygonsIndices.Items[i]); + } + convexPolygonsIndices.Clear(); + + var polygonIndices = polygonIndicesPool.Obtain(); + polygonIndices.Clear(); + + var polygon = polygonPool.Obtain(); + polygon.Clear(); + + // Merge subsequent triangles if they form a triangle fan. + int fanBaseIndex = -1, lastWinding = 0; + int[] trianglesItems = triangles.Items; + for (int i = 0, n = triangles.Count; i < n; i += 3) + { + int t1 = trianglesItems[i] << 1, t2 = trianglesItems[i + 1] << 1, t3 = trianglesItems[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). + var merged = false; + if (fanBaseIndex == t1) + { + int o = polygon.Count - 4; + float[] p = polygon.Items; + 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.Count > 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.Count > 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 (int i = 0, n = convexPolygons.Count; i < n; i++) + { + polygonIndices = convexPolygonsIndices.Items[i]; + if (polygonIndices.Count == 0) continue; + int firstIndex = polygonIndices.Items[0]; + int lastIndex = polygonIndices.Items[polygonIndices.Count - 1]; + + polygon = convexPolygons.Items[i]; + int o = polygon.Count - 4; + float[] p = polygon.Items; + 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 winding = Winding(prevPrevX, prevPrevY, prevX, prevY, firstX, firstY); + + for (int ii = 0; ii < n; ii++) + { + if (ii == i) continue; + var otherIndices = convexPolygonsIndices.Items[ii]; + if (otherIndices.Count != 3) continue; + int otherFirstIndex = otherIndices.Items[0]; + int otherSecondIndex = otherIndices.Items[1]; + int otherLastIndex = otherIndices.Items[2]; + + var otherPoly = convexPolygons.Items[ii]; + float x3 = otherPoly.Items[otherPoly.Count - 2], y3 = otherPoly.Items[otherPoly.Count - 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 == winding && winding2 == winding) + { + 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 = convexPolygons.Count - 1; i >= 0; i--) + { + polygon = convexPolygons.Items[i]; + if (polygon.Count == 0) + { + convexPolygons.RemoveAt(i); + polygonPool.Free(polygon); + polygonIndices = convexPolygonsIndices.Items[i]; + convexPolygonsIndices.RemoveAt(i); + polygonIndicesPool.Free(polygonIndices); + } + } + + return convexPolygons; + } + + static private bool IsConcave(int index, int vertexCount, float[] vertices, int[] 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]); + } + + static private bool PositiveArea(float p1x, float p1y, float p2x, float p2y, float p3x, float p3y) + { + return p1x * (p3y - p2y) + p2x * (p1y - p3y) + p3x * (p2y - p1y) >= 0; + } + + static private int 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/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/XnaLoader/MeshBatcher.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/XnaLoader/MeshBatcher.cs index 2c26b1b..4902516 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/XnaLoader/MeshBatcher.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/XnaLoader/MeshBatcher.cs @@ -29,167 +29,183 @@ using System; using System.Collections.Generic; -using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; -namespace Spine3_8_95 { - public struct VertexPositionColorTextureColor : IVertexType { - public Vector3 Position; - public Color Color; - public Vector2 TextureCoordinate; - public Color Color2; - - public readonly static VertexDeclaration VertexDeclaration = new VertexDeclaration - ( - new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0), - new VertexElement(12, VertexElementFormat.Color, VertexElementUsage.Color, 0), - new VertexElement(16, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0), - new VertexElement(24, VertexElementFormat.Color, VertexElementUsage.Color, 1) - ); - - VertexDeclaration IVertexType.VertexDeclaration { - get { return VertexDeclaration; } - } - } - - // #region License - // /* - // Microsoft Public License (Ms-PL) - // MonoGame - Copyright � 2009 The MonoGame Team - // - // All rights reserved. - // - // This license governs use of the accompanying software. If you use the software, you accept this license. If you do not - // accept the license, do not use the software. - // - // 1. Definitions - // The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under - // U.S. copyright law. - // - // A "contribution" is the original software, or any additions or changes to the software. - // A "contributor" is any person that distributes its contribution under this license. - // "Licensed patents" are a contributor's patent claims that read directly on its contribution. - // - // 2. Grant of Rights - // (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, - // each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. - // (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, - // each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. - // - // 3. Conditions and Limitations - // (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. - // (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, - // your patent license from such contributor to the software ends automatically. - // (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution - // notices that are present in the software. - // (D) If you distribute any portion of the software in source code form, you may do so only under this license by including - // a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object - // code form, you may only do so under a license that complies with this license. - // (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees - // or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent - // permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular - // purpose and non-infringement. - // */ - // #endregion License - // - - /// Draws batched meshes. - public class MeshBatcher { - private readonly List items; - private readonly Queue freeItems; - private VertexPositionColorTextureColor[] vertexArray = { }; - private short[] triangles = { }; - - public MeshBatcher () { - items = new List(256); - freeItems = new Queue(256); - EnsureCapacity(256, 512); - } - - /// Returns a pooled MeshItem. - public MeshItem NextItem (int vertexCount, int triangleCount) { - MeshItem item = freeItems.Count > 0 ? freeItems.Dequeue() : new MeshItem(); - if (item.vertices.Length < vertexCount) item.vertices = new VertexPositionColorTextureColor[vertexCount]; - if (item.triangles.Length < triangleCount) item.triangles = new int[triangleCount]; - item.vertexCount = vertexCount; - item.triangleCount = triangleCount; - items.Add(item); - return item; - } - - private void EnsureCapacity (int vertexCount, int triangleCount) { - if (vertexArray.Length < vertexCount) vertexArray = new VertexPositionColorTextureColor[vertexCount]; - if (triangles.Length < triangleCount) triangles = new short[triangleCount]; - } - - public void Draw (GraphicsDevice device) { - if (items.Count == 0) return; - - int itemCount = items.Count; - int vertexCount = 0, triangleCount = 0; - for (int i = 0; i < itemCount; i++) { - MeshItem item = items[i]; - vertexCount += item.vertexCount; - triangleCount += item.triangleCount; - } - EnsureCapacity(vertexCount, triangleCount); - - vertexCount = 0; - triangleCount = 0; - Texture2D lastTexture = null; - for (int i = 0; i < itemCount; i++) { - MeshItem item = items[i]; - int itemVertexCount = item.vertexCount; - - if (item.texture != lastTexture || vertexCount + itemVertexCount > short.MaxValue) { - FlushVertexArray(device, vertexCount, triangleCount); - vertexCount = 0; - triangleCount = 0; - lastTexture = item.texture; - device.Textures[0] = lastTexture; - if (item.textureLayers != null) { - for (int layer = 1; layer < item.textureLayers.Length; ++layer) - device.Textures[layer] = item.textureLayers[layer]; - } - } - - int[] itemTriangles = item.triangles; - int itemTriangleCount = item.triangleCount; - for (int ii = 0, t = triangleCount; ii < itemTriangleCount; ii++, t++) - triangles[t] = (short)(itemTriangles[ii] + vertexCount); - triangleCount += itemTriangleCount; - - Array.Copy(item.vertices, 0, vertexArray, vertexCount, itemVertexCount); - vertexCount += itemVertexCount; - } - FlushVertexArray(device, vertexCount, triangleCount); - } - - public void AfterLastDrawPass () { - int itemCount = items.Count; - for (int i = 0; i < itemCount; i++) { - var item = items[i]; - item.texture = null; - freeItems.Enqueue(item); - } - items.Clear(); - } - - private void FlushVertexArray (GraphicsDevice device, int vertexCount, int triangleCount) { - if (vertexCount == 0) return; - device.DrawUserIndexedPrimitives( - PrimitiveType.TriangleList, - vertexArray, 0, vertexCount, - triangles, 0, triangleCount / 3, - VertexPositionColorTextureColor.VertexDeclaration); - } - } - - public class MeshItem { - public Texture2D texture = null; - public Texture2D[] textureLayers = null; - public int vertexCount, triangleCount; - public VertexPositionColorTextureColor[] vertices = { }; - public int[] triangles = { }; - } +namespace Spine3_8_95 +{ + public struct VertexPositionColorTextureColor : IVertexType + { + public Vector3 Position; + public Color Color; + public Vector2 TextureCoordinate; + public Color Color2; + + public readonly static VertexDeclaration VertexDeclaration = new VertexDeclaration + ( + new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0), + new VertexElement(12, VertexElementFormat.Color, VertexElementUsage.Color, 0), + new VertexElement(16, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0), + new VertexElement(24, VertexElementFormat.Color, VertexElementUsage.Color, 1) + ); + + VertexDeclaration IVertexType.VertexDeclaration + { + get { return VertexDeclaration; } + } + } + + // #region License + // /* + // Microsoft Public License (Ms-PL) + // MonoGame - Copyright � 2009 The MonoGame Team + // + // All rights reserved. + // + // This license governs use of the accompanying software. If you use the software, you accept this license. If you do not + // accept the license, do not use the software. + // + // 1. Definitions + // The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under + // U.S. copyright law. + // + // A "contribution" is the original software, or any additions or changes to the software. + // A "contributor" is any person that distributes its contribution under this license. + // "Licensed patents" are a contributor's patent claims that read directly on its contribution. + // + // 2. Grant of Rights + // (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, + // each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. + // (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, + // each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. + // + // 3. Conditions and Limitations + // (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. + // (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, + // your patent license from such contributor to the software ends automatically. + // (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution + // notices that are present in the software. + // (D) If you distribute any portion of the software in source code form, you may do so only under this license by including + // a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object + // code form, you may only do so under a license that complies with this license. + // (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees + // or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent + // permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular + // purpose and non-infringement. + // */ + // #endregion License + // + + /// Draws batched meshes. + public class MeshBatcher + { + private readonly List items; + private readonly Queue freeItems; + private VertexPositionColorTextureColor[] vertexArray = { }; + private short[] triangles = { }; + + public MeshBatcher() + { + items = new List(256); + freeItems = new Queue(256); + EnsureCapacity(256, 512); + } + + /// Returns a pooled MeshItem. + public MeshItem NextItem(int vertexCount, int triangleCount) + { + MeshItem item = freeItems.Count > 0 ? freeItems.Dequeue() : new MeshItem(); + if (item.vertices.Length < vertexCount) item.vertices = new VertexPositionColorTextureColor[vertexCount]; + if (item.triangles.Length < triangleCount) item.triangles = new int[triangleCount]; + item.vertexCount = vertexCount; + item.triangleCount = triangleCount; + items.Add(item); + return item; + } + + private void EnsureCapacity(int vertexCount, int triangleCount) + { + if (vertexArray.Length < vertexCount) vertexArray = new VertexPositionColorTextureColor[vertexCount]; + if (triangles.Length < triangleCount) triangles = new short[triangleCount]; + } + + public void Draw(GraphicsDevice device) + { + if (items.Count == 0) return; + + int itemCount = items.Count; + int vertexCount = 0, triangleCount = 0; + for (int i = 0; i < itemCount; i++) + { + MeshItem item = items[i]; + vertexCount += item.vertexCount; + triangleCount += item.triangleCount; + } + EnsureCapacity(vertexCount, triangleCount); + + vertexCount = 0; + triangleCount = 0; + Texture2D lastTexture = null; + for (int i = 0; i < itemCount; i++) + { + MeshItem item = items[i]; + int itemVertexCount = item.vertexCount; + + if (item.texture != lastTexture || vertexCount + itemVertexCount > short.MaxValue) + { + FlushVertexArray(device, vertexCount, triangleCount); + vertexCount = 0; + triangleCount = 0; + lastTexture = item.texture; + device.Textures[0] = lastTexture; + if (item.textureLayers != null) + { + for (int layer = 1; layer < item.textureLayers.Length; ++layer) + device.Textures[layer] = item.textureLayers[layer]; + } + } + + int[] itemTriangles = item.triangles; + int itemTriangleCount = item.triangleCount; + for (int ii = 0, t = triangleCount; ii < itemTriangleCount; ii++, t++) + triangles[t] = (short)(itemTriangles[ii] + vertexCount); + triangleCount += itemTriangleCount; + + Array.Copy(item.vertices, 0, vertexArray, vertexCount, itemVertexCount); + vertexCount += itemVertexCount; + } + FlushVertexArray(device, vertexCount, triangleCount); + } + + public void AfterLastDrawPass() + { + int itemCount = items.Count; + for (int i = 0; i < itemCount; i++) + { + var item = items[i]; + item.texture = null; + freeItems.Enqueue(item); + } + items.Clear(); + } + + private void FlushVertexArray(GraphicsDevice device, int vertexCount, int triangleCount) + { + if (vertexCount == 0) return; + device.DrawUserIndexedPrimitives( + PrimitiveType.TriangleList, + vertexArray, 0, vertexCount, + triangles, 0, triangleCount / 3, + VertexPositionColorTextureColor.VertexDeclaration); + } + } + + public class MeshItem + { + public Texture2D texture = null; + public Texture2D[] textureLayers = null; + public int vertexCount, triangleCount; + public VertexPositionColorTextureColor[] vertices = { }; + public int[] triangles = { }; + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/XnaLoader/ShapeRenderer.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/XnaLoader/ShapeRenderer.cs index 8555d3c..79f6487 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/XnaLoader/ShapeRenderer.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/XnaLoader/ShapeRenderer.cs @@ -27,140 +27,157 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Spine3_8_95 { - /// - /// Batch drawing of lines and shapes that can be derived from lines. - /// - /// Call drawing methods in between Begin()/End() - /// - public class ShapeRenderer { - GraphicsDevice device; - List vertices = new List(); - Color color = Color.White; - BasicEffect effect; - public BasicEffect Effect { get { return effect; } set { effect = value; } } - - public ShapeRenderer(GraphicsDevice device) { - this.device = device; - this.effect = new BasicEffect(device); - effect.World = Matrix.Identity; - effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); - effect.TextureEnabled = false; - effect.VertexColorEnabled = true; - } - - public void SetColor(Color color) { - this.color = color; - } - - public void Begin() { - device.RasterizerState = new RasterizerState(); - device.BlendState = BlendState.AlphaBlend; - } - - public void Line(float x1, float y1, float x2, float y2) { - vertices.Add(new VertexPositionColor(new Vector3(x1, y1, 0), color)); - vertices.Add(new VertexPositionColor(new Vector3(x2, y2, 0), color)); - } - - /** Calls {@link #circle(float, float, float, int)} by estimating the number of segments needed for a smooth circle. */ - public void Circle(float x, float y, float radius) { - Circle(x, y, radius, Math.Max(1, (int)(6 * (float)Math.Pow(radius, 1.0f / 3.0f)))); - } - - /** Draws a circle using {@link ShapeType#Line} or {@link ShapeType#Filled}. */ - public void Circle(float x, float y, float radius, int segments) { - if (segments <= 0) throw new ArgumentException("segments must be > 0."); - float angle = 2 * MathUtils.PI / segments; - float cos = MathUtils.Cos(angle); - float sin = MathUtils.Sin(angle); - float cx = radius, cy = 0; - float temp = 0; - - for (int i = 0; i < segments; i++) { - vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); - temp = cx; - cx = cos * cx - sin * cy; - cy = sin * temp + cos * cy; - vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); - } - vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); - - temp = cx; - cx = radius; - cy = 0; - vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); - } - - public void Triangle(float x1, float y1, float x2, float y2, float x3, float y3) { - vertices.Add(new VertexPositionColor(new Vector3(x1, y1, 0), color)); - vertices.Add(new VertexPositionColor(new Vector3(x2, y2, 0), color)); - - vertices.Add(new VertexPositionColor(new Vector3(x2, y2, 0), color)); - vertices.Add(new VertexPositionColor(new Vector3(x3, y3, 0), color)); - - vertices.Add(new VertexPositionColor(new Vector3(x3, y3, 0), color)); - vertices.Add(new VertexPositionColor(new Vector3(x1, y1, 0), color)); - } - - public void X(float x, float y, float len) { - Line(x + len, y + len, x - len, y - len); - Line(x - len, y + len, x + len, y - len); - } - - public void Polygon(float[] polygonVertices, int offset, int count) { - if (count< 3) throw new ArgumentException("Polygon must contain at least 3 vertices"); - - offset <<= 1; - count <<= 1; - - var firstX = polygonVertices[offset]; - var firstY = polygonVertices[offset + 1]; - var last = offset + count; - - for (int i = offset, n = offset + count - 2; i= last) { - x2 = firstX; - y2 = firstY; - } else { - x2 = polygonVertices[i + 2]; - y2 = polygonVertices[i + 3]; - } - - Line(x1, y1, x2, y2); - } - } - - public void Rect(float x, float y, float width, float height) { - Line(x, y, x + width, y); - Line(x + width, y, x + width, y + height); - Line(x + width, y + height, x, y + height); - Line(x, y + height, x, y); - } - - public void End() { - if (vertices.Count == 0) return; - var verticesArray = vertices.ToArray(); - - foreach (EffectPass pass in effect.CurrentTechnique.Passes) { - pass.Apply(); - device.DrawUserPrimitives(PrimitiveType.LineList, verticesArray, 0, verticesArray.Length / 2); - } - - vertices.Clear(); - } - } +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Spine3_8_95 +{ + /// + /// Batch drawing of lines and shapes that can be derived from lines. + /// + /// Call drawing methods in between Begin()/End() + /// + public class ShapeRenderer + { + GraphicsDevice device; + List vertices = new List(); + Color color = Color.White; + BasicEffect effect; + public BasicEffect Effect { get { return effect; } set { effect = value; } } + + public ShapeRenderer(GraphicsDevice device) + { + this.device = device; + this.effect = new BasicEffect(device); + effect.World = Matrix.Identity; + effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); + effect.TextureEnabled = false; + effect.VertexColorEnabled = true; + } + + public void SetColor(Color color) + { + this.color = color; + } + + public void Begin() + { + device.RasterizerState = new RasterizerState(); + device.BlendState = BlendState.AlphaBlend; + } + + public void Line(float x1, float y1, float x2, float y2) + { + vertices.Add(new VertexPositionColor(new Vector3(x1, y1, 0), color)); + vertices.Add(new VertexPositionColor(new Vector3(x2, y2, 0), color)); + } + + /** Calls {@link #circle(float, float, float, int)} by estimating the number of segments needed for a smooth circle. */ + public void Circle(float x, float y, float radius) + { + Circle(x, y, radius, Math.Max(1, (int)(6 * (float)Math.Pow(radius, 1.0f / 3.0f)))); + } + + /** Draws a circle using {@link ShapeType#Line} or {@link ShapeType#Filled}. */ + public void Circle(float x, float y, float radius, int segments) + { + if (segments <= 0) throw new ArgumentException("segments must be > 0."); + float angle = 2 * MathUtils.PI / segments; + float cos = MathUtils.Cos(angle); + float sin = MathUtils.Sin(angle); + float cx = radius, cy = 0; + float temp = 0; + + for (int i = 0; i < segments; i++) + { + vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); + temp = cx; + cx = cos * cx - sin * cy; + cy = sin * temp + cos * cy; + vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); + } + vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); + + temp = cx; + cx = radius; + cy = 0; + vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); + } + + public void Triangle(float x1, float y1, float x2, float y2, float x3, float y3) + { + vertices.Add(new VertexPositionColor(new Vector3(x1, y1, 0), color)); + vertices.Add(new VertexPositionColor(new Vector3(x2, y2, 0), color)); + + vertices.Add(new VertexPositionColor(new Vector3(x2, y2, 0), color)); + vertices.Add(new VertexPositionColor(new Vector3(x3, y3, 0), color)); + + vertices.Add(new VertexPositionColor(new Vector3(x3, y3, 0), color)); + vertices.Add(new VertexPositionColor(new Vector3(x1, y1, 0), color)); + } + + public void X(float x, float y, float len) + { + Line(x + len, y + len, x - len, y - len); + Line(x - len, y + len, x + len, y - len); + } + + public void Polygon(float[] polygonVertices, int offset, int count) + { + if (count < 3) throw new ArgumentException("Polygon must contain at least 3 vertices"); + + offset <<= 1; + count <<= 1; + + var firstX = polygonVertices[offset]; + var firstY = polygonVertices[offset + 1]; + var last = offset + count; + + for (int i = offset, n = offset + count - 2; i < n; i += 2) + { + var x1 = polygonVertices[i]; + var y1 = polygonVertices[i + 1]; + + var x2 = 0f; + var y2 = 0f; + + if (i + 2 >= last) + { + x2 = firstX; + y2 = firstY; + } + else + { + x2 = polygonVertices[i + 2]; + y2 = polygonVertices[i + 3]; + } + + Line(x1, y1, x2, y2); + } + } + + public void Rect(float x, float y, float width, float height) + { + Line(x, y, x + width, y); + Line(x + width, y, x + width, y + height); + Line(x + width, y + height, x, y + height); + Line(x, y + height, x, y); + } + + public void End() + { + if (vertices.Count == 0) return; + var verticesArray = vertices.ToArray(); + + foreach (EffectPass pass in effect.CurrentTechnique.Passes) + { + pass.Apply(); + device.DrawUserPrimitives(PrimitiveType.LineList, verticesArray, 0, verticesArray.Length / 2); + } + + vertices.Clear(); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/XnaLoader/SkeletonRenderer.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/XnaLoader/SkeletonRenderer.cs index 9162b93..cae8c8c 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/XnaLoader/SkeletonRenderer.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/XnaLoader/SkeletonRenderer.cs @@ -27,129 +27,139 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; -using System.Collections.Generic; -using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; -namespace Spine3_8_95 { - /// Draws region and mesh attachments. - public class SkeletonRenderer { - private const int TL = 0; - private const int TR = 1; - private const int BL = 2; - private const int BR = 3; +namespace Spine3_8_95 +{ + /// Draws region and mesh attachments. + public class SkeletonRenderer + { + private const int TL = 0; + private const int TR = 1; + private const int BL = 2; + private const int BR = 3; - SkeletonClipping clipper = new SkeletonClipping(); - GraphicsDevice device; - MeshBatcher batcher; - public MeshBatcher Batcher { get { return batcher; } } - RasterizerState rasterizerState; - float[] vertices = new float[8]; - int[] quadTriangles = { 0, 1, 2, 2, 3, 0 }; - BlendState defaultBlendState; + SkeletonClipping clipper = new SkeletonClipping(); + GraphicsDevice device; + MeshBatcher batcher; + public MeshBatcher Batcher { get { return batcher; } } + RasterizerState rasterizerState; + float[] vertices = new float[8]; + int[] quadTriangles = { 0, 1, 2, 2, 3, 0 }; + BlendState defaultBlendState; - Effect effect; - public Effect Effect { get { return effect; } set { effect = value; } } - public IVertexEffect VertexEffect { get; set; } + Effect effect; + public Effect Effect { get { return effect; } set { effect = value; } } + public IVertexEffect VertexEffect { get; set; } - private bool premultipliedAlpha; - public bool PremultipliedAlpha { get { return premultipliedAlpha; } set { premultipliedAlpha = value; } } + private bool premultipliedAlpha; + public bool PremultipliedAlpha { get { return premultipliedAlpha; } set { premultipliedAlpha = value; } } - /// Attachments are rendered back to front in the x/y plane by the SkeletonRenderer. - /// Each attachment is offset by a customizable z-spacing value on the z-axis to avoid z-fighting - /// in shaders with ZWrite enabled. Typical values lie in the range [-0.1, 0]. - private float zSpacing = 0.0f; - public float ZSpacing { get { return zSpacing; } set { zSpacing = value; } } + /// Attachments are rendered back to front in the x/y plane by the SkeletonRenderer. + /// Each attachment is offset by a customizable z-spacing value on the z-axis to avoid z-fighting + /// in shaders with ZWrite enabled. Typical values lie in the range [-0.1, 0]. + private float zSpacing = 0.0f; + public float ZSpacing { get { return zSpacing; } set { zSpacing = value; } } - public SkeletonRenderer (GraphicsDevice device) { - this.device = device; + public SkeletonRenderer(GraphicsDevice device) + { + this.device = device; - batcher = new MeshBatcher(); + batcher = new MeshBatcher(); - var basicEffect = new BasicEffect(device); - basicEffect.World = Matrix.Identity; - basicEffect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); - basicEffect.TextureEnabled = true; - basicEffect.VertexColorEnabled = true; - effect = basicEffect; + var basicEffect = new BasicEffect(device); + basicEffect.World = Matrix.Identity; + basicEffect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); + basicEffect.TextureEnabled = true; + basicEffect.VertexColorEnabled = true; + effect = basicEffect; - rasterizerState = new RasterizerState(); - rasterizerState.CullMode = CullMode.None; + rasterizerState = new RasterizerState(); + rasterizerState.CullMode = CullMode.None; - Bone.yDown = true; - } + Bone.yDown = true; + } - public void Begin () { - defaultBlendState = premultipliedAlpha ? BlendState.AlphaBlend : BlendState.NonPremultiplied; + public void Begin() + { + defaultBlendState = premultipliedAlpha ? BlendState.AlphaBlend : BlendState.NonPremultiplied; - device.RasterizerState = rasterizerState; - device.BlendState = defaultBlendState; - } + device.RasterizerState = rasterizerState; + device.BlendState = defaultBlendState; + } - public void End () { - foreach (EffectPass pass in effect.CurrentTechnique.Passes) { - pass.Apply(); - batcher.Draw(device); - } - batcher.AfterLastDrawPass(); - } + public void End() + { + foreach (EffectPass pass in effect.CurrentTechnique.Passes) + { + pass.Apply(); + batcher.Draw(device); + } + batcher.AfterLastDrawPass(); + } - public void Draw(Skeleton skeleton) { - var drawOrder = skeleton.DrawOrder; - var drawOrderItems = skeleton.DrawOrder.Items; - float skeletonR = skeleton.R, skeletonG = skeleton.G, skeletonB = skeleton.B, skeletonA = skeleton.A; - Color color = new Color(); + public void Draw(Skeleton skeleton) + { + var drawOrder = skeleton.DrawOrder; + var drawOrderItems = skeleton.DrawOrder.Items; + float skeletonR = skeleton.R, skeletonG = skeleton.G, skeletonB = skeleton.B, skeletonA = skeleton.A; + Color color = new Color(); - if (VertexEffect != null) VertexEffect.Begin(skeleton); + if (VertexEffect != null) VertexEffect.Begin(skeleton); - for (int i = 0, n = drawOrder.Count; i < n; i++) { - Slot slot = drawOrderItems[i]; - Attachment attachment = slot.Attachment; - float attachmentZOffset = zSpacing * i; + for (int i = 0, n = drawOrder.Count; i < n; i++) + { + Slot slot = drawOrderItems[i]; + Attachment attachment = slot.Attachment; + float attachmentZOffset = zSpacing * i; - float attachmentColorR, attachmentColorG, attachmentColorB, attachmentColorA; - object textureObject = null; - int verticesCount = 0; - float[] vertices = this.vertices; - int indicesCount = 0; - int[] indices = null; - float[] uvs = null; + float attachmentColorR, attachmentColorG, attachmentColorB, attachmentColorA; + object textureObject = null; + int verticesCount = 0; + float[] vertices = this.vertices; + int indicesCount = 0; + int[] indices = null; + float[] uvs = null; - if (attachment is RegionAttachment) { - RegionAttachment regionAttachment = (RegionAttachment)attachment; - attachmentColorR = regionAttachment.R; attachmentColorG = regionAttachment.G; attachmentColorB = regionAttachment.B; attachmentColorA = regionAttachment.A; - AtlasRegion region = (AtlasRegion)regionAttachment.RendererObject; - textureObject = region.page.rendererObject; - verticesCount = 4; - regionAttachment.ComputeWorldVertices(slot.Bone, vertices, 0, 2); - indicesCount = 6; - indices = quadTriangles; - uvs = regionAttachment.UVs; - } - else if (attachment is MeshAttachment) { - MeshAttachment mesh = (MeshAttachment)attachment; - attachmentColorR = mesh.R; attachmentColorG = mesh.G; attachmentColorB = mesh.B; attachmentColorA = mesh.A; - AtlasRegion region = (AtlasRegion)mesh.RendererObject; - textureObject = region.page.rendererObject; - int vertexCount = mesh.WorldVerticesLength; - if (vertices.Length < vertexCount) vertices = new float[vertexCount]; - verticesCount = vertexCount >> 1; - mesh.ComputeWorldVertices(slot, vertices); - indicesCount = mesh.Triangles.Length; - indices = mesh.Triangles; - uvs = mesh.UVs; - } - else if (attachment is ClippingAttachment) { - ClippingAttachment clip = (ClippingAttachment)attachment; - clipper.ClipStart(slot, clip); - continue; - } - else { - continue; - } + if (attachment is RegionAttachment) + { + RegionAttachment regionAttachment = (RegionAttachment)attachment; + attachmentColorR = regionAttachment.R; attachmentColorG = regionAttachment.G; attachmentColorB = regionAttachment.B; attachmentColorA = regionAttachment.A; + AtlasRegion region = (AtlasRegion)regionAttachment.RendererObject; + textureObject = region.page.rendererObject; + verticesCount = 4; + regionAttachment.ComputeWorldVertices(slot.Bone, vertices, 0, 2); + indicesCount = 6; + indices = quadTriangles; + uvs = regionAttachment.UVs; + } + else if (attachment is MeshAttachment) + { + MeshAttachment mesh = (MeshAttachment)attachment; + attachmentColorR = mesh.R; attachmentColorG = mesh.G; attachmentColorB = mesh.B; attachmentColorA = mesh.A; + AtlasRegion region = (AtlasRegion)mesh.RendererObject; + textureObject = region.page.rendererObject; + int vertexCount = mesh.WorldVerticesLength; + if (vertices.Length < vertexCount) vertices = new float[vertexCount]; + verticesCount = vertexCount >> 1; + mesh.ComputeWorldVertices(slot, vertices); + indicesCount = mesh.Triangles.Length; + indices = mesh.Triangles; + uvs = mesh.UVs; + } + else if (attachment is ClippingAttachment) + { + ClippingAttachment clip = (ClippingAttachment)attachment; + clipper.ClipStart(slot, clip); + continue; + } + else + { + continue; + } - // set blend state + // set blend state BlendState blendState = new BlendState(); Blend blendSrc; Blend blendDst; @@ -201,77 +211,88 @@ public void Draw(Skeleton skeleton) { break; } - if (device.BlendState != blendState) { + if (device.BlendState != blendState) + { End(); device.BlendState = blendState; } - // calculate color - float a = skeletonA * slot.A * attachmentColorA; - if (premultipliedAlpha) { - color = new Color( - skeletonR * slot.R * attachmentColorR * a, - skeletonG * slot.G * attachmentColorG * a, - skeletonB * slot.B * attachmentColorB * a, a); - } - else { - color = new Color( - skeletonR * slot.R * attachmentColorR, - skeletonG * slot.G * attachmentColorG, - skeletonB * slot.B * attachmentColorB, a); - } + // calculate color + float a = skeletonA * slot.A * attachmentColorA; + if (premultipliedAlpha) + { + color = new Color( + skeletonR * slot.R * attachmentColorR * a, + skeletonG * slot.G * attachmentColorG * a, + skeletonB * slot.B * attachmentColorB * a, a); + } + else + { + color = new Color( + skeletonR * slot.R * attachmentColorR, + skeletonG * slot.G * attachmentColorG, + skeletonB * slot.B * attachmentColorB, a); + } - Color darkColor = new Color(); - if (slot.HasSecondColor) { - if (premultipliedAlpha) { - darkColor = new Color(slot.R2 * a, slot.G2 * a, slot.B2 * a); - } else { - darkColor = new Color(slot.R2 * a, slot.G2 * a, slot.B2 * a); - } - } - darkColor.A = premultipliedAlpha ? (byte)255 : (byte)0; + Color darkColor = new Color(); + if (slot.HasSecondColor) + { + if (premultipliedAlpha) + { + darkColor = new Color(slot.R2 * a, slot.G2 * a, slot.B2 * a); + } + else + { + darkColor = new Color(slot.R2 * a, slot.G2 * a, slot.B2 * a); + } + } + darkColor.A = premultipliedAlpha ? (byte)255 : (byte)0; - // clip - if (clipper.IsClipping) { - clipper.ClipTriangles(vertices, verticesCount << 1, indices, indicesCount, uvs); - vertices = clipper.ClippedVertices.Items; - verticesCount = clipper.ClippedVertices.Count >> 1; - indices = clipper.ClippedTriangles.Items; - indicesCount = clipper.ClippedTriangles.Count; - uvs = clipper.ClippedUVs.Items; - } + // clip + if (clipper.IsClipping) + { + clipper.ClipTriangles(vertices, verticesCount << 1, indices, indicesCount, uvs); + vertices = clipper.ClippedVertices.Items; + verticesCount = clipper.ClippedVertices.Count >> 1; + indices = clipper.ClippedTriangles.Items; + indicesCount = clipper.ClippedTriangles.Count; + uvs = clipper.ClippedUVs.Items; + } - if (verticesCount == 0 || indicesCount == 0) - continue; + if (verticesCount == 0 || indicesCount == 0) + continue; - // submit to batch - MeshItem item = batcher.NextItem(verticesCount, indicesCount); - if (textureObject is Texture2D) - item.texture = (Texture2D) textureObject; - else { - item.textureLayers = (Texture2D[]) textureObject; - item.texture = item.textureLayers[0]; - } - for (int ii = 0, nn = indicesCount; ii < nn; ii++) { - item.triangles[ii] = indices[ii]; - } - VertexPositionColorTextureColor[] itemVertices = item.vertices; - for (int ii = 0, v = 0, nn = verticesCount << 1; v < nn; ii++, v += 2) { - itemVertices[ii].Color = color; - itemVertices[ii].Color2 = darkColor; - itemVertices[ii].Position.X = vertices[v]; - itemVertices[ii].Position.Y = vertices[v + 1]; - itemVertices[ii].Position.Z = attachmentZOffset; - itemVertices[ii].TextureCoordinate.X = uvs[v]; - itemVertices[ii].TextureCoordinate.Y = uvs[v + 1]; - if (VertexEffect != null) VertexEffect.Transform(ref itemVertices[ii]); - } + // submit to batch + MeshItem item = batcher.NextItem(verticesCount, indicesCount); + if (textureObject is Texture2D) + item.texture = (Texture2D)textureObject; + else + { + item.textureLayers = (Texture2D[])textureObject; + item.texture = item.textureLayers[0]; + } + for (int ii = 0, nn = indicesCount; ii < nn; ii++) + { + item.triangles[ii] = indices[ii]; + } + VertexPositionColorTextureColor[] itemVertices = item.vertices; + for (int ii = 0, v = 0, nn = verticesCount << 1; v < nn; ii++, v += 2) + { + itemVertices[ii].Color = color; + itemVertices[ii].Color2 = darkColor; + itemVertices[ii].Position.X = vertices[v]; + itemVertices[ii].Position.Y = vertices[v + 1]; + itemVertices[ii].Position.Z = attachmentZOffset; + itemVertices[ii].TextureCoordinate.X = uvs[v]; + itemVertices[ii].TextureCoordinate.Y = uvs[v + 1]; + if (VertexEffect != null) VertexEffect.Transform(ref itemVertices[ii]); + } - clipper.ClipEnd(slot); - } - clipper.ClipEnd(); - if (VertexEffect != null) VertexEffect.End(); - } - } + clipper.ClipEnd(slot); + } + clipper.ClipEnd(); + if (VertexEffect != null) VertexEffect.End(); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/XnaLoader/VertexEffect.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/XnaLoader/VertexEffect.cs index da1c724..efde4ec 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/XnaLoader/VertexEffect.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/XnaLoader/VertexEffect.cs @@ -28,70 +28,80 @@ *****************************************************************************/ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -namespace Spine3_8_95 { - public interface IVertexEffect { - void Begin(Skeleton skeleton); - void Transform(ref VertexPositionColorTextureColor vertex); - void End(); - } +namespace Spine3_8_95 +{ + public interface IVertexEffect + { + void Begin(Skeleton skeleton); + void Transform(ref VertexPositionColorTextureColor vertex); + void End(); + } - public class JitterEffect : IVertexEffect { - public float JitterX { get; set; } - public float JitterY { get; set; } + public class JitterEffect : IVertexEffect + { + public float JitterX { get; set; } + public float JitterY { get; set; } - public JitterEffect(float jitterX, float jitterY) { - JitterX = jitterX; - JitterY = jitterY; - } + public JitterEffect(float jitterX, float jitterY) + { + JitterX = jitterX; + JitterY = jitterY; + } - public void Begin(Skeleton skeleton) { - } + public void Begin(Skeleton skeleton) + { + } - public void End() { - } + public void End() + { + } - public void Transform(ref VertexPositionColorTextureColor vertex) { - vertex.Position.X += MathUtils.RandomTriangle(-JitterX, JitterY); - vertex.Position.Y += MathUtils.RandomTriangle(-JitterX, JitterY); - } - } + public void Transform(ref VertexPositionColorTextureColor vertex) + { + vertex.Position.X += MathUtils.RandomTriangle(-JitterX, JitterY); + vertex.Position.Y += MathUtils.RandomTriangle(-JitterX, JitterY); + } + } - public class SwirlEffect : IVertexEffect { - private float worldX, worldY, angle; + public class SwirlEffect : IVertexEffect + { + private float worldX, worldY, angle; - public float Radius { get; set; } - public float Angle { get { return angle; } set { angle = value * MathUtils.DegRad; } } - public float CenterX { get; set; } - public float CenterY { get; set; } - public IInterpolation Interpolation { get; set; } + public float Radius { get; set; } + public float Angle { get { return angle; } set { angle = value * MathUtils.DegRad; } } + public float CenterX { get; set; } + public float CenterY { get; set; } + public IInterpolation Interpolation { get; set; } - public SwirlEffect(float radius) { - Radius = radius; - Interpolation = IInterpolation.Pow2; - } + public SwirlEffect(float radius) + { + Radius = radius; + Interpolation = IInterpolation.Pow2; + } - public void Begin(Skeleton skeleton) { - worldX = skeleton.X + CenterX; - worldY = skeleton.Y + CenterY; - } + public void Begin(Skeleton skeleton) + { + worldX = skeleton.X + CenterX; + worldY = skeleton.Y + CenterY; + } - public void End() { - } + public void End() + { + } - public void Transform(ref VertexPositionColorTextureColor vertex) { - float x = vertex.Position.X - worldX; - float y = vertex.Position.Y - worldY; - float dist = (float)Math.Sqrt(x * x + y * y); - if (dist < Radius) { - float theta = Interpolation.Apply(0, angle, (Radius - dist) / Radius); - float cos = MathUtils.Cos(theta), sin = MathUtils.Sin(theta); - vertex.Position.X = cos * x - sin * y + worldX; - vertex.Position.Y = sin * x + cos * y + worldY; - } - } - } + public void Transform(ref VertexPositionColorTextureColor vertex) + { + float x = vertex.Position.X - worldX; + float y = vertex.Position.Y - worldY; + float dist = (float)Math.Sqrt(x * x + y * y); + if (dist < Radius) + { + float theta = Interpolation.Apply(0, angle, (Radius - dist) / Radius); + float cos = MathUtils.Cos(theta), sin = MathUtils.Sin(theta); + vertex.Position.X = cos * x - sin * y + worldX; + vertex.Position.Y = sin * x + cos * y + worldY; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/XnaLoader/XnaTextureLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/XnaLoader/XnaTextureLoader.cs index 3ac9059..cf1abd5 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/XnaLoader/XnaTextureLoader.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-3.8.95/XnaLoader/XnaTextureLoader.cs @@ -28,8 +28,6 @@ *****************************************************************************/ using System; -using System.IO; -using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; namespace Spine3_8_95 diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Animation.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Animation.cs index 6186902..a185756 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Animation.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Animation.cs @@ -30,2561 +30,2949 @@ using System; using System.Collections.Generic; -namespace Spine4_0_31 { - - /// - /// Stores a list of timelines to animate a skeleton's pose over time. - public class Animation { - internal String name; - internal ExposedList timelines; - internal HashSet timelineIds; - internal float duration; - - public Animation (string name, ExposedList timelines, float duration) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - - this.name = name; - SetTimelines(timelines); - this.duration = duration; - } - - public ExposedList Timelines { - get { return timelines; } - set { SetTimelines(value); } - } - - public void SetTimelines (ExposedList timelines) { - if (timelines == null) throw new ArgumentNullException("timelines", "timelines cannot be null."); - this.timelines = timelines; - // Note: avoiding reallocations by adding all hash set entries at - // once (EnsureCapacity() is only available in newer .Net versions). - int idCount = 0; - int timelinesCount = timelines.Count; - Timeline[] timelinesItems = timelines.Items; - for (int t = 0; t < timelinesCount; ++t) - idCount += timelinesItems[t].PropertyIds.Length; - string[] propertyIds = new string[idCount]; - int currentId = 0; - for (int t = 0; t < timelinesCount; ++t) { - var ids = timelinesItems[t].PropertyIds; - for (int i = 0, idsLength = ids.Length; i < idsLength; ++i) - propertyIds[currentId++] = ids[i]; - } - this.timelineIds = new HashSet(propertyIds); - } - - /// The duration of the animation in seconds, which is usually the highest time of all frames in the timeline. The duration is - /// used to know when it has completed and when it should loop back to the start. - public float Duration { get { return duration; } set { duration = value; } } - - /// The animation's name, which is unique across all animations in the skeleton. - public string Name { get { return name; } } - - /// Returns true if this animation contains a timeline with any of the specified property IDs. - public bool HasTimeline (string[] propertyIds) { - foreach (string id in propertyIds) - if (timelineIds.Contains(id)) return true; - return false; - } - - /// Applies the animation's timelines to the specified skeleton. - /// - /// The skeleton the animation is being applied to. This provides access to the bones, slots, and other skeleton - /// components the timelines may change. - /// The last time in seconds this animation was applied. Some timelines trigger only at specific times rather - /// than every frame. Pass -1 the first time an animation is applied to ensure frame 0 is triggered. - /// The time in seconds the skeleton is being posed for. Most timelines find the frame before and the frame after - /// this time and interpolate between the frame values. If beyond the and loop is - /// true then the animation will repeat, else the last frame will be applied. - /// If true, the animation repeats after the . - /// If any events are fired, they are added to this list. Can be null to ignore fired events or if no timelines - /// fire events. - /// 0 applies the current or setup values (depending on blend). 1 applies the timeline values. Between - /// 0 and 1 applies values between the current or setup values and the timeline values. 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 (layering). - /// Controls how mixing is applied when alpha < 1. - /// Indicates whether the timelines are mixing in or out. Used by timelines which perform instant transitions, - /// such as or . - public void Apply (Skeleton skeleton, float lastTime, float time, bool loop, ExposedList events, float alpha, - MixBlend blend, MixDirection direction) { - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - - if (loop && duration != 0) { - time %= duration; - if (lastTime > 0) lastTime %= duration; - } - - var timelines = this.timelines.Items; - for (int i = 0, n = this.timelines.Count; i < n; i++) - timelines[i].Apply(skeleton, lastTime, time, events, alpha, blend, direction); - } - - override public string ToString () { - return name; - } - } - - /// - /// Controls how timeline values are mixed with setup pose values or current pose values when a timeline is applied with - /// alpha < 1. - /// - public enum MixBlend { - /// Transitions from the setup value to the timeline value (the current value is not used). Before the first frame, the - /// setup value is set. - Setup, - - /// - /// - /// Transitions from the current value to the timeline value. Before the first frame, transitions from the current value to - /// the setup value. Timelines which perform instant transitions, such as or - /// , use the setup value before the first frame. - /// - /// First is intended for the first animations applied, not for animations layered on top of those. - /// - First, - - /// - /// - /// Transitions from the current value to the timeline value. No change is made before the first frame (the current value is - /// kept until the first frame). - /// - /// Replace is intended for animations layered on top of others, not for the first animations applied. - /// - Replace, - - /// - /// - /// Transitions from the current value to the current value plus the timeline value. No change is made before the first frame - /// (the current value is kept until the first frame). - /// - /// Add is intended for animations layered on top of others, not for the first animations applied. Properties - /// set by additive animations must be set manually or by another animation before applying the additive animations, else the - /// property values will increase each time the additive animations are applied. - /// - /// - Add - } - - /// - /// Indicates whether a timeline's alpha is mixing out over time toward 0 (the setup or current pose value) or - /// mixing in toward 1 (the timeline's value). Some timelines use this to decide how values are applied. - /// - public enum MixDirection { - In, - Out - } - - internal enum Property { - Rotate = 0, X, Y, ScaleX, ScaleY, ShearX, ShearY, // - RGB, Alpha, RGB2, // - Attachment, Deform, // - Event, DrawOrder, // - IkConstraint, TransformConstraint, // - PathConstraintPosition, PathConstraintSpacing, PathConstraintMix - } - - /// - /// The base class for all timelines. - public abstract class Timeline { - private readonly string[] propertyIds; - internal readonly float[] frames; - - /// Unique identifiers for the properties the timeline modifies. - public Timeline (int frameCount, params string[] propertyIds) { - if (propertyIds == null) throw new System.ArgumentNullException("propertyIds", "propertyIds cannot be null."); - this.propertyIds = propertyIds; - frames = new float[frameCount * FrameEntries]; - } - - /// Uniquely encodes both the type of this timeline and the skeleton properties that it affects. - public string[] PropertyIds { - get { return propertyIds; } - } - - /// The time in seconds and any other values for each frame. - public float[] Frames { - get { return frames; } - } - - /// The number of entries stored per frame. - public virtual int FrameEntries { - get { return 1; } - } - - /// The number of frames for this timeline. - public int FrameCount { - get { return frames.Length / FrameEntries; } - } - - public float Duration { - get { - return frames[frames.Length - FrameEntries]; - } - } - - /// Applies this timeline to the skeleton. - /// The skeleton the timeline is being applied to. This provides access to the bones, slots, and other - /// skeleton components the timeline may change. - /// The time this timeline was last applied. Timelines such as trigger only - /// at specific times rather than every frame. In that case, the timeline triggers everything between - /// lastTime (exclusive) and time (inclusive). Pass -1 the first time an animation is - /// applied to ensure frame 0 is triggered. - /// The time in seconds that the skeleton is being posed for. Most timelines find the frame before and the frame - /// after this time and interpolate between the frame values.If beyond the last frame, the last frame will be - /// applied. - /// If any events are fired, they are added to this list. Can be null to ignore fired events or if the timeline - /// does not fire events. - /// 0 applies the current or setup value (depending on blend). 1 applies the timeline value. - /// Between 0 and 1 applies a value between the current or setup value 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 (layering). - /// Controls how mixing is applied when alpha < 1. - /// Indicates whether the timeline is mixing in or out. Used by timelines which perform instant transitions, - /// such as or , and other such as . - public abstract void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, - MixBlend blend, MixDirection direction); - - /// Search using a stride of 1. - /// Must be >= the first value in frames. - /// The index of the first value <= time. - internal static int Search (float[] frames, float time) { - int n = frames.Length; - for (int i = 1; i < n; i++) - if (frames[i] > time) return i - 1; - return n - 1; - } - - /// Search using the specified stride. - /// Must be >= the first value in frames. - /// The index of the first value <= time. - internal static int Search (float[] frames, float time, int step) { - int n = frames.Length; - for (int i = step; i < n; i += step) - if (frames[i] > time) return i - step; - return n - step; - } - } - - /// An interface for timelines which change the property of a bone. - public interface IBoneTimeline { - /// The index of the bone in that will be changed when this timeline is applied. - int BoneIndex { get; } - } - - /// An interface for timelines which change the property of a slot. - public interface ISlotTimeline { - /// The index of the slot in that will be changed when this timeline is applied. - int SlotIndex { get; } - } - - /// The base class for timelines that interpolate between frame values using stepped, linear, or a Bezier curve. - public abstract class CurveTimeline : Timeline { - public const int LINEAR = 0, STEPPED = 1, BEZIER = 2, BEZIER_SIZE = 18; - - internal float[] curves; - /// The number of key frames for this timeline. - - /// The maximum number of Bezier curves. See . - /// Unique identifiers for the properties the timeline modifies. - public CurveTimeline (int frameCount, int bezierCount, params string[] propertyIds) - : base(frameCount, propertyIds) { - curves = new float[frameCount + bezierCount * BEZIER_SIZE]; - curves[frameCount - 1] = STEPPED; - } - - /// Sets the specified frame to linear interpolation. - /// Between 0 and frameCount - 1, inclusive. - public void SetLinear (int frame) { - curves[frame] = LINEAR; - } - - /// Sets the specified frame to stepped interpolation. - /// Between 0 and frameCount - 1, inclusive. - public void SetStepped (int frame) { - curves[frame] = STEPPED; - } - - /// Returns the interpolation type for the specified frame. - /// Between 0 and frameCount - 1, inclusive. - /// , or + the index of the Bezier segments. - public float GetCurveType (int frame) { - return (int)curves[frame]; - } - - /// Shrinks the storage for Bezier curves, for use when bezierCount (specified in the constructor) was larger - /// than the actual number of Bezier curves. - public void Shrink (int bezierCount) { - int size = FrameCount + bezierCount * BEZIER_SIZE; - if (curves.Length > size) { - float[] newCurves = new float[size]; - Array.Copy(curves, 0, newCurves, 0, size); - curves = newCurves; - } - } - - /// - /// Stores the segments for the specified Bezier curve. For timelines that modify multiple values, there may be more than - /// one curve per frame. - /// The ordinal of this Bezier curve for this timeline, between 0 and bezierCount - 1 (specified - /// in the constructor), inclusive. - /// Between 0 and frameCount - 1, inclusive. - /// The index of the value for the frame this curve is used for. - /// The time for the first key. - /// The value for the first key. - /// The time for the first Bezier handle. - /// The value for the first Bezier handle. - /// The time of the second Bezier handle. - /// The value for the second Bezier handle. - /// The time for the second key. - /// The value for the second key. - public void SetBezier (int bezier, int frame, int value, float time1, float value1, float cx1, float cy1, float cx2, - float cy2, float time2, float value2) { - - float[] curves = this.curves; - int i = FrameCount + bezier * BEZIER_SIZE; - if (value == 0) curves[frame] = BEZIER + i; - float tmpx = (time1 - cx1 * 2 + cx2) * 0.03f, tmpy = (value1 - cy1 * 2 + cy2) * 0.03f; - float dddx = ((cx1 - cx2) * 3 - time1 + time2) * 0.006f, dddy = ((cy1 - cy2) * 3 - value1 + value2) * 0.006f; - float ddx = tmpx * 2 + dddx, ddy = tmpy * 2 + dddy; - float dx = (cx1 - time1) * 0.3f + tmpx + dddx * 0.16666667f, dy = (cy1 - value1) * 0.3f + tmpy + dddy * 0.16666667f; - float x = time1 + dx, y = value1 + dy; - for (int 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; - } - } - - /// - /// Returns the Bezier interpolated value for the specified time. - /// The index into for the values of the frame before time. - /// The offset from frameIndex to the value this curve is used for. - /// The index of the Bezier segments. See . - public float GetBezierValue (float time, int frameIndex, int valueOffset, int i) { - float[] curves = this.curves; - if (curves[i] > time) { - float x = frames[frameIndex], y = frames[frameIndex + valueOffset]; - return y + (time - x) / (curves[i] - x) * (curves[i + 1] - y); - } - int 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 += FrameEntries; - { // scope added to prevent compile error "float x and y declared in enclosing scope" - float x = curves[n - 2], y = curves[n - 1]; - return y + (time - x) / (frames[frameIndex] - x) * (frames[frameIndex + valueOffset] - y); - } - } - } - - /// The base class for a that sets one property. - public abstract class CurveTimeline1 : CurveTimeline { - public const int ENTRIES = 2; - internal const int VALUE = 1; - - /// The maximum number of Bezier curves. See . - /// Unique identifiers for the properties the timeline modifies. - public CurveTimeline1 (int frameCount, int bezierCount, string propertyId) - : base(frameCount, bezierCount, propertyId) { - } - - public override int FrameEntries { - get { return ENTRIES; } - } - - /// Sets the time and value for the specified frame. - /// Between 0 and frameCount, inclusive. - /// The frame time in seconds - public void SetFrame (int frame, float time, float value) { - frame <<= 1; - frames[frame] = time; - frames[frame + VALUE] = value; - } - - /// Returns the interpolated value for the specified time. - public float GetCurveValue (float time) { - float[] frames = this.frames; - int i = frames.Length - 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 LINEAR: - float before = frames[i], value = frames[i + VALUE]; - return value + (time - before) / (frames[i + ENTRIES] - before) * (frames[i + ENTRIES + VALUE] - value); - case STEPPED: - return frames[i + VALUE]; - } - return GetBezierValue(time, i, VALUE, curveType - BEZIER); - } - } - - /// The base class for a which sets two properties. - public abstract class CurveTimeline2 : CurveTimeline { - public const int ENTRIES = 3; - internal const int VALUE1 = 1, VALUE2 = 2; - - /// The maximum number of Bezier curves. See . - /// Unique identifiers for the properties the timeline modifies. - public CurveTimeline2 (int frameCount, int bezierCount, string propertyId1, string propertyId2) - : base(frameCount, bezierCount, propertyId1, propertyId2) { - } - - public override int FrameEntries { - get { return ENTRIES; } - } - - /// Sets the time and values for the specified frame. - /// Between 0 and frameCount, inclusive. - /// The frame time in seconds. - public void SetFrame (int frame, float time, float value1, float value2) { - frame *= ENTRIES; - frames[frame] = time; - frames[frame + VALUE1] = value1; - frames[frame + VALUE2] = value2; - } - } - - /// Changes a bone's local . - public class RotateTimeline : CurveTimeline1, IBoneTimeline { - readonly int boneIndex; - - public RotateTimeline (int frameCount, int bezierCount, int boneIndex) - : base(frameCount, bezierCount, (int)Property.Rotate + "|" + boneIndex) { - this.boneIndex = boneIndex; - } - - public int BoneIndex { - get { - return boneIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - if (!bone.active) return; - - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case MixBlend.Setup: - bone.rotation = bone.data.rotation; - return; - case MixBlend.First: - bone.rotation += (bone.data.rotation - bone.rotation) * alpha; - return; - } - return; - } - - float r = GetCurveValue(time); - switch (blend) { - case MixBlend.Setup: - bone.rotation = bone.data.rotation + r * alpha; - break; - case MixBlend.First: - case MixBlend.Replace: - r += bone.data.rotation - bone.rotation; - goto case MixBlend.Add; // Fall through. - case MixBlend.Add: - bone.rotation += r * alpha; - break; - } - } - } - - /// Changes a bone's local and . - public class TranslateTimeline : CurveTimeline2, IBoneTimeline { - readonly int boneIndex; - - public TranslateTimeline (int frameCount, int bezierCount, int boneIndex) - : base(frameCount, bezierCount, // - (int)Property.X + "|" + boneIndex, // - (int)Property.Y + "|" + boneIndex) { - this.boneIndex = boneIndex; - } - - public int BoneIndex { - get { - return boneIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - if (!bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - 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; - return; - } - return; - } - - float x, y; - int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; - switch (curveType) { - case LINEAR: - float before = frames[i]; - x = frames[i + VALUE1]; - y = frames[i + VALUE2]; - float t = (time - before) / (frames[i + ENTRIES] - before); - x += (frames[i + ENTRIES + VALUE1] - x) * t; - y += (frames[i + ENTRIES + VALUE2] - y) * t; - break; - case STEPPED: - x = frames[i + VALUE1]; - y = frames[i + VALUE2]; - break; - default: - x = GetBezierValue(time, i, VALUE1, curveType - BEZIER); - y = GetBezierValue(time, i, VALUE2, curveType + BEZIER_SIZE - BEZIER); - break; - } - - 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; - break; - } - } - } - - /// Changes a bone's local . - public class TranslateXTimeline : CurveTimeline1, IBoneTimeline { - readonly int boneIndex; - - public TranslateXTimeline (int frameCount, int bezierCount, int boneIndex) - : base(frameCount, bezierCount, (int)Property.X + "|" + boneIndex) { - this.boneIndex = boneIndex; - } - - public int BoneIndex { - get { - return boneIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - if (!bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case MixBlend.Setup: - bone.x = bone.data.x; - return; - case MixBlend.First: - bone.x += (bone.data.x - bone.x) * alpha; - return; - } - return; - } - - float x = GetCurveValue(time); - switch (blend) { - case MixBlend.Setup: - bone.x = bone.data.x + x * alpha; - break; - case MixBlend.First: - case MixBlend.Replace: - bone.x += (bone.data.x + x - bone.x) * alpha; - break; - case MixBlend.Add: - bone.x += x * alpha; - break; - } - } - } - - /// Changes a bone's local . - public class TranslateYTimeline : CurveTimeline1, IBoneTimeline { - readonly int boneIndex; - - public TranslateYTimeline (int frameCount, int bezierCount, int boneIndex) - : base(frameCount, bezierCount, (int)Property.Y + "|" + boneIndex) { - this.boneIndex = boneIndex; - } - - public int BoneIndex { - get { - return boneIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - if (!bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case MixBlend.Setup: - bone.y = bone.data.y; - return; - case MixBlend.First: - bone.y += (bone.data.y - bone.y) * alpha; - return; - } - return; - } - - float y = GetCurveValue(time); - switch (blend) { - case MixBlend.Setup: - bone.y = bone.data.y + y * alpha; - break; - case MixBlend.First: - case MixBlend.Replace: - bone.y += (bone.data.y + y - bone.y) * alpha; - break; - case MixBlend.Add: - bone.y += y * alpha; - break; - } - } - } - - /// Changes a bone's local and . - public class ScaleTimeline : CurveTimeline2, IBoneTimeline { - readonly int boneIndex; - - public ScaleTimeline (int frameCount, int bezierCount, int boneIndex) - : base(frameCount, bezierCount, // - (int)Property.ScaleX + "|" + boneIndex, // - (int)Property.ScaleY + "|" + boneIndex) { - this.boneIndex = boneIndex; - } - - public int BoneIndex { - get { - return boneIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - if (!bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - 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; - return; - } - return; - } - - float x, y; - int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; - switch (curveType) { - case LINEAR: - float before = frames[i]; - x = frames[i + VALUE1]; - y = frames[i + VALUE2]; - float t = (time - before) / (frames[i + ENTRIES] - before); - x += (frames[i + ENTRIES + VALUE1] - x) * t; - y += (frames[i + ENTRIES + VALUE2] - y) * t; - break; - case STEPPED: - x = frames[i + VALUE1]; - y = frames[i + VALUE2]; - break; - default: - x = GetBezierValue(time, i, VALUE1, curveType - BEZIER); - y = GetBezierValue(time, i, VALUE2, curveType + BEZIER_SIZE - BEZIER); - break; - } - 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 { - // Mixing out uses sign of setup or current pose, else use sign of key. - float bx, by; - if (direction == MixDirection.Out) { - switch (blend) { - case MixBlend.Setup: - bx = bone.data.scaleX; - by = bone.data.scaleY; - bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha; - bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha; - break; - case MixBlend.First: - case MixBlend.Replace: - bx = bone.scaleX; - by = bone.scaleY; - bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha; - bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha; - break; - case MixBlend.Add: - bx = bone.scaleX; - by = bone.scaleY; - bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bone.data.scaleX) * alpha; - bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - bone.data.scaleY) * alpha; - break; - } - } else { - switch (blend) { - case MixBlend.Setup: - bx = Math.Abs(bone.data.scaleX) * Math.Sign(x); - by = Math.Abs(bone.data.scaleY) * Math.Sign(y); - bone.scaleX = bx + (x - bx) * alpha; - bone.scaleY = by + (y - by) * alpha; - break; - case MixBlend.First: - case MixBlend.Replace: - bx = Math.Abs(bone.scaleX) * Math.Sign(x); - by = Math.Abs(bone.scaleY) * Math.Sign(y); - bone.scaleX = bx + (x - bx) * alpha; - bone.scaleY = by + (y - by) * alpha; - break; - case MixBlend.Add: - bx = Math.Sign(x); - by = Math.Sign(y); - bone.scaleX = Math.Abs(bone.scaleX) * bx + (x - Math.Abs(bone.data.scaleX) * bx) * alpha; - bone.scaleY = Math.Abs(bone.scaleY) * by + (y - Math.Abs(bone.data.scaleY) * by) * alpha; - break; - } - } - } - } - } - - /// Changes a bone's local . - public class ScaleXTimeline : CurveTimeline1, IBoneTimeline { - readonly int boneIndex; - - public ScaleXTimeline (int frameCount, int bezierCount, int boneIndex) - : base(frameCount, bezierCount, (int)Property.ScaleX + "|" + boneIndex) { - this.boneIndex = boneIndex; - } - - public int BoneIndex { - get { - return boneIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - if (!bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case MixBlend.Setup: - bone.scaleX = bone.data.scaleX; - return; - case MixBlend.First: - bone.scaleX += (bone.data.scaleX - bone.scaleX) * alpha; - return; - } - return; - } - - float x = GetCurveValue(time) * bone.data.scaleX; - if (alpha == 1) { - if (blend == MixBlend.Add) - bone.scaleX += x - bone.data.scaleX; - else - bone.scaleX = x; - } else { - // Mixing out uses sign of setup or current pose, else use sign of key. - float bx; - if (direction == MixDirection.Out) { - switch (blend) { - case MixBlend.Setup: - bx = bone.data.scaleX; - bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha; - break; - case MixBlend.First: - case MixBlend.Replace: - bx = bone.scaleX; - bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha; - break; - case MixBlend.Add: - bx = bone.scaleX; - bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bone.data.scaleX) * alpha; - break; - } - } else { - switch (blend) { - case MixBlend.Setup: - bx = Math.Abs(bone.data.scaleX) * Math.Sign(x); - bone.scaleX = bx + (x - bx) * alpha; - break; - case MixBlend.First: - case MixBlend.Replace: - bx = Math.Abs(bone.scaleX) * Math.Sign(x); - bone.scaleX = bx + (x - bx) * alpha; - break; - case MixBlend.Add: - bx = Math.Sign(x); - bone.scaleX = Math.Abs(bone.scaleX) * bx + (x - Math.Abs(bone.data.scaleX) * bx) * alpha; - break; - } - } - } - } - } - - /// Changes a bone's local . - public class ScaleYTimeline : CurveTimeline1, IBoneTimeline { - readonly int boneIndex; - - public ScaleYTimeline (int frameCount, int bezierCount, int boneIndex) - : base(frameCount, bezierCount, (int)Property.ScaleY + "|" + boneIndex) { - this.boneIndex = boneIndex; - } - - public int BoneIndex { - get { - return boneIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - if (!bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case MixBlend.Setup: - bone.scaleY = bone.data.scaleY; - return; - case MixBlend.First: - bone.scaleY += (bone.data.scaleY - bone.scaleY) * alpha; - return; - } - return; - } - - float y = GetCurveValue(time) * bone.data.scaleY; - if (alpha == 1) { - if (blend == MixBlend.Add) - bone.scaleY += y - bone.data.scaleY; - else - bone.scaleY = y; - } else { - // Mixing out uses sign of setup or current pose, else use sign of key. - float by; - if (direction == MixDirection.Out) { - switch (blend) { - case MixBlend.Setup: - by = bone.data.scaleY; - bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha; - break; - case MixBlend.First: - case MixBlend.Replace: - by = bone.scaleY; - bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha; - break; - case MixBlend.Add: - by = bone.scaleY; - bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - bone.data.scaleY) * alpha; - break; - } - } else { - switch (blend) { - case MixBlend.Setup: - by = Math.Abs(bone.data.scaleY) * Math.Sign(y); - bone.scaleY = by + (y - by) * alpha; - break; - case MixBlend.First: - case MixBlend.Replace: - by = Math.Abs(bone.scaleY) * Math.Sign(y); - bone.scaleY = by + (y - by) * alpha; - break; - case MixBlend.Add: - by = Math.Sign(y); - bone.scaleY = Math.Abs(bone.scaleY) * by + (y - Math.Abs(bone.data.scaleY) * by) * alpha; - break; - } - } - } - } - } - - /// Changes a bone's local and . - public class ShearTimeline : CurveTimeline2, IBoneTimeline { - readonly int boneIndex; - - public ShearTimeline (int frameCount, int bezierCount, int boneIndex) - : base(frameCount, bezierCount, // - (int)Property.ShearX + "|" + boneIndex, // - (int)Property.ShearY + "|" + boneIndex) { - this.boneIndex = boneIndex; - } - - public int BoneIndex { - get { - return boneIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - if (!bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - 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; - return; - } - return; - } - - float x, y; - int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; - switch (curveType) { - case LINEAR: - float before = frames[i]; - x = frames[i + VALUE1]; - y = frames[i + VALUE2]; - float t = (time - before) / (frames[i + ENTRIES] - before); - x += (frames[i + ENTRIES + VALUE1] - x) * t; - y += (frames[i + ENTRIES + VALUE2] - y) * t; - break; - case STEPPED: - x = frames[i + VALUE1]; - y = frames[i + VALUE2]; - break; - default: - x = GetBezierValue(time, i, VALUE1, curveType - BEZIER); - y = GetBezierValue(time, i, VALUE2, curveType + BEZIER_SIZE - BEZIER); - break; - } - - 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; - break; - } - } - } - - /// Changes a bone's local . - public class ShearXTimeline : CurveTimeline1, IBoneTimeline { - readonly int boneIndex; - - public ShearXTimeline (int frameCount, int bezierCount, int boneIndex) - : base(frameCount, bezierCount, (int)Property.ShearX + "|" + boneIndex) { - this.boneIndex = boneIndex; - } - - public int BoneIndex { - get { - return boneIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - if (!bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case MixBlend.Setup: - bone.shearX = bone.data.shearX; - return; - case MixBlend.First: - bone.shearX += (bone.data.shearX - bone.shearX) * alpha; - return; - } - return; - } - - float x = GetCurveValue(time); - switch (blend) { - case MixBlend.Setup: - bone.shearX = bone.data.shearX + x * alpha; - break; - case MixBlend.First: - case MixBlend.Replace: - bone.shearX += (bone.data.shearX + x - bone.shearX) * alpha; - break; - case MixBlend.Add: - bone.shearX += x * alpha; - break; - } - } - } - - /// Changes a bone's local . - public class ShearYTimeline : CurveTimeline1, IBoneTimeline { - readonly int boneIndex; - - public ShearYTimeline (int frameCount, int bezierCount, int boneIndex) - : base(frameCount, bezierCount, (int)Property.ShearY + "|" + boneIndex) { - this.boneIndex = boneIndex; - } - - public int BoneIndex { - get { - return boneIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - if (!bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case MixBlend.Setup: - bone.shearY = bone.data.shearY; - return; - case MixBlend.First: - bone.shearY += (bone.data.shearY - bone.shearY) * alpha; - return; - } - return; - } - - float y = GetCurveValue(time); - switch (blend) { - case MixBlend.Setup: - bone.shearY = bone.data.shearY + y * alpha; - break; - case MixBlend.First: - case MixBlend.Replace: - bone.shearY += (bone.data.shearY + y - bone.shearY) * alpha; - break; - case MixBlend.Add: - bone.shearY += y * alpha; - break; - } - } - } - - /// Changes a slot's . - public class RGBATimeline : CurveTimeline, ISlotTimeline { - public const int ENTRIES = 5; - protected const int R = 1, G = 2, B = 3, A = 4; - - readonly int slotIndex; - - public RGBATimeline (int frameCount, int bezierCount, int slotIndex) - : base(frameCount, bezierCount, // - (int)Property.RGB + "|" + slotIndex, // - (int)Property.Alpha + "|" + slotIndex) { - this.slotIndex = slotIndex; - } - public override int FrameEntries { - get { return ENTRIES; } - } - - public int SlotIndex { - get { - return slotIndex; - } - } - - /// Sets the time and color for the specified frame. - /// Between 0 and frameCount, inclusive. - /// The frame time in seconds. - public void 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; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Slot slot = skeleton.slots.Items[slotIndex]; - if (!slot.bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - var setup = slot.data; - switch (blend) { - case MixBlend.Setup: - slot.r = setup.r; - slot.g = setup.g; - slot.b = setup.b; - slot.a = setup.a; - return; - case MixBlend.First: - slot.r += (setup.r - slot.r) * alpha; - slot.g += (setup.g - slot.g) * alpha; - slot.b += (setup.b - slot.b) * alpha; - slot.a += (setup.a - slot.a) * alpha; - slot.ClampColor(); - return; - } - return; - } - - float r, g, b, a; - int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; - switch (curveType) { - case LINEAR: - float before = frames[i]; - r = frames[i + R]; - g = frames[i + G]; - b = frames[i + B]; - a = frames[i + A]; - float t = (time - before) / (frames[i + ENTRIES] - before); - r += (frames[i + ENTRIES + R] - r) * t; - g += (frames[i + ENTRIES + G] - g) * t; - b += (frames[i + ENTRIES + B] - b) * t; - a += (frames[i + ENTRIES + A] - a) * t; - break; - case STEPPED: - r = frames[i + R]; - g = frames[i + G]; - b = frames[i + B]; - a = frames[i + A]; - break; - default: - r = GetBezierValue(time, i, R, curveType - BEZIER); - g = GetBezierValue(time, i, G, curveType + BEZIER_SIZE - BEZIER); - b = GetBezierValue(time, i, B, curveType + BEZIER_SIZE * 2 - BEZIER); - a = GetBezierValue(time, i, A, curveType + BEZIER_SIZE * 3 - BEZIER); - break; - } - - if (alpha == 1) { - slot.r = r; - slot.g = g; - slot.b = b; - slot.a = a; - } else { - float br, bg, bb, ba; - if (blend == MixBlend.Setup) { - br = slot.data.r; - bg = slot.data.g; - bb = slot.data.b; - ba = slot.data.a; - } else { - br = slot.r; - bg = slot.g; - bb = slot.b; - ba = slot.a; - } - slot.r = br + (r - br) * alpha; - slot.g = bg + (g - bg) * alpha; - slot.b = bb + (b - bb) * alpha; - slot.a = ba + (a - ba) * alpha; - } - slot.ClampColor(); - } - } - - /// Changes the RGB for a slot's . - public class RGBTimeline : CurveTimeline, ISlotTimeline { - public const int ENTRIES = 4; - protected const int R = 1, G = 2, B = 3; - - readonly int slotIndex; - - public RGBTimeline (int frameCount, int bezierCount, int slotIndex) - : base(frameCount, bezierCount, // - (int)Property.RGB + "|" + slotIndex) { - this.slotIndex = slotIndex; - } - - public override int FrameEntries { - get { return ENTRIES; } - } - - public int SlotIndex { - get { - return slotIndex; - } - } - - /// Sets the time and color for the specified frame. - /// Between 0 and frameCount, inclusive. - /// The frame time in seconds. - public void SetFrame (int frame, float time, float r, float g, float b) { - frame <<= 2; - frames[frame] = time; - frames[frame + R] = r; - frames[frame + G] = g; - frames[frame + B] = b; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Slot slot = skeleton.slots.Items[slotIndex]; - if (!slot.bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - var setup = slot.data; - switch (blend) { - case MixBlend.Setup: - slot.r = setup.r; - slot.g = setup.g; - slot.b = setup.b; - return; - case MixBlend.First: - slot.r += (setup.r - slot.r) * alpha; - slot.g += (setup.g - slot.g) * alpha; - slot.b += (setup.b - slot.b) * alpha; - slot.ClampColor(); - return; - } - return; - } - - float r, g, b; - int i = Search(frames, time, ENTRIES), curveType = (int)curves[i >> 2]; - switch (curveType) { - case LINEAR: - float before = frames[i]; - r = frames[i + R]; - g = frames[i + G]; - b = frames[i + B]; - float t = (time - before) / (frames[i + ENTRIES] - before); - r += (frames[i + ENTRIES + R] - r) * t; - g += (frames[i + ENTRIES + G] - g) * t; - b += (frames[i + ENTRIES + B] - b) * t; - break; - case STEPPED: - r = frames[i + R]; - g = frames[i + G]; - b = frames[i + B]; - break; - default: - r = GetBezierValue(time, i, R, curveType - BEZIER); - g = GetBezierValue(time, i, G, curveType + BEZIER_SIZE - BEZIER); - b = GetBezierValue(time, i, B, curveType + BEZIER_SIZE * 2 - BEZIER); - break; - } - - if (alpha == 1) { - slot.r = r; - slot.g = g; - slot.b = b; - } else { - float br, bg, bb; - if (blend == MixBlend.Setup) { - var setup = slot.data; - br = setup.r; - bg = setup.g; - bb = setup.b; - } else { - br = slot.r; - bg = slot.g; - bb = slot.b; - } - slot.r = br + (r - br) * alpha; - slot.g = bg + (g - bg) * alpha; - slot.b = bb + (b - bb) * alpha; - } - slot.ClampColor(); - } - } - - /// Changes the alpha for a slot's . - public class AlphaTimeline : CurveTimeline1, ISlotTimeline { - readonly int slotIndex; - - public AlphaTimeline (int frameCount, int bezierCount, int slotIndex) - : base(frameCount, bezierCount, (int)Property.Alpha + "|" + slotIndex) { - this.slotIndex = slotIndex; - } - - public int SlotIndex { - get { - return slotIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Slot slot = skeleton.slots.Items[slotIndex]; - if (!slot.bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - var setup = slot.data; - switch (blend) { - case MixBlend.Setup: - slot.a = setup.a; - return; - case MixBlend.First: - slot.a += (setup.a - slot.a) * alpha; - slot.ClampColor(); - return; - } - return; - } - - float a = GetCurveValue(time); - if (alpha == 1) - slot.a = a; - else { - if (blend == MixBlend.Setup) slot.a = slot.data.a; - slot.a += (a - slot.a) * alpha; - } - slot.ClampColor(); - } - } - - /// Changes a slot's and for two color tinting. - public class RGBA2Timeline : CurveTimeline, ISlotTimeline { - public const int ENTRIES = 8; - protected const int R = 1, G = 2, B = 3, A = 4, R2 = 5, G2 = 6, B2 = 7; - - readonly int slotIndex; - - public RGBA2Timeline (int frameCount, int bezierCount, int slotIndex) - : base(frameCount, bezierCount, // - (int)Property.RGB + "|" + slotIndex, // - (int)Property.Alpha + "|" + slotIndex, // - (int)Property.RGB2 + "|" + slotIndex) { - this.slotIndex = slotIndex; - } - - public override int FrameEntries { - get { - return ENTRIES; - } - } - - /// - /// The index of the slot in that will be changed when this timeline is applied. The - /// must have a dark color available. - public int SlotIndex { - get { - return slotIndex; - } - } - - /// Sets the time, light color, and dark color for the specified frame. - /// Between 0 and frameCount, inclusive. - /// The frame time in seconds. - public void SetFrame (int frame, float time, float r, float g, float b, float a, float r2, float g2, float b2) { - frame <<= 3; - 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; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Slot slot = skeleton.slots.Items[slotIndex]; - if (!slot.bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - SlotData setup = slot.data; - switch (blend) { - case MixBlend.Setup: - slot.r = setup.r; - slot.g = setup.g; - slot.b = setup.b; - slot.a = setup.a; - slot.ClampColor(); - slot.r2 = setup.r2; - slot.g2 = setup.g2; - slot.b2 = setup.b2; - slot.ClampSecondColor(); - return; - case MixBlend.First: - slot.r += (slot.r - setup.r) * alpha; - slot.g += (slot.g - setup.g) * alpha; - slot.b += (slot.b - setup.b) * alpha; - slot.a += (slot.a - setup.a) * alpha; - slot.ClampColor(); - slot.r2 += (slot.r2 - setup.r2) * alpha; - slot.g2 += (slot.g2 - setup.g2) * alpha; - slot.b2 += (slot.b2 - setup.b2) * alpha; - slot.ClampSecondColor(); - return; - } - return; - } - - float r, g, b, a, r2, g2, b2; - int i = Search(frames, time, ENTRIES), curveType = (int)curves[i >> 3]; - switch (curveType) { - case LINEAR: - float before = frames[i]; - r = frames[i + R]; - g = frames[i + G]; - b = frames[i + B]; - a = frames[i + A]; - r2 = frames[i + R2]; - g2 = frames[i + G2]; - b2 = frames[i + B2]; - float t = (time - before) / (frames[i + ENTRIES] - before); - r += (frames[i + ENTRIES + R] - r) * t; - g += (frames[i + ENTRIES + G] - g) * t; - b += (frames[i + ENTRIES + B] - b) * t; - a += (frames[i + ENTRIES + A] - a) * t; - r2 += (frames[i + ENTRIES + R2] - r2) * t; - g2 += (frames[i + ENTRIES + G2] - g2) * t; - b2 += (frames[i + ENTRIES + B2] - b2) * t; - break; - case STEPPED: - r = frames[i + R]; - g = frames[i + G]; - b = frames[i + B]; - a = frames[i + A]; - r2 = frames[i + R2]; - g2 = frames[i + G2]; - b2 = frames[i + B2]; - break; - default: - r = GetBezierValue(time, i, R, curveType - BEZIER); - g = GetBezierValue(time, i, G, curveType + BEZIER_SIZE - BEZIER); - b = GetBezierValue(time, i, B, curveType + BEZIER_SIZE * 2 - BEZIER); - a = GetBezierValue(time, i, A, curveType + BEZIER_SIZE * 3 - BEZIER); - r2 = GetBezierValue(time, i, R2, curveType + BEZIER_SIZE * 4 - BEZIER); - g2 = GetBezierValue(time, i, G2, curveType + BEZIER_SIZE * 5 - BEZIER); - b2 = GetBezierValue(time, i, B2, curveType + BEZIER_SIZE * 6 - BEZIER); - break; - } - - if (alpha == 1) { - slot.r = r; - slot.g = g; - slot.b = b; - slot.a = a; - slot.r2 = r2; - slot.g2 = g2; - slot.b2 = b2; - } else { - float br, bg, bb, ba, br2, bg2, bb2; - if (blend == MixBlend.Setup) { - br = slot.data.r; - bg = slot.data.g; - bb = slot.data.b; - ba = slot.data.a; - br2 = slot.data.r2; - bg2 = slot.data.g2; - bb2 = slot.data.b2; - } else { - br = slot.r; - bg = slot.g; - bb = slot.b; - ba = slot.a; - br2 = slot.r2; - bg2 = slot.g2; - bb2 = slot.b2; - } - slot.r = br + (r - br) * alpha; - slot.g = bg + (g - bg) * alpha; - slot.b = bb + (b - bb) * alpha; - slot.a = ba + (a - ba) * alpha; - slot.r2 = br2 + (r2 - br2) * alpha; - slot.g2 = bg2 + (g2 - bg2) * alpha; - slot.b2 = bb2 + (b2 - bb2) * alpha; - } - slot.ClampColor(); - slot.ClampSecondColor(); - } - } - - /// Changes the RGB for a slot's and for two color tinting. - public class RGB2Timeline : CurveTimeline, ISlotTimeline { - public const int ENTRIES = 7; - protected const int R = 1, G = 2, B = 3, R2 = 4, G2 = 5, B2 = 6; - - readonly int slotIndex; - - public RGB2Timeline (int frameCount, int bezierCount, int slotIndex) - : base(frameCount, bezierCount, // - (int)Property.RGB + "|" + slotIndex, // - (int)Property.RGB2 + "|" + slotIndex) { - this.slotIndex = slotIndex; - } - - public override int FrameEntries { - get { - return ENTRIES; - } - } - - /// - /// The index of the slot in that will be changed when this timeline is applied. The - /// must have a dark color available. - public int SlotIndex { - get { - return slotIndex; - } - } - - /// Sets the time, light color, and dark color for the specified frame. - /// Between 0 and frameCount, inclusive. - /// The frame time in seconds. - public void 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; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Slot slot = skeleton.slots.Items[slotIndex]; - if (!slot.bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - SlotData setup = slot.data; - switch (blend) { - case MixBlend.Setup: - slot.r = setup.r; - slot.g = setup.g; - slot.b = setup.b; - slot.ClampColor(); - slot.r2 = setup.r2; - slot.g2 = setup.g2; - slot.b2 = setup.b2; - slot.ClampSecondColor(); - return; - case MixBlend.First: - slot.r += (slot.r - setup.r) * alpha; - slot.g += (slot.g - setup.g) * alpha; - slot.b += (slot.b - setup.b) * alpha; - slot.ClampColor(); - slot.r2 += (slot.r2 - setup.r2) * alpha; - slot.g2 += (slot.g2 - setup.g2) * alpha; - slot.b2 += (slot.b2 - setup.b2) * alpha; - slot.ClampSecondColor(); - return; - } - return; - } - - float r, g, b, r2, g2, b2; - int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; - switch (curveType) { - case LINEAR: - float before = frames[i]; - r = frames[i + R]; - g = frames[i + G]; - b = frames[i + B]; - r2 = frames[i + R2]; - g2 = frames[i + G2]; - b2 = frames[i + B2]; - float t = (time - before) / (frames[i + ENTRIES] - before); - r += (frames[i + ENTRIES + R] - r) * t; - g += (frames[i + ENTRIES + G] - g) * t; - b += (frames[i + ENTRIES + B] - b) * t; - r2 += (frames[i + ENTRIES + R2] - r2) * t; - g2 += (frames[i + ENTRIES + G2] - g2) * t; - b2 += (frames[i + ENTRIES + B2] - b2) * t; - break; - case STEPPED: - r = frames[i + R]; - g = frames[i + G]; - b = frames[i + B]; - r2 = frames[i + R2]; - g2 = frames[i + G2]; - b2 = frames[i + B2]; - break; - default: - r = GetBezierValue(time, i, R, curveType - BEZIER); - g = GetBezierValue(time, i, G, curveType + BEZIER_SIZE - BEZIER); - b = GetBezierValue(time, i, B, curveType + BEZIER_SIZE * 2 - BEZIER); - r2 = GetBezierValue(time, i, R2, curveType + BEZIER_SIZE * 3 - BEZIER); - g2 = GetBezierValue(time, i, G2, curveType + BEZIER_SIZE * 4 - BEZIER); - b2 = GetBezierValue(time, i, B2, curveType + BEZIER_SIZE * 5 - BEZIER); - break; - } - - if (alpha == 1) { - slot.r = r; - slot.g = g; - slot.b = b; - slot.r2 = r2; - slot.g2 = g2; - slot.b2 = b2; - } else { - float br, bg, bb, br2, bg2, bb2; - if (blend == MixBlend.Setup) { - SlotData setup = slot.data; - br = setup.r; - bg = setup.g; - bb = setup.b; - br2 = setup.r2; - bg2 = setup.g2; - bb2 = setup.b2; - } else { - br = slot.r; - bg = slot.g; - bb = slot.b; - br2 = slot.r2; - bg2 = slot.g2; - bb2 = slot.b2; - } - slot.r = br + (r - br) * alpha; - slot.g = bg + (g - bg) * alpha; - slot.b = bb + (b - bb) * alpha; - slot.r2 = br2 + (r2 - br2) * alpha; - slot.g2 = bg2 + (g2 - bg2) * alpha; - slot.b2 = bb2 + (b2 - bb2) * alpha; - } - slot.ClampColor(); - slot.ClampSecondColor(); - } - } - - /// Changes a slot's . - public class AttachmentTimeline : Timeline, ISlotTimeline { - readonly int slotIndex; - readonly string[] attachmentNames; - - public AttachmentTimeline (int frameCount, int slotIndex) - : base(frameCount, (int)Property.Attachment + "|" + slotIndex) { - this.slotIndex = slotIndex; - attachmentNames = new String[frameCount]; - } - - public int SlotIndex { - get { - return slotIndex; - } - } - - /// The attachment name for each frame. May contain null values to clear the attachment. - public string[] AttachmentNames { - get { - return attachmentNames; - } - } - - /// Sets the time and attachment name for the specified frame. - /// Between 0 and frameCount, inclusive. - /// The frame time in seconds. - public void SetFrame (int frame, float time, String attachmentName) { - frames[frame] = time; - attachmentNames[frame] = attachmentName; - } - - public override void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Slot slot = skeleton.slots.Items[slotIndex]; - if (!slot.bone.active) return; - - if (direction == MixDirection.Out) { - if (blend == MixBlend.Setup) SetAttachment(skeleton, slot, slot.data.attachmentName); - return; - } - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - if (blend == MixBlend.Setup || blend == MixBlend.First) SetAttachment(skeleton, slot, slot.data.attachmentName); - return; - } - - SetAttachment(skeleton, slot, attachmentNames[Search(frames, time)]); - } - - private void SetAttachment (Skeleton skeleton, Slot slot, string attachmentName) { - slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); - } - } - - /// Changes a slot's to deform a . - public class DeformTimeline : CurveTimeline, ISlotTimeline { - readonly int slotIndex; - readonly VertexAttachment attachment; - internal float[][] vertices; - - public DeformTimeline (int frameCount, int bezierCount, int slotIndex, VertexAttachment attachment) - : base(frameCount, bezierCount, (int)Property.Deform + "|" + slotIndex + "|" + attachment.Id) { - this.slotIndex = slotIndex; - this.attachment = attachment; - vertices = new float[frameCount][]; - } - - public int SlotIndex { - get { - return slotIndex; - } - } - /// The attachment that will be deformed. - /// - public VertexAttachment Attachment { - get { - return attachment; - } - } - - /// The vertices for each frame. - public float[][] Vertices { - get { - return vertices; - } - } - - /// Sets the time and vertices for the specified frame. - /// Between 0 and frameCount, inclusive. - /// The frame time in seconds. - /// Vertex positions for an unweighted VertexAttachment, or deform offsets if it has weights. - public void SetFrame (int frame, float time, float[] vertices) { - frames[frame] = time; - this.vertices[frame] = vertices; - } - - /// Ignored (0 is used for a deform timeline). - /// Ignored (1 is used for a deform timeline). - public void setBezier (int bezier, int frame, int value, float time1, float value1, float cx1, float cy1, float cx2, - float cy2, float time2, float value2) { - float[] curves = this.curves; - int i = FrameCount + bezier * BEZIER_SIZE; - if (value == 0) curves[frame] = BEZIER + i; - float tmpx = (time1 - cx1 * 2 + cx2) * 0.03f, tmpy = cy2 * 0.03f - cy1 * 0.06f; - float dddx = ((cx1 - cx2) * 3 - time1 + time2) * 0.006f, dddy = (cy1 - cy2 + 0.33333333f) * 0.018f; - float ddx = tmpx * 2 + dddx, ddy = tmpy * 2 + dddy; - float dx = (cx1 - time1) * 0.3f + tmpx + dddx * 0.16666667f, dy = cy1 * 0.3f + tmpy + dddy * 0.16666667f; - float x = time1 + dx, y = dy; - for (int 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; - } - } - - /// Returns the interpolated percentage for the specified time. - /// The frame before time. - private float GetCurvePercent (float time, int frame) { - float[] curves = this.curves; - int i = (int)curves[frame]; - switch (i) { - case LINEAR: - float x = frames[frame]; - return (time - x) / (frames[frame + FrameEntries] - x); - case STEPPED: - return 0; - } - i -= BEZIER; - if (curves[i] > time) { - float x = frames[frame]; - return curves[i + 1] * (time - x) / (curves[i] - x); - } - int 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); - } - } - { // scope added to prevent compile error "float x and y declared in enclosing scope" - float x = curves[n - 2], y = curves[n - 1]; - return y + (1 - y) * (time - x) / (frames[frame + FrameEntries] - x); - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - - Slot slot = skeleton.slots.Items[slotIndex]; - if (!slot.bone.active) return; - var vertexAttachment = slot.attachment as VertexAttachment; - if (vertexAttachment == null || vertexAttachment.DeformAttachment != attachment) return; - - var deformArray = slot.Deform; - if (deformArray.Count == 0) blend = MixBlend.Setup; - - float[][] vertices = this.vertices; - int vertexCount = vertices[0].Length; - - float[] deform; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case MixBlend.Setup: - deformArray.Clear(); - return; - case MixBlend.First: - if (alpha == 1) { - deformArray.Clear(); - return; - } - - // Ensure size and preemptively set count. - if (deformArray.Capacity < vertexCount) deformArray.Capacity = vertexCount; - deformArray.Count = vertexCount; - deform = deformArray.Items; - - if (vertexAttachment.bones == null) { - // Unweighted vertex positions. - float[] setupVertices = vertexAttachment.vertices; - for (int i = 0; i < vertexCount; i++) - deform[i] += (setupVertices[i] - deform[i]) * alpha; - } else { - // Weighted deform offsets. - alpha = 1 - alpha; - for (int i = 0; i < vertexCount; i++) - deform[i] *= alpha; - } - return; - } - return; - } - - // Ensure size and preemptively set count. - if (deformArray.Capacity < vertexCount) deformArray.Capacity = vertexCount; - deformArray.Count = vertexCount; - deform = deformArray.Items; - - if (time >= frames[frames.Length - 1]) { // Time is after last frame. - float[] lastVertices = vertices[frames.Length - 1]; - if (alpha == 1) { - if (blend == MixBlend.Add) { - if (vertexAttachment.bones == null) { - // Unweighted vertex positions, no alpha. - float[] setupVertices = vertexAttachment.vertices; - for (int i = 0; i < vertexCount; i++) - deform[i] += lastVertices[i] - setupVertices[i]; - } else { - // Weighted deform offsets, no alpha. - for (int i = 0; i < vertexCount; i++) - deform[i] += lastVertices[i]; - } - } else { - // Vertex positions or deform offsets, no alpha. - Array.Copy(lastVertices, 0, deform, 0, vertexCount); - } - } else { - switch (blend) { - case MixBlend.Setup: { - if (vertexAttachment.bones == null) { - // Unweighted vertex positions, with alpha. - float[] setupVertices = vertexAttachment.vertices; - for (int i = 0; i < vertexCount; i++) { - float setup = setupVertices[i]; - deform[i] = setup + (lastVertices[i] - setup) * alpha; - } - } else { - // Weighted deform offsets, with alpha. - for (int 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 (int i = 0; i < vertexCount; i++) - deform[i] += (lastVertices[i] - deform[i]) * alpha; - break; - case MixBlend.Add: - if (vertexAttachment.bones == null) { - // Unweighted vertex positions, no alpha. - float[] setupVertices = vertexAttachment.vertices; - for (int i = 0; i < vertexCount; i++) - deform[i] += (lastVertices[i] - setupVertices[i]) * alpha; - } else { - // Weighted deform offsets, alpha. - for (int i = 0; i < vertexCount; i++) - deform[i] += lastVertices[i] * alpha; - } - break; - } - } - return; - } - - int frame = Search(frames, time); - float percent = GetCurvePercent(time, frame); - float[] prevVertices = vertices[frame]; - float[] nextVertices = vertices[frame + 1]; - - if (alpha == 1) { - if (blend == MixBlend.Add) { - if (vertexAttachment.bones == null) { - // Unweighted vertex positions, no alpha. - float[] setupVertices = vertexAttachment.vertices; - for (int 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 (int 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 (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - deform[i] = prev + (nextVertices[i] - prev) * percent; - } - } - } else { - switch (blend) { - case MixBlend.Setup: { - if (vertexAttachment.bones == null) { - // Unweighted vertex positions, with alpha. - float[] setupVertices = vertexAttachment.vertices; - for (int 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 (int 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 (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - deform[i] += (prev + (nextVertices[i] - prev) * percent - deform[i]) * alpha; - } - break; - } - case MixBlend.Add: - if (vertexAttachment.bones == null) { - // Unweighted vertex positions, with alpha. - float[] setupVertices = vertexAttachment.vertices; - for (int 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 (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - deform[i] += (prev + (nextVertices[i] - prev) * percent) * alpha; - } - } - break; - } - } - } - } - - /// Fires an when specific animation times are reached. - public class EventTimeline : Timeline { - readonly static string[] propertyIds = { ((int)Property.Event).ToString() }; - readonly Event[] events; - - public EventTimeline (int frameCount) - : base(frameCount, propertyIds) { - events = new Event[frameCount]; - } - - /// The event for each frame. - public Event[] Events { - get { - return events; - } - } - - /// Sets the time and event for the specified frame. - /// Between 0 and frameCount, inclusive. - public void SetFrame (int frame, Event e) { - frames[frame] = e.time; - events[frame] = e; - } - - /// Fires events for frames > lastTime and <= time. - public override void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, - MixBlend blend, MixDirection direction) { - - if (firedEvents == null) return; - - float[] frames = this.frames; - int frameCount = frames.Length; - - if (lastTime > time) { // Fire events after last time for looped animations. - Apply(skeleton, lastTime, int.MaxValue, firedEvents, alpha, blend, direction); - lastTime = -1f; - } else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame. - return; - if (time < frames[0]) return; // Time is before first frame. - - int i; - if (lastTime < frames[0]) - i = 0; - else { - i = Search(frames, lastTime) + 1; - float frameTime = frames[i]; - while (i > 0) { // Fire multiple events with the same frame. - if (frames[i - 1] != frameTime) break; - i--; - } - } - for (; i < frameCount && time >= frames[i]; i++) - firedEvents.Add(events[i]); - } - } - - /// Changes a skeleton's . - public class DrawOrderTimeline : Timeline { - static readonly string[] propertyIds = { ((int)Property.DrawOrder).ToString() }; - - readonly int[][] drawOrders; - - public DrawOrderTimeline (int frameCount) - : base(frameCount, propertyIds) { - drawOrders = new int[frameCount][]; - } - - /// The draw order for each frame. - /// . - public int[][] DrawOrders { - get { - return drawOrders; - } - } - - /// Sets the time and draw order for the specified frame. - /// Between 0 and frameCount, inclusive. - /// The frame time in seconds. - /// For each slot in , the index of the slot in the new draw order. May be null to use - /// setup pose draw order. - public void SetFrame (int frame, float time, int[] drawOrder) { - frames[frame] = time; - drawOrders[frame] = drawOrder; - } - - public override void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - - if (direction == MixDirection.Out) { - if (blend == MixBlend.Setup) Array.Copy(skeleton.slots.Items, 0, skeleton.drawOrder.Items, 0, skeleton.slots.Count); - return; - } - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - if (blend == MixBlend.Setup || blend == MixBlend.First) Array.Copy(skeleton.slots.Items, 0, skeleton.drawOrder.Items, 0, skeleton.slots.Count); - return; - } - - int[] drawOrderToSetupIndex = drawOrders[Search(frames, time)]; - if (drawOrderToSetupIndex == null) - Array.Copy(skeleton.slots.Items, 0, skeleton.drawOrder.Items, 0, skeleton.slots.Count); - else { - Slot[] slots = skeleton.slots.Items; - Slot[] drawOrder = skeleton.drawOrder.Items; - for (int i = 0, n = drawOrderToSetupIndex.Length; i < n; i++) - drawOrder[i] = slots[drawOrderToSetupIndex[i]]; - } - } - } - - /// Changes an IK constraint's , , - /// , , and . - public class IkConstraintTimeline : CurveTimeline { - public const int ENTRIES = 6; - private const int MIX = 1, SOFTNESS = 2, BEND_DIRECTION = 3, COMPRESS = 4, STRETCH = 5; - - readonly int ikConstraintIndex; - - public IkConstraintTimeline (int frameCount, int bezierCount, int ikConstraintIndex) - : base(frameCount, bezierCount, (int)Property.IkConstraint + "|" + ikConstraintIndex) { - this.ikConstraintIndex = ikConstraintIndex; - } - - public override int FrameEntries { - get { - return ENTRIES; - } - } - - /// The index of the IK constraint slot in that will be changed when this timeline is - /// applied. - public int IkConstraintIndex { - get { - return ikConstraintIndex; - } - } - - /// Sets the time, mix, softness, bend direction, compress, and stretch for the specified frame. - /// Between 0 and frameCount, inclusive. - /// The frame time in seconds. - /// 1 or -1. - public void 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] = bendDirection; - frames[frame + COMPRESS] = compress ? 1 : 0; - frames[frame + STRETCH] = stretch ? 1 : 0; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - IkConstraint constraint = skeleton.ikConstraints.Items[ikConstraintIndex]; - if (!constraint.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - 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; - } - return; - } - - float mix, softness; - int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; - switch (curveType) { - case LINEAR: - float before = frames[i]; - mix = frames[i + MIX]; - softness = frames[i + SOFTNESS]; - float t = (time - before) / (frames[i + ENTRIES] - before); - mix += (frames[i + ENTRIES + MIX] - mix) * t; - softness += (frames[i + ENTRIES + SOFTNESS] - softness) * t; - break; - case STEPPED: - mix = frames[i + MIX]; - softness = frames[i + SOFTNESS]; - break; - default: - mix = GetBezierValue(time, i, MIX, curveType - BEZIER); - softness = GetBezierValue(time, i, SOFTNESS, curveType + BEZIER_SIZE - BEZIER); - break; - } - - 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 = (int)frames[i + BEND_DIRECTION]; - constraint.compress = frames[i + COMPRESS] != 0; - constraint.stretch = frames[i + STRETCH] != 0; - } - } else { - constraint.mix += (mix - constraint.mix) * alpha; - constraint.softness += (softness - constraint.softness) * alpha; - if (direction == MixDirection.In) { - constraint.bendDirection = (int)frames[i + BEND_DIRECTION]; - constraint.compress = frames[i + COMPRESS] != 0; - constraint.stretch = frames[i + STRETCH] != 0; - } - } - } - } - - /// Changes a transform constraint's mixes. - public class TransformConstraintTimeline : CurveTimeline { - public const int ENTRIES = 7; - private const int ROTATE = 1, X = 2, Y = 3, SCALEX = 4, SCALEY = 5, SHEARY = 6; - - readonly int transformConstraintIndex; - - public TransformConstraintTimeline (int frameCount, int bezierCount, int transformConstraintIndex) - : base(frameCount, bezierCount, (int)Property.TransformConstraint + "|" + transformConstraintIndex) { - this.transformConstraintIndex = transformConstraintIndex; - } - - public override int FrameEntries { - get { - return ENTRIES; - } - } - - /// The index of the transform constraint slot in that will be changed when this - /// timeline is applied. - public int TransformConstraintIndex { - get { - return transformConstraintIndex; - } - } - - /// Sets the time, rotate mix, translate mix, scale mix, and shear mix for the specified frame. - /// Between 0 and frameCount, inclusive. - /// The frame time in seconds. - public void SetFrame (int 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; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - TransformConstraint constraint = skeleton.transformConstraints.Items[transformConstraintIndex]; - if (!constraint.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - TransformConstraintData data = constraint.data; - 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; - } - return; - } - - float rotate, x, y, scaleX, scaleY, shearY; - int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; - switch (curveType) { - case 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 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); - break; - } - - if (blend == MixBlend.Setup) { - TransformConstraintData 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; - 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; - } - } - } - - /// Changes a path constraint's . - public class PathConstraintPositionTimeline : CurveTimeline1 { - readonly int pathConstraintIndex; - - public PathConstraintPositionTimeline (int frameCount, int bezierCount, int pathConstraintIndex) - : base(frameCount, bezierCount, (int)Property.PathConstraintPosition + "|" + pathConstraintIndex) { - this.pathConstraintIndex = pathConstraintIndex; - } - - /// The index of the path constraint slot in that will be changed when this timeline - /// is applied. - public int PathConstraintIndex { - get { - return pathConstraintIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; - if (!constraint.active) return; - - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case MixBlend.Setup: - constraint.position = constraint.data.position; - return; - case MixBlend.First: - constraint.position += (constraint.data.position - constraint.position) * alpha; - return; - } - return; - } - - float position = GetCurveValue(time); - if (blend == MixBlend.Setup) - constraint.position = constraint.data.position + (position - constraint.data.position) * alpha; - else - constraint.position += (position - constraint.position) * alpha; - } - } - - /// Changes a path constraint's . - public class PathConstraintSpacingTimeline : CurveTimeline1 { - readonly int pathConstraintIndex; - - public PathConstraintSpacingTimeline (int frameCount, int bezierCount, int pathConstraintIndex) - : base(frameCount, bezierCount, (int)Property.PathConstraintSpacing + "|" + pathConstraintIndex) { - this.pathConstraintIndex = pathConstraintIndex; - } - - /// The index of the path constraint slot in that will be changed when this timeline - /// is applied. - public int PathConstraintIndex { - get { - return pathConstraintIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, MixBlend blend, - MixDirection direction) { - - PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; - if (!constraint.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case MixBlend.Setup: - constraint.spacing = constraint.data.spacing; - return; - case MixBlend.First: - constraint.spacing += (constraint.data.spacing - constraint.spacing) * alpha; - return; - } - return; - } - - float spacing = GetCurveValue(time); - if (blend == MixBlend.Setup) - constraint.spacing = constraint.data.spacing + (spacing - constraint.data.spacing) * alpha; - else - constraint.spacing += (spacing - constraint.spacing) * alpha; - } - } - - /// Changes a transform constraint's , , and - /// . - public class PathConstraintMixTimeline : CurveTimeline { - public const int ENTRIES = 4; - private const int ROTATE = 1, X = 2, Y = 3; - - readonly int pathConstraintIndex; - - public PathConstraintMixTimeline (int frameCount, int bezierCount, int pathConstraintIndex) - : base(frameCount, bezierCount, (int)Property.PathConstraintMix + "|" + pathConstraintIndex) { - this.pathConstraintIndex = pathConstraintIndex; - } - - public override int FrameEntries { - get { return ENTRIES; } - } - - /// The index of the path constraint slot in that will be changed when this timeline - /// is applied. - public int PathConstraintIndex { - get { - return pathConstraintIndex; - } - } - - /// Sets the time and color for the specified frame. - /// Between 0 and frameCount, inclusive. - /// The frame time in seconds. - public void SetFrame (int frame, float time, float mixRotate, float mixX, float mixY) { - frame <<= 2; - frames[frame] = time; - frames[frame + ROTATE] = mixRotate; - frames[frame + X] = mixX; - frames[frame + Y] = mixY; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; - if (!constraint.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - 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; - return; - } - return; - } - - float rotate, x, y; - int i = Search(frames, time, ENTRIES), 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); - break; - } - - 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; - } - } - } +namespace Spine4_0_31 +{ + + /// + /// Stores a list of timelines to animate a skeleton's pose over time. + public class Animation + { + internal String name; + internal ExposedList timelines; + internal HashSet timelineIds; + internal float duration; + + public Animation(string name, ExposedList timelines, float duration) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + + this.name = name; + SetTimelines(timelines); + this.duration = duration; + } + + public ExposedList Timelines + { + get { return timelines; } + set { SetTimelines(value); } + } + + public void SetTimelines(ExposedList timelines) + { + if (timelines == null) throw new ArgumentNullException("timelines", "timelines cannot be null."); + this.timelines = timelines; + // Note: avoiding reallocations by adding all hash set entries at + // once (EnsureCapacity() is only available in newer .Net versions). + int idCount = 0; + int timelinesCount = timelines.Count; + Timeline[] timelinesItems = timelines.Items; + for (int t = 0; t < timelinesCount; ++t) + idCount += timelinesItems[t].PropertyIds.Length; + string[] propertyIds = new string[idCount]; + int currentId = 0; + for (int t = 0; t < timelinesCount; ++t) + { + var ids = timelinesItems[t].PropertyIds; + for (int i = 0, idsLength = ids.Length; i < idsLength; ++i) + propertyIds[currentId++] = ids[i]; + } + this.timelineIds = new HashSet(propertyIds); + } + + /// The duration of the animation in seconds, which is usually the highest time of all frames in the timeline. The duration is + /// used to know when it has completed and when it should loop back to the start. + public float Duration { get { return duration; } set { duration = value; } } + + /// The animation's name, which is unique across all animations in the skeleton. + public string Name { get { return name; } } + + /// Returns true if this animation contains a timeline with any of the specified property IDs. + public bool HasTimeline(string[] propertyIds) + { + foreach (string id in propertyIds) + if (timelineIds.Contains(id)) return true; + return false; + } + + /// Applies the animation's timelines to the specified skeleton. + /// + /// The skeleton the animation is being applied to. This provides access to the bones, slots, and other skeleton + /// components the timelines may change. + /// The last time in seconds this animation was applied. Some timelines trigger only at specific times rather + /// than every frame. Pass -1 the first time an animation is applied to ensure frame 0 is triggered. + /// The time in seconds the skeleton is being posed for. Most timelines find the frame before and the frame after + /// this time and interpolate between the frame values. If beyond the and loop is + /// true then the animation will repeat, else the last frame will be applied. + /// If true, the animation repeats after the . + /// If any events are fired, they are added to this list. Can be null to ignore fired events or if no timelines + /// fire events. + /// 0 applies the current or setup values (depending on blend). 1 applies the timeline values. Between + /// 0 and 1 applies values between the current or setup values and the timeline values. 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 (layering). + /// Controls how mixing is applied when alpha < 1. + /// Indicates whether the timelines are mixing in or out. Used by timelines which perform instant transitions, + /// such as or . + public void Apply(Skeleton skeleton, float lastTime, float time, bool loop, ExposedList events, float alpha, + MixBlend blend, MixDirection direction) + { + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + + if (loop && duration != 0) + { + time %= duration; + if (lastTime > 0) lastTime %= duration; + } + + var timelines = this.timelines.Items; + for (int i = 0, n = this.timelines.Count; i < n; i++) + timelines[i].Apply(skeleton, lastTime, time, events, alpha, blend, direction); + } + + override public string ToString() + { + return name; + } + } + + /// + /// Controls how timeline values are mixed with setup pose values or current pose values when a timeline is applied with + /// alpha < 1. + /// + public enum MixBlend + { + /// Transitions from the setup value to the timeline value (the current value is not used). Before the first frame, the + /// setup value is set. + Setup, + + /// + /// + /// Transitions from the current value to the timeline value. Before the first frame, transitions from the current value to + /// the setup value. Timelines which perform instant transitions, such as or + /// , use the setup value before the first frame. + /// + /// First is intended for the first animations applied, not for animations layered on top of those. + /// + First, + + /// + /// + /// Transitions from the current value to the timeline value. No change is made before the first frame (the current value is + /// kept until the first frame). + /// + /// Replace is intended for animations layered on top of others, not for the first animations applied. + /// + Replace, + + /// + /// + /// Transitions from the current value to the current value plus the timeline value. No change is made before the first frame + /// (the current value is kept until the first frame). + /// + /// Add is intended for animations layered on top of others, not for the first animations applied. Properties + /// set by additive animations must be set manually or by another animation before applying the additive animations, else the + /// property values will increase each time the additive animations are applied. + /// + /// + Add + } + + /// + /// Indicates whether a timeline's alpha is mixing out over time toward 0 (the setup or current pose value) or + /// mixing in toward 1 (the timeline's value). Some timelines use this to decide how values are applied. + /// + public enum MixDirection + { + In, + Out + } + + internal enum Property + { + Rotate = 0, X, Y, ScaleX, ScaleY, ShearX, ShearY, // + RGB, Alpha, RGB2, // + Attachment, Deform, // + Event, DrawOrder, // + IkConstraint, TransformConstraint, // + PathConstraintPosition, PathConstraintSpacing, PathConstraintMix + } + + /// + /// The base class for all timelines. + public abstract class Timeline + { + private readonly string[] propertyIds; + internal readonly float[] frames; + + /// Unique identifiers for the properties the timeline modifies. + public Timeline(int frameCount, params string[] propertyIds) + { + if (propertyIds == null) throw new System.ArgumentNullException("propertyIds", "propertyIds cannot be null."); + this.propertyIds = propertyIds; + frames = new float[frameCount * FrameEntries]; + } + + /// Uniquely encodes both the type of this timeline and the skeleton properties that it affects. + public string[] PropertyIds + { + get { return propertyIds; } + } + + /// The time in seconds and any other values for each frame. + public float[] Frames + { + get { return frames; } + } + + /// The number of entries stored per frame. + public virtual int FrameEntries + { + get { return 1; } + } + + /// The number of frames for this timeline. + public int FrameCount + { + get { return frames.Length / FrameEntries; } + } + + public float Duration + { + get + { + return frames[frames.Length - FrameEntries]; + } + } + + /// Applies this timeline to the skeleton. + /// The skeleton the timeline is being applied to. This provides access to the bones, slots, and other + /// skeleton components the timeline may change. + /// The time this timeline was last applied. Timelines such as trigger only + /// at specific times rather than every frame. In that case, the timeline triggers everything between + /// lastTime (exclusive) and time (inclusive). Pass -1 the first time an animation is + /// applied to ensure frame 0 is triggered. + /// The time in seconds that the skeleton is being posed for. Most timelines find the frame before and the frame + /// after this time and interpolate between the frame values.If beyond the last frame, the last frame will be + /// applied. + /// If any events are fired, they are added to this list. Can be null to ignore fired events or if the timeline + /// does not fire events. + /// 0 applies the current or setup value (depending on blend). 1 applies the timeline value. + /// Between 0 and 1 applies a value between the current or setup value 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 (layering). + /// Controls how mixing is applied when alpha < 1. + /// Indicates whether the timeline is mixing in or out. Used by timelines which perform instant transitions, + /// such as or , and other such as . + public abstract void Apply(Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, + MixBlend blend, MixDirection direction); + + /// Search using a stride of 1. + /// Must be >= the first value in frames. + /// The index of the first value <= time. + internal static int Search(float[] frames, float time) + { + int n = frames.Length; + for (int i = 1; i < n; i++) + if (frames[i] > time) return i - 1; + return n - 1; + } + + /// Search using the specified stride. + /// Must be >= the first value in frames. + /// The index of the first value <= time. + internal static int Search(float[] frames, float time, int step) + { + int n = frames.Length; + for (int i = step; i < n; i += step) + if (frames[i] > time) return i - step; + return n - step; + } + } + + /// An interface for timelines which change the property of a bone. + public interface IBoneTimeline + { + /// The index of the bone in that will be changed when this timeline is applied. + int BoneIndex { get; } + } + + /// An interface for timelines which change the property of a slot. + public interface ISlotTimeline + { + /// The index of the slot in that will be changed when this timeline is applied. + int SlotIndex { get; } + } + + /// The base class for timelines that interpolate between frame values using stepped, linear, or a Bezier curve. + public abstract class CurveTimeline : Timeline + { + public const int LINEAR = 0, STEPPED = 1, BEZIER = 2, BEZIER_SIZE = 18; + + internal float[] curves; + /// The number of key frames for this timeline. + + /// The maximum number of Bezier curves. See . + /// Unique identifiers for the properties the timeline modifies. + public CurveTimeline(int frameCount, int bezierCount, params string[] propertyIds) + : base(frameCount, propertyIds) + { + curves = new float[frameCount + bezierCount * BEZIER_SIZE]; + curves[frameCount - 1] = STEPPED; + } + + /// Sets the specified frame to linear interpolation. + /// Between 0 and frameCount - 1, inclusive. + public void SetLinear(int frame) + { + curves[frame] = LINEAR; + } + + /// Sets the specified frame to stepped interpolation. + /// Between 0 and frameCount - 1, inclusive. + public void SetStepped(int frame) + { + curves[frame] = STEPPED; + } + + /// Returns the interpolation type for the specified frame. + /// Between 0 and frameCount - 1, inclusive. + /// , or + the index of the Bezier segments. + public float GetCurveType(int frame) + { + return (int)curves[frame]; + } + + /// Shrinks the storage for Bezier curves, for use when bezierCount (specified in the constructor) was larger + /// than the actual number of Bezier curves. + public void Shrink(int bezierCount) + { + int size = FrameCount + bezierCount * BEZIER_SIZE; + if (curves.Length > size) + { + float[] newCurves = new float[size]; + Array.Copy(curves, 0, newCurves, 0, size); + curves = newCurves; + } + } + + /// + /// Stores the segments for the specified Bezier curve. For timelines that modify multiple values, there may be more than + /// one curve per frame. + /// The ordinal of this Bezier curve for this timeline, between 0 and bezierCount - 1 (specified + /// in the constructor), inclusive. + /// Between 0 and frameCount - 1, inclusive. + /// The index of the value for the frame this curve is used for. + /// The time for the first key. + /// The value for the first key. + /// The time for the first Bezier handle. + /// The value for the first Bezier handle. + /// The time of the second Bezier handle. + /// The value for the second Bezier handle. + /// The time for the second key. + /// The value for the second key. + public void SetBezier(int bezier, int frame, int value, float time1, float value1, float cx1, float cy1, float cx2, + float cy2, float time2, float value2) + { + + float[] curves = this.curves; + int i = FrameCount + bezier * BEZIER_SIZE; + if (value == 0) curves[frame] = BEZIER + i; + float tmpx = (time1 - cx1 * 2 + cx2) * 0.03f, tmpy = (value1 - cy1 * 2 + cy2) * 0.03f; + float dddx = ((cx1 - cx2) * 3 - time1 + time2) * 0.006f, dddy = ((cy1 - cy2) * 3 - value1 + value2) * 0.006f; + float ddx = tmpx * 2 + dddx, ddy = tmpy * 2 + dddy; + float dx = (cx1 - time1) * 0.3f + tmpx + dddx * 0.16666667f, dy = (cy1 - value1) * 0.3f + tmpy + dddy * 0.16666667f; + float x = time1 + dx, y = value1 + dy; + for (int 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; + } + } + + /// + /// Returns the Bezier interpolated value for the specified time. + /// The index into for the values of the frame before time. + /// The offset from frameIndex to the value this curve is used for. + /// The index of the Bezier segments. See . + public float GetBezierValue(float time, int frameIndex, int valueOffset, int i) + { + float[] curves = this.curves; + if (curves[i] > time) + { + float x = frames[frameIndex], y = frames[frameIndex + valueOffset]; + return y + (time - x) / (curves[i] - x) * (curves[i + 1] - y); + } + int 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 += FrameEntries; + { // scope added to prevent compile error "float x and y declared in enclosing scope" + float x = curves[n - 2], y = curves[n - 1]; + return y + (time - x) / (frames[frameIndex] - x) * (frames[frameIndex + valueOffset] - y); + } + } + } + + /// The base class for a that sets one property. + public abstract class CurveTimeline1 : CurveTimeline + { + public const int ENTRIES = 2; + internal const int VALUE = 1; + + /// The maximum number of Bezier curves. See . + /// Unique identifiers for the properties the timeline modifies. + public CurveTimeline1(int frameCount, int bezierCount, string propertyId) + : base(frameCount, bezierCount, propertyId) + { + } + + public override int FrameEntries + { + get { return ENTRIES; } + } + + /// Sets the time and value for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds + public void SetFrame(int frame, float time, float value) + { + frame <<= 1; + frames[frame] = time; + frames[frame + VALUE] = value; + } + + /// Returns the interpolated value for the specified time. + public float GetCurveValue(float time) + { + float[] frames = this.frames; + int i = frames.Length - 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 LINEAR: + float before = frames[i], value = frames[i + VALUE]; + return value + (time - before) / (frames[i + ENTRIES] - before) * (frames[i + ENTRIES + VALUE] - value); + case STEPPED: + return frames[i + VALUE]; + } + return GetBezierValue(time, i, VALUE, curveType - BEZIER); + } + } + + /// The base class for a which sets two properties. + public abstract class CurveTimeline2 : CurveTimeline + { + public const int ENTRIES = 3; + internal const int VALUE1 = 1, VALUE2 = 2; + + /// The maximum number of Bezier curves. See . + /// Unique identifiers for the properties the timeline modifies. + public CurveTimeline2(int frameCount, int bezierCount, string propertyId1, string propertyId2) + : base(frameCount, bezierCount, propertyId1, propertyId2) + { + } + + public override int FrameEntries + { + get { return ENTRIES; } + } + + /// Sets the time and values for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds. + public void SetFrame(int frame, float time, float value1, float value2) + { + frame *= ENTRIES; + frames[frame] = time; + frames[frame + VALUE1] = value1; + frames[frame + VALUE2] = value2; + } + } + + /// Changes a bone's local . + public class RotateTimeline : CurveTimeline1, IBoneTimeline + { + readonly int boneIndex; + + public RotateTimeline(int frameCount, int bezierCount, int boneIndex) + : base(frameCount, bezierCount, (int)Property.Rotate + "|" + boneIndex) + { + this.boneIndex = boneIndex; + } + + public int BoneIndex + { + get + { + return boneIndex; + } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + if (!bone.active) return; + + if (time < frames[0]) + { // Time is before first frame. + switch (blend) + { + case MixBlend.Setup: + bone.rotation = bone.data.rotation; + return; + case MixBlend.First: + bone.rotation += (bone.data.rotation - bone.rotation) * alpha; + return; + } + return; + } + + float r = GetCurveValue(time); + switch (blend) + { + case MixBlend.Setup: + bone.rotation = bone.data.rotation + r * alpha; + break; + case MixBlend.First: + case MixBlend.Replace: + r += bone.data.rotation - bone.rotation; + goto case MixBlend.Add; // Fall through. + case MixBlend.Add: + bone.rotation += r * alpha; + break; + } + } + } + + /// Changes a bone's local and . + public class TranslateTimeline : CurveTimeline2, IBoneTimeline + { + readonly int boneIndex; + + public TranslateTimeline(int frameCount, int bezierCount, int boneIndex) + : base(frameCount, bezierCount, // + (int)Property.X + "|" + boneIndex, // + (int)Property.Y + "|" + boneIndex) + { + this.boneIndex = boneIndex; + } + + public int BoneIndex + { + get + { + return boneIndex; + } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + if (!bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + 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; + return; + } + return; + } + + float x, y; + int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; + switch (curveType) + { + case LINEAR: + float before = frames[i]; + x = frames[i + VALUE1]; + y = frames[i + VALUE2]; + float t = (time - before) / (frames[i + ENTRIES] - before); + x += (frames[i + ENTRIES + VALUE1] - x) * t; + y += (frames[i + ENTRIES + VALUE2] - y) * t; + break; + case STEPPED: + x = frames[i + VALUE1]; + y = frames[i + VALUE2]; + break; + default: + x = GetBezierValue(time, i, VALUE1, curveType - BEZIER); + y = GetBezierValue(time, i, VALUE2, curveType + BEZIER_SIZE - BEZIER); + break; + } + + 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; + break; + } + } + } + + /// Changes a bone's local . + public class TranslateXTimeline : CurveTimeline1, IBoneTimeline + { + readonly int boneIndex; + + public TranslateXTimeline(int frameCount, int bezierCount, int boneIndex) + : base(frameCount, bezierCount, (int)Property.X + "|" + boneIndex) + { + this.boneIndex = boneIndex; + } + + public int BoneIndex + { + get + { + return boneIndex; + } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + if (!bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + switch (blend) + { + case MixBlend.Setup: + bone.x = bone.data.x; + return; + case MixBlend.First: + bone.x += (bone.data.x - bone.x) * alpha; + return; + } + return; + } + + float x = GetCurveValue(time); + switch (blend) + { + case MixBlend.Setup: + bone.x = bone.data.x + x * alpha; + break; + case MixBlend.First: + case MixBlend.Replace: + bone.x += (bone.data.x + x - bone.x) * alpha; + break; + case MixBlend.Add: + bone.x += x * alpha; + break; + } + } + } + + /// Changes a bone's local . + public class TranslateYTimeline : CurveTimeline1, IBoneTimeline + { + readonly int boneIndex; + + public TranslateYTimeline(int frameCount, int bezierCount, int boneIndex) + : base(frameCount, bezierCount, (int)Property.Y + "|" + boneIndex) + { + this.boneIndex = boneIndex; + } + + public int BoneIndex + { + get + { + return boneIndex; + } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + if (!bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + switch (blend) + { + case MixBlend.Setup: + bone.y = bone.data.y; + return; + case MixBlend.First: + bone.y += (bone.data.y - bone.y) * alpha; + return; + } + return; + } + + float y = GetCurveValue(time); + switch (blend) + { + case MixBlend.Setup: + bone.y = bone.data.y + y * alpha; + break; + case MixBlend.First: + case MixBlend.Replace: + bone.y += (bone.data.y + y - bone.y) * alpha; + break; + case MixBlend.Add: + bone.y += y * alpha; + break; + } + } + } + + /// Changes a bone's local and . + public class ScaleTimeline : CurveTimeline2, IBoneTimeline + { + readonly int boneIndex; + + public ScaleTimeline(int frameCount, int bezierCount, int boneIndex) + : base(frameCount, bezierCount, // + (int)Property.ScaleX + "|" + boneIndex, // + (int)Property.ScaleY + "|" + boneIndex) + { + this.boneIndex = boneIndex; + } + + public int BoneIndex + { + get + { + return boneIndex; + } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + if (!bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + 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; + return; + } + return; + } + + float x, y; + int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; + switch (curveType) + { + case LINEAR: + float before = frames[i]; + x = frames[i + VALUE1]; + y = frames[i + VALUE2]; + float t = (time - before) / (frames[i + ENTRIES] - before); + x += (frames[i + ENTRIES + VALUE1] - x) * t; + y += (frames[i + ENTRIES + VALUE2] - y) * t; + break; + case STEPPED: + x = frames[i + VALUE1]; + y = frames[i + VALUE2]; + break; + default: + x = GetBezierValue(time, i, VALUE1, curveType - BEZIER); + y = GetBezierValue(time, i, VALUE2, curveType + BEZIER_SIZE - BEZIER); + break; + } + 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 + { + // Mixing out uses sign of setup or current pose, else use sign of key. + float bx, by; + if (direction == MixDirection.Out) + { + switch (blend) + { + case MixBlend.Setup: + bx = bone.data.scaleX; + by = bone.data.scaleY; + bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha; + bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha; + break; + case MixBlend.First: + case MixBlend.Replace: + bx = bone.scaleX; + by = bone.scaleY; + bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha; + bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha; + break; + case MixBlend.Add: + bx = bone.scaleX; + by = bone.scaleY; + bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bone.data.scaleX) * alpha; + bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - bone.data.scaleY) * alpha; + break; + } + } + else + { + switch (blend) + { + case MixBlend.Setup: + bx = Math.Abs(bone.data.scaleX) * Math.Sign(x); + by = Math.Abs(bone.data.scaleY) * Math.Sign(y); + bone.scaleX = bx + (x - bx) * alpha; + bone.scaleY = by + (y - by) * alpha; + break; + case MixBlend.First: + case MixBlend.Replace: + bx = Math.Abs(bone.scaleX) * Math.Sign(x); + by = Math.Abs(bone.scaleY) * Math.Sign(y); + bone.scaleX = bx + (x - bx) * alpha; + bone.scaleY = by + (y - by) * alpha; + break; + case MixBlend.Add: + bx = Math.Sign(x); + by = Math.Sign(y); + bone.scaleX = Math.Abs(bone.scaleX) * bx + (x - Math.Abs(bone.data.scaleX) * bx) * alpha; + bone.scaleY = Math.Abs(bone.scaleY) * by + (y - Math.Abs(bone.data.scaleY) * by) * alpha; + break; + } + } + } + } + } + + /// Changes a bone's local . + public class ScaleXTimeline : CurveTimeline1, IBoneTimeline + { + readonly int boneIndex; + + public ScaleXTimeline(int frameCount, int bezierCount, int boneIndex) + : base(frameCount, bezierCount, (int)Property.ScaleX + "|" + boneIndex) + { + this.boneIndex = boneIndex; + } + + public int BoneIndex + { + get + { + return boneIndex; + } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + if (!bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + switch (blend) + { + case MixBlend.Setup: + bone.scaleX = bone.data.scaleX; + return; + case MixBlend.First: + bone.scaleX += (bone.data.scaleX - bone.scaleX) * alpha; + return; + } + return; + } + + float x = GetCurveValue(time) * bone.data.scaleX; + if (alpha == 1) + { + if (blend == MixBlend.Add) + bone.scaleX += x - bone.data.scaleX; + else + bone.scaleX = x; + } + else + { + // Mixing out uses sign of setup or current pose, else use sign of key. + float bx; + if (direction == MixDirection.Out) + { + switch (blend) + { + case MixBlend.Setup: + bx = bone.data.scaleX; + bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha; + break; + case MixBlend.First: + case MixBlend.Replace: + bx = bone.scaleX; + bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha; + break; + case MixBlend.Add: + bx = bone.scaleX; + bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bone.data.scaleX) * alpha; + break; + } + } + else + { + switch (blend) + { + case MixBlend.Setup: + bx = Math.Abs(bone.data.scaleX) * Math.Sign(x); + bone.scaleX = bx + (x - bx) * alpha; + break; + case MixBlend.First: + case MixBlend.Replace: + bx = Math.Abs(bone.scaleX) * Math.Sign(x); + bone.scaleX = bx + (x - bx) * alpha; + break; + case MixBlend.Add: + bx = Math.Sign(x); + bone.scaleX = Math.Abs(bone.scaleX) * bx + (x - Math.Abs(bone.data.scaleX) * bx) * alpha; + break; + } + } + } + } + } + + /// Changes a bone's local . + public class ScaleYTimeline : CurveTimeline1, IBoneTimeline + { + readonly int boneIndex; + + public ScaleYTimeline(int frameCount, int bezierCount, int boneIndex) + : base(frameCount, bezierCount, (int)Property.ScaleY + "|" + boneIndex) + { + this.boneIndex = boneIndex; + } + + public int BoneIndex + { + get + { + return boneIndex; + } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + if (!bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + switch (blend) + { + case MixBlend.Setup: + bone.scaleY = bone.data.scaleY; + return; + case MixBlend.First: + bone.scaleY += (bone.data.scaleY - bone.scaleY) * alpha; + return; + } + return; + } + + float y = GetCurveValue(time) * bone.data.scaleY; + if (alpha == 1) + { + if (blend == MixBlend.Add) + bone.scaleY += y - bone.data.scaleY; + else + bone.scaleY = y; + } + else + { + // Mixing out uses sign of setup or current pose, else use sign of key. + float by; + if (direction == MixDirection.Out) + { + switch (blend) + { + case MixBlend.Setup: + by = bone.data.scaleY; + bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha; + break; + case MixBlend.First: + case MixBlend.Replace: + by = bone.scaleY; + bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha; + break; + case MixBlend.Add: + by = bone.scaleY; + bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - bone.data.scaleY) * alpha; + break; + } + } + else + { + switch (blend) + { + case MixBlend.Setup: + by = Math.Abs(bone.data.scaleY) * Math.Sign(y); + bone.scaleY = by + (y - by) * alpha; + break; + case MixBlend.First: + case MixBlend.Replace: + by = Math.Abs(bone.scaleY) * Math.Sign(y); + bone.scaleY = by + (y - by) * alpha; + break; + case MixBlend.Add: + by = Math.Sign(y); + bone.scaleY = Math.Abs(bone.scaleY) * by + (y - Math.Abs(bone.data.scaleY) * by) * alpha; + break; + } + } + } + } + } + + /// Changes a bone's local and . + public class ShearTimeline : CurveTimeline2, IBoneTimeline + { + readonly int boneIndex; + + public ShearTimeline(int frameCount, int bezierCount, int boneIndex) + : base(frameCount, bezierCount, // + (int)Property.ShearX + "|" + boneIndex, // + (int)Property.ShearY + "|" + boneIndex) + { + this.boneIndex = boneIndex; + } + + public int BoneIndex + { + get + { + return boneIndex; + } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + if (!bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + 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; + return; + } + return; + } + + float x, y; + int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; + switch (curveType) + { + case LINEAR: + float before = frames[i]; + x = frames[i + VALUE1]; + y = frames[i + VALUE2]; + float t = (time - before) / (frames[i + ENTRIES] - before); + x += (frames[i + ENTRIES + VALUE1] - x) * t; + y += (frames[i + ENTRIES + VALUE2] - y) * t; + break; + case STEPPED: + x = frames[i + VALUE1]; + y = frames[i + VALUE2]; + break; + default: + x = GetBezierValue(time, i, VALUE1, curveType - BEZIER); + y = GetBezierValue(time, i, VALUE2, curveType + BEZIER_SIZE - BEZIER); + break; + } + + 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; + break; + } + } + } + + /// Changes a bone's local . + public class ShearXTimeline : CurveTimeline1, IBoneTimeline + { + readonly int boneIndex; + + public ShearXTimeline(int frameCount, int bezierCount, int boneIndex) + : base(frameCount, bezierCount, (int)Property.ShearX + "|" + boneIndex) + { + this.boneIndex = boneIndex; + } + + public int BoneIndex + { + get + { + return boneIndex; + } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + if (!bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + switch (blend) + { + case MixBlend.Setup: + bone.shearX = bone.data.shearX; + return; + case MixBlend.First: + bone.shearX += (bone.data.shearX - bone.shearX) * alpha; + return; + } + return; + } + + float x = GetCurveValue(time); + switch (blend) + { + case MixBlend.Setup: + bone.shearX = bone.data.shearX + x * alpha; + break; + case MixBlend.First: + case MixBlend.Replace: + bone.shearX += (bone.data.shearX + x - bone.shearX) * alpha; + break; + case MixBlend.Add: + bone.shearX += x * alpha; + break; + } + } + } + + /// Changes a bone's local . + public class ShearYTimeline : CurveTimeline1, IBoneTimeline + { + readonly int boneIndex; + + public ShearYTimeline(int frameCount, int bezierCount, int boneIndex) + : base(frameCount, bezierCount, (int)Property.ShearY + "|" + boneIndex) + { + this.boneIndex = boneIndex; + } + + public int BoneIndex + { + get + { + return boneIndex; + } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + if (!bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + switch (blend) + { + case MixBlend.Setup: + bone.shearY = bone.data.shearY; + return; + case MixBlend.First: + bone.shearY += (bone.data.shearY - bone.shearY) * alpha; + return; + } + return; + } + + float y = GetCurveValue(time); + switch (blend) + { + case MixBlend.Setup: + bone.shearY = bone.data.shearY + y * alpha; + break; + case MixBlend.First: + case MixBlend.Replace: + bone.shearY += (bone.data.shearY + y - bone.shearY) * alpha; + break; + case MixBlend.Add: + bone.shearY += y * alpha; + break; + } + } + } + + /// Changes a slot's . + public class RGBATimeline : CurveTimeline, ISlotTimeline + { + public const int ENTRIES = 5; + protected const int R = 1, G = 2, B = 3, A = 4; + + readonly int slotIndex; + + public RGBATimeline(int frameCount, int bezierCount, int slotIndex) + : base(frameCount, bezierCount, // + (int)Property.RGB + "|" + slotIndex, // + (int)Property.Alpha + "|" + slotIndex) + { + this.slotIndex = slotIndex; + } + public override int FrameEntries + { + get { return ENTRIES; } + } + + public int SlotIndex + { + get + { + return slotIndex; + } + } + + /// Sets the time and color for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds. + public void 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; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Slot slot = skeleton.slots.Items[slotIndex]; + if (!slot.bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + var setup = slot.data; + switch (blend) + { + case MixBlend.Setup: + slot.r = setup.r; + slot.g = setup.g; + slot.b = setup.b; + slot.a = setup.a; + return; + case MixBlend.First: + slot.r += (setup.r - slot.r) * alpha; + slot.g += (setup.g - slot.g) * alpha; + slot.b += (setup.b - slot.b) * alpha; + slot.a += (setup.a - slot.a) * alpha; + slot.ClampColor(); + return; + } + return; + } + + float r, g, b, a; + int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; + switch (curveType) + { + case LINEAR: + float before = frames[i]; + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + a = frames[i + A]; + float t = (time - before) / (frames[i + ENTRIES] - before); + r += (frames[i + ENTRIES + R] - r) * t; + g += (frames[i + ENTRIES + G] - g) * t; + b += (frames[i + ENTRIES + B] - b) * t; + a += (frames[i + ENTRIES + A] - a) * t; + break; + case STEPPED: + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + a = frames[i + A]; + break; + default: + r = GetBezierValue(time, i, R, curveType - BEZIER); + g = GetBezierValue(time, i, G, curveType + BEZIER_SIZE - BEZIER); + b = GetBezierValue(time, i, B, curveType + BEZIER_SIZE * 2 - BEZIER); + a = GetBezierValue(time, i, A, curveType + BEZIER_SIZE * 3 - BEZIER); + break; + } + + if (alpha == 1) + { + slot.r = r; + slot.g = g; + slot.b = b; + slot.a = a; + } + else + { + float br, bg, bb, ba; + if (blend == MixBlend.Setup) + { + br = slot.data.r; + bg = slot.data.g; + bb = slot.data.b; + ba = slot.data.a; + } + else + { + br = slot.r; + bg = slot.g; + bb = slot.b; + ba = slot.a; + } + slot.r = br + (r - br) * alpha; + slot.g = bg + (g - bg) * alpha; + slot.b = bb + (b - bb) * alpha; + slot.a = ba + (a - ba) * alpha; + } + slot.ClampColor(); + } + } + + /// Changes the RGB for a slot's . + public class RGBTimeline : CurveTimeline, ISlotTimeline + { + public const int ENTRIES = 4; + protected const int R = 1, G = 2, B = 3; + + readonly int slotIndex; + + public RGBTimeline(int frameCount, int bezierCount, int slotIndex) + : base(frameCount, bezierCount, // + (int)Property.RGB + "|" + slotIndex) + { + this.slotIndex = slotIndex; + } + + public override int FrameEntries + { + get { return ENTRIES; } + } + + public int SlotIndex + { + get + { + return slotIndex; + } + } + + /// Sets the time and color for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds. + public void SetFrame(int frame, float time, float r, float g, float b) + { + frame <<= 2; + frames[frame] = time; + frames[frame + R] = r; + frames[frame + G] = g; + frames[frame + B] = b; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Slot slot = skeleton.slots.Items[slotIndex]; + if (!slot.bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + var setup = slot.data; + switch (blend) + { + case MixBlend.Setup: + slot.r = setup.r; + slot.g = setup.g; + slot.b = setup.b; + return; + case MixBlend.First: + slot.r += (setup.r - slot.r) * alpha; + slot.g += (setup.g - slot.g) * alpha; + slot.b += (setup.b - slot.b) * alpha; + slot.ClampColor(); + return; + } + return; + } + + float r, g, b; + int i = Search(frames, time, ENTRIES), curveType = (int)curves[i >> 2]; + switch (curveType) + { + case LINEAR: + float before = frames[i]; + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + float t = (time - before) / (frames[i + ENTRIES] - before); + r += (frames[i + ENTRIES + R] - r) * t; + g += (frames[i + ENTRIES + G] - g) * t; + b += (frames[i + ENTRIES + B] - b) * t; + break; + case STEPPED: + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + break; + default: + r = GetBezierValue(time, i, R, curveType - BEZIER); + g = GetBezierValue(time, i, G, curveType + BEZIER_SIZE - BEZIER); + b = GetBezierValue(time, i, B, curveType + BEZIER_SIZE * 2 - BEZIER); + break; + } + + if (alpha == 1) + { + slot.r = r; + slot.g = g; + slot.b = b; + } + else + { + float br, bg, bb; + if (blend == MixBlend.Setup) + { + var setup = slot.data; + br = setup.r; + bg = setup.g; + bb = setup.b; + } + else + { + br = slot.r; + bg = slot.g; + bb = slot.b; + } + slot.r = br + (r - br) * alpha; + slot.g = bg + (g - bg) * alpha; + slot.b = bb + (b - bb) * alpha; + } + slot.ClampColor(); + } + } + + /// Changes the alpha for a slot's . + public class AlphaTimeline : CurveTimeline1, ISlotTimeline + { + readonly int slotIndex; + + public AlphaTimeline(int frameCount, int bezierCount, int slotIndex) + : base(frameCount, bezierCount, (int)Property.Alpha + "|" + slotIndex) + { + this.slotIndex = slotIndex; + } + + public int SlotIndex + { + get + { + return slotIndex; + } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Slot slot = skeleton.slots.Items[slotIndex]; + if (!slot.bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + var setup = slot.data; + switch (blend) + { + case MixBlend.Setup: + slot.a = setup.a; + return; + case MixBlend.First: + slot.a += (setup.a - slot.a) * alpha; + slot.ClampColor(); + return; + } + return; + } + + float a = GetCurveValue(time); + if (alpha == 1) + slot.a = a; + else + { + if (blend == MixBlend.Setup) slot.a = slot.data.a; + slot.a += (a - slot.a) * alpha; + } + slot.ClampColor(); + } + } + + /// Changes a slot's and for two color tinting. + public class RGBA2Timeline : CurveTimeline, ISlotTimeline + { + public const int ENTRIES = 8; + protected const int R = 1, G = 2, B = 3, A = 4, R2 = 5, G2 = 6, B2 = 7; + + readonly int slotIndex; + + public RGBA2Timeline(int frameCount, int bezierCount, int slotIndex) + : base(frameCount, bezierCount, // + (int)Property.RGB + "|" + slotIndex, // + (int)Property.Alpha + "|" + slotIndex, // + (int)Property.RGB2 + "|" + slotIndex) + { + this.slotIndex = slotIndex; + } + + public override int FrameEntries + { + get + { + return ENTRIES; + } + } + + /// + /// The index of the slot in that will be changed when this timeline is applied. The + /// must have a dark color available. + public int SlotIndex + { + get + { + return slotIndex; + } + } + + /// Sets the time, light color, and dark color for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds. + public void SetFrame(int frame, float time, float r, float g, float b, float a, float r2, float g2, float b2) + { + frame <<= 3; + 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; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Slot slot = skeleton.slots.Items[slotIndex]; + if (!slot.bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + SlotData setup = slot.data; + switch (blend) + { + case MixBlend.Setup: + slot.r = setup.r; + slot.g = setup.g; + slot.b = setup.b; + slot.a = setup.a; + slot.ClampColor(); + slot.r2 = setup.r2; + slot.g2 = setup.g2; + slot.b2 = setup.b2; + slot.ClampSecondColor(); + return; + case MixBlend.First: + slot.r += (slot.r - setup.r) * alpha; + slot.g += (slot.g - setup.g) * alpha; + slot.b += (slot.b - setup.b) * alpha; + slot.a += (slot.a - setup.a) * alpha; + slot.ClampColor(); + slot.r2 += (slot.r2 - setup.r2) * alpha; + slot.g2 += (slot.g2 - setup.g2) * alpha; + slot.b2 += (slot.b2 - setup.b2) * alpha; + slot.ClampSecondColor(); + return; + } + return; + } + + float r, g, b, a, r2, g2, b2; + int i = Search(frames, time, ENTRIES), curveType = (int)curves[i >> 3]; + switch (curveType) + { + case LINEAR: + float before = frames[i]; + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + a = frames[i + A]; + r2 = frames[i + R2]; + g2 = frames[i + G2]; + b2 = frames[i + B2]; + float t = (time - before) / (frames[i + ENTRIES] - before); + r += (frames[i + ENTRIES + R] - r) * t; + g += (frames[i + ENTRIES + G] - g) * t; + b += (frames[i + ENTRIES + B] - b) * t; + a += (frames[i + ENTRIES + A] - a) * t; + r2 += (frames[i + ENTRIES + R2] - r2) * t; + g2 += (frames[i + ENTRIES + G2] - g2) * t; + b2 += (frames[i + ENTRIES + B2] - b2) * t; + break; + case STEPPED: + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + a = frames[i + A]; + r2 = frames[i + R2]; + g2 = frames[i + G2]; + b2 = frames[i + B2]; + break; + default: + r = GetBezierValue(time, i, R, curveType - BEZIER); + g = GetBezierValue(time, i, G, curveType + BEZIER_SIZE - BEZIER); + b = GetBezierValue(time, i, B, curveType + BEZIER_SIZE * 2 - BEZIER); + a = GetBezierValue(time, i, A, curveType + BEZIER_SIZE * 3 - BEZIER); + r2 = GetBezierValue(time, i, R2, curveType + BEZIER_SIZE * 4 - BEZIER); + g2 = GetBezierValue(time, i, G2, curveType + BEZIER_SIZE * 5 - BEZIER); + b2 = GetBezierValue(time, i, B2, curveType + BEZIER_SIZE * 6 - BEZIER); + break; + } + + if (alpha == 1) + { + slot.r = r; + slot.g = g; + slot.b = b; + slot.a = a; + slot.r2 = r2; + slot.g2 = g2; + slot.b2 = b2; + } + else + { + float br, bg, bb, ba, br2, bg2, bb2; + if (blend == MixBlend.Setup) + { + br = slot.data.r; + bg = slot.data.g; + bb = slot.data.b; + ba = slot.data.a; + br2 = slot.data.r2; + bg2 = slot.data.g2; + bb2 = slot.data.b2; + } + else + { + br = slot.r; + bg = slot.g; + bb = slot.b; + ba = slot.a; + br2 = slot.r2; + bg2 = slot.g2; + bb2 = slot.b2; + } + slot.r = br + (r - br) * alpha; + slot.g = bg + (g - bg) * alpha; + slot.b = bb + (b - bb) * alpha; + slot.a = ba + (a - ba) * alpha; + slot.r2 = br2 + (r2 - br2) * alpha; + slot.g2 = bg2 + (g2 - bg2) * alpha; + slot.b2 = bb2 + (b2 - bb2) * alpha; + } + slot.ClampColor(); + slot.ClampSecondColor(); + } + } + + /// Changes the RGB for a slot's and for two color tinting. + public class RGB2Timeline : CurveTimeline, ISlotTimeline + { + public const int ENTRIES = 7; + protected const int R = 1, G = 2, B = 3, R2 = 4, G2 = 5, B2 = 6; + + readonly int slotIndex; + + public RGB2Timeline(int frameCount, int bezierCount, int slotIndex) + : base(frameCount, bezierCount, // + (int)Property.RGB + "|" + slotIndex, // + (int)Property.RGB2 + "|" + slotIndex) + { + this.slotIndex = slotIndex; + } + + public override int FrameEntries + { + get + { + return ENTRIES; + } + } + + /// + /// The index of the slot in that will be changed when this timeline is applied. The + /// must have a dark color available. + public int SlotIndex + { + get + { + return slotIndex; + } + } + + /// Sets the time, light color, and dark color for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds. + public void 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; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Slot slot = skeleton.slots.Items[slotIndex]; + if (!slot.bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + SlotData setup = slot.data; + switch (blend) + { + case MixBlend.Setup: + slot.r = setup.r; + slot.g = setup.g; + slot.b = setup.b; + slot.ClampColor(); + slot.r2 = setup.r2; + slot.g2 = setup.g2; + slot.b2 = setup.b2; + slot.ClampSecondColor(); + return; + case MixBlend.First: + slot.r += (slot.r - setup.r) * alpha; + slot.g += (slot.g - setup.g) * alpha; + slot.b += (slot.b - setup.b) * alpha; + slot.ClampColor(); + slot.r2 += (slot.r2 - setup.r2) * alpha; + slot.g2 += (slot.g2 - setup.g2) * alpha; + slot.b2 += (slot.b2 - setup.b2) * alpha; + slot.ClampSecondColor(); + return; + } + return; + } + + float r, g, b, r2, g2, b2; + int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; + switch (curveType) + { + case LINEAR: + float before = frames[i]; + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + r2 = frames[i + R2]; + g2 = frames[i + G2]; + b2 = frames[i + B2]; + float t = (time - before) / (frames[i + ENTRIES] - before); + r += (frames[i + ENTRIES + R] - r) * t; + g += (frames[i + ENTRIES + G] - g) * t; + b += (frames[i + ENTRIES + B] - b) * t; + r2 += (frames[i + ENTRIES + R2] - r2) * t; + g2 += (frames[i + ENTRIES + G2] - g2) * t; + b2 += (frames[i + ENTRIES + B2] - b2) * t; + break; + case STEPPED: + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + r2 = frames[i + R2]; + g2 = frames[i + G2]; + b2 = frames[i + B2]; + break; + default: + r = GetBezierValue(time, i, R, curveType - BEZIER); + g = GetBezierValue(time, i, G, curveType + BEZIER_SIZE - BEZIER); + b = GetBezierValue(time, i, B, curveType + BEZIER_SIZE * 2 - BEZIER); + r2 = GetBezierValue(time, i, R2, curveType + BEZIER_SIZE * 3 - BEZIER); + g2 = GetBezierValue(time, i, G2, curveType + BEZIER_SIZE * 4 - BEZIER); + b2 = GetBezierValue(time, i, B2, curveType + BEZIER_SIZE * 5 - BEZIER); + break; + } + + if (alpha == 1) + { + slot.r = r; + slot.g = g; + slot.b = b; + slot.r2 = r2; + slot.g2 = g2; + slot.b2 = b2; + } + else + { + float br, bg, bb, br2, bg2, bb2; + if (blend == MixBlend.Setup) + { + SlotData setup = slot.data; + br = setup.r; + bg = setup.g; + bb = setup.b; + br2 = setup.r2; + bg2 = setup.g2; + bb2 = setup.b2; + } + else + { + br = slot.r; + bg = slot.g; + bb = slot.b; + br2 = slot.r2; + bg2 = slot.g2; + bb2 = slot.b2; + } + slot.r = br + (r - br) * alpha; + slot.g = bg + (g - bg) * alpha; + slot.b = bb + (b - bb) * alpha; + slot.r2 = br2 + (r2 - br2) * alpha; + slot.g2 = bg2 + (g2 - bg2) * alpha; + slot.b2 = bb2 + (b2 - bb2) * alpha; + } + slot.ClampColor(); + slot.ClampSecondColor(); + } + } + + /// Changes a slot's . + public class AttachmentTimeline : Timeline, ISlotTimeline + { + readonly int slotIndex; + readonly string[] attachmentNames; + + public AttachmentTimeline(int frameCount, int slotIndex) + : base(frameCount, (int)Property.Attachment + "|" + slotIndex) + { + this.slotIndex = slotIndex; + attachmentNames = new String[frameCount]; + } + + public int SlotIndex + { + get + { + return slotIndex; + } + } + + /// The attachment name for each frame. May contain null values to clear the attachment. + public string[] AttachmentNames + { + get + { + return attachmentNames; + } + } + + /// Sets the time and attachment name for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds. + public void SetFrame(int frame, float time, String attachmentName) + { + frames[frame] = time; + attachmentNames[frame] = attachmentName; + } + + public override void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Slot slot = skeleton.slots.Items[slotIndex]; + if (!slot.bone.active) return; + + if (direction == MixDirection.Out) + { + if (blend == MixBlend.Setup) SetAttachment(skeleton, slot, slot.data.attachmentName); + return; + } + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + if (blend == MixBlend.Setup || blend == MixBlend.First) SetAttachment(skeleton, slot, slot.data.attachmentName); + return; + } + + SetAttachment(skeleton, slot, attachmentNames[Search(frames, time)]); + } + + private void SetAttachment(Skeleton skeleton, Slot slot, string attachmentName) + { + slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); + } + } + + /// Changes a slot's to deform a . + public class DeformTimeline : CurveTimeline, ISlotTimeline + { + readonly int slotIndex; + readonly VertexAttachment attachment; + internal float[][] vertices; + + public DeformTimeline(int frameCount, int bezierCount, int slotIndex, VertexAttachment attachment) + : base(frameCount, bezierCount, (int)Property.Deform + "|" + slotIndex + "|" + attachment.Id) + { + this.slotIndex = slotIndex; + this.attachment = attachment; + vertices = new float[frameCount][]; + } + + public int SlotIndex + { + get + { + return slotIndex; + } + } + /// The attachment that will be deformed. + /// + public VertexAttachment Attachment + { + get + { + return attachment; + } + } + + /// The vertices for each frame. + public float[][] Vertices + { + get + { + return vertices; + } + } + + /// Sets the time and vertices for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds. + /// Vertex positions for an unweighted VertexAttachment, or deform offsets if it has weights. + public void SetFrame(int frame, float time, float[] vertices) + { + frames[frame] = time; + this.vertices[frame] = vertices; + } + + /// Ignored (0 is used for a deform timeline). + /// Ignored (1 is used for a deform timeline). + public void setBezier(int bezier, int frame, int value, float time1, float value1, float cx1, float cy1, float cx2, + float cy2, float time2, float value2) + { + float[] curves = this.curves; + int i = FrameCount + bezier * BEZIER_SIZE; + if (value == 0) curves[frame] = BEZIER + i; + float tmpx = (time1 - cx1 * 2 + cx2) * 0.03f, tmpy = cy2 * 0.03f - cy1 * 0.06f; + float dddx = ((cx1 - cx2) * 3 - time1 + time2) * 0.006f, dddy = (cy1 - cy2 + 0.33333333f) * 0.018f; + float ddx = tmpx * 2 + dddx, ddy = tmpy * 2 + dddy; + float dx = (cx1 - time1) * 0.3f + tmpx + dddx * 0.16666667f, dy = cy1 * 0.3f + tmpy + dddy * 0.16666667f; + float x = time1 + dx, y = dy; + for (int 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; + } + } + + /// Returns the interpolated percentage for the specified time. + /// The frame before time. + private float GetCurvePercent(float time, int frame) + { + float[] curves = this.curves; + int i = (int)curves[frame]; + switch (i) + { + case LINEAR: + float x = frames[frame]; + return (time - x) / (frames[frame + FrameEntries] - x); + case STEPPED: + return 0; + } + i -= BEZIER; + if (curves[i] > time) + { + float x = frames[frame]; + return curves[i + 1] * (time - x) / (curves[i] - x); + } + int 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); + } + } + { // scope added to prevent compile error "float x and y declared in enclosing scope" + float x = curves[n - 2], y = curves[n - 1]; + return y + (1 - y) * (time - x) / (frames[frame + FrameEntries] - x); + } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + + Slot slot = skeleton.slots.Items[slotIndex]; + if (!slot.bone.active) return; + var vertexAttachment = slot.attachment as VertexAttachment; + if (vertexAttachment == null || vertexAttachment.DeformAttachment != attachment) return; + + var deformArray = slot.Deform; + if (deformArray.Count == 0) blend = MixBlend.Setup; + + float[][] vertices = this.vertices; + int vertexCount = vertices[0].Length; + + float[] deform; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + switch (blend) + { + case MixBlend.Setup: + deformArray.Clear(); + return; + case MixBlend.First: + if (alpha == 1) + { + deformArray.Clear(); + return; + } + + // Ensure size and preemptively set count. + if (deformArray.Capacity < vertexCount) deformArray.Capacity = vertexCount; + deformArray.Count = vertexCount; + deform = deformArray.Items; + + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions. + float[] setupVertices = vertexAttachment.vertices; + for (int i = 0; i < vertexCount; i++) + deform[i] += (setupVertices[i] - deform[i]) * alpha; + } + else + { + // Weighted deform offsets. + alpha = 1 - alpha; + for (int i = 0; i < vertexCount; i++) + deform[i] *= alpha; + } + return; + } + return; + } + + // Ensure size and preemptively set count. + if (deformArray.Capacity < vertexCount) deformArray.Capacity = vertexCount; + deformArray.Count = vertexCount; + deform = deformArray.Items; + + if (time >= frames[frames.Length - 1]) + { // Time is after last frame. + float[] lastVertices = vertices[frames.Length - 1]; + if (alpha == 1) + { + if (blend == MixBlend.Add) + { + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions, no alpha. + float[] setupVertices = vertexAttachment.vertices; + for (int i = 0; i < vertexCount; i++) + deform[i] += lastVertices[i] - setupVertices[i]; + } + else + { + // Weighted deform offsets, no alpha. + for (int i = 0; i < vertexCount; i++) + deform[i] += lastVertices[i]; + } + } + else + { + // Vertex positions or deform offsets, no alpha. + Array.Copy(lastVertices, 0, deform, 0, vertexCount); + } + } + else + { + switch (blend) + { + case MixBlend.Setup: + { + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions, with alpha. + float[] setupVertices = vertexAttachment.vertices; + for (int i = 0; i < vertexCount; i++) + { + float setup = setupVertices[i]; + deform[i] = setup + (lastVertices[i] - setup) * alpha; + } + } + else + { + // Weighted deform offsets, with alpha. + for (int 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 (int i = 0; i < vertexCount; i++) + deform[i] += (lastVertices[i] - deform[i]) * alpha; + break; + case MixBlend.Add: + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions, no alpha. + float[] setupVertices = vertexAttachment.vertices; + for (int i = 0; i < vertexCount; i++) + deform[i] += (lastVertices[i] - setupVertices[i]) * alpha; + } + else + { + // Weighted deform offsets, alpha. + for (int i = 0; i < vertexCount; i++) + deform[i] += lastVertices[i] * alpha; + } + break; + } + } + return; + } + + int frame = Search(frames, time); + float percent = GetCurvePercent(time, frame); + float[] prevVertices = vertices[frame]; + float[] nextVertices = vertices[frame + 1]; + + if (alpha == 1) + { + if (blend == MixBlend.Add) + { + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions, no alpha. + float[] setupVertices = vertexAttachment.vertices; + for (int 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 (int 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 (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i]; + deform[i] = prev + (nextVertices[i] - prev) * percent; + } + } + } + else + { + switch (blend) + { + case MixBlend.Setup: + { + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions, with alpha. + float[] setupVertices = vertexAttachment.vertices; + for (int 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 (int 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 (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i]; + deform[i] += (prev + (nextVertices[i] - prev) * percent - deform[i]) * alpha; + } + break; + } + case MixBlend.Add: + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions, with alpha. + float[] setupVertices = vertexAttachment.vertices; + for (int 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 (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i]; + deform[i] += (prev + (nextVertices[i] - prev) * percent) * alpha; + } + } + break; + } + } + } + } + + /// Fires an when specific animation times are reached. + public class EventTimeline : Timeline + { + readonly static string[] propertyIds = { ((int)Property.Event).ToString() }; + readonly Event[] events; + + public EventTimeline(int frameCount) + : base(frameCount, propertyIds) + { + events = new Event[frameCount]; + } + + /// The event for each frame. + public Event[] Events + { + get + { + return events; + } + } + + /// Sets the time and event for the specified frame. + /// Between 0 and frameCount, inclusive. + public void SetFrame(int frame, Event e) + { + frames[frame] = e.time; + events[frame] = e; + } + + /// Fires events for frames > lastTime and <= time. + public override void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, + MixBlend blend, MixDirection direction) + { + + if (firedEvents == null) return; + + float[] frames = this.frames; + int frameCount = frames.Length; + + if (lastTime > time) + { // Fire events after last time for looped animations. + Apply(skeleton, lastTime, int.MaxValue, firedEvents, alpha, blend, direction); + lastTime = -1f; + } + else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame. + return; + if (time < frames[0]) return; // Time is before first frame. + + int i; + if (lastTime < frames[0]) + i = 0; + else + { + i = Search(frames, lastTime) + 1; + float frameTime = frames[i]; + while (i > 0) + { // Fire multiple events with the same frame. + if (frames[i - 1] != frameTime) break; + i--; + } + } + for (; i < frameCount && time >= frames[i]; i++) + firedEvents.Add(events[i]); + } + } + + /// Changes a skeleton's . + public class DrawOrderTimeline : Timeline + { + static readonly string[] propertyIds = { ((int)Property.DrawOrder).ToString() }; + + readonly int[][] drawOrders; + + public DrawOrderTimeline(int frameCount) + : base(frameCount, propertyIds) + { + drawOrders = new int[frameCount][]; + } + + /// The draw order for each frame. + /// . + public int[][] DrawOrders + { + get + { + return drawOrders; + } + } + + /// Sets the time and draw order for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds. + /// For each slot in , the index of the slot in the new draw order. May be null to use + /// setup pose draw order. + public void SetFrame(int frame, float time, int[] drawOrder) + { + frames[frame] = time; + drawOrders[frame] = drawOrder; + } + + public override void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + + if (direction == MixDirection.Out) + { + if (blend == MixBlend.Setup) Array.Copy(skeleton.slots.Items, 0, skeleton.drawOrder.Items, 0, skeleton.slots.Count); + return; + } + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + if (blend == MixBlend.Setup || blend == MixBlend.First) Array.Copy(skeleton.slots.Items, 0, skeleton.drawOrder.Items, 0, skeleton.slots.Count); + return; + } + + int[] drawOrderToSetupIndex = drawOrders[Search(frames, time)]; + if (drawOrderToSetupIndex == null) + Array.Copy(skeleton.slots.Items, 0, skeleton.drawOrder.Items, 0, skeleton.slots.Count); + else + { + Slot[] slots = skeleton.slots.Items; + Slot[] drawOrder = skeleton.drawOrder.Items; + for (int i = 0, n = drawOrderToSetupIndex.Length; i < n; i++) + drawOrder[i] = slots[drawOrderToSetupIndex[i]]; + } + } + } + + /// Changes an IK constraint's , , + /// , , and . + public class IkConstraintTimeline : CurveTimeline + { + public const int ENTRIES = 6; + private const int MIX = 1, SOFTNESS = 2, BEND_DIRECTION = 3, COMPRESS = 4, STRETCH = 5; + + readonly int ikConstraintIndex; + + public IkConstraintTimeline(int frameCount, int bezierCount, int ikConstraintIndex) + : base(frameCount, bezierCount, (int)Property.IkConstraint + "|" + ikConstraintIndex) + { + this.ikConstraintIndex = ikConstraintIndex; + } + + public override int FrameEntries + { + get + { + return ENTRIES; + } + } + + /// The index of the IK constraint slot in that will be changed when this timeline is + /// applied. + public int IkConstraintIndex + { + get + { + return ikConstraintIndex; + } + } + + /// Sets the time, mix, softness, bend direction, compress, and stretch for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds. + /// 1 or -1. + public void 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] = bendDirection; + frames[frame + COMPRESS] = compress ? 1 : 0; + frames[frame + STRETCH] = stretch ? 1 : 0; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + IkConstraint constraint = skeleton.ikConstraints.Items[ikConstraintIndex]; + if (!constraint.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + 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; + } + return; + } + + float mix, softness; + int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; + switch (curveType) + { + case LINEAR: + float before = frames[i]; + mix = frames[i + MIX]; + softness = frames[i + SOFTNESS]; + float t = (time - before) / (frames[i + ENTRIES] - before); + mix += (frames[i + ENTRIES + MIX] - mix) * t; + softness += (frames[i + ENTRIES + SOFTNESS] - softness) * t; + break; + case STEPPED: + mix = frames[i + MIX]; + softness = frames[i + SOFTNESS]; + break; + default: + mix = GetBezierValue(time, i, MIX, curveType - BEZIER); + softness = GetBezierValue(time, i, SOFTNESS, curveType + BEZIER_SIZE - BEZIER); + break; + } + + 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 = (int)frames[i + BEND_DIRECTION]; + constraint.compress = frames[i + COMPRESS] != 0; + constraint.stretch = frames[i + STRETCH] != 0; + } + } + else + { + constraint.mix += (mix - constraint.mix) * alpha; + constraint.softness += (softness - constraint.softness) * alpha; + if (direction == MixDirection.In) + { + constraint.bendDirection = (int)frames[i + BEND_DIRECTION]; + constraint.compress = frames[i + COMPRESS] != 0; + constraint.stretch = frames[i + STRETCH] != 0; + } + } + } + } + + /// Changes a transform constraint's mixes. + public class TransformConstraintTimeline : CurveTimeline + { + public const int ENTRIES = 7; + private const int ROTATE = 1, X = 2, Y = 3, SCALEX = 4, SCALEY = 5, SHEARY = 6; + + readonly int transformConstraintIndex; + + public TransformConstraintTimeline(int frameCount, int bezierCount, int transformConstraintIndex) + : base(frameCount, bezierCount, (int)Property.TransformConstraint + "|" + transformConstraintIndex) + { + this.transformConstraintIndex = transformConstraintIndex; + } + + public override int FrameEntries + { + get + { + return ENTRIES; + } + } + + /// The index of the transform constraint slot in that will be changed when this + /// timeline is applied. + public int TransformConstraintIndex + { + get + { + return transformConstraintIndex; + } + } + + /// Sets the time, rotate mix, translate mix, scale mix, and shear mix for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds. + public void SetFrame(int 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; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + TransformConstraint constraint = skeleton.transformConstraints.Items[transformConstraintIndex]; + if (!constraint.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + TransformConstraintData data = constraint.data; + 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; + } + return; + } + + float rotate, x, y, scaleX, scaleY, shearY; + int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; + switch (curveType) + { + case 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 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); + break; + } + + if (blend == MixBlend.Setup) + { + TransformConstraintData 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; + 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; + } + } + } + + /// Changes a path constraint's . + public class PathConstraintPositionTimeline : CurveTimeline1 + { + readonly int pathConstraintIndex; + + public PathConstraintPositionTimeline(int frameCount, int bezierCount, int pathConstraintIndex) + : base(frameCount, bezierCount, (int)Property.PathConstraintPosition + "|" + pathConstraintIndex) + { + this.pathConstraintIndex = pathConstraintIndex; + } + + /// The index of the path constraint slot in that will be changed when this timeline + /// is applied. + public int PathConstraintIndex + { + get + { + return pathConstraintIndex; + } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; + if (!constraint.active) return; + + if (time < frames[0]) + { // Time is before first frame. + switch (blend) + { + case MixBlend.Setup: + constraint.position = constraint.data.position; + return; + case MixBlend.First: + constraint.position += (constraint.data.position - constraint.position) * alpha; + return; + } + return; + } + + float position = GetCurveValue(time); + if (blend == MixBlend.Setup) + constraint.position = constraint.data.position + (position - constraint.data.position) * alpha; + else + constraint.position += (position - constraint.position) * alpha; + } + } + + /// Changes a path constraint's . + public class PathConstraintSpacingTimeline : CurveTimeline1 + { + readonly int pathConstraintIndex; + + public PathConstraintSpacingTimeline(int frameCount, int bezierCount, int pathConstraintIndex) + : base(frameCount, bezierCount, (int)Property.PathConstraintSpacing + "|" + pathConstraintIndex) + { + this.pathConstraintIndex = pathConstraintIndex; + } + + /// The index of the path constraint slot in that will be changed when this timeline + /// is applied. + public int PathConstraintIndex + { + get + { + return pathConstraintIndex; + } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, MixBlend blend, + MixDirection direction) + { + + PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; + if (!constraint.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + switch (blend) + { + case MixBlend.Setup: + constraint.spacing = constraint.data.spacing; + return; + case MixBlend.First: + constraint.spacing += (constraint.data.spacing - constraint.spacing) * alpha; + return; + } + return; + } + + float spacing = GetCurveValue(time); + if (blend == MixBlend.Setup) + constraint.spacing = constraint.data.spacing + (spacing - constraint.data.spacing) * alpha; + else + constraint.spacing += (spacing - constraint.spacing) * alpha; + } + } + + /// Changes a transform constraint's , , and + /// . + public class PathConstraintMixTimeline : CurveTimeline + { + public const int ENTRIES = 4; + private const int ROTATE = 1, X = 2, Y = 3; + + readonly int pathConstraintIndex; + + public PathConstraintMixTimeline(int frameCount, int bezierCount, int pathConstraintIndex) + : base(frameCount, bezierCount, (int)Property.PathConstraintMix + "|" + pathConstraintIndex) + { + this.pathConstraintIndex = pathConstraintIndex; + } + + public override int FrameEntries + { + get { return ENTRIES; } + } + + /// The index of the path constraint slot in that will be changed when this timeline + /// is applied. + public int PathConstraintIndex + { + get + { + return pathConstraintIndex; + } + } + + /// Sets the time and color for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds. + public void SetFrame(int frame, float time, float mixRotate, float mixX, float mixY) + { + frame <<= 2; + frames[frame] = time; + frames[frame + ROTATE] = mixRotate; + frames[frame + X] = mixX; + frames[frame + Y] = mixY; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; + if (!constraint.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + 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; + return; + } + return; + } + + float rotate, x, y; + int i = Search(frames, time, ENTRIES), 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); + break; + } + + 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; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/AnimationState.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/AnimationState.cs index 7e8e94c..d671be6 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/AnimationState.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/AnimationState.cs @@ -30,1389 +30,1542 @@ using System; using System.Collections.Generic; -namespace Spine4_0_31 { - - /// - /// - /// Applies animations over time, queues animations for later playback, mixes (crossfading) between animations, and applies - /// multiple animations on top of each other (layering). - /// - /// See Applying Animations in the Spine Runtimes Guide. - /// - public class AnimationState { - static readonly Animation EmptyAnimation = new Animation("", new ExposedList(), 0); - - /// 1) A previously applied timeline has set this property. - /// Result: Mix from the current pose to the timeline pose. - internal const int Subsequent = 0; - /// 1) This is the first timeline to set this property. - /// 2) The next track entry applied after this one does not have a timeline to set this property. - /// Result: Mix from the setup pose to the timeline pose. - internal const int First = 1; - /// 1) A previously applied timeline has set this property.
- /// 2) The next track entry to be applied does have a timeline to set this property.
- /// 3) The next track entry after that one does not have a timeline to set this property.
- /// Result: Mix from the current pose to the timeline pose, but do not mix out. This avoids "dipping" when crossfading - /// animations that key the same property. A subsequent timeline will set this property using a mix. - internal const int HoldSubsequent = 2; - /// 1) This is the first timeline to set this property. - /// 2) The next track entry to be applied does have a timeline to set this property. - /// 3) The next track entry after that one does not have a timeline to set this property. - /// Result: Mix from the setup pose to the timeline pose, but do not mix out. This avoids "dipping" when crossfading animations - /// that key the same property. A subsequent timeline will set this property using a mix. - internal const int HoldFirst = 3; - /// 1) This is the first timeline to set this property. - /// 2) The next track entry to be applied does have a timeline to set this property. - /// 3) The next track entry after that one does have a timeline to set this property. - /// 4) timelineHoldMix stores the first subsequent track entry that does not have a timeline to set this property. - /// Result: The same as HOLD except the mix percentage from the timelineHoldMix track entry is used. This handles when more than - /// 2 track entries in a row have a timeline that sets the same property. - /// Eg, A -> B -> C -> D where A, B, and C have a timeline setting same property, but D does not. When A is applied, to avoid - /// "dipping" A is not mixed out, however D (the first entry that doesn't set the property) mixing in is used to mix out A - /// (which affects B and C). Without using D to mix out, A would be applied fully until mixing completes, then snap to the mixed - /// out position. - internal const int HoldMix = 4; - - internal const int Setup = 1, Current = 2; - - protected AnimationStateData data; - private readonly ExposedList tracks = new ExposedList(); - private readonly ExposedList events = new ExposedList(); - // difference to libgdx reference: delegates are used for event callbacks instead of 'final SnapshotArray listeners'. - internal void OnStart (TrackEntry entry) { if (Start != null) Start(entry); } - internal void OnInterrupt (TrackEntry entry) { if (Interrupt != null) Interrupt(entry); } - internal void OnEnd (TrackEntry entry) { if (End != null) End(entry); } - internal void OnDispose (TrackEntry entry) { if (Dispose != null) Dispose(entry); } - internal void OnComplete (TrackEntry entry) { if (Complete != null) Complete(entry); } - internal void OnEvent (TrackEntry entry, Event e) { if (Event != null) Event(entry, e); } - - public delegate void TrackEntryDelegate (TrackEntry trackEntry); - /// See - /// API Reference documentation pages here for details. Usage in C# and spine-unity is explained - /// here - /// on the spine-unity documentation pages. - public event TrackEntryDelegate Start, Interrupt, End, Dispose, Complete; - - public delegate void TrackEntryEventDelegate (TrackEntry trackEntry, Event e); - public event TrackEntryEventDelegate Event; - - public void AssignEventSubscribersFrom (AnimationState src) { - Event = src.Event; - Start = src.Start; - Interrupt = src.Interrupt; - End = src.End; - Dispose = src.Dispose; - Complete = src.Complete; - } - - public void AddEventSubscribersFrom (AnimationState src) { - Event += src.Event; - Start += src.Start; - Interrupt += src.Interrupt; - End += src.End; - Dispose += src.Dispose; - Complete += src.Complete; - } - - // end of difference - private readonly EventQueue queue; // Initialized by constructor. - private readonly HashSet propertyIds = new HashSet(); - private bool animationsChanged; - private float timeScale = 1; - private int unkeyedState; - - private readonly Pool trackEntryPool = new Pool(); - - public AnimationState (AnimationStateData data) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - this.data = data; - this.queue = new EventQueue( - this, - delegate { this.animationsChanged = true; }, - trackEntryPool - ); - } - - /// - /// Increments the track entry , setting queued animations as current if needed. - /// delta time - public void Update (float delta) { - delta *= timeScale; - TrackEntry[] tracksItems = tracks.Items; - for (int i = 0, n = tracks.Count; i < n; i++) { - TrackEntry current = tracksItems[i]; - if (current == null) continue; - - 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. - tracksItems[i] = null; - queue.End(current); - ClearNext(current); - continue; - } - if (current.mixingFrom != null && UpdateMixingFrom(current, 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(); - } - - /// Returns true when all mixing from entries are complete. - private bool 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; - - // Require mixTime > 0 to ensure the mixing from entry was applied at least once. - if (to.mixTime > 0 && to.mixTime >= to.mixDuration) { - // Require totalAlpha == 0 to ensure mixing is complete, unless mixDuration == 0 (the transition is a single frame). - if (from.totalAlpha == 0 || to.mixDuration == 0) { - to.mixingFrom = from.mixingFrom; - if (from.mixingFrom != null) from.mixingFrom.mixingTo = to; - to.interruptAlpha = from.interruptAlpha; - queue.End(from); - } - return finished; - } - - from.trackTime += delta * from.timeScale; - to.mixTime += delta; - return false; - } - - /// - /// Poses the skeleton using the track entry animations. The animation state is not changed, so can be applied to multiple - /// skeletons to pose them identically. - /// True if any animations were applied. - public bool Apply (Skeleton skeleton) { - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - if (animationsChanged) AnimationsChanged(); - - ExposedList events = this.events; - bool applied = false; - TrackEntry[] tracksItems = tracks.Items; - for (int i = 0, n = tracks.Count; i < n; i++) { - TrackEntry current = tracksItems[i]; - if (current == null || current.delay > 0) continue; - applied = true; - - // Track 0 animations aren't for layering, so do not show the previously applied animations before the first key. - MixBlend blend = i == 0 ? MixBlend.First : current.mixBlend; - - // Apply mixing from entries first. - float mix = current.alpha; - if (current.mixingFrom != null) - mix *= ApplyMixingFrom(current, skeleton, blend); - else if (current.trackTime >= current.trackEnd && current.next == null) // - mix = 0; // Set to setup pose the last time the entry will be applied. - - // Apply current entry. - float animationLast = current.animationLast, animationTime = current.AnimationTime, applyTime = animationTime; - ExposedList applyEvents = events; - if (current.reverse) { - applyTime = current.animation.duration - applyTime; - applyEvents = null; - } - - int timelineCount = current.animation.timelines.Count; - Timeline[] timelines = current.animation.timelines.Items; - if ((i == 0 && mix == 1) || blend == MixBlend.Add) { - for (int ii = 0; ii < timelineCount; ii++) { - Timeline timeline = timelines[ii]; - if (timeline is AttachmentTimeline) - ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, applyTime, blend, true); - else - timeline.Apply(skeleton, animationLast, applyTime, applyEvents, mix, blend, MixDirection.In); - } - } else { - int[] timelineMode = current.timelineMode.Items; - - bool firstFrame = current.timelinesRotation.Count != timelineCount << 1; - if (firstFrame) current.timelinesRotation.Resize(timelineCount << 1); - float[] timelinesRotation = current.timelinesRotation.Items; - - for (int ii = 0; ii < timelineCount; ii++) { - Timeline timeline = timelines[ii]; - MixBlend timelineBlend = timelineMode[ii] == AnimationState.Subsequent ? blend : MixBlend.Setup; - var rotateTimeline = timeline as RotateTimeline; - if (rotateTimeline != null) - ApplyRotateTimeline(rotateTimeline, skeleton, applyTime, mix, timelineBlend, timelinesRotation, - ii << 1, firstFrame); - else if (timeline is AttachmentTimeline) - ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, applyTime, blend, true); - else - timeline.Apply(skeleton, animationLast, applyTime, applyEvents, mix, timelineBlend, MixDirection.In); - } - } - QueueEvents(current, animationTime); - events.Clear(false); - current.nextAnimationLast = animationTime; - current.nextTrackLast = current.trackTime; - } - - // Set slots attachments to the setup pose, if needed. This occurs if an animation that is mixing out sets attachments so - // subsequent timelines see any deform, but the subsequent timelines don't set an attachment (eg they are also mixing out or - // the time is before the first key). - int setupState = unkeyedState + Setup; - Slot[] slots = skeleton.slots.Items; - for (int i = 0, n = skeleton.slots.Count; i < n; i++) { - Slot slot = slots[i]; - if (slot.attachmentState == setupState) { - string attachmentName = slot.data.attachmentName; - slot.Attachment = (attachmentName == null ? null : skeleton.GetAttachment(slot.data.index, attachmentName)); - } - } - unkeyedState += 2; // Increasing after each use avoids the need to reset attachmentState for every slot. - - queue.Drain(); - return applied; - } - - /// Version of only applying EventTimelines for lightweight off-screen updates. - // Note: This method is not part of the libgdx reference implementation. - public bool ApplyEventTimelinesOnly (Skeleton skeleton) { - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - - ExposedList events = this.events; - bool applied = false; - TrackEntry[] tracksItems = tracks.Items; - for (int i = 0, n = tracks.Count; i < n; i++) { - TrackEntry current = tracksItems[i]; - if (current == null || current.delay > 0) continue; - applied = true; - - // Apply mixing from entries first. - if (current.mixingFrom != null) ApplyMixingFromEventTimelinesOnly(current, skeleton); - - // Apply current entry. - float animationLast = current.animationLast, animationTime = current.AnimationTime; - int timelineCount = current.animation.timelines.Count; - Timeline[] timelines = current.animation.timelines.Items; - for (int ii = 0; ii < timelineCount; ii++) { - Timeline timeline = timelines[ii]; - if (timeline is EventTimeline) - timeline.Apply(skeleton, animationLast, animationTime, events, 1.0f, MixBlend.Setup, MixDirection.In); - } - QueueEvents(current, animationTime); - events.Clear(false); - current.nextAnimationLast = animationTime; - current.nextTrackLast = current.trackTime; - } - - queue.Drain(); - return applied; - } - - private float 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; // Tracks > 0 are transparent and can't reset to setup pose. - } else { - mix = to.mixTime / to.mixDuration; - if (mix > 1) mix = 1; - if (blend != MixBlend.First) blend = from.mixBlend; // Track 0 ignores track mix blend. - } - - bool attachments = mix < from.attachmentThreshold, drawOrder = mix < from.drawOrderThreshold; - int timelineCount = from.animation.timelines.Count; - Timeline[] timelines = from.animation.timelines.Items; - float alphaHold = from.alpha * to.interruptAlpha, alphaMix = alphaHold * (1 - mix); - float animationLast = from.animationLast, animationTime = from.AnimationTime, applyTime = animationTime; - ExposedList events = null; - if (from.reverse) - applyTime = from.animation.duration - applyTime; - else { - if (mix < from.eventThreshold) events = this.events; - } - - if (blend == MixBlend.Add) { - for (int i = 0; i < timelineCount; i++) - timelines[i].Apply(skeleton, animationLast, applyTime, events, alphaMix, blend, MixDirection.Out); - } else { - int[] timelineMode = from.timelineMode.Items; - TrackEntry[] timelineHoldMix = from.timelineHoldMix.Items; - - bool firstFrame = from.timelinesRotation.Count != timelineCount << 1; - if (firstFrame) from.timelinesRotation.Resize(timelineCount << 1); - float[] timelinesRotation = from.timelinesRotation.Items; - - from.totalAlpha = 0; - for (int i = 0; i < timelineCount; i++) { - Timeline timeline = timelines[i]; - MixDirection direction = MixDirection.Out; - MixBlend timelineBlend; - float alpha; - switch (timelineMode[i]) { - case AnimationState.Subsequent: - if (!drawOrder && timeline is DrawOrderTimeline) continue; - timelineBlend = blend; - alpha = alphaMix; - break; - case AnimationState.First: - timelineBlend = MixBlend.Setup; - alpha = alphaMix; - break; - case AnimationState.HoldSubsequent: - timelineBlend = blend; - alpha = alphaHold; - break; - case AnimationState.HoldFirst: - timelineBlend = MixBlend.Setup; - alpha = alphaHold; - break; - default: // HoldMix - timelineBlend = MixBlend.Setup; - TrackEntry holdMix = timelineHoldMix[i]; - alpha = alphaHold * Math.Max(0, 1 - holdMix.mixTime / holdMix.mixDuration); - break; - } - from.totalAlpha += alpha; - var rotateTimeline = timeline as RotateTimeline; - if (rotateTimeline != null) { - ApplyRotateTimeline(rotateTimeline, skeleton, applyTime, alpha, timelineBlend, timelinesRotation, i << 1, - firstFrame); - } else if (timeline is AttachmentTimeline) { - ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, applyTime, timelineBlend, attachments); - } else { - if (drawOrder && timeline is DrawOrderTimeline && timelineBlend == MixBlend.Setup) - direction = MixDirection.In; - timeline.Apply(skeleton, animationLast, applyTime, events, alpha, timelineBlend, direction); - } - } - } - - if (to.mixDuration > 0) QueueEvents(from, animationTime); - this.events.Clear(false); - from.nextAnimationLast = animationTime; - from.nextTrackLast = from.trackTime; - - return mix; - } - - /// Version of only applying EventTimelines for lightweight off-screen updates. - // Note: This method is not part of the libgdx reference implementation. - private float ApplyMixingFromEventTimelinesOnly (TrackEntry to, Skeleton skeleton) { - TrackEntry from = to.mixingFrom; - if (from.mixingFrom != null) ApplyMixingFromEventTimelinesOnly(from, skeleton); - - float mix; - if (to.mixDuration == 0) { // Single frame mix to undo mixingFrom changes. - mix = 1; - } else { - mix = to.mixTime / to.mixDuration; - if (mix > 1) mix = 1; - } - - ExposedList eventBuffer = mix < from.eventThreshold ? this.events : null; - if (eventBuffer == null) return mix; - - float animationLast = from.animationLast, animationTime = from.AnimationTime; - int timelineCount = from.animation.timelines.Count; - Timeline[] timelines = from.animation.timelines.Items; - for (int i = 0; i < timelineCount; i++) { - Timeline timeline = timelines[i]; - if (timeline is EventTimeline) - timeline.Apply(skeleton, animationLast, animationTime, eventBuffer, 0, MixBlend.Setup, MixDirection.Out); - } - - if (to.mixDuration > 0) QueueEvents(from, animationTime); - this.events.Clear(false); - from.nextAnimationLast = animationTime; - from.nextTrackLast = from.trackTime; - - return mix; - } - - /// Applies the attachment timeline and sets . - /// False when: 1) the attachment timeline is mixing out, 2) mix < attachmentThreshold, and 3) the timeline - /// is not the last timeline to set the slot's attachment. In that case the timeline is applied only so subsequent - /// timelines see any deform. - private void ApplyAttachmentTimeline (AttachmentTimeline timeline, Skeleton skeleton, float time, MixBlend blend, - bool attachments) { - - Slot slot = skeleton.slots.Items[timeline.SlotIndex]; - if (!slot.bone.active) return; - - float[] frames = timeline.frames; - if (time < frames[0]) { // Time is before first frame. - if (blend == MixBlend.Setup || blend == MixBlend.First) - SetAttachment(skeleton, slot, slot.data.attachmentName, attachments); - } else - SetAttachment(skeleton, slot, timeline.AttachmentNames[Timeline.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.attachmentState <= unkeyedState) slot.attachmentState = unkeyedState + Setup; - } - - private void SetAttachment (Skeleton skeleton, Slot slot, String attachmentName, bool attachments) { - slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slot.data.index, attachmentName); - if (attachments) slot.attachmentState = unkeyedState + Current; - } - - /// - /// Applies the rotate timeline, mixing with the current pose while keeping the same rotation direction chosen as the shortest - /// the first time the mixing was applied. - static private void ApplyRotateTimeline (RotateTimeline timeline, Skeleton skeleton, float time, float alpha, MixBlend blend, - float[] timelinesRotation, int i, bool firstFrame) { - - if (firstFrame) timelinesRotation[i] = 0; - - if (alpha == 1) { - timeline.Apply(skeleton, 0, time, null, 1, blend, MixDirection.In); - return; - } - - Bone bone = skeleton.bones.Items[timeline.BoneIndex]; - if (!bone.active) return; - - float[] frames = timeline.frames; - float r1, r2; - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case MixBlend.Setup: - bone.rotation = bone.data.rotation; - goto default; // Fall through. - default: - return; - case MixBlend.First: - r1 = bone.rotation; - r2 = bone.data.rotation; - break; - } - } else { - r1 = blend == MixBlend.Setup ? bone.data.rotation : bone.rotation; - r2 = bone.data.rotation + timeline.GetCurveValue(time); - } - - // Mix between rotations using the direction of the shortest route on the first frame. - float total, diff = r2 - r1; - diff -= (16384 - (int)(16384.499999999996 - diff / 360)) * 360; - if (diff == 0) { - total = timelinesRotation[i]; - } else { - float lastTotal, lastDiff; - if (firstFrame) { - lastTotal = 0; - lastDiff = diff; - } else { - lastTotal = timelinesRotation[i]; // Angle and direction of mix, including loops. - lastDiff = timelinesRotation[i + 1]; // Difference between bones. - } - bool current = diff > 0, dir = lastTotal >= 0; - // Detect cross at 0 (not 180). - if (Math.Sign(lastDiff) != Math.Sign(diff) && Math.Abs(lastDiff) <= 90) { - // A cross after a 360 rotation is a loop. - if (Math.Abs(lastTotal) > 180) lastTotal += 360 * Math.Sign(lastTotal); - dir = current; - } - total = diff + lastTotal - lastTotal % 360; // Store loops as part of lastTotal. - if (dir != current) total += 360 * Math.Sign(lastTotal); - timelinesRotation[i] = total; - } - timelinesRotation[i + 1] = diff; - bone.rotation = r1 + total * alpha; - } - - private void QueueEvents (TrackEntry entry, float animationTime) { - float animationStart = entry.animationStart, animationEnd = entry.animationEnd; - float duration = animationEnd - animationStart; - float trackLastWrapped = entry.trackLast % duration; - - // Queue events before complete. - Event[] eventsItems = this.events.Items; - int i = 0, n = events.Count; - for (; i < n; i++) { - Event e = eventsItems[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) - complete = duration == 0 || (trackLastWrapped > entry.trackTime % duration); - else - complete = animationTime >= animationEnd && entry.animationLast < animationEnd; - if (complete) queue.Complete(entry); - - // Queue events after complete. - for (; i < n; i++) { - Event e = eventsItems[i]; - if (e.time < animationStart) continue; // Discard events outside animation start/end. - queue.Event(entry, eventsItems[i]); - } - } - - /// - /// Removes all animations from all tracks, leaving skeletons in their current pose. - /// - /// It may be desired to use to mix the skeletons back to the setup pose, - /// rather than leaving them in their current pose. - /// - public void ClearTracks () { - bool oldDrainDisabled = queue.drainDisabled; - queue.drainDisabled = true; - for (int i = 0, n = tracks.Count; i < n; i++) { - ClearTrack(i); - } - tracks.Clear(); - queue.drainDisabled = oldDrainDisabled; - queue.Drain(); - } - - /// - /// Removes all animations from the track, leaving skeletons in their current pose. - /// - /// It may be desired to use to mix the skeletons back to the setup pose, - /// rather than leaving them in their current pose. - /// - public void ClearTrack (int trackIndex) { - if (trackIndex >= tracks.Count) return; - TrackEntry current = tracks.Items[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.Items[current.trackIndex] = null; - - queue.Drain(); - } - - /// Sets the active TrackEntry for a given track number. - private void SetCurrent (int index, TrackEntry current, bool interrupt) { - TrackEntry from = ExpandToIndex(index); - tracks.Items[index] = current; - current.previous = null; - - if (from != null) { - if (interrupt) queue.Interrupt(from); - current.mixingFrom = from; - from.mixingTo = current; - current.mixTime = 0; - - // Store the interrupted mix percentage. - if (from.mixingFrom != null && from.mixDuration > 0) - current.interruptAlpha *= Math.Min(1, from.mixTime / from.mixDuration); - - from.timelinesRotation.Clear(); // Reset rotation for mixing out, in case entry was mixed in. - } - - queue.Start(current); // triggers AnimationsChanged - } - - /// Sets an animation by name. - public TrackEntry SetAnimation (int trackIndex, string animationName, bool loop) { - Animation animation = data.skeletonData.FindAnimation(animationName); - if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName"); - return SetAnimation(trackIndex, animation, loop); - } - - /// Sets the current animation for a track, discarding any queued animations. If the formerly current track entry was never - /// applied to a skeleton, it is replaced (not mixed from). - /// 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 determines when the track is cleared. - /// A track entry to allow further customization of animation playback. References to the track entry must not be kept - /// after the event occurs. - public TrackEntry SetAnimation (int trackIndex, Animation animation, bool loop) { - if (animation == null) throw new ArgumentNullException("animation", "animation cannot be 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.Items[trackIndex] = current.mixingFrom; - queue.Interrupt(current); - queue.End(current); - ClearNext(current); - current = current.mixingFrom; - interrupt = false; // mixingFrom is current again, but don't interrupt it twice. - } else - ClearNext(current); - } - TrackEntry entry = NewTrackEntry(trackIndex, animation, loop, current); - SetCurrent(trackIndex, entry, interrupt); - queue.Drain(); - return entry; - } - - /// Queues an animation by name. - /// - public TrackEntry AddAnimation (int trackIndex, string animationName, bool loop, float delay) { - Animation animation = data.skeletonData.FindAnimation(animationName); - if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName"); - return AddAnimation(trackIndex, animation, loop, delay); - } - - /// Adds an animation to be played after the current or last queued animation for a track. If the track is empty, it is - /// equivalent to calling . - /// - /// If > 0, sets . If <= 0, the delay set is the duration of the previous track entry - /// minus any mix duration (from the plus the specified Delay (ie the mix - /// ends at (Delay = 0) or before (Delay < 0) the previous track entry duration). If the - /// previous entry is looping, its next loop completion is used instead of its duration. - /// - /// A track entry to allow further customization of animation playback. References to the track entry must not be kept - /// after the event occurs. - public TrackEntry AddAnimation (int trackIndex, Animation animation, bool loop, float delay) { - if (animation == null) throw new ArgumentNullException("animation", "animation cannot be 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(); - } else { - last.next = entry; - entry.previous = last; - if (delay <= 0) delay += last.TrackComplete - entry.mixDuration; - } - - entry.delay = delay; - return entry; - } - - /// - /// Sets an empty animation for a track, discarding any queued animations, and sets the track entry's - /// . An empty animation has no timelines and serves as a placeholder for mixing in or out. - /// - /// Mixing out is done by setting an empty animation with a mix duration using either , - /// , or . Mixing to an empty animation causes - /// the previous animation to be applied less and less over the mix duration. Properties keyed in the previous animation - /// transition to the value from lower tracks or to the setup pose value if no lower tracks key the property. A mix duration of - /// 0 still mixes out over one frame. - /// - /// Mixing in is done by first setting an empty animation, then adding an animation using - /// with the desired delay (an empty animation has a duration of 0) and on - /// the returned track entry, set the . Mixing from an empty animation causes the new - /// animation to be applied more and more over the mix duration. Properties keyed in the new animation transition from the value - /// from lower tracks or from the setup pose value if no lower tracks key the property to the value keyed in the new - /// animation. - public TrackEntry SetEmptyAnimation (int trackIndex, float mixDuration) { - TrackEntry entry = SetAnimation(trackIndex, AnimationState.EmptyAnimation, false); - entry.mixDuration = mixDuration; - entry.trackEnd = mixDuration; - return entry; - } - - /// - /// Adds an empty animation to be played after the current or last queued animation for a track, and sets the track entry's - /// . If the track is empty, it is equivalent to calling - /// . - /// - /// Track number. - /// Mix duration. - /// If > 0, sets . If <= 0, the delay set is the duration of the previous track entry - /// minus any mix duration plus the specified Delay (ie the mix ends at (Delay = 0) or - /// before (Delay < 0) the previous track entry duration). If the previous entry is looping, its next - /// loop completion is used instead of its duration. - /// A track entry to allow further customization of animation playback. References to the track entry must not be kept - /// after the event occurs. - /// - public TrackEntry AddEmptyAnimation (int trackIndex, float mixDuration, float delay) { - TrackEntry entry = AddAnimation(trackIndex, AnimationState.EmptyAnimation, false, delay); - if (delay <= 0) entry.delay += entry.mixDuration - mixDuration; - entry.mixDuration = mixDuration; - entry.trackEnd = mixDuration; - return entry; - } - - /// - /// Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix - /// duration. - public void SetEmptyAnimations (float mixDuration) { - bool oldDrainDisabled = queue.drainDisabled; - queue.drainDisabled = true; - TrackEntry[] tracksItems = tracks.Items; - for (int i = 0, n = tracks.Count; i < n; i++) { - TrackEntry current = tracksItems[i]; - if (current != null) SetEmptyAnimation(current.trackIndex, mixDuration); - } - queue.drainDisabled = oldDrainDisabled; - queue.Drain(); - } - - private TrackEntry ExpandToIndex (int index) { - if (index < tracks.Count) return tracks.Items[index]; - tracks.Resize(index + 1); - return null; - } - - /// Object-pooling version of new TrackEntry. Obtain an unused TrackEntry from the pool and clear/initialize its values. - /// May be null. - private TrackEntry NewTrackEntry (int trackIndex, Animation animation, bool loop, TrackEntry last) { - TrackEntry entry = trackEntryPool.Obtain(); - entry.trackIndex = trackIndex; - entry.animation = animation; - entry.loop = loop; - entry.holdPrevious = false; - - entry.eventThreshold = 0; - entry.attachmentThreshold = 0; - entry.drawOrderThreshold = 0; - - entry.animationStart = 0; - entry.animationEnd = animation.Duration; - entry.animationLast = -1; - entry.nextAnimationLast = -1; - - entry.delay = 0; - entry.trackTime = 0; - entry.trackLast = -1; - entry.nextTrackLast = -1; - entry.trackEnd = float.MaxValue; - entry.timeScale = 1; - - entry.alpha = 1; - entry.interruptAlpha = 1; - entry.mixTime = 0; - entry.mixDuration = last == null ? 0 : data.GetMix(last.animation, animation); - entry.mixBlend = MixBlend.Replace; - return entry; - } - - /// Removes the next entry and all entries after it for the specified entry. - public void ClearNext (TrackEntry entry) { - TrackEntry next = entry.next; - while (next != null) { - queue.Dispose(next); - next = next.next; - } - entry.next = null; - } - - private void AnimationsChanged () { - animationsChanged = false; - - // Process in the order that animations are applied. - propertyIds.Clear(); - int n = tracks.Count; - TrackEntry[] tracksItems = tracks.Items; - for (int i = 0; i < n; i++) { - TrackEntry entry = tracksItems[i]; - if (entry == null) continue; - while (entry.mixingFrom != null) // Move to last entry, then iterate in reverse. - entry = entry.mixingFrom; - do { - if (entry.mixingTo == null || entry.mixBlend != MixBlend.Add) ComputeHold(entry); - entry = entry.mixingTo; - } while (entry != null); - } - } - - private void ComputeHold (TrackEntry entry) { - TrackEntry to = entry.mixingTo; - Timeline[] timelines = entry.animation.timelines.Items; - int timelinesCount = entry.animation.timelines.Count; - int[] timelineMode = entry.timelineMode.Resize(timelinesCount).Items; - entry.timelineHoldMix.Clear(); - TrackEntry[] timelineHoldMix = entry.timelineHoldMix.Resize(timelinesCount).Items; - HashSet propertyIds = this.propertyIds; - - if (to != null && to.holdPrevious) { - for (int i = 0; i < timelinesCount; i++) - timelineMode[i] = propertyIds.AddAll(timelines[i].PropertyIds) ? AnimationState.HoldFirst : AnimationState.HoldSubsequent; - - return; - } - - // outer: - for (int i = 0; i < timelinesCount; i++) { - Timeline timeline = timelines[i]; - String[] ids = timeline.PropertyIds; - if (!propertyIds.AddAll(ids)) - timelineMode[i] = AnimationState.Subsequent; - else if (to == null || timeline is AttachmentTimeline || timeline is DrawOrderTimeline - || timeline is EventTimeline || !to.animation.HasTimeline(ids)) { - timelineMode[i] = AnimationState.First; - } else { - for (TrackEntry next = to.mixingTo; next != null; next = next.mixingTo) { - if (next.animation.HasTimeline(ids)) continue; - if (next.mixDuration > 0) { - timelineMode[i] = AnimationState.HoldMix; - timelineHoldMix[i] = next; - goto continue_outer; // continue outer; - } - break; - } - timelineMode[i] = AnimationState.HoldFirst; - } - continue_outer: { } - } - } - - /// The track entry for the animation currently playing on the track, or null if no animation is currently playing. - public TrackEntry GetCurrent (int trackIndex) { - if (trackIndex >= tracks.Count) return null; - return tracks.Items[trackIndex]; - } - - /// Discards all listener notifications that have not yet been delivered. This can be useful to call from an - /// AnimationState event subscriber when it is known that further notifications that may have been already queued for delivery - /// are not wanted because new animations are being set. - public void ClearListenerNotifications () { - queue.Clear(); - } - - /// - /// Multiplier for the delta time when the animation state is updated, causing time for all animations and mixes to play slower - /// or faster. Defaults to 1. - /// - /// See TrackEntry for affecting a single animation. - /// - public float TimeScale { get { return timeScale; } set { timeScale = value; } } - - /// The AnimationStateData to look up mix durations. - public AnimationStateData Data { - get { - return data; - } - set { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - this.data = value; - } - } - - /// A list of tracks that have animations, which may contain nulls. - public ExposedList Tracks { get { return tracks; } } - - override public string ToString () { - var buffer = new System.Text.StringBuilder(); - TrackEntry[] tracksItems = tracks.Items; - for (int i = 0, n = tracks.Count; i < n; i++) { - TrackEntry entry = tracksItems[i]; - if (entry == null) continue; - if (buffer.Length > 0) buffer.Append(", "); - buffer.Append(entry.ToString()); - } - if (buffer.Length == 0) return ""; - return buffer.ToString(); - } - } - - /// - /// - /// Stores settings and other state for the playback of an animation on an track. - /// - /// References to a track entry must not be kept after the event occurs. - /// - public class TrackEntry : Pool.IPoolable { - internal Animation animation; - - internal TrackEntry previous, next, mixingFrom, mixingTo; - // difference to libgdx reference: delegates are used for event callbacks instead of 'AnimationStateListener listener'. - /// See - /// API Reference documentation pages here for details. Usage in C# and spine-unity is explained - /// here - /// on the spine-unity documentation pages. - public event AnimationState.TrackEntryDelegate Start, Interrupt, End, Dispose, Complete; - public event AnimationState.TrackEntryEventDelegate Event; - internal void OnStart () { if (Start != null) Start(this); } - internal void OnInterrupt () { if (Interrupt != null) Interrupt(this); } - internal void OnEnd () { if (End != null) End(this); } - internal void OnDispose () { if (Dispose != null) Dispose(this); } - internal void OnComplete () { if (Complete != null) Complete(this); } - internal void OnEvent (Event e) { if (Event != null) Event(this, e); } - - internal int trackIndex; - - internal bool loop, holdPrevious, reverse; - internal float eventThreshold, attachmentThreshold, drawOrderThreshold; - internal float animationStart, animationEnd, animationLast, nextAnimationLast; - internal float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale = 1f; - internal float alpha, mixTime, mixDuration, interruptAlpha, totalAlpha; - internal MixBlend mixBlend = MixBlend.Replace; - internal readonly ExposedList timelineMode = new ExposedList(); - internal readonly ExposedList timelineHoldMix = new ExposedList(); - internal readonly ExposedList timelinesRotation = new ExposedList(); - - // IPoolable.Reset() - public void Reset () { - previous = null; - next = null; - mixingFrom = null; - mixingTo = null; - animation = null; - // replaces 'listener = null;' since delegates are used for event callbacks - Start = null; - Interrupt = null; - End = null; - Dispose = null; - Complete = null; - Event = null; - timelineMode.Clear(); - timelineHoldMix.Clear(); - timelinesRotation.Clear(); - } - - /// The index of the track where this entry is either current or queued. - /// - public int TrackIndex { get { return trackIndex; } } - - /// The animation to apply for this track entry. - public Animation Animation { get { return animation; } } - - /// - /// If true, the animation will repeat. If false it will not, instead its last frame is applied if played beyond its - /// duration. - public bool Loop { get { return loop; } set { loop = value; } } - - /// - /// - /// Seconds to postpone playing the animation. When this track entry is the current track entry, Delay - /// postpones incrementing the . When this track entry is queued, Delay is the time from - /// the start of the previous animation to when this track entry will become the current track entry (ie when the previous - /// track entry >= this track entry's Delay). - /// - /// affects the delay. - /// - /// When using with a delay <= 0, the delay - /// is set using the mix duration from the . If is set afterward, the delay - /// may need to be adjusted. - public float Delay { get { return delay; } set { delay = value; } } - - /// - /// Current time in seconds this track entry has been the current track entry. The track time determines - /// . The track time can be set to start the animation at a time other than 0, without affecting - /// looping. - public float TrackTime { get { return trackTime; } set { trackTime = value; } } - - /// - /// - /// The track time in seconds when this animation will be removed from the track. Defaults to the highest possible float - /// value, meaning the animation will be applied until a new animation is set or the track is cleared. If the track end time - /// is reached, no other animations are queued for playback, and mixing from any previous animations is complete, then the - /// properties keyed by the animation are set to the setup pose and the track is cleared. - /// - /// It may be desired to use rather than have the animation - /// abruptly cease being applied. - /// - public float TrackEnd { get { return trackEnd; } set { trackEnd = value; } } - - /// - /// If this track entry is non-looping, the track time in seconds when is reached, or the current - /// if it has already been reached. If this track entry is looping, the track time when this - /// animation will reach its next (the next loop completion). - public float TrackComplete { - get { - 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. - } - } - - /// - /// - /// Seconds when this animation starts, both initially and after looping. Defaults to 0. - /// - /// When changing the AnimationStart time, it often makes sense to set to the same - /// value to prevent timeline keys before the start time from triggering. - /// - public float AnimationStart { get { return animationStart; } set { animationStart = value; } } - - /// - /// Seconds for the last frame of this animation. Non-looping animations won't play past this time. Looping animations will - /// loop back to at this time. Defaults to the animation . - /// - public float AnimationEnd { get { return animationEnd; } set { animationEnd = value; } } - - /// - /// 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 AnimationLast time (exclusive) and - /// AnimationTime (inclusive). Defaults to -1 to ensure triggers on frame 0 happen the first time this animation - /// is applied. - public float AnimationLast { - get { return animationLast; } - set { - animationLast = value; - nextAnimationLast = value; - } - } - - /// - /// Uses to compute the AnimationTime, which is between - /// and . When the TrackTime is 0, the AnimationTime is equal to the - /// AnimationStart time. - /// - public float AnimationTime { - get { - if (loop) { - float duration = animationEnd - animationStart; - if (duration == 0) return animationStart; - return (trackTime % duration) + animationStart; - } - return Math.Min(trackTime + animationStart, animationEnd); - } - } - - /// - /// - /// Multiplier for the delta time when this track entry is updated, causing time for this animation to pass slower or - /// faster. Defaults to 1. - /// - /// Values < 0 are not supported. To play an animation in reverse, use . - /// - /// is not affected by track entry time scale, so may need to be adjusted to - /// match the animation speed. - /// - /// When using with a Delay <= 0, the - /// is set using the mix duration from the , assuming time scale to be 1. If - /// the time scale is not 1, the delay may need to be adjusted. - /// - /// See AnimationState for affecting all animations. - /// - public float TimeScale { get { return timeScale; } set { timeScale = value; } } - - /// - /// - /// Values < 1 mix this animation with the skeleton's current pose (usually the pose resulting from lower tracks). Defaults - /// to 1, which overwrites the skeleton's current pose with this animation. - /// - /// Typically track 0 is used to completely pose the skeleton, then alpha is 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. - /// - public float Alpha { get { return alpha; } set { alpha = value; } } - - /// - /// When the mix percentage ( / ) is less than the - /// EventThreshold, event timelines are applied while this animation is being mixed out. Defaults to 0, so event - /// timelines are not applied while this animation is being mixed out. - /// - public float EventThreshold { get { return eventThreshold; } set { eventThreshold = value; } } - - /// - /// When the mix percentage ( / ) is less than the - /// AttachmentThreshold, attachment timelines are applied while this animation is being mixed out. Defaults to - /// 0, so attachment timelines are not applied while this animation is being mixed out. - /// - public float AttachmentThreshold { get { return attachmentThreshold; } set { attachmentThreshold = value; } } - - /// - /// When the mix percentage ( / ) is less than the - /// DrawOrderThreshold, draw order timelines are applied while this animation is being mixed out. Defaults to 0, - /// so draw order timelines are not applied while this animation is being mixed out. - /// - public float DrawOrderThreshold { get { return drawOrderThreshold; } set { drawOrderThreshold = value; } } - - /// - /// The animation queued to start after this animation, or null if there is none. next makes up a doubly linked - /// list. - /// - /// See to truncate the list. - public TrackEntry Next { get { return next; } } - - /// - /// The animation queued to play before this animation, or null. previous makes up a doubly linked list. - public TrackEntry Previous { get { return previous; } } - - /// - /// Returns true if at least one loop has been completed. - /// - public bool IsComplete { - get { return trackTime >= animationEnd - animationStart; } - } - - /// - /// Seconds from 0 to the when mixing from the previous animation to this animation. May be - /// slightly more than MixDuration when the mix is complete. - public float MixTime { get { return mixTime; } set { mixTime = value; } } - - /// - /// - /// 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 MixDuration can be set manually rather than use the value from - /// . In that case, the MixDuration can be set for a new - /// track entry only before is first called. - /// - /// When using with a Delay <= 0, the - /// is set using the mix duration from the . If mixDuration is set - /// afterward, the delay may need to be adjusted. For example: - /// entry.Delay = entry.previous.TrackComplete - entry.MixDuration; - /// - public float MixDuration { get { return mixDuration; } set { mixDuration = value; } } - - /// - /// - /// Controls how properties keyed in the animation are mixed with lower tracks. Defaults to . - /// - /// Track entries on track 0 ignore this setting and always use . - /// - /// The MixBlend can be set for a new track entry only before is first - /// called. - /// - public MixBlend MixBlend { get { return mixBlend; } set { mixBlend = value; } } - - /// - /// 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 linked list. - public TrackEntry MixingFrom { get { return mixingFrom; } } - - /// - /// The track entry for the next animation when mixing from this animation to the next animation, or null if no mixing is - /// currently occuring. When mixing to multiple animations, MixingTo makes up a linked list. - public TrackEntry MixingTo { get { return mixingTo; } } - - /// - /// - /// 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. - /// - public bool HoldPrevious { get { return holdPrevious; } set { holdPrevious = value; } } - - /// - /// If true, the animation will be applied in reverse. Events are not fired when an animation is applied in reverse. - public bool Reverse { get { return reverse; } set { reverse = value; } } - - /// - /// - /// 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 and starting animations on other tracks. - /// - /// Mixing with 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. - /// - public void ResetRotationDirections () { - timelinesRotation.Clear(); - } - - override public string ToString () { - return animation == null ? "" : animation.name; - } - } - - class EventQueue { - private readonly List eventQueueEntries = new List(); - internal bool drainDisabled; - - private readonly AnimationState state; - private readonly Pool trackEntryPool; - internal event Action AnimationsChanged; - - internal EventQueue (AnimationState state, Action HandleAnimationsChanged, Pool trackEntryPool) { - this.state = state; - this.AnimationsChanged += HandleAnimationsChanged; - this.trackEntryPool = trackEntryPool; - } - - internal void Start (TrackEntry entry) { - eventQueueEntries.Add(new EventQueueEntry(EventType.Start, entry)); - if (AnimationsChanged != null) AnimationsChanged(); - } - - internal void Interrupt (TrackEntry entry) { - eventQueueEntries.Add(new EventQueueEntry(EventType.Interrupt, entry)); - } - - internal void End (TrackEntry entry) { - eventQueueEntries.Add(new EventQueueEntry(EventType.End, entry)); - if (AnimationsChanged != null) AnimationsChanged(); - } - - internal void Dispose (TrackEntry entry) { - eventQueueEntries.Add(new EventQueueEntry(EventType.Dispose, entry)); - } - - internal void Complete (TrackEntry entry) { - eventQueueEntries.Add(new EventQueueEntry(EventType.Complete, entry)); - } - - internal void Event (TrackEntry entry, Event e) { - eventQueueEntries.Add(new EventQueueEntry(EventType.Event, entry, e)); - } - - /// Raises all events in the queue and drains the queue. - internal void Drain () { - if (drainDisabled) return; - drainDisabled = true; - - List eventQueueEntries = this.eventQueueEntries; - AnimationState state = this.state; - - // Don't cache eventQueueEntries.Count so callbacks can queue their own events (eg, call SetAnimation in AnimationState_Complete). - for (int i = 0; i < eventQueueEntries.Count; i++) { - EventQueueEntry queueEntry = eventQueueEntries[i]; - TrackEntry trackEntry = queueEntry.entry; - - switch (queueEntry.type) { - case EventType.Start: - trackEntry.OnStart(); - state.OnStart(trackEntry); - break; - case EventType.Interrupt: - trackEntry.OnInterrupt(); - state.OnInterrupt(trackEntry); - break; - case EventType.End: - trackEntry.OnEnd(); - state.OnEnd(trackEntry); - goto case EventType.Dispose; // Fall through. (C#) - case EventType.Dispose: - trackEntry.OnDispose(); - state.OnDispose(trackEntry); - trackEntryPool.Free(trackEntry); - break; - case EventType.Complete: - trackEntry.OnComplete(); - state.OnComplete(trackEntry); - break; - case EventType.Event: - trackEntry.OnEvent(queueEntry.e); - state.OnEvent(trackEntry, queueEntry.e); - break; - } - } - eventQueueEntries.Clear(); - - drainDisabled = false; - } - - internal void Clear () { - eventQueueEntries.Clear(); - } - - struct EventQueueEntry { - public EventType type; - public TrackEntry entry; - public Event e; - - public EventQueueEntry (EventType eventType, TrackEntry trackEntry, Event e = null) { - this.type = eventType; - this.entry = trackEntry; - this.e = e; - } - } - - enum EventType { - Start, Interrupt, End, Dispose, Complete, Event - } - } - - class Pool where T : class, new() { - public readonly int max; - readonly Stack freeObjects; - - public int Count { get { return freeObjects.Count; } } - public int Peak { get; private set; } - - public Pool (int initialCapacity = 16, int max = int.MaxValue) { - freeObjects = new Stack(initialCapacity); - this.max = max; - } - - public T Obtain () { - return freeObjects.Count == 0 ? new T() : freeObjects.Pop(); - } - - public void Free (T obj) { - if (obj == null) throw new ArgumentNullException("obj", "obj cannot be null"); - if (freeObjects.Count < max) { - freeObjects.Push(obj); - Peak = Math.Max(Peak, freeObjects.Count); - } - Reset(obj); - } - - public void Clear () { - freeObjects.Clear(); - } - - protected void Reset (T obj) { - var poolable = obj as IPoolable; - if (poolable != null) poolable.Reset(); - } - - public interface IPoolable { - void Reset (); - } - } - - public static class HashSetExtensions { - public static bool AddAll (this HashSet set, T[] addSet) { - bool anyItemAdded = false; - for (int i = 0, n = addSet.Length; i < n; ++i) { - T item = addSet[i]; - anyItemAdded |= set.Add(item); - } - return anyItemAdded; - } - } +namespace Spine4_0_31 +{ + + /// + /// + /// Applies animations over time, queues animations for later playback, mixes (crossfading) between animations, and applies + /// multiple animations on top of each other (layering). + /// + /// See Applying Animations in the Spine Runtimes Guide. + /// + public class AnimationState + { + static readonly Animation EmptyAnimation = new Animation("", new ExposedList(), 0); + + /// 1) A previously applied timeline has set this property. + /// Result: Mix from the current pose to the timeline pose. + internal const int Subsequent = 0; + /// 1) This is the first timeline to set this property. + /// 2) The next track entry applied after this one does not have a timeline to set this property. + /// Result: Mix from the setup pose to the timeline pose. + internal const int First = 1; + /// 1) A previously applied timeline has set this property.
+ /// 2) The next track entry to be applied does have a timeline to set this property.
+ /// 3) The next track entry after that one does not have a timeline to set this property.
+ /// Result: Mix from the current pose to the timeline pose, but do not mix out. This avoids "dipping" when crossfading + /// animations that key the same property. A subsequent timeline will set this property using a mix. + internal const int HoldSubsequent = 2; + /// 1) This is the first timeline to set this property. + /// 2) The next track entry to be applied does have a timeline to set this property. + /// 3) The next track entry after that one does not have a timeline to set this property. + /// Result: Mix from the setup pose to the timeline pose, but do not mix out. This avoids "dipping" when crossfading animations + /// that key the same property. A subsequent timeline will set this property using a mix. + internal const int HoldFirst = 3; + /// 1) This is the first timeline to set this property. + /// 2) The next track entry to be applied does have a timeline to set this property. + /// 3) The next track entry after that one does have a timeline to set this property. + /// 4) timelineHoldMix stores the first subsequent track entry that does not have a timeline to set this property. + /// Result: The same as HOLD except the mix percentage from the timelineHoldMix track entry is used. This handles when more than + /// 2 track entries in a row have a timeline that sets the same property. + /// Eg, A -> B -> C -> D where A, B, and C have a timeline setting same property, but D does not. When A is applied, to avoid + /// "dipping" A is not mixed out, however D (the first entry that doesn't set the property) mixing in is used to mix out A + /// (which affects B and C). Without using D to mix out, A would be applied fully until mixing completes, then snap to the mixed + /// out position. + internal const int HoldMix = 4; + + internal const int Setup = 1, Current = 2; + + protected AnimationStateData data; + private readonly ExposedList tracks = new ExposedList(); + private readonly ExposedList events = new ExposedList(); + // difference to libgdx reference: delegates are used for event callbacks instead of 'final SnapshotArray listeners'. + internal void OnStart(TrackEntry entry) { if (Start != null) Start(entry); } + internal void OnInterrupt(TrackEntry entry) { if (Interrupt != null) Interrupt(entry); } + internal void OnEnd(TrackEntry entry) { if (End != null) End(entry); } + internal void OnDispose(TrackEntry entry) { if (Dispose != null) Dispose(entry); } + internal void OnComplete(TrackEntry entry) { if (Complete != null) Complete(entry); } + internal void OnEvent(TrackEntry entry, Event e) { if (Event != null) Event(entry, e); } + + public delegate void TrackEntryDelegate(TrackEntry trackEntry); + /// See + /// API Reference documentation pages here for details. Usage in C# and spine-unity is explained + /// here + /// on the spine-unity documentation pages. + public event TrackEntryDelegate Start, Interrupt, End, Dispose, Complete; + + public delegate void TrackEntryEventDelegate(TrackEntry trackEntry, Event e); + public event TrackEntryEventDelegate Event; + + public void AssignEventSubscribersFrom(AnimationState src) + { + Event = src.Event; + Start = src.Start; + Interrupt = src.Interrupt; + End = src.End; + Dispose = src.Dispose; + Complete = src.Complete; + } + + public void AddEventSubscribersFrom(AnimationState src) + { + Event += src.Event; + Start += src.Start; + Interrupt += src.Interrupt; + End += src.End; + Dispose += src.Dispose; + Complete += src.Complete; + } + + // end of difference + private readonly EventQueue queue; // Initialized by constructor. + private readonly HashSet propertyIds = new HashSet(); + private bool animationsChanged; + private float timeScale = 1; + private int unkeyedState; + + private readonly Pool trackEntryPool = new Pool(); + + public AnimationState(AnimationStateData data) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + this.data = data; + this.queue = new EventQueue( + this, + delegate { this.animationsChanged = true; }, + trackEntryPool + ); + } + + /// + /// Increments the track entry , setting queued animations as current if needed. + /// delta time + public void Update(float delta) + { + delta *= timeScale; + TrackEntry[] tracksItems = tracks.Items; + for (int i = 0, n = tracks.Count; i < n; i++) + { + TrackEntry current = tracksItems[i]; + if (current == null) continue; + + 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. + tracksItems[i] = null; + queue.End(current); + ClearNext(current); + continue; + } + if (current.mixingFrom != null && UpdateMixingFrom(current, 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(); + } + + /// Returns true when all mixing from entries are complete. + private bool 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; + + // Require mixTime > 0 to ensure the mixing from entry was applied at least once. + if (to.mixTime > 0 && to.mixTime >= to.mixDuration) + { + // Require totalAlpha == 0 to ensure mixing is complete, unless mixDuration == 0 (the transition is a single frame). + if (from.totalAlpha == 0 || to.mixDuration == 0) + { + to.mixingFrom = from.mixingFrom; + if (from.mixingFrom != null) from.mixingFrom.mixingTo = to; + to.interruptAlpha = from.interruptAlpha; + queue.End(from); + } + return finished; + } + + from.trackTime += delta * from.timeScale; + to.mixTime += delta; + return false; + } + + /// + /// Poses the skeleton using the track entry animations. The animation state is not changed, so can be applied to multiple + /// skeletons to pose them identically. + /// True if any animations were applied. + public bool Apply(Skeleton skeleton) + { + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + if (animationsChanged) AnimationsChanged(); + + ExposedList events = this.events; + bool applied = false; + TrackEntry[] tracksItems = tracks.Items; + for (int i = 0, n = tracks.Count; i < n; i++) + { + TrackEntry current = tracksItems[i]; + if (current == null || current.delay > 0) continue; + applied = true; + + // Track 0 animations aren't for layering, so do not show the previously applied animations before the first key. + MixBlend blend = i == 0 ? MixBlend.First : current.mixBlend; + + // Apply mixing from entries first. + float mix = current.alpha; + if (current.mixingFrom != null) + mix *= ApplyMixingFrom(current, skeleton, blend); + else if (current.trackTime >= current.trackEnd && current.next == null) // + mix = 0; // Set to setup pose the last time the entry will be applied. + + // Apply current entry. + float animationLast = current.animationLast, animationTime = current.AnimationTime, applyTime = animationTime; + ExposedList applyEvents = events; + if (current.reverse) + { + applyTime = current.animation.duration - applyTime; + applyEvents = null; + } + + int timelineCount = current.animation.timelines.Count; + Timeline[] timelines = current.animation.timelines.Items; + if ((i == 0 && mix == 1) || blend == MixBlend.Add) + { + for (int ii = 0; ii < timelineCount; ii++) + { + Timeline timeline = timelines[ii]; + if (timeline is AttachmentTimeline) + ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, applyTime, blend, true); + else + timeline.Apply(skeleton, animationLast, applyTime, applyEvents, mix, blend, MixDirection.In); + } + } + else + { + int[] timelineMode = current.timelineMode.Items; + + bool firstFrame = current.timelinesRotation.Count != timelineCount << 1; + if (firstFrame) current.timelinesRotation.Resize(timelineCount << 1); + float[] timelinesRotation = current.timelinesRotation.Items; + + for (int ii = 0; ii < timelineCount; ii++) + { + Timeline timeline = timelines[ii]; + MixBlend timelineBlend = timelineMode[ii] == AnimationState.Subsequent ? blend : MixBlend.Setup; + var rotateTimeline = timeline as RotateTimeline; + if (rotateTimeline != null) + ApplyRotateTimeline(rotateTimeline, skeleton, applyTime, mix, timelineBlend, timelinesRotation, + ii << 1, firstFrame); + else if (timeline is AttachmentTimeline) + ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, applyTime, blend, true); + else + timeline.Apply(skeleton, animationLast, applyTime, applyEvents, mix, timelineBlend, MixDirection.In); + } + } + QueueEvents(current, animationTime); + events.Clear(false); + current.nextAnimationLast = animationTime; + current.nextTrackLast = current.trackTime; + } + + // Set slots attachments to the setup pose, if needed. This occurs if an animation that is mixing out sets attachments so + // subsequent timelines see any deform, but the subsequent timelines don't set an attachment (eg they are also mixing out or + // the time is before the first key). + int setupState = unkeyedState + Setup; + Slot[] slots = skeleton.slots.Items; + for (int i = 0, n = skeleton.slots.Count; i < n; i++) + { + Slot slot = slots[i]; + if (slot.attachmentState == setupState) + { + string attachmentName = slot.data.attachmentName; + slot.Attachment = (attachmentName == null ? null : skeleton.GetAttachment(slot.data.index, attachmentName)); + } + } + unkeyedState += 2; // Increasing after each use avoids the need to reset attachmentState for every slot. + + queue.Drain(); + return applied; + } + + /// Version of only applying EventTimelines for lightweight off-screen updates. + // Note: This method is not part of the libgdx reference implementation. + public bool ApplyEventTimelinesOnly(Skeleton skeleton) + { + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + + ExposedList events = this.events; + bool applied = false; + TrackEntry[] tracksItems = tracks.Items; + for (int i = 0, n = tracks.Count; i < n; i++) + { + TrackEntry current = tracksItems[i]; + if (current == null || current.delay > 0) continue; + applied = true; + + // Apply mixing from entries first. + if (current.mixingFrom != null) ApplyMixingFromEventTimelinesOnly(current, skeleton); + + // Apply current entry. + float animationLast = current.animationLast, animationTime = current.AnimationTime; + int timelineCount = current.animation.timelines.Count; + Timeline[] timelines = current.animation.timelines.Items; + for (int ii = 0; ii < timelineCount; ii++) + { + Timeline timeline = timelines[ii]; + if (timeline is EventTimeline) + timeline.Apply(skeleton, animationLast, animationTime, events, 1.0f, MixBlend.Setup, MixDirection.In); + } + QueueEvents(current, animationTime); + events.Clear(false); + current.nextAnimationLast = animationTime; + current.nextTrackLast = current.trackTime; + } + + queue.Drain(); + return applied; + } + + private float 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; // Tracks > 0 are transparent and can't reset to setup pose. + } + else + { + mix = to.mixTime / to.mixDuration; + if (mix > 1) mix = 1; + if (blend != MixBlend.First) blend = from.mixBlend; // Track 0 ignores track mix blend. + } + + bool attachments = mix < from.attachmentThreshold, drawOrder = mix < from.drawOrderThreshold; + int timelineCount = from.animation.timelines.Count; + Timeline[] timelines = from.animation.timelines.Items; + float alphaHold = from.alpha * to.interruptAlpha, alphaMix = alphaHold * (1 - mix); + float animationLast = from.animationLast, animationTime = from.AnimationTime, applyTime = animationTime; + ExposedList events = null; + if (from.reverse) + applyTime = from.animation.duration - applyTime; + else + { + if (mix < from.eventThreshold) events = this.events; + } + + if (blend == MixBlend.Add) + { + for (int i = 0; i < timelineCount; i++) + timelines[i].Apply(skeleton, animationLast, applyTime, events, alphaMix, blend, MixDirection.Out); + } + else + { + int[] timelineMode = from.timelineMode.Items; + TrackEntry[] timelineHoldMix = from.timelineHoldMix.Items; + + bool firstFrame = from.timelinesRotation.Count != timelineCount << 1; + if (firstFrame) from.timelinesRotation.Resize(timelineCount << 1); + float[] timelinesRotation = from.timelinesRotation.Items; + + from.totalAlpha = 0; + for (int i = 0; i < timelineCount; i++) + { + Timeline timeline = timelines[i]; + MixDirection direction = MixDirection.Out; + MixBlend timelineBlend; + float alpha; + switch (timelineMode[i]) + { + case AnimationState.Subsequent: + if (!drawOrder && timeline is DrawOrderTimeline) continue; + timelineBlend = blend; + alpha = alphaMix; + break; + case AnimationState.First: + timelineBlend = MixBlend.Setup; + alpha = alphaMix; + break; + case AnimationState.HoldSubsequent: + timelineBlend = blend; + alpha = alphaHold; + break; + case AnimationState.HoldFirst: + timelineBlend = MixBlend.Setup; + alpha = alphaHold; + break; + default: // HoldMix + timelineBlend = MixBlend.Setup; + TrackEntry holdMix = timelineHoldMix[i]; + alpha = alphaHold * Math.Max(0, 1 - holdMix.mixTime / holdMix.mixDuration); + break; + } + from.totalAlpha += alpha; + var rotateTimeline = timeline as RotateTimeline; + if (rotateTimeline != null) + { + ApplyRotateTimeline(rotateTimeline, skeleton, applyTime, alpha, timelineBlend, timelinesRotation, i << 1, + firstFrame); + } + else if (timeline is AttachmentTimeline) + { + ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, applyTime, timelineBlend, attachments); + } + else + { + if (drawOrder && timeline is DrawOrderTimeline && timelineBlend == MixBlend.Setup) + direction = MixDirection.In; + timeline.Apply(skeleton, animationLast, applyTime, events, alpha, timelineBlend, direction); + } + } + } + + if (to.mixDuration > 0) QueueEvents(from, animationTime); + this.events.Clear(false); + from.nextAnimationLast = animationTime; + from.nextTrackLast = from.trackTime; + + return mix; + } + + /// Version of only applying EventTimelines for lightweight off-screen updates. + // Note: This method is not part of the libgdx reference implementation. + private float ApplyMixingFromEventTimelinesOnly(TrackEntry to, Skeleton skeleton) + { + TrackEntry from = to.mixingFrom; + if (from.mixingFrom != null) ApplyMixingFromEventTimelinesOnly(from, skeleton); + + float mix; + if (to.mixDuration == 0) + { // Single frame mix to undo mixingFrom changes. + mix = 1; + } + else + { + mix = to.mixTime / to.mixDuration; + if (mix > 1) mix = 1; + } + + ExposedList eventBuffer = mix < from.eventThreshold ? this.events : null; + if (eventBuffer == null) return mix; + + float animationLast = from.animationLast, animationTime = from.AnimationTime; + int timelineCount = from.animation.timelines.Count; + Timeline[] timelines = from.animation.timelines.Items; + for (int i = 0; i < timelineCount; i++) + { + Timeline timeline = timelines[i]; + if (timeline is EventTimeline) + timeline.Apply(skeleton, animationLast, animationTime, eventBuffer, 0, MixBlend.Setup, MixDirection.Out); + } + + if (to.mixDuration > 0) QueueEvents(from, animationTime); + this.events.Clear(false); + from.nextAnimationLast = animationTime; + from.nextTrackLast = from.trackTime; + + return mix; + } + + /// Applies the attachment timeline and sets . + /// False when: 1) the attachment timeline is mixing out, 2) mix < attachmentThreshold, and 3) the timeline + /// is not the last timeline to set the slot's attachment. In that case the timeline is applied only so subsequent + /// timelines see any deform. + private void ApplyAttachmentTimeline(AttachmentTimeline timeline, Skeleton skeleton, float time, MixBlend blend, + bool attachments) + { + + Slot slot = skeleton.slots.Items[timeline.SlotIndex]; + if (!slot.bone.active) return; + + float[] frames = timeline.frames; + if (time < frames[0]) + { // Time is before first frame. + if (blend == MixBlend.Setup || blend == MixBlend.First) + SetAttachment(skeleton, slot, slot.data.attachmentName, attachments); + } + else + SetAttachment(skeleton, slot, timeline.AttachmentNames[Timeline.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.attachmentState <= unkeyedState) slot.attachmentState = unkeyedState + Setup; + } + + private void SetAttachment(Skeleton skeleton, Slot slot, String attachmentName, bool attachments) + { + slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slot.data.index, attachmentName); + if (attachments) slot.attachmentState = unkeyedState + Current; + } + + /// + /// Applies the rotate timeline, mixing with the current pose while keeping the same rotation direction chosen as the shortest + /// the first time the mixing was applied. + static private void ApplyRotateTimeline(RotateTimeline timeline, Skeleton skeleton, float time, float alpha, MixBlend blend, + float[] timelinesRotation, int i, bool firstFrame) + { + + if (firstFrame) timelinesRotation[i] = 0; + + if (alpha == 1) + { + timeline.Apply(skeleton, 0, time, null, 1, blend, MixDirection.In); + return; + } + + Bone bone = skeleton.bones.Items[timeline.BoneIndex]; + if (!bone.active) return; + + float[] frames = timeline.frames; + float r1, r2; + if (time < frames[0]) + { // Time is before first frame. + switch (blend) + { + case MixBlend.Setup: + bone.rotation = bone.data.rotation; + goto default; // Fall through. + default: + return; + case MixBlend.First: + r1 = bone.rotation; + r2 = bone.data.rotation; + break; + } + } + else + { + r1 = blend == MixBlend.Setup ? bone.data.rotation : bone.rotation; + r2 = bone.data.rotation + timeline.GetCurveValue(time); + } + + // Mix between rotations using the direction of the shortest route on the first frame. + float total, diff = r2 - r1; + diff -= (16384 - (int)(16384.499999999996 - diff / 360)) * 360; + if (diff == 0) + { + total = timelinesRotation[i]; + } + else + { + float lastTotal, lastDiff; + if (firstFrame) + { + lastTotal = 0; + lastDiff = diff; + } + else + { + lastTotal = timelinesRotation[i]; // Angle and direction of mix, including loops. + lastDiff = timelinesRotation[i + 1]; // Difference between bones. + } + bool current = diff > 0, dir = lastTotal >= 0; + // Detect cross at 0 (not 180). + if (Math.Sign(lastDiff) != Math.Sign(diff) && Math.Abs(lastDiff) <= 90) + { + // A cross after a 360 rotation is a loop. + if (Math.Abs(lastTotal) > 180) lastTotal += 360 * Math.Sign(lastTotal); + dir = current; + } + total = diff + lastTotal - lastTotal % 360; // Store loops as part of lastTotal. + if (dir != current) total += 360 * Math.Sign(lastTotal); + timelinesRotation[i] = total; + } + timelinesRotation[i + 1] = diff; + bone.rotation = r1 + total * alpha; + } + + private void QueueEvents(TrackEntry entry, float animationTime) + { + float animationStart = entry.animationStart, animationEnd = entry.animationEnd; + float duration = animationEnd - animationStart; + float trackLastWrapped = entry.trackLast % duration; + + // Queue events before complete. + Event[] eventsItems = this.events.Items; + int i = 0, n = events.Count; + for (; i < n; i++) + { + Event e = eventsItems[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) + complete = duration == 0 || (trackLastWrapped > entry.trackTime % duration); + else + complete = animationTime >= animationEnd && entry.animationLast < animationEnd; + if (complete) queue.Complete(entry); + + // Queue events after complete. + for (; i < n; i++) + { + Event e = eventsItems[i]; + if (e.time < animationStart) continue; // Discard events outside animation start/end. + queue.Event(entry, eventsItems[i]); + } + } + + /// + /// Removes all animations from all tracks, leaving skeletons in their current pose. + /// + /// It may be desired to use to mix the skeletons back to the setup pose, + /// rather than leaving them in their current pose. + /// + public void ClearTracks() + { + bool oldDrainDisabled = queue.drainDisabled; + queue.drainDisabled = true; + for (int i = 0, n = tracks.Count; i < n; i++) + { + ClearTrack(i); + } + tracks.Clear(); + queue.drainDisabled = oldDrainDisabled; + queue.Drain(); + } + + /// + /// Removes all animations from the track, leaving skeletons in their current pose. + /// + /// It may be desired to use to mix the skeletons back to the setup pose, + /// rather than leaving them in their current pose. + /// + public void ClearTrack(int trackIndex) + { + if (trackIndex >= tracks.Count) return; + TrackEntry current = tracks.Items[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.Items[current.trackIndex] = null; + + queue.Drain(); + } + + /// Sets the active TrackEntry for a given track number. + private void SetCurrent(int index, TrackEntry current, bool interrupt) + { + TrackEntry from = ExpandToIndex(index); + tracks.Items[index] = current; + current.previous = null; + + if (from != null) + { + if (interrupt) queue.Interrupt(from); + current.mixingFrom = from; + from.mixingTo = current; + current.mixTime = 0; + + // Store the interrupted mix percentage. + if (from.mixingFrom != null && from.mixDuration > 0) + current.interruptAlpha *= Math.Min(1, from.mixTime / from.mixDuration); + + from.timelinesRotation.Clear(); // Reset rotation for mixing out, in case entry was mixed in. + } + + queue.Start(current); // triggers AnimationsChanged + } + + /// Sets an animation by name. + public TrackEntry SetAnimation(int trackIndex, string animationName, bool loop) + { + Animation animation = data.skeletonData.FindAnimation(animationName); + if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName"); + return SetAnimation(trackIndex, animation, loop); + } + + /// Sets the current animation for a track, discarding any queued animations. If the formerly current track entry was never + /// applied to a skeleton, it is replaced (not mixed from). + /// 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 determines when the track is cleared. + /// A track entry to allow further customization of animation playback. References to the track entry must not be kept + /// after the event occurs. + public TrackEntry SetAnimation(int trackIndex, Animation animation, bool loop) + { + if (animation == null) throw new ArgumentNullException("animation", "animation cannot be 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.Items[trackIndex] = current.mixingFrom; + queue.Interrupt(current); + queue.End(current); + ClearNext(current); + current = current.mixingFrom; + interrupt = false; // mixingFrom is current again, but don't interrupt it twice. + } + else + ClearNext(current); + } + TrackEntry entry = NewTrackEntry(trackIndex, animation, loop, current); + SetCurrent(trackIndex, entry, interrupt); + queue.Drain(); + return entry; + } + + /// Queues an animation by name. + /// + public TrackEntry AddAnimation(int trackIndex, string animationName, bool loop, float delay) + { + Animation animation = data.skeletonData.FindAnimation(animationName); + if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName"); + return AddAnimation(trackIndex, animation, loop, delay); + } + + /// Adds an animation to be played after the current or last queued animation for a track. If the track is empty, it is + /// equivalent to calling . + /// + /// If > 0, sets . If <= 0, the delay set is the duration of the previous track entry + /// minus any mix duration (from the plus the specified Delay (ie the mix + /// ends at (Delay = 0) or before (Delay < 0) the previous track entry duration). If the + /// previous entry is looping, its next loop completion is used instead of its duration. + /// + /// A track entry to allow further customization of animation playback. References to the track entry must not be kept + /// after the event occurs. + public TrackEntry AddAnimation(int trackIndex, Animation animation, bool loop, float delay) + { + if (animation == null) throw new ArgumentNullException("animation", "animation cannot be 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(); + } + else + { + last.next = entry; + entry.previous = last; + if (delay <= 0) delay += last.TrackComplete - entry.mixDuration; + } + + entry.delay = delay; + return entry; + } + + /// + /// Sets an empty animation for a track, discarding any queued animations, and sets the track entry's + /// . An empty animation has no timelines and serves as a placeholder for mixing in or out. + /// + /// Mixing out is done by setting an empty animation with a mix duration using either , + /// , or . Mixing to an empty animation causes + /// the previous animation to be applied less and less over the mix duration. Properties keyed in the previous animation + /// transition to the value from lower tracks or to the setup pose value if no lower tracks key the property. A mix duration of + /// 0 still mixes out over one frame. + /// + /// Mixing in is done by first setting an empty animation, then adding an animation using + /// with the desired delay (an empty animation has a duration of 0) and on + /// the returned track entry, set the . Mixing from an empty animation causes the new + /// animation to be applied more and more over the mix duration. Properties keyed in the new animation transition from the value + /// from lower tracks or from the setup pose value if no lower tracks key the property to the value keyed in the new + /// animation. + public TrackEntry SetEmptyAnimation(int trackIndex, float mixDuration) + { + TrackEntry entry = SetAnimation(trackIndex, AnimationState.EmptyAnimation, false); + entry.mixDuration = mixDuration; + entry.trackEnd = mixDuration; + return entry; + } + + /// + /// Adds an empty animation to be played after the current or last queued animation for a track, and sets the track entry's + /// . If the track is empty, it is equivalent to calling + /// . + /// + /// Track number. + /// Mix duration. + /// If > 0, sets . If <= 0, the delay set is the duration of the previous track entry + /// minus any mix duration plus the specified Delay (ie the mix ends at (Delay = 0) or + /// before (Delay < 0) the previous track entry duration). If the previous entry is looping, its next + /// loop completion is used instead of its duration. + /// A track entry to allow further customization of animation playback. References to the track entry must not be kept + /// after the event occurs. + /// + public TrackEntry AddEmptyAnimation(int trackIndex, float mixDuration, float delay) + { + TrackEntry entry = AddAnimation(trackIndex, AnimationState.EmptyAnimation, false, delay); + if (delay <= 0) entry.delay += entry.mixDuration - mixDuration; + entry.mixDuration = mixDuration; + entry.trackEnd = mixDuration; + return entry; + } + + /// + /// Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix + /// duration. + public void SetEmptyAnimations(float mixDuration) + { + bool oldDrainDisabled = queue.drainDisabled; + queue.drainDisabled = true; + TrackEntry[] tracksItems = tracks.Items; + for (int i = 0, n = tracks.Count; i < n; i++) + { + TrackEntry current = tracksItems[i]; + if (current != null) SetEmptyAnimation(current.trackIndex, mixDuration); + } + queue.drainDisabled = oldDrainDisabled; + queue.Drain(); + } + + private TrackEntry ExpandToIndex(int index) + { + if (index < tracks.Count) return tracks.Items[index]; + tracks.Resize(index + 1); + return null; + } + + /// Object-pooling version of new TrackEntry. Obtain an unused TrackEntry from the pool and clear/initialize its values. + /// May be null. + private TrackEntry NewTrackEntry(int trackIndex, Animation animation, bool loop, TrackEntry last) + { + TrackEntry entry = trackEntryPool.Obtain(); + entry.trackIndex = trackIndex; + entry.animation = animation; + entry.loop = loop; + entry.holdPrevious = false; + + entry.eventThreshold = 0; + entry.attachmentThreshold = 0; + entry.drawOrderThreshold = 0; + + entry.animationStart = 0; + entry.animationEnd = animation.Duration; + entry.animationLast = -1; + entry.nextAnimationLast = -1; + + entry.delay = 0; + entry.trackTime = 0; + entry.trackLast = -1; + entry.nextTrackLast = -1; + entry.trackEnd = float.MaxValue; + entry.timeScale = 1; + + entry.alpha = 1; + entry.interruptAlpha = 1; + entry.mixTime = 0; + entry.mixDuration = last == null ? 0 : data.GetMix(last.animation, animation); + entry.mixBlend = MixBlend.Replace; + return entry; + } + + /// Removes the next entry and all entries after it for the specified entry. + public void ClearNext(TrackEntry entry) + { + TrackEntry next = entry.next; + while (next != null) + { + queue.Dispose(next); + next = next.next; + } + entry.next = null; + } + + private void AnimationsChanged() + { + animationsChanged = false; + + // Process in the order that animations are applied. + propertyIds.Clear(); + int n = tracks.Count; + TrackEntry[] tracksItems = tracks.Items; + for (int i = 0; i < n; i++) + { + TrackEntry entry = tracksItems[i]; + if (entry == null) continue; + while (entry.mixingFrom != null) // Move to last entry, then iterate in reverse. + entry = entry.mixingFrom; + do + { + if (entry.mixingTo == null || entry.mixBlend != MixBlend.Add) ComputeHold(entry); + entry = entry.mixingTo; + } while (entry != null); + } + } + + private void ComputeHold(TrackEntry entry) + { + TrackEntry to = entry.mixingTo; + Timeline[] timelines = entry.animation.timelines.Items; + int timelinesCount = entry.animation.timelines.Count; + int[] timelineMode = entry.timelineMode.Resize(timelinesCount).Items; + entry.timelineHoldMix.Clear(); + TrackEntry[] timelineHoldMix = entry.timelineHoldMix.Resize(timelinesCount).Items; + HashSet propertyIds = this.propertyIds; + + if (to != null && to.holdPrevious) + { + for (int i = 0; i < timelinesCount; i++) + timelineMode[i] = propertyIds.AddAll(timelines[i].PropertyIds) ? AnimationState.HoldFirst : AnimationState.HoldSubsequent; + + return; + } + + // outer: + for (int i = 0; i < timelinesCount; i++) + { + Timeline timeline = timelines[i]; + String[] ids = timeline.PropertyIds; + if (!propertyIds.AddAll(ids)) + timelineMode[i] = AnimationState.Subsequent; + else if (to == null || timeline is AttachmentTimeline || timeline is DrawOrderTimeline + || timeline is EventTimeline || !to.animation.HasTimeline(ids)) + { + timelineMode[i] = AnimationState.First; + } + else + { + for (TrackEntry next = to.mixingTo; next != null; next = next.mixingTo) + { + if (next.animation.HasTimeline(ids)) continue; + if (next.mixDuration > 0) + { + timelineMode[i] = AnimationState.HoldMix; + timelineHoldMix[i] = next; + goto continue_outer; // continue outer; + } + break; + } + timelineMode[i] = AnimationState.HoldFirst; + } + continue_outer: { } + } + } + + /// The track entry for the animation currently playing on the track, or null if no animation is currently playing. + public TrackEntry GetCurrent(int trackIndex) + { + if (trackIndex >= tracks.Count) return null; + return tracks.Items[trackIndex]; + } + + /// Discards all listener notifications that have not yet been delivered. This can be useful to call from an + /// AnimationState event subscriber when it is known that further notifications that may have been already queued for delivery + /// are not wanted because new animations are being set. + public void ClearListenerNotifications() + { + queue.Clear(); + } + + /// + /// Multiplier for the delta time when the animation state is updated, causing time for all animations and mixes to play slower + /// or faster. Defaults to 1. + /// + /// See TrackEntry for affecting a single animation. + /// + public float TimeScale { get { return timeScale; } set { timeScale = value; } } + + /// The AnimationStateData to look up mix durations. + public AnimationStateData Data + { + get + { + return data; + } + set + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + this.data = value; + } + } + + /// A list of tracks that have animations, which may contain nulls. + public ExposedList Tracks { get { return tracks; } } + + override public string ToString() + { + var buffer = new System.Text.StringBuilder(); + TrackEntry[] tracksItems = tracks.Items; + for (int i = 0, n = tracks.Count; i < n; i++) + { + TrackEntry entry = tracksItems[i]; + if (entry == null) continue; + if (buffer.Length > 0) buffer.Append(", "); + buffer.Append(entry.ToString()); + } + if (buffer.Length == 0) return ""; + return buffer.ToString(); + } + } + + /// + /// + /// Stores settings and other state for the playback of an animation on an track. + /// + /// References to a track entry must not be kept after the event occurs. + /// + public class TrackEntry : Pool.IPoolable + { + internal Animation animation; + + internal TrackEntry previous, next, mixingFrom, mixingTo; + // difference to libgdx reference: delegates are used for event callbacks instead of 'AnimationStateListener listener'. + /// See + /// API Reference documentation pages here for details. Usage in C# and spine-unity is explained + /// here + /// on the spine-unity documentation pages. + public event AnimationState.TrackEntryDelegate Start, Interrupt, End, Dispose, Complete; + public event AnimationState.TrackEntryEventDelegate Event; + internal void OnStart() { if (Start != null) Start(this); } + internal void OnInterrupt() { if (Interrupt != null) Interrupt(this); } + internal void OnEnd() { if (End != null) End(this); } + internal void OnDispose() { if (Dispose != null) Dispose(this); } + internal void OnComplete() { if (Complete != null) Complete(this); } + internal void OnEvent(Event e) { if (Event != null) Event(this, e); } + + internal int trackIndex; + + internal bool loop, holdPrevious, reverse; + internal float eventThreshold, attachmentThreshold, drawOrderThreshold; + internal float animationStart, animationEnd, animationLast, nextAnimationLast; + internal float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale = 1f; + internal float alpha, mixTime, mixDuration, interruptAlpha, totalAlpha; + internal MixBlend mixBlend = MixBlend.Replace; + internal readonly ExposedList timelineMode = new ExposedList(); + internal readonly ExposedList timelineHoldMix = new ExposedList(); + internal readonly ExposedList timelinesRotation = new ExposedList(); + + // IPoolable.Reset() + public void Reset() + { + previous = null; + next = null; + mixingFrom = null; + mixingTo = null; + animation = null; + // replaces 'listener = null;' since delegates are used for event callbacks + Start = null; + Interrupt = null; + End = null; + Dispose = null; + Complete = null; + Event = null; + timelineMode.Clear(); + timelineHoldMix.Clear(); + timelinesRotation.Clear(); + } + + /// The index of the track where this entry is either current or queued. + /// + public int TrackIndex { get { return trackIndex; } } + + /// The animation to apply for this track entry. + public Animation Animation { get { return animation; } } + + /// + /// If true, the animation will repeat. If false it will not, instead its last frame is applied if played beyond its + /// duration. + public bool Loop { get { return loop; } set { loop = value; } } + + /// + /// + /// Seconds to postpone playing the animation. When this track entry is the current track entry, Delay + /// postpones incrementing the . When this track entry is queued, Delay is the time from + /// the start of the previous animation to when this track entry will become the current track entry (ie when the previous + /// track entry >= this track entry's Delay). + /// + /// affects the delay. + /// + /// When using with a delay <= 0, the delay + /// is set using the mix duration from the . If is set afterward, the delay + /// may need to be adjusted. + public float Delay { get { return delay; } set { delay = value; } } + + /// + /// Current time in seconds this track entry has been the current track entry. The track time determines + /// . The track time can be set to start the animation at a time other than 0, without affecting + /// looping. + public float TrackTime { get { return trackTime; } set { trackTime = value; } } + + /// + /// + /// The track time in seconds when this animation will be removed from the track. Defaults to the highest possible float + /// value, meaning the animation will be applied until a new animation is set or the track is cleared. If the track end time + /// is reached, no other animations are queued for playback, and mixing from any previous animations is complete, then the + /// properties keyed by the animation are set to the setup pose and the track is cleared. + /// + /// It may be desired to use rather than have the animation + /// abruptly cease being applied. + /// + public float TrackEnd { get { return trackEnd; } set { trackEnd = value; } } + + /// + /// If this track entry is non-looping, the track time in seconds when is reached, or the current + /// if it has already been reached. If this track entry is looping, the track time when this + /// animation will reach its next (the next loop completion). + public float TrackComplete + { + get + { + 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. + } + } + + /// + /// + /// Seconds when this animation starts, both initially and after looping. Defaults to 0. + /// + /// When changing the AnimationStart time, it often makes sense to set to the same + /// value to prevent timeline keys before the start time from triggering. + /// + public float AnimationStart { get { return animationStart; } set { animationStart = value; } } + + /// + /// Seconds for the last frame of this animation. Non-looping animations won't play past this time. Looping animations will + /// loop back to at this time. Defaults to the animation . + /// + public float AnimationEnd { get { return animationEnd; } set { animationEnd = value; } } + + /// + /// 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 AnimationLast time (exclusive) and + /// AnimationTime (inclusive). Defaults to -1 to ensure triggers on frame 0 happen the first time this animation + /// is applied. + public float AnimationLast + { + get { return animationLast; } + set + { + animationLast = value; + nextAnimationLast = value; + } + } + + /// + /// Uses to compute the AnimationTime, which is between + /// and . When the TrackTime is 0, the AnimationTime is equal to the + /// AnimationStart time. + /// + public float AnimationTime + { + get + { + if (loop) + { + float duration = animationEnd - animationStart; + if (duration == 0) return animationStart; + return (trackTime % duration) + animationStart; + } + return Math.Min(trackTime + animationStart, animationEnd); + } + } + + /// + /// + /// Multiplier for the delta time when this track entry is updated, causing time for this animation to pass slower or + /// faster. Defaults to 1. + /// + /// Values < 0 are not supported. To play an animation in reverse, use . + /// + /// is not affected by track entry time scale, so may need to be adjusted to + /// match the animation speed. + /// + /// When using with a Delay <= 0, the + /// is set using the mix duration from the , assuming time scale to be 1. If + /// the time scale is not 1, the delay may need to be adjusted. + /// + /// See AnimationState for affecting all animations. + /// + public float TimeScale { get { return timeScale; } set { timeScale = value; } } + + /// + /// + /// Values < 1 mix this animation with the skeleton's current pose (usually the pose resulting from lower tracks). Defaults + /// to 1, which overwrites the skeleton's current pose with this animation. + /// + /// Typically track 0 is used to completely pose the skeleton, then alpha is 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. + /// + public float Alpha { get { return alpha; } set { alpha = value; } } + + /// + /// When the mix percentage ( / ) is less than the + /// EventThreshold, event timelines are applied while this animation is being mixed out. Defaults to 0, so event + /// timelines are not applied while this animation is being mixed out. + /// + public float EventThreshold { get { return eventThreshold; } set { eventThreshold = value; } } + + /// + /// When the mix percentage ( / ) is less than the + /// AttachmentThreshold, attachment timelines are applied while this animation is being mixed out. Defaults to + /// 0, so attachment timelines are not applied while this animation is being mixed out. + /// + public float AttachmentThreshold { get { return attachmentThreshold; } set { attachmentThreshold = value; } } + + /// + /// When the mix percentage ( / ) is less than the + /// DrawOrderThreshold, draw order timelines are applied while this animation is being mixed out. Defaults to 0, + /// so draw order timelines are not applied while this animation is being mixed out. + /// + public float DrawOrderThreshold { get { return drawOrderThreshold; } set { drawOrderThreshold = value; } } + + /// + /// The animation queued to start after this animation, or null if there is none. next makes up a doubly linked + /// list. + /// + /// See to truncate the list. + public TrackEntry Next { get { return next; } } + + /// + /// The animation queued to play before this animation, or null. previous makes up a doubly linked list. + public TrackEntry Previous { get { return previous; } } + + /// + /// Returns true if at least one loop has been completed. + /// + public bool IsComplete + { + get { return trackTime >= animationEnd - animationStart; } + } + + /// + /// Seconds from 0 to the when mixing from the previous animation to this animation. May be + /// slightly more than MixDuration when the mix is complete. + public float MixTime { get { return mixTime; } set { mixTime = value; } } + + /// + /// + /// 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 MixDuration can be set manually rather than use the value from + /// . In that case, the MixDuration can be set for a new + /// track entry only before is first called. + /// + /// When using with a Delay <= 0, the + /// is set using the mix duration from the . If mixDuration is set + /// afterward, the delay may need to be adjusted. For example: + /// entry.Delay = entry.previous.TrackComplete - entry.MixDuration; + /// + public float MixDuration { get { return mixDuration; } set { mixDuration = value; } } + + /// + /// + /// Controls how properties keyed in the animation are mixed with lower tracks. Defaults to . + /// + /// Track entries on track 0 ignore this setting and always use . + /// + /// The MixBlend can be set for a new track entry only before is first + /// called. + /// + public MixBlend MixBlend { get { return mixBlend; } set { mixBlend = value; } } + + /// + /// 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 linked list. + public TrackEntry MixingFrom { get { return mixingFrom; } } + + /// + /// The track entry for the next animation when mixing from this animation to the next animation, or null if no mixing is + /// currently occuring. When mixing to multiple animations, MixingTo makes up a linked list. + public TrackEntry MixingTo { get { return mixingTo; } } + + /// + /// + /// 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. + /// + public bool HoldPrevious { get { return holdPrevious; } set { holdPrevious = value; } } + + /// + /// If true, the animation will be applied in reverse. Events are not fired when an animation is applied in reverse. + public bool Reverse { get { return reverse; } set { reverse = value; } } + + /// + /// + /// 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 and starting animations on other tracks. + /// + /// Mixing with 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. + /// + public void ResetRotationDirections() + { + timelinesRotation.Clear(); + } + + override public string ToString() + { + return animation == null ? "" : animation.name; + } + } + + class EventQueue + { + private readonly List eventQueueEntries = new List(); + internal bool drainDisabled; + + private readonly AnimationState state; + private readonly Pool trackEntryPool; + internal event Action AnimationsChanged; + + internal EventQueue(AnimationState state, Action HandleAnimationsChanged, Pool trackEntryPool) + { + this.state = state; + this.AnimationsChanged += HandleAnimationsChanged; + this.trackEntryPool = trackEntryPool; + } + + internal void Start(TrackEntry entry) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.Start, entry)); + if (AnimationsChanged != null) AnimationsChanged(); + } + + internal void Interrupt(TrackEntry entry) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.Interrupt, entry)); + } + + internal void End(TrackEntry entry) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.End, entry)); + if (AnimationsChanged != null) AnimationsChanged(); + } + + internal void Dispose(TrackEntry entry) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.Dispose, entry)); + } + + internal void Complete(TrackEntry entry) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.Complete, entry)); + } + + internal void Event(TrackEntry entry, Event e) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.Event, entry, e)); + } + + /// Raises all events in the queue and drains the queue. + internal void Drain() + { + if (drainDisabled) return; + drainDisabled = true; + + List eventQueueEntries = this.eventQueueEntries; + AnimationState state = this.state; + + // Don't cache eventQueueEntries.Count so callbacks can queue their own events (eg, call SetAnimation in AnimationState_Complete). + for (int i = 0; i < eventQueueEntries.Count; i++) + { + EventQueueEntry queueEntry = eventQueueEntries[i]; + TrackEntry trackEntry = queueEntry.entry; + + switch (queueEntry.type) + { + case EventType.Start: + trackEntry.OnStart(); + state.OnStart(trackEntry); + break; + case EventType.Interrupt: + trackEntry.OnInterrupt(); + state.OnInterrupt(trackEntry); + break; + case EventType.End: + trackEntry.OnEnd(); + state.OnEnd(trackEntry); + goto case EventType.Dispose; // Fall through. (C#) + case EventType.Dispose: + trackEntry.OnDispose(); + state.OnDispose(trackEntry); + trackEntryPool.Free(trackEntry); + break; + case EventType.Complete: + trackEntry.OnComplete(); + state.OnComplete(trackEntry); + break; + case EventType.Event: + trackEntry.OnEvent(queueEntry.e); + state.OnEvent(trackEntry, queueEntry.e); + break; + } + } + eventQueueEntries.Clear(); + + drainDisabled = false; + } + + internal void Clear() + { + eventQueueEntries.Clear(); + } + + struct EventQueueEntry + { + public EventType type; + public TrackEntry entry; + public Event e; + + public EventQueueEntry(EventType eventType, TrackEntry trackEntry, Event e = null) + { + this.type = eventType; + this.entry = trackEntry; + this.e = e; + } + } + + enum EventType + { + Start, Interrupt, End, Dispose, Complete, Event + } + } + + class Pool where T : class, new() + { + public readonly int max; + readonly Stack freeObjects; + + public int Count { get { return freeObjects.Count; } } + public int Peak { get; private set; } + + public Pool(int initialCapacity = 16, int max = int.MaxValue) + { + freeObjects = new Stack(initialCapacity); + this.max = max; + } + + public T Obtain() + { + return freeObjects.Count == 0 ? new T() : freeObjects.Pop(); + } + + public void Free(T obj) + { + if (obj == null) throw new ArgumentNullException("obj", "obj cannot be null"); + if (freeObjects.Count < max) + { + freeObjects.Push(obj); + Peak = Math.Max(Peak, freeObjects.Count); + } + Reset(obj); + } + + public void Clear() + { + freeObjects.Clear(); + } + + protected void Reset(T obj) + { + var poolable = obj as IPoolable; + if (poolable != null) poolable.Reset(); + } + + public interface IPoolable + { + void Reset(); + } + } + + public static class HashSetExtensions + { + public static bool AddAll(this HashSet set, T[] addSet) + { + bool anyItemAdded = false; + for (int i = 0, n = addSet.Length; i < n; ++i) + { + T item = addSet[i]; + anyItemAdded |= set.Add(item); + } + return anyItemAdded; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/AnimationStateData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/AnimationStateData.cs index a62815c..b98a280 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/AnimationStateData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/AnimationStateData.cs @@ -30,85 +30,97 @@ using System; using System.Collections.Generic; -namespace Spine4_0_31 { +namespace Spine4_0_31 +{ - /// Stores mix (crossfade) durations to be applied when AnimationState animations are changed. - public class AnimationStateData { - internal SkeletonData skeletonData; - readonly Dictionary animationToMixTime = new Dictionary(AnimationPairComparer.Instance); - internal float defaultMix; + /// Stores mix (crossfade) durations to be applied when AnimationState animations are changed. + public class AnimationStateData + { + internal SkeletonData skeletonData; + readonly Dictionary animationToMixTime = new Dictionary(AnimationPairComparer.Instance); + internal float defaultMix; - /// The SkeletonData to look up animations when they are specified by name. - public SkeletonData SkeletonData { get { return skeletonData; } } + /// The SkeletonData to look up animations when they are specified by name. + public SkeletonData SkeletonData { get { return skeletonData; } } - /// - /// The mix duration to use when no mix duration has been specifically defined between two animations. - public float DefaultMix { get { return defaultMix; } set { defaultMix = value; } } + /// + /// The mix duration to use when no mix duration has been specifically defined between two animations. + public float DefaultMix { get { return defaultMix; } set { defaultMix = value; } } - public AnimationStateData (SkeletonData skeletonData) { - if (skeletonData == null) throw new ArgumentException("skeletonData cannot be null.", "skeletonData"); - this.skeletonData = skeletonData; - } + public AnimationStateData(SkeletonData skeletonData) + { + if (skeletonData == null) throw new ArgumentException("skeletonData cannot be null.", "skeletonData"); + this.skeletonData = skeletonData; + } - /// Sets a mix duration by animation names. - public void SetMix (string fromName, string toName, float duration) { - Animation from = skeletonData.FindAnimation(fromName); - if (from == null) throw new ArgumentException("Animation not found: " + fromName, "fromName"); - Animation to = skeletonData.FindAnimation(toName); - if (to == null) throw new ArgumentException("Animation not found: " + toName, "toName"); - SetMix(from, to, duration); - } + /// Sets a mix duration by animation names. + public void SetMix(string fromName, string toName, float duration) + { + Animation from = skeletonData.FindAnimation(fromName); + if (from == null) throw new ArgumentException("Animation not found: " + fromName, "fromName"); + Animation to = skeletonData.FindAnimation(toName); + if (to == null) throw new ArgumentException("Animation not found: " + toName, "toName"); + SetMix(from, to, duration); + } - /// Sets a mix duration when changing from the specified animation to the other. - /// See TrackEntry.MixDuration. - public void SetMix (Animation from, Animation to, float duration) { - if (from == null) throw new ArgumentNullException("from", "from cannot be null."); - if (to == null) throw new ArgumentNullException("to", "to cannot be null."); - AnimationPair key = new AnimationPair(from, to); - animationToMixTime.Remove(key); - animationToMixTime.Add(key, duration); - } + /// Sets a mix duration when changing from the specified animation to the other. + /// See TrackEntry.MixDuration. + public void SetMix(Animation from, Animation to, float duration) + { + if (from == null) throw new ArgumentNullException("from", "from cannot be null."); + if (to == null) throw new ArgumentNullException("to", "to cannot be null."); + AnimationPair key = new AnimationPair(from, to); + animationToMixTime.Remove(key); + animationToMixTime.Add(key, 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. - /// - public float GetMix (Animation from, Animation to) { - if (from == null) throw new ArgumentNullException("from", "from cannot be null."); - if (to == null) throw new ArgumentNullException("to", "to cannot be null."); - AnimationPair key = new AnimationPair(from, to); - float duration; - if (animationToMixTime.TryGetValue(key, out duration)) return duration; - return defaultMix; - } + /// + /// The mix duration to use when changing from the specified animation to the other, + /// or the DefaultMix if no mix duration has been set. + /// + public float GetMix(Animation from, Animation to) + { + if (from == null) throw new ArgumentNullException("from", "from cannot be null."); + if (to == null) throw new ArgumentNullException("to", "to cannot be null."); + AnimationPair key = new AnimationPair(from, to); + float duration; + if (animationToMixTime.TryGetValue(key, out duration)) return duration; + return defaultMix; + } - public struct AnimationPair { - public readonly Animation a1; - public readonly Animation a2; + public struct AnimationPair + { + public readonly Animation a1; + public readonly Animation a2; - public AnimationPair (Animation a1, Animation a2) { - this.a1 = a1; - this.a2 = a2; - } + public AnimationPair(Animation a1, Animation a2) + { + this.a1 = a1; + this.a2 = a2; + } - public override string ToString () { - return a1.name + "->" + a2.name; - } - } + public override string ToString() + { + return a1.name + "->" + a2.name; + } + } - // Avoids boxing in the dictionary. - public class AnimationPairComparer : IEqualityComparer { - public static readonly AnimationPairComparer Instance = new AnimationPairComparer(); + // Avoids boxing in the dictionary. + public class AnimationPairComparer : IEqualityComparer + { + public static readonly AnimationPairComparer Instance = new AnimationPairComparer(); - bool IEqualityComparer.Equals (AnimationPair x, AnimationPair y) { - return ReferenceEquals(x.a1, y.a1) && ReferenceEquals(x.a2, y.a2); - } + bool IEqualityComparer.Equals(AnimationPair x, AnimationPair y) + { + return ReferenceEquals(x.a1, y.a1) && ReferenceEquals(x.a2, y.a2); + } - int IEqualityComparer.GetHashCode (AnimationPair obj) { - // from Tuple.CombineHashCodes // return (((h1 << 5) + h1) ^ h2); - int h1 = obj.a1.GetHashCode(); - return (((h1 << 5) + h1) ^ obj.a2.GetHashCode()); - } - } - } + int IEqualityComparer.GetHashCode(AnimationPair obj) + { + // from Tuple.CombineHashCodes // return (((h1 << 5) + h1) ^ h2); + int h1 = obj.a1.GetHashCode(); + return (((h1 << 5) + h1) ^ obj.a2.GetHashCode()); + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Atlas.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Atlas.cs index 6b1631a..3b4e651 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Atlas.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Atlas.cs @@ -35,31 +35,34 @@ using System.Collections.Generic; using System.Globalization; using System.IO; -using System.Reflection; #if WINDOWS_STOREAPP using System.Threading.Tasks; using Windows.Storage; #endif -namespace Spine4_0_31 { - public class Atlas : IEnumerable { - readonly List pages = new List(); - List regions = new List(); - TextureLoader textureLoader; +namespace Spine4_0_31 +{ + public class Atlas : IEnumerable + { + readonly List pages = new List(); + List regions = new List(); + TextureLoader textureLoader; - #region IEnumerable implementation - public IEnumerator GetEnumerator () { - return regions.GetEnumerator(); - } + #region IEnumerable implementation + public IEnumerator GetEnumerator() + { + return regions.GetEnumerator(); + } - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () { - return regions.GetEnumerator(); - } - #endregion + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return regions.GetEnumerator(); + } + #endregion - public List Regions { get { return regions; } } - public List Pages { get { return pages; } } + public List Regions { get { return regions; } } + public List Pages { get { return pages; } } #if !(IS_UNITY) #if WINDOWS_STOREAPP @@ -82,283 +85,334 @@ public Atlas(string path, TextureLoader textureLoader) { this.ReadFile(path, textureLoader).Wait(); } #else - public Atlas (string path, TextureLoader textureLoader) { + public Atlas(string path, TextureLoader textureLoader) + { #if WINDOWS_PHONE Stream stream = Microsoft.Xna.Framework.TitleContainer.OpenStream(path); using (StreamReader reader = new StreamReader(stream)) { #else - using (StreamReader reader = new StreamReader(path)) { + using (StreamReader reader = new StreamReader(path)) + { #endif // WINDOWS_PHONE - try { - Atlas atlas = new Atlas(reader, Path.GetDirectoryName(path), textureLoader); - this.pages = atlas.pages; - this.regions = atlas.regions; - this.textureLoader = atlas.textureLoader; - } catch (Exception ex) { - throw new Exception("Error reading atlas file: " + path, ex); - } - } - } + try + { + Atlas atlas = new Atlas(reader, Path.GetDirectoryName(path), textureLoader); + this.pages = atlas.pages; + this.regions = atlas.regions; + this.textureLoader = atlas.textureLoader; + } + catch (Exception ex) + { + throw new Exception("Error reading atlas file: " + path, ex); + } + } + } #endif // WINDOWS_STOREAPP #endif - public Atlas (List pages, List regions) { - if (pages == null) throw new ArgumentNullException("pages", "pages cannot be null."); - if (regions == null) throw new ArgumentNullException("regions", "regions cannot be null."); - this.pages = pages; - this.regions = regions; - this.textureLoader = null; - } + public Atlas(List pages, List regions) + { + if (pages == null) throw new ArgumentNullException("pages", "pages cannot be null."); + if (regions == null) throw new ArgumentNullException("regions", "regions cannot be null."); + this.pages = pages; + this.regions = regions; + this.textureLoader = null; + } - public Atlas (TextReader reader, string imagesDir, TextureLoader textureLoader) { - if (reader == null) throw new ArgumentNullException("reader", "reader cannot be null."); - if (imagesDir == null) throw new ArgumentNullException("imagesDir", "imagesDir cannot be null."); - if (textureLoader == null) throw new ArgumentNullException("textureLoader", "textureLoader cannot be null."); - this.textureLoader = textureLoader; + public Atlas(TextReader reader, string imagesDir, TextureLoader textureLoader) + { + if (reader == null) throw new ArgumentNullException("reader", "reader cannot be null."); + if (imagesDir == null) throw new ArgumentNullException("imagesDir", "imagesDir cannot be null."); + if (textureLoader == null) throw new ArgumentNullException("textureLoader", "textureLoader cannot be null."); + this.textureLoader = textureLoader; - string[] entry = new string[5]; - AtlasPage page = null; - AtlasRegion region = null; + string[] entry = new string[5]; + AtlasPage page = null; + AtlasRegion region = null; - var pageFields = new Dictionary(5); - pageFields.Add("size", () => { - page.width = int.Parse(entry[1], CultureInfo.InvariantCulture); - page.height = int.Parse(entry[2], CultureInfo.InvariantCulture); - }); - pageFields.Add("format", () => { - page.format = (Format)Enum.Parse(typeof(Format), entry[1], false); - }); - pageFields.Add("filter", () => { - page.minFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), entry[1], false); - page.magFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), entry[2], false); - }); - pageFields.Add("repeat", () => { - if (entry[1].IndexOf('x') != -1) page.uWrap = TextureWrap.Repeat; - if (entry[1].IndexOf('y') != -1) page.vWrap = TextureWrap.Repeat; - }); - pageFields.Add("pma", () => { - page.pma = entry[1] == "true"; - }); + var pageFields = new Dictionary(5); + pageFields.Add("size", () => + { + page.width = int.Parse(entry[1], CultureInfo.InvariantCulture); + page.height = int.Parse(entry[2], CultureInfo.InvariantCulture); + }); + pageFields.Add("format", () => + { + page.format = (Format)Enum.Parse(typeof(Format), entry[1], false); + }); + pageFields.Add("filter", () => + { + page.minFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), entry[1], false); + page.magFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), entry[2], false); + }); + pageFields.Add("repeat", () => + { + if (entry[1].IndexOf('x') != -1) page.uWrap = TextureWrap.Repeat; + if (entry[1].IndexOf('y') != -1) page.vWrap = TextureWrap.Repeat; + }); + pageFields.Add("pma", () => + { + page.pma = entry[1] == "true"; + }); - var regionFields = new Dictionary(8); - regionFields.Add("xy", () => { // Deprecated, use bounds. - region.x = int.Parse(entry[1], CultureInfo.InvariantCulture); - region.y = int.Parse(entry[2], CultureInfo.InvariantCulture); - }); - regionFields.Add("size", () => { // Deprecated, use bounds. - region.width = int.Parse(entry[1], CultureInfo.InvariantCulture); - region.height = int.Parse(entry[2], CultureInfo.InvariantCulture); - }); - regionFields.Add("bounds", () => { - region.x = int.Parse(entry[1], CultureInfo.InvariantCulture); - region.y = int.Parse(entry[2], CultureInfo.InvariantCulture); - region.width = int.Parse(entry[3], CultureInfo.InvariantCulture); - region.height = int.Parse(entry[4], CultureInfo.InvariantCulture); - }); - regionFields.Add("offset", () => { // Deprecated, use offsets. - region.offsetX = int.Parse(entry[1], CultureInfo.InvariantCulture); - region.offsetY = int.Parse(entry[2], CultureInfo.InvariantCulture); - }); - regionFields.Add("orig", () => { // Deprecated, use offsets. - region.originalWidth = int.Parse(entry[1], CultureInfo.InvariantCulture); - region.originalHeight = int.Parse(entry[2], CultureInfo.InvariantCulture); - }); - regionFields.Add("offsets", () => { - region.offsetX = int.Parse(entry[1], CultureInfo.InvariantCulture); - region.offsetY = int.Parse(entry[2], CultureInfo.InvariantCulture); - region.originalWidth = int.Parse(entry[3], CultureInfo.InvariantCulture); - region.originalHeight = int.Parse(entry[4], CultureInfo.InvariantCulture); - }); - regionFields.Add("rotate", () => { - string value = entry[1]; - if (value == "true") - region.degrees = 90; - else if (value != "false") - region.degrees = int.Parse(value, CultureInfo.InvariantCulture); - }); - regionFields.Add("index", () => { - region.index = int.Parse(entry[1], CultureInfo.InvariantCulture); - }); + var regionFields = new Dictionary(8); + regionFields.Add("xy", () => + { // Deprecated, use bounds. + region.x = int.Parse(entry[1], CultureInfo.InvariantCulture); + region.y = int.Parse(entry[2], CultureInfo.InvariantCulture); + }); + regionFields.Add("size", () => + { // Deprecated, use bounds. + region.width = int.Parse(entry[1], CultureInfo.InvariantCulture); + region.height = int.Parse(entry[2], CultureInfo.InvariantCulture); + }); + regionFields.Add("bounds", () => + { + region.x = int.Parse(entry[1], CultureInfo.InvariantCulture); + region.y = int.Parse(entry[2], CultureInfo.InvariantCulture); + region.width = int.Parse(entry[3], CultureInfo.InvariantCulture); + region.height = int.Parse(entry[4], CultureInfo.InvariantCulture); + }); + regionFields.Add("offset", () => + { // Deprecated, use offsets. + region.offsetX = int.Parse(entry[1], CultureInfo.InvariantCulture); + region.offsetY = int.Parse(entry[2], CultureInfo.InvariantCulture); + }); + regionFields.Add("orig", () => + { // Deprecated, use offsets. + region.originalWidth = int.Parse(entry[1], CultureInfo.InvariantCulture); + region.originalHeight = int.Parse(entry[2], CultureInfo.InvariantCulture); + }); + regionFields.Add("offsets", () => + { + region.offsetX = int.Parse(entry[1], CultureInfo.InvariantCulture); + region.offsetY = int.Parse(entry[2], CultureInfo.InvariantCulture); + region.originalWidth = int.Parse(entry[3], CultureInfo.InvariantCulture); + region.originalHeight = int.Parse(entry[4], CultureInfo.InvariantCulture); + }); + regionFields.Add("rotate", () => + { + string value = entry[1]; + if (value == "true") + region.degrees = 90; + else if (value != "false") + region.degrees = int.Parse(value, CultureInfo.InvariantCulture); + }); + regionFields.Add("index", () => + { + region.index = int.Parse(entry[1], CultureInfo.InvariantCulture); + }); - string line = reader.ReadLine(); - // Ignore empty lines before first entry. - while (line != null && line.Trim().Length == 0) - line = reader.ReadLine(); - // Header entries. - while (true) { - if (line == null || line.Trim().Length == 0) break; - if (ReadEntry(entry, line) == 0) break; // Silently ignore all header fields. - line = reader.ReadLine(); - } - // Page and region entries. - List names = null; - List values = null; - while (true) { - if (line == null) break; - if (line.Trim().Length == 0) { - page = null; - line = reader.ReadLine(); - } else if (page == null) { - page = new AtlasPage(); - page.name = line.Trim(); - while (true) { - if (ReadEntry(entry, line = reader.ReadLine()) == 0) break; - Action field; - if (pageFields.TryGetValue(entry[0], out field)) field(); // Silently ignore unknown page fields. - } - textureLoader.Load(page, Path.Combine(imagesDir, page.name)); - pages.Add(page); - } else { - region = new AtlasRegion(); - region.page = page; - region.name = line; - while (true) { - int count = ReadEntry(entry, line = reader.ReadLine()); - if (count == 0) break; - Action field; - if (regionFields.TryGetValue(entry[0], out field)) - field(); - else { - if (names == null) { - names = new List(8); - values = new List(8); - } - names.Add(entry[0]); - int[] entryValues = new int[count]; - for (int i = 0; i < count; i++) - int.TryParse(entry[i + 1], NumberStyles.Any, CultureInfo.InvariantCulture, out entryValues[i]); // Silently ignore non-integer values. - values.Add(entryValues); - } - } - if (region.originalWidth == 0 && region.originalHeight == 0) { - region.originalWidth = region.width; - region.originalHeight = region.height; - } - if (names != null && names.Count > 0) { - region.names = names.ToArray(); - region.values = values.ToArray(); - names.Clear(); - values.Clear(); - } - region.u = region.x / (float)page.width; - region.v = region.y / (float)page.height; - if (region.degrees == 90) { - region.u2 = (region.x + region.height) / (float)page.width; - region.v2 = (region.y + region.width) / (float)page.height; - } else { - region.u2 = (region.x + region.width) / (float)page.width; - region.v2 = (region.y + region.height) / (float)page.height; - } - regions.Add(region); - } - } - } + string line = reader.ReadLine(); + // Ignore empty lines before first entry. + while (line != null && line.Trim().Length == 0) + line = reader.ReadLine(); + // Header entries. + while (true) + { + if (line == null || line.Trim().Length == 0) break; + if (ReadEntry(entry, line) == 0) break; // Silently ignore all header fields. + line = reader.ReadLine(); + } + // Page and region entries. + List names = null; + List values = null; + while (true) + { + if (line == null) break; + if (line.Trim().Length == 0) + { + page = null; + line = reader.ReadLine(); + } + else if (page == null) + { + page = new AtlasPage(); + page.name = line.Trim(); + while (true) + { + if (ReadEntry(entry, line = reader.ReadLine()) == 0) break; + Action field; + if (pageFields.TryGetValue(entry[0], out field)) field(); // Silently ignore unknown page fields. + } + textureLoader.Load(page, Path.Combine(imagesDir, page.name)); + pages.Add(page); + } + else + { + region = new AtlasRegion(); + region.page = page; + region.name = line; + while (true) + { + int count = ReadEntry(entry, line = reader.ReadLine()); + if (count == 0) break; + Action field; + if (regionFields.TryGetValue(entry[0], out field)) + field(); + else + { + if (names == null) + { + names = new List(8); + values = new List(8); + } + names.Add(entry[0]); + int[] entryValues = new int[count]; + for (int i = 0; i < count; i++) + int.TryParse(entry[i + 1], NumberStyles.Any, CultureInfo.InvariantCulture, out entryValues[i]); // Silently ignore non-integer values. + values.Add(entryValues); + } + } + if (region.originalWidth == 0 && region.originalHeight == 0) + { + region.originalWidth = region.width; + region.originalHeight = region.height; + } + if (names != null && names.Count > 0) + { + region.names = names.ToArray(); + region.values = values.ToArray(); + names.Clear(); + values.Clear(); + } + region.u = region.x / (float)page.width; + region.v = region.y / (float)page.height; + if (region.degrees == 90) + { + region.u2 = (region.x + region.height) / (float)page.width; + region.v2 = (region.y + region.width) / (float)page.height; + } + else + { + region.u2 = (region.x + region.width) / (float)page.width; + region.v2 = (region.y + region.height) / (float)page.height; + } + regions.Add(region); + } + } + } - static private int ReadEntry (string[] entry, string line) { - if (line == null) return 0; - line = line.Trim(); - if (line.Length == 0) return 0; - int colon = line.IndexOf(':'); - if (colon == -1) return 0; - entry[0] = line.Substring(0, colon).Trim(); - for (int i = 1, lastMatch = colon + 1; ; i++) { - int comma = line.IndexOf(',', lastMatch); - if (comma == -1) { - entry[i] = line.Substring(lastMatch).Trim(); - return i; - } - entry[i] = line.Substring(lastMatch, comma - lastMatch).Trim(); - lastMatch = comma + 1; - if (i == 4) return 4; - } - } + static private int ReadEntry(string[] entry, string line) + { + if (line == null) return 0; + line = line.Trim(); + if (line.Length == 0) return 0; + int colon = line.IndexOf(':'); + if (colon == -1) return 0; + entry[0] = line.Substring(0, colon).Trim(); + for (int i = 1, lastMatch = colon + 1; ; i++) + { + int comma = line.IndexOf(',', lastMatch); + if (comma == -1) + { + entry[i] = line.Substring(lastMatch).Trim(); + return i; + } + entry[i] = line.Substring(lastMatch, comma - lastMatch).Trim(); + lastMatch = comma + 1; + if (i == 4) return 4; + } + } - public void FlipV () { - for (int i = 0, n = regions.Count; i < n; i++) { - AtlasRegion region = regions[i]; - region.v = 1 - region.v; - region.v2 = 1 - region.v2; - } - } + public void FlipV() + { + for (int i = 0, n = regions.Count; i < n; i++) + { + AtlasRegion region = regions[i]; + region.v = 1 - region.v; + region.v2 = 1 - region.v2; + } + } - /// 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. - /// The region, or null. - public AtlasRegion FindRegion (string name) { - for (int i = 0, n = regions.Count; i < n; i++) - if (regions[i].name == name) return regions[i]; - return null; - } + /// 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. + /// The region, or null. + public AtlasRegion FindRegion(string name) + { + for (int i = 0, n = regions.Count; i < n; i++) + if (regions[i].name == name) return regions[i]; + return null; + } - public void Dispose () { - if (textureLoader == null) return; - for (int i = 0, n = pages.Count; i < n; i++) - textureLoader.Unload(pages[i].rendererObject); - } - } + public void Dispose() + { + if (textureLoader == null) return; + for (int i = 0, n = pages.Count; i < n; i++) + textureLoader.Unload(pages[i].rendererObject); + } + } - public enum Format { - Alpha, - Intensity, - LuminanceAlpha, - RGB565, - RGBA4444, - RGB888, - RGBA8888 - } + public enum Format + { + Alpha, + Intensity, + LuminanceAlpha, + RGB565, + RGBA4444, + RGB888, + RGBA8888 + } - public enum TextureFilter { - Nearest, - Linear, - MipMap, - MipMapNearestNearest, - MipMapLinearNearest, - MipMapNearestLinear, - MipMapLinearLinear - } + public enum TextureFilter + { + Nearest, + Linear, + MipMap, + MipMapNearestNearest, + MipMapLinearNearest, + MipMapNearestLinear, + MipMapLinearLinear + } - public enum TextureWrap { - MirroredRepeat, - ClampToEdge, - Repeat - } + public enum TextureWrap + { + MirroredRepeat, + ClampToEdge, + Repeat + } - public class AtlasPage { - public string name; - public int width, height; - public Format format = Format.RGBA8888; - public TextureFilter minFilter = TextureFilter.Nearest; - public TextureFilter magFilter = TextureFilter.Nearest; - public TextureWrap uWrap = TextureWrap.ClampToEdge; - public TextureWrap vWrap = TextureWrap.ClampToEdge; - public bool pma; - public object rendererObject; + public class AtlasPage + { + public string name; + public int width, height; + public Format format = Format.RGBA8888; + public TextureFilter minFilter = TextureFilter.Nearest; + public TextureFilter magFilter = TextureFilter.Nearest; + public TextureWrap uWrap = TextureWrap.ClampToEdge; + public TextureWrap vWrap = TextureWrap.ClampToEdge; + public bool pma; + public object rendererObject; - public AtlasPage Clone () { - return MemberwiseClone() as AtlasPage; - } - } + public AtlasPage Clone() + { + return MemberwiseClone() as AtlasPage; + } + } - public class AtlasRegion { - public AtlasPage page; - public string name; - public int x, y, width, height; - public float u, v, u2, v2; - public float offsetX, offsetY; - public int originalWidth, originalHeight; - public int degrees; - public bool rotate; - public int index; - public string[] names; - public int[][] values; + public class AtlasRegion + { + public AtlasPage page; + public string name; + public int x, y, width, height; + public float u, v, u2, v2; + public float offsetX, offsetY; + public int originalWidth, originalHeight; + public int degrees; + public bool rotate; + public int index; + public string[] names; + public int[][] values; - public AtlasRegion Clone () { - return MemberwiseClone() as AtlasRegion; - } - } + public AtlasRegion Clone() + { + return MemberwiseClone() as AtlasRegion; + } + } - public interface TextureLoader { - void Load (AtlasPage page, string path); - void Unload (Object texture); - } + public interface TextureLoader + { + void Load(AtlasPage page, string path); + void Unload(Object texture); + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Attachments/AtlasAttachmentLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Attachments/AtlasAttachmentLoader.cs index 1f7816f..b7ad176 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Attachments/AtlasAttachmentLoader.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Attachments/AtlasAttachmentLoader.cs @@ -29,80 +29,91 @@ using System; -namespace Spine4_0_31 { +namespace Spine4_0_31 +{ - /// - /// An AttachmentLoader that configures attachments using texture regions from an Atlas. - /// See Loading Skeleton Data in the Spine Runtimes Guide. - /// - public class AtlasAttachmentLoader : AttachmentLoader { - private Atlas[] atlasArray; + /// + /// An AttachmentLoader that configures attachments using texture regions from an Atlas. + /// See Loading Skeleton Data in the Spine Runtimes Guide. + /// + public class AtlasAttachmentLoader : AttachmentLoader + { + private Atlas[] atlasArray; - public AtlasAttachmentLoader (params Atlas[] atlasArray) { - if (atlasArray == null) throw new ArgumentNullException("atlas", "atlas array cannot be null."); - this.atlasArray = atlasArray; - } + public AtlasAttachmentLoader(params Atlas[] atlasArray) + { + if (atlasArray == null) throw new ArgumentNullException("atlas", "atlas array cannot be null."); + this.atlasArray = atlasArray; + } - public RegionAttachment NewRegionAttachment (Skin skin, string name, string path) { - AtlasRegion region = FindRegion(path); - if (region == null) throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name)); - RegionAttachment attachment = new RegionAttachment(name); - attachment.RendererObject = region; - attachment.SetUVs(region.u, region.v, region.u2, region.v2, region.degrees); - attachment.regionOffsetX = region.offsetX; - attachment.regionOffsetY = region.offsetY; - attachment.regionWidth = region.width; - attachment.regionHeight = region.height; - attachment.regionOriginalWidth = region.originalWidth; - attachment.regionOriginalHeight = region.originalHeight; - return attachment; - } + public RegionAttachment NewRegionAttachment(Skin skin, string name, string path) + { + AtlasRegion region = FindRegion(path); + if (region == null) throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name)); + RegionAttachment attachment = new RegionAttachment(name); + attachment.RendererObject = region; + attachment.SetUVs(region.u, region.v, region.u2, region.v2, region.degrees); + attachment.regionOffsetX = region.offsetX; + attachment.regionOffsetY = region.offsetY; + attachment.regionWidth = region.width; + attachment.regionHeight = region.height; + attachment.regionOriginalWidth = region.originalWidth; + attachment.regionOriginalHeight = region.originalHeight; + return attachment; + } - public MeshAttachment NewMeshAttachment (Skin skin, string name, string path) { - AtlasRegion region = FindRegion(path); - if (region == null) throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name)); - MeshAttachment attachment = new MeshAttachment(name); - attachment.RendererObject = region; - attachment.RegionU = region.u; - attachment.RegionV = region.v; - attachment.RegionU2 = region.u2; - attachment.RegionV2 = region.v2; - attachment.RegionDegrees = region.degrees; - attachment.regionOffsetX = region.offsetX; - attachment.regionOffsetY = region.offsetY; - attachment.regionWidth = region.width; - attachment.regionHeight = region.height; - attachment.regionOriginalWidth = region.originalWidth; - attachment.regionOriginalHeight = region.originalHeight; - return attachment; - } + public MeshAttachment NewMeshAttachment(Skin skin, string name, string path) + { + AtlasRegion region = FindRegion(path); + if (region == null) throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name)); + MeshAttachment attachment = new MeshAttachment(name); + attachment.RendererObject = region; + attachment.RegionU = region.u; + attachment.RegionV = region.v; + attachment.RegionU2 = region.u2; + attachment.RegionV2 = region.v2; + attachment.RegionDegrees = region.degrees; + attachment.regionOffsetX = region.offsetX; + attachment.regionOffsetY = region.offsetY; + attachment.regionWidth = region.width; + attachment.regionHeight = region.height; + attachment.regionOriginalWidth = region.originalWidth; + attachment.regionOriginalHeight = region.originalHeight; + return attachment; + } - public BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, string name) { - return new BoundingBoxAttachment(name); - } + public BoundingBoxAttachment NewBoundingBoxAttachment(Skin skin, string name) + { + return new BoundingBoxAttachment(name); + } - public PathAttachment NewPathAttachment (Skin skin, string name) { - return new PathAttachment(name); - } + public PathAttachment NewPathAttachment(Skin skin, string name) + { + return new PathAttachment(name); + } - public PointAttachment NewPointAttachment (Skin skin, string name) { - return new PointAttachment(name); - } + public PointAttachment NewPointAttachment(Skin skin, string name) + { + return new PointAttachment(name); + } - public ClippingAttachment NewClippingAttachment (Skin skin, string name) { - return new ClippingAttachment(name); - } + public ClippingAttachment NewClippingAttachment(Skin skin, string name) + { + return new ClippingAttachment(name); + } - public AtlasRegion FindRegion (string name) { - AtlasRegion region; + public AtlasRegion FindRegion(string name) + { + AtlasRegion region; - for (int i = 0; i < atlasArray.Length; i++) { - region = atlasArray[i].FindRegion(name); - if (region != null) - return region; - } + for (int i = 0; i < atlasArray.Length; i++) + { + region = atlasArray[i].FindRegion(name); + if (region != null) + return region; + } - return null; - } - } + return null; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Attachments/Attachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Attachments/Attachment.cs index 3fdb9fb..f13e347 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Attachments/Attachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Attachments/Attachment.cs @@ -29,24 +29,29 @@ using System; -namespace Spine4_0_31 { - abstract public class Attachment { - public string Name { get; private set; } +namespace Spine4_0_31 +{ + abstract public class Attachment + { + public string Name { get; private set; } - protected Attachment (string name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null"); - Name = name; - } + protected Attachment(string name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null"); + Name = name; + } - override public string ToString () { - return Name; - } + override public string ToString() + { + return Name; + } - ///Returns a copy of the attachment. - public abstract Attachment Copy (); - } + ///Returns a copy of the attachment. + public abstract Attachment Copy(); + } - public interface IHasRendererObject { - object RendererObject { get; set; } - } + public interface IHasRendererObject + { + object RendererObject { get; set; } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Attachments/AttachmentLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Attachments/AttachmentLoader.cs index 7645178..cd9c12c 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Attachments/AttachmentLoader.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Attachments/AttachmentLoader.cs @@ -27,22 +27,24 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine4_0_31 { - public interface AttachmentLoader { - /// May be null to not load any attachment. - RegionAttachment NewRegionAttachment (Skin skin, string name, string path); +namespace Spine4_0_31 +{ + public interface AttachmentLoader + { + /// May be null to not load any attachment. + RegionAttachment NewRegionAttachment(Skin skin, string name, string path); - /// May be null to not load any attachment. - MeshAttachment NewMeshAttachment (Skin skin, string name, string path); + /// May be null to not load any attachment. + MeshAttachment NewMeshAttachment(Skin skin, string name, string path); - /// May be null to not load any attachment. - BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, string name); + /// May be null to not load any attachment. + BoundingBoxAttachment NewBoundingBoxAttachment(Skin skin, string name); - /// May be null to not load any attachment - PathAttachment NewPathAttachment (Skin skin, string name); + /// May be null to not load any attachment + PathAttachment NewPathAttachment(Skin skin, string name); - PointAttachment NewPointAttachment (Skin skin, string name); + PointAttachment NewPointAttachment(Skin skin, string name); - ClippingAttachment NewClippingAttachment (Skin skin, string name); - } + ClippingAttachment NewClippingAttachment(Skin skin, string name); + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Attachments/AttachmentType.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Attachments/AttachmentType.cs index b6ca666..e30e958 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Attachments/AttachmentType.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Attachments/AttachmentType.cs @@ -27,8 +27,10 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine4_0_31 { - public enum AttachmentType { - Region, Boundingbox, Mesh, Linkedmesh, Path, Point, Clipping - } +namespace Spine4_0_31 +{ + public enum AttachmentType + { + Region, Boundingbox, Mesh, Linkedmesh, Path, Point, Clipping + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Attachments/BoundingBoxAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Attachments/BoundingBoxAttachment.cs index b684860..4696844 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Attachments/BoundingBoxAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Attachments/BoundingBoxAttachment.cs @@ -27,19 +27,21 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; +namespace Spine4_0_31 +{ + /// Attachment that has a polygon for bounds checking. + public class BoundingBoxAttachment : VertexAttachment + { + public BoundingBoxAttachment(string name) + : base(name) + { + } -namespace Spine4_0_31 { - /// Attachment that has a polygon for bounds checking. - public class BoundingBoxAttachment : VertexAttachment { - public BoundingBoxAttachment (string name) - : base(name) { - } - - public override Attachment Copy () { - BoundingBoxAttachment copy = new BoundingBoxAttachment(this.Name); - CopyTo(copy); - return copy; - } - } + public override Attachment Copy() + { + BoundingBoxAttachment copy = new BoundingBoxAttachment(this.Name); + CopyTo(copy); + return copy; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Attachments/ClippingAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Attachments/ClippingAttachment.cs index 5862cf1..347054b 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Attachments/ClippingAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Attachments/ClippingAttachment.cs @@ -27,22 +27,24 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; +namespace Spine4_0_31 +{ + public class ClippingAttachment : VertexAttachment + { + internal SlotData endSlot; -namespace Spine4_0_31 { - public class ClippingAttachment : VertexAttachment { - internal SlotData endSlot; + public SlotData EndSlot { get { return endSlot; } set { endSlot = value; } } - public SlotData EndSlot { get { return endSlot; } set { endSlot = value; } } + public ClippingAttachment(string name) : base(name) + { + } - public ClippingAttachment (string name) : base(name) { - } - - public override Attachment Copy () { - ClippingAttachment copy = new ClippingAttachment(this.Name); - CopyTo(copy); - copy.endSlot = endSlot; - return copy; - } - } + public override Attachment Copy() + { + ClippingAttachment copy = new ClippingAttachment(this.Name); + CopyTo(copy); + copy.endSlot = endSlot; + return copy; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Attachments/MeshAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Attachments/MeshAttachment.cs index 26d8f37..090fe11 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Attachments/MeshAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Attachments/MeshAttachment.cs @@ -29,193 +29,214 @@ using System; -namespace Spine4_0_31 { - /// Attachment that displays a texture region using a mesh. - public class MeshAttachment : VertexAttachment, IHasRendererObject { - internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; - private MeshAttachment parentMesh; - internal float[] uvs, regionUVs; - internal int[] triangles; - internal float r = 1, g = 1, b = 1, a = 1; - internal int hulllength; - - public int HullLength { get { return hulllength; } set { hulllength = value; } } - public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } } - /// The UV pair for each vertex, normalized within the entire texture. - public float[] UVs { get { return uvs; } set { uvs = value; } } - public int[] Triangles { get { return triangles; } set { triangles = value; } } - - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } - - public string Path { get; set; } - public object RendererObject { get; set; } - public float RegionU { get; set; } - public float RegionV { get; set; } - public float RegionU2 { get; set; } - public float RegionV2 { get; set; } - public int RegionDegrees { get; set; } - public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } - public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. - public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } - public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. - public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } - public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. - - public MeshAttachment ParentMesh { - get { return parentMesh; } - set { - parentMesh = value; - if (value != null) { - bones = value.bones; - vertices = value.vertices; - worldVerticesLength = value.worldVerticesLength; - regionUVs = value.regionUVs; - triangles = value.triangles; - HullLength = value.HullLength; - Edges = value.Edges; - Width = value.Width; - Height = value.Height; - } - } - } - - // Nonessential. - public int[] Edges { get; set; } - public float Width { get; set; } - public float Height { get; set; } - - public MeshAttachment (string name) - : base(name) { - } - - public void UpdateUVs () { - float[] regionUVs = this.regionUVs; - if (this.uvs == null || this.uvs.Length != regionUVs.Length) this.uvs = new float[regionUVs.Length]; - float[] uvs = this.uvs; - float u = RegionU, v = RegionV, width = 0, height = 0; - - if (RegionDegrees == 90) { - float textureHeight = this.regionWidth / (RegionV2 - RegionV); - float textureWidth = this.regionHeight / (RegionU2 - RegionU); - u -= (RegionOriginalHeight - RegionOffsetY - RegionHeight) / textureWidth; - v -= (RegionOriginalWidth - RegionOffsetX - RegionWidth) / textureHeight; - width = RegionOriginalHeight / textureWidth; - height = RegionOriginalWidth / textureHeight; - - for (int i = 0, n = uvs.Length; i < n; i += 2) { - uvs[i] = u + regionUVs[i + 1] * width; - uvs[i + 1] = v + (1 - regionUVs[i]) * height; - } - } else if (RegionDegrees == 180) { - float textureWidth = this.regionWidth / (RegionU2 - RegionU); - float textureHeight = this.regionHeight / (RegionV2 - RegionV); - u -= (RegionOriginalWidth - RegionOffsetX - RegionWidth) / textureWidth; - v -= RegionOffsetY / textureHeight; - width = RegionOriginalWidth / textureWidth; - height = RegionOriginalHeight / textureHeight; - - for (int i = 0, n = uvs.Length; i < n; i += 2) { - uvs[i] = u + (1 - regionUVs[i]) * width; - uvs[i + 1] = v + (1 - regionUVs[i + 1]) * height; - } - } else if (RegionDegrees == 270) { - float textureWidth = this.regionWidth / (RegionU2 - RegionU); - float textureHeight = this.regionHeight / (RegionV2 - RegionV); - u -= RegionOffsetY / textureWidth; - v -= RegionOffsetX / textureHeight; - width = RegionOriginalHeight / textureWidth; - height = RegionOriginalWidth / textureHeight; - - for (int i = 0, n = uvs.Length; i < n; i += 2) { - uvs[i] = u + (1 - regionUVs[i + 1]) * width; - uvs[i + 1] = v + regionUVs[i] * height; - } - } else { - float textureWidth = this.regionWidth / (RegionU2 - RegionU); - float textureHeight = this.regionHeight / (RegionV2 - RegionV); - u -= RegionOffsetX / textureWidth; - v -= (RegionOriginalHeight - RegionOffsetY - RegionHeight) / textureHeight; - width = RegionOriginalWidth / textureWidth; - height = RegionOriginalHeight / textureHeight; - - for (int i = 0, n = uvs.Length; i < n; i += 2) { - uvs[i] = u + regionUVs[i] * width; - uvs[i + 1] = v + regionUVs[i + 1] * height; - } - } - } - - public override Attachment Copy () { - if (parentMesh != null) return NewLinkedMesh(); - - MeshAttachment copy = new MeshAttachment(this.Name); - copy.RendererObject = RendererObject; - copy.regionOffsetX = regionOffsetX; - copy.regionOffsetY = regionOffsetY; - copy.regionWidth = regionWidth; - copy.regionHeight = regionHeight; - copy.regionOriginalWidth = regionOriginalWidth; - copy.regionOriginalHeight = regionOriginalHeight; - copy.RegionDegrees = RegionDegrees; - copy.RegionU = RegionU; - copy.RegionV = RegionV; - copy.RegionU2 = RegionU2; - copy.RegionV2 = RegionV2; - - copy.Path = Path; - copy.r = r; - copy.g = g; - copy.b = b; - copy.a = a; - - CopyTo(copy); - copy.regionUVs = new float[regionUVs.Length]; - Array.Copy(regionUVs, 0, copy.regionUVs, 0, regionUVs.Length); - copy.uvs = new float[uvs.Length]; - Array.Copy(uvs, 0, copy.uvs, 0, uvs.Length); - copy.triangles = new int[triangles.Length]; - Array.Copy(triangles, 0, copy.triangles, 0, triangles.Length); - copy.HullLength = HullLength; - - // Nonessential. - if (Edges != null) { - copy.Edges = new int[Edges.Length]; - Array.Copy(Edges, 0, copy.Edges, 0, Edges.Length); - } - copy.Width = Width; - copy.Height = Height; - return copy; - } - - ///Returns a new mesh with this mesh set as the . - public MeshAttachment NewLinkedMesh () { - MeshAttachment mesh = new MeshAttachment(Name); - mesh.RendererObject = RendererObject; - mesh.regionOffsetX = regionOffsetX; - mesh.regionOffsetY = regionOffsetY; - mesh.regionWidth = regionWidth; - mesh.regionHeight = regionHeight; - mesh.regionOriginalWidth = regionOriginalWidth; - mesh.regionOriginalHeight = regionOriginalHeight; - mesh.RegionDegrees = RegionDegrees; - mesh.RegionU = RegionU; - mesh.RegionV = RegionV; - mesh.RegionU2 = RegionU2; - mesh.RegionV2 = RegionV2; - - mesh.Path = Path; - mesh.r = r; - mesh.g = g; - mesh.b = b; - mesh.a = a; - - mesh.deformAttachment = deformAttachment; - mesh.ParentMesh = parentMesh != null ? parentMesh : this; - mesh.UpdateUVs(); - return mesh; - } - } +namespace Spine4_0_31 +{ + /// Attachment that displays a texture region using a mesh. + public class MeshAttachment : VertexAttachment, IHasRendererObject + { + internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; + private MeshAttachment parentMesh; + internal float[] uvs, regionUVs; + internal int[] triangles; + internal float r = 1, g = 1, b = 1, a = 1; + internal int hulllength; + + public int HullLength { get { return hulllength; } set { hulllength = value; } } + public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } } + /// The UV pair for each vertex, normalized within the entire texture. + public float[] UVs { get { return uvs; } set { uvs = value; } } + public int[] Triangles { get { return triangles; } set { triangles = value; } } + + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } + + public string Path { get; set; } + public object RendererObject { get; set; } + public float RegionU { get; set; } + public float RegionV { get; set; } + public float RegionU2 { get; set; } + public float RegionV2 { get; set; } + public int RegionDegrees { get; set; } + public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } + public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. + public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } + public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. + public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } + public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. + + public MeshAttachment ParentMesh + { + get { return parentMesh; } + set + { + parentMesh = value; + if (value != null) + { + bones = value.bones; + vertices = value.vertices; + worldVerticesLength = value.worldVerticesLength; + regionUVs = value.regionUVs; + triangles = value.triangles; + HullLength = value.HullLength; + Edges = value.Edges; + Width = value.Width; + Height = value.Height; + } + } + } + + // Nonessential. + public int[] Edges { get; set; } + public float Width { get; set; } + public float Height { get; set; } + + public MeshAttachment(string name) + : base(name) + { + } + + public void UpdateUVs() + { + float[] regionUVs = this.regionUVs; + if (this.uvs == null || this.uvs.Length != regionUVs.Length) this.uvs = new float[regionUVs.Length]; + float[] uvs = this.uvs; + float u = RegionU, v = RegionV, width = 0, height = 0; + + if (RegionDegrees == 90) + { + float textureHeight = this.regionWidth / (RegionV2 - RegionV); + float textureWidth = this.regionHeight / (RegionU2 - RegionU); + u -= (RegionOriginalHeight - RegionOffsetY - RegionHeight) / textureWidth; + v -= (RegionOriginalWidth - RegionOffsetX - RegionWidth) / textureHeight; + width = RegionOriginalHeight / textureWidth; + height = RegionOriginalWidth / textureHeight; + + for (int i = 0, n = uvs.Length; i < n; i += 2) + { + uvs[i] = u + regionUVs[i + 1] * width; + uvs[i + 1] = v + (1 - regionUVs[i]) * height; + } + } + else if (RegionDegrees == 180) + { + float textureWidth = this.regionWidth / (RegionU2 - RegionU); + float textureHeight = this.regionHeight / (RegionV2 - RegionV); + u -= (RegionOriginalWidth - RegionOffsetX - RegionWidth) / textureWidth; + v -= RegionOffsetY / textureHeight; + width = RegionOriginalWidth / textureWidth; + height = RegionOriginalHeight / textureHeight; + + for (int i = 0, n = uvs.Length; i < n; i += 2) + { + uvs[i] = u + (1 - regionUVs[i]) * width; + uvs[i + 1] = v + (1 - regionUVs[i + 1]) * height; + } + } + else if (RegionDegrees == 270) + { + float textureWidth = this.regionWidth / (RegionU2 - RegionU); + float textureHeight = this.regionHeight / (RegionV2 - RegionV); + u -= RegionOffsetY / textureWidth; + v -= RegionOffsetX / textureHeight; + width = RegionOriginalHeight / textureWidth; + height = RegionOriginalWidth / textureHeight; + + for (int i = 0, n = uvs.Length; i < n; i += 2) + { + uvs[i] = u + (1 - regionUVs[i + 1]) * width; + uvs[i + 1] = v + regionUVs[i] * height; + } + } + else + { + float textureWidth = this.regionWidth / (RegionU2 - RegionU); + float textureHeight = this.regionHeight / (RegionV2 - RegionV); + u -= RegionOffsetX / textureWidth; + v -= (RegionOriginalHeight - RegionOffsetY - RegionHeight) / textureHeight; + width = RegionOriginalWidth / textureWidth; + height = RegionOriginalHeight / textureHeight; + + for (int i = 0, n = uvs.Length; i < n; i += 2) + { + uvs[i] = u + regionUVs[i] * width; + uvs[i + 1] = v + regionUVs[i + 1] * height; + } + } + } + + public override Attachment Copy() + { + if (parentMesh != null) return NewLinkedMesh(); + + MeshAttachment copy = new MeshAttachment(this.Name); + copy.RendererObject = RendererObject; + copy.regionOffsetX = regionOffsetX; + copy.regionOffsetY = regionOffsetY; + copy.regionWidth = regionWidth; + copy.regionHeight = regionHeight; + copy.regionOriginalWidth = regionOriginalWidth; + copy.regionOriginalHeight = regionOriginalHeight; + copy.RegionDegrees = RegionDegrees; + copy.RegionU = RegionU; + copy.RegionV = RegionV; + copy.RegionU2 = RegionU2; + copy.RegionV2 = RegionV2; + + copy.Path = Path; + copy.r = r; + copy.g = g; + copy.b = b; + copy.a = a; + + CopyTo(copy); + copy.regionUVs = new float[regionUVs.Length]; + Array.Copy(regionUVs, 0, copy.regionUVs, 0, regionUVs.Length); + copy.uvs = new float[uvs.Length]; + Array.Copy(uvs, 0, copy.uvs, 0, uvs.Length); + copy.triangles = new int[triangles.Length]; + Array.Copy(triangles, 0, copy.triangles, 0, triangles.Length); + copy.HullLength = HullLength; + + // Nonessential. + if (Edges != null) + { + copy.Edges = new int[Edges.Length]; + Array.Copy(Edges, 0, copy.Edges, 0, Edges.Length); + } + copy.Width = Width; + copy.Height = Height; + return copy; + } + + ///Returns a new mesh with this mesh set as the . + public MeshAttachment NewLinkedMesh() + { + MeshAttachment mesh = new MeshAttachment(Name); + mesh.RendererObject = RendererObject; + mesh.regionOffsetX = regionOffsetX; + mesh.regionOffsetY = regionOffsetY; + mesh.regionWidth = regionWidth; + mesh.regionHeight = regionHeight; + mesh.regionOriginalWidth = regionOriginalWidth; + mesh.regionOriginalHeight = regionOriginalHeight; + mesh.RegionDegrees = RegionDegrees; + mesh.RegionU = RegionU; + mesh.RegionV = RegionV; + mesh.RegionU2 = RegionU2; + mesh.RegionV2 = RegionV2; + + mesh.Path = Path; + mesh.r = r; + mesh.g = g; + mesh.b = b; + mesh.a = a; + + mesh.deformAttachment = deformAttachment; + mesh.ParentMesh = parentMesh != null ? parentMesh : this; + mesh.UpdateUVs(); + return mesh; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Attachments/PathAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Attachments/PathAttachment.cs index 5656998..107a2b0 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Attachments/PathAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Attachments/PathAttachment.cs @@ -28,33 +28,36 @@ *****************************************************************************/ using System; -using System.Collections.Generic; -namespace Spine4_0_31 { - public class PathAttachment : VertexAttachment { - internal float[] lengths; - internal bool closed, constantSpeed; +namespace Spine4_0_31 +{ + public class PathAttachment : VertexAttachment + { + internal float[] lengths; + internal bool closed, constantSpeed; - /// The length in the setup pose from the start of the path to the end of each curve. - public float[] Lengths { get { return lengths; } set { lengths = value; } } - /// If true, the start and end knots are connected. - public bool Closed { get { return closed; } set { closed = value; } } - /// If true, additional calculations are performed to make computing positions along the path more accurate and movement along - /// the path have a constant speed. - public bool ConstantSpeed { get { return constantSpeed; } set { constantSpeed = value; } } + /// The length in the setup pose from the start of the path to the end of each curve. + public float[] Lengths { get { return lengths; } set { lengths = value; } } + /// If true, the start and end knots are connected. + public bool Closed { get { return closed; } set { closed = value; } } + /// If true, additional calculations are performed to make computing positions along the path more accurate and movement along + /// the path have a constant speed. + public bool ConstantSpeed { get { return constantSpeed; } set { constantSpeed = value; } } - public PathAttachment (String name) - : base(name) { - } + public PathAttachment(String name) + : base(name) + { + } - public override Attachment Copy () { - PathAttachment copy = new PathAttachment(this.Name); - CopyTo(copy); - copy.lengths = new float[lengths.Length]; - Array.Copy(lengths, 0, copy.lengths, 0, lengths.Length); - copy.closed = closed; - copy.constantSpeed = constantSpeed; - return copy; - } - } + public override Attachment Copy() + { + PathAttachment copy = new PathAttachment(this.Name); + CopyTo(copy); + copy.lengths = new float[lengths.Length]; + Array.Copy(lengths, 0, copy.lengths, 0, lengths.Length); + copy.closed = closed; + copy.constantSpeed = constantSpeed; + return copy; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Attachments/PointAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Attachments/PointAttachment.cs index 5cea020..5700447 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Attachments/PointAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Attachments/PointAttachment.cs @@ -27,41 +27,47 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine4_0_31 { - /// - /// 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 Point Attachments in the Spine User Guide. - ///

- public class PointAttachment : Attachment { - internal float x, y, rotation; - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public float Rotation { get { return rotation; } set { rotation = value; } } +namespace Spine4_0_31 +{ + /// + /// 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 Point Attachments in the Spine User Guide. + ///

+ public class PointAttachment : Attachment + { + internal float x, y, rotation; + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public float Rotation { get { return rotation; } set { rotation = value; } } - public PointAttachment (string name) - : base(name) { - } + public PointAttachment(string name) + : base(name) + { + } - public void ComputeWorldPosition (Bone bone, out float ox, out float oy) { - bone.LocalToWorld(this.x, this.y, out ox, out oy); - } + public void ComputeWorldPosition(Bone bone, out float ox, out float oy) + { + bone.LocalToWorld(this.x, this.y, out ox, out oy); + } - public float ComputeWorldRotation (Bone bone) { - float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation); - float ix = cos * bone.a + sin * bone.b; - float iy = cos * bone.c + sin * bone.d; - return MathUtils.Atan2(iy, ix) * MathUtils.RadDeg; - } + public float ComputeWorldRotation(Bone bone) + { + float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation); + float ix = cos * bone.a + sin * bone.b; + float iy = cos * bone.c + sin * bone.d; + return MathUtils.Atan2(iy, ix) * MathUtils.RadDeg; + } - public override Attachment Copy () { - PointAttachment copy = new PointAttachment(this.Name); - copy.x = x; - copy.y = y; - copy.rotation = rotation; - return copy; - } - } + public override Attachment Copy() + { + PointAttachment copy = new PointAttachment(this.Name); + copy.x = x; + copy.y = y; + copy.rotation = rotation; + return copy; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Attachments/RegionAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Attachments/RegionAttachment.cs index 30db658..b9e2bcc 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Attachments/RegionAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Attachments/RegionAttachment.cs @@ -29,166 +29,176 @@ using System; -namespace Spine4_0_31 { - /// Attachment that displays a texture region. - public class RegionAttachment : Attachment, IHasRendererObject { - public const int BLX = 0; - public const int BLY = 1; - public const int ULX = 2; - public const int ULY = 3; - public const int URX = 4; - public const int URY = 5; - public const int BRX = 6; - public const int BRY = 7; - - internal float x, y, rotation, scaleX = 1, scaleY = 1, width, height; - internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; - internal float[] offset = new float[8], uvs = new float[8]; - internal float r = 1, g = 1, b = 1, a = 1; - - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public float Rotation { get { return rotation; } set { rotation = value; } } - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - public float ScaleY { get { return scaleY; } set { scaleY = value; } } - public float Width { get { return width; } set { width = value; } } - public float Height { get { return height; } set { height = value; } } - - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } - - public string Path { get; set; } - public object RendererObject { get; set; } - public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } - public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. - public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } - public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. - public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } - public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. - - public float[] Offset { get { return offset; } } - public float[] UVs { get { return uvs; } } - - public RegionAttachment (string name) - : base(name) { - } - - public void UpdateOffset () { - float regionScaleX = width / regionOriginalWidth * scaleX; - float regionScaleY = height / regionOriginalHeight * scaleY; - float localX = -width / 2 * scaleX + regionOffsetX * regionScaleX; - float localY = -height / 2 * scaleY + regionOffsetY * regionScaleY; - float localX2 = localX + regionWidth * regionScaleX; - float localY2 = localY + regionHeight * regionScaleY; - float cos = MathUtils.CosDeg(this.rotation); - float sin = MathUtils.SinDeg(this.rotation); - float x = this.x, y = this.y; - 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; - float[] offset = this.offset; - offset[BLX] = localXCos - localYSin; - offset[BLY] = localYCos + localXSin; - offset[ULX] = localXCos - localY2Sin; - offset[ULY] = localY2Cos + localXSin; - offset[URX] = localX2Cos - localY2Sin; - offset[URY] = localY2Cos + localX2Sin; - offset[BRX] = localX2Cos - localYSin; - offset[BRY] = localYCos + localX2Sin; - } - - public void SetUVs (float u, float v, float u2, float v2, int degrees) { - float[] uvs = this.uvs; - // UV values differ from spine-libgdx. - if (degrees == 90) { - uvs[URX] = u; - uvs[URY] = v2; - uvs[BRX] = u; - uvs[BRY] = v; - uvs[BLX] = u2; - uvs[BLY] = v; - uvs[ULX] = u2; - uvs[ULY] = v2; - } else { - uvs[ULX] = u; - uvs[ULY] = v2; - uvs[URX] = u; - uvs[URY] = v; - uvs[BRX] = u2; - uvs[BRY] = v; - uvs[BLX] = u2; - uvs[BLY] = v2; - } - } - - /// Transforms the attachment's four vertices to world coordinates. - /// The parent bone. - /// The output world vertices. Must have a length greater than or equal to offset + 8. - /// The worldVertices index to begin writing values. - /// The number of worldVertices entries between the value pairs written. - public void ComputeWorldVertices (Bone bone, float[] worldVertices, int offset, int stride = 2) { - float[] vertexOffset = this.offset; - float bwx = bone.worldX, bwy = bone.worldY; - float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - float offsetX, offsetY; - - // Vertex order is different from RegionAttachment.java - offsetX = vertexOffset[BRX]; // 0 - offsetY = vertexOffset[BRY]; // 1 - worldVertices[offset] = offsetX * a + offsetY * b + bwx; // bl - worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; - offset += stride; - - offsetX = vertexOffset[BLX]; // 2 - offsetY = vertexOffset[BLY]; // 3 - worldVertices[offset] = offsetX * a + offsetY * b + bwx; // ul - worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; - offset += stride; - - offsetX = vertexOffset[ULX]; // 4 - offsetY = vertexOffset[ULY]; // 5 - worldVertices[offset] = offsetX * a + offsetY * b + bwx; // ur - worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; - offset += stride; - - offsetX = vertexOffset[URX]; // 6 - offsetY = vertexOffset[URY]; // 7 - worldVertices[offset] = offsetX * a + offsetY * b + bwx; // br - worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; - //offset += stride; - } - - public override Attachment Copy () { - RegionAttachment copy = new RegionAttachment(this.Name); - copy.RendererObject = RendererObject; - copy.regionOffsetX = regionOffsetX; - copy.regionOffsetY = regionOffsetY; - copy.regionWidth = regionWidth; - copy.regionHeight = regionHeight; - copy.regionOriginalWidth = regionOriginalWidth; - copy.regionOriginalHeight = regionOriginalHeight; - copy.Path = Path; - copy.x = x; - copy.y = y; - copy.scaleX = scaleX; - copy.scaleY = scaleY; - copy.rotation = rotation; - copy.width = width; - copy.height = height; - Array.Copy(uvs, 0, copy.uvs, 0, 8); - Array.Copy(offset, 0, copy.offset, 0, 8); - copy.r = r; - copy.g = g; - copy.b = b; - copy.a = a; - return copy; - } - } +namespace Spine4_0_31 +{ + /// Attachment that displays a texture region. + public class RegionAttachment : Attachment, IHasRendererObject + { + public const int BLX = 0; + public const int BLY = 1; + public const int ULX = 2; + public const int ULY = 3; + public const int URX = 4; + public const int URY = 5; + public const int BRX = 6; + public const int BRY = 7; + + internal float x, y, rotation, scaleX = 1, scaleY = 1, width, height; + internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; + internal float[] offset = new float[8], uvs = new float[8]; + internal float r = 1, g = 1, b = 1, a = 1; + + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public float Rotation { get { return rotation; } set { rotation = value; } } + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + public float Width { get { return width; } set { width = value; } } + public float Height { get { return height; } set { height = value; } } + + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } + + public string Path { get; set; } + public object RendererObject { get; set; } + public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } + public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. + public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } + public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. + public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } + public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. + + public float[] Offset { get { return offset; } } + public float[] UVs { get { return uvs; } } + + public RegionAttachment(string name) + : base(name) + { + } + + public void UpdateOffset() + { + float regionScaleX = width / regionOriginalWidth * scaleX; + float regionScaleY = height / regionOriginalHeight * scaleY; + float localX = -width / 2 * scaleX + regionOffsetX * regionScaleX; + float localY = -height / 2 * scaleY + regionOffsetY * regionScaleY; + float localX2 = localX + regionWidth * regionScaleX; + float localY2 = localY + regionHeight * regionScaleY; + float cos = MathUtils.CosDeg(this.rotation); + float sin = MathUtils.SinDeg(this.rotation); + float x = this.x, y = this.y; + 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; + float[] offset = this.offset; + offset[BLX] = localXCos - localYSin; + offset[BLY] = localYCos + localXSin; + offset[ULX] = localXCos - localY2Sin; + offset[ULY] = localY2Cos + localXSin; + offset[URX] = localX2Cos - localY2Sin; + offset[URY] = localY2Cos + localX2Sin; + offset[BRX] = localX2Cos - localYSin; + offset[BRY] = localYCos + localX2Sin; + } + + public void SetUVs(float u, float v, float u2, float v2, int degrees) + { + float[] uvs = this.uvs; + // UV values differ from spine-libgdx. + if (degrees == 90) + { + uvs[URX] = u; + uvs[URY] = v2; + uvs[BRX] = u; + uvs[BRY] = v; + uvs[BLX] = u2; + uvs[BLY] = v; + uvs[ULX] = u2; + uvs[ULY] = v2; + } + else + { + uvs[ULX] = u; + uvs[ULY] = v2; + uvs[URX] = u; + uvs[URY] = v; + uvs[BRX] = u2; + uvs[BRY] = v; + uvs[BLX] = u2; + uvs[BLY] = v2; + } + } + + /// Transforms the attachment's four vertices to world coordinates. + /// The parent bone. + /// The output world vertices. Must have a length greater than or equal to offset + 8. + /// The worldVertices index to begin writing values. + /// The number of worldVertices entries between the value pairs written. + public void ComputeWorldVertices(Bone bone, float[] worldVertices, int offset, int stride = 2) + { + float[] vertexOffset = this.offset; + float bwx = bone.worldX, bwy = bone.worldY; + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + float offsetX, offsetY; + + // Vertex order is different from RegionAttachment.java + offsetX = vertexOffset[BRX]; // 0 + offsetY = vertexOffset[BRY]; // 1 + worldVertices[offset] = offsetX * a + offsetY * b + bwx; // bl + worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; + offset += stride; + + offsetX = vertexOffset[BLX]; // 2 + offsetY = vertexOffset[BLY]; // 3 + worldVertices[offset] = offsetX * a + offsetY * b + bwx; // ul + worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; + offset += stride; + + offsetX = vertexOffset[ULX]; // 4 + offsetY = vertexOffset[ULY]; // 5 + worldVertices[offset] = offsetX * a + offsetY * b + bwx; // ur + worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; + offset += stride; + + offsetX = vertexOffset[URX]; // 6 + offsetY = vertexOffset[URY]; // 7 + worldVertices[offset] = offsetX * a + offsetY * b + bwx; // br + worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; + //offset += stride; + } + + public override Attachment Copy() + { + RegionAttachment copy = new RegionAttachment(this.Name); + copy.RendererObject = RendererObject; + copy.regionOffsetX = regionOffsetX; + copy.regionOffsetY = regionOffsetY; + copy.regionWidth = regionWidth; + copy.regionHeight = regionHeight; + copy.regionOriginalWidth = regionOriginalWidth; + copy.regionOriginalHeight = regionOriginalHeight; + copy.Path = Path; + copy.x = x; + copy.y = y; + copy.scaleX = scaleX; + copy.scaleY = scaleY; + copy.rotation = rotation; + copy.width = width; + copy.height = height; + Array.Copy(uvs, 0, copy.uvs, 0, 8); + Array.Copy(offset, 0, copy.offset, 0, 8); + copy.r = r; + copy.g = g; + copy.b = b; + copy.a = a; + return copy; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Attachments/VertexAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Attachments/VertexAttachment.cs index d4ba6ac..609330f 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Attachments/VertexAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Attachments/VertexAttachment.cs @@ -29,125 +29,146 @@ using System; -namespace Spine4_0_31 { - /// >An attachment with vertices that are transformed by one or more bones and can be deformed by a slot's - /// . - public abstract class VertexAttachment : Attachment { - static int nextID = 0; - static readonly Object nextIdLock = new Object(); +namespace Spine4_0_31 +{ + /// >An attachment with vertices that are transformed by one or more bones and can be deformed by a slot's + /// . + public abstract class VertexAttachment : Attachment + { + static int nextID = 0; + static readonly Object nextIdLock = new Object(); - internal readonly int id; - internal int[] bones; - internal float[] vertices; - internal int worldVerticesLength; - internal VertexAttachment deformAttachment; + internal readonly int id; + internal int[] bones; + internal float[] vertices; + internal int worldVerticesLength; + internal VertexAttachment deformAttachment; - /// Gets a unique ID for this attachment. - public int Id { get { return id; } } - public int[] Bones { get { return bones; } set { bones = value; } } - public float[] Vertices { get { return vertices; } set { vertices = value; } } - public int WorldVerticesLength { get { return worldVerticesLength; } set { worldVerticesLength = value; } } - ///Deform keys for the deform attachment are also applied to this attachment. - /// May be null if no deform keys should be applied. - public VertexAttachment DeformAttachment { get { return deformAttachment; } set { deformAttachment = value; } } + /// Gets a unique ID for this attachment. + public int Id { get { return id; } } + public int[] Bones { get { return bones; } set { bones = value; } } + public float[] Vertices { get { return vertices; } set { vertices = value; } } + public int WorldVerticesLength { get { return worldVerticesLength; } set { worldVerticesLength = value; } } + ///Deform keys for the deform attachment are also applied to this attachment. + /// May be null if no deform keys should be applied. + public VertexAttachment DeformAttachment { get { return deformAttachment; } set { deformAttachment = value; } } - public VertexAttachment (string name) - : base(name) { + public VertexAttachment(string name) + : base(name) + { - deformAttachment = this; - lock (VertexAttachment.nextIdLock) { - id = VertexAttachment.nextID++; - } - } + deformAttachment = this; + lock (VertexAttachment.nextIdLock) + { + id = VertexAttachment.nextID++; + } + } - public void ComputeWorldVertices (Slot slot, float[] worldVertices) { - ComputeWorldVertices(slot, 0, worldVerticesLength, worldVertices, 0); - } + public void ComputeWorldVertices(Slot slot, float[] worldVertices) + { + ComputeWorldVertices(slot, 0, worldVerticesLength, worldVertices, 0); + } - /// - /// Transforms the attachment's local to world coordinates. If the slot's is - /// not empty, it is used to deform the vertices. - /// - /// See World transforms in the Spine - /// Runtimes Guide. - /// - /// The index of the first value to transform. Each vertex has 2 values, x and y. - /// The number of world vertex values to output. Must be less than or equal to - start. - /// The output world vertices. Must have a length greater than or equal to + . - /// The index to begin writing values. - /// The number of entries between the value pairs written. - public void ComputeWorldVertices (Slot slot, int start, int count, float[] worldVertices, int offset, int stride = 2) { - count = offset + (count >> 1) * stride; - ExposedList deformArray = slot.deform; - float[] vertices = this.vertices; - int[] bones = this.bones; - if (bones == null) { - if (deformArray.Count > 0) vertices = deformArray.Items; - Bone bone = slot.bone; - float x = bone.worldX, y = bone.worldY; - float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - for (int vv = start, w = offset; w < count; vv += 2, w += stride) { - float vx = vertices[vv], 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 (int i = 0; i < start; i += 2) { - int n = bones[v]; - v += n + 1; - skip += n; - } - Bone[] skeletonBones = slot.bone.skeleton.bones.Items; - if (deformArray.Count == 0) { - for (int w = offset, b = skip * 3; w < count; w += stride) { - float wx = 0, wy = 0; - int n = bones[v++]; - n += v; - for (; v < n; v++, b += 3) { - Bone bone = skeletonBones[bones[v]]; - float vx = vertices[b], vy = vertices[b + 1], 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 { - float[] deform = deformArray.Items; - for (int w = offset, b = skip * 3, f = skip << 1; w < count; w += stride) { - float wx = 0, wy = 0; - int n = bones[v++]; - n += v; - for (; v < n; v++, b += 3, f += 2) { - Bone bone = skeletonBones[bones[v]]; - float vx = vertices[b] + deform[f], vy = vertices[b + 1] + deform[f + 1], 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; - } - } - } + /// + /// Transforms the attachment's local to world coordinates. If the slot's is + /// not empty, it is used to deform the vertices. + /// + /// See World transforms in the Spine + /// Runtimes Guide. + /// + /// The index of the first value to transform. Each vertex has 2 values, x and y. + /// The number of world vertex values to output. Must be less than or equal to - start. + /// The output world vertices. Must have a length greater than or equal to + . + /// The index to begin writing values. + /// The number of entries between the value pairs written. + public void ComputeWorldVertices(Slot slot, int start, int count, float[] worldVertices, int offset, int stride = 2) + { + count = offset + (count >> 1) * stride; + ExposedList deformArray = slot.deform; + float[] vertices = this.vertices; + int[] bones = this.bones; + if (bones == null) + { + if (deformArray.Count > 0) vertices = deformArray.Items; + Bone bone = slot.bone; + float x = bone.worldX, y = bone.worldY; + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + for (int vv = start, w = offset; w < count; vv += 2, w += stride) + { + float vx = vertices[vv], 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 (int i = 0; i < start; i += 2) + { + int n = bones[v]; + v += n + 1; + skip += n; + } + Bone[] skeletonBones = slot.bone.skeleton.bones.Items; + if (deformArray.Count == 0) + { + for (int w = offset, b = skip * 3; w < count; w += stride) + { + float wx = 0, wy = 0; + int n = bones[v++]; + n += v; + for (; v < n; v++, b += 3) + { + Bone bone = skeletonBones[bones[v]]; + float vx = vertices[b], vy = vertices[b + 1], 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 + { + float[] deform = deformArray.Items; + for (int w = offset, b = skip * 3, f = skip << 1; w < count; w += stride) + { + float wx = 0, wy = 0; + int n = bones[v++]; + n += v; + for (; v < n; v++, b += 3, f += 2) + { + Bone bone = skeletonBones[bones[v]]; + float vx = vertices[b] + deform[f], vy = vertices[b + 1] + deform[f + 1], 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; + } + } + } - ///Does not copy id (generated) or name (set on construction). - internal void CopyTo (VertexAttachment attachment) { - if (bones != null) { - attachment.bones = new int[bones.Length]; - Array.Copy(bones, 0, attachment.bones, 0, bones.Length); - } else - attachment.bones = null; + ///Does not copy id (generated) or name (set on construction). + internal void CopyTo(VertexAttachment attachment) + { + if (bones != null) + { + attachment.bones = new int[bones.Length]; + Array.Copy(bones, 0, attachment.bones, 0, bones.Length); + } + else + attachment.bones = null; - if (vertices != null) { - attachment.vertices = new float[vertices.Length]; - Array.Copy(vertices, 0, attachment.vertices, 0, vertices.Length); - } else - attachment.vertices = null; + if (vertices != null) + { + attachment.vertices = new float[vertices.Length]; + Array.Copy(vertices, 0, attachment.vertices, 0, vertices.Length); + } + else + attachment.vertices = null; - attachment.worldVerticesLength = worldVerticesLength; - attachment.deformAttachment = deformAttachment; - } - } + attachment.worldVerticesLength = worldVerticesLength; + attachment.deformAttachment = deformAttachment; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/BlendMode.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/BlendMode.cs index 0fdc78f..77c172b 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/BlendMode.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/BlendMode.cs @@ -27,8 +27,10 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine4_0_31 { - public enum BlendMode { - Normal, Additive, Multiply, Screen - } +namespace Spine4_0_31 +{ + public enum BlendMode + { + Normal, Additive, Multiply, Screen + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Bone.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Bone.cs index 282cdd7..6890e8d 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Bone.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Bone.cs @@ -29,350 +29,381 @@ using System; -namespace Spine4_0_31 { - /// - /// 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. - /// - /// - public class Bone : IUpdatable { - static public bool yDown; - - internal BoneData data; - internal Skeleton skeleton; - internal Bone parent; - internal ExposedList children = new ExposedList(); - internal float x, y, rotation, scaleX, scaleY, shearX, shearY; - internal float ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY; - - internal float a, b, worldX; - internal float c, d, worldY; - - internal bool sorted, active; - - public BoneData Data { get { return data; } } - public Skeleton Skeleton { get { return skeleton; } } - public Bone Parent { get { return parent; } } - public ExposedList Children { get { return children; } } - /// Returns false when the bone has not been computed because is true and the - /// active skin does not contain this bone. - public bool Active { get { return active; } } - /// The local X translation. - public float X { get { return x; } set { x = value; } } - /// The local Y translation. - public float Y { get { return y; } set { y = value; } } - /// The local rotation. - public float Rotation { get { return rotation; } set { rotation = value; } } - - /// The local scaleX. - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - - /// The local scaleY. - public float ScaleY { get { return scaleY; } set { scaleY = value; } } - - /// The local shearX. - public float ShearX { get { return shearX; } set { shearX = value; } } - - /// The local shearY. - public float ShearY { get { return shearY; } set { shearY = value; } } - - /// The rotation, as calculated by any constraints. - public float AppliedRotation { get { return arotation; } set { arotation = value; } } - - /// The applied local x translation. - public float AX { get { return ax; } set { ax = value; } } - - /// The applied local y translation. - public float AY { get { return ay; } set { ay = value; } } - - /// The applied local scaleX. - public float AScaleX { get { return ascaleX; } set { ascaleX = value; } } - - /// The applied local scaleY. - public float AScaleY { get { return ascaleY; } set { ascaleY = value; } } - - /// The applied local shearX. - public float AShearX { get { return ashearX; } set { ashearX = value; } } - - /// The applied local shearY. - public float AShearY { get { return ashearY; } set { ashearY = value; } } - - /// Part of the world transform matrix for the X axis. If changed, should be called. - public float A { get { return a; } set { a = value; } } - /// Part of the world transform matrix for the Y axis. If changed, should be called. - public float B { get { return b; } set { b = value; } } - /// Part of the world transform matrix for the X axis. If changed, should be called. - public float C { get { return c; } set { c = value; } } - /// Part of the world transform matrix for the Y axis. If changed, should be called. - public float D { get { return d; } set { d = value; } } - - /// The world X position. If changed, should be called. - public float WorldX { get { return worldX; } set { worldX = value; } } - /// The world Y position. If changed, should be called. - public float WorldY { get { return worldY; } set { worldY = value; } } - public float WorldRotationX { get { return MathUtils.Atan2(c, a) * MathUtils.RadDeg; } } - public float WorldRotationY { get { return MathUtils.Atan2(d, b) * MathUtils.RadDeg; } } - - /// Returns the magnitide (always positive) of the world scale X. - public float WorldScaleX { get { return (float)Math.Sqrt(a * a + c * c); } } - /// Returns the magnitide (always positive) of the world scale Y. - public float WorldScaleY { get { return (float)Math.Sqrt(b * b + d * d); } } - - /// Copy constructor. Does not copy the bones. - /// May be null. - public Bone (BoneData data, Skeleton skeleton, Bone parent) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.data = data; - this.skeleton = skeleton; - this.parent = parent; - SetToSetupPose(); - } - - /// Computes the world transform using the parent bone and this bone's local applied transform. - public void Update () { - UpdateWorldTransform(ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY); - } - - /// Computes the world transform using the parent bone and this bone's local transform. - public void UpdateWorldTransform () { - UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); - } - - /// Computes the world transform using the parent bone and the specified local transform. The applied transform is set to the - /// specified local transform. Child bones are not updated. - /// - /// See World transforms in the Spine - /// Runtimes Guide. - public void UpdateWorldTransform (float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY) { - ax = x; - ay = y; - arotation = rotation; - ascaleX = scaleX; - ascaleY = scaleY; - ashearX = shearX; - ashearY = shearY; - - Bone parent = this.parent; - if (parent == null) { // Root bone. - float rotationY = rotation + 90 + shearY, sx = skeleton.ScaleX, sy = skeleton.ScaleY; - a = MathUtils.CosDeg(rotation + shearX) * scaleX * sx; - b = MathUtils.CosDeg(rotationY) * scaleY * sx; - c = MathUtils.SinDeg(rotation + shearX) * scaleX * sy; - d = MathUtils.SinDeg(rotationY) * scaleY * sy; - worldX = x * sx + skeleton.x; - worldY = y * sy + skeleton.y; - return; - } - - float 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 (data.transformMode) { - case TransformMode.Normal: { - float rotationY = rotation + 90 + shearY; - float la = MathUtils.CosDeg(rotation + shearX) * scaleX; - float lb = MathUtils.CosDeg(rotationY) * scaleY; - float lc = MathUtils.SinDeg(rotation + shearX) * scaleX; - float ld = MathUtils.SinDeg(rotationY) * scaleY; - a = pa * la + pb * lc; - b = pa * lb + pb * ld; - c = pc * la + pd * lc; - d = pc * lb + pd * ld; - return; - } - case TransformMode.OnlyTranslation: { - float rotationY = rotation + 90 + shearY; - a = MathUtils.CosDeg(rotation + shearX) * scaleX; - b = MathUtils.CosDeg(rotationY) * scaleY; - c = MathUtils.SinDeg(rotation + shearX) * scaleX; - d = MathUtils.SinDeg(rotationY) * scaleY; - break; - } - case TransformMode.NoRotationOrReflection: { - float s = pa * pa + pc * pc, prx; - if (s > 0.0001f) { - s = Math.Abs(pa * pd - pb * pc) / s; - pa /= skeleton.ScaleX; - pc /= skeleton.ScaleY; - pb = pc * s; - pd = pa * s; - prx = MathUtils.Atan2(pc, pa) * MathUtils.RadDeg; - } else { - pa = 0; - pc = 0; - prx = 90 - MathUtils.Atan2(pd, pb) * MathUtils.RadDeg; - } - float rx = rotation + shearX - prx; - float ry = rotation + shearY - prx + 90; - float la = MathUtils.CosDeg(rx) * scaleX; - float lb = MathUtils.CosDeg(ry) * scaleY; - float lc = MathUtils.SinDeg(rx) * scaleX; - float ld = MathUtils.SinDeg(ry) * scaleY; - a = pa * la - pb * lc; - b = pa * lb - pb * ld; - c = pc * la + pd * lc; - d = pc * lb + pd * ld; - break; - } - case TransformMode.NoScale: - case TransformMode.NoScaleOrReflection: { - float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation); - float za = (pa * cos + pb * sin) / skeleton.ScaleX; - float zc = (pc * cos + pd * sin) / skeleton.ScaleY; - float s = (float)Math.Sqrt(za * za + zc * zc); - if (s > 0.00001f) s = 1 / s; - za *= s; - zc *= s; - s = (float)Math.Sqrt(za * za + zc * zc); - if (data.transformMode == TransformMode.NoScale - && (pa * pd - pb * pc < 0) != (skeleton.ScaleX < 0 != skeleton.ScaleY < 0)) s = -s; - - float r = MathUtils.PI / 2 + MathUtils.Atan2(zc, za); - float zb = MathUtils.Cos(r) * s; - float zd = MathUtils.Sin(r) * s; - float la = MathUtils.CosDeg(shearX) * scaleX; - float lb = MathUtils.CosDeg(90 + shearY) * scaleY; - float lc = MathUtils.SinDeg(shearX) * scaleX; - float ld = MathUtils.SinDeg(90 + shearY) * scaleY; - a = za * la + zb * lc; - b = za * lb + zb * ld; - c = zc * la + zd * lc; - d = zc * lb + zd * ld; - break; - } - } - - a *= skeleton.ScaleX; - b *= skeleton.ScaleX; - c *= skeleton.ScaleY; - d *= skeleton.ScaleY; - } - - public void SetToSetupPose () { - BoneData data = this.data; - x = data.x; - y = data.y; - rotation = data.rotation; - scaleX = data.scaleX; - scaleY = data.scaleY; - shearX = data.shearX; - shearY = data.shearY; - } - - /// - /// Computes the applied transform values from the world transform. - /// - /// If the world transform is modified (by a constraint, , etc) then this method should be called so - /// the applied transform matches the world transform. The applied transform may be needed by other code (eg to apply another - /// constraint). - /// - /// Some information is ambiguous in the world transform, such as -1,-1 scale versus 180 rotation. The applied transform after - /// calling this method is equivalent to the local transform used to compute the world transform, but may not be identical. - /// - internal void UpdateAppliedTransform () { - Bone parent = this.parent; - if (parent == null) { - ax = worldX; - ay = worldY; - arotation = MathUtils.Atan2(c, a) * MathUtils.RadDeg; - ascaleX = (float)Math.Sqrt(a * a + c * c); - ascaleY = (float)Math.Sqrt(b * b + d * d); - ashearX = 0; - ashearY = MathUtils.Atan2(a * b + c * d, a * d - b * c) * MathUtils.RadDeg; - return; - } - float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d; - float pid = 1 / (pa * pd - pb * pc); - float dx = worldX - parent.worldX, dy = worldY - parent.worldY; - ax = (dx * pd * pid - dy * pb * pid); - ay = (dy * pa * pid - dx * pc * pid); - float ia = pid * pd; - float id = pid * pa; - float ib = pid * pb; - float ic = pid * pc; - float ra = ia * a - ib * c; - float rb = ia * b - ib * d; - float rc = id * c - ic * a; - float rd = id * d - ic * b; - ashearX = 0; - ascaleX = (float)Math.Sqrt(ra * ra + rc * rc); - if (ascaleX > 0.0001f) { - float det = ra * rd - rb * rc; - ascaleY = det / ascaleX; - ashearY = MathUtils.Atan2(ra * rb + rc * rd, det) * MathUtils.RadDeg; - arotation = MathUtils.Atan2(rc, ra) * MathUtils.RadDeg; - } else { - ascaleX = 0; - ascaleY = (float)Math.Sqrt(rb * rb + rd * rd); - ashearY = 0; - arotation = 90 - MathUtils.Atan2(rd, rb) * MathUtils.RadDeg; - } - } - - public void WorldToLocal (float worldX, float worldY, out float localX, out float localY) { - float a = this.a, b = this.b, c = this.c, d = this.d; - float det = a * d - b * c; - float x = worldX - this.worldX, y = worldY - this.worldY; - localX = (x * d - y * b) / det; - localY = (y * a - x * c) / det; - } - - public void LocalToWorld (float localX, float localY, out float worldX, out float worldY) { - worldX = localX * a + localY * b + this.worldX; - worldY = localX * c + localY * d + this.worldY; - } - - public float WorldToLocalRotationX { - get { - Bone parent = this.parent; - if (parent == null) return arotation; - float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, a = this.a, c = this.c; - return MathUtils.Atan2(pa * c - pc * a, pd * a - pb * c) * MathUtils.RadDeg; - } - } - - public float WorldToLocalRotationY { - get { - Bone parent = this.parent; - if (parent == null) return arotation; - float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, b = this.b, d = this.d; - return MathUtils.Atan2(pa * d - pc * b, pd * b - pb * d) * MathUtils.RadDeg; - } - } - - public float WorldToLocalRotation (float worldRotation) { - float sin = MathUtils.SinDeg(worldRotation), cos = MathUtils.CosDeg(worldRotation); - return MathUtils.Atan2(a * sin - c * cos, d * cos - b * sin) * MathUtils.RadDeg + rotation - shearX; - } - - public float LocalToWorldRotation (float localRotation) { - localRotation -= rotation - shearX; - float sin = MathUtils.SinDeg(localRotation), cos = MathUtils.CosDeg(localRotation); - return MathUtils.Atan2(cos * c + sin * d, cos * a + sin * b) * MathUtils.RadDeg; - } - - /// - /// Rotates the world transform the specified amount. - /// - /// After changes are made to the world transform, should be called and will - /// need to be called on any child bones, recursively. - /// - public void RotateWorld (float degrees) { - float a = this.a, b = this.b, c = this.c, d = this.d; - float cos = MathUtils.CosDeg(degrees), sin = MathUtils.SinDeg(degrees); - this.a = cos * a - sin * c; - this.b = cos * b - sin * d; - this.c = sin * a + cos * c; - this.d = sin * b + cos * d; - } - - override public string ToString () { - return data.name; - } - } +namespace Spine4_0_31 +{ + /// + /// 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. + /// + /// + public class Bone : IUpdatable + { + static public bool yDown; + + internal BoneData data; + internal Skeleton skeleton; + internal Bone parent; + internal ExposedList children = new ExposedList(); + internal float x, y, rotation, scaleX, scaleY, shearX, shearY; + internal float ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY; + + internal float a, b, worldX; + internal float c, d, worldY; + + internal bool sorted, active; + + public BoneData Data { get { return data; } } + public Skeleton Skeleton { get { return skeleton; } } + public Bone Parent { get { return parent; } } + public ExposedList Children { get { return children; } } + /// Returns false when the bone has not been computed because is true and the + /// active skin does not contain this bone. + public bool Active { get { return active; } } + /// The local X translation. + public float X { get { return x; } set { x = value; } } + /// The local Y translation. + public float Y { get { return y; } set { y = value; } } + /// The local rotation. + public float Rotation { get { return rotation; } set { rotation = value; } } + + /// The local scaleX. + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + + /// The local scaleY. + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + + /// The local shearX. + public float ShearX { get { return shearX; } set { shearX = value; } } + + /// The local shearY. + public float ShearY { get { return shearY; } set { shearY = value; } } + + /// The rotation, as calculated by any constraints. + public float AppliedRotation { get { return arotation; } set { arotation = value; } } + + /// The applied local x translation. + public float AX { get { return ax; } set { ax = value; } } + + /// The applied local y translation. + public float AY { get { return ay; } set { ay = value; } } + + /// The applied local scaleX. + public float AScaleX { get { return ascaleX; } set { ascaleX = value; } } + + /// The applied local scaleY. + public float AScaleY { get { return ascaleY; } set { ascaleY = value; } } + + /// The applied local shearX. + public float AShearX { get { return ashearX; } set { ashearX = value; } } + + /// The applied local shearY. + public float AShearY { get { return ashearY; } set { ashearY = value; } } + + /// Part of the world transform matrix for the X axis. If changed, should be called. + public float A { get { return a; } set { a = value; } } + /// Part of the world transform matrix for the Y axis. If changed, should be called. + public float B { get { return b; } set { b = value; } } + /// Part of the world transform matrix for the X axis. If changed, should be called. + public float C { get { return c; } set { c = value; } } + /// Part of the world transform matrix for the Y axis. If changed, should be called. + public float D { get { return d; } set { d = value; } } + + /// The world X position. If changed, should be called. + public float WorldX { get { return worldX; } set { worldX = value; } } + /// The world Y position. If changed, should be called. + public float WorldY { get { return worldY; } set { worldY = value; } } + public float WorldRotationX { get { return MathUtils.Atan2(c, a) * MathUtils.RadDeg; } } + public float WorldRotationY { get { return MathUtils.Atan2(d, b) * MathUtils.RadDeg; } } + + /// Returns the magnitide (always positive) of the world scale X. + public float WorldScaleX { get { return (float)Math.Sqrt(a * a + c * c); } } + /// Returns the magnitide (always positive) of the world scale Y. + public float WorldScaleY { get { return (float)Math.Sqrt(b * b + d * d); } } + + /// Copy constructor. Does not copy the bones. + /// May be null. + public Bone(BoneData data, Skeleton skeleton, Bone parent) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + this.skeleton = skeleton; + this.parent = parent; + SetToSetupPose(); + } + + /// Computes the world transform using the parent bone and this bone's local applied transform. + public void Update() + { + UpdateWorldTransform(ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY); + } + + /// Computes the world transform using the parent bone and this bone's local transform. + public void UpdateWorldTransform() + { + UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); + } + + /// Computes the world transform using the parent bone and the specified local transform. The applied transform is set to the + /// specified local transform. Child bones are not updated. + /// + /// See World transforms in the Spine + /// Runtimes Guide. + public void UpdateWorldTransform(float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY) + { + ax = x; + ay = y; + arotation = rotation; + ascaleX = scaleX; + ascaleY = scaleY; + ashearX = shearX; + ashearY = shearY; + + Bone parent = this.parent; + if (parent == null) + { // Root bone. + float rotationY = rotation + 90 + shearY, sx = skeleton.ScaleX, sy = skeleton.ScaleY; + a = MathUtils.CosDeg(rotation + shearX) * scaleX * sx; + b = MathUtils.CosDeg(rotationY) * scaleY * sx; + c = MathUtils.SinDeg(rotation + shearX) * scaleX * sy; + d = MathUtils.SinDeg(rotationY) * scaleY * sy; + worldX = x * sx + skeleton.x; + worldY = y * sy + skeleton.y; + return; + } + + float 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 (data.transformMode) + { + case TransformMode.Normal: + { + float rotationY = rotation + 90 + shearY; + float la = MathUtils.CosDeg(rotation + shearX) * scaleX; + float lb = MathUtils.CosDeg(rotationY) * scaleY; + float lc = MathUtils.SinDeg(rotation + shearX) * scaleX; + float ld = MathUtils.SinDeg(rotationY) * scaleY; + a = pa * la + pb * lc; + b = pa * lb + pb * ld; + c = pc * la + pd * lc; + d = pc * lb + pd * ld; + return; + } + case TransformMode.OnlyTranslation: + { + float rotationY = rotation + 90 + shearY; + a = MathUtils.CosDeg(rotation + shearX) * scaleX; + b = MathUtils.CosDeg(rotationY) * scaleY; + c = MathUtils.SinDeg(rotation + shearX) * scaleX; + d = MathUtils.SinDeg(rotationY) * scaleY; + break; + } + case TransformMode.NoRotationOrReflection: + { + float s = pa * pa + pc * pc, prx; + if (s > 0.0001f) + { + s = Math.Abs(pa * pd - pb * pc) / s; + pa /= skeleton.ScaleX; + pc /= skeleton.ScaleY; + pb = pc * s; + pd = pa * s; + prx = MathUtils.Atan2(pc, pa) * MathUtils.RadDeg; + } + else + { + pa = 0; + pc = 0; + prx = 90 - MathUtils.Atan2(pd, pb) * MathUtils.RadDeg; + } + float rx = rotation + shearX - prx; + float ry = rotation + shearY - prx + 90; + float la = MathUtils.CosDeg(rx) * scaleX; + float lb = MathUtils.CosDeg(ry) * scaleY; + float lc = MathUtils.SinDeg(rx) * scaleX; + float ld = MathUtils.SinDeg(ry) * scaleY; + a = pa * la - pb * lc; + b = pa * lb - pb * ld; + c = pc * la + pd * lc; + d = pc * lb + pd * ld; + break; + } + case TransformMode.NoScale: + case TransformMode.NoScaleOrReflection: + { + float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation); + float za = (pa * cos + pb * sin) / skeleton.ScaleX; + float zc = (pc * cos + pd * sin) / skeleton.ScaleY; + float s = (float)Math.Sqrt(za * za + zc * zc); + if (s > 0.00001f) s = 1 / s; + za *= s; + zc *= s; + s = (float)Math.Sqrt(za * za + zc * zc); + if (data.transformMode == TransformMode.NoScale + && (pa * pd - pb * pc < 0) != (skeleton.ScaleX < 0 != skeleton.ScaleY < 0)) s = -s; + + float r = MathUtils.PI / 2 + MathUtils.Atan2(zc, za); + float zb = MathUtils.Cos(r) * s; + float zd = MathUtils.Sin(r) * s; + float la = MathUtils.CosDeg(shearX) * scaleX; + float lb = MathUtils.CosDeg(90 + shearY) * scaleY; + float lc = MathUtils.SinDeg(shearX) * scaleX; + float ld = MathUtils.SinDeg(90 + shearY) * scaleY; + a = za * la + zb * lc; + b = za * lb + zb * ld; + c = zc * la + zd * lc; + d = zc * lb + zd * ld; + break; + } + } + + a *= skeleton.ScaleX; + b *= skeleton.ScaleX; + c *= skeleton.ScaleY; + d *= skeleton.ScaleY; + } + + public void SetToSetupPose() + { + BoneData data = this.data; + x = data.x; + y = data.y; + rotation = data.rotation; + scaleX = data.scaleX; + scaleY = data.scaleY; + shearX = data.shearX; + shearY = data.shearY; + } + + /// + /// Computes the applied transform values from the world transform. + /// + /// If the world transform is modified (by a constraint, , etc) then this method should be called so + /// the applied transform matches the world transform. The applied transform may be needed by other code (eg to apply another + /// constraint). + /// + /// Some information is ambiguous in the world transform, such as -1,-1 scale versus 180 rotation. The applied transform after + /// calling this method is equivalent to the local transform used to compute the world transform, but may not be identical. + /// + internal void UpdateAppliedTransform() + { + Bone parent = this.parent; + if (parent == null) + { + ax = worldX; + ay = worldY; + arotation = MathUtils.Atan2(c, a) * MathUtils.RadDeg; + ascaleX = (float)Math.Sqrt(a * a + c * c); + ascaleY = (float)Math.Sqrt(b * b + d * d); + ashearX = 0; + ashearY = MathUtils.Atan2(a * b + c * d, a * d - b * c) * MathUtils.RadDeg; + return; + } + float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d; + float pid = 1 / (pa * pd - pb * pc); + float dx = worldX - parent.worldX, dy = worldY - parent.worldY; + ax = (dx * pd * pid - dy * pb * pid); + ay = (dy * pa * pid - dx * pc * pid); + float ia = pid * pd; + float id = pid * pa; + float ib = pid * pb; + float ic = pid * pc; + float ra = ia * a - ib * c; + float rb = ia * b - ib * d; + float rc = id * c - ic * a; + float rd = id * d - ic * b; + ashearX = 0; + ascaleX = (float)Math.Sqrt(ra * ra + rc * rc); + if (ascaleX > 0.0001f) + { + float det = ra * rd - rb * rc; + ascaleY = det / ascaleX; + ashearY = MathUtils.Atan2(ra * rb + rc * rd, det) * MathUtils.RadDeg; + arotation = MathUtils.Atan2(rc, ra) * MathUtils.RadDeg; + } + else + { + ascaleX = 0; + ascaleY = (float)Math.Sqrt(rb * rb + rd * rd); + ashearY = 0; + arotation = 90 - MathUtils.Atan2(rd, rb) * MathUtils.RadDeg; + } + } + + public void WorldToLocal(float worldX, float worldY, out float localX, out float localY) + { + float a = this.a, b = this.b, c = this.c, d = this.d; + float det = a * d - b * c; + float x = worldX - this.worldX, y = worldY - this.worldY; + localX = (x * d - y * b) / det; + localY = (y * a - x * c) / det; + } + + public void LocalToWorld(float localX, float localY, out float worldX, out float worldY) + { + worldX = localX * a + localY * b + this.worldX; + worldY = localX * c + localY * d + this.worldY; + } + + public float WorldToLocalRotationX + { + get + { + Bone parent = this.parent; + if (parent == null) return arotation; + float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, a = this.a, c = this.c; + return MathUtils.Atan2(pa * c - pc * a, pd * a - pb * c) * MathUtils.RadDeg; + } + } + + public float WorldToLocalRotationY + { + get + { + Bone parent = this.parent; + if (parent == null) return arotation; + float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, b = this.b, d = this.d; + return MathUtils.Atan2(pa * d - pc * b, pd * b - pb * d) * MathUtils.RadDeg; + } + } + + public float WorldToLocalRotation(float worldRotation) + { + float sin = MathUtils.SinDeg(worldRotation), cos = MathUtils.CosDeg(worldRotation); + return MathUtils.Atan2(a * sin - c * cos, d * cos - b * sin) * MathUtils.RadDeg + rotation - shearX; + } + + public float LocalToWorldRotation(float localRotation) + { + localRotation -= rotation - shearX; + float sin = MathUtils.SinDeg(localRotation), cos = MathUtils.CosDeg(localRotation); + return MathUtils.Atan2(cos * c + sin * d, cos * a + sin * b) * MathUtils.RadDeg; + } + + /// + /// Rotates the world transform the specified amount. + /// + /// After changes are made to the world transform, should be called and will + /// need to be called on any child bones, recursively. + /// + public void RotateWorld(float degrees) + { + float a = this.a, b = this.b, c = this.c, d = this.d; + float cos = MathUtils.CosDeg(degrees), sin = MathUtils.SinDeg(degrees); + this.a = cos * a - sin * c; + this.b = cos * b - sin * d; + this.c = sin * a + cos * c; + this.d = sin * b + cos * d; + } + + override public string ToString() + { + return data.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/BoneData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/BoneData.cs index 210972d..bf3e643 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/BoneData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/BoneData.cs @@ -29,77 +29,82 @@ using System; -namespace Spine4_0_31 { - public class BoneData { - internal int index; - internal string name; - internal BoneData parent; - internal float length; - internal float x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY; - internal TransformMode transformMode = TransformMode.Normal; - internal bool skinRequired; - - /// The index of the bone in Skeleton.Bones - public int Index { get { return index; } } - - /// The name of the bone, which is unique across all bones in the skeleton. - public string Name { get { return name; } } - - /// May be null. - public BoneData Parent { get { return parent; } } - - public float Length { get { return length; } set { length = value; } } - - /// Local X translation. - public float X { get { return x; } set { x = value; } } - - /// Local Y translation. - public float Y { get { return y; } set { y = value; } } - - /// Local rotation. - public float Rotation { get { return rotation; } set { rotation = value; } } - - /// Local scaleX. - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - - /// Local scaleY. - public float ScaleY { get { return scaleY; } set { scaleY = value; } } - - /// Local shearX. - public float ShearX { get { return shearX; } set { shearX = value; } } - - /// Local shearY. - public float ShearY { get { return shearY; } set { shearY = value; } } - - /// The transform mode for how parent world transforms affect this bone. - public TransformMode TransformMode { get { return transformMode; } set { transformMode = value; } } - - ///When true, only updates this bone if the contains this - /// bone. - /// - public bool SkinRequired { get { return skinRequired; } set { skinRequired = value; } } - - /// May be null. - public BoneData (int index, string name, BoneData parent) { - if (index < 0) throw new ArgumentException("index must be >= 0", "index"); - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.index = index; - this.name = name; - this.parent = parent; - } - - override public string ToString () { - return name; - } - } - - [Flags] - public enum TransformMode { - //0000 0 Flip Scale Rotation - Normal = 0, // 0000 - OnlyTranslation = 7, // 0111 - NoRotationOrReflection = 1, // 0001 - NoScale = 2, // 0010 - NoScaleOrReflection = 6, // 0110 - } +namespace Spine4_0_31 +{ + public class BoneData + { + internal int index; + internal string name; + internal BoneData parent; + internal float length; + internal float x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY; + internal TransformMode transformMode = TransformMode.Normal; + internal bool skinRequired; + + /// The index of the bone in Skeleton.Bones + public int Index { get { return index; } } + + /// The name of the bone, which is unique across all bones in the skeleton. + public string Name { get { return name; } } + + /// May be null. + public BoneData Parent { get { return parent; } } + + public float Length { get { return length; } set { length = value; } } + + /// Local X translation. + public float X { get { return x; } set { x = value; } } + + /// Local Y translation. + public float Y { get { return y; } set { y = value; } } + + /// Local rotation. + public float Rotation { get { return rotation; } set { rotation = value; } } + + /// Local scaleX. + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + + /// Local scaleY. + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + + /// Local shearX. + public float ShearX { get { return shearX; } set { shearX = value; } } + + /// Local shearY. + public float ShearY { get { return shearY; } set { shearY = value; } } + + /// The transform mode for how parent world transforms affect this bone. + public TransformMode TransformMode { get { return transformMode; } set { transformMode = value; } } + + ///When true, only updates this bone if the contains this + /// bone. + /// + public bool SkinRequired { get { return skinRequired; } set { skinRequired = value; } } + + /// May be null. + public BoneData(int index, string name, BoneData parent) + { + if (index < 0) throw new ArgumentException("index must be >= 0", "index"); + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.index = index; + this.name = name; + this.parent = parent; + } + + override public string ToString() + { + return name; + } + } + + [Flags] + public enum TransformMode + { + //0000 0 Flip Scale Rotation + Normal = 0, // 0000 + OnlyTranslation = 7, // 0111 + NoRotationOrReflection = 1, // 0001 + NoScale = 2, // 0010 + NoScaleOrReflection = 6, // 0110 + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/ConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/ConstraintData.cs index 6186bb2..704b01b 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/ConstraintData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/ConstraintData.cs @@ -28,34 +28,37 @@ *****************************************************************************/ using System; -using System.Collections.Generic; -namespace Spine4_0_31 { - /// The base class for all constraint datas. - public abstract class ConstraintData { - internal readonly string name; - internal int order; - internal bool skinRequired; +namespace Spine4_0_31 +{ + /// The base class for all constraint datas. + public abstract class ConstraintData + { + internal readonly string name; + internal int order; + internal bool skinRequired; - public ConstraintData (string name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; - } + public ConstraintData(string name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + } - /// The constraint's name, which is unique across all constraints in the skeleton of the same type. - public string Name { get { return name; } } + /// The constraint's name, which is unique across all constraints in the skeleton of the same type. + public string Name { get { return name; } } - ///The ordinal of this constraint for the order a skeleton's constraints will be applied by - /// . - public int Order { get { return order; } set { order = value; } } + ///The ordinal of this constraint for the order a skeleton's constraints will be applied by + /// . + public int Order { get { return order; } set { order = value; } } - ///When true, only updates this constraint if the contains - /// this constraint. - /// - public bool SkinRequired { get { return skinRequired; } set { skinRequired = value; } } + ///When true, only updates this constraint if the contains + /// this constraint. + /// + public bool SkinRequired { get { return skinRequired; } set { skinRequired = value; } } - override public string ToString () { - return name; - } - } + override public string ToString() + { + return name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Event.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Event.cs index 8276f62..b468346 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Event.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Event.cs @@ -29,36 +29,40 @@ using System; -namespace Spine4_0_31 { - /// Stores the current pose values for an Event. - public class Event { - internal readonly EventData data; - internal readonly float time; - internal int intValue; - internal float floatValue; - internal string stringValue; - internal float volume; - internal float balance; +namespace Spine4_0_31 +{ + /// Stores the current pose values for an Event. + public class Event + { + internal readonly EventData data; + internal readonly float time; + internal int intValue; + internal float floatValue; + internal string stringValue; + internal float volume; + internal float balance; - public EventData Data { get { return data; } } - /// The animation time this event was keyed. - public float Time { get { return time; } } + public EventData Data { get { return data; } } + /// The animation time this event was keyed. + public float Time { get { return time; } } - public int Int { get { return intValue; } set { intValue = value; } } - public float Float { get { return floatValue; } set { floatValue = value; } } - public string String { get { return stringValue; } set { stringValue = value; } } + public int Int { get { return intValue; } set { intValue = value; } } + public float Float { get { return floatValue; } set { floatValue = value; } } + public string String { get { return stringValue; } set { stringValue = value; } } - public float Volume { get { return volume; } set { volume = value; } } - public float Balance { get { return balance; } set { balance = value; } } + public float Volume { get { return volume; } set { volume = value; } } + public float Balance { get { return balance; } set { balance = value; } } - public Event (float time, EventData data) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - this.time = time; - this.data = data; - } + public Event(float time, EventData data) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + this.time = time; + this.data = data; + } - override public string ToString () { - return this.data.Name; - } - } + override public string ToString() + { + return this.data.Name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/EventData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/EventData.cs index 65dd886..4d8dc52 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/EventData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/EventData.cs @@ -29,28 +29,32 @@ using System; -namespace Spine4_0_31 { - /// Stores the setup pose values for an Event. - public class EventData { - internal string name; +namespace Spine4_0_31 +{ + /// Stores the setup pose values for an Event. + public class EventData + { + internal string name; - /// The name of the event, which is unique across all events in the skeleton. - public string Name { get { return name; } } - public int Int { get; set; } - public float Float { get; set; } - public string @String { get; set; } + /// The name of the event, which is unique across all events in the skeleton. + public string Name { get { return name; } } + public int Int { get; set; } + public float Float { get; set; } + public string @String { get; set; } - public string AudioPath { get; set; } - public float Volume { get; set; } - public float Balance { get; set; } + public string AudioPath { get; set; } + public float Volume { get; set; } + public float Balance { get; set; } - public EventData (string name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; - } + public EventData(string name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + } - override public string ToString () { - return Name; - } - } + override public string ToString() + { + return Name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/ExposedList.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/ExposedList.cs index b288e15..77cf1eb 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/ExposedList.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/ExposedList.cs @@ -35,603 +35,700 @@ using System.Collections.Generic; using System.Diagnostics; -namespace Spine4_0_31 { - [DebuggerDisplay("Count={Count}")] - public class ExposedList : IEnumerable { - public T[] Items; - public int Count; - private const int DefaultCapacity = 4; - private static readonly T[] EmptyArray = new T[0]; - private int version; - - public ExposedList () { - Items = EmptyArray; - } - - public ExposedList (IEnumerable collection) { - CheckCollection(collection); - - // initialize to needed size (if determinable) - ICollection c = collection as ICollection; - if (c == null) { - Items = EmptyArray; - AddEnumerable(collection); - } else { - Items = new T[c.Count]; - AddCollection(c); - } - } - - public ExposedList (int capacity) { - if (capacity < 0) - throw new ArgumentOutOfRangeException("capacity"); - Items = new T[capacity]; - } - - internal ExposedList (T[] data, int size) { - Items = data; - Count = size; - } - - public void Add (T item) { - // If we check to see if we need to grow before trying to grow - // we can speed things up by 25% - if (Count == Items.Length) - GrowIfNeeded(1); - Items[Count++] = item; - version++; - } - - public void GrowIfNeeded (int addedCount) { - int minimumSize = Count + addedCount; - if (minimumSize > Items.Length) - Capacity = Math.Max(Math.Max(Capacity * 2, DefaultCapacity), minimumSize); - } - - public ExposedList Resize (int newSize) { - int itemsLength = Items.Length; - var oldItems = Items; - if (newSize > itemsLength) { - Array.Resize(ref Items, newSize); - } else if (newSize < itemsLength) { - // Allow nulling of T reference type to allow GC. - for (int i = newSize; i < itemsLength; i++) - oldItems[i] = default(T); - } - Count = newSize; - return this; - } - - public void EnsureCapacity (int min) { - if (Items.Length < min) { - int newCapacity = Items.Length == 0 ? DefaultCapacity : Items.Length * 2; - //if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength; - if (newCapacity < min) newCapacity = min; - Capacity = newCapacity; - } - } - - private void CheckRange (int index, int count) { - if (index < 0) - throw new ArgumentOutOfRangeException("index"); - - if (count < 0) - throw new ArgumentOutOfRangeException("count"); - - if ((uint)index + (uint)count > (uint)Count) - throw new ArgumentException("index and count exceed length of list"); - } - - private void AddCollection (ICollection collection) { - int collectionCount = collection.Count; - if (collectionCount == 0) - return; - - GrowIfNeeded(collectionCount); - collection.CopyTo(Items, Count); - Count += collectionCount; - } - - private void AddEnumerable (IEnumerable enumerable) { - foreach (T t in enumerable) { - Add(t); - } - } - - // Additional overload provided because ExposedList only implements IEnumerable, - // leading to sub-optimal behavior: It grows multiple times as it assumes not - // to know the final size ahead of insertion. - public void AddRange (ExposedList list) { - CheckCollection(list); - - int collectionCount = list.Count; - if (collectionCount == 0) - return; - - GrowIfNeeded(collectionCount); - list.CopyTo(Items, Count); - Count += collectionCount; - - version++; - } - - public void AddRange (IEnumerable collection) { - CheckCollection(collection); - - ICollection c = collection as ICollection; - if (c != null) - AddCollection(c); - else - AddEnumerable(collection); - version++; - } - - public int BinarySearch (T item) { - return Array.BinarySearch(Items, 0, Count, item); - } - - public int BinarySearch (T item, IComparer comparer) { - return Array.BinarySearch(Items, 0, Count, item, comparer); - } - - public int BinarySearch (int index, int count, T item, IComparer comparer) { - CheckRange(index, count); - return Array.BinarySearch(Items, index, count, item, comparer); - } - - public void Clear (bool clearArray = true) { - if (clearArray) - Array.Clear(Items, 0, Items.Length); - - Count = 0; - version++; - } - - public bool Contains (T item) { - return Array.IndexOf(Items, item, 0, Count) != -1; - } - - public ExposedList ConvertAll (Converter converter) { - if (converter == null) - throw new ArgumentNullException("converter"); - ExposedList u = new ExposedList(Count); - u.Count = Count; - T[] items = Items; - TOutput[] uItems = u.Items; - for (int i = 0; i < Count; i++) - uItems[i] = converter(items[i]); - return u; - } - - public void CopyTo (T[] array) { - Array.Copy(Items, 0, array, 0, Count); - } - - public void CopyTo (T[] array, int arrayIndex) { - Array.Copy(Items, 0, array, arrayIndex, Count); - } - - public void CopyTo (int index, T[] array, int arrayIndex, int count) { - CheckRange(index, count); - Array.Copy(Items, index, array, arrayIndex, count); - } - - public bool Exists (Predicate match) { - CheckMatch(match); - return GetIndex(0, Count, match) != -1; - } - - public T Find (Predicate match) { - CheckMatch(match); - int i = GetIndex(0, Count, match); - return (i != -1) ? Items[i] : default(T); - } - - private static void CheckMatch (Predicate match) { - if (match == null) - throw new ArgumentNullException("match"); - } - - public ExposedList FindAll (Predicate match) { - CheckMatch(match); - return FindAllList(match); - } - - private ExposedList FindAllList (Predicate match) { - ExposedList results = new ExposedList(); - for (int i = 0; i < Count; i++) - if (match(Items[i])) - results.Add(Items[i]); - - return results; - } - - public int FindIndex (Predicate match) { - CheckMatch(match); - return GetIndex(0, Count, match); - } - - public int FindIndex (int startIndex, Predicate match) { - CheckMatch(match); - CheckIndex(startIndex); - return GetIndex(startIndex, Count - startIndex, match); - } - - public int FindIndex (int startIndex, int count, Predicate match) { - CheckMatch(match); - CheckRange(startIndex, count); - return GetIndex(startIndex, count, match); - } - - private int GetIndex (int startIndex, int count, Predicate match) { - int end = startIndex + count; - for (int i = startIndex; i < end; i++) - if (match(Items[i])) - return i; - - return -1; - } - - public T FindLast (Predicate match) { - CheckMatch(match); - int i = GetLastIndex(0, Count, match); - return i == -1 ? default(T) : Items[i]; - } - - public int FindLastIndex (Predicate match) { - CheckMatch(match); - return GetLastIndex(0, Count, match); - } - - public int FindLastIndex (int startIndex, Predicate match) { - CheckMatch(match); - CheckIndex(startIndex); - return GetLastIndex(0, startIndex + 1, match); - } - - public int FindLastIndex (int startIndex, int count, Predicate match) { - CheckMatch(match); - int start = startIndex - count + 1; - CheckRange(start, count); - return GetLastIndex(start, count, match); - } - - private int GetLastIndex (int startIndex, int count, Predicate match) { - // unlike FindLastIndex, takes regular params for search range - for (int i = startIndex + count; i != startIndex;) - if (match(Items[--i])) - return i; - return -1; - } - - public void ForEach (Action action) { - if (action == null) - throw new ArgumentNullException("action"); - for (int i = 0; i < Count; i++) - action(Items[i]); - } - - public Enumerator GetEnumerator () { - return new Enumerator(this); - } - - public ExposedList GetRange (int index, int count) { - CheckRange(index, count); - T[] tmpArray = new T[count]; - Array.Copy(Items, index, tmpArray, 0, count); - return new ExposedList(tmpArray, count); - } - - public int IndexOf (T item) { - return Array.IndexOf(Items, item, 0, Count); - } - - public int IndexOf (T item, int index) { - CheckIndex(index); - return Array.IndexOf(Items, item, index, Count - index); - } - - public int IndexOf (T item, int index, int count) { - if (index < 0) - throw new ArgumentOutOfRangeException("index"); - - if (count < 0) - throw new ArgumentOutOfRangeException("count"); - - if ((uint)index + (uint)count > (uint)Count) - throw new ArgumentOutOfRangeException("index and count exceed length of list"); - - return Array.IndexOf(Items, item, index, count); - } - - private void Shift (int start, int delta) { - if (delta < 0) - start -= delta; - - if (start < Count) - Array.Copy(Items, start, Items, start + delta, Count - start); - - Count += delta; - - if (delta < 0) - Array.Clear(Items, Count, -delta); - } - - private void CheckIndex (int index) { - if (index < 0 || (uint)index > (uint)Count) - throw new ArgumentOutOfRangeException("index"); - } - - public void Insert (int index, T item) { - CheckIndex(index); - if (Count == Items.Length) - GrowIfNeeded(1); - Shift(index, 1); - Items[index] = item; - version++; - } - - private void CheckCollection (IEnumerable collection) { - if (collection == null) - throw new ArgumentNullException("collection"); - } - - public void InsertRange (int index, IEnumerable collection) { - CheckCollection(collection); - CheckIndex(index); - if (collection == this) { - T[] buffer = new T[Count]; - CopyTo(buffer, 0); - GrowIfNeeded(Count); - Shift(index, buffer.Length); - Array.Copy(buffer, 0, Items, index, buffer.Length); - } else { - ICollection c = collection as ICollection; - if (c != null) - InsertCollection(index, c); - else - InsertEnumeration(index, collection); - } - version++; - } - - private void InsertCollection (int index, ICollection collection) { - int collectionCount = collection.Count; - GrowIfNeeded(collectionCount); - - Shift(index, collectionCount); - collection.CopyTo(Items, index); - } - - private void InsertEnumeration (int index, IEnumerable enumerable) { - foreach (T t in enumerable) - Insert(index++, t); - } - - public int LastIndexOf (T item) { - return Array.LastIndexOf(Items, item, Count - 1, Count); - } - - public int LastIndexOf (T item, int index) { - CheckIndex(index); - return Array.LastIndexOf(Items, item, index, index + 1); - } - - public int LastIndexOf (T item, int index, int count) { - if (index < 0) - throw new ArgumentOutOfRangeException("index", index, "index is negative"); - - if (count < 0) - throw new ArgumentOutOfRangeException("count", count, "count is negative"); - - if (index - count + 1 < 0) - throw new ArgumentOutOfRangeException("count", count, "count is too large"); - - return Array.LastIndexOf(Items, item, index, count); - } - - public bool Remove (T item) { - int loc = IndexOf(item); - if (loc != -1) - RemoveAt(loc); - - return loc != -1; - } - - public int RemoveAll (Predicate match) { - CheckMatch(match); - int i = 0; - int j = 0; - - // Find the first item to remove - for (i = 0; i < Count; i++) - if (match(Items[i])) - break; - - if (i == Count) - return 0; - - version++; - - // Remove any additional items - for (j = i + 1; j < Count; j++) { - if (!match(Items[j])) - Items[i++] = Items[j]; - } - if (j - i > 0) - Array.Clear(Items, i, j - i); - - Count = i; - return (j - i); - } - - public void RemoveAt (int index) { - if (index < 0 || (uint)index >= (uint)Count) - throw new ArgumentOutOfRangeException("index"); - Shift(index, -1); - Array.Clear(Items, Count, 1); - version++; - } - - // Spine Added Method - // Based on Stack.Pop(); https://referencesource.microsoft.com/#mscorlib/system/collections/stack.cs - /// Pops the last item of the list. If the list is empty, Pop throws an InvalidOperationException. - public T Pop () { - if (Count == 0) - throw new InvalidOperationException("List is empty. Nothing to pop."); - - int i = Count - 1; - T item = Items[i]; - Items[i] = default(T); - Count--; - version++; - return item; - } - - public void RemoveRange (int index, int count) { - CheckRange(index, count); - if (count > 0) { - Shift(index, -count); - Array.Clear(Items, Count, count); - version++; - } - } - - public void Reverse () { - Array.Reverse(Items, 0, Count); - version++; - } - - public void Reverse (int index, int count) { - CheckRange(index, count); - Array.Reverse(Items, index, count); - version++; - } - - public void Sort () { - Array.Sort(Items, 0, Count, Comparer.Default); - version++; - } - - public void Sort (IComparer comparer) { - Array.Sort(Items, 0, Count, comparer); - version++; - } - - public void Sort (Comparison comparison) { - Array.Sort(Items, comparison); - version++; - } - - public void Sort (int index, int count, IComparer comparer) { - CheckRange(index, count); - Array.Sort(Items, index, count, comparer); - version++; - } - - public T[] ToArray () { - T[] t = new T[Count]; - Array.Copy(Items, t, Count); - - return t; - } - - public void TrimExcess () { - Capacity = Count; - } - - public bool TrueForAll (Predicate match) { - CheckMatch(match); - - for (int i = 0; i < Count; i++) - if (!match(Items[i])) - return false; - - return true; - } - - public int Capacity { - get { - return Items.Length; - } - set { - if ((uint)value < (uint)Count) - throw new ArgumentOutOfRangeException(); - - Array.Resize(ref Items, value); - } - } - - #region Interface implementations. - - IEnumerator IEnumerable.GetEnumerator () { - return GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator () { - return GetEnumerator(); - } - - #endregion - - public struct Enumerator : IEnumerator, IDisposable { - private ExposedList l; - private int next; - private int ver; - private T current; - - internal Enumerator (ExposedList l) - : this() { - this.l = l; - ver = l.version; - } - - public void Dispose () { - l = null; - } - - private void VerifyState () { - if (l == null) - throw new ObjectDisposedException(GetType().FullName); - if (ver != l.version) - throw new InvalidOperationException( - "Collection was modified; enumeration operation may not execute."); - } - - public bool MoveNext () { - VerifyState(); - - if (next < 0) - return false; - - if (next < l.Count) { - current = l.Items[next++]; - return true; - } - - next = -1; - return false; - } - - public T Current { - get { - return current; - } - } - - void IEnumerator.Reset () { - VerifyState(); - next = 0; - } - - object IEnumerator.Current { - get { - VerifyState(); - if (next <= 0) - throw new InvalidOperationException(); - return current; - } - } - } - } +namespace Spine4_0_31 +{ + [DebuggerDisplay("Count={Count}")] + public class ExposedList : IEnumerable + { + public T[] Items; + public int Count; + private const int DefaultCapacity = 4; + private static readonly T[] EmptyArray = new T[0]; + private int version; + + public ExposedList() + { + Items = EmptyArray; + } + + public ExposedList(IEnumerable collection) + { + CheckCollection(collection); + + // initialize to needed size (if determinable) + ICollection c = collection as ICollection; + if (c == null) + { + Items = EmptyArray; + AddEnumerable(collection); + } + else + { + Items = new T[c.Count]; + AddCollection(c); + } + } + + public ExposedList(int capacity) + { + if (capacity < 0) + throw new ArgumentOutOfRangeException("capacity"); + Items = new T[capacity]; + } + + internal ExposedList(T[] data, int size) + { + Items = data; + Count = size; + } + + public void Add(T item) + { + // If we check to see if we need to grow before trying to grow + // we can speed things up by 25% + if (Count == Items.Length) + GrowIfNeeded(1); + Items[Count++] = item; + version++; + } + + public void GrowIfNeeded(int addedCount) + { + int minimumSize = Count + addedCount; + if (minimumSize > Items.Length) + Capacity = Math.Max(Math.Max(Capacity * 2, DefaultCapacity), minimumSize); + } + + public ExposedList Resize(int newSize) + { + int itemsLength = Items.Length; + var oldItems = Items; + if (newSize > itemsLength) + { + Array.Resize(ref Items, newSize); + } + else if (newSize < itemsLength) + { + // Allow nulling of T reference type to allow GC. + for (int i = newSize; i < itemsLength; i++) + oldItems[i] = default(T); + } + Count = newSize; + return this; + } + + public void EnsureCapacity(int min) + { + if (Items.Length < min) + { + int newCapacity = Items.Length == 0 ? DefaultCapacity : Items.Length * 2; + //if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength; + if (newCapacity < min) newCapacity = min; + Capacity = newCapacity; + } + } + + private void CheckRange(int index, int count) + { + if (index < 0) + throw new ArgumentOutOfRangeException("index"); + + if (count < 0) + throw new ArgumentOutOfRangeException("count"); + + if ((uint)index + (uint)count > (uint)Count) + throw new ArgumentException("index and count exceed length of list"); + } + + private void AddCollection(ICollection collection) + { + int collectionCount = collection.Count; + if (collectionCount == 0) + return; + + GrowIfNeeded(collectionCount); + collection.CopyTo(Items, Count); + Count += collectionCount; + } + + private void AddEnumerable(IEnumerable enumerable) + { + foreach (T t in enumerable) + { + Add(t); + } + } + + // Additional overload provided because ExposedList only implements IEnumerable, + // leading to sub-optimal behavior: It grows multiple times as it assumes not + // to know the final size ahead of insertion. + public void AddRange(ExposedList list) + { + CheckCollection(list); + + int collectionCount = list.Count; + if (collectionCount == 0) + return; + + GrowIfNeeded(collectionCount); + list.CopyTo(Items, Count); + Count += collectionCount; + + version++; + } + + public void AddRange(IEnumerable collection) + { + CheckCollection(collection); + + ICollection c = collection as ICollection; + if (c != null) + AddCollection(c); + else + AddEnumerable(collection); + version++; + } + + public int BinarySearch(T item) + { + return Array.BinarySearch(Items, 0, Count, item); + } + + public int BinarySearch(T item, IComparer comparer) + { + return Array.BinarySearch(Items, 0, Count, item, comparer); + } + + public int BinarySearch(int index, int count, T item, IComparer comparer) + { + CheckRange(index, count); + return Array.BinarySearch(Items, index, count, item, comparer); + } + + public void Clear(bool clearArray = true) + { + if (clearArray) + Array.Clear(Items, 0, Items.Length); + + Count = 0; + version++; + } + + public bool Contains(T item) + { + return Array.IndexOf(Items, item, 0, Count) != -1; + } + + public ExposedList ConvertAll(Converter converter) + { + if (converter == null) + throw new ArgumentNullException("converter"); + ExposedList u = new ExposedList(Count); + u.Count = Count; + T[] items = Items; + TOutput[] uItems = u.Items; + for (int i = 0; i < Count; i++) + uItems[i] = converter(items[i]); + return u; + } + + public void CopyTo(T[] array) + { + Array.Copy(Items, 0, array, 0, Count); + } + + public void CopyTo(T[] array, int arrayIndex) + { + Array.Copy(Items, 0, array, arrayIndex, Count); + } + + public void CopyTo(int index, T[] array, int arrayIndex, int count) + { + CheckRange(index, count); + Array.Copy(Items, index, array, arrayIndex, count); + } + + public bool Exists(Predicate match) + { + CheckMatch(match); + return GetIndex(0, Count, match) != -1; + } + + public T Find(Predicate match) + { + CheckMatch(match); + int i = GetIndex(0, Count, match); + return (i != -1) ? Items[i] : default(T); + } + + private static void CheckMatch(Predicate match) + { + if (match == null) + throw new ArgumentNullException("match"); + } + + public ExposedList FindAll(Predicate match) + { + CheckMatch(match); + return FindAllList(match); + } + + private ExposedList FindAllList(Predicate match) + { + ExposedList results = new ExposedList(); + for (int i = 0; i < Count; i++) + if (match(Items[i])) + results.Add(Items[i]); + + return results; + } + + public int FindIndex(Predicate match) + { + CheckMatch(match); + return GetIndex(0, Count, match); + } + + public int FindIndex(int startIndex, Predicate match) + { + CheckMatch(match); + CheckIndex(startIndex); + return GetIndex(startIndex, Count - startIndex, match); + } + + public int FindIndex(int startIndex, int count, Predicate match) + { + CheckMatch(match); + CheckRange(startIndex, count); + return GetIndex(startIndex, count, match); + } + + private int GetIndex(int startIndex, int count, Predicate match) + { + int end = startIndex + count; + for (int i = startIndex; i < end; i++) + if (match(Items[i])) + return i; + + return -1; + } + + public T FindLast(Predicate match) + { + CheckMatch(match); + int i = GetLastIndex(0, Count, match); + return i == -1 ? default(T) : Items[i]; + } + + public int FindLastIndex(Predicate match) + { + CheckMatch(match); + return GetLastIndex(0, Count, match); + } + + public int FindLastIndex(int startIndex, Predicate match) + { + CheckMatch(match); + CheckIndex(startIndex); + return GetLastIndex(0, startIndex + 1, match); + } + + public int FindLastIndex(int startIndex, int count, Predicate match) + { + CheckMatch(match); + int start = startIndex - count + 1; + CheckRange(start, count); + return GetLastIndex(start, count, match); + } + + private int GetLastIndex(int startIndex, int count, Predicate match) + { + // unlike FindLastIndex, takes regular params for search range + for (int i = startIndex + count; i != startIndex;) + if (match(Items[--i])) + return i; + return -1; + } + + public void ForEach(Action action) + { + if (action == null) + throw new ArgumentNullException("action"); + for (int i = 0; i < Count; i++) + action(Items[i]); + } + + public Enumerator GetEnumerator() + { + return new Enumerator(this); + } + + public ExposedList GetRange(int index, int count) + { + CheckRange(index, count); + T[] tmpArray = new T[count]; + Array.Copy(Items, index, tmpArray, 0, count); + return new ExposedList(tmpArray, count); + } + + public int IndexOf(T item) + { + return Array.IndexOf(Items, item, 0, Count); + } + + public int IndexOf(T item, int index) + { + CheckIndex(index); + return Array.IndexOf(Items, item, index, Count - index); + } + + public int IndexOf(T item, int index, int count) + { + if (index < 0) + throw new ArgumentOutOfRangeException("index"); + + if (count < 0) + throw new ArgumentOutOfRangeException("count"); + + if ((uint)index + (uint)count > (uint)Count) + throw new ArgumentOutOfRangeException("index and count exceed length of list"); + + return Array.IndexOf(Items, item, index, count); + } + + private void Shift(int start, int delta) + { + if (delta < 0) + start -= delta; + + if (start < Count) + Array.Copy(Items, start, Items, start + delta, Count - start); + + Count += delta; + + if (delta < 0) + Array.Clear(Items, Count, -delta); + } + + private void CheckIndex(int index) + { + if (index < 0 || (uint)index > (uint)Count) + throw new ArgumentOutOfRangeException("index"); + } + + public void Insert(int index, T item) + { + CheckIndex(index); + if (Count == Items.Length) + GrowIfNeeded(1); + Shift(index, 1); + Items[index] = item; + version++; + } + + private void CheckCollection(IEnumerable collection) + { + if (collection == null) + throw new ArgumentNullException("collection"); + } + + public void InsertRange(int index, IEnumerable collection) + { + CheckCollection(collection); + CheckIndex(index); + if (collection == this) + { + T[] buffer = new T[Count]; + CopyTo(buffer, 0); + GrowIfNeeded(Count); + Shift(index, buffer.Length); + Array.Copy(buffer, 0, Items, index, buffer.Length); + } + else + { + ICollection c = collection as ICollection; + if (c != null) + InsertCollection(index, c); + else + InsertEnumeration(index, collection); + } + version++; + } + + private void InsertCollection(int index, ICollection collection) + { + int collectionCount = collection.Count; + GrowIfNeeded(collectionCount); + + Shift(index, collectionCount); + collection.CopyTo(Items, index); + } + + private void InsertEnumeration(int index, IEnumerable enumerable) + { + foreach (T t in enumerable) + Insert(index++, t); + } + + public int LastIndexOf(T item) + { + return Array.LastIndexOf(Items, item, Count - 1, Count); + } + + public int LastIndexOf(T item, int index) + { + CheckIndex(index); + return Array.LastIndexOf(Items, item, index, index + 1); + } + + public int LastIndexOf(T item, int index, int count) + { + if (index < 0) + throw new ArgumentOutOfRangeException("index", index, "index is negative"); + + if (count < 0) + throw new ArgumentOutOfRangeException("count", count, "count is negative"); + + if (index - count + 1 < 0) + throw new ArgumentOutOfRangeException("count", count, "count is too large"); + + return Array.LastIndexOf(Items, item, index, count); + } + + public bool Remove(T item) + { + int loc = IndexOf(item); + if (loc != -1) + RemoveAt(loc); + + return loc != -1; + } + + public int RemoveAll(Predicate match) + { + CheckMatch(match); + int i = 0; + int j = 0; + + // Find the first item to remove + for (i = 0; i < Count; i++) + if (match(Items[i])) + break; + + if (i == Count) + return 0; + + version++; + + // Remove any additional items + for (j = i + 1; j < Count; j++) + { + if (!match(Items[j])) + Items[i++] = Items[j]; + } + if (j - i > 0) + Array.Clear(Items, i, j - i); + + Count = i; + return (j - i); + } + + public void RemoveAt(int index) + { + if (index < 0 || (uint)index >= (uint)Count) + throw new ArgumentOutOfRangeException("index"); + Shift(index, -1); + Array.Clear(Items, Count, 1); + version++; + } + + // Spine Added Method + // Based on Stack.Pop(); https://referencesource.microsoft.com/#mscorlib/system/collections/stack.cs + /// Pops the last item of the list. If the list is empty, Pop throws an InvalidOperationException. + public T Pop() + { + if (Count == 0) + throw new InvalidOperationException("List is empty. Nothing to pop."); + + int i = Count - 1; + T item = Items[i]; + Items[i] = default(T); + Count--; + version++; + return item; + } + + public void RemoveRange(int index, int count) + { + CheckRange(index, count); + if (count > 0) + { + Shift(index, -count); + Array.Clear(Items, Count, count); + version++; + } + } + + public void Reverse() + { + Array.Reverse(Items, 0, Count); + version++; + } + + public void Reverse(int index, int count) + { + CheckRange(index, count); + Array.Reverse(Items, index, count); + version++; + } + + public void Sort() + { + Array.Sort(Items, 0, Count, Comparer.Default); + version++; + } + + public void Sort(IComparer comparer) + { + Array.Sort(Items, 0, Count, comparer); + version++; + } + + public void Sort(Comparison comparison) + { + Array.Sort(Items, comparison); + version++; + } + + public void Sort(int index, int count, IComparer comparer) + { + CheckRange(index, count); + Array.Sort(Items, index, count, comparer); + version++; + } + + public T[] ToArray() + { + T[] t = new T[Count]; + Array.Copy(Items, t, Count); + + return t; + } + + public void TrimExcess() + { + Capacity = Count; + } + + public bool TrueForAll(Predicate match) + { + CheckMatch(match); + + for (int i = 0; i < Count; i++) + if (!match(Items[i])) + return false; + + return true; + } + + public int Capacity + { + get + { + return Items.Length; + } + set + { + if ((uint)value < (uint)Count) + throw new ArgumentOutOfRangeException(); + + Array.Resize(ref Items, value); + } + } + + #region Interface implementations. + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion + + public struct Enumerator : IEnumerator, IDisposable + { + private ExposedList l; + private int next; + private int ver; + private T current; + + internal Enumerator(ExposedList l) + : this() + { + this.l = l; + ver = l.version; + } + + public void Dispose() + { + l = null; + } + + private void VerifyState() + { + if (l == null) + throw new ObjectDisposedException(GetType().FullName); + if (ver != l.version) + throw new InvalidOperationException( + "Collection was modified; enumeration operation may not execute."); + } + + public bool MoveNext() + { + VerifyState(); + + if (next < 0) + return false; + + if (next < l.Count) + { + current = l.Items[next++]; + return true; + } + + next = -1; + return false; + } + + public T Current + { + get + { + return current; + } + } + + void IEnumerator.Reset() + { + VerifyState(); + next = 0; + } + + object IEnumerator.Current + { + get + { + VerifyState(); + if (next <= 0) + throw new InvalidOperationException(); + return current; + } + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/IUpdatable.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/IUpdatable.cs index 5cc697f..854cba8 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/IUpdatable.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/IUpdatable.cs @@ -27,16 +27,18 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine4_0_31 { +namespace Spine4_0_31 +{ - ///The interface for items updated by . - public interface IUpdatable { - void Update (); + ///The interface for items updated by . + public interface IUpdatable + { + void Update(); - ///Returns false when this item has not been updated because a skin is required and the active - /// skin does not contain this item. - /// - /// - bool Active { get; } - } + ///Returns false when this item has not been updated because a skin is required and the active + /// skin does not contain this item. + /// + /// + bool Active { get; } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/IkConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/IkConstraint.cs index 7dcfbca..717acef 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/IkConstraint.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/IkConstraint.cs @@ -29,340 +29,392 @@ using System; -namespace Spine4_0_31 { - /// - /// - /// Stores the current pose for an IK constraint. An IK constraint adjusts the rotation of 1 or 2 constrained bones so the tip of - /// the last bone is as close to the target bone as possible. - /// - /// See IK constraints in the Spine User Guide. - /// - public class IkConstraint : IUpdatable { - internal IkConstraintData data; - internal ExposedList bones = new ExposedList(); - internal Bone target; - internal int bendDirection; - internal bool compress, stretch; - internal float mix = 1, softness; +namespace Spine4_0_31 +{ + /// + /// + /// Stores the current pose for an IK constraint. An IK constraint adjusts the rotation of 1 or 2 constrained bones so the tip of + /// the last bone is as close to the target bone as possible. + /// + /// See IK constraints in the Spine User Guide. + /// + public class IkConstraint : IUpdatable + { + internal IkConstraintData data; + internal ExposedList bones = new ExposedList(); + internal Bone target; + internal int bendDirection; + internal bool compress, stretch; + internal float mix = 1, softness; - internal bool active; + internal bool active; - public IkConstraint (IkConstraintData data, Skeleton skeleton) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.data = data; - mix = data.mix; - softness = data.softness; - bendDirection = data.bendDirection; - compress = data.compress; - stretch = data.stretch; + public IkConstraint(IkConstraintData data, Skeleton skeleton) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + mix = data.mix; + softness = data.softness; + bendDirection = data.bendDirection; + compress = data.compress; + stretch = data.stretch; - bones = new ExposedList(data.bones.Count); - foreach (BoneData boneData in data.bones) - bones.Add(skeleton.FindBone(boneData.name)); - target = skeleton.FindBone(data.target.name); - } + bones = new ExposedList(data.bones.Count); + foreach (BoneData boneData in data.bones) + bones.Add(skeleton.FindBone(boneData.name)); + target = skeleton.FindBone(data.target.name); + } - /// Copy constructor. - public IkConstraint (IkConstraint constraint, Skeleton skeleton) { - if (constraint == null) throw new ArgumentNullException("constraint cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); - data = constraint.data; - bones = new ExposedList(constraint.Bones.Count); - foreach (Bone bone in constraint.Bones) - bones.Add(skeleton.Bones.Items[bone.data.index]); - target = skeleton.Bones.Items[constraint.target.data.index]; - mix = constraint.mix; - softness = constraint.softness; - bendDirection = constraint.bendDirection; - compress = constraint.compress; - stretch = constraint.stretch; - } + /// Copy constructor. + public IkConstraint(IkConstraint constraint, Skeleton skeleton) + { + if (constraint == null) throw new ArgumentNullException("constraint cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); + data = constraint.data; + bones = new ExposedList(constraint.Bones.Count); + foreach (Bone bone in constraint.Bones) + bones.Add(skeleton.Bones.Items[bone.data.index]); + target = skeleton.Bones.Items[constraint.target.data.index]; + mix = constraint.mix; + softness = constraint.softness; + bendDirection = constraint.bendDirection; + compress = constraint.compress; + stretch = constraint.stretch; + } - public void Update () { - if (mix == 0) return; - Bone target = this.target; - var bones = this.bones.Items; - switch (this.bones.Count) { - case 1: - Apply(bones[0], target.worldX, target.worldY, compress, stretch, data.uniform, mix); - break; - case 2: - Apply(bones[0], bones[1], target.worldX, target.worldY, bendDirection, stretch, data.uniform, softness, mix); - break; - } - } + public void Update() + { + if (mix == 0) return; + Bone target = this.target; + var bones = this.bones.Items; + switch (this.bones.Count) + { + case 1: + Apply(bones[0], target.worldX, target.worldY, compress, stretch, data.uniform, mix); + break; + case 2: + Apply(bones[0], bones[1], target.worldX, target.worldY, bendDirection, stretch, data.uniform, softness, mix); + break; + } + } - /// The bones that will be modified by this IK constraint. - public ExposedList Bones { - get { return bones; } - } + /// The bones that will be modified by this IK constraint. + public ExposedList Bones + { + get { return bones; } + } - /// The bone that is the IK target. - public Bone Target { - get { return target; } - set { target = value; } - } + /// The bone that is the IK target. + public Bone Target + { + get { return target; } + set { target = value; } + } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. - /// - /// For two bone IK: if the parent bone has local nonuniform scale, the child bone's local Y translation is set to 0. - /// - public float Mix { - get { return mix; } - set { mix = value; } - } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. + /// + /// For two bone IK: if the parent bone has local nonuniform scale, the child bone's local Y translation is set to 0. + /// + public float Mix + { + get { return mix; } + set { mix = value; } + } - /// For two bone IK, the target bone's distance from the maximum reach of the bones where rotation begins to slow. The bones - /// will not straighten completely until the target is this far out of range. - public float Softness { - get { return softness; } - set { softness = value; } - } + /// For two bone IK, the target bone's distance from the maximum reach of the bones where rotation begins to slow. The bones + /// will not straighten completely until the target is this far out of range. + public float Softness + { + get { return softness; } + set { softness = value; } + } - /// For two bone IK, controls the bend direction of the IK bones, either 1 or -1. - public int BendDirection { - get { return bendDirection; } - set { bendDirection = value; } - } + /// For two bone IK, controls the bend direction of the IK bones, either 1 or -1. + public int BendDirection + { + get { return bendDirection; } + set { bendDirection = value; } + } - /// For one bone IK, when true and the target is too close, the bone is scaled to reach it. - public bool Compress { - get { return compress; } - set { compress = value; } - } + /// For one bone IK, when true and the target is too close, the bone is scaled to reach it. + public bool Compress + { + get { return compress; } + set { compress = value; } + } - /// When true and the target is out of range, the parent bone is scaled to reach it. - /// - /// For two bone IK: 1) the child bone's local Y translation is set to 0, - /// 2) stretch is not applied if is > 0, - /// and 3) if the parent bone has local nonuniform scale, stretch is not applied. - /// - public bool Stretch { - get { return stretch; } - set { stretch = value; } - } + /// When true and the target is out of range, the parent bone is scaled to reach it. + /// + /// For two bone IK: 1) the child bone's local Y translation is set to 0, + /// 2) stretch is not applied if is > 0, + /// and 3) if the parent bone has local nonuniform scale, stretch is not applied. + /// + public bool Stretch + { + get { return stretch; } + set { stretch = value; } + } - public bool Active { - get { return active; } - } + public bool Active + { + get { return active; } + } - /// The IK constraint's setup pose data. - public IkConstraintData Data { - get { return data; } - } + /// The IK constraint's setup pose data. + public IkConstraintData Data + { + get { return data; } + } - override public string ToString () { - return data.name; - } + override public string ToString() + { + return data.name; + } - /// Applies 1 bone IK. The target is specified in the world coordinate system. - static public void Apply (Bone bone, float targetX, float targetY, bool compress, bool stretch, bool uniform, - float alpha) { - if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); - Bone p = bone.parent; + /// Applies 1 bone IK. The target is specified in the world coordinate system. + static public void Apply(Bone bone, float targetX, float targetY, bool compress, bool stretch, bool uniform, + float alpha) + { + if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); + Bone p = bone.parent; - float pa = p.a, pb = p.b, pc = p.c, pd = p.d; - float rotationIK = -bone.ashearX - bone.arotation; - float tx = 0, ty = 0; + 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.data.transformMode) { - case TransformMode.OnlyTranslation: - tx = targetX - bone.worldX; - ty = targetY - bone.worldY; - break; - case TransformMode.NoRotationOrReflection: { - float s = Math.Abs(pa * pd - pb * pc) / (pa * pa + pc * pc); - float sa = pa / bone.skeleton.ScaleX; - float sc = pc / bone.skeleton.ScaleY; - pb = -sc * s * bone.skeleton.ScaleX; - pd = sa * s * bone.skeleton.ScaleY; - rotationIK += (float)Math.Atan2(sc, sa) * MathUtils.RadDeg; - goto default; // Fall through. - } - default: { - float x = targetX - p.worldX, y = targetY - p.worldY; - float d = pa * pd - pb * pc; - tx = (x * pd - y * pb) / d - bone.ax; - ty = (y * pa - x * pc) / d - bone.ay; - break; - } - } + switch (bone.data.transformMode) + { + case TransformMode.OnlyTranslation: + tx = targetX - bone.worldX; + ty = targetY - bone.worldY; + break; + case TransformMode.NoRotationOrReflection: + { + float s = Math.Abs(pa * pd - pb * pc) / (pa * pa + pc * pc); + float sa = pa / bone.skeleton.ScaleX; + float sc = pc / bone.skeleton.ScaleY; + pb = -sc * s * bone.skeleton.ScaleX; + pd = sa * s * bone.skeleton.ScaleY; + rotationIK += (float)Math.Atan2(sc, sa) * MathUtils.RadDeg; + goto default; // Fall through. + } + default: + { + float x = targetX - p.worldX, y = targetY - p.worldY; + float d = pa * pd - pb * pc; + tx = (x * pd - y * pb) / d - bone.ax; + ty = (y * pa - x * pc) / d - bone.ay; + break; + } + } - rotationIK += (float)Math.Atan2(ty, tx) * MathUtils.RadDeg; - if (bone.ascaleX < 0) rotationIK += 180; - if (rotationIK > 180) - rotationIK -= 360; - else if (rotationIK < -180) // - rotationIK += 360; + rotationIK += (float)Math.Atan2(ty, tx) * MathUtils.RadDeg; + if (bone.ascaleX < 0) rotationIK += 180; + if (rotationIK > 180) + rotationIK -= 360; + else if (rotationIK < -180) // + rotationIK += 360; - float sx = bone.ascaleX, sy = bone.ascaleY; - if (compress || stretch) { - switch (bone.data.transformMode) { - case TransformMode.NoScale: - case TransformMode.NoScaleOrReflection: - tx = targetX - bone.worldX; - ty = targetY - bone.worldY; - break; - } - float b = bone.data.length * sx, dd = (float)Math.Sqrt(tx * tx + ty * ty); - if ((compress && dd < b) || (stretch && dd > b) && b > 0.0001f) { - float s = (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); - } + float sx = bone.ascaleX, sy = bone.ascaleY; + if (compress || stretch) + { + switch (bone.data.transformMode) + { + case TransformMode.NoScale: + case TransformMode.NoScaleOrReflection: + tx = targetX - bone.worldX; + ty = targetY - bone.worldY; + break; + } + float b = bone.data.length * sx, dd = (float)Math.Sqrt(tx * tx + ty * ty); + if ((compress && dd < b) || (stretch && dd > b) && b > 0.0001f) + { + float s = (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); + } - /// Applies 2 bone IK. The target is specified in the world coordinate system. - /// A direct descendant of the parent bone. - static public void Apply (Bone parent, Bone child, float targetX, float targetY, int bendDir, bool stretch, bool uniform, - float softness, float alpha) { - if (parent == null) throw new ArgumentNullException("parent", "parent cannot be null."); - if (child == null) throw new ArgumentNullException("child", "child cannot be null."); - float px = parent.ax, py = parent.ay, psx = parent.ascaleX, psy = parent.ascaleY, sx = psx, sy = psy, csx = child.ascaleX; - int os1, os2, s2; - if (psx < 0) { - psx = -psx; - os1 = 180; - s2 = -1; - } else { - os1 = 0; - s2 = 1; - } - if (psy < 0) { - psy = -psy; - s2 = -s2; - } - if (csx < 0) { - csx = -csx; - os2 = 180; - } else - os2 = 0; - float cx = child.ax, cy, cwx, cwy, a = parent.a, b = parent.b, c = parent.c, d = parent.d; - bool u = Math.Abs(psx - psy) <= 0.0001f; - if (!u || stretch) { - cy = 0; - cwx = a * cx + parent.worldX; - cwy = c * cx + parent.worldY; - } else { - cy = child.ay; - cwx = a * cx + b * cy + parent.worldX; - cwy = c * cx + d * cy + parent.worldY; - } - Bone pp = parent.parent; - a = pp.a; - b = pp.b; - c = pp.c; - d = pp.d; - float id = 1 / (a * d - b * c), x = cwx - pp.worldX, y = cwy - pp.worldY; - float dx = (x * d - y * b) * id - px, dy = (y * a - x * c) * id - py; - float l1 = (float)Math.Sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2; - if (l1 < 0.0001f) { - 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; - float tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py; - float dd = tx * tx + ty * ty; - if (softness != 0) { - softness *= psx * (csx + 1) * 0.5f; - float td = (float)Math.Sqrt(dd), sd = td - l1 - l2 * psx + softness; - if (sd > 0) { - float p = Math.Min(1, sd / (softness * 2)) - 1; - p = (sd - softness * (1 - p * p)) / td; - tx -= p * tx; - ty -= p * ty; - dd = tx * tx + ty * ty; - } - } - if (u) { - l2 *= psx; - float cos = (dd - l1 * l1 - l2 * l2) / (2 * l1 * l2); - if (cos < -1) { - cos = -1; - a2 = MathUtils.PI * bendDir; - } else if (cos > 1) { - cos = 1; - a2 = 0; - if (stretch) { - a = ((float)Math.Sqrt(dd) / (l1 + l2) - 1) * alpha + 1; - sx *= a; - if (uniform) sy *= a; - } - } else - a2 = (float)Math.Acos(cos) * bendDir; - a = l1 + l2 * cos; - b = l2 * (float)Math.Sin(a2); - a1 = (float)Math.Atan2(ty * a - tx * b, tx * a + ty * b); - } else { - a = psx * l2; - b = psy * l2; - float aa = a * a, bb = b * b, ta = (float)Math.Atan2(ty, tx); - c = bb * l1 * l1 + aa * dd - aa * bb; - float c1 = -2 * bb * l1, c2 = bb - aa; - d = c1 * c1 - 4 * c2 * c; - if (d >= 0) { - float q = (float)Math.Sqrt(d); - if (c1 < 0) q = -q; - q = -(c1 + q) * 0.5f; - float r0 = q / c2, r1 = c / q; - float r = Math.Abs(r0) < Math.Abs(r1) ? r0 : r1; - if (r * r <= dd) { - y = (float)Math.Sqrt(dd - r * r) * bendDir; - a1 = ta - (float)Math.Atan2(y, r); - a2 = (float)Math.Atan2(y / psy, (r - l1) / psx); - goto break_outer; // break outer; - } - } - float minAngle = MathUtils.PI, minX = l1 - a, minDist = minX * minX, minY = 0; - float maxAngle = 0, maxX = l1 + a, maxDist = maxX * maxX, maxY = 0; - c = -a * l1 / (aa - bb); - if (c >= -1 && c <= 1) { - c = (float)Math.Acos(c); - x = a * (float)Math.Cos(c) + l1; - y = b * (float)Math.Sin(c); - d = x * x + y * y; - if (d < minDist) { - minAngle = c; - minDist = d; - minX = x; - minY = y; - } - if (d > maxDist) { - maxAngle = c; - maxDist = d; - maxX = x; - maxY = y; - } - } - if (dd <= (minDist + maxDist) * 0.5f) { - a1 = ta - (float)Math.Atan2(minY * bendDir, minX); - a2 = minAngle * bendDir; - } else { - a1 = ta - (float)Math.Atan2(maxY * bendDir, maxX); - a2 = maxAngle * bendDir; - } - } - break_outer: - float os = (float)Math.Atan2(cy, cx) * s2; - float rotation = parent.arotation; - a1 = (a1 - os) * MathUtils.RadDeg + os1 - rotation; - if (a1 > 180) - a1 -= 360; - else if (a1 < -180) - a1 += 360; - parent.UpdateWorldTransform(px, py, rotation + a1 * alpha, sx, sy, 0, 0); - rotation = child.arotation; - a2 = ((a2 + os) * MathUtils.RadDeg - child.ashearX) * s2 + os2 - rotation; - if (a2 > 180) - a2 -= 360; - else if (a2 < -180) - a2 += 360; - child.UpdateWorldTransform(cx, cy, rotation + a2 * alpha, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY); - } - } + /// Applies 2 bone IK. The target is specified in the world coordinate system. + /// A direct descendant of the parent bone. + static public void Apply(Bone parent, Bone child, float targetX, float targetY, int bendDir, bool stretch, bool uniform, + float softness, float alpha) + { + if (parent == null) throw new ArgumentNullException("parent", "parent cannot be null."); + if (child == null) throw new ArgumentNullException("child", "child cannot be null."); + float px = parent.ax, py = parent.ay, psx = parent.ascaleX, psy = parent.ascaleY, sx = psx, sy = psy, csx = child.ascaleX; + int os1, os2, s2; + if (psx < 0) + { + psx = -psx; + os1 = 180; + s2 = -1; + } + else + { + os1 = 0; + s2 = 1; + } + if (psy < 0) + { + psy = -psy; + s2 = -s2; + } + if (csx < 0) + { + csx = -csx; + os2 = 180; + } + else + os2 = 0; + float cx = child.ax, cy, cwx, cwy, a = parent.a, b = parent.b, c = parent.c, d = parent.d; + bool u = Math.Abs(psx - psy) <= 0.0001f; + if (!u || stretch) + { + cy = 0; + cwx = a * cx + parent.worldX; + cwy = c * cx + parent.worldY; + } + else + { + cy = child.ay; + cwx = a * cx + b * cy + parent.worldX; + cwy = c * cx + d * cy + parent.worldY; + } + Bone pp = parent.parent; + a = pp.a; + b = pp.b; + c = pp.c; + d = pp.d; + float id = 1 / (a * d - b * c), x = cwx - pp.worldX, y = cwy - pp.worldY; + float dx = (x * d - y * b) * id - px, dy = (y * a - x * c) * id - py; + float l1 = (float)Math.Sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2; + if (l1 < 0.0001f) + { + 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; + float tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py; + float dd = tx * tx + ty * ty; + if (softness != 0) + { + softness *= psx * (csx + 1) * 0.5f; + float td = (float)Math.Sqrt(dd), sd = td - l1 - l2 * psx + softness; + if (sd > 0) + { + float p = Math.Min(1, sd / (softness * 2)) - 1; + p = (sd - softness * (1 - p * p)) / td; + tx -= p * tx; + ty -= p * ty; + dd = tx * tx + ty * ty; + } + } + if (u) + { + l2 *= psx; + float cos = (dd - l1 * l1 - l2 * l2) / (2 * l1 * l2); + if (cos < -1) + { + cos = -1; + a2 = MathUtils.PI * bendDir; + } + else if (cos > 1) + { + cos = 1; + a2 = 0; + if (stretch) + { + a = ((float)Math.Sqrt(dd) / (l1 + l2) - 1) * alpha + 1; + sx *= a; + if (uniform) sy *= a; + } + } + else + a2 = (float)Math.Acos(cos) * bendDir; + a = l1 + l2 * cos; + b = l2 * (float)Math.Sin(a2); + a1 = (float)Math.Atan2(ty * a - tx * b, tx * a + ty * b); + } + else + { + a = psx * l2; + b = psy * l2; + float aa = a * a, bb = b * b, ta = (float)Math.Atan2(ty, tx); + c = bb * l1 * l1 + aa * dd - aa * bb; + float c1 = -2 * bb * l1, c2 = bb - aa; + d = c1 * c1 - 4 * c2 * c; + if (d >= 0) + { + float q = (float)Math.Sqrt(d); + if (c1 < 0) q = -q; + q = -(c1 + q) * 0.5f; + float r0 = q / c2, r1 = c / q; + float r = Math.Abs(r0) < Math.Abs(r1) ? r0 : r1; + if (r * r <= dd) + { + y = (float)Math.Sqrt(dd - r * r) * bendDir; + a1 = ta - (float)Math.Atan2(y, r); + a2 = (float)Math.Atan2(y / psy, (r - l1) / psx); + goto break_outer; // break outer; + } + } + float minAngle = MathUtils.PI, minX = l1 - a, minDist = minX * minX, minY = 0; + float maxAngle = 0, maxX = l1 + a, maxDist = maxX * maxX, maxY = 0; + c = -a * l1 / (aa - bb); + if (c >= -1 && c <= 1) + { + c = (float)Math.Acos(c); + x = a * (float)Math.Cos(c) + l1; + y = b * (float)Math.Sin(c); + d = x * x + y * y; + if (d < minDist) + { + minAngle = c; + minDist = d; + minX = x; + minY = y; + } + if (d > maxDist) + { + maxAngle = c; + maxDist = d; + maxX = x; + maxY = y; + } + } + if (dd <= (minDist + maxDist) * 0.5f) + { + a1 = ta - (float)Math.Atan2(minY * bendDir, minX); + a2 = minAngle * bendDir; + } + else + { + a1 = ta - (float)Math.Atan2(maxY * bendDir, maxX); + a2 = maxAngle * bendDir; + } + } + break_outer: + float os = (float)Math.Atan2(cy, cx) * s2; + float rotation = parent.arotation; + a1 = (a1 - os) * MathUtils.RadDeg + os1 - rotation; + if (a1 > 180) + a1 -= 360; + else if (a1 < -180) + a1 += 360; + parent.UpdateWorldTransform(px, py, rotation + a1 * alpha, sx, sy, 0, 0); + rotation = child.arotation; + a2 = ((a2 + os) * MathUtils.RadDeg - child.ashearX) * s2 + os2 - rotation; + if (a2 > 180) + a2 -= 360; + else if (a2 < -180) + a2 += 360; + child.UpdateWorldTransform(cx, cy, rotation + a2 * alpha, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/IkConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/IkConstraintData.cs index 520529f..67c5910 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/IkConstraintData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/IkConstraintData.cs @@ -27,77 +27,85 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; -using System.Collections.Generic; +namespace Spine4_0_31 +{ + /// Stores the setup pose for an IkConstraint. + public class IkConstraintData : ConstraintData + { + internal ExposedList bones = new ExposedList(); + internal BoneData target; + internal int bendDirection = 1; + internal bool compress, stretch, uniform; + internal float mix = 1, softness; -namespace Spine4_0_31 { - /// Stores the setup pose for an IkConstraint. - public class IkConstraintData : ConstraintData { - internal ExposedList bones = new ExposedList(); - internal BoneData target; - internal int bendDirection = 1; - internal bool compress, stretch, uniform; - internal float mix = 1, softness; + public IkConstraintData(string name) : base(name) + { + } - public IkConstraintData (string name) : base(name) { - } + /// The bones that are constrained by this IK Constraint. + public ExposedList Bones + { + get { return bones; } + } - /// The bones that are constrained by this IK Constraint. - public ExposedList Bones { - get { return bones; } - } + /// The bone that is the IK target. + public BoneData Target + { + get { return target; } + set { target = value; } + } - /// The bone that is the IK target. - public BoneData Target { - get { return target; } - set { target = value; } - } + /// + /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. + /// + /// For two bone IK: if the parent bone has local nonuniform scale, the child bone's local Y translation is set to 0. + /// + public float Mix + { + get { return mix; } + set { mix = value; } + } - /// - /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. - /// - /// For two bone IK: if the parent bone has local nonuniform scale, the child bone's local Y translation is set to 0. - /// - public float Mix { - get { return mix; } - set { mix = value; } - } + /// For two bone IK, the target bone's distance from the maximum reach of the bones where rotation begins to slow. The bones + /// will not straighten completely until the target is this far out of range. + public float Softness + { + get { return softness; } + set { softness = value; } + } - /// For two bone IK, the target bone's distance from the maximum reach of the bones where rotation begins to slow. The bones - /// will not straighten completely until the target is this far out of range. - public float Softness { - get { return softness; } - set { softness = value; } - } + /// For two bone IK, controls the bend direction of the IK bones, either 1 or -1. + public int BendDirection + { + get { return bendDirection; } + set { bendDirection = value; } + } - /// For two bone IK, controls the bend direction of the IK bones, either 1 or -1. - public int BendDirection { - get { return bendDirection; } - set { bendDirection = value; } - } + /// For one bone IK, when true and the target is too close, the bone is scaled to reach it. + public bool Compress + { + get { return compress; } + set { compress = value; } + } - /// For one bone IK, when true and the target is too close, the bone is scaled to reach it. - public bool Compress { - get { return compress; } - set { compress = value; } - } + /// When true and the target is out of range, the parent bone is scaled to reach it. + /// + /// For two bone IK: 1) the child bone's local Y translation is set to 0, + /// 2) stretch is not applied if is > 0, + /// and 3) if the parent bone has local nonuniform scale, stretch is not applied. + public bool Stretch + { + get { return stretch; } + set { stretch = value; } + } - /// When true and the target is out of range, the parent bone is scaled to reach it. - /// - /// For two bone IK: 1) the child bone's local Y translation is set to 0, - /// 2) stretch is not applied if is > 0, - /// and 3) if the parent bone has local nonuniform scale, stretch is not applied. - public bool Stretch { - get { return stretch; } - set { stretch = value; } - } - - /// - /// When true and or is used, the bone is scaled on both the X and Y axes. - /// - public bool Uniform { - get { return uniform; } - set { uniform = value; } - } - } + /// + /// When true and or is used, the bone is scaled on both the X and Y axes. + /// + public bool Uniform + { + get { return uniform; } + set { uniform = value; } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Json.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Json.cs index 7a9285e..63c500c 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Json.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Json.cs @@ -28,20 +28,22 @@ *****************************************************************************/ using System; -using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Text; -namespace Spine4_0_31 { - public static class Json { - public static object Deserialize (TextReader text) { - var parser = new SharpJson.JsonDecoder(); - parser.parseNumbersAsFloat = true; - return parser.Decode(text.ReadToEnd()); - } - } +namespace Spine4_0_31 +{ + public static class Json + { + public static object Deserialize(TextReader text) + { + var parser = new SharpJson.JsonDecoder(); + parser.parseNumbersAsFloat = true; + return parser.Decode(text.ReadToEnd()); + } + } } /** @@ -70,448 +72,504 @@ public static object Deserialize (TextReader text) { * 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. */ -namespace Spine4_0_31 { - class Lexer { - public enum Token { - None, - Null, - True, - False, - Colon, - Comma, - String, - Number, - CurlyOpen, - CurlyClose, - SquaredOpen, - SquaredClose, - }; - - public bool hasError { - get { - return !success; - } - } - - public int lineNumber { - get; - private set; - } - - public bool parseNumbersAsFloat { - get; - set; - } - - char[] json; - int index = 0; - bool success = true; - char[] stringBuffer = new char[4096]; - - public Lexer (string text) { - Reset(); - - json = text.ToCharArray(); - parseNumbersAsFloat = false; - } - - public void Reset () { - index = 0; - lineNumber = 1; - success = true; - } - - public string ParseString () { - int idx = 0; - StringBuilder builder = null; - - SkipWhiteSpaces(); - - // " - char c = json[index++]; - - bool failed = false; - bool complete = false; - - while (!complete && !failed) { - if (index == json.Length) - break; - - c = json[index++]; - if (c == '"') { - complete = true; - break; - } else if (c == '\\') { - if (index == json.Length) - break; - - c = json[index++]; - - switch (c) { - case '"': - stringBuffer[idx++] = '"'; - break; - case '\\': - stringBuffer[idx++] = '\\'; - break; - case '/': - stringBuffer[idx++] = '/'; - break; - case 'b': - stringBuffer[idx++] = '\b'; - break; - case 'f': - stringBuffer[idx++] = '\f'; - break; - case 'n': - stringBuffer[idx++] = '\n'; - break; - case 'r': - stringBuffer[idx++] = '\r'; - break; - case 't': - stringBuffer[idx++] = '\t'; - break; - case 'u': - int remainingLength = json.Length - index; - if (remainingLength >= 4) { - var hex = new string(json, index, 4); - - // XXX: handle UTF - stringBuffer[idx++] = (char)Convert.ToInt32(hex, 16); - - // skip 4 chars - index += 4; - } else { - failed = true; - } - break; - } - } else { - stringBuffer[idx++] = c; - } - - if (idx >= stringBuffer.Length) { - if (builder == null) - builder = new StringBuilder(); - - builder.Append(stringBuffer, 0, idx); - idx = 0; - } - } - - if (!complete) { - success = false; - return null; - } - - if (builder != null) - return builder.ToString(); - else - return new string(stringBuffer, 0, idx); - } - - string GetNumberString () { - SkipWhiteSpaces(); - - int lastIndex = GetLastIndexOfNumber(index); - int charLength = (lastIndex - index) + 1; - - var result = new string(json, index, charLength); - - index = lastIndex + 1; - - return result; - } - - public float ParseFloatNumber () { - float number; - var str = GetNumberString(); - - if (!float.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out number)) - return 0; - - return number; - } - - public double ParseDoubleNumber () { - double number; - var str = GetNumberString(); - - if (!double.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out number)) - return 0; - - return number; - } - - int GetLastIndexOfNumber (int index) { - int lastIndex; - - for (lastIndex = index; lastIndex < json.Length; lastIndex++) { - char ch = json[lastIndex]; - - if ((ch < '0' || ch > '9') && ch != '+' && ch != '-' - && ch != '.' && ch != 'e' && ch != 'E') - break; - } - - return lastIndex - 1; - } - - void SkipWhiteSpaces () { - for (; index < json.Length; index++) { - char ch = json[index]; - - if (ch == '\n') - lineNumber++; - - if (!char.IsWhiteSpace(json[index])) - break; - } - } - - public Token LookAhead () { - SkipWhiteSpaces(); - - int savedIndex = index; - return NextToken(json, ref savedIndex); - } - - public Token NextToken () { - SkipWhiteSpaces(); - return NextToken(json, ref index); - } - - static Token NextToken (char[] json, ref int index) { - if (index == json.Length) - return Token.None; - - char c = json[index++]; - - switch (c) { - case '{': - return Token.CurlyOpen; - case '}': - return Token.CurlyClose; - case '[': - return Token.SquaredOpen; - case ']': - return Token.SquaredClose; - case ',': - return Token.Comma; - case '"': - return Token.String; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case '-': - return Token.Number; - case ':': - return Token.Colon; - } - - index--; - - int remainingLength = json.Length - index; - - // false - if (remainingLength >= 5) { - if (json[index] == 'f' && - json[index + 1] == 'a' && - json[index + 2] == 'l' && - json[index + 3] == 's' && - json[index + 4] == 'e') { - index += 5; - return Token.False; - } - } - - // true - if (remainingLength >= 4) { - if (json[index] == 't' && - json[index + 1] == 'r' && - json[index + 2] == 'u' && - json[index + 3] == 'e') { - index += 4; - return Token.True; - } - } - - // null - if (remainingLength >= 4) { - if (json[index] == 'n' && - json[index + 1] == 'u' && - json[index + 2] == 'l' && - json[index + 3] == 'l') { - index += 4; - return Token.Null; - } - } - - return Token.None; - } - } - - public class JsonDecoder { - public string errorMessage { - get; - private set; - } - - public bool parseNumbersAsFloat { - get; - set; - } - - Lexer lexer; - - public JsonDecoder () { - errorMessage = null; - parseNumbersAsFloat = false; - } - - public object Decode (string text) { - errorMessage = null; - - lexer = new Lexer(text); - lexer.parseNumbersAsFloat = parseNumbersAsFloat; - - return ParseValue(); - } - - public static object DecodeText (string text) { - var builder = new JsonDecoder(); - return builder.Decode(text); - } - - IDictionary ParseObject () { - var table = new Dictionary(); - - // { - lexer.NextToken(); - - while (true) { - var token = lexer.LookAhead(); - - switch (token) { - case Lexer.Token.None: - TriggerError("Invalid token"); - return null; - case Lexer.Token.Comma: - lexer.NextToken(); - break; - case Lexer.Token.CurlyClose: - lexer.NextToken(); - return table; - default: - // name - string name = EvalLexer(lexer.ParseString()); - - if (errorMessage != null) - return null; - - // : - token = lexer.NextToken(); - - if (token != Lexer.Token.Colon) { - TriggerError("Invalid token; expected ':'"); - return null; - } - - // value - object value = ParseValue(); - - if (errorMessage != null) - return null; - - table[name] = value; - break; - } - } - - //return null; // Unreachable code - } - - IList ParseArray () { - var array = new List(); - - // [ - lexer.NextToken(); - - while (true) { - var token = lexer.LookAhead(); - - switch (token) { - case Lexer.Token.None: - TriggerError("Invalid token"); - return null; - case Lexer.Token.Comma: - lexer.NextToken(); - break; - case Lexer.Token.SquaredClose: - lexer.NextToken(); - return array; - default: - object value = ParseValue(); - - if (errorMessage != null) - return null; - - array.Add(value); - break; - } - } - - //return null; // Unreachable code - } - - object ParseValue () { - switch (lexer.LookAhead()) { - case Lexer.Token.String: - return EvalLexer(lexer.ParseString()); - case Lexer.Token.Number: - if (parseNumbersAsFloat) - return EvalLexer(lexer.ParseFloatNumber()); - else - return EvalLexer(lexer.ParseDoubleNumber()); - case Lexer.Token.CurlyOpen: - return ParseObject(); - case Lexer.Token.SquaredOpen: - return ParseArray(); - case Lexer.Token.True: - lexer.NextToken(); - return true; - case Lexer.Token.False: - lexer.NextToken(); - return false; - case Lexer.Token.Null: - lexer.NextToken(); - return null; - case Lexer.Token.None: - break; - } - - TriggerError("Unable to parse value"); - return null; - } - - void TriggerError (string message) { - errorMessage = string.Format("Error: '{0}' at line {1}", - message, lexer.lineNumber); - } - - T EvalLexer (T value) { - if (lexer.hasError) - TriggerError("Lexical error ocurred"); - - return value; - } - } +namespace Spine4_0_31 +{ + class Lexer + { + public enum Token + { + None, + Null, + True, + False, + Colon, + Comma, + String, + Number, + CurlyOpen, + CurlyClose, + SquaredOpen, + SquaredClose, + }; + + public bool hasError + { + get + { + return !success; + } + } + + public int lineNumber + { + get; + private set; + } + + public bool parseNumbersAsFloat + { + get; + set; + } + + char[] json; + int index = 0; + bool success = true; + char[] stringBuffer = new char[4096]; + + public Lexer(string text) + { + Reset(); + + json = text.ToCharArray(); + parseNumbersAsFloat = false; + } + + public void Reset() + { + index = 0; + lineNumber = 1; + success = true; + } + + public string ParseString() + { + int idx = 0; + StringBuilder builder = null; + + SkipWhiteSpaces(); + + // " + char c = json[index++]; + + bool failed = false; + bool complete = false; + + while (!complete && !failed) + { + if (index == json.Length) + break; + + c = json[index++]; + if (c == '"') + { + complete = true; + break; + } + else if (c == '\\') + { + if (index == json.Length) + break; + + c = json[index++]; + + switch (c) + { + case '"': + stringBuffer[idx++] = '"'; + break; + case '\\': + stringBuffer[idx++] = '\\'; + break; + case '/': + stringBuffer[idx++] = '/'; + break; + case 'b': + stringBuffer[idx++] = '\b'; + break; + case 'f': + stringBuffer[idx++] = '\f'; + break; + case 'n': + stringBuffer[idx++] = '\n'; + break; + case 'r': + stringBuffer[idx++] = '\r'; + break; + case 't': + stringBuffer[idx++] = '\t'; + break; + case 'u': + int remainingLength = json.Length - index; + if (remainingLength >= 4) + { + var hex = new string(json, index, 4); + + // XXX: handle UTF + stringBuffer[idx++] = (char)Convert.ToInt32(hex, 16); + + // skip 4 chars + index += 4; + } + else + { + failed = true; + } + break; + } + } + else + { + stringBuffer[idx++] = c; + } + + if (idx >= stringBuffer.Length) + { + if (builder == null) + builder = new StringBuilder(); + + builder.Append(stringBuffer, 0, idx); + idx = 0; + } + } + + if (!complete) + { + success = false; + return null; + } + + if (builder != null) + return builder.ToString(); + else + return new string(stringBuffer, 0, idx); + } + + string GetNumberString() + { + SkipWhiteSpaces(); + + int lastIndex = GetLastIndexOfNumber(index); + int charLength = (lastIndex - index) + 1; + + var result = new string(json, index, charLength); + + index = lastIndex + 1; + + return result; + } + + public float ParseFloatNumber() + { + float number; + var str = GetNumberString(); + + if (!float.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out number)) + return 0; + + return number; + } + + public double ParseDoubleNumber() + { + double number; + var str = GetNumberString(); + + if (!double.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out number)) + return 0; + + return number; + } + + int GetLastIndexOfNumber(int index) + { + int lastIndex; + + for (lastIndex = index; lastIndex < json.Length; lastIndex++) + { + char ch = json[lastIndex]; + + if ((ch < '0' || ch > '9') && ch != '+' && ch != '-' + && ch != '.' && ch != 'e' && ch != 'E') + break; + } + + return lastIndex - 1; + } + + void SkipWhiteSpaces() + { + for (; index < json.Length; index++) + { + char ch = json[index]; + + if (ch == '\n') + lineNumber++; + + if (!char.IsWhiteSpace(json[index])) + break; + } + } + + public Token LookAhead() + { + SkipWhiteSpaces(); + + int savedIndex = index; + return NextToken(json, ref savedIndex); + } + + public Token NextToken() + { + SkipWhiteSpaces(); + return NextToken(json, ref index); + } + + static Token NextToken(char[] json, ref int index) + { + if (index == json.Length) + return Token.None; + + char c = json[index++]; + + switch (c) + { + case '{': + return Token.CurlyOpen; + case '}': + return Token.CurlyClose; + case '[': + return Token.SquaredOpen; + case ']': + return Token.SquaredClose; + case ',': + return Token.Comma; + case '"': + return Token.String; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + return Token.Number; + case ':': + return Token.Colon; + } + + index--; + + int remainingLength = json.Length - index; + + // false + if (remainingLength >= 5) + { + if (json[index] == 'f' && + json[index + 1] == 'a' && + json[index + 2] == 'l' && + json[index + 3] == 's' && + json[index + 4] == 'e') + { + index += 5; + return Token.False; + } + } + + // true + if (remainingLength >= 4) + { + if (json[index] == 't' && + json[index + 1] == 'r' && + json[index + 2] == 'u' && + json[index + 3] == 'e') + { + index += 4; + return Token.True; + } + } + + // null + if (remainingLength >= 4) + { + if (json[index] == 'n' && + json[index + 1] == 'u' && + json[index + 2] == 'l' && + json[index + 3] == 'l') + { + index += 4; + return Token.Null; + } + } + + return Token.None; + } + } + + public class JsonDecoder + { + public string errorMessage + { + get; + private set; + } + + public bool parseNumbersAsFloat + { + get; + set; + } + + Lexer lexer; + + public JsonDecoder() + { + errorMessage = null; + parseNumbersAsFloat = false; + } + + public object Decode(string text) + { + errorMessage = null; + + lexer = new Lexer(text); + lexer.parseNumbersAsFloat = parseNumbersAsFloat; + + return ParseValue(); + } + + public static object DecodeText(string text) + { + var builder = new JsonDecoder(); + return builder.Decode(text); + } + + IDictionary ParseObject() + { + var table = new Dictionary(); + + // { + lexer.NextToken(); + + while (true) + { + var token = lexer.LookAhead(); + + switch (token) + { + case Lexer.Token.None: + TriggerError("Invalid token"); + return null; + case Lexer.Token.Comma: + lexer.NextToken(); + break; + case Lexer.Token.CurlyClose: + lexer.NextToken(); + return table; + default: + // name + string name = EvalLexer(lexer.ParseString()); + + if (errorMessage != null) + return null; + + // : + token = lexer.NextToken(); + + if (token != Lexer.Token.Colon) + { + TriggerError("Invalid token; expected ':'"); + return null; + } + + // value + object value = ParseValue(); + + if (errorMessage != null) + return null; + + table[name] = value; + break; + } + } + + //return null; // Unreachable code + } + + IList ParseArray() + { + var array = new List(); + + // [ + lexer.NextToken(); + + while (true) + { + var token = lexer.LookAhead(); + + switch (token) + { + case Lexer.Token.None: + TriggerError("Invalid token"); + return null; + case Lexer.Token.Comma: + lexer.NextToken(); + break; + case Lexer.Token.SquaredClose: + lexer.NextToken(); + return array; + default: + object value = ParseValue(); + + if (errorMessage != null) + return null; + + array.Add(value); + break; + } + } + + //return null; // Unreachable code + } + + object ParseValue() + { + switch (lexer.LookAhead()) + { + case Lexer.Token.String: + return EvalLexer(lexer.ParseString()); + case Lexer.Token.Number: + if (parseNumbersAsFloat) + return EvalLexer(lexer.ParseFloatNumber()); + else + return EvalLexer(lexer.ParseDoubleNumber()); + case Lexer.Token.CurlyOpen: + return ParseObject(); + case Lexer.Token.SquaredOpen: + return ParseArray(); + case Lexer.Token.True: + lexer.NextToken(); + return true; + case Lexer.Token.False: + lexer.NextToken(); + return false; + case Lexer.Token.Null: + lexer.NextToken(); + return null; + case Lexer.Token.None: + break; + } + + TriggerError("Unable to parse value"); + return null; + } + + void TriggerError(string message) + { + errorMessage = string.Format("Error: '{0}' at line {1}", + message, lexer.lineNumber); + } + + T EvalLexer(T value) + { + if (lexer.hasError) + TriggerError("Lexical error ocurred"); + + return value; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/MathUtils.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/MathUtils.cs index 5d5762c..63e32ad 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/MathUtils.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/MathUtils.cs @@ -31,14 +31,16 @@ using System; -namespace Spine4_0_31 { - public static class MathUtils { - public const float PI = 3.1415927f; - public const float PI2 = PI * 2; - public const float RadDeg = 180f / PI; - public const float DegRad = PI / 180; +namespace Spine4_0_31 +{ + public static class MathUtils + { + public const float PI = 3.1415927f; + public const float PI2 = PI * 2; + public const float RadDeg = 180f / PI; + public const float DegRad = PI / 180; - static Random random = new Random(); + static Random random = new Random(); #if USE_FAST_SIN_COS_ATAN2_APPROXIMATIONS const int SIN_BITS = 14; // 16KB. Adjust for accuracy. @@ -95,79 +97,95 @@ static public float Atan2 (float y, float x) { return y < 0f ? atan - PI : atan; } #else - /// Returns the sine of a given angle in radians. - static public float Sin (float radians) { - return (float)Math.Sin(radians); - } - - /// Returns the cosine of a given angle in radians. - static public float Cos (float radians) { - return (float)Math.Cos(radians); - } - - /// Returns the sine of a given angle in degrees. - static public float SinDeg (float degrees) { - return (float)Math.Sin(degrees * DegRad); - } - - /// Returns the cosine of a given angle in degrees. - static public float CosDeg (float degrees) { - return (float)Math.Cos(degrees * DegRad); - } - - /// Returns the atan2 using Math.Atan2. - static public float Atan2 (float y, float x) { - return (float)Math.Atan2(y, x); - } + /// Returns the sine of a given angle in radians. + static public float Sin(float radians) + { + return (float)Math.Sin(radians); + } + + /// Returns the cosine of a given angle in radians. + static public float Cos(float radians) + { + return (float)Math.Cos(radians); + } + + /// Returns the sine of a given angle in degrees. + static public float SinDeg(float degrees) + { + return (float)Math.Sin(degrees * DegRad); + } + + /// Returns the cosine of a given angle in degrees. + static public float CosDeg(float degrees) + { + return (float)Math.Cos(degrees * DegRad); + } + + /// Returns the atan2 using Math.Atan2. + static public float Atan2(float y, float x) + { + return (float)Math.Atan2(y, x); + } #endif - static public float Clamp (float value, float min, float max) { - if (value < min) return min; - if (value > max) return max; - return value; - } - - static public float RandomTriangle (float min, float max) { - return RandomTriangle(min, max, (min + max) * 0.5f); - } - - static public float RandomTriangle (float min, float max, float mode) { - float u = (float)random.NextDouble(); - float d = max - min; - if (u <= (mode - min) / d) return min + (float)Math.Sqrt(u * d * (mode - min)); - return max - (float)Math.Sqrt((1 - u) * d * (max - mode)); - } - } - - public abstract class IInterpolation { - public static IInterpolation Pow2 = new Pow(2); - public static IInterpolation Pow2Out = new PowOut(2); - - protected abstract float Apply (float a); - - public float Apply (float start, float end, float a) { - return start + (end - start) * Apply(a); - } - } - - public class Pow : IInterpolation { - public float Power { get; set; } - - public Pow (float power) { - Power = power; - } - - protected override float Apply (float a) { - if (a <= 0.5f) return (float)Math.Pow(a * 2, Power) / 2; - return (float)Math.Pow((a - 1) * 2, Power) / (Power % 2 == 0 ? -2 : 2) + 1; - } - } - - public class PowOut : Pow { - public PowOut (float power) : base(power) { - } - - protected override float Apply (float a) { - return (float)Math.Pow(a - 1, Power) * (Power % 2 == 0 ? -1 : 1) + 1; - } - } + static public float Clamp(float value, float min, float max) + { + if (value < min) return min; + if (value > max) return max; + return value; + } + + static public float RandomTriangle(float min, float max) + { + return RandomTriangle(min, max, (min + max) * 0.5f); + } + + static public float RandomTriangle(float min, float max, float mode) + { + float u = (float)random.NextDouble(); + float d = max - min; + if (u <= (mode - min) / d) return min + (float)Math.Sqrt(u * d * (mode - min)); + return max - (float)Math.Sqrt((1 - u) * d * (max - mode)); + } + } + + public abstract class IInterpolation + { + public static IInterpolation Pow2 = new Pow(2); + public static IInterpolation Pow2Out = new PowOut(2); + + protected abstract float Apply(float a); + + public float Apply(float start, float end, float a) + { + return start + (end - start) * Apply(a); + } + } + + public class Pow : IInterpolation + { + public float Power { get; set; } + + public Pow(float power) + { + Power = power; + } + + protected override float Apply(float a) + { + if (a <= 0.5f) return (float)Math.Pow(a * 2, Power) / 2; + return (float)Math.Pow((a - 1) * 2, Power) / (Power % 2 == 0 ? -2 : 2) + 1; + } + } + + public class PowOut : Pow + { + public PowOut(float power) : base(power) + { + } + + protected override float Apply(float a) + { + return (float)Math.Pow(a - 1, Power) * (Power % 2 == 0 ? -1 : 1) + 1; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/PathConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/PathConstraint.cs index 3f3e27c..a9d3b7e 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/PathConstraint.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/PathConstraint.cs @@ -29,482 +29,550 @@ using System; -namespace Spine4_0_31 { - - /// - /// - /// Stores the current pose for a path constraint. A path constraint adjusts the rotation, translation, and scale of the - /// constrained bones so they follow a . - /// - /// See Path constraints in the Spine User Guide. - /// - public class PathConstraint : IUpdatable { - const int NONE = -1, BEFORE = -2, AFTER = -3; - const float Epsilon = 0.00001f; - - internal PathConstraintData data; - internal ExposedList bones; - internal Slot target; - internal float position, spacing, mixRotate, mixX, mixY; - - internal bool active; - - internal ExposedList spaces = new ExposedList(), positions = new ExposedList(); - internal ExposedList world = new ExposedList(), curves = new ExposedList(), lengths = new ExposedList(); - internal float[] segments = new float[10]; - - public PathConstraint (PathConstraintData data, Skeleton skeleton) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.data = data; - bones = new ExposedList(data.Bones.Count); - foreach (BoneData boneData in data.bones) - bones.Add(skeleton.FindBone(boneData.name)); - target = skeleton.FindSlot(data.target.name); - position = data.position; - spacing = data.spacing; - mixRotate = data.mixRotate; - mixX = data.mixX; - mixY = data.mixY; - } - - /// Copy constructor. - public PathConstraint (PathConstraint constraint, Skeleton skeleton) { - if (constraint == null) throw new ArgumentNullException("constraint cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); - data = constraint.data; - bones = new ExposedList(constraint.Bones.Count); - foreach (Bone bone in constraint.Bones) - bones.Add(skeleton.Bones.Items[bone.data.index]); - target = skeleton.slots.Items[constraint.target.data.index]; - position = constraint.position; - spacing = constraint.spacing; - mixRotate = constraint.mixRotate; - mixX = constraint.mixX; - mixY = constraint.mixY; - } - - public static void ArraysFill (float[] a, int fromIndex, int toIndex, float val) { - for (int i = fromIndex; i < toIndex; i++) - a[i] = val; - } - - public void Update () { - PathAttachment attachment = target.Attachment as PathAttachment; - if (attachment == null) return; - - float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY; - if (mixRotate == 0 && mixX == 0 && mixY == 0) return; - - PathConstraintData data = this.data; - bool tangents = data.rotateMode == RotateMode.Tangent, scale = data.rotateMode == RotateMode.ChainScale; - int boneCount = this.bones.Count, spacesCount = tangents ? boneCount : boneCount + 1; - Bone[] bonesItems = this.bones.Items; - float[] spaces = this.spaces.Resize(spacesCount).Items, lengths = scale ? this.lengths.Resize(boneCount).Items : null; - float spacing = this.spacing; - switch (data.spacingMode) { - case SpacingMode.Percent: - if (scale) { - for (int i = 0, n = spacesCount - 1; i < n; i++) { - Bone bone = bonesItems[i]; - float setupLength = bone.data.length; - if (setupLength < PathConstraint.Epsilon) - lengths[i] = 0; - else { - float x = setupLength * bone.a, y = setupLength * bone.c; - lengths[i] = (float)Math.Sqrt(x * x + y * y); - } - } - } - ArraysFill(spaces, 1, spacesCount, spacing); - break; - case SpacingMode.Proportional: { - float sum = 0; - for (int i = 0, n = spacesCount - 1; i < n;) { - Bone bone = bonesItems[i]; - float setupLength = bone.data.length; - if (setupLength < PathConstraint.Epsilon) { - if (scale) lengths[i] = 0; - spaces[++i] = spacing; - } else { - float x = setupLength * bone.a, y = setupLength * bone.c; - float length = (float)Math.Sqrt(x * x + y * y); - if (scale) lengths[i] = length; - spaces[++i] = length; - sum += length; - } - } - if (sum > 0) { - sum = spacesCount / sum * spacing; - for (int i = 1; i < spacesCount; i++) - spaces[i] *= sum; - } - break; - } - default: { - bool lengthSpacing = data.spacingMode == SpacingMode.Length; - for (int i = 0, n = spacesCount - 1; i < n;) { - Bone bone = bonesItems[i]; - float setupLength = bone.data.length; - if (setupLength < PathConstraint.Epsilon) { - if (scale) lengths[i] = 0; - spaces[++i] = spacing; - } else { - float x = setupLength * bone.a, y = setupLength * bone.c; - float length = (float)Math.Sqrt(x * x + y * y); - if (scale) lengths[i] = length; - spaces[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length / setupLength; - } - } - break; - } - } - - float[] positions = ComputeWorldPositions(attachment, spacesCount, tangents); - float boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation; - bool tip; - if (offsetRotation == 0) { - tip = data.rotateMode == RotateMode.Chain; - } else { - tip = false; - Bone p = target.bone; - offsetRotation *= p.a * p.d - p.b * p.c > 0 ? MathUtils.DegRad : -MathUtils.DegRad; - } - for (int i = 0, p = 3; i < boneCount; i++, p += 3) { - Bone bone = bonesItems[i]; - bone.worldX += (boneX - bone.worldX) * mixX; - bone.worldY += (boneY - bone.worldY) * mixY; - float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY; - if (scale) { - float length = lengths[i]; - if (length >= PathConstraint.Epsilon) { - float s = ((float)Math.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 = MathUtils.Atan2(dy, dx); - r -= MathUtils.Atan2(c, a); - if (tip) { - cos = MathUtils.Cos(r); - sin = MathUtils.Sin(r); - float length = bone.data.length; - boneX += (length * (cos * a - sin * c) - dx) * mixRotate; - boneY += (length * (sin * a + cos * c) - dy) * mixRotate; - } else - r += offsetRotation; - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) // - r += MathUtils.PI2; - r *= mixRotate; - cos = MathUtils.Cos(r); - sin = MathUtils.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(); - } - } - - float[] ComputeWorldPositions (PathAttachment path, int spacesCount, bool tangents) { - Slot target = this.target; - float position = this.position; - float[] spaces = this.spaces.Items, output = this.positions.Resize(spacesCount * 3 + 2).Items, world; - bool closed = path.Closed; - int verticesLength = path.WorldVerticesLength, curveCount = verticesLength / 6, prevCurve = NONE; - - float pathLength, multiplier; - if (!path.ConstantSpeed) { - float[] lengths = path.Lengths; - curveCount -= closed ? 1 : 2; - pathLength = lengths[curveCount]; - - if (data.positionMode == PositionMode.Percent) position *= pathLength; - - switch (data.spacingMode) { - case SpacingMode.Percent: - multiplier = pathLength; - break; - case SpacingMode.Proportional: - multiplier = pathLength / spacesCount; - break; - default: - multiplier = 1; - break; - } - - world = this.world.Resize(8).Items; - 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 %= pathLength; - if (p < 0) p += pathLength; - curve = 0; - } else if (p < 0) { - if (prevCurve != BEFORE) { - prevCurve = BEFORE; - path.ComputeWorldVertices(target, 2, 4, world, 0, 2); - } - AddBeforePosition(p, world, 0, output, o); - continue; - } else if (p > pathLength) { - if (prevCurve != AFTER) { - prevCurve = AFTER; - path.ComputeWorldVertices(target, verticesLength - 6, 4, world, 0, 2); - } - AddAfterPosition(p - pathLength, world, 0, output, 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, 2); - path.ComputeWorldVertices(target, 0, 4, world, 4, 2); - } else - path.ComputeWorldVertices(target, curve * 6 + 2, 8, world, 0, 2); - } - AddCurvePosition(p, world[0], world[1], world[2], world[3], world[4], world[5], world[6], world[7], output, o, - tangents || (i > 0 && space < PathConstraint.Epsilon)); - } - return output; - } - - // World vertices. - if (closed) { - verticesLength += 2; - world = this.world.Resize(verticesLength).Items; - path.ComputeWorldVertices(target, 2, verticesLength - 4, world, 0, 2); - path.ComputeWorldVertices(target, 0, 2, world, verticesLength - 4, 2); - world[verticesLength - 2] = world[0]; - world[verticesLength - 1] = world[1]; - } else { - curveCount--; - verticesLength -= 4; - world = this.world.Resize(verticesLength).Items; - path.ComputeWorldVertices(target, 2, verticesLength, world, 0, 2); - } - - // Curve lengths. - float[] curves = this.curves.Resize(curveCount).Items; - 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 += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - dfx += ddfx; - dfy += ddfy; - ddfx += dddfx; - ddfy += dddfy; - pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - dfx += ddfx; - dfy += ddfy; - pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - dfx += ddfx + dddfx; - dfy += ddfy + dddfy; - pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - curves[i] = pathLength; - x1 = x2; - y1 = y2; - } - - if (data.positionMode == PositionMode.Percent) position *= pathLength; - - switch (data.spacingMode) { - case SpacingMode.Percent: - multiplier = pathLength; - break; - case SpacingMode.Proportional: - multiplier = pathLength / spacesCount; - break; - default: - multiplier = 1; - break; - } - - float[] segments = this.segments; - 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 %= pathLength; - if (p < 0) p += pathLength; - curve = 0; - } else if (p < 0) { - AddBeforePosition(p, world, 0, output, o); - continue; - } else if (p > pathLength) { - AddAfterPosition(p - pathLength, world, verticesLength - 4, output, 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 = (float)Math.Sqrt(dfx * dfx + dfy * dfy); - segments[0] = curveLength; - for (ii = 1; ii < 8; ii++) { - dfx += ddfx; - dfy += ddfy; - ddfx += dddfx; - ddfy += dddfy; - curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - segments[ii] = curveLength; - } - dfx += ddfx; - dfy += ddfy; - curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - segments[8] = curveLength; - dfx += ddfx + dddfx; - dfy += ddfy + dddfy; - curveLength += (float)Math.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, output, o, tangents || (i > 0 && space < PathConstraint.Epsilon)); - } - return output; - } - - static void AddBeforePosition (float p, float[] temp, int i, float[] output, int o) { - float x1 = temp[i], y1 = temp[i + 1], dx = temp[i + 2] - x1, dy = temp[i + 3] - y1, r = MathUtils.Atan2(dy, dx); - output[o] = x1 + p * MathUtils.Cos(r); - output[o + 1] = y1 + p * MathUtils.Sin(r); - output[o + 2] = r; - } - - static void AddAfterPosition (float p, float[] temp, int i, float[] output, int o) { - float x1 = temp[i + 2], y1 = temp[i + 3], dx = x1 - temp[i], dy = y1 - temp[i + 1], r = MathUtils.Atan2(dy, dx); - output[o] = x1 + p * MathUtils.Cos(r); - output[o + 1] = y1 + p * MathUtils.Sin(r); - output[o + 2] = r; - } - - static void AddCurvePosition (float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2, - float[] output, int o, bool tangents) { - if (p < PathConstraint.Epsilon || float.IsNaN(p)) { - output[o] = x1; - output[o + 1] = y1; - output[o + 2] = (float)Math.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.001f) - output[o + 2] = (float)Math.Atan2(cy1 - y1, cx1 - x1); - else - output[o + 2] = (float)Math.Atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt)); - } - } - - /// The position along the path. - public float Position { get { return position; } set { position = value; } } - /// The spacing between bones. - public float Spacing { get { return spacing; } set { spacing = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotations. - public float MixRotate { get { return mixRotate; } set { mixRotate = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. - public float MixX { get { return mixX; } set { mixX = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. - public float MixY { get { return mixY; } set { mixY = value; } } - /// The bones that will be modified by this path constraint. - public ExposedList Bones { get { return bones; } } - /// The slot whose path attachment will be used to constrained the bones. - public Slot Target { get { return target; } set { target = value; } } - public bool Active { get { return active; } } - /// The path constraint's setup pose data. - public PathConstraintData Data { get { return data; } } - } +namespace Spine4_0_31 +{ + + /// + /// + /// Stores the current pose for a path constraint. A path constraint adjusts the rotation, translation, and scale of the + /// constrained bones so they follow a . + /// + /// See Path constraints in the Spine User Guide. + /// + public class PathConstraint : IUpdatable + { + const int NONE = -1, BEFORE = -2, AFTER = -3; + const float Epsilon = 0.00001f; + + internal PathConstraintData data; + internal ExposedList bones; + internal Slot target; + internal float position, spacing, mixRotate, mixX, mixY; + + internal bool active; + + internal ExposedList spaces = new ExposedList(), positions = new ExposedList(); + internal ExposedList world = new ExposedList(), curves = new ExposedList(), lengths = new ExposedList(); + internal float[] segments = new float[10]; + + public PathConstraint(PathConstraintData data, Skeleton skeleton) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + bones = new ExposedList(data.Bones.Count); + foreach (BoneData boneData in data.bones) + bones.Add(skeleton.FindBone(boneData.name)); + target = skeleton.FindSlot(data.target.name); + position = data.position; + spacing = data.spacing; + mixRotate = data.mixRotate; + mixX = data.mixX; + mixY = data.mixY; + } + + /// Copy constructor. + public PathConstraint(PathConstraint constraint, Skeleton skeleton) + { + if (constraint == null) throw new ArgumentNullException("constraint cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); + data = constraint.data; + bones = new ExposedList(constraint.Bones.Count); + foreach (Bone bone in constraint.Bones) + bones.Add(skeleton.Bones.Items[bone.data.index]); + target = skeleton.slots.Items[constraint.target.data.index]; + position = constraint.position; + spacing = constraint.spacing; + mixRotate = constraint.mixRotate; + mixX = constraint.mixX; + mixY = constraint.mixY; + } + + public static void ArraysFill(float[] a, int fromIndex, int toIndex, float val) + { + for (int i = fromIndex; i < toIndex; i++) + a[i] = val; + } + + public void Update() + { + PathAttachment attachment = target.Attachment as PathAttachment; + if (attachment == null) return; + + float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY; + if (mixRotate == 0 && mixX == 0 && mixY == 0) return; + + PathConstraintData data = this.data; + bool tangents = data.rotateMode == RotateMode.Tangent, scale = data.rotateMode == RotateMode.ChainScale; + int boneCount = this.bones.Count, spacesCount = tangents ? boneCount : boneCount + 1; + Bone[] bonesItems = this.bones.Items; + float[] spaces = this.spaces.Resize(spacesCount).Items, lengths = scale ? this.lengths.Resize(boneCount).Items : null; + float spacing = this.spacing; + switch (data.spacingMode) + { + case SpacingMode.Percent: + if (scale) + { + for (int i = 0, n = spacesCount - 1; i < n; i++) + { + Bone bone = bonesItems[i]; + float setupLength = bone.data.length; + if (setupLength < PathConstraint.Epsilon) + lengths[i] = 0; + else + { + float x = setupLength * bone.a, y = setupLength * bone.c; + lengths[i] = (float)Math.Sqrt(x * x + y * y); + } + } + } + ArraysFill(spaces, 1, spacesCount, spacing); + break; + case SpacingMode.Proportional: + { + float sum = 0; + for (int i = 0, n = spacesCount - 1; i < n;) + { + Bone bone = bonesItems[i]; + float setupLength = bone.data.length; + if (setupLength < PathConstraint.Epsilon) + { + if (scale) lengths[i] = 0; + spaces[++i] = spacing; + } + else + { + float x = setupLength * bone.a, y = setupLength * bone.c; + float length = (float)Math.Sqrt(x * x + y * y); + if (scale) lengths[i] = length; + spaces[++i] = length; + sum += length; + } + } + if (sum > 0) + { + sum = spacesCount / sum * spacing; + for (int i = 1; i < spacesCount; i++) + spaces[i] *= sum; + } + break; + } + default: + { + bool lengthSpacing = data.spacingMode == SpacingMode.Length; + for (int i = 0, n = spacesCount - 1; i < n;) + { + Bone bone = bonesItems[i]; + float setupLength = bone.data.length; + if (setupLength < PathConstraint.Epsilon) + { + if (scale) lengths[i] = 0; + spaces[++i] = spacing; + } + else + { + float x = setupLength * bone.a, y = setupLength * bone.c; + float length = (float)Math.Sqrt(x * x + y * y); + if (scale) lengths[i] = length; + spaces[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length / setupLength; + } + } + break; + } + } + + float[] positions = ComputeWorldPositions(attachment, spacesCount, tangents); + float boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation; + bool tip; + if (offsetRotation == 0) + { + tip = data.rotateMode == RotateMode.Chain; + } + else + { + tip = false; + Bone p = target.bone; + offsetRotation *= p.a * p.d - p.b * p.c > 0 ? MathUtils.DegRad : -MathUtils.DegRad; + } + for (int i = 0, p = 3; i < boneCount; i++, p += 3) + { + Bone bone = bonesItems[i]; + bone.worldX += (boneX - bone.worldX) * mixX; + bone.worldY += (boneY - bone.worldY) * mixY; + float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY; + if (scale) + { + float length = lengths[i]; + if (length >= PathConstraint.Epsilon) + { + float s = ((float)Math.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 = MathUtils.Atan2(dy, dx); + r -= MathUtils.Atan2(c, a); + if (tip) + { + cos = MathUtils.Cos(r); + sin = MathUtils.Sin(r); + float length = bone.data.length; + boneX += (length * (cos * a - sin * c) - dx) * mixRotate; + boneY += (length * (sin * a + cos * c) - dy) * mixRotate; + } + else + r += offsetRotation; + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) // + r += MathUtils.PI2; + r *= mixRotate; + cos = MathUtils.Cos(r); + sin = MathUtils.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(); + } + } + + float[] ComputeWorldPositions(PathAttachment path, int spacesCount, bool tangents) + { + Slot target = this.target; + float position = this.position; + float[] spaces = this.spaces.Items, output = this.positions.Resize(spacesCount * 3 + 2).Items, world; + bool closed = path.Closed; + int verticesLength = path.WorldVerticesLength, curveCount = verticesLength / 6, prevCurve = NONE; + + float pathLength, multiplier; + if (!path.ConstantSpeed) + { + float[] lengths = path.Lengths; + curveCount -= closed ? 1 : 2; + pathLength = lengths[curveCount]; + + if (data.positionMode == PositionMode.Percent) position *= pathLength; + + switch (data.spacingMode) + { + case SpacingMode.Percent: + multiplier = pathLength; + break; + case SpacingMode.Proportional: + multiplier = pathLength / spacesCount; + break; + default: + multiplier = 1; + break; + } + + world = this.world.Resize(8).Items; + 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 %= pathLength; + if (p < 0) p += pathLength; + curve = 0; + } + else if (p < 0) + { + if (prevCurve != BEFORE) + { + prevCurve = BEFORE; + path.ComputeWorldVertices(target, 2, 4, world, 0, 2); + } + AddBeforePosition(p, world, 0, output, o); + continue; + } + else if (p > pathLength) + { + if (prevCurve != AFTER) + { + prevCurve = AFTER; + path.ComputeWorldVertices(target, verticesLength - 6, 4, world, 0, 2); + } + AddAfterPosition(p - pathLength, world, 0, output, 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, 2); + path.ComputeWorldVertices(target, 0, 4, world, 4, 2); + } + else + path.ComputeWorldVertices(target, curve * 6 + 2, 8, world, 0, 2); + } + AddCurvePosition(p, world[0], world[1], world[2], world[3], world[4], world[5], world[6], world[7], output, o, + tangents || (i > 0 && space < PathConstraint.Epsilon)); + } + return output; + } + + // World vertices. + if (closed) + { + verticesLength += 2; + world = this.world.Resize(verticesLength).Items; + path.ComputeWorldVertices(target, 2, verticesLength - 4, world, 0, 2); + path.ComputeWorldVertices(target, 0, 2, world, verticesLength - 4, 2); + world[verticesLength - 2] = world[0]; + world[verticesLength - 1] = world[1]; + } + else + { + curveCount--; + verticesLength -= 4; + world = this.world.Resize(verticesLength).Items; + path.ComputeWorldVertices(target, 2, verticesLength, world, 0, 2); + } + + // Curve lengths. + float[] curves = this.curves.Resize(curveCount).Items; + 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 += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx; + dfy += ddfy; + pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx + dddfx; + dfy += ddfy + dddfy; + pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + curves[i] = pathLength; + x1 = x2; + y1 = y2; + } + + if (data.positionMode == PositionMode.Percent) position *= pathLength; + + switch (data.spacingMode) + { + case SpacingMode.Percent: + multiplier = pathLength; + break; + case SpacingMode.Proportional: + multiplier = pathLength / spacesCount; + break; + default: + multiplier = 1; + break; + } + + float[] segments = this.segments; + 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 %= pathLength; + if (p < 0) p += pathLength; + curve = 0; + } + else if (p < 0) + { + AddBeforePosition(p, world, 0, output, o); + continue; + } + else if (p > pathLength) + { + AddAfterPosition(p - pathLength, world, verticesLength - 4, output, 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 = (float)Math.Sqrt(dfx * dfx + dfy * dfy); + segments[0] = curveLength; + for (ii = 1; ii < 8; ii++) + { + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + segments[ii] = curveLength; + } + dfx += ddfx; + dfy += ddfy; + curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + segments[8] = curveLength; + dfx += ddfx + dddfx; + dfy += ddfy + dddfy; + curveLength += (float)Math.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, output, o, tangents || (i > 0 && space < PathConstraint.Epsilon)); + } + return output; + } + + static void AddBeforePosition(float p, float[] temp, int i, float[] output, int o) + { + float x1 = temp[i], y1 = temp[i + 1], dx = temp[i + 2] - x1, dy = temp[i + 3] - y1, r = MathUtils.Atan2(dy, dx); + output[o] = x1 + p * MathUtils.Cos(r); + output[o + 1] = y1 + p * MathUtils.Sin(r); + output[o + 2] = r; + } + + static void AddAfterPosition(float p, float[] temp, int i, float[] output, int o) + { + float x1 = temp[i + 2], y1 = temp[i + 3], dx = x1 - temp[i], dy = y1 - temp[i + 1], r = MathUtils.Atan2(dy, dx); + output[o] = x1 + p * MathUtils.Cos(r); + output[o + 1] = y1 + p * MathUtils.Sin(r); + output[o + 2] = r; + } + + static void AddCurvePosition(float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2, + float[] output, int o, bool tangents) + { + if (p < PathConstraint.Epsilon || float.IsNaN(p)) + { + output[o] = x1; + output[o + 1] = y1; + output[o + 2] = (float)Math.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.001f) + output[o + 2] = (float)Math.Atan2(cy1 - y1, cx1 - x1); + else + output[o + 2] = (float)Math.Atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt)); + } + } + + /// The position along the path. + public float Position { get { return position; } set { position = value; } } + /// The spacing between bones. + public float Spacing { get { return spacing; } set { spacing = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotations. + public float MixRotate { get { return mixRotate; } set { mixRotate = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. + public float MixX { get { return mixX; } set { mixX = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. + public float MixY { get { return mixY; } set { mixY = value; } } + /// The bones that will be modified by this path constraint. + public ExposedList Bones { get { return bones; } } + /// The slot whose path attachment will be used to constrained the bones. + public Slot Target { get { return target; } set { target = value; } } + public bool Active { get { return active; } } + /// The path constraint's setup pose data. + public PathConstraintData Data { get { return data; } } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/PathConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/PathConstraintData.cs index fda0981..28d4c07 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/PathConstraintData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/PathConstraintData.cs @@ -27,46 +27,50 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; +namespace Spine4_0_31 +{ + public class PathConstraintData : ConstraintData + { + internal ExposedList bones = new ExposedList(); + internal SlotData target; + internal PositionMode positionMode; + internal SpacingMode spacingMode; + internal RotateMode rotateMode; + internal float offsetRotation; + internal float position, spacing, mixRotate, mixX, mixY; -namespace Spine4_0_31 { - public class PathConstraintData : ConstraintData { - internal ExposedList bones = new ExposedList(); - internal SlotData target; - internal PositionMode positionMode; - internal SpacingMode spacingMode; - internal RotateMode rotateMode; - internal float offsetRotation; - internal float position, spacing, mixRotate, mixX, mixY; + public PathConstraintData(string name) : base(name) + { + } - public PathConstraintData (string name) : base(name) { - } + public ExposedList Bones { get { return bones; } } + public SlotData Target { get { return target; } set { target = value; } } + public PositionMode PositionMode { get { return positionMode; } set { positionMode = value; } } + public SpacingMode SpacingMode { get { return spacingMode; } set { spacingMode = value; } } + public RotateMode RotateMode { get { return rotateMode; } set { rotateMode = value; } } + public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } + public float Position { get { return position; } set { position = value; } } + public float Spacing { get { return spacing; } set { spacing = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. + public float RotateMix { get { return mixRotate; } set { mixRotate = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. + public float MixX { get { return mixX; } set { mixX = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. + public float MixY { get { return mixY; } set { mixY = value; } } + } - public ExposedList Bones { get { return bones; } } - public SlotData Target { get { return target; } set { target = value; } } - public PositionMode PositionMode { get { return positionMode; } set { positionMode = value; } } - public SpacingMode SpacingMode { get { return spacingMode; } set { spacingMode = value; } } - public RotateMode RotateMode { get { return rotateMode; } set { rotateMode = value; } } - public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } - public float Position { get { return position; } set { position = value; } } - public float Spacing { get { return spacing; } set { spacing = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. - public float RotateMix { get { return mixRotate; } set { mixRotate = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. - public float MixX { get { return mixX; } set { mixX = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. - public float MixY { get { return mixY; } set { mixY = value; } } - } + public enum PositionMode + { + Fixed, Percent + } - public enum PositionMode { - Fixed, Percent - } + public enum SpacingMode + { + Length, Fixed, Percent, Proportional + } - public enum SpacingMode { - Length, Fixed, Percent, Proportional - } - - public enum RotateMode { - Tangent, Chain, ChainScale - } + public enum RotateMode + { + Tangent, Chain, ChainScale + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Skeleton.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Skeleton.cs index 8e4f414..4e21450 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Skeleton.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Skeleton.cs @@ -28,589 +28,669 @@ *****************************************************************************/ using System; -using System.Collections.Generic; - -namespace Spine4_0_31 { - public class Skeleton { - internal SkeletonData data; - internal ExposedList bones; - internal ExposedList slots; - internal ExposedList drawOrder; - internal ExposedList ikConstraints; - internal ExposedList transformConstraints; - internal ExposedList pathConstraints; - internal ExposedList updateCache = new ExposedList(); - internal Skin skin; - internal float r = 1, g = 1, b = 1, a = 1; - internal float time; - private float scaleX = 1, scaleY = 1; - internal float x, y; - - public SkeletonData Data { get { return data; } } - public ExposedList Bones { get { return bones; } } - public ExposedList UpdateCacheList { get { return updateCache; } } - public ExposedList Slots { get { return slots; } } - public ExposedList DrawOrder { get { return drawOrder; } } - public ExposedList IkConstraints { get { return ikConstraints; } } - public ExposedList PathConstraints { get { return pathConstraints; } } - public ExposedList TransformConstraints { get { return transformConstraints; } } - - public Skin Skin { - /// The skeleton's current skin. May be null. - get { return skin; } - /// Sets a skin, . - set { SetSkin(value); } - } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } - public float Time { get { return time; } set { time = value; } } - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - public float ScaleY { get { return scaleY * (Bone.yDown ? -1 : 1); } set { scaleY = value; } } - - [Obsolete("Use ScaleX instead. FlipX is when ScaleX is negative.")] - public bool FlipX { get { return scaleX < 0; } set { scaleX = value ? -1f : 1f; } } - - [Obsolete("Use ScaleY instead. FlipY is when ScaleY is negative.")] - public bool FlipY { get { return scaleY < 0; } set { scaleY = value ? -1f : 1f; } } - - /// Returns the root bone, or null if the skeleton has no bones. - public Bone RootBone { - get { return bones.Count == 0 ? null : bones.Items[0]; } - } - - public Skeleton (SkeletonData data) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - this.data = data; - - bones = new ExposedList(data.bones.Count); - Bone[] bonesItems = this.bones.Items; - foreach (BoneData boneData in data.bones) { - Bone bone; - if (boneData.parent == null) { - bone = new Bone(boneData, this, null); - } else { - Bone parent = bonesItems[boneData.parent.index]; - bone = new Bone(boneData, this, parent); - parent.children.Add(bone); - } - this.bones.Add(bone); - } - - slots = new ExposedList(data.slots.Count); - drawOrder = new ExposedList(data.slots.Count); - foreach (SlotData slotData in data.slots) { - Bone bone = bonesItems[slotData.boneData.index]; - Slot slot = new Slot(slotData, bone); - slots.Add(slot); - drawOrder.Add(slot); - } - - ikConstraints = new ExposedList(data.ikConstraints.Count); - foreach (IkConstraintData ikConstraintData in data.ikConstraints) - ikConstraints.Add(new IkConstraint(ikConstraintData, this)); - - transformConstraints = new ExposedList(data.transformConstraints.Count); - foreach (TransformConstraintData transformConstraintData in data.transformConstraints) - transformConstraints.Add(new TransformConstraint(transformConstraintData, this)); - - pathConstraints = new ExposedList(data.pathConstraints.Count); - foreach (PathConstraintData pathConstraintData in data.pathConstraints) - pathConstraints.Add(new PathConstraint(pathConstraintData, this)); - - UpdateCache(); - //UpdateWorldTransform(); - } - - /// Caches information about bones and constraints. Must be called if the is modified or if bones, constraints, or - /// constraints, or weighted path attachments are added or removed. - public void UpdateCache () { - var updateCache = this.updateCache; - updateCache.Clear(); - - int boneCount = this.bones.Count; - Bone[] bones = this.bones.Items; - for (int i = 0; i < boneCount; i++) { - Bone bone = bones[i]; - bone.sorted = bone.data.skinRequired; - bone.active = !bone.sorted; - } - if (skin != null) { - BoneData[] skinBones = skin.bones.Items; - for (int i = 0, n = skin.bones.Count; i < n; i++) { - var bone = bones[skinBones[i].index]; - do { - bone.sorted = false; - bone.active = true; - bone = bone.parent; - } while (bone != null); - } - } - - int ikCount = this.ikConstraints.Count, transformCount = this.transformConstraints.Count, pathCount = this.pathConstraints.Count; - IkConstraint[] ikConstraints = this.ikConstraints.Items; - TransformConstraint[] transformConstraints = this.transformConstraints.Items; - PathConstraint[] pathConstraints = this.pathConstraints.Items; - int constraintCount = ikCount + transformCount + pathCount; - for (int i = 0; i < constraintCount; i++) { - for (int ii = 0; ii < ikCount; ii++) { - IkConstraint constraint = ikConstraints[ii]; - if (constraint.data.order == i) { - SortIkConstraint(constraint); - goto continue_outer; - } - } - for (int ii = 0; ii < transformCount; ii++) { - TransformConstraint constraint = transformConstraints[ii]; - if (constraint.data.order == i) { - SortTransformConstraint(constraint); - goto continue_outer; - } - } - for (int ii = 0; ii < pathCount; ii++) { - PathConstraint constraint = pathConstraints[ii]; - if (constraint.data.order == i) { - SortPathConstraint(constraint); - goto continue_outer; - } - } - continue_outer: { } - } - - for (int i = 0; i < boneCount; i++) - SortBone(bones[i]); - } - - private void SortIkConstraint (IkConstraint constraint) { - constraint.active = constraint.target.active - && (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data))); - if (!constraint.active) return; - - Bone target = constraint.target; - SortBone(target); - - var constrained = constraint.bones; - Bone parent = constrained.Items[0]; - SortBone(parent); - - if (constrained.Count == 1) { - updateCache.Add(constraint); - SortReset(parent.children); - } else { - Bone child = constrained.Items[constrained.Count - 1]; - SortBone(child); - - updateCache.Add(constraint); - - SortReset(parent.children); - child.sorted = true; - } - } - - private void SortPathConstraint (PathConstraint constraint) { - constraint.active = constraint.target.bone.active - && (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data))); - if (!constraint.active) return; - - Slot slot = constraint.target; - int slotIndex = slot.data.index; - Bone slotBone = slot.bone; - if (skin != null) SortPathConstraintAttachment(skin, slotIndex, slotBone); - if (data.defaultSkin != null && data.defaultSkin != skin) - SortPathConstraintAttachment(data.defaultSkin, slotIndex, slotBone); - - Attachment attachment = slot.attachment; - if (attachment is PathAttachment) SortPathConstraintAttachment(attachment, slotBone); - - var constrained = constraint.bones.Items; - int boneCount = constraint.bones.Count; - for (int i = 0; i < boneCount; i++) - SortBone(constrained[i]); - - updateCache.Add(constraint); - - for (int i = 0; i < boneCount; i++) - SortReset(constrained[i].children); - for (int i = 0; i < boneCount; i++) - constrained[i].sorted = true; - } - - private void SortTransformConstraint (TransformConstraint constraint) { - constraint.active = constraint.target.active - && (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data))); - if (!constraint.active) return; - - SortBone(constraint.target); - - var constrained = constraint.bones.Items; - int boneCount = constraint.bones.Count; - if (constraint.data.local) { - for (int i = 0; i < boneCount; i++) { - Bone child = constrained[i]; - SortBone(child.parent); - SortBone(child); - } - } else { - for (int i = 0; i < boneCount; i++) - SortBone(constrained[i]); - } - - updateCache.Add(constraint); - - for (int i = 0; i < boneCount; i++) - SortReset(constrained[i].children); - for (int i = 0; i < boneCount; i++) - constrained[i].sorted = true; - } - - private void SortPathConstraintAttachment (Skin skin, int slotIndex, Bone slotBone) { - foreach (var entry in skin.Attachments) - if (entry.SlotIndex == slotIndex) SortPathConstraintAttachment(entry.Attachment, slotBone); - } - - private void SortPathConstraintAttachment (Attachment attachment, Bone slotBone) { - if (!(attachment is PathAttachment)) return; - int[] pathBones = ((PathAttachment)attachment).bones; - if (pathBones == null) - SortBone(slotBone); - else { - var bones = this.bones.Items; - for (int i = 0, n = pathBones.Length; i < n;) { - int nn = pathBones[i++]; - nn += i; - while (i < nn) - SortBone(bones[pathBones[i++]]); - } - } - } - - private void SortBone (Bone bone) { - if (bone.sorted) return; - Bone parent = bone.parent; - if (parent != null) SortBone(parent); - bone.sorted = true; - updateCache.Add(bone); - } - - private static void SortReset (ExposedList bones) { - Bone[] bonesItems = bones.Items; - for (int i = 0, n = bones.Count; i < n; i++) { - Bone bone = bonesItems[i]; - if (!bone.active) continue; - if (bone.sorted) SortReset(bone.children); - bone.sorted = false; - } - } - - /// - /// Updates the world transform for each bone and applies all constraints. - /// - /// See World transforms in the Spine - /// Runtimes Guide. - /// - public void UpdateWorldTransform () { - Bone[] bones = this.bones.Items; - for (int i = 0, n = this.bones.Count; 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; - } - - var updateCache = this.updateCache.Items; - for (int i = 0, n = this.updateCache.Count; i < n; i++) - updateCache[i].Update(); - } - - /// - /// Temporarily sets the root bone as a child of the specified bone, then updates the world transform for each bone and applies - /// all constraints. - /// - public void UpdateWorldTransform (Bone parent) { - if (parent == null) throw new ArgumentNullException("parent", "parent cannot be null."); - - // Apply the parent bone transform to the root bone. The root bone always inherits scale, rotation and reflection. - Bone rootBone = this.RootBone; - 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 rotationY = rootBone.rotation + 90 + rootBone.shearY; - float la = MathUtils.CosDeg(rootBone.rotation + rootBone.shearX) * rootBone.scaleX; - float lb = MathUtils.CosDeg(rotationY) * rootBone.scaleY; - float lc = MathUtils.SinDeg(rootBone.rotation + rootBone.shearX) * rootBone.scaleX; - float ld = MathUtils.SinDeg(rotationY) * 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. - var updateCache = this.updateCache.Items; - for (int i = 0, n = this.updateCache.Count; i < n; i++) { - var updatable = updateCache[i]; - if (updatable != rootBone) updatable.Update(); - } - } - - /// Sets the bones, constraints, and slots to their setup pose values. - public void SetToSetupPose () { - SetBonesToSetupPose(); - SetSlotsToSetupPose(); - } - - /// Sets the bones and constraints to their setup pose values. - public void SetBonesToSetupPose () { - var bones = this.bones.Items; - for (int i = 0, n = this.bones.Count; i < n; i++) - bones[i].SetToSetupPose(); - - var ikConstraints = this.ikConstraints.Items; - for (int i = 0, n = this.ikConstraints.Count; i < n; i++) { - IkConstraint constraint = ikConstraints[i]; - IkConstraintData data = constraint.data; - constraint.mix = data.mix; - constraint.softness = data.softness; - constraint.bendDirection = data.bendDirection; - constraint.compress = data.compress; - constraint.stretch = data.stretch; - } - - var transformConstraints = this.transformConstraints.Items; - for (int i = 0, n = this.transformConstraints.Count; i < n; i++) { - TransformConstraint constraint = transformConstraints[i]; - TransformConstraintData data = constraint.data; - constraint.mixRotate = data.mixRotate; - constraint.mixX = data.mixX; - constraint.mixY = data.mixY; - constraint.mixScaleX = data.mixScaleX; - constraint.mixScaleY = data.mixScaleY; - constraint.mixShearY = data.mixShearY; - } - - var pathConstraints = this.pathConstraints.Items; - for (int i = 0, n = this.pathConstraints.Count; i < n; i++) { - PathConstraint constraint = pathConstraints[i]; - PathConstraintData data = constraint.data; - constraint.position = data.position; - constraint.spacing = data.spacing; - constraint.mixRotate = data.mixRotate; - constraint.mixX = data.mixX; - constraint.mixY = data.mixY; - } - } - - public void SetSlotsToSetupPose () { - var slots = this.slots.Items; - int n = this.slots.Count; - Array.Copy(slots, 0, drawOrder.Items, 0, n); - for (int i = 0; i < n; i++) - slots[i].SetToSetupPose(); - } - - /// Finds a bone by comparing each bone's name. It is more efficient to cache the results of this method than to call it - /// repeatedly. - /// May be null. - public Bone FindBone (string boneName) { - if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); - var bones = this.bones.Items; - for (int i = 0, n = this.bones.Count; i < n; i++) { - Bone bone = bones[i]; - if (bone.data.name == boneName) return bone; - } - return null; - } - - /// Finds a slot by comparing each slot's name. It is more efficient to cache the results of this method than to call it - /// repeatedly. - /// May be null. - public Slot FindSlot (string slotName) { - if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); - var slots = this.slots.Items; - for (int i = 0, n = this.slots.Count; i < n; i++) { - Slot slot = slots[i]; - if (slot.data.name == slotName) return slot; - } - return null; - } - - /// Sets a skin by name (). - public void SetSkin (string skinName) { - Skin foundSkin = data.FindSkin(skinName); - if (foundSkin == null) throw new ArgumentException("Skin not found: " + skinName, "skinName"); - SetSkin(foundSkin); - } - - /// - /// Sets the skin used to look up attachments before looking in the . If the - /// skin is changed, is called. - /// - /// 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 - /// . - /// Also, often 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. - /// - /// May be null. - public void SetSkin (Skin newSkin) { - if (newSkin == skin) return; - if (newSkin != null) { - if (skin != null) - newSkin.AttachAll(this, skin); - else { - Slot[] slots = this.slots.Items; - for (int i = 0, n = this.slots.Count; i < n; i++) { - Slot slot = slots[i]; - string name = slot.data.attachmentName; - if (name != null) { - Attachment attachment = newSkin.GetAttachment(i, name); - if (attachment != null) slot.Attachment = attachment; - } - } - } - } - skin = newSkin; - UpdateCache(); - } - - /// Finds an attachment by looking in the and using the slot name and attachment name. - /// May be null. - public Attachment GetAttachment (string slotName, string attachmentName) { - return GetAttachment(data.FindSlot(slotName).index, attachmentName); - } - - /// Finds an attachment by looking in the skin and skeletonData.defaultSkin using the slot index and attachment name.First the skin is checked and if the attachment was not found, the default skin is checked. - /// May be null. - public Attachment GetAttachment (int slotIndex, string attachmentName) { - if (attachmentName == null) throw new ArgumentNullException("attachmentName", "attachmentName cannot be null."); - if (skin != null) { - Attachment attachment = skin.GetAttachment(slotIndex, attachmentName); - if (attachment != null) return attachment; - } - return data.defaultSkin != null ? data.defaultSkin.GetAttachment(slotIndex, attachmentName) : null; - } - - /// A convenience method to set an attachment by finding the slot with FindSlot, finding the attachment with GetAttachment, then setting the slot's slot.Attachment. - /// May be null to clear the slot's attachment. - public void SetAttachment (string slotName, string attachmentName) { - if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); - Slot[] slots = this.slots.Items; - for (int i = 0, n = this.slots.Count; i < n; i++) { - Slot slot = slots[i]; - if (slot.data.name == slotName) { - Attachment attachment = null; - if (attachmentName != null) { - attachment = GetAttachment(i, attachmentName); - if (attachment == null) throw new Exception("Attachment not found: " + attachmentName + ", for slot: " + slotName); - } - slot.Attachment = attachment; - return; - } - } - throw new Exception("Slot not found: " + slotName); - } - - /// Finds an IK constraint by comparing each IK constraint's name. It is more efficient to cache the results of this method - /// than to call it repeatedly. - /// May be null. - public IkConstraint FindIkConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - var ikConstraints = this.ikConstraints.Items; - for (int i = 0, n = this.ikConstraints.Count; i < n; i++) { - IkConstraint ikConstraint = ikConstraints[i]; - if (ikConstraint.data.name == constraintName) return ikConstraint; - } - return null; - } - - /// Finds a transform constraint by comparing each transform constraint's name. It is more efficient to cache the results of - /// this method than to call it repeatedly. - /// May be null. - public TransformConstraint FindTransformConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - var transformConstraints = this.transformConstraints.Items; - for (int i = 0, n = this.transformConstraints.Count; i < n; i++) { - TransformConstraint transformConstraint = transformConstraints[i]; - if (transformConstraint.data.Name == constraintName) return transformConstraint; - } - return null; - } - - /// Finds a path constraint by comparing each path constraint's name. It is more efficient to cache the results of this method - /// than to call it repeatedly. - /// May be null. - public PathConstraint FindPathConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - var pathConstraints = this.pathConstraints.Items; - for (int i = 0, n = this.pathConstraints.Count; i < n; i++) { - PathConstraint constraint = pathConstraints[i]; - if (constraint.data.Name.Equals(constraintName)) return constraint; - } - return null; - } - - public void Update (float delta) { - time += delta; - } - - /// Returns the axis aligned bounding box (AABB) of the region and mesh attachments for the current pose. - /// The horizontal distance between the skeleton origin and the left side of the AABB. - /// The vertical distance between the skeleton origin and the bottom side of the AABB. - /// The width of the AABB - /// The height of the AABB. - /// Reference to hold a float[]. May be a null reference. This method will assign it a new float[] with the appropriate size as needed. - public void GetBounds (out float x, out float y, out float width, out float height, ref float[] vertexBuffer) { - float[] temp = vertexBuffer; - temp = temp ?? new float[8]; - var drawOrder = this.drawOrder.Items; - float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; - for (int i = 0, n = this.drawOrder.Count; i < n; i++) { - Slot slot = drawOrder[i]; - if (!slot.bone.active) continue; - int verticesLength = 0; - float[] vertices = null; - Attachment attachment = slot.attachment; - var regionAttachment = attachment as RegionAttachment; - if (regionAttachment != null) { - verticesLength = 8; - vertices = temp; - if (vertices.Length < 8) vertices = temp = new float[8]; - regionAttachment.ComputeWorldVertices(slot.bone, temp, 0); - } else { - var meshAttachment = attachment as MeshAttachment; - if (meshAttachment != null) { - MeshAttachment mesh = meshAttachment; - verticesLength = mesh.WorldVerticesLength; - vertices = temp; - if (vertices.Length < verticesLength) vertices = temp = new float[verticesLength]; - mesh.ComputeWorldVertices(slot, 0, verticesLength, temp, 0); - } - } - - if (vertices != null) { - for (int ii = 0; ii < verticesLength; ii += 2) { - float vx = vertices[ii], vy = vertices[ii + 1]; - minX = Math.Min(minX, vx); - minY = Math.Min(minY, vy); - maxX = Math.Max(maxX, vx); - maxY = Math.Max(maxY, vy); - } - } - } - x = minX; - y = minY; - width = maxX - minX; - height = maxY - minY; - vertexBuffer = temp; - } - } + +namespace Spine4_0_31 +{ + public class Skeleton + { + internal SkeletonData data; + internal ExposedList bones; + internal ExposedList slots; + internal ExposedList drawOrder; + internal ExposedList ikConstraints; + internal ExposedList transformConstraints; + internal ExposedList pathConstraints; + internal ExposedList updateCache = new ExposedList(); + internal Skin skin; + internal float r = 1, g = 1, b = 1, a = 1; + internal float time; + private float scaleX = 1, scaleY = 1; + internal float x, y; + + public SkeletonData Data { get { return data; } } + public ExposedList Bones { get { return bones; } } + public ExposedList UpdateCacheList { get { return updateCache; } } + public ExposedList Slots { get { return slots; } } + public ExposedList DrawOrder { get { return drawOrder; } } + public ExposedList IkConstraints { get { return ikConstraints; } } + public ExposedList PathConstraints { get { return pathConstraints; } } + public ExposedList TransformConstraints { get { return transformConstraints; } } + + public Skin Skin + { + /// The skeleton's current skin. May be null. + get { return skin; } + /// Sets a skin, . + set { SetSkin(value); } + } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } + public float Time { get { return time; } set { time = value; } } + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + public float ScaleY { get { return scaleY * (Bone.yDown ? -1 : 1); } set { scaleY = value; } } + + [Obsolete("Use ScaleX instead. FlipX is when ScaleX is negative.")] + public bool FlipX { get { return scaleX < 0; } set { scaleX = value ? -1f : 1f; } } + + [Obsolete("Use ScaleY instead. FlipY is when ScaleY is negative.")] + public bool FlipY { get { return scaleY < 0; } set { scaleY = value ? -1f : 1f; } } + + /// Returns the root bone, or null if the skeleton has no bones. + public Bone RootBone + { + get { return bones.Count == 0 ? null : bones.Items[0]; } + } + + public Skeleton(SkeletonData data) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + this.data = data; + + bones = new ExposedList(data.bones.Count); + Bone[] bonesItems = this.bones.Items; + foreach (BoneData boneData in data.bones) + { + Bone bone; + if (boneData.parent == null) + { + bone = new Bone(boneData, this, null); + } + else + { + Bone parent = bonesItems[boneData.parent.index]; + bone = new Bone(boneData, this, parent); + parent.children.Add(bone); + } + this.bones.Add(bone); + } + + slots = new ExposedList(data.slots.Count); + drawOrder = new ExposedList(data.slots.Count); + foreach (SlotData slotData in data.slots) + { + Bone bone = bonesItems[slotData.boneData.index]; + Slot slot = new Slot(slotData, bone); + slots.Add(slot); + drawOrder.Add(slot); + } + + ikConstraints = new ExposedList(data.ikConstraints.Count); + foreach (IkConstraintData ikConstraintData in data.ikConstraints) + ikConstraints.Add(new IkConstraint(ikConstraintData, this)); + + transformConstraints = new ExposedList(data.transformConstraints.Count); + foreach (TransformConstraintData transformConstraintData in data.transformConstraints) + transformConstraints.Add(new TransformConstraint(transformConstraintData, this)); + + pathConstraints = new ExposedList(data.pathConstraints.Count); + foreach (PathConstraintData pathConstraintData in data.pathConstraints) + pathConstraints.Add(new PathConstraint(pathConstraintData, this)); + + UpdateCache(); + //UpdateWorldTransform(); + } + + /// Caches information about bones and constraints. Must be called if the is modified or if bones, constraints, or + /// constraints, or weighted path attachments are added or removed. + public void UpdateCache() + { + var updateCache = this.updateCache; + updateCache.Clear(); + + int boneCount = this.bones.Count; + Bone[] bones = this.bones.Items; + for (int i = 0; i < boneCount; i++) + { + Bone bone = bones[i]; + bone.sorted = bone.data.skinRequired; + bone.active = !bone.sorted; + } + if (skin != null) + { + BoneData[] skinBones = skin.bones.Items; + for (int i = 0, n = skin.bones.Count; i < n; i++) + { + var bone = bones[skinBones[i].index]; + do + { + bone.sorted = false; + bone.active = true; + bone = bone.parent; + } while (bone != null); + } + } + + int ikCount = this.ikConstraints.Count, transformCount = this.transformConstraints.Count, pathCount = this.pathConstraints.Count; + IkConstraint[] ikConstraints = this.ikConstraints.Items; + TransformConstraint[] transformConstraints = this.transformConstraints.Items; + PathConstraint[] pathConstraints = this.pathConstraints.Items; + int constraintCount = ikCount + transformCount + pathCount; + for (int i = 0; i < constraintCount; i++) + { + for (int ii = 0; ii < ikCount; ii++) + { + IkConstraint constraint = ikConstraints[ii]; + if (constraint.data.order == i) + { + SortIkConstraint(constraint); + goto continue_outer; + } + } + for (int ii = 0; ii < transformCount; ii++) + { + TransformConstraint constraint = transformConstraints[ii]; + if (constraint.data.order == i) + { + SortTransformConstraint(constraint); + goto continue_outer; + } + } + for (int ii = 0; ii < pathCount; ii++) + { + PathConstraint constraint = pathConstraints[ii]; + if (constraint.data.order == i) + { + SortPathConstraint(constraint); + goto continue_outer; + } + } + continue_outer: { } + } + + for (int i = 0; i < boneCount; i++) + SortBone(bones[i]); + } + + private void SortIkConstraint(IkConstraint constraint) + { + constraint.active = constraint.target.active + && (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data))); + if (!constraint.active) return; + + Bone target = constraint.target; + SortBone(target); + + var constrained = constraint.bones; + Bone parent = constrained.Items[0]; + SortBone(parent); + + if (constrained.Count == 1) + { + updateCache.Add(constraint); + SortReset(parent.children); + } + else + { + Bone child = constrained.Items[constrained.Count - 1]; + SortBone(child); + + updateCache.Add(constraint); + + SortReset(parent.children); + child.sorted = true; + } + } + + private void SortPathConstraint(PathConstraint constraint) + { + constraint.active = constraint.target.bone.active + && (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data))); + if (!constraint.active) return; + + Slot slot = constraint.target; + int slotIndex = slot.data.index; + Bone slotBone = slot.bone; + if (skin != null) SortPathConstraintAttachment(skin, slotIndex, slotBone); + if (data.defaultSkin != null && data.defaultSkin != skin) + SortPathConstraintAttachment(data.defaultSkin, slotIndex, slotBone); + + Attachment attachment = slot.attachment; + if (attachment is PathAttachment) SortPathConstraintAttachment(attachment, slotBone); + + var constrained = constraint.bones.Items; + int boneCount = constraint.bones.Count; + for (int i = 0; i < boneCount; i++) + SortBone(constrained[i]); + + updateCache.Add(constraint); + + for (int i = 0; i < boneCount; i++) + SortReset(constrained[i].children); + for (int i = 0; i < boneCount; i++) + constrained[i].sorted = true; + } + + private void SortTransformConstraint(TransformConstraint constraint) + { + constraint.active = constraint.target.active + && (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data))); + if (!constraint.active) return; + + SortBone(constraint.target); + + var constrained = constraint.bones.Items; + int boneCount = constraint.bones.Count; + if (constraint.data.local) + { + for (int i = 0; i < boneCount; i++) + { + Bone child = constrained[i]; + SortBone(child.parent); + SortBone(child); + } + } + else + { + for (int i = 0; i < boneCount; i++) + SortBone(constrained[i]); + } + + updateCache.Add(constraint); + + for (int i = 0; i < boneCount; i++) + SortReset(constrained[i].children); + for (int i = 0; i < boneCount; i++) + constrained[i].sorted = true; + } + + private void SortPathConstraintAttachment(Skin skin, int slotIndex, Bone slotBone) + { + foreach (var entry in skin.Attachments) + if (entry.SlotIndex == slotIndex) SortPathConstraintAttachment(entry.Attachment, slotBone); + } + + private void SortPathConstraintAttachment(Attachment attachment, Bone slotBone) + { + if (!(attachment is PathAttachment)) return; + int[] pathBones = ((PathAttachment)attachment).bones; + if (pathBones == null) + SortBone(slotBone); + else + { + var bones = this.bones.Items; + for (int i = 0, n = pathBones.Length; i < n;) + { + int nn = pathBones[i++]; + nn += i; + while (i < nn) + SortBone(bones[pathBones[i++]]); + } + } + } + + private void SortBone(Bone bone) + { + if (bone.sorted) return; + Bone parent = bone.parent; + if (parent != null) SortBone(parent); + bone.sorted = true; + updateCache.Add(bone); + } + + private static void SortReset(ExposedList bones) + { + Bone[] bonesItems = bones.Items; + for (int i = 0, n = bones.Count; i < n; i++) + { + Bone bone = bonesItems[i]; + if (!bone.active) continue; + if (bone.sorted) SortReset(bone.children); + bone.sorted = false; + } + } + + /// + /// Updates the world transform for each bone and applies all constraints. + /// + /// See World transforms in the Spine + /// Runtimes Guide. + /// + public void UpdateWorldTransform() + { + Bone[] bones = this.bones.Items; + for (int i = 0, n = this.bones.Count; 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; + } + + var updateCache = this.updateCache.Items; + for (int i = 0, n = this.updateCache.Count; i < n; i++) + updateCache[i].Update(); + } + + /// + /// Temporarily sets the root bone as a child of the specified bone, then updates the world transform for each bone and applies + /// all constraints. + /// + public void UpdateWorldTransform(Bone parent) + { + if (parent == null) throw new ArgumentNullException("parent", "parent cannot be null."); + + // Apply the parent bone transform to the root bone. The root bone always inherits scale, rotation and reflection. + Bone rootBone = this.RootBone; + 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 rotationY = rootBone.rotation + 90 + rootBone.shearY; + float la = MathUtils.CosDeg(rootBone.rotation + rootBone.shearX) * rootBone.scaleX; + float lb = MathUtils.CosDeg(rotationY) * rootBone.scaleY; + float lc = MathUtils.SinDeg(rootBone.rotation + rootBone.shearX) * rootBone.scaleX; + float ld = MathUtils.SinDeg(rotationY) * 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. + var updateCache = this.updateCache.Items; + for (int i = 0, n = this.updateCache.Count; i < n; i++) + { + var updatable = updateCache[i]; + if (updatable != rootBone) updatable.Update(); + } + } + + /// Sets the bones, constraints, and slots to their setup pose values. + public void SetToSetupPose() + { + SetBonesToSetupPose(); + SetSlotsToSetupPose(); + } + + /// Sets the bones and constraints to their setup pose values. + public void SetBonesToSetupPose() + { + var bones = this.bones.Items; + for (int i = 0, n = this.bones.Count; i < n; i++) + bones[i].SetToSetupPose(); + + var ikConstraints = this.ikConstraints.Items; + for (int i = 0, n = this.ikConstraints.Count; i < n; i++) + { + IkConstraint constraint = ikConstraints[i]; + IkConstraintData data = constraint.data; + constraint.mix = data.mix; + constraint.softness = data.softness; + constraint.bendDirection = data.bendDirection; + constraint.compress = data.compress; + constraint.stretch = data.stretch; + } + + var transformConstraints = this.transformConstraints.Items; + for (int i = 0, n = this.transformConstraints.Count; i < n; i++) + { + TransformConstraint constraint = transformConstraints[i]; + TransformConstraintData data = constraint.data; + constraint.mixRotate = data.mixRotate; + constraint.mixX = data.mixX; + constraint.mixY = data.mixY; + constraint.mixScaleX = data.mixScaleX; + constraint.mixScaleY = data.mixScaleY; + constraint.mixShearY = data.mixShearY; + } + + var pathConstraints = this.pathConstraints.Items; + for (int i = 0, n = this.pathConstraints.Count; i < n; i++) + { + PathConstraint constraint = pathConstraints[i]; + PathConstraintData data = constraint.data; + constraint.position = data.position; + constraint.spacing = data.spacing; + constraint.mixRotate = data.mixRotate; + constraint.mixX = data.mixX; + constraint.mixY = data.mixY; + } + } + + public void SetSlotsToSetupPose() + { + var slots = this.slots.Items; + int n = this.slots.Count; + Array.Copy(slots, 0, drawOrder.Items, 0, n); + for (int i = 0; i < n; i++) + slots[i].SetToSetupPose(); + } + + /// Finds a bone by comparing each bone's name. It is more efficient to cache the results of this method than to call it + /// repeatedly. + /// May be null. + public Bone FindBone(string boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); + var bones = this.bones.Items; + for (int i = 0, n = this.bones.Count; i < n; i++) + { + Bone bone = bones[i]; + if (bone.data.name == boneName) return bone; + } + return null; + } + + /// Finds a slot by comparing each slot's name. It is more efficient to cache the results of this method than to call it + /// repeatedly. + /// May be null. + public Slot FindSlot(string slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + var slots = this.slots.Items; + for (int i = 0, n = this.slots.Count; i < n; i++) + { + Slot slot = slots[i]; + if (slot.data.name == slotName) return slot; + } + return null; + } + + /// Sets a skin by name (). + public void SetSkin(string skinName) + { + Skin foundSkin = data.FindSkin(skinName); + if (foundSkin == null) throw new ArgumentException("Skin not found: " + skinName, "skinName"); + SetSkin(foundSkin); + } + + /// + /// Sets the skin used to look up attachments before looking in the . If the + /// skin is changed, is called. + /// + /// 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 + /// . + /// Also, often 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. + /// + /// May be null. + public void SetSkin(Skin newSkin) + { + if (newSkin == skin) return; + if (newSkin != null) + { + if (skin != null) + newSkin.AttachAll(this, skin); + else + { + Slot[] slots = this.slots.Items; + for (int i = 0, n = this.slots.Count; i < n; i++) + { + Slot slot = slots[i]; + string name = slot.data.attachmentName; + if (name != null) + { + Attachment attachment = newSkin.GetAttachment(i, name); + if (attachment != null) slot.Attachment = attachment; + } + } + } + } + skin = newSkin; + UpdateCache(); + } + + /// Finds an attachment by looking in the and using the slot name and attachment name. + /// May be null. + public Attachment GetAttachment(string slotName, string attachmentName) + { + return GetAttachment(data.FindSlot(slotName).index, attachmentName); + } + + /// Finds an attachment by looking in the skin and skeletonData.defaultSkin using the slot index and attachment name.First the skin is checked and if the attachment was not found, the default skin is checked. + /// May be null. + public Attachment GetAttachment(int slotIndex, string attachmentName) + { + if (attachmentName == null) throw new ArgumentNullException("attachmentName", "attachmentName cannot be null."); + if (skin != null) + { + Attachment attachment = skin.GetAttachment(slotIndex, attachmentName); + if (attachment != null) return attachment; + } + return data.defaultSkin != null ? data.defaultSkin.GetAttachment(slotIndex, attachmentName) : null; + } + + /// A convenience method to set an attachment by finding the slot with FindSlot, finding the attachment with GetAttachment, then setting the slot's slot.Attachment. + /// May be null to clear the slot's attachment. + public void SetAttachment(string slotName, string attachmentName) + { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + Slot[] slots = this.slots.Items; + for (int i = 0, n = this.slots.Count; i < n; i++) + { + Slot slot = slots[i]; + if (slot.data.name == slotName) + { + Attachment attachment = null; + if (attachmentName != null) + { + attachment = GetAttachment(i, attachmentName); + if (attachment == null) throw new Exception("Attachment not found: " + attachmentName + ", for slot: " + slotName); + } + slot.Attachment = attachment; + return; + } + } + throw new Exception("Slot not found: " + slotName); + } + + /// Finds an IK constraint by comparing each IK constraint's name. It is more efficient to cache the results of this method + /// than to call it repeatedly. + /// May be null. + public IkConstraint FindIkConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + var ikConstraints = this.ikConstraints.Items; + for (int i = 0, n = this.ikConstraints.Count; i < n; i++) + { + IkConstraint ikConstraint = ikConstraints[i]; + if (ikConstraint.data.name == constraintName) return ikConstraint; + } + return null; + } + + /// Finds a transform constraint by comparing each transform constraint's name. It is more efficient to cache the results of + /// this method than to call it repeatedly. + /// May be null. + public TransformConstraint FindTransformConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + var transformConstraints = this.transformConstraints.Items; + for (int i = 0, n = this.transformConstraints.Count; i < n; i++) + { + TransformConstraint transformConstraint = transformConstraints[i]; + if (transformConstraint.data.Name == constraintName) return transformConstraint; + } + return null; + } + + /// Finds a path constraint by comparing each path constraint's name. It is more efficient to cache the results of this method + /// than to call it repeatedly. + /// May be null. + public PathConstraint FindPathConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + var pathConstraints = this.pathConstraints.Items; + for (int i = 0, n = this.pathConstraints.Count; i < n; i++) + { + PathConstraint constraint = pathConstraints[i]; + if (constraint.data.Name.Equals(constraintName)) return constraint; + } + return null; + } + + public void Update(float delta) + { + time += delta; + } + + /// Returns the axis aligned bounding box (AABB) of the region and mesh attachments for the current pose. + /// The horizontal distance between the skeleton origin and the left side of the AABB. + /// The vertical distance between the skeleton origin and the bottom side of the AABB. + /// The width of the AABB + /// The height of the AABB. + /// Reference to hold a float[]. May be a null reference. This method will assign it a new float[] with the appropriate size as needed. + public void GetBounds(out float x, out float y, out float width, out float height, ref float[] vertexBuffer) + { + float[] temp = vertexBuffer; + temp = temp ?? new float[8]; + var drawOrder = this.drawOrder.Items; + float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; + for (int i = 0, n = this.drawOrder.Count; i < n; i++) + { + Slot slot = drawOrder[i]; + if (!slot.bone.active) continue; + int verticesLength = 0; + float[] vertices = null; + Attachment attachment = slot.attachment; + var regionAttachment = attachment as RegionAttachment; + if (regionAttachment != null) + { + verticesLength = 8; + vertices = temp; + if (vertices.Length < 8) vertices = temp = new float[8]; + regionAttachment.ComputeWorldVertices(slot.bone, temp, 0); + } + else + { + var meshAttachment = attachment as MeshAttachment; + if (meshAttachment != null) + { + MeshAttachment mesh = meshAttachment; + verticesLength = mesh.WorldVerticesLength; + vertices = temp; + if (vertices.Length < verticesLength) vertices = temp = new float[verticesLength]; + mesh.ComputeWorldVertices(slot, 0, verticesLength, temp, 0); + } + } + + if (vertices != null) + { + for (int ii = 0; ii < verticesLength; ii += 2) + { + float vx = vertices[ii], vy = vertices[ii + 1]; + minX = Math.Min(minX, vx); + minY = Math.Min(minY, vy); + maxX = Math.Max(maxX, vx); + maxY = Math.Max(maxY, vy); + } + } + } + x = minX; + y = minY; + width = maxX - minX; + height = maxY - minY; + vertexBuffer = temp; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/SkeletonBinary.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/SkeletonBinary.cs index e59a292..d13edd7 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/SkeletonBinary.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/SkeletonBinary.cs @@ -32,7 +32,6 @@ #endif using System; -using System.Collections.Generic; using System.IO; using System.Runtime.Serialization; @@ -41,41 +40,45 @@ using Windows.Storage; #endif -namespace Spine4_0_31 { - public class SkeletonBinary : SkeletonLoader { - public const int BONE_ROTATE = 0; - public const int BONE_TRANSLATE = 1; - public const int BONE_TRANSLATEX = 2; - public const int BONE_TRANSLATEY = 3; - public const int BONE_SCALE = 4; - public const int BONE_SCALEX = 5; - public const int BONE_SCALEY = 6; - public const int BONE_SHEAR = 7; - public const int BONE_SHEARX = 8; - public const int BONE_SHEARY = 9; - - public const int SLOT_ATTACHMENT = 0; - public const int SLOT_RGBA = 1; - public const int SLOT_RGB = 2; - public const int SLOT_RGBA2 = 3; - public const int SLOT_RGB2 = 4; - public const int SLOT_ALPHA = 5; - - public const int PATH_POSITION = 0; - public const int PATH_SPACING = 1; - public const int PATH_MIX = 2; - - public const int CURVE_LINEAR = 0; - public const int CURVE_STEPPED = 1; - public const int CURVE_BEZIER = 2; - - public SkeletonBinary (AttachmentLoader attachmentLoader) - : base(attachmentLoader) { - } - - public SkeletonBinary (params Atlas[] atlasArray) - : base(atlasArray) { - } +namespace Spine4_0_31 +{ + public class SkeletonBinary : SkeletonLoader + { + public const int BONE_ROTATE = 0; + public const int BONE_TRANSLATE = 1; + public const int BONE_TRANSLATEX = 2; + public const int BONE_TRANSLATEY = 3; + public const int BONE_SCALE = 4; + public const int BONE_SCALEX = 5; + public const int BONE_SCALEY = 6; + public const int BONE_SHEAR = 7; + public const int BONE_SHEARX = 8; + public const int BONE_SHEARY = 9; + + public const int SLOT_ATTACHMENT = 0; + public const int SLOT_RGBA = 1; + public const int SLOT_RGB = 2; + public const int SLOT_RGBA2 = 3; + public const int SLOT_RGB2 = 4; + public const int SLOT_ALPHA = 5; + + public const int PATH_POSITION = 0; + public const int PATH_SPACING = 1; + public const int PATH_MIX = 2; + + public const int CURVE_LINEAR = 0; + public const int CURVE_STEPPED = 1; + public const int CURVE_BEZIER = 2; + + public SkeletonBinary(AttachmentLoader attachmentLoader) + : base(attachmentLoader) + { + } + + public SkeletonBinary(params Atlas[] atlasArray) + : base(atlasArray) + { + } #if !ISUNITY && WINDOWS_STOREAPP private async Task ReadFile(string path) { @@ -91,1135 +94,1261 @@ public override SkeletonData ReadSkeletonData (string path) { return this.ReadFile(path).Result; } #else - public override SkeletonData ReadSkeletonData (string path) { + public override SkeletonData ReadSkeletonData(string path) + { #if WINDOWS_PHONE using (var input = new BufferedStream(Microsoft.Xna.Framework.TitleContainer.OpenStream(path))) { #else - using (var input = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) { + using (var input = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) + { #endif - SkeletonData skeletonData = ReadSkeletonData(input); - skeletonData.name = Path.GetFileNameWithoutExtension(path); - return skeletonData; - } - } + SkeletonData skeletonData = ReadSkeletonData(input); + skeletonData.name = Path.GetFileNameWithoutExtension(path); + return skeletonData; + } + } #endif // WINDOWS_STOREAPP - public static readonly TransformMode[] TransformModeValues = { - TransformMode.Normal, - TransformMode.OnlyTranslation, - TransformMode.NoRotationOrReflection, - TransformMode.NoScale, - TransformMode.NoScaleOrReflection - }; - - /// Returns the version string of binary skeleton data. - public static string GetVersionString (Stream file) { - if (file == null) throw new ArgumentNullException("file"); - - SkeletonInput input = new SkeletonInput(file); - return input.GetVersionString(); - } - - public SkeletonData ReadSkeletonData (Stream file) { - if (file == null) throw new ArgumentNullException("file"); - float scale = this.scale; - - var skeletonData = new SkeletonData(); - SkeletonInput input = new SkeletonInput(file); - - long hash = input.ReadLong(); - skeletonData.hash = hash == 0 ? null : hash.ToString(); - skeletonData.version = input.ReadString(); - if (skeletonData.version.Length == 0) skeletonData.version = null; - // early return for old 3.8 format instead of reading past the end - if (skeletonData.version.Length > 13) return null; - skeletonData.x = input.ReadFloat(); - skeletonData.y = input.ReadFloat(); - skeletonData.width = input.ReadFloat(); - skeletonData.height = input.ReadFloat(); - - bool nonessential = input.ReadBoolean(); - - if (nonessential) { - skeletonData.fps = input.ReadFloat(); - - skeletonData.imagesPath = input.ReadString(); - if (string.IsNullOrEmpty(skeletonData.imagesPath)) skeletonData.imagesPath = null; - - skeletonData.audioPath = input.ReadString(); - if (string.IsNullOrEmpty(skeletonData.audioPath)) skeletonData.audioPath = null; - } - - int n; - Object[] o; - - // Strings. - o = input.strings = new String[n = input.ReadInt(true)]; - for (int i = 0; i < n; i++) - o[i] = input.ReadString(); - - // Bones. - var bones = skeletonData.bones.Resize(n = input.ReadInt(true)).Items; - for (int i = 0; i < n; i++) { - String name = input.ReadString(); - BoneData parent = i == 0 ? null : bones[input.ReadInt(true)]; - BoneData data = new BoneData(i, name, parent); - data.rotation = input.ReadFloat(); - data.x = input.ReadFloat() * scale; - data.y = input.ReadFloat() * scale; - data.scaleX = input.ReadFloat(); - data.scaleY = input.ReadFloat(); - data.shearX = input.ReadFloat(); - data.shearY = input.ReadFloat(); - data.Length = input.ReadFloat() * scale; - data.transformMode = TransformModeValues[input.ReadInt(true)]; - data.skinRequired = input.ReadBoolean(); - if (nonessential) input.ReadInt(); // Skip bone color. - bones[i] = data; - } - - // Slots. - var slots = skeletonData.slots.Resize(n = input.ReadInt(true)).Items; - for (int i = 0; i < n; i++) { - String slotName = input.ReadString(); - BoneData boneData = bones[input.ReadInt(true)]; - SlotData slotData = new SlotData(i, slotName, boneData); - int color = input.ReadInt(); - slotData.r = ((color & 0xff000000) >> 24) / 255f; - slotData.g = ((color & 0x00ff0000) >> 16) / 255f; - slotData.b = ((color & 0x0000ff00) >> 8) / 255f; - slotData.a = ((color & 0x000000ff)) / 255f; - - int darkColor = input.ReadInt(); // 0x00rrggbb - if (darkColor != -1) { - slotData.hasSecondColor = true; - slotData.r2 = ((darkColor & 0x00ff0000) >> 16) / 255f; - slotData.g2 = ((darkColor & 0x0000ff00) >> 8) / 255f; - slotData.b2 = ((darkColor & 0x000000ff)) / 255f; - } - - slotData.attachmentName = input.ReadStringRef(); - slotData.blendMode = (BlendMode)input.ReadInt(true); - slots[i] = slotData; - } - - // IK constraints. - o = skeletonData.ikConstraints.Resize(n = input.ReadInt(true)).Items; - for (int i = 0, nn; i < n; i++) { - IkConstraintData data = new IkConstraintData(input.ReadString()); - data.order = input.ReadInt(true); - data.skinRequired = input.ReadBoolean(); - var constraintBones = data.bones.Resize(nn = input.ReadInt(true)).Items; - for (int ii = 0; ii < nn; ii++) - constraintBones[ii] = bones[input.ReadInt(true)]; - data.target = bones[input.ReadInt(true)]; - data.mix = input.ReadFloat(); - data.softness = input.ReadFloat() * scale; - data.bendDirection = input.ReadSByte(); - data.compress = input.ReadBoolean(); - data.stretch = input.ReadBoolean(); - data.uniform = input.ReadBoolean(); - o[i] = data; - } - - // Transform constraints. - o = skeletonData.transformConstraints.Resize(n = input.ReadInt(true)).Items; - for (int i = 0, nn; i < n; i++) { - TransformConstraintData data = new TransformConstraintData(input.ReadString()); - data.order = input.ReadInt(true); - data.skinRequired = input.ReadBoolean(); - var constraintBones = data.bones.Resize(nn = input.ReadInt(true)).Items; - for (int ii = 0; ii < nn; ii++) - constraintBones[ii] = bones[input.ReadInt(true)]; - data.target = bones[input.ReadInt(true)]; - data.local = input.ReadBoolean(); - data.relative = input.ReadBoolean(); - data.offsetRotation = input.ReadFloat(); - data.offsetX = input.ReadFloat() * scale; - data.offsetY = input.ReadFloat() * scale; - data.offsetScaleX = input.ReadFloat(); - data.offsetScaleY = input.ReadFloat(); - data.offsetShearY = input.ReadFloat(); - data.mixRotate = input.ReadFloat(); - data.mixX = input.ReadFloat(); - data.mixY = input.ReadFloat(); - data.mixScaleX = input.ReadFloat(); - data.mixScaleY = input.ReadFloat(); - data.mixShearY = input.ReadFloat(); - o[i] = data; - } - - // Path constraints - o = skeletonData.pathConstraints.Resize(n = input.ReadInt(true)).Items; - for (int i = 0, nn; i < n; i++) { - PathConstraintData data = new PathConstraintData(input.ReadString()); - data.order = input.ReadInt(true); - data.skinRequired = input.ReadBoolean(); - Object[] constraintBones = data.bones.Resize(nn = input.ReadInt(true)).Items; - for (int ii = 0; ii < nn; ii++) - constraintBones[ii] = bones[input.ReadInt(true)]; - data.target = slots[input.ReadInt(true)]; - data.positionMode = (PositionMode)Enum.GetValues(typeof(PositionMode)).GetValue(input.ReadInt(true)); - data.spacingMode = (SpacingMode)Enum.GetValues(typeof(SpacingMode)).GetValue(input.ReadInt(true)); - data.rotateMode = (RotateMode)Enum.GetValues(typeof(RotateMode)).GetValue(input.ReadInt(true)); - data.offsetRotation = input.ReadFloat(); - data.position = input.ReadFloat(); - if (data.positionMode == PositionMode.Fixed) data.position *= scale; - data.spacing = input.ReadFloat(); - if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale; - data.mixRotate = input.ReadFloat(); - data.mixX = input.ReadFloat(); - data.mixY = input.ReadFloat(); - o[i] = data; - } - - // Default skin. - Skin defaultSkin = ReadSkin(input, skeletonData, true, nonessential); - if (defaultSkin != null) { - skeletonData.defaultSkin = defaultSkin; - skeletonData.skins.Add(defaultSkin); - } - - // Skins. - { - int i = skeletonData.skins.Count; - o = skeletonData.skins.Resize(n = i + input.ReadInt(true)).Items; - for (; i < n; i++) - o[i] = ReadSkin(input, skeletonData, false, nonessential); - } - - // Linked meshes. - n = linkedMeshes.Count; - for (int i = 0; i < n; i++) { - LinkedMesh linkedMesh = linkedMeshes[i]; - Skin skin = linkedMesh.skin == null ? skeletonData.DefaultSkin : skeletonData.FindSkin(linkedMesh.skin); - if (skin == null) throw new Exception("Skin not found: " + linkedMesh.skin); - Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); - if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); - linkedMesh.mesh.DeformAttachment = linkedMesh.inheritDeform ? (VertexAttachment)parent : linkedMesh.mesh; - linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; - linkedMesh.mesh.UpdateUVs(); - } - linkedMeshes.Clear(); - - // Events. - o = skeletonData.events.Resize(n = input.ReadInt(true)).Items; - for (int i = 0; i < n; i++) { - EventData data = new EventData(input.ReadStringRef()); - data.Int = input.ReadInt(false); - data.Float = input.ReadFloat(); - data.String = input.ReadString(); - data.AudioPath = input.ReadString(); - if (data.AudioPath != null) { - data.Volume = input.ReadFloat(); - data.Balance = input.ReadFloat(); - } - o[i] = data; - } - - // Animations. - o = skeletonData.animations.Resize(n = input.ReadInt(true)).Items; - for (int i = 0; i < n; i++) - o[i] = ReadAnimation(input.ReadString(), input, skeletonData); - - return skeletonData; - } - - /// May be null. - private Skin ReadSkin (SkeletonInput input, SkeletonData skeletonData, bool defaultSkin, bool nonessential) { - - Skin skin; - int slotCount; - - if (defaultSkin) { - slotCount = input.ReadInt(true); - if (slotCount == 0) return null; - skin = new Skin("default"); - } else { - skin = new Skin(input.ReadStringRef()); - Object[] bones = skin.bones.Resize(input.ReadInt(true)).Items; - var bonesItems = skeletonData.bones.Items; - for (int i = 0, n = skin.bones.Count; i < n; i++) - bones[i] = bonesItems[input.ReadInt(true)]; - - var ikConstraintsItems = skeletonData.ikConstraints.Items; - for (int i = 0, n = input.ReadInt(true); i < n; i++) - skin.constraints.Add(ikConstraintsItems[input.ReadInt(true)]); - var transformConstraintsItems = skeletonData.transformConstraints.Items; - for (int i = 0, n = input.ReadInt(true); i < n; i++) - skin.constraints.Add(transformConstraintsItems[input.ReadInt(true)]); - var pathConstraintsItems = skeletonData.pathConstraints.Items; - for (int i = 0, n = input.ReadInt(true); i < n; i++) - skin.constraints.Add(pathConstraintsItems[input.ReadInt(true)]); - skin.constraints.TrimExcess(); - - slotCount = input.ReadInt(true); - } - for (int i = 0; i < slotCount; i++) { - int slotIndex = input.ReadInt(true); - for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) { - String name = input.ReadStringRef(); - Attachment attachment = ReadAttachment(input, skeletonData, skin, slotIndex, name, nonessential); - if (attachment != null) skin.SetAttachment(slotIndex, name, attachment); - } - } - return skin; - } - - private Attachment ReadAttachment (SkeletonInput input, SkeletonData skeletonData, Skin skin, int slotIndex, - String attachmentName, bool nonessential) { - float scale = this.scale; - - String name = input.ReadStringRef(); - if (name == null) name = attachmentName; - - switch ((AttachmentType)input.ReadByte()) { - case AttachmentType.Region: { - String path = input.ReadStringRef(); - float rotation = input.ReadFloat(); - float x = input.ReadFloat(); - float y = input.ReadFloat(); - float scaleX = input.ReadFloat(); - float scaleY = input.ReadFloat(); - float width = input.ReadFloat(); - float height = input.ReadFloat(); - int color = input.ReadInt(); - - if (path == null) path = name; - RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); - if (region == null) return null; - region.Path = path; - region.x = x * scale; - region.y = y * scale; - region.scaleX = scaleX; - region.scaleY = scaleY; - region.rotation = rotation; - region.width = width * scale; - region.height = height * scale; - region.r = ((color & 0xff000000) >> 24) / 255f; - region.g = ((color & 0x00ff0000) >> 16) / 255f; - region.b = ((color & 0x0000ff00) >> 8) / 255f; - region.a = ((color & 0x000000ff)) / 255f; - region.UpdateOffset(); - return region; - } - case AttachmentType.Boundingbox: { - int vertexCount = input.ReadInt(true); - Vertices vertices = ReadVertices(input, vertexCount); - if (nonessential) input.ReadInt(); //int color = nonessential ? input.ReadInt() : 0; // Avoid unused local warning. - - BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); - if (box == null) return null; - box.worldVerticesLength = vertexCount << 1; - box.vertices = vertices.vertices; - box.bones = vertices.bones; - // skipped porting: if (nonessential) Color.rgba8888ToColor(box.getColor(), color); - return box; - } - case AttachmentType.Mesh: { - String path = input.ReadStringRef(); - int color = input.ReadInt(); - int vertexCount = input.ReadInt(true); - float[] uvs = ReadFloatArray(input, vertexCount << 1, 1); - int[] triangles = ReadShortArray(input); - Vertices vertices = ReadVertices(input, vertexCount); - int hullLength = input.ReadInt(true); - int[] edges = null; - float width = 0, height = 0; - if (nonessential) { - edges = ReadShortArray(input); - width = input.ReadFloat(); - height = input.ReadFloat(); - } - - if (path == null) path = name; - MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); - if (mesh == null) return null; - mesh.Path = path; - mesh.r = ((color & 0xff000000) >> 24) / 255f; - mesh.g = ((color & 0x00ff0000) >> 16) / 255f; - mesh.b = ((color & 0x0000ff00) >> 8) / 255f; - mesh.a = ((color & 0x000000ff)) / 255f; - mesh.bones = vertices.bones; - mesh.vertices = vertices.vertices; - mesh.WorldVerticesLength = vertexCount << 1; - mesh.triangles = triangles; - mesh.regionUVs = uvs; - mesh.UpdateUVs(); - mesh.HullLength = hullLength << 1; - if (nonessential) { - mesh.Edges = edges; - mesh.Width = width * scale; - mesh.Height = height * scale; - } - return mesh; - } - case AttachmentType.Linkedmesh: { - String path = input.ReadStringRef(); - int color = input.ReadInt(); - String skinName = input.ReadStringRef(); - String parent = input.ReadStringRef(); - bool inheritDeform = input.ReadBoolean(); - float width = 0, height = 0; - if (nonessential) { - width = input.ReadFloat(); - height = input.ReadFloat(); - } - - if (path == null) path = name; - MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); - if (mesh == null) return null; - mesh.Path = path; - mesh.r = ((color & 0xff000000) >> 24) / 255f; - mesh.g = ((color & 0x00ff0000) >> 16) / 255f; - mesh.b = ((color & 0x0000ff00) >> 8) / 255f; - mesh.a = ((color & 0x000000ff)) / 255f; - if (nonessential) { - mesh.Width = width * scale; - mesh.Height = height * scale; - } - linkedMeshes.Add(new SkeletonJson.LinkedMesh(mesh, skinName, slotIndex, parent, inheritDeform)); - return mesh; - } - case AttachmentType.Path: { - bool closed = input.ReadBoolean(); - bool constantSpeed = input.ReadBoolean(); - int vertexCount = input.ReadInt(true); - Vertices vertices = ReadVertices(input, vertexCount); - float[] lengths = new float[vertexCount / 3]; - for (int i = 0, n = lengths.Length; i < n; i++) - lengths[i] = input.ReadFloat() * scale; - if (nonessential) input.ReadInt(); //int color = nonessential ? input.ReadInt() : 0; - - PathAttachment path = attachmentLoader.NewPathAttachment(skin, name); - if (path == null) return null; - path.closed = closed; - path.constantSpeed = constantSpeed; - path.worldVerticesLength = vertexCount << 1; - path.vertices = vertices.vertices; - path.bones = vertices.bones; - path.lengths = lengths; - // skipped porting: if (nonessential) Color.rgba8888ToColor(path.getColor(), color); - return path; - } - case AttachmentType.Point: { - float rotation = input.ReadFloat(); - float x = input.ReadFloat(); - float y = input.ReadFloat(); - if (nonessential) input.ReadInt(); //int color = nonessential ? input.ReadInt() : 0; - - PointAttachment point = attachmentLoader.NewPointAttachment(skin, name); - if (point == null) return null; - point.x = x * scale; - point.y = y * scale; - point.rotation = rotation; - // skipped porting: if (nonessential) point.color = color; - return point; - } - case AttachmentType.Clipping: { - int endSlotIndex = input.ReadInt(true); - int vertexCount = input.ReadInt(true); - Vertices vertices = ReadVertices(input, vertexCount); - if (nonessential) input.ReadInt(); - - ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name); - if (clip == null) return null; - clip.EndSlot = skeletonData.slots.Items[endSlotIndex]; - clip.worldVerticesLength = vertexCount << 1; - clip.vertices = vertices.vertices; - clip.bones = vertices.bones; - // skipped porting: if (nonessential) Color.rgba8888ToColor(clip.getColor(), color); - return clip; - } - } - return null; - } - - private Vertices ReadVertices (SkeletonInput input, int vertexCount) { - float scale = this.scale; - int verticesLength = vertexCount << 1; - Vertices vertices = new Vertices(); - if (!input.ReadBoolean()) { - vertices.vertices = ReadFloatArray(input, verticesLength, scale); - return vertices; - } - var weights = new ExposedList(verticesLength * 3 * 3); - var bonesArray = new ExposedList(verticesLength * 3); - for (int i = 0; i < vertexCount; i++) { - int boneCount = input.ReadInt(true); - bonesArray.Add(boneCount); - for (int ii = 0; ii < boneCount; ii++) { - bonesArray.Add(input.ReadInt(true)); - weights.Add(input.ReadFloat() * scale); - weights.Add(input.ReadFloat() * scale); - weights.Add(input.ReadFloat()); - } - } - - vertices.vertices = weights.ToArray(); - vertices.bones = bonesArray.ToArray(); - return vertices; - } - - private float[] ReadFloatArray (SkeletonInput input, int n, float scale) { - float[] array = new float[n]; - if (scale == 1) { - for (int i = 0; i < n; i++) - array[i] = input.ReadFloat(); - } else { - for (int i = 0; i < n; i++) - array[i] = input.ReadFloat() * scale; - } - return array; - } - - private int[] ReadShortArray (SkeletonInput input) { - int n = input.ReadInt(true); - int[] array = new int[n]; - for (int i = 0; i < n; i++) - array[i] = (input.ReadByte() << 8) | input.ReadByte(); - return array; - } - - /// SerializationException will be thrown when a Vertex attachment is not found. - /// Throws IOException when a read operation fails. - private Animation ReadAnimation (String name, SkeletonInput input, SkeletonData skeletonData) { - var timelines = new ExposedList(input.ReadInt(true)); - float scale = this.scale; - - // Slot timelines. - for (int i = 0, n = input.ReadInt(true); i < n; i++) { - int slotIndex = input.ReadInt(true); - for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) { - int timelineType = input.ReadByte(), frameCount = input.ReadInt(true), frameLast = frameCount - 1; - switch (timelineType) { - case SLOT_ATTACHMENT: { - AttachmentTimeline timeline = new AttachmentTimeline(frameCount, slotIndex); - for (int frame = 0; frame < frameCount; frame++) - timeline.SetFrame(frame, input.ReadFloat(), input.ReadStringRef()); - timelines.Add(timeline); - break; - } - case SLOT_RGBA: { - RGBATimeline timeline = new RGBATimeline(frameCount, input.ReadInt(true), slotIndex); - float time = input.ReadFloat(); - float r = input.Read() / 255f, g = input.Read() / 255f; - float b = input.Read() / 255f, a = input.Read() / 255f; - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, r, g, b, a); - if (frame == frameLast) break; - float time2 = input.ReadFloat(); - float r2 = input.Read() / 255f, g2 = input.Read() / 255f; - float b2 = input.Read() / 255f, a2 = input.Read() / 255f; - switch (input.ReadByte()) { - 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); - break; - } - time = time2; - r = r2; - g = g2; - b = b2; - a = a2; - } - timelines.Add(timeline); - break; - } - case SLOT_RGB: { - RGBTimeline timeline = new RGBTimeline(frameCount, input.ReadInt(true), slotIndex); - float time = input.ReadFloat(); - float r = input.Read() / 255f, g = input.Read() / 255f, b = input.Read() / 255f; - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, r, g, b); - if (frame == frameLast) break; - float time2 = input.ReadFloat(); - float r2 = input.Read() / 255f, g2 = input.Read() / 255f, b2 = input.Read() / 255f; - switch (input.ReadByte()) { - 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); - break; - } - time = time2; - r = r2; - g = g2; - b = b2; - } - timelines.Add(timeline); - break; - } - case SLOT_RGBA2: { - RGBA2Timeline timeline = new RGBA2Timeline(frameCount, input.ReadInt(true), slotIndex); - float time = input.ReadFloat(); - float r = input.Read() / 255f, g = input.Read() / 255f; - float b = input.Read() / 255f, a = input.Read() / 255f; - float r2 = input.Read() / 255f, g2 = input.Read() / 255f, b2 = input.Read() / 255f; - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, r, g, b, a, r2, g2, b2); - if (frame == frameLast) break; - float time2 = input.ReadFloat(); - float nr = input.Read() / 255f, ng = input.Read() / 255f; - float nb = input.Read() / 255f, na = input.Read() / 255f; - float nr2 = input.Read() / 255f, ng2 = input.Read() / 255f, nb2 = input.Read() / 255f; - switch (input.ReadByte()) { - 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); - break; - } - time = time2; - r = nr; - g = ng; - b = nb; - a = na; - r2 = nr2; - g2 = ng2; - b2 = nb2; - } - timelines.Add(timeline); - break; - } - case SLOT_RGB2: { - RGB2Timeline timeline = new RGB2Timeline(frameCount, input.ReadInt(true), slotIndex); - float time = input.ReadFloat(); - float r = input.Read() / 255f, g = input.Read() / 255f, b = input.Read() / 255f; - float r2 = input.Read() / 255f, g2 = input.Read() / 255f, b2 = input.Read() / 255f; - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, r, g, b, r2, g2, b2); - if (frame == frameLast) break; - float time2 = input.ReadFloat(); - float nr = input.Read() / 255f, ng = input.Read() / 255f, nb = input.Read() / 255f; - float nr2 = input.Read() / 255f, ng2 = input.Read() / 255f, nb2 = input.Read() / 255f; - switch (input.ReadByte()) { - 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); - break; - } - time = time2; - r = nr; - g = ng; - b = nb; - r2 = nr2; - g2 = ng2; - b2 = nb2; - } - timelines.Add(timeline); - break; - } - case SLOT_ALPHA: { - AlphaTimeline timeline = new AlphaTimeline(frameCount, input.ReadInt(true), slotIndex); - float time = input.ReadFloat(), a = input.Read() / 255f; - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, a); - if (frame == frameLast) break; - float time2 = input.ReadFloat(); - float a2 = input.Read() / 255f; - switch (input.ReadByte()) { - case CURVE_STEPPED: - timeline.SetStepped(frame); - break; - case CURVE_BEZIER: - SetBezier(input, timeline, bezier++, frame, 0, time, time2, a, a2, 1); - break; - } - time = time2; - a = a2; - } - timelines.Add(timeline); - break; - } - } - } - } - - // Bone timelines. - for (int i = 0, n = input.ReadInt(true); i < n; i++) { - int boneIndex = input.ReadInt(true); - for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) { - int type = input.ReadByte(), frameCount = input.ReadInt(true), bezierCount = input.ReadInt(true); - switch (type) { - case BONE_ROTATE: - timelines.Add(ReadTimeline(input, new RotateTimeline(frameCount, bezierCount, boneIndex), 1)); - break; - case BONE_TRANSLATE: - timelines.Add(ReadTimeline(input, new TranslateTimeline(frameCount, bezierCount, boneIndex), scale)); - break; - case BONE_TRANSLATEX: - timelines.Add(ReadTimeline(input, new TranslateXTimeline(frameCount, bezierCount, boneIndex), scale)); - break; - case BONE_TRANSLATEY: - timelines.Add(ReadTimeline(input, new TranslateYTimeline(frameCount, bezierCount, boneIndex), scale)); - break; - case BONE_SCALE: - timelines.Add(ReadTimeline(input, new ScaleTimeline(frameCount, bezierCount, boneIndex), 1)); - break; - case BONE_SCALEX: - timelines.Add(ReadTimeline(input, new ScaleXTimeline(frameCount, bezierCount, boneIndex), 1)); - break; - case BONE_SCALEY: - timelines.Add(ReadTimeline(input, new ScaleYTimeline(frameCount, bezierCount, boneIndex), 1)); - break; - case BONE_SHEAR: - timelines.Add(ReadTimeline(input, new ShearTimeline(frameCount, bezierCount, boneIndex), 1)); - break; - case BONE_SHEARX: - timelines.Add(ReadTimeline(input, new ShearXTimeline(frameCount, bezierCount, boneIndex), 1)); - break; - case BONE_SHEARY: - timelines.Add(ReadTimeline(input, new ShearYTimeline(frameCount, bezierCount, boneIndex), 1)); - break; - } - } - } - - // IK constraint timelines. - for (int i = 0, n = input.ReadInt(true); i < n; i++) { - int index = input.ReadInt(true), frameCount = input.ReadInt(true), frameLast = frameCount - 1; - IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount, input.ReadInt(true), index); - float time = input.ReadFloat(), mix = input.ReadFloat(), softness = input.ReadFloat() * scale; - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, mix, softness, input.ReadSByte(), input.ReadBoolean(), input.ReadBoolean()); - if (frame == frameLast) break; - float time2 = input.ReadFloat(), mix2 = input.ReadFloat(), softness2 = input.ReadFloat() * scale; - switch (input.ReadByte()) { - case CURVE_STEPPED: - timeline.SetStepped(frame); - break; - case CURVE_BEZIER: - SetBezier(input, timeline, bezier++, frame, 0, time, time2, mix, mix2, 1); - SetBezier(input, timeline, bezier++, frame, 1, time, time2, softness, softness2, scale); - break; - } - time = time2; - mix = mix2; - softness = softness2; - } - timelines.Add(timeline); - } - - // Transform constraint timelines. - for (int i = 0, n = input.ReadInt(true); i < n; i++) { - int index = input.ReadInt(true), frameCount = input.ReadInt(true), frameLast = frameCount - 1; - TransformConstraintTimeline timeline = new TransformConstraintTimeline(frameCount, input.ReadInt(true), index); - float time = input.ReadFloat(), mixRotate = input.ReadFloat(), mixX = input.ReadFloat(), mixY = input.ReadFloat(), - mixScaleX = input.ReadFloat(), mixScaleY = input.ReadFloat(), mixShearY = input.ReadFloat(); - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY); - if (frame == frameLast) break; - float time2 = input.ReadFloat(), mixRotate2 = input.ReadFloat(), mixX2 = input.ReadFloat(), mixY2 = input.ReadFloat(), - mixScaleX2 = input.ReadFloat(), mixScaleY2 = input.ReadFloat(), mixShearY2 = input.ReadFloat(); - switch (input.ReadByte()) { - 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); - break; - } - 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 = input.ReadInt(true); i < n; i++) { - int index = input.ReadInt(true); - PathConstraintData data = skeletonData.pathConstraints.Items[index]; - for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) { - switch (input.ReadByte()) { - case PATH_POSITION: - timelines - .Add(ReadTimeline(input, new PathConstraintPositionTimeline(input.ReadInt(true), input.ReadInt(true), index), - data.positionMode == PositionMode.Fixed ? scale : 1)); - break; - case PATH_SPACING: - timelines - .Add(ReadTimeline(input, new PathConstraintSpacingTimeline(input.ReadInt(true), input.ReadInt(true), index), - data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed ? scale : 1)); - break; - case PATH_MIX: - PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(input.ReadInt(true), input.ReadInt(true), - index); - float time = input.ReadFloat(), mixRotate = input.ReadFloat(), mixX = input.ReadFloat(), mixY = input.ReadFloat(); - for (int frame = 0, bezier = 0, frameLast = timeline.FrameCount - 1; ; frame++) { - timeline.SetFrame(frame, time, mixRotate, mixX, mixY); - if (frame == frameLast) break; - float time2 = input.ReadFloat(), mixRotate2 = input.ReadFloat(), mixX2 = input.ReadFloat(), - mixY2 = input.ReadFloat(); - switch (input.ReadByte()) { - 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); - break; - } - time = time2; - mixRotate = mixRotate2; - mixX = mixX2; - mixY = mixY2; - } - timelines.Add(timeline); - break; - } - } - } - - // Deform timelines. - for (int i = 0, n = input.ReadInt(true); i < n; i++) { - Skin skin = skeletonData.skins.Items[input.ReadInt(true)]; - for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) { - int slotIndex = input.ReadInt(true); - for (int iii = 0, nnn = input.ReadInt(true); iii < nnn; iii++) { - String attachmentName = input.ReadStringRef(); - VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, attachmentName); - if (attachment == null) throw new SerializationException("Vertex attachment not found: " + attachmentName); - bool weighted = attachment.Bones != null; - float[] vertices = attachment.Vertices; - int deformLength = weighted ? (vertices.Length / 3) << 1 : vertices.Length; - - int frameCount = input.ReadInt(true), frameLast = frameCount - 1; - DeformTimeline timeline = new DeformTimeline(frameCount, input.ReadInt(true), slotIndex, attachment); - - float time = input.ReadFloat(); - for (int frame = 0, bezier = 0; ; frame++) { - float[] deform; - int end = input.ReadInt(true); - if (end == 0) - deform = weighted ? new float[deformLength] : vertices; - else { - deform = new float[deformLength]; - int start = input.ReadInt(true); - end += start; - if (scale == 1) { - for (int v = start; v < end; v++) - deform[v] = input.ReadFloat(); - } else { - for (int v = start; v < end; v++) - deform[v] = input.ReadFloat() * scale; - } - if (!weighted) { - for (int v = 0, vn = deform.Length; v < vn; v++) - deform[v] += vertices[v]; - } - } - timeline.SetFrame(frame, time, deform); - if (frame == frameLast) break; - float time2 = input.ReadFloat(); - switch (input.ReadByte()) { - case CURVE_STEPPED: - timeline.SetStepped(frame); - break; - case CURVE_BEZIER: - SetBezier(input, timeline, bezier++, frame, 0, time, time2, 0, 1, 1); - break; - } - time = time2; - } - timelines.Add(timeline); - } - } - } - - // Draw order timeline. - int drawOrderCount = input.ReadInt(true); - if (drawOrderCount > 0) { - DrawOrderTimeline timeline = new DrawOrderTimeline(drawOrderCount); - int slotCount = skeletonData.slots.Count; - for (int i = 0; i < drawOrderCount; i++) { - float time = input.ReadFloat(); - int offsetCount = input.ReadInt(true); - int[] drawOrder = new int[slotCount]; - for (int ii = slotCount - 1; ii >= 0; ii--) - drawOrder[ii] = -1; - int[] unchanged = new int[slotCount - offsetCount]; - int originalIndex = 0, unchangedIndex = 0; - for (int ii = 0; ii < offsetCount; ii++) { - int slotIndex = input.ReadInt(true); - // Collect unchanged items. - while (originalIndex != slotIndex) - unchanged[unchangedIndex++] = originalIndex++; - // Set changed items. - drawOrder[originalIndex + input.ReadInt(true)] = originalIndex++; - } - // Collect remaining unchanged items. - while (originalIndex < slotCount) - unchanged[unchangedIndex++] = originalIndex++; - // Fill in unchanged items. - for (int ii = 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 = input.ReadInt(true); - if (eventCount > 0) { - EventTimeline timeline = new EventTimeline(eventCount); - for (int i = 0; i < eventCount; i++) { - float time = input.ReadFloat(); - EventData eventData = skeletonData.events.Items[input.ReadInt(true)]; - Event e = new Event(time, eventData); - e.intValue = input.ReadInt(false); - e.floatValue = input.ReadFloat(); - e.stringValue = input.ReadBoolean() ? input.ReadString() : eventData.String; - if (e.Data.AudioPath != null) { - e.volume = input.ReadFloat(); - e.balance = input.ReadFloat(); - } - timeline.SetFrame(i, e); - } - timelines.Add(timeline); - } - - float duration = 0; - var items = timelines.Items; - for (int i = 0, n = timelines.Count; i < n; i++) - duration = Math.Max(duration, items[i].Duration); - return new Animation(name, timelines, duration); - } - - /// Throws IOException when a read operation fails. - private Timeline ReadTimeline (SkeletonInput input, CurveTimeline1 timeline, float scale) { - float time = input.ReadFloat(), value = input.ReadFloat() * scale; - for (int frame = 0, bezier = 0, frameLast = timeline.FrameCount - 1; ; frame++) { - timeline.SetFrame(frame, time, value); - if (frame == frameLast) break; - float time2 = input.ReadFloat(), value2 = input.ReadFloat() * scale; - switch (input.ReadByte()) { - case CURVE_STEPPED: - timeline.SetStepped(frame); - break; - case CURVE_BEZIER: - SetBezier(input, timeline, bezier++, frame, 0, time, time2, value, value2, scale); - break; - } - time = time2; - value = value2; - } - return timeline; - } - - /// Throws IOException when a read operation fails. - private Timeline ReadTimeline (SkeletonInput input, CurveTimeline2 timeline, float scale) { - float time = input.ReadFloat(), value1 = input.ReadFloat() * scale, value2 = input.ReadFloat() * scale; - for (int frame = 0, bezier = 0, frameLast = timeline.FrameCount - 1; ; frame++) { - timeline.SetFrame(frame, time, value1, value2); - if (frame == frameLast) break; - float time2 = input.ReadFloat(), nvalue1 = input.ReadFloat() * scale, nvalue2 = input.ReadFloat() * scale; - switch (input.ReadByte()) { - 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); - break; - } - time = time2; - value1 = nvalue1; - value2 = nvalue2; - } - return timeline; - } - - /// Throws IOException when a read operation fails. - void SetBezier (SkeletonInput input, CurveTimeline timeline, int bezier, int frame, int value, float time1, float time2, - float value1, float value2, float scale) { - timeline.SetBezier(bezier, frame, value, time1, value1, input.ReadFloat(), input.ReadFloat() * scale, input.ReadFloat(), - input.ReadFloat() * scale, time2, value2); - } - - internal class Vertices { - public int[] bones; - public float[] vertices; - } - - internal class SkeletonInput { - private byte[] chars = new byte[32]; - private byte[] bytesBigEndian = new byte[8]; - internal string[] strings; - Stream input; - - public SkeletonInput (Stream input) { - this.input = input; - } - - public int Read () { - return input.ReadByte(); - } - - public byte ReadByte () { - return (byte)input.ReadByte(); - } - - public sbyte ReadSByte () { - int value = input.ReadByte(); - if (value == -1) throw new EndOfStreamException(); - return (sbyte)value; - } - - public bool ReadBoolean () { - return input.ReadByte() != 0; - } - - public float ReadFloat () { - input.Read(bytesBigEndian, 0, 4); - chars[3] = bytesBigEndian[0]; - chars[2] = bytesBigEndian[1]; - chars[1] = bytesBigEndian[2]; - chars[0] = bytesBigEndian[3]; - return BitConverter.ToSingle(chars, 0); - } - - public int ReadInt () { - input.Read(bytesBigEndian, 0, 4); - return (bytesBigEndian[0] << 24) - + (bytesBigEndian[1] << 16) - + (bytesBigEndian[2] << 8) - + bytesBigEndian[3]; - } - - public long ReadLong () { - input.Read(bytesBigEndian, 0, 8); - return ((long)(bytesBigEndian[0]) << 56) - + ((long)(bytesBigEndian[1]) << 48) - + ((long)(bytesBigEndian[2]) << 40) - + ((long)(bytesBigEndian[3]) << 32) - + ((long)(bytesBigEndian[4]) << 24) - + ((long)(bytesBigEndian[5]) << 16) - + ((long)(bytesBigEndian[6]) << 8) - + (long)(bytesBigEndian[7]); - } - - public int ReadInt (bool optimizePositive) { - int b = input.ReadByte(); - int result = b & 0x7F; - if ((b & 0x80) != 0) { - b = input.ReadByte(); - result |= (b & 0x7F) << 7; - if ((b & 0x80) != 0) { - b = input.ReadByte(); - result |= (b & 0x7F) << 14; - if ((b & 0x80) != 0) { - b = input.ReadByte(); - result |= (b & 0x7F) << 21; - if ((b & 0x80) != 0) result |= (input.ReadByte() & 0x7F) << 28; - } - } - } - return optimizePositive ? result : ((result >> 1) ^ -(result & 1)); - } - - public string ReadString () { - int byteCount = ReadInt(true); - switch (byteCount) { - case 0: - return null; - case 1: - return ""; - } - byteCount--; - byte[] buffer = this.chars; - if (buffer.Length < byteCount) buffer = new byte[byteCount]; - ReadFully(buffer, 0, byteCount); - return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount); - } - - ///May be null. - public String ReadStringRef () { - int index = ReadInt(true); - return index == 0 ? null : strings[index - 1]; - } - - public void ReadFully (byte[] buffer, int offset, int length) { - while (length > 0) { - int count = input.Read(buffer, offset, length); - if (count <= 0) throw new EndOfStreamException(); - offset += count; - length -= count; - } - } - - /// Returns the version string of binary skeleton data. - public string GetVersionString () { - try { - // try reading 4.0+ format - var initialPosition = input.Position; - ReadLong(); // long hash - - var stringPosition = input.Position; - int stringByteCount = ReadInt(true); - input.Position = stringPosition; - if (stringByteCount <= 13) { - string version = ReadString(); - if (char.IsDigit(version[0])) - return version; - } - // fallback to old version format - input.Position = initialPosition; - return GetVersionStringOld3X(); - } catch (Exception e) { - throw new ArgumentException("Stream does not contain a valid binary Skeleton Data.\n" + e, "input"); - } - } - - /// Returns old 3.8 and earlier format version string of binary skeleton data. - public string GetVersionStringOld3X () { - // Hash. - int byteCount = ReadInt(true); - if (byteCount > 1) input.Position += byteCount - 1; - - // Version. - byteCount = ReadInt(true); - if (byteCount > 1) { - byteCount--; - var buffer = new byte[byteCount]; - ReadFully(buffer, 0, byteCount); - return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount); - } - return null; - } - } - } + public static readonly TransformMode[] TransformModeValues = { + TransformMode.Normal, + TransformMode.OnlyTranslation, + TransformMode.NoRotationOrReflection, + TransformMode.NoScale, + TransformMode.NoScaleOrReflection + }; + + /// Returns the version string of binary skeleton data. + public static string GetVersionString(Stream file) + { + if (file == null) throw new ArgumentNullException("file"); + + SkeletonInput input = new SkeletonInput(file); + return input.GetVersionString(); + } + + public SkeletonData ReadSkeletonData(Stream file) + { + if (file == null) throw new ArgumentNullException("file"); + float scale = this.scale; + + var skeletonData = new SkeletonData(); + SkeletonInput input = new SkeletonInput(file); + + long hash = input.ReadLong(); + skeletonData.hash = hash == 0 ? null : hash.ToString(); + skeletonData.version = input.ReadString(); + if (skeletonData.version.Length == 0) skeletonData.version = null; + // early return for old 3.8 format instead of reading past the end + if (skeletonData.version.Length > 13) return null; + skeletonData.x = input.ReadFloat(); + skeletonData.y = input.ReadFloat(); + skeletonData.width = input.ReadFloat(); + skeletonData.height = input.ReadFloat(); + + bool nonessential = input.ReadBoolean(); + + if (nonessential) + { + skeletonData.fps = input.ReadFloat(); + + skeletonData.imagesPath = input.ReadString(); + if (string.IsNullOrEmpty(skeletonData.imagesPath)) skeletonData.imagesPath = null; + + skeletonData.audioPath = input.ReadString(); + if (string.IsNullOrEmpty(skeletonData.audioPath)) skeletonData.audioPath = null; + } + + int n; + Object[] o; + + // Strings. + o = input.strings = new String[n = input.ReadInt(true)]; + for (int i = 0; i < n; i++) + o[i] = input.ReadString(); + + // Bones. + var bones = skeletonData.bones.Resize(n = input.ReadInt(true)).Items; + for (int i = 0; i < n; i++) + { + String name = input.ReadString(); + BoneData parent = i == 0 ? null : bones[input.ReadInt(true)]; + BoneData data = new BoneData(i, name, parent); + data.rotation = input.ReadFloat(); + data.x = input.ReadFloat() * scale; + data.y = input.ReadFloat() * scale; + data.scaleX = input.ReadFloat(); + data.scaleY = input.ReadFloat(); + data.shearX = input.ReadFloat(); + data.shearY = input.ReadFloat(); + data.Length = input.ReadFloat() * scale; + data.transformMode = TransformModeValues[input.ReadInt(true)]; + data.skinRequired = input.ReadBoolean(); + if (nonessential) input.ReadInt(); // Skip bone color. + bones[i] = data; + } + + // Slots. + var slots = skeletonData.slots.Resize(n = input.ReadInt(true)).Items; + for (int i = 0; i < n; i++) + { + String slotName = input.ReadString(); + BoneData boneData = bones[input.ReadInt(true)]; + SlotData slotData = new SlotData(i, slotName, boneData); + int color = input.ReadInt(); + slotData.r = ((color & 0xff000000) >> 24) / 255f; + slotData.g = ((color & 0x00ff0000) >> 16) / 255f; + slotData.b = ((color & 0x0000ff00) >> 8) / 255f; + slotData.a = ((color & 0x000000ff)) / 255f; + + int darkColor = input.ReadInt(); // 0x00rrggbb + if (darkColor != -1) + { + slotData.hasSecondColor = true; + slotData.r2 = ((darkColor & 0x00ff0000) >> 16) / 255f; + slotData.g2 = ((darkColor & 0x0000ff00) >> 8) / 255f; + slotData.b2 = ((darkColor & 0x000000ff)) / 255f; + } + + slotData.attachmentName = input.ReadStringRef(); + slotData.blendMode = (BlendMode)input.ReadInt(true); + slots[i] = slotData; + } + + // IK constraints. + o = skeletonData.ikConstraints.Resize(n = input.ReadInt(true)).Items; + for (int i = 0, nn; i < n; i++) + { + IkConstraintData data = new IkConstraintData(input.ReadString()); + data.order = input.ReadInt(true); + data.skinRequired = input.ReadBoolean(); + var constraintBones = data.bones.Resize(nn = input.ReadInt(true)).Items; + for (int ii = 0; ii < nn; ii++) + constraintBones[ii] = bones[input.ReadInt(true)]; + data.target = bones[input.ReadInt(true)]; + data.mix = input.ReadFloat(); + data.softness = input.ReadFloat() * scale; + data.bendDirection = input.ReadSByte(); + data.compress = input.ReadBoolean(); + data.stretch = input.ReadBoolean(); + data.uniform = input.ReadBoolean(); + o[i] = data; + } + + // Transform constraints. + o = skeletonData.transformConstraints.Resize(n = input.ReadInt(true)).Items; + for (int i = 0, nn; i < n; i++) + { + TransformConstraintData data = new TransformConstraintData(input.ReadString()); + data.order = input.ReadInt(true); + data.skinRequired = input.ReadBoolean(); + var constraintBones = data.bones.Resize(nn = input.ReadInt(true)).Items; + for (int ii = 0; ii < nn; ii++) + constraintBones[ii] = bones[input.ReadInt(true)]; + data.target = bones[input.ReadInt(true)]; + data.local = input.ReadBoolean(); + data.relative = input.ReadBoolean(); + data.offsetRotation = input.ReadFloat(); + data.offsetX = input.ReadFloat() * scale; + data.offsetY = input.ReadFloat() * scale; + data.offsetScaleX = input.ReadFloat(); + data.offsetScaleY = input.ReadFloat(); + data.offsetShearY = input.ReadFloat(); + data.mixRotate = input.ReadFloat(); + data.mixX = input.ReadFloat(); + data.mixY = input.ReadFloat(); + data.mixScaleX = input.ReadFloat(); + data.mixScaleY = input.ReadFloat(); + data.mixShearY = input.ReadFloat(); + o[i] = data; + } + + // Path constraints + o = skeletonData.pathConstraints.Resize(n = input.ReadInt(true)).Items; + for (int i = 0, nn; i < n; i++) + { + PathConstraintData data = new PathConstraintData(input.ReadString()); + data.order = input.ReadInt(true); + data.skinRequired = input.ReadBoolean(); + Object[] constraintBones = data.bones.Resize(nn = input.ReadInt(true)).Items; + for (int ii = 0; ii < nn; ii++) + constraintBones[ii] = bones[input.ReadInt(true)]; + data.target = slots[input.ReadInt(true)]; + data.positionMode = (PositionMode)Enum.GetValues(typeof(PositionMode)).GetValue(input.ReadInt(true)); + data.spacingMode = (SpacingMode)Enum.GetValues(typeof(SpacingMode)).GetValue(input.ReadInt(true)); + data.rotateMode = (RotateMode)Enum.GetValues(typeof(RotateMode)).GetValue(input.ReadInt(true)); + data.offsetRotation = input.ReadFloat(); + data.position = input.ReadFloat(); + if (data.positionMode == PositionMode.Fixed) data.position *= scale; + data.spacing = input.ReadFloat(); + if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale; + data.mixRotate = input.ReadFloat(); + data.mixX = input.ReadFloat(); + data.mixY = input.ReadFloat(); + o[i] = data; + } + + // Default skin. + Skin defaultSkin = ReadSkin(input, skeletonData, true, nonessential); + if (defaultSkin != null) + { + skeletonData.defaultSkin = defaultSkin; + skeletonData.skins.Add(defaultSkin); + } + + // Skins. + { + int i = skeletonData.skins.Count; + o = skeletonData.skins.Resize(n = i + input.ReadInt(true)).Items; + for (; i < n; i++) + o[i] = ReadSkin(input, skeletonData, false, nonessential); + } + + // Linked meshes. + n = linkedMeshes.Count; + for (int i = 0; i < n; i++) + { + LinkedMesh linkedMesh = linkedMeshes[i]; + Skin skin = linkedMesh.skin == null ? skeletonData.DefaultSkin : skeletonData.FindSkin(linkedMesh.skin); + if (skin == null) throw new Exception("Skin not found: " + linkedMesh.skin); + Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); + if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); + linkedMesh.mesh.DeformAttachment = linkedMesh.inheritDeform ? (VertexAttachment)parent : linkedMesh.mesh; + linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; + linkedMesh.mesh.UpdateUVs(); + } + linkedMeshes.Clear(); + + // Events. + o = skeletonData.events.Resize(n = input.ReadInt(true)).Items; + for (int i = 0; i < n; i++) + { + EventData data = new EventData(input.ReadStringRef()); + data.Int = input.ReadInt(false); + data.Float = input.ReadFloat(); + data.String = input.ReadString(); + data.AudioPath = input.ReadString(); + if (data.AudioPath != null) + { + data.Volume = input.ReadFloat(); + data.Balance = input.ReadFloat(); + } + o[i] = data; + } + + // Animations. + o = skeletonData.animations.Resize(n = input.ReadInt(true)).Items; + for (int i = 0; i < n; i++) + o[i] = ReadAnimation(input.ReadString(), input, skeletonData); + + return skeletonData; + } + + /// May be null. + private Skin ReadSkin(SkeletonInput input, SkeletonData skeletonData, bool defaultSkin, bool nonessential) + { + + Skin skin; + int slotCount; + + if (defaultSkin) + { + slotCount = input.ReadInt(true); + if (slotCount == 0) return null; + skin = new Skin("default"); + } + else + { + skin = new Skin(input.ReadStringRef()); + Object[] bones = skin.bones.Resize(input.ReadInt(true)).Items; + var bonesItems = skeletonData.bones.Items; + for (int i = 0, n = skin.bones.Count; i < n; i++) + bones[i] = bonesItems[input.ReadInt(true)]; + + var ikConstraintsItems = skeletonData.ikConstraints.Items; + for (int i = 0, n = input.ReadInt(true); i < n; i++) + skin.constraints.Add(ikConstraintsItems[input.ReadInt(true)]); + var transformConstraintsItems = skeletonData.transformConstraints.Items; + for (int i = 0, n = input.ReadInt(true); i < n; i++) + skin.constraints.Add(transformConstraintsItems[input.ReadInt(true)]); + var pathConstraintsItems = skeletonData.pathConstraints.Items; + for (int i = 0, n = input.ReadInt(true); i < n; i++) + skin.constraints.Add(pathConstraintsItems[input.ReadInt(true)]); + skin.constraints.TrimExcess(); + + slotCount = input.ReadInt(true); + } + for (int i = 0; i < slotCount; i++) + { + int slotIndex = input.ReadInt(true); + for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) + { + String name = input.ReadStringRef(); + Attachment attachment = ReadAttachment(input, skeletonData, skin, slotIndex, name, nonessential); + if (attachment != null) skin.SetAttachment(slotIndex, name, attachment); + } + } + return skin; + } + + private Attachment ReadAttachment(SkeletonInput input, SkeletonData skeletonData, Skin skin, int slotIndex, + String attachmentName, bool nonessential) + { + float scale = this.scale; + + String name = input.ReadStringRef(); + if (name == null) name = attachmentName; + + switch ((AttachmentType)input.ReadByte()) + { + case AttachmentType.Region: + { + String path = input.ReadStringRef(); + float rotation = input.ReadFloat(); + float x = input.ReadFloat(); + float y = input.ReadFloat(); + float scaleX = input.ReadFloat(); + float scaleY = input.ReadFloat(); + float width = input.ReadFloat(); + float height = input.ReadFloat(); + int color = input.ReadInt(); + + if (path == null) path = name; + RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); + if (region == null) return null; + region.Path = path; + region.x = x * scale; + region.y = y * scale; + region.scaleX = scaleX; + region.scaleY = scaleY; + region.rotation = rotation; + region.width = width * scale; + region.height = height * scale; + region.r = ((color & 0xff000000) >> 24) / 255f; + region.g = ((color & 0x00ff0000) >> 16) / 255f; + region.b = ((color & 0x0000ff00) >> 8) / 255f; + region.a = ((color & 0x000000ff)) / 255f; + region.UpdateOffset(); + return region; + } + case AttachmentType.Boundingbox: + { + int vertexCount = input.ReadInt(true); + Vertices vertices = ReadVertices(input, vertexCount); + if (nonessential) input.ReadInt(); //int color = nonessential ? input.ReadInt() : 0; // Avoid unused local warning. + + BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); + if (box == null) return null; + box.worldVerticesLength = vertexCount << 1; + box.vertices = vertices.vertices; + box.bones = vertices.bones; + // skipped porting: if (nonessential) Color.rgba8888ToColor(box.getColor(), color); + return box; + } + case AttachmentType.Mesh: + { + String path = input.ReadStringRef(); + int color = input.ReadInt(); + int vertexCount = input.ReadInt(true); + float[] uvs = ReadFloatArray(input, vertexCount << 1, 1); + int[] triangles = ReadShortArray(input); + Vertices vertices = ReadVertices(input, vertexCount); + int hullLength = input.ReadInt(true); + int[] edges = null; + float width = 0, height = 0; + if (nonessential) + { + edges = ReadShortArray(input); + width = input.ReadFloat(); + height = input.ReadFloat(); + } + + if (path == null) path = name; + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); + if (mesh == null) return null; + mesh.Path = path; + mesh.r = ((color & 0xff000000) >> 24) / 255f; + mesh.g = ((color & 0x00ff0000) >> 16) / 255f; + mesh.b = ((color & 0x0000ff00) >> 8) / 255f; + mesh.a = ((color & 0x000000ff)) / 255f; + mesh.bones = vertices.bones; + mesh.vertices = vertices.vertices; + mesh.WorldVerticesLength = vertexCount << 1; + mesh.triangles = triangles; + mesh.regionUVs = uvs; + mesh.UpdateUVs(); + mesh.HullLength = hullLength << 1; + if (nonessential) + { + mesh.Edges = edges; + mesh.Width = width * scale; + mesh.Height = height * scale; + } + return mesh; + } + case AttachmentType.Linkedmesh: + { + String path = input.ReadStringRef(); + int color = input.ReadInt(); + String skinName = input.ReadStringRef(); + String parent = input.ReadStringRef(); + bool inheritDeform = input.ReadBoolean(); + float width = 0, height = 0; + if (nonessential) + { + width = input.ReadFloat(); + height = input.ReadFloat(); + } + + if (path == null) path = name; + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); + if (mesh == null) return null; + mesh.Path = path; + mesh.r = ((color & 0xff000000) >> 24) / 255f; + mesh.g = ((color & 0x00ff0000) >> 16) / 255f; + mesh.b = ((color & 0x0000ff00) >> 8) / 255f; + mesh.a = ((color & 0x000000ff)) / 255f; + if (nonessential) + { + mesh.Width = width * scale; + mesh.Height = height * scale; + } + linkedMeshes.Add(new SkeletonJson.LinkedMesh(mesh, skinName, slotIndex, parent, inheritDeform)); + return mesh; + } + case AttachmentType.Path: + { + bool closed = input.ReadBoolean(); + bool constantSpeed = input.ReadBoolean(); + int vertexCount = input.ReadInt(true); + Vertices vertices = ReadVertices(input, vertexCount); + float[] lengths = new float[vertexCount / 3]; + for (int i = 0, n = lengths.Length; i < n; i++) + lengths[i] = input.ReadFloat() * scale; + if (nonessential) input.ReadInt(); //int color = nonessential ? input.ReadInt() : 0; + + PathAttachment path = attachmentLoader.NewPathAttachment(skin, name); + if (path == null) return null; + path.closed = closed; + path.constantSpeed = constantSpeed; + path.worldVerticesLength = vertexCount << 1; + path.vertices = vertices.vertices; + path.bones = vertices.bones; + path.lengths = lengths; + // skipped porting: if (nonessential) Color.rgba8888ToColor(path.getColor(), color); + return path; + } + case AttachmentType.Point: + { + float rotation = input.ReadFloat(); + float x = input.ReadFloat(); + float y = input.ReadFloat(); + if (nonessential) input.ReadInt(); //int color = nonessential ? input.ReadInt() : 0; + + PointAttachment point = attachmentLoader.NewPointAttachment(skin, name); + if (point == null) return null; + point.x = x * scale; + point.y = y * scale; + point.rotation = rotation; + // skipped porting: if (nonessential) point.color = color; + return point; + } + case AttachmentType.Clipping: + { + int endSlotIndex = input.ReadInt(true); + int vertexCount = input.ReadInt(true); + Vertices vertices = ReadVertices(input, vertexCount); + if (nonessential) input.ReadInt(); + + ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name); + if (clip == null) return null; + clip.EndSlot = skeletonData.slots.Items[endSlotIndex]; + clip.worldVerticesLength = vertexCount << 1; + clip.vertices = vertices.vertices; + clip.bones = vertices.bones; + // skipped porting: if (nonessential) Color.rgba8888ToColor(clip.getColor(), color); + return clip; + } + } + return null; + } + + private Vertices ReadVertices(SkeletonInput input, int vertexCount) + { + float scale = this.scale; + int verticesLength = vertexCount << 1; + Vertices vertices = new Vertices(); + if (!input.ReadBoolean()) + { + vertices.vertices = ReadFloatArray(input, verticesLength, scale); + return vertices; + } + var weights = new ExposedList(verticesLength * 3 * 3); + var bonesArray = new ExposedList(verticesLength * 3); + for (int i = 0; i < vertexCount; i++) + { + int boneCount = input.ReadInt(true); + bonesArray.Add(boneCount); + for (int ii = 0; ii < boneCount; ii++) + { + bonesArray.Add(input.ReadInt(true)); + weights.Add(input.ReadFloat() * scale); + weights.Add(input.ReadFloat() * scale); + weights.Add(input.ReadFloat()); + } + } + + vertices.vertices = weights.ToArray(); + vertices.bones = bonesArray.ToArray(); + return vertices; + } + + private float[] ReadFloatArray(SkeletonInput input, int n, float scale) + { + float[] array = new float[n]; + if (scale == 1) + { + for (int i = 0; i < n; i++) + array[i] = input.ReadFloat(); + } + else + { + for (int i = 0; i < n; i++) + array[i] = input.ReadFloat() * scale; + } + return array; + } + + private int[] ReadShortArray(SkeletonInput input) + { + int n = input.ReadInt(true); + int[] array = new int[n]; + for (int i = 0; i < n; i++) + array[i] = (input.ReadByte() << 8) | input.ReadByte(); + return array; + } + + /// SerializationException will be thrown when a Vertex attachment is not found. + /// Throws IOException when a read operation fails. + private Animation ReadAnimation(String name, SkeletonInput input, SkeletonData skeletonData) + { + var timelines = new ExposedList(input.ReadInt(true)); + float scale = this.scale; + + // Slot timelines. + for (int i = 0, n = input.ReadInt(true); i < n; i++) + { + int slotIndex = input.ReadInt(true); + for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) + { + int timelineType = input.ReadByte(), frameCount = input.ReadInt(true), frameLast = frameCount - 1; + switch (timelineType) + { + case SLOT_ATTACHMENT: + { + AttachmentTimeline timeline = new AttachmentTimeline(frameCount, slotIndex); + for (int frame = 0; frame < frameCount; frame++) + timeline.SetFrame(frame, input.ReadFloat(), input.ReadStringRef()); + timelines.Add(timeline); + break; + } + case SLOT_RGBA: + { + RGBATimeline timeline = new RGBATimeline(frameCount, input.ReadInt(true), slotIndex); + float time = input.ReadFloat(); + float r = input.Read() / 255f, g = input.Read() / 255f; + float b = input.Read() / 255f, a = input.Read() / 255f; + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, r, g, b, a); + if (frame == frameLast) break; + float time2 = input.ReadFloat(); + float r2 = input.Read() / 255f, g2 = input.Read() / 255f; + float b2 = input.Read() / 255f, a2 = input.Read() / 255f; + switch (input.ReadByte()) + { + 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); + break; + } + time = time2; + r = r2; + g = g2; + b = b2; + a = a2; + } + timelines.Add(timeline); + break; + } + case SLOT_RGB: + { + RGBTimeline timeline = new RGBTimeline(frameCount, input.ReadInt(true), slotIndex); + float time = input.ReadFloat(); + float r = input.Read() / 255f, g = input.Read() / 255f, b = input.Read() / 255f; + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, r, g, b); + if (frame == frameLast) break; + float time2 = input.ReadFloat(); + float r2 = input.Read() / 255f, g2 = input.Read() / 255f, b2 = input.Read() / 255f; + switch (input.ReadByte()) + { + 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); + break; + } + time = time2; + r = r2; + g = g2; + b = b2; + } + timelines.Add(timeline); + break; + } + case SLOT_RGBA2: + { + RGBA2Timeline timeline = new RGBA2Timeline(frameCount, input.ReadInt(true), slotIndex); + float time = input.ReadFloat(); + float r = input.Read() / 255f, g = input.Read() / 255f; + float b = input.Read() / 255f, a = input.Read() / 255f; + float r2 = input.Read() / 255f, g2 = input.Read() / 255f, b2 = input.Read() / 255f; + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, r, g, b, a, r2, g2, b2); + if (frame == frameLast) break; + float time2 = input.ReadFloat(); + float nr = input.Read() / 255f, ng = input.Read() / 255f; + float nb = input.Read() / 255f, na = input.Read() / 255f; + float nr2 = input.Read() / 255f, ng2 = input.Read() / 255f, nb2 = input.Read() / 255f; + switch (input.ReadByte()) + { + 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); + break; + } + time = time2; + r = nr; + g = ng; + b = nb; + a = na; + r2 = nr2; + g2 = ng2; + b2 = nb2; + } + timelines.Add(timeline); + break; + } + case SLOT_RGB2: + { + RGB2Timeline timeline = new RGB2Timeline(frameCount, input.ReadInt(true), slotIndex); + float time = input.ReadFloat(); + float r = input.Read() / 255f, g = input.Read() / 255f, b = input.Read() / 255f; + float r2 = input.Read() / 255f, g2 = input.Read() / 255f, b2 = input.Read() / 255f; + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, r, g, b, r2, g2, b2); + if (frame == frameLast) break; + float time2 = input.ReadFloat(); + float nr = input.Read() / 255f, ng = input.Read() / 255f, nb = input.Read() / 255f; + float nr2 = input.Read() / 255f, ng2 = input.Read() / 255f, nb2 = input.Read() / 255f; + switch (input.ReadByte()) + { + 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); + break; + } + time = time2; + r = nr; + g = ng; + b = nb; + r2 = nr2; + g2 = ng2; + b2 = nb2; + } + timelines.Add(timeline); + break; + } + case SLOT_ALPHA: + { + AlphaTimeline timeline = new AlphaTimeline(frameCount, input.ReadInt(true), slotIndex); + float time = input.ReadFloat(), a = input.Read() / 255f; + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, a); + if (frame == frameLast) break; + float time2 = input.ReadFloat(); + float a2 = input.Read() / 255f; + switch (input.ReadByte()) + { + case CURVE_STEPPED: + timeline.SetStepped(frame); + break; + case CURVE_BEZIER: + SetBezier(input, timeline, bezier++, frame, 0, time, time2, a, a2, 1); + break; + } + time = time2; + a = a2; + } + timelines.Add(timeline); + break; + } + } + } + } + + // Bone timelines. + for (int i = 0, n = input.ReadInt(true); i < n; i++) + { + int boneIndex = input.ReadInt(true); + for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) + { + int type = input.ReadByte(), frameCount = input.ReadInt(true), bezierCount = input.ReadInt(true); + switch (type) + { + case BONE_ROTATE: + timelines.Add(ReadTimeline(input, new RotateTimeline(frameCount, bezierCount, boneIndex), 1)); + break; + case BONE_TRANSLATE: + timelines.Add(ReadTimeline(input, new TranslateTimeline(frameCount, bezierCount, boneIndex), scale)); + break; + case BONE_TRANSLATEX: + timelines.Add(ReadTimeline(input, new TranslateXTimeline(frameCount, bezierCount, boneIndex), scale)); + break; + case BONE_TRANSLATEY: + timelines.Add(ReadTimeline(input, new TranslateYTimeline(frameCount, bezierCount, boneIndex), scale)); + break; + case BONE_SCALE: + timelines.Add(ReadTimeline(input, new ScaleTimeline(frameCount, bezierCount, boneIndex), 1)); + break; + case BONE_SCALEX: + timelines.Add(ReadTimeline(input, new ScaleXTimeline(frameCount, bezierCount, boneIndex), 1)); + break; + case BONE_SCALEY: + timelines.Add(ReadTimeline(input, new ScaleYTimeline(frameCount, bezierCount, boneIndex), 1)); + break; + case BONE_SHEAR: + timelines.Add(ReadTimeline(input, new ShearTimeline(frameCount, bezierCount, boneIndex), 1)); + break; + case BONE_SHEARX: + timelines.Add(ReadTimeline(input, new ShearXTimeline(frameCount, bezierCount, boneIndex), 1)); + break; + case BONE_SHEARY: + timelines.Add(ReadTimeline(input, new ShearYTimeline(frameCount, bezierCount, boneIndex), 1)); + break; + } + } + } + + // IK constraint timelines. + for (int i = 0, n = input.ReadInt(true); i < n; i++) + { + int index = input.ReadInt(true), frameCount = input.ReadInt(true), frameLast = frameCount - 1; + IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount, input.ReadInt(true), index); + float time = input.ReadFloat(), mix = input.ReadFloat(), softness = input.ReadFloat() * scale; + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, mix, softness, input.ReadSByte(), input.ReadBoolean(), input.ReadBoolean()); + if (frame == frameLast) break; + float time2 = input.ReadFloat(), mix2 = input.ReadFloat(), softness2 = input.ReadFloat() * scale; + switch (input.ReadByte()) + { + case CURVE_STEPPED: + timeline.SetStepped(frame); + break; + case CURVE_BEZIER: + SetBezier(input, timeline, bezier++, frame, 0, time, time2, mix, mix2, 1); + SetBezier(input, timeline, bezier++, frame, 1, time, time2, softness, softness2, scale); + break; + } + time = time2; + mix = mix2; + softness = softness2; + } + timelines.Add(timeline); + } + + // Transform constraint timelines. + for (int i = 0, n = input.ReadInt(true); i < n; i++) + { + int index = input.ReadInt(true), frameCount = input.ReadInt(true), frameLast = frameCount - 1; + TransformConstraintTimeline timeline = new TransformConstraintTimeline(frameCount, input.ReadInt(true), index); + float time = input.ReadFloat(), mixRotate = input.ReadFloat(), mixX = input.ReadFloat(), mixY = input.ReadFloat(), + mixScaleX = input.ReadFloat(), mixScaleY = input.ReadFloat(), mixShearY = input.ReadFloat(); + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY); + if (frame == frameLast) break; + float time2 = input.ReadFloat(), mixRotate2 = input.ReadFloat(), mixX2 = input.ReadFloat(), mixY2 = input.ReadFloat(), + mixScaleX2 = input.ReadFloat(), mixScaleY2 = input.ReadFloat(), mixShearY2 = input.ReadFloat(); + switch (input.ReadByte()) + { + 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); + break; + } + 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 = input.ReadInt(true); i < n; i++) + { + int index = input.ReadInt(true); + PathConstraintData data = skeletonData.pathConstraints.Items[index]; + for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) + { + switch (input.ReadByte()) + { + case PATH_POSITION: + timelines + .Add(ReadTimeline(input, new PathConstraintPositionTimeline(input.ReadInt(true), input.ReadInt(true), index), + data.positionMode == PositionMode.Fixed ? scale : 1)); + break; + case PATH_SPACING: + timelines + .Add(ReadTimeline(input, new PathConstraintSpacingTimeline(input.ReadInt(true), input.ReadInt(true), index), + data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed ? scale : 1)); + break; + case PATH_MIX: + PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(input.ReadInt(true), input.ReadInt(true), + index); + float time = input.ReadFloat(), mixRotate = input.ReadFloat(), mixX = input.ReadFloat(), mixY = input.ReadFloat(); + for (int frame = 0, bezier = 0, frameLast = timeline.FrameCount - 1; ; frame++) + { + timeline.SetFrame(frame, time, mixRotate, mixX, mixY); + if (frame == frameLast) break; + float time2 = input.ReadFloat(), mixRotate2 = input.ReadFloat(), mixX2 = input.ReadFloat(), + mixY2 = input.ReadFloat(); + switch (input.ReadByte()) + { + 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); + break; + } + time = time2; + mixRotate = mixRotate2; + mixX = mixX2; + mixY = mixY2; + } + timelines.Add(timeline); + break; + } + } + } + + // Deform timelines. + for (int i = 0, n = input.ReadInt(true); i < n; i++) + { + Skin skin = skeletonData.skins.Items[input.ReadInt(true)]; + for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) + { + int slotIndex = input.ReadInt(true); + for (int iii = 0, nnn = input.ReadInt(true); iii < nnn; iii++) + { + String attachmentName = input.ReadStringRef(); + VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, attachmentName); + if (attachment == null) throw new SerializationException("Vertex attachment not found: " + attachmentName); + bool weighted = attachment.Bones != null; + float[] vertices = attachment.Vertices; + int deformLength = weighted ? (vertices.Length / 3) << 1 : vertices.Length; + + int frameCount = input.ReadInt(true), frameLast = frameCount - 1; + DeformTimeline timeline = new DeformTimeline(frameCount, input.ReadInt(true), slotIndex, attachment); + + float time = input.ReadFloat(); + for (int frame = 0, bezier = 0; ; frame++) + { + float[] deform; + int end = input.ReadInt(true); + if (end == 0) + deform = weighted ? new float[deformLength] : vertices; + else + { + deform = new float[deformLength]; + int start = input.ReadInt(true); + end += start; + if (scale == 1) + { + for (int v = start; v < end; v++) + deform[v] = input.ReadFloat(); + } + else + { + for (int v = start; v < end; v++) + deform[v] = input.ReadFloat() * scale; + } + if (!weighted) + { + for (int v = 0, vn = deform.Length; v < vn; v++) + deform[v] += vertices[v]; + } + } + timeline.SetFrame(frame, time, deform); + if (frame == frameLast) break; + float time2 = input.ReadFloat(); + switch (input.ReadByte()) + { + case CURVE_STEPPED: + timeline.SetStepped(frame); + break; + case CURVE_BEZIER: + SetBezier(input, timeline, bezier++, frame, 0, time, time2, 0, 1, 1); + break; + } + time = time2; + } + timelines.Add(timeline); + } + } + } + + // Draw order timeline. + int drawOrderCount = input.ReadInt(true); + if (drawOrderCount > 0) + { + DrawOrderTimeline timeline = new DrawOrderTimeline(drawOrderCount); + int slotCount = skeletonData.slots.Count; + for (int i = 0; i < drawOrderCount; i++) + { + float time = input.ReadFloat(); + int offsetCount = input.ReadInt(true); + int[] drawOrder = new int[slotCount]; + for (int ii = slotCount - 1; ii >= 0; ii--) + drawOrder[ii] = -1; + int[] unchanged = new int[slotCount - offsetCount]; + int originalIndex = 0, unchangedIndex = 0; + for (int ii = 0; ii < offsetCount; ii++) + { + int slotIndex = input.ReadInt(true); + // Collect unchanged items. + while (originalIndex != slotIndex) + unchanged[unchangedIndex++] = originalIndex++; + // Set changed items. + drawOrder[originalIndex + input.ReadInt(true)] = originalIndex++; + } + // Collect remaining unchanged items. + while (originalIndex < slotCount) + unchanged[unchangedIndex++] = originalIndex++; + // Fill in unchanged items. + for (int ii = 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 = input.ReadInt(true); + if (eventCount > 0) + { + EventTimeline timeline = new EventTimeline(eventCount); + for (int i = 0; i < eventCount; i++) + { + float time = input.ReadFloat(); + EventData eventData = skeletonData.events.Items[input.ReadInt(true)]; + Event e = new Event(time, eventData); + e.intValue = input.ReadInt(false); + e.floatValue = input.ReadFloat(); + e.stringValue = input.ReadBoolean() ? input.ReadString() : eventData.String; + if (e.Data.AudioPath != null) + { + e.volume = input.ReadFloat(); + e.balance = input.ReadFloat(); + } + timeline.SetFrame(i, e); + } + timelines.Add(timeline); + } + + float duration = 0; + var items = timelines.Items; + for (int i = 0, n = timelines.Count; i < n; i++) + duration = Math.Max(duration, items[i].Duration); + return new Animation(name, timelines, duration); + } + + /// Throws IOException when a read operation fails. + private Timeline ReadTimeline(SkeletonInput input, CurveTimeline1 timeline, float scale) + { + float time = input.ReadFloat(), value = input.ReadFloat() * scale; + for (int frame = 0, bezier = 0, frameLast = timeline.FrameCount - 1; ; frame++) + { + timeline.SetFrame(frame, time, value); + if (frame == frameLast) break; + float time2 = input.ReadFloat(), value2 = input.ReadFloat() * scale; + switch (input.ReadByte()) + { + case CURVE_STEPPED: + timeline.SetStepped(frame); + break; + case CURVE_BEZIER: + SetBezier(input, timeline, bezier++, frame, 0, time, time2, value, value2, scale); + break; + } + time = time2; + value = value2; + } + return timeline; + } + + /// Throws IOException when a read operation fails. + private Timeline ReadTimeline(SkeletonInput input, CurveTimeline2 timeline, float scale) + { + float time = input.ReadFloat(), value1 = input.ReadFloat() * scale, value2 = input.ReadFloat() * scale; + for (int frame = 0, bezier = 0, frameLast = timeline.FrameCount - 1; ; frame++) + { + timeline.SetFrame(frame, time, value1, value2); + if (frame == frameLast) break; + float time2 = input.ReadFloat(), nvalue1 = input.ReadFloat() * scale, nvalue2 = input.ReadFloat() * scale; + switch (input.ReadByte()) + { + 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); + break; + } + time = time2; + value1 = nvalue1; + value2 = nvalue2; + } + return timeline; + } + + /// Throws IOException when a read operation fails. + void SetBezier(SkeletonInput input, CurveTimeline timeline, int bezier, int frame, int value, float time1, float time2, + float value1, float value2, float scale) + { + timeline.SetBezier(bezier, frame, value, time1, value1, input.ReadFloat(), input.ReadFloat() * scale, input.ReadFloat(), + input.ReadFloat() * scale, time2, value2); + } + + internal class Vertices + { + public int[] bones; + public float[] vertices; + } + + internal class SkeletonInput + { + private byte[] chars = new byte[32]; + private byte[] bytesBigEndian = new byte[8]; + internal string[] strings; + Stream input; + + public SkeletonInput(Stream input) + { + this.input = input; + } + + public int Read() + { + return input.ReadByte(); + } + + public byte ReadByte() + { + return (byte)input.ReadByte(); + } + + public sbyte ReadSByte() + { + int value = input.ReadByte(); + if (value == -1) throw new EndOfStreamException(); + return (sbyte)value; + } + + public bool ReadBoolean() + { + return input.ReadByte() != 0; + } + + public float ReadFloat() + { + input.Read(bytesBigEndian, 0, 4); + chars[3] = bytesBigEndian[0]; + chars[2] = bytesBigEndian[1]; + chars[1] = bytesBigEndian[2]; + chars[0] = bytesBigEndian[3]; + return BitConverter.ToSingle(chars, 0); + } + + public int ReadInt() + { + input.Read(bytesBigEndian, 0, 4); + return (bytesBigEndian[0] << 24) + + (bytesBigEndian[1] << 16) + + (bytesBigEndian[2] << 8) + + bytesBigEndian[3]; + } + + public long ReadLong() + { + input.Read(bytesBigEndian, 0, 8); + return ((long)(bytesBigEndian[0]) << 56) + + ((long)(bytesBigEndian[1]) << 48) + + ((long)(bytesBigEndian[2]) << 40) + + ((long)(bytesBigEndian[3]) << 32) + + ((long)(bytesBigEndian[4]) << 24) + + ((long)(bytesBigEndian[5]) << 16) + + ((long)(bytesBigEndian[6]) << 8) + + bytesBigEndian[7]; + } + + public int ReadInt(bool optimizePositive) + { + int b = input.ReadByte(); + int result = b & 0x7F; + if ((b & 0x80) != 0) + { + b = input.ReadByte(); + result |= (b & 0x7F) << 7; + if ((b & 0x80) != 0) + { + b = input.ReadByte(); + result |= (b & 0x7F) << 14; + if ((b & 0x80) != 0) + { + b = input.ReadByte(); + result |= (b & 0x7F) << 21; + if ((b & 0x80) != 0) result |= (input.ReadByte() & 0x7F) << 28; + } + } + } + return optimizePositive ? result : ((result >> 1) ^ -(result & 1)); + } + + public string ReadString() + { + int byteCount = ReadInt(true); + switch (byteCount) + { + case 0: + return null; + case 1: + return ""; + } + byteCount--; + byte[] buffer = this.chars; + if (buffer.Length < byteCount) buffer = new byte[byteCount]; + ReadFully(buffer, 0, byteCount); + return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount); + } + + ///May be null. + public String ReadStringRef() + { + int index = ReadInt(true); + return index == 0 ? null : strings[index - 1]; + } + + public void ReadFully(byte[] buffer, int offset, int length) + { + while (length > 0) + { + int count = input.Read(buffer, offset, length); + if (count <= 0) throw new EndOfStreamException(); + offset += count; + length -= count; + } + } + + /// Returns the version string of binary skeleton data. + public string GetVersionString() + { + try + { + // try reading 4.0+ format + var initialPosition = input.Position; + ReadLong(); // long hash + + var stringPosition = input.Position; + int stringByteCount = ReadInt(true); + input.Position = stringPosition; + if (stringByteCount <= 13) + { + string version = ReadString(); + if (char.IsDigit(version[0])) + return version; + } + // fallback to old version format + input.Position = initialPosition; + return GetVersionStringOld3X(); + } + catch (Exception e) + { + throw new ArgumentException("Stream does not contain a valid binary Skeleton Data.\n" + e, "input"); + } + } + + /// Returns old 3.8 and earlier format version string of binary skeleton data. + public string GetVersionStringOld3X() + { + // Hash. + int byteCount = ReadInt(true); + if (byteCount > 1) input.Position += byteCount - 1; + + // Version. + byteCount = ReadInt(true); + if (byteCount > 1) + { + byteCount--; + var buffer = new byte[byteCount]; + ReadFully(buffer, 0, byteCount); + return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount); + } + return null; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/SkeletonBounds.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/SkeletonBounds.cs index 2c36f8a..bfc2a09 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/SkeletonBounds.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/SkeletonBounds.cs @@ -29,205 +29,232 @@ using System; -namespace Spine4_0_31 { - - /// - /// 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. - /// - public class SkeletonBounds { - private ExposedList polygonPool = new ExposedList(); - private float minX, minY, maxX, maxY; - - public ExposedList BoundingBoxes { get; private set; } - public ExposedList Polygons { get; private set; } - public float MinX { get { return minX; } set { minX = value; } } - public float MinY { get { return minY; } set { minY = value; } } - public float MaxX { get { return maxX; } set { maxX = value; } } - public float MaxY { get { return maxY; } set { maxY = value; } } - public float Width { get { return maxX - minX; } } - public float Height { get { return maxY - minY; } } - - public SkeletonBounds () { - BoundingBoxes = new ExposedList(); - Polygons = new ExposedList(); - } - - /// - /// Clears any previous polygons, finds all visible bounding box attachments, - /// and computes the world vertices for each bounding box's polygon. - /// The skeleton. - /// - /// If true, the axis aligned bounding box containing all the polygons is computed. - /// If false, the SkeletonBounds AABB methods will always return true. - /// - public void Update (Skeleton skeleton, bool updateAabb) { - ExposedList boundingBoxes = BoundingBoxes; - ExposedList polygons = Polygons; - Slot[] slots = skeleton.slots.Items; - int slotCount = skeleton.slots.Count; - - boundingBoxes.Clear(); - for (int i = 0, n = polygons.Count; i < n; i++) - polygonPool.Add(polygons.Items[i]); - polygons.Clear(); - - for (int i = 0; i < slotCount; i++) { - Slot slot = slots[i]; - if (!slot.bone.active) continue; - BoundingBoxAttachment boundingBox = slot.attachment as BoundingBoxAttachment; - if (boundingBox == null) continue; - boundingBoxes.Add(boundingBox); - - Polygon polygon = null; - int poolCount = polygonPool.Count; - if (poolCount > 0) { - polygon = polygonPool.Items[poolCount - 1]; - polygonPool.RemoveAt(poolCount - 1); - } else - polygon = new Polygon(); - polygons.Add(polygon); - - int count = boundingBox.worldVerticesLength; - polygon.Count = count; - if (polygon.Vertices.Length < count) polygon.Vertices = new float[count]; - boundingBox.ComputeWorldVertices(slot, polygon.Vertices); - } - - if (updateAabb) { - AabbCompute(); - } else { - minX = int.MinValue; - minY = int.MinValue; - maxX = int.MaxValue; - maxY = int.MaxValue; - } - } - - private void AabbCompute () { - float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; - Polygon[] polygons = Polygons.Items; - for (int i = 0, n = Polygons.Count; i < n; i++) { - Polygon polygon = polygons[i]; - float[] vertices = polygon.Vertices; - for (int ii = 0, nn = polygon.Count; ii < nn; ii += 2) { - float x = vertices[ii]; - float y = vertices[ii + 1]; - minX = Math.Min(minX, x); - minY = Math.Min(minY, y); - maxX = Math.Max(maxX, x); - maxY = Math.Max(maxY, y); - } - } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; - } - - /// Returns true if the axis aligned bounding box contains the point. - public bool AabbContainsPoint (float x, float y) { - return x >= minX && x <= maxX && y >= minY && y <= maxY; - } - - /// Returns true if the axis aligned bounding box intersects the line segment. - public bool AabbIntersectsSegment (float x1, float y1, float x2, float y2) { - float minX = this.minX; - float minY = this.minY; - float maxX = this.maxX; - float maxY = this.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; - } - - /// Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds. - public bool AabbIntersectsSkeleton (SkeletonBounds bounds) { - return minX < bounds.maxX && maxX > bounds.minX && minY < bounds.maxY && maxY > bounds.minY; - } - - /// Returns true if the polygon contains the point. - public bool ContainsPoint (Polygon polygon, float x, float y) { - float[] 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; - } - - /// 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 returns true. - public BoundingBoxAttachment ContainsPoint (float x, float y) { - Polygon[] polygons = Polygons.Items; - for (int i = 0, n = Polygons.Count; i < n; i++) - if (ContainsPoint(polygons[i], x, y)) return BoundingBoxes.Items[i]; - return null; - } - - /// 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 returns true. - public BoundingBoxAttachment IntersectsSegment (float x1, float y1, float x2, float y2) { - Polygon[] polygons = Polygons.Items; - for (int i = 0, n = Polygons.Count; i < n; i++) - if (IntersectsSegment(polygons[i], x1, y1, x2, y2)) return BoundingBoxes.Items[i]; - return null; - } - - /// Returns true if the polygon contains the line segment. - public bool IntersectsSegment (Polygon polygon, float x1, float y1, float x2, float y2) { - float[] vertices = polygon.Vertices; - int 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 (int 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; - } - - public Polygon GetPolygon (BoundingBoxAttachment attachment) { - int index = BoundingBoxes.IndexOf(attachment); - return index == -1 ? null : Polygons.Items[index]; - } - } - - public class Polygon { - public float[] Vertices { get; set; } - public int Count { get; set; } - - public Polygon () { - Vertices = new float[16]; - } - } +namespace Spine4_0_31 +{ + + /// + /// 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. + /// + public class SkeletonBounds + { + private ExposedList polygonPool = new ExposedList(); + private float minX, minY, maxX, maxY; + + public ExposedList BoundingBoxes { get; private set; } + public ExposedList Polygons { get; private set; } + public float MinX { get { return minX; } set { minX = value; } } + public float MinY { get { return minY; } set { minY = value; } } + public float MaxX { get { return maxX; } set { maxX = value; } } + public float MaxY { get { return maxY; } set { maxY = value; } } + public float Width { get { return maxX - minX; } } + public float Height { get { return maxY - minY; } } + + public SkeletonBounds() + { + BoundingBoxes = new ExposedList(); + Polygons = new ExposedList(); + } + + /// + /// Clears any previous polygons, finds all visible bounding box attachments, + /// and computes the world vertices for each bounding box's polygon. + /// The skeleton. + /// + /// If true, the axis aligned bounding box containing all the polygons is computed. + /// If false, the SkeletonBounds AABB methods will always return true. + /// + public void Update(Skeleton skeleton, bool updateAabb) + { + ExposedList boundingBoxes = BoundingBoxes; + ExposedList polygons = Polygons; + Slot[] slots = skeleton.slots.Items; + int slotCount = skeleton.slots.Count; + + boundingBoxes.Clear(); + for (int i = 0, n = polygons.Count; i < n; i++) + polygonPool.Add(polygons.Items[i]); + polygons.Clear(); + + for (int i = 0; i < slotCount; i++) + { + Slot slot = slots[i]; + if (!slot.bone.active) continue; + BoundingBoxAttachment boundingBox = slot.attachment as BoundingBoxAttachment; + if (boundingBox == null) continue; + boundingBoxes.Add(boundingBox); + + Polygon polygon = null; + int poolCount = polygonPool.Count; + if (poolCount > 0) + { + polygon = polygonPool.Items[poolCount - 1]; + polygonPool.RemoveAt(poolCount - 1); + } + else + polygon = new Polygon(); + polygons.Add(polygon); + + int count = boundingBox.worldVerticesLength; + polygon.Count = count; + if (polygon.Vertices.Length < count) polygon.Vertices = new float[count]; + boundingBox.ComputeWorldVertices(slot, polygon.Vertices); + } + + if (updateAabb) + { + AabbCompute(); + } + else + { + minX = int.MinValue; + minY = int.MinValue; + maxX = int.MaxValue; + maxY = int.MaxValue; + } + } + + private void AabbCompute() + { + float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; + Polygon[] polygons = Polygons.Items; + for (int i = 0, n = Polygons.Count; i < n; i++) + { + Polygon polygon = polygons[i]; + float[] vertices = polygon.Vertices; + for (int ii = 0, nn = polygon.Count; ii < nn; ii += 2) + { + float x = vertices[ii]; + float y = vertices[ii + 1]; + minX = Math.Min(minX, x); + minY = Math.Min(minY, y); + maxX = Math.Max(maxX, x); + maxY = Math.Max(maxY, y); + } + } + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /// Returns true if the axis aligned bounding box contains the point. + public bool AabbContainsPoint(float x, float y) + { + return x >= minX && x <= maxX && y >= minY && y <= maxY; + } + + /// Returns true if the axis aligned bounding box intersects the line segment. + public bool AabbIntersectsSegment(float x1, float y1, float x2, float y2) + { + float minX = this.minX; + float minY = this.minY; + float maxX = this.maxX; + float maxY = this.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; + } + + /// Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds. + public bool AabbIntersectsSkeleton(SkeletonBounds bounds) + { + return minX < bounds.maxX && maxX > bounds.minX && minY < bounds.maxY && maxY > bounds.minY; + } + + /// Returns true if the polygon contains the point. + public bool ContainsPoint(Polygon polygon, float x, float y) + { + float[] 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; + } + + /// 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 returns true. + public BoundingBoxAttachment ContainsPoint(float x, float y) + { + Polygon[] polygons = Polygons.Items; + for (int i = 0, n = Polygons.Count; i < n; i++) + if (ContainsPoint(polygons[i], x, y)) return BoundingBoxes.Items[i]; + return null; + } + + /// 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 returns true. + public BoundingBoxAttachment IntersectsSegment(float x1, float y1, float x2, float y2) + { + Polygon[] polygons = Polygons.Items; + for (int i = 0, n = Polygons.Count; i < n; i++) + if (IntersectsSegment(polygons[i], x1, y1, x2, y2)) return BoundingBoxes.Items[i]; + return null; + } + + /// Returns true if the polygon contains the line segment. + public bool IntersectsSegment(Polygon polygon, float x1, float y1, float x2, float y2) + { + float[] vertices = polygon.Vertices; + int 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 (int 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; + } + + public Polygon GetPolygon(BoundingBoxAttachment attachment) + { + int index = BoundingBoxes.IndexOf(attachment); + return index == -1 ? null : Polygons.Items[index]; + } + } + + public class Polygon + { + public float[] Vertices { get; set; } + public int Count { get; set; } + + public Polygon() + { + Vertices = new float[16]; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/SkeletonClipping.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/SkeletonClipping.cs index 8e397aa..09775d9 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/SkeletonClipping.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/SkeletonClipping.cs @@ -29,264 +29,300 @@ using System; -namespace Spine4_0_31 { - public class SkeletonClipping { - internal readonly Triangulator triangulator = new Triangulator(); - internal readonly ExposedList clippingPolygon = new ExposedList(); - internal readonly ExposedList clipOutput = new ExposedList(128); - internal readonly ExposedList clippedVertices = new ExposedList(128); - internal readonly ExposedList clippedTriangles = new ExposedList(128); - internal readonly ExposedList clippedUVs = new ExposedList(128); - internal readonly ExposedList scratch = new ExposedList(); - - internal ClippingAttachment clipAttachment; - internal ExposedList> clippingPolygons; - - public ExposedList ClippedVertices { get { return clippedVertices; } } - public ExposedList ClippedTriangles { get { return clippedTriangles; } } - public ExposedList ClippedUVs { get { return clippedUVs; } } - - public bool IsClipping { get { return clipAttachment != null; } } - - public int ClipStart (Slot slot, ClippingAttachment clip) { - if (clipAttachment != null) return 0; - clipAttachment = clip; - - int n = clip.worldVerticesLength; - float[] vertices = clippingPolygon.Resize(n).Items; - clip.ComputeWorldVertices(slot, 0, n, vertices, 0, 2); - MakeClockwise(clippingPolygon); - clippingPolygons = triangulator.Decompose(clippingPolygon, triangulator.Triangulate(clippingPolygon)); - foreach (var polygon in clippingPolygons) { - MakeClockwise(polygon); - polygon.Add(polygon.Items[0]); - polygon.Add(polygon.Items[1]); - } - return clippingPolygons.Count; - } - - public void ClipEnd (Slot slot) { - if (clipAttachment != null && clipAttachment.endSlot == slot.data) ClipEnd(); - } - - public void ClipEnd () { - if (clipAttachment == null) return; - clipAttachment = null; - clippingPolygons = null; - clippedVertices.Clear(); - clippedTriangles.Clear(); - clippingPolygon.Clear(); - } - - public void ClipTriangles (float[] vertices, int verticesLength, int[] triangles, int trianglesLength, float[] uvs) { - ExposedList clipOutput = this.clipOutput, clippedVertices = this.clippedVertices; - var clippedTriangles = this.clippedTriangles; - var polygons = clippingPolygons.Items; - int polygonsCount = clippingPolygons.Count; - - int index = 0; - clippedVertices.Clear(); - clippedUVs.Clear(); - clippedTriangles.Clear(); - //outer: - for (int i = 0; i < trianglesLength; i += 3) { - int vertexOffset = triangles[i] << 1; - float x1 = vertices[vertexOffset], y1 = vertices[vertexOffset + 1]; - float u1 = uvs[vertexOffset], v1 = uvs[vertexOffset + 1]; - - vertexOffset = triangles[i + 1] << 1; - float x2 = vertices[vertexOffset], y2 = vertices[vertexOffset + 1]; - float u2 = uvs[vertexOffset], v2 = uvs[vertexOffset + 1]; - - vertexOffset = triangles[i + 2] << 1; - float x3 = vertices[vertexOffset], y3 = vertices[vertexOffset + 1]; - float u3 = uvs[vertexOffset], v3 = uvs[vertexOffset + 1]; - - for (int p = 0; p < polygonsCount; p++) { - int s = clippedVertices.Count; - if (Clip(x1, y1, x2, y2, x3, y3, polygons[p], clipOutput)) { - int clipOutputLength = clipOutput.Count; - 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)); - - int clipOutputCount = clipOutputLength >> 1; - float[] clipOutputItems = clipOutput.Items; - float[] clippedVerticesItems = clippedVertices.Resize(s + clipOutputCount * 2).Items; - float[] clippedUVsItems = clippedUVs.Resize(s + clipOutputCount * 2).Items; - for (int ii = 0; ii < clipOutputLength; ii += 2) { - float x = clipOutputItems[ii], y = clipOutputItems[ii + 1]; - clippedVerticesItems[s] = x; - clippedVerticesItems[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; - clippedUVsItems[s] = u1 * a + u2 * b + u3 * c; - clippedUVsItems[s + 1] = v1 * a + v2 * b + v3 * c; - s += 2; - } - - s = clippedTriangles.Count; - int[] clippedTrianglesItems = clippedTriangles.Resize(s + 3 * (clipOutputCount - 2)).Items; - clipOutputCount--; - for (int ii = 1; ii < clipOutputCount; ii++) { - clippedTrianglesItems[s] = index; - clippedTrianglesItems[s + 1] = index + ii; - clippedTrianglesItems[s + 2] = index + ii + 1; - s += 3; - } - index += clipOutputCount + 1; - } else { - float[] clippedVerticesItems = clippedVertices.Resize(s + 3 * 2).Items; - float[] clippedUVsItems = clippedUVs.Resize(s + 3 * 2).Items; - clippedVerticesItems[s] = x1; - clippedVerticesItems[s + 1] = y1; - clippedVerticesItems[s + 2] = x2; - clippedVerticesItems[s + 3] = y2; - clippedVerticesItems[s + 4] = x3; - clippedVerticesItems[s + 5] = y3; - - clippedUVsItems[s] = u1; - clippedUVsItems[s + 1] = v1; - clippedUVsItems[s + 2] = u2; - clippedUVsItems[s + 3] = v2; - clippedUVsItems[s + 4] = u3; - clippedUVsItems[s + 5] = v3; - - s = clippedTriangles.Count; - int[] clippedTrianglesItems = clippedTriangles.Resize(s + 3).Items; - clippedTrianglesItems[s] = index; - clippedTrianglesItems[s + 1] = index + 1; - clippedTrianglesItems[s + 2] = index + 2; - index += 3; - break; //continue outer; - } - } - } - - } - - /** Clips the input triangle against the convex, clockwise clipping area. If the triangle lies entirely within the clipping +namespace Spine4_0_31 +{ + public class SkeletonClipping + { + internal readonly Triangulator triangulator = new Triangulator(); + internal readonly ExposedList clippingPolygon = new ExposedList(); + internal readonly ExposedList clipOutput = new ExposedList(128); + internal readonly ExposedList clippedVertices = new ExposedList(128); + internal readonly ExposedList clippedTriangles = new ExposedList(128); + internal readonly ExposedList clippedUVs = new ExposedList(128); + internal readonly ExposedList scratch = new ExposedList(); + + internal ClippingAttachment clipAttachment; + internal ExposedList> clippingPolygons; + + public ExposedList ClippedVertices { get { return clippedVertices; } } + public ExposedList ClippedTriangles { get { return clippedTriangles; } } + public ExposedList ClippedUVs { get { return clippedUVs; } } + + public bool IsClipping { get { return clipAttachment != null; } } + + public int ClipStart(Slot slot, ClippingAttachment clip) + { + if (clipAttachment != null) return 0; + clipAttachment = clip; + + int n = clip.worldVerticesLength; + float[] vertices = clippingPolygon.Resize(n).Items; + clip.ComputeWorldVertices(slot, 0, n, vertices, 0, 2); + MakeClockwise(clippingPolygon); + clippingPolygons = triangulator.Decompose(clippingPolygon, triangulator.Triangulate(clippingPolygon)); + foreach (var polygon in clippingPolygons) + { + MakeClockwise(polygon); + polygon.Add(polygon.Items[0]); + polygon.Add(polygon.Items[1]); + } + return clippingPolygons.Count; + } + + public void ClipEnd(Slot slot) + { + if (clipAttachment != null && clipAttachment.endSlot == slot.data) ClipEnd(); + } + + public void ClipEnd() + { + if (clipAttachment == null) return; + clipAttachment = null; + clippingPolygons = null; + clippedVertices.Clear(); + clippedTriangles.Clear(); + clippingPolygon.Clear(); + } + + public void ClipTriangles(float[] vertices, int verticesLength, int[] triangles, int trianglesLength, float[] uvs) + { + ExposedList clipOutput = this.clipOutput, clippedVertices = this.clippedVertices; + var clippedTriangles = this.clippedTriangles; + var polygons = clippingPolygons.Items; + int polygonsCount = clippingPolygons.Count; + + int index = 0; + clippedVertices.Clear(); + clippedUVs.Clear(); + clippedTriangles.Clear(); + //outer: + for (int i = 0; i < trianglesLength; i += 3) + { + int vertexOffset = triangles[i] << 1; + float x1 = vertices[vertexOffset], y1 = vertices[vertexOffset + 1]; + float u1 = uvs[vertexOffset], v1 = uvs[vertexOffset + 1]; + + vertexOffset = triangles[i + 1] << 1; + float x2 = vertices[vertexOffset], y2 = vertices[vertexOffset + 1]; + float u2 = uvs[vertexOffset], v2 = uvs[vertexOffset + 1]; + + vertexOffset = triangles[i + 2] << 1; + float x3 = vertices[vertexOffset], y3 = vertices[vertexOffset + 1]; + float u3 = uvs[vertexOffset], v3 = uvs[vertexOffset + 1]; + + for (int p = 0; p < polygonsCount; p++) + { + int s = clippedVertices.Count; + if (Clip(x1, y1, x2, y2, x3, y3, polygons[p], clipOutput)) + { + int clipOutputLength = clipOutput.Count; + 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)); + + int clipOutputCount = clipOutputLength >> 1; + float[] clipOutputItems = clipOutput.Items; + float[] clippedVerticesItems = clippedVertices.Resize(s + clipOutputCount * 2).Items; + float[] clippedUVsItems = clippedUVs.Resize(s + clipOutputCount * 2).Items; + for (int ii = 0; ii < clipOutputLength; ii += 2) + { + float x = clipOutputItems[ii], y = clipOutputItems[ii + 1]; + clippedVerticesItems[s] = x; + clippedVerticesItems[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; + clippedUVsItems[s] = u1 * a + u2 * b + u3 * c; + clippedUVsItems[s + 1] = v1 * a + v2 * b + v3 * c; + s += 2; + } + + s = clippedTriangles.Count; + int[] clippedTrianglesItems = clippedTriangles.Resize(s + 3 * (clipOutputCount - 2)).Items; + clipOutputCount--; + for (int ii = 1; ii < clipOutputCount; ii++) + { + clippedTrianglesItems[s] = index; + clippedTrianglesItems[s + 1] = index + ii; + clippedTrianglesItems[s + 2] = index + ii + 1; + s += 3; + } + index += clipOutputCount + 1; + } + else + { + float[] clippedVerticesItems = clippedVertices.Resize(s + 3 * 2).Items; + float[] clippedUVsItems = clippedUVs.Resize(s + 3 * 2).Items; + clippedVerticesItems[s] = x1; + clippedVerticesItems[s + 1] = y1; + clippedVerticesItems[s + 2] = x2; + clippedVerticesItems[s + 3] = y2; + clippedVerticesItems[s + 4] = x3; + clippedVerticesItems[s + 5] = y3; + + clippedUVsItems[s] = u1; + clippedUVsItems[s + 1] = v1; + clippedUVsItems[s + 2] = u2; + clippedUVsItems[s + 3] = v2; + clippedUVsItems[s + 4] = u3; + clippedUVsItems[s + 5] = v3; + + s = clippedTriangles.Count; + int[] clippedTrianglesItems = clippedTriangles.Resize(s + 3).Items; + clippedTrianglesItems[s] = index; + clippedTrianglesItems[s + 1] = index + 1; + clippedTrianglesItems[s + 2] = index + 2; + index += 3; + break; //continue outer; + } + } + } + + } + + /** 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. */ - internal bool Clip (float x1, float y1, float x2, float y2, float x3, float y3, ExposedList clippingArea, ExposedList output) { - var originalOutput = output; - var clipped = false; - - // Avoid copy at the end. - ExposedList input = null; - if (clippingArea.Count % 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(); - - float[] clippingVertices = clippingArea.Items; - int clippingVerticesLast = clippingArea.Count - 4; - for (int i = 0; ; i += 2) { - float edgeX = clippingVertices[i], edgeY = clippingVertices[i + 1]; - float edgeX2 = clippingVertices[i + 2], edgeY2 = clippingVertices[i + 3]; - float deltaX = edgeX - edgeX2, deltaY = edgeY - edgeY2; - - float[] inputVertices = input.Items; - int inputVerticesLength = input.Count - 2, outputStart = output.Count; - for (int ii = 0; ii < inputVerticesLength; ii += 2) { - float inputX = inputVertices[ii], inputY = inputVertices[ii + 1]; - float inputX2 = inputVertices[ii + 2], inputY2 = inputVertices[ii + 3]; - bool side2 = deltaX * (inputY2 - edgeY2) - deltaY * (inputX2 - edgeX2) > 0; - if (deltaX * (inputY - edgeY2) - deltaY * (inputX - edgeX2) > 0) { - if (side2) { // v1 inside, v2 inside - output.Add(inputX2); - output.Add(inputY2); - continue; - } - // v1 inside, v2 outside - float c0 = inputY2 - inputY, c2 = inputX2 - inputX; - float s = c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY); - if (Math.Abs(s) > 0.000001f) { - float ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / s; - output.Add(edgeX + (edgeX2 - edgeX) * ua); - output.Add(edgeY + (edgeY2 - edgeY) * ua); - } else { - output.Add(edgeX); - output.Add(edgeY); - } - } else if (side2) { // v1 outside, v2 inside - float c0 = inputY2 - inputY, c2 = inputX2 - inputX; - float s = c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY); - if (Math.Abs(s) > 0.000001f) { - float ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / s; - output.Add(edgeX + (edgeX2 - edgeX) * ua); - output.Add(edgeY + (edgeY2 - edgeY) * ua); - } else { - output.Add(edgeX); - output.Add(edgeY); - } - output.Add(inputX2); - output.Add(inputY2); - } - clipped = true; - } - - if (outputStart == output.Count) { // All edges outside. - originalOutput.Clear(); - return true; - } - - output.Add(output.Items[0]); - output.Add(output.Items[1]); - - if (i == clippingVerticesLast) break; - var temp = output; - output = input; - output.Clear(); - input = temp; - } - - if (originalOutput != output) { - originalOutput.Clear(); - for (int i = 0, n = output.Count - 2; i < n; i++) - originalOutput.Add(output.Items[i]); - } else - originalOutput.Resize(originalOutput.Count - 2); - - return clipped; - } - - public static void MakeClockwise (ExposedList polygon) { - float[] vertices = polygon.Items; - int verticeslength = polygon.Count; - - float area = vertices[verticeslength - 2] * vertices[1] - vertices[0] * vertices[verticeslength - 1], p1x, p1y, p2x, p2y; - for (int i = 0, n = verticeslength - 3; i < n; i += 2) { - p1x = vertices[i]; - p1y = vertices[i + 1]; - p2x = vertices[i + 2]; - p2y = vertices[i + 3]; - area += p1x * p2y - p2x * p1y; - } - if (area < 0) return; - - for (int i = 0, lastX = verticeslength - 2, n = verticeslength >> 1; i < n; i += 2) { - float x = vertices[i], y = vertices[i + 1]; - int other = lastX - i; - vertices[i] = vertices[other]; - vertices[i + 1] = vertices[other + 1]; - vertices[other] = x; - vertices[other + 1] = y; - } - } - } + internal bool Clip(float x1, float y1, float x2, float y2, float x3, float y3, ExposedList clippingArea, ExposedList output) + { + var originalOutput = output; + var clipped = false; + + // Avoid copy at the end. + ExposedList input = null; + if (clippingArea.Count % 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(); + + float[] clippingVertices = clippingArea.Items; + int clippingVerticesLast = clippingArea.Count - 4; + for (int i = 0; ; i += 2) + { + float edgeX = clippingVertices[i], edgeY = clippingVertices[i + 1]; + float edgeX2 = clippingVertices[i + 2], edgeY2 = clippingVertices[i + 3]; + float deltaX = edgeX - edgeX2, deltaY = edgeY - edgeY2; + + float[] inputVertices = input.Items; + int inputVerticesLength = input.Count - 2, outputStart = output.Count; + for (int ii = 0; ii < inputVerticesLength; ii += 2) + { + float inputX = inputVertices[ii], inputY = inputVertices[ii + 1]; + float inputX2 = inputVertices[ii + 2], inputY2 = inputVertices[ii + 3]; + bool side2 = deltaX * (inputY2 - edgeY2) - deltaY * (inputX2 - edgeX2) > 0; + if (deltaX * (inputY - edgeY2) - deltaY * (inputX - edgeX2) > 0) + { + if (side2) + { // v1 inside, v2 inside + output.Add(inputX2); + output.Add(inputY2); + continue; + } + // v1 inside, v2 outside + float c0 = inputY2 - inputY, c2 = inputX2 - inputX; + float s = c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY); + if (Math.Abs(s) > 0.000001f) + { + float ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / s; + output.Add(edgeX + (edgeX2 - edgeX) * ua); + output.Add(edgeY + (edgeY2 - edgeY) * ua); + } + else + { + output.Add(edgeX); + output.Add(edgeY); + } + } + else if (side2) + { // v1 outside, v2 inside + float c0 = inputY2 - inputY, c2 = inputX2 - inputX; + float s = c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY); + if (Math.Abs(s) > 0.000001f) + { + float ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / s; + output.Add(edgeX + (edgeX2 - edgeX) * ua); + output.Add(edgeY + (edgeY2 - edgeY) * ua); + } + else + { + output.Add(edgeX); + output.Add(edgeY); + } + output.Add(inputX2); + output.Add(inputY2); + } + clipped = true; + } + + if (outputStart == output.Count) + { // All edges outside. + originalOutput.Clear(); + return true; + } + + output.Add(output.Items[0]); + output.Add(output.Items[1]); + + if (i == clippingVerticesLast) break; + var temp = output; + output = input; + output.Clear(); + input = temp; + } + + if (originalOutput != output) + { + originalOutput.Clear(); + for (int i = 0, n = output.Count - 2; i < n; i++) + originalOutput.Add(output.Items[i]); + } + else + originalOutput.Resize(originalOutput.Count - 2); + + return clipped; + } + + public static void MakeClockwise(ExposedList polygon) + { + float[] vertices = polygon.Items; + int verticeslength = polygon.Count; + + float area = vertices[verticeslength - 2] * vertices[1] - vertices[0] * vertices[verticeslength - 1], p1x, p1y, p2x, p2y; + for (int i = 0, n = verticeslength - 3; i < n; i += 2) + { + p1x = vertices[i]; + p1y = vertices[i + 1]; + p2x = vertices[i + 2]; + p2y = vertices[i + 3]; + area += p1x * p2y - p2x * p1y; + } + if (area < 0) return; + + for (int i = 0, lastX = verticeslength - 2, n = verticeslength >> 1; i < n; i += 2) + { + float x = vertices[i], y = vertices[i + 1]; + int other = lastX - i; + vertices[i] = vertices[other]; + vertices[i + 1] = vertices[other + 1]; + vertices[other] = x; + vertices[other + 1] = y; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/SkeletonData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/SkeletonData.cs index bb0b73e..eb147b0 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/SkeletonData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/SkeletonData.cs @@ -29,177 +29,194 @@ using System; -namespace Spine4_0_31 { - - /// Stores the setup pose and all of the stateless data for a skeleton. - public class SkeletonData { - internal string name; - internal ExposedList bones = new ExposedList(); // Ordered parents first - internal ExposedList slots = new ExposedList(); // Setup pose draw order. - internal ExposedList skins = new ExposedList(); - internal Skin defaultSkin; - internal ExposedList events = new ExposedList(); - internal ExposedList animations = new ExposedList(); - internal ExposedList ikConstraints = new ExposedList(); - internal ExposedList transformConstraints = new ExposedList(); - internal ExposedList pathConstraints = new ExposedList(); - internal float x, y, width, height; - internal string version, hash; - - // Nonessential. - internal float fps; - internal string imagesPath, audioPath; - - ///The skeleton's name, which by default is the name of the skeleton data file when possible, or null when a name hasn't been - ///set. - public string Name { get { return name; } set { name = value; } } - - /// The skeleton's bones, sorted parent first. The root bone is always the first bone. - public ExposedList Bones { get { return bones; } } - - public ExposedList Slots { get { return slots; } } - - /// All skins, including the default skin. - public ExposedList Skins { get { return skins; } set { skins = value; } } - - /// - /// The skeleton's default skin. - /// By default this skin contains all attachments that were not in a skin in Spine. - /// - /// May be null. - public Skin DefaultSkin { get { return defaultSkin; } set { defaultSkin = value; } } - - public ExposedList Events { get { return events; } set { events = value; } } - public ExposedList Animations { get { return animations; } set { animations = value; } } - public ExposedList IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } } - public ExposedList TransformConstraints { get { return transformConstraints; } set { transformConstraints = value; } } - public ExposedList PathConstraints { get { return pathConstraints; } set { pathConstraints = value; } } - - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public float Width { get { return width; } set { width = value; } } - public float Height { get { return height; } set { height = value; } } - /// The Spine version used to export this data, or null. - public string Version { get { return version; } set { version = value; } } - - ///The skeleton data hash. This value will change if any of the skeleton data has changed. - ///May be null. - public string Hash { get { return hash; } set { hash = value; } } - - public string ImagesPath { get { return imagesPath; } set { imagesPath = value; } } - - /// The path to the audio directory as defined in Spine. Available only when nonessential data was exported. - /// May be null. - public string AudioPath { get { return audioPath; } set { audioPath = value; } } - - /// The dopesheet FPS in Spine, or zero if nonessential data was not exported. - public float Fps { get { return fps; } set { fps = value; } } - - // --- Bones. - - /// - /// 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. - /// May be null. - public BoneData FindBone (string boneName) { - if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); - var bones = this.bones.Items; - for (int i = 0, n = this.bones.Count; i < n; i++) { - BoneData bone = bones[i]; - if (bone.name == boneName) return bone; - } - return null; - } - - // --- Slots. - - /// May be null. - public SlotData FindSlot (string slotName) { - if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); - var slots = this.slots.Items; - for (int i = 0, n = this.slots.Count; i < n; i++) { - SlotData slot = slots[i]; - if (slot.name == slotName) return slot; - } - return null; - } - - // --- Skins. - - /// May be null. - public Skin FindSkin (string skinName) { - if (skinName == null) throw new ArgumentNullException("skinName", "skinName cannot be null."); - foreach (Skin skin in skins) - if (skin.name == skinName) return skin; - return null; - } - - // --- Events. - - /// May be null. - public EventData FindEvent (string eventDataName) { - if (eventDataName == null) throw new ArgumentNullException("eventDataName", "eventDataName cannot be null."); - foreach (EventData eventData in events) - if (eventData.name == eventDataName) return eventData; - return null; - } - - // --- Animations. - - /// May be null. - public Animation FindAnimation (string animationName) { - if (animationName == null) throw new ArgumentNullException("animationName", "animationName cannot be null."); - var animations = this.animations.Items; - for (int i = 0, n = this.animations.Count; i < n; i++) { - Animation animation = animations[i]; - if (animation.name == animationName) return animation; - } - return null; - } - - // --- IK constraints. - - /// May be null. - public IkConstraintData FindIkConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - var ikConstraints = this.ikConstraints.Items; - for (int i = 0, n = this.ikConstraints.Count; i < n; i++) { - IkConstraintData ikConstraint = ikConstraints[i]; - if (ikConstraint.name == constraintName) return ikConstraint; - } - return null; - } - - // --- Transform constraints. - - /// May be null. - public TransformConstraintData FindTransformConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - var transformConstraints = this.transformConstraints.Items; - for (int i = 0, n = this.transformConstraints.Count; i < n; i++) { - TransformConstraintData transformConstraint = transformConstraints[i]; - if (transformConstraint.name == constraintName) return transformConstraint; - } - return null; - } - - // --- Path constraints. - - /// May be null. - public PathConstraintData FindPathConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - var pathConstraints = this.pathConstraints.Items; - for (int i = 0, n = this.pathConstraints.Count; i < n; i++) { - PathConstraintData constraint = pathConstraints[i]; - if (constraint.name.Equals(constraintName)) return constraint; - } - return null; - } - - // --- - - override public string ToString () { - return name ?? base.ToString(); - } - } +namespace Spine4_0_31 +{ + + /// Stores the setup pose and all of the stateless data for a skeleton. + public class SkeletonData + { + internal string name; + internal ExposedList bones = new ExposedList(); // Ordered parents first + internal ExposedList slots = new ExposedList(); // Setup pose draw order. + internal ExposedList skins = new ExposedList(); + internal Skin defaultSkin; + internal ExposedList events = new ExposedList(); + internal ExposedList animations = new ExposedList(); + internal ExposedList ikConstraints = new ExposedList(); + internal ExposedList transformConstraints = new ExposedList(); + internal ExposedList pathConstraints = new ExposedList(); + internal float x, y, width, height; + internal string version, hash; + + // Nonessential. + internal float fps; + internal string imagesPath, audioPath; + + ///The skeleton's name, which by default is the name of the skeleton data file when possible, or null when a name hasn't been + ///set. + public string Name { get { return name; } set { name = value; } } + + /// The skeleton's bones, sorted parent first. The root bone is always the first bone. + public ExposedList Bones { get { return bones; } } + + public ExposedList Slots { get { return slots; } } + + /// All skins, including the default skin. + public ExposedList Skins { get { return skins; } set { skins = value; } } + + /// + /// The skeleton's default skin. + /// By default this skin contains all attachments that were not in a skin in Spine. + /// + /// May be null. + public Skin DefaultSkin { get { return defaultSkin; } set { defaultSkin = value; } } + + public ExposedList Events { get { return events; } set { events = value; } } + public ExposedList Animations { get { return animations; } set { animations = value; } } + public ExposedList IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } } + public ExposedList TransformConstraints { get { return transformConstraints; } set { transformConstraints = value; } } + public ExposedList PathConstraints { get { return pathConstraints; } set { pathConstraints = value; } } + + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public float Width { get { return width; } set { width = value; } } + public float Height { get { return height; } set { height = value; } } + /// The Spine version used to export this data, or null. + public string Version { get { return version; } set { version = value; } } + + ///The skeleton data hash. This value will change if any of the skeleton data has changed. + ///May be null. + public string Hash { get { return hash; } set { hash = value; } } + + public string ImagesPath { get { return imagesPath; } set { imagesPath = value; } } + + /// The path to the audio directory as defined in Spine. Available only when nonessential data was exported. + /// May be null. + public string AudioPath { get { return audioPath; } set { audioPath = value; } } + + /// The dopesheet FPS in Spine, or zero if nonessential data was not exported. + public float Fps { get { return fps; } set { fps = value; } } + + // --- Bones. + + /// + /// 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. + /// May be null. + public BoneData FindBone(string boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); + var bones = this.bones.Items; + for (int i = 0, n = this.bones.Count; i < n; i++) + { + BoneData bone = bones[i]; + if (bone.name == boneName) return bone; + } + return null; + } + + // --- Slots. + + /// May be null. + public SlotData FindSlot(string slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + var slots = this.slots.Items; + for (int i = 0, n = this.slots.Count; i < n; i++) + { + SlotData slot = slots[i]; + if (slot.name == slotName) return slot; + } + return null; + } + + // --- Skins. + + /// May be null. + public Skin FindSkin(string skinName) + { + if (skinName == null) throw new ArgumentNullException("skinName", "skinName cannot be null."); + foreach (Skin skin in skins) + if (skin.name == skinName) return skin; + return null; + } + + // --- Events. + + /// May be null. + public EventData FindEvent(string eventDataName) + { + if (eventDataName == null) throw new ArgumentNullException("eventDataName", "eventDataName cannot be null."); + foreach (EventData eventData in events) + if (eventData.name == eventDataName) return eventData; + return null; + } + + // --- Animations. + + /// May be null. + public Animation FindAnimation(string animationName) + { + if (animationName == null) throw new ArgumentNullException("animationName", "animationName cannot be null."); + var animations = this.animations.Items; + for (int i = 0, n = this.animations.Count; i < n; i++) + { + Animation animation = animations[i]; + if (animation.name == animationName) return animation; + } + return null; + } + + // --- IK constraints. + + /// May be null. + public IkConstraintData FindIkConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + var ikConstraints = this.ikConstraints.Items; + for (int i = 0, n = this.ikConstraints.Count; i < n; i++) + { + IkConstraintData ikConstraint = ikConstraints[i]; + if (ikConstraint.name == constraintName) return ikConstraint; + } + return null; + } + + // --- Transform constraints. + + /// May be null. + public TransformConstraintData FindTransformConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + var transformConstraints = this.transformConstraints.Items; + for (int i = 0, n = this.transformConstraints.Count; i < n; i++) + { + TransformConstraintData transformConstraint = transformConstraints[i]; + if (transformConstraint.name == constraintName) return transformConstraint; + } + return null; + } + + // --- Path constraints. + + /// May be null. + public PathConstraintData FindPathConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + var pathConstraints = this.pathConstraints.Items; + for (int i = 0, n = this.pathConstraints.Count; i < n; i++) + { + PathConstraintData constraint = pathConstraints[i]; + if (constraint.name.Equals(constraintName)) return constraint; + } + return null; + } + + // --- + + override public string ToString() + { + return name ?? base.ToString(); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/SkeletonJson.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/SkeletonJson.cs index f8b3434..5449d42 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/SkeletonJson.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/SkeletonJson.cs @@ -40,26 +40,30 @@ using Windows.Storage; #endif -namespace Spine4_0_31 { - - /// - /// Loads skeleton data in the Spine JSON format. - /// - /// JSON is human readable but the binary format is much smaller on disk and faster to load. See . - /// - /// See Spine JSON format and - /// JSON and binary data in the Spine - /// Runtimes Guide. - /// - public class SkeletonJson : SkeletonLoader { - - public SkeletonJson (AttachmentLoader attachmentLoader) - : base(attachmentLoader) { - } - - public SkeletonJson (params Atlas[] atlasArray) - : base(atlasArray) { - } +namespace Spine4_0_31 +{ + + /// + /// Loads skeleton data in the Spine JSON format. + /// + /// JSON is human readable but the binary format is much smaller on disk and faster to load. See . + /// + /// See Spine JSON format and + /// JSON and binary data in the Spine + /// Runtimes Guide. + /// + public class SkeletonJson : SkeletonLoader + { + + public SkeletonJson(AttachmentLoader attachmentLoader) + : base(attachmentLoader) + { + } + + public SkeletonJson(params Atlas[] atlasArray) + : base(atlasArray) + { + } #if !IS_UNITY && WINDOWS_STOREAPP private async Task ReadFile(string path) { @@ -76,1118 +80,1289 @@ public override SkeletonData ReadSkeletonData (string path) { return this.ReadFile(path).Result; } #else - public override SkeletonData ReadSkeletonData (string path) { + public override SkeletonData ReadSkeletonData(string path) + { #if WINDOWS_PHONE using (var reader = new StreamReader(Microsoft.Xna.Framework.TitleContainer.OpenStream(path))) { #else - using (var reader = new StreamReader(new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))) { + using (var reader = new StreamReader(new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))) + { #endif - SkeletonData skeletonData = ReadSkeletonData(reader); - skeletonData.name = Path.GetFileNameWithoutExtension(path); - return skeletonData; - } - } + SkeletonData skeletonData = ReadSkeletonData(reader); + skeletonData.name = Path.GetFileNameWithoutExtension(path); + return skeletonData; + } + } #endif - public SkeletonData ReadSkeletonData (TextReader reader) { - if (reader == null) throw new ArgumentNullException("reader", "reader cannot be null."); - - float scale = this.scale; - var skeletonData = new SkeletonData(); - - var root = Json.Deserialize(reader) as Dictionary; - if (root == null) throw new Exception("Invalid JSON."); - - // Skeleton. - if (root.ContainsKey("skeleton")) { - var skeletonMap = (Dictionary)root["skeleton"]; - skeletonData.hash = (string)skeletonMap["hash"]; - skeletonData.version = (string)skeletonMap["spine"]; - skeletonData.x = GetFloat(skeletonMap, "x", 0); - skeletonData.y = GetFloat(skeletonMap, "y", 0); - skeletonData.width = GetFloat(skeletonMap, "width", 0); - skeletonData.height = GetFloat(skeletonMap, "height", 0); - skeletonData.fps = GetFloat(skeletonMap, "fps", 30); - skeletonData.imagesPath = GetString(skeletonMap, "images", null); - skeletonData.audioPath = GetString(skeletonMap, "audio", null); - } - - // Bones. - if (root.ContainsKey("bones")) { - foreach (Dictionary boneMap in (List)root["bones"]) { - BoneData parent = null; - if (boneMap.ContainsKey("parent")) { - parent = skeletonData.FindBone((string)boneMap["parent"]); - if (parent == null) - throw new Exception("Parent bone not found: " + boneMap["parent"]); - } - var data = new BoneData(skeletonData.Bones.Count, (string)boneMap["name"], parent); - data.length = GetFloat(boneMap, "length", 0) * scale; - data.x = GetFloat(boneMap, "x", 0) * scale; - data.y = GetFloat(boneMap, "y", 0) * scale; - data.rotation = GetFloat(boneMap, "rotation", 0); - data.scaleX = GetFloat(boneMap, "scaleX", 1); - data.scaleY = GetFloat(boneMap, "scaleY", 1); - data.shearX = GetFloat(boneMap, "shearX", 0); - data.shearY = GetFloat(boneMap, "shearY", 0); - - string tm = GetString(boneMap, "transform", TransformMode.Normal.ToString()); - data.transformMode = (TransformMode)Enum.Parse(typeof(TransformMode), tm, true); - data.skinRequired = GetBoolean(boneMap, "skin", false); - - skeletonData.bones.Add(data); - } - } - - // Slots. - if (root.ContainsKey("slots")) { - foreach (Dictionary slotMap in (List)root["slots"]) { - var slotName = (string)slotMap["name"]; - var boneName = (string)slotMap["bone"]; - BoneData boneData = skeletonData.FindBone(boneName); - if (boneData == null) throw new Exception("Slot bone not found: " + boneName); - var data = new SlotData(skeletonData.Slots.Count, slotName, boneData); - - if (slotMap.ContainsKey("color")) { - string color = (string)slotMap["color"]; - data.r = ToColor(color, 0); - data.g = ToColor(color, 1); - data.b = ToColor(color, 2); - data.a = ToColor(color, 3); - } - - if (slotMap.ContainsKey("dark")) { - var color2 = (string)slotMap["dark"]; - data.r2 = ToColor(color2, 0, 6); // expectedLength = 6. ie. "RRGGBB" - data.g2 = ToColor(color2, 1, 6); - data.b2 = ToColor(color2, 2, 6); - data.hasSecondColor = true; - } - - data.attachmentName = GetString(slotMap, "attachment", null); - if (slotMap.ContainsKey("blend")) - data.blendMode = (BlendMode)Enum.Parse(typeof(BlendMode), (string)slotMap["blend"], true); - else - data.blendMode = BlendMode.Normal; - skeletonData.slots.Add(data); - } - } - - // IK constraints. - if (root.ContainsKey("ik")) { - foreach (Dictionary constraintMap in (List)root["ik"]) { - IkConstraintData data = new IkConstraintData((string)constraintMap["name"]); - data.order = GetInt(constraintMap, "order", 0); - data.skinRequired = GetBoolean(constraintMap, "skin", false); - - if (constraintMap.ContainsKey("bones")) { - foreach (string boneName in (List)constraintMap["bones"]) { - BoneData bone = skeletonData.FindBone(boneName); - if (bone == null) throw new Exception("IK bone not found: " + boneName); - data.bones.Add(bone); - } - } - - string targetName = (string)constraintMap["target"]; - data.target = skeletonData.FindBone(targetName); - if (data.target == null) throw new Exception("IK target bone not found: " + targetName); - data.mix = GetFloat(constraintMap, "mix", 1); - data.softness = GetFloat(constraintMap, "softness", 0) * scale; - data.bendDirection = GetBoolean(constraintMap, "bendPositive", true) ? 1 : -1; - data.compress = GetBoolean(constraintMap, "compress", false); - data.stretch = GetBoolean(constraintMap, "stretch", false); - data.uniform = GetBoolean(constraintMap, "uniform", false); - - skeletonData.ikConstraints.Add(data); - } - } - - // Transform constraints. - if (root.ContainsKey("transform")) { - foreach (Dictionary constraintMap in (List)root["transform"]) { - TransformConstraintData data = new TransformConstraintData((string)constraintMap["name"]); - data.order = GetInt(constraintMap, "order", 0); - data.skinRequired = GetBoolean(constraintMap, "skin", false); - - if (constraintMap.ContainsKey("bones")) { - foreach (string boneName in (List)constraintMap["bones"]) { - BoneData bone = skeletonData.FindBone(boneName); - if (bone == null) throw new Exception("Transform constraint bone not found: " + boneName); - data.bones.Add(bone); - } - } - - string targetName = (string)constraintMap["target"]; - data.target = skeletonData.FindBone(targetName); - if (data.target == null) throw new Exception("Transform constraint target bone not found: " + targetName); - - data.local = GetBoolean(constraintMap, "local", false); - data.relative = GetBoolean(constraintMap, "relative", false); - - data.offsetRotation = GetFloat(constraintMap, "rotation", 0); - data.offsetX = GetFloat(constraintMap, "x", 0) * scale; - data.offsetY = GetFloat(constraintMap, "y", 0) * scale; - data.offsetScaleX = GetFloat(constraintMap, "scaleX", 0); - data.offsetScaleY = GetFloat(constraintMap, "scaleY", 0); - data.offsetShearY = GetFloat(constraintMap, "shearY", 0); - - data.mixRotate = GetFloat(constraintMap, "mixRotate", 1); - data.mixX = GetFloat(constraintMap, "mixX", 1); - data.mixY = GetFloat(constraintMap, "mixY", data.mixX); - data.mixScaleX = GetFloat(constraintMap, "mixScaleX", 1); - data.mixScaleY = GetFloat(constraintMap, "mixScaleY", data.mixScaleX); - data.mixShearY = GetFloat(constraintMap, "mixShearY", 1); - - skeletonData.transformConstraints.Add(data); - } - } - - // Path constraints. - if (root.ContainsKey("path")) { - foreach (Dictionary constraintMap in (List)root["path"]) { - PathConstraintData data = new PathConstraintData((string)constraintMap["name"]); - data.order = GetInt(constraintMap, "order", 0); - data.skinRequired = GetBoolean(constraintMap, "skin", false); - - if (constraintMap.ContainsKey("bones")) { - foreach (string boneName in (List)constraintMap["bones"]) { - BoneData bone = skeletonData.FindBone(boneName); - if (bone == null) throw new Exception("Path bone not found: " + boneName); - data.bones.Add(bone); - } - } - - string targetName = (string)constraintMap["target"]; - data.target = skeletonData.FindSlot(targetName); - if (data.target == null) throw new Exception("Path target slot not found: " + targetName); - - data.positionMode = (PositionMode)Enum.Parse(typeof(PositionMode), GetString(constraintMap, "positionMode", "percent"), true); - data.spacingMode = (SpacingMode)Enum.Parse(typeof(SpacingMode), GetString(constraintMap, "spacingMode", "length"), true); - data.rotateMode = (RotateMode)Enum.Parse(typeof(RotateMode), GetString(constraintMap, "rotateMode", "tangent"), true); - data.offsetRotation = GetFloat(constraintMap, "rotation", 0); - data.position = GetFloat(constraintMap, "position", 0); - if (data.positionMode == PositionMode.Fixed) data.position *= scale; - data.spacing = GetFloat(constraintMap, "spacing", 0); - if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale; - data.mixRotate = GetFloat(constraintMap, "mixRotate", 1); - data.mixX = GetFloat(constraintMap, "mixX", 1); - data.mixY = GetFloat(constraintMap, "mixY", data.mixX); - - skeletonData.pathConstraints.Add(data); - } - } - - // Skins. - if (root.ContainsKey("skins")) { - foreach (Dictionary skinMap in (List)root["skins"]) { - Skin skin = new Skin((string)skinMap["name"]); - if (skinMap.ContainsKey("bones")) { - foreach (string entryName in (List)skinMap["bones"]) { - BoneData bone = skeletonData.FindBone(entryName); - if (bone == null) throw new Exception("Skin bone not found: " + entryName); - skin.bones.Add(bone); - } - } - skin.bones.TrimExcess(); - if (skinMap.ContainsKey("ik")) { - foreach (string entryName in (List)skinMap["ik"]) { - IkConstraintData constraint = skeletonData.FindIkConstraint(entryName); - if (constraint == null) throw new Exception("Skin IK constraint not found: " + entryName); - skin.constraints.Add(constraint); - } - } - if (skinMap.ContainsKey("transform")) { - foreach (string entryName in (List)skinMap["transform"]) { - TransformConstraintData constraint = skeletonData.FindTransformConstraint(entryName); - if (constraint == null) throw new Exception("Skin transform constraint not found: " + entryName); - skin.constraints.Add(constraint); - } - } - if (skinMap.ContainsKey("path")) { - foreach (string entryName in (List)skinMap["path"]) { - PathConstraintData constraint = skeletonData.FindPathConstraint(entryName); - if (constraint == null) throw new Exception("Skin path constraint not found: " + entryName); - skin.constraints.Add(constraint); - } - } - skin.constraints.TrimExcess(); - if (skinMap.ContainsKey("attachments")) { - foreach (KeyValuePair slotEntry in (Dictionary)skinMap["attachments"]) { - int slotIndex = FindSlotIndex(skeletonData, slotEntry.Key); - foreach (KeyValuePair entry in ((Dictionary)slotEntry.Value)) { - try { - Attachment attachment = ReadAttachment((Dictionary)entry.Value, skin, slotIndex, entry.Key, skeletonData); - if (attachment != null) skin.SetAttachment(slotIndex, entry.Key, attachment); - } catch (Exception e) { - throw new Exception("Error reading attachment: " + entry.Key + ", skin: " + skin, e); - } - } - } - } - skeletonData.skins.Add(skin); - if (skin.name == "default") skeletonData.defaultSkin = skin; - } - } - - // Linked meshes. - for (int i = 0, n = linkedMeshes.Count; i < n; i++) { - LinkedMesh linkedMesh = linkedMeshes[i]; - Skin skin = linkedMesh.skin == null ? skeletonData.defaultSkin : skeletonData.FindSkin(linkedMesh.skin); - if (skin == null) throw new Exception("Slot not found: " + linkedMesh.skin); - Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); - if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); - linkedMesh.mesh.DeformAttachment = linkedMesh.inheritDeform ? (VertexAttachment)parent : linkedMesh.mesh; - linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; - linkedMesh.mesh.UpdateUVs(); - } - linkedMeshes.Clear(); - - // Events. - if (root.ContainsKey("events")) { - foreach (KeyValuePair entry in (Dictionary)root["events"]) { - var entryMap = (Dictionary)entry.Value; - var data = new EventData(entry.Key); - data.Int = GetInt(entryMap, "int", 0); - data.Float = GetFloat(entryMap, "float", 0); - data.String = GetString(entryMap, "string", string.Empty); - data.AudioPath = GetString(entryMap, "audio", null); - if (data.AudioPath != null) { - data.Volume = GetFloat(entryMap, "volume", 1); - data.Balance = GetFloat(entryMap, "balance", 0); - } - skeletonData.events.Add(data); - } - } - - // Animations. - if (root.ContainsKey("animations")) { - foreach (KeyValuePair entry in (Dictionary)root["animations"]) { - try { - ReadAnimation((Dictionary)entry.Value, entry.Key, skeletonData); - } catch (Exception e) { - throw new Exception("Error reading animation: " + entry.Key + "\n" + e.Message, e); - } - } - } - - skeletonData.bones.TrimExcess(); - skeletonData.slots.TrimExcess(); - skeletonData.skins.TrimExcess(); - skeletonData.events.TrimExcess(); - skeletonData.animations.TrimExcess(); - skeletonData.ikConstraints.TrimExcess(); - return skeletonData; - } - - private Attachment ReadAttachment (Dictionary map, Skin skin, int slotIndex, string name, SkeletonData skeletonData) { - float scale = this.scale; - name = GetString(map, "name", name); - - var typeName = GetString(map, "type", "region"); - var type = (AttachmentType)Enum.Parse(typeof(AttachmentType), typeName, true); - - string path = GetString(map, "path", name); - - switch (type) { - case AttachmentType.Region: - RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); - if (region == null) return null; - region.Path = path; - region.x = GetFloat(map, "x", 0) * scale; - region.y = GetFloat(map, "y", 0) * scale; - region.scaleX = GetFloat(map, "scaleX", 1); - region.scaleY = GetFloat(map, "scaleY", 1); - region.rotation = GetFloat(map, "rotation", 0); - region.width = GetFloat(map, "width", 32) * scale; - region.height = GetFloat(map, "height", 32) * scale; - - if (map.ContainsKey("color")) { - var color = (string)map["color"]; - region.r = ToColor(color, 0); - region.g = ToColor(color, 1); - region.b = ToColor(color, 2); - region.a = ToColor(color, 3); - } - - region.UpdateOffset(); - return region; - case AttachmentType.Boundingbox: - BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); - if (box == null) return null; - ReadVertices(map, box, GetInt(map, "vertexCount", 0) << 1); - return box; - case AttachmentType.Mesh: - case AttachmentType.Linkedmesh: { - MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); - if (mesh == null) return null; - mesh.Path = path; - - if (map.ContainsKey("color")) { - var color = (string)map["color"]; - mesh.r = ToColor(color, 0); - mesh.g = ToColor(color, 1); - mesh.b = ToColor(color, 2); - mesh.a = ToColor(color, 3); - } - - mesh.Width = GetFloat(map, "width", 0) * scale; - mesh.Height = GetFloat(map, "height", 0) * scale; - - string parent = GetString(map, "parent", null); - if (parent != null) { - linkedMeshes.Add(new LinkedMesh(mesh, GetString(map, "skin", null), slotIndex, parent, GetBoolean(map, "deform", true))); - return mesh; - } - - float[] uvs = GetFloatArray(map, "uvs", 1); - ReadVertices(map, mesh, uvs.Length); - mesh.triangles = GetIntArray(map, "triangles"); - mesh.regionUVs = uvs; - mesh.UpdateUVs(); - - if (map.ContainsKey("hull")) mesh.HullLength = GetInt(map, "hull", 0) << 1; - if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges"); - return mesh; - } - case AttachmentType.Path: { - PathAttachment pathAttachment = attachmentLoader.NewPathAttachment(skin, name); - if (pathAttachment == null) return null; - pathAttachment.closed = GetBoolean(map, "closed", false); - pathAttachment.constantSpeed = GetBoolean(map, "constantSpeed", true); - - int vertexCount = GetInt(map, "vertexCount", 0); - ReadVertices(map, pathAttachment, vertexCount << 1); - - // potential BOZO see Java impl - pathAttachment.lengths = GetFloatArray(map, "lengths", scale); - return pathAttachment; - } - case AttachmentType.Point: { - PointAttachment point = attachmentLoader.NewPointAttachment(skin, name); - if (point == null) return null; - point.x = GetFloat(map, "x", 0) * scale; - point.y = GetFloat(map, "y", 0) * scale; - point.rotation = GetFloat(map, "rotation", 0); - - //string color = GetString(map, "color", null); - //if (color != null) point.color = color; - return point; - } - case AttachmentType.Clipping: { - ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name); - if (clip == null) return null; - - string end = GetString(map, "end", null); - if (end != null) { - SlotData slot = skeletonData.FindSlot(end); - if (slot == null) throw new Exception("Clipping end slot not found: " + end); - clip.EndSlot = slot; - } - - ReadVertices(map, clip, GetInt(map, "vertexCount", 0) << 1); - - //string color = GetString(map, "color", null); - // if (color != null) clip.color = color; - return clip; - } - } - return null; - } - - private void ReadVertices (Dictionary map, VertexAttachment attachment, int verticesLength) { - attachment.WorldVerticesLength = verticesLength; - float[] vertices = GetFloatArray(map, "vertices", 1); - float scale = Scale; - if (verticesLength == vertices.Length) { - if (scale != 1) { - for (int i = 0; i < vertices.Length; i++) { - vertices[i] *= scale; - } - } - attachment.vertices = vertices; - return; - } - ExposedList weights = new ExposedList(verticesLength * 3 * 3); - ExposedList bones = new ExposedList(verticesLength * 3); - for (int i = 0, n = vertices.Length; i < n;) { - int boneCount = (int)vertices[i++]; - bones.Add(boneCount); - for (int nn = i + (boneCount << 2); i < nn; i += 4) { - bones.Add((int)vertices[i]); - weights.Add(vertices[i + 1] * this.Scale); - weights.Add(vertices[i + 2] * this.Scale); - weights.Add(vertices[i + 3]); - } - } - attachment.bones = bones.ToArray(); - attachment.vertices = weights.ToArray(); - } - - private int FindSlotIndex (SkeletonData skeletonData, string slotName) { - SlotData[] slots = skeletonData.slots.Items; - for (int i = 0, n = skeletonData.slots.Count; i < n; i++) - if (slots[i].name == slotName) return i; - throw new Exception("Slot not found: " + slotName); - } - - private void ReadAnimation (Dictionary map, string name, SkeletonData skeletonData) { - var scale = this.scale; - var timelines = new ExposedList(); - - // Slot timelines. - if (map.ContainsKey("slots")) { - foreach (KeyValuePair entry in (Dictionary)map["slots"]) { - string slotName = entry.Key; - int slotIndex = FindSlotIndex(skeletonData, slotName); - var timelineMap = (Dictionary)entry.Value; - foreach (KeyValuePair timelineEntry in timelineMap) { - var values = (List)timelineEntry.Value; - int frames = values.Count; - if (frames == 0) continue; - var timelineName = (string)timelineEntry.Key; - if (timelineName == "attachment") { - var timeline = new AttachmentTimeline(frames, slotIndex); - int frame = 0; - foreach (Dictionary keyMap in values) { - timeline.SetFrame(frame++, GetFloat(keyMap, "time", 0), (string)keyMap["name"]); - } - timelines.Add(timeline); - - } else if (timelineName == "rgba") { - var timeline = new RGBATimeline(frames, frames << 2, slotIndex); - - var keyMapEnumerator = values.GetEnumerator(); - keyMapEnumerator.MoveNext(); - var keyMap = (Dictionary)keyMapEnumerator.Current; - float time = GetFloat(keyMap, "time", 0); - string color = (string)keyMap["color"]; - float r = ToColor(color, 0); - float g = ToColor(color, 1); - float b = ToColor(color, 2); - float a = ToColor(color, 3); - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, r, g, b, a); - if (!keyMapEnumerator.MoveNext()) { - timeline.Shrink(bezier); - break; - } - var nextMap = (Dictionary)keyMapEnumerator.Current; - - float time2 = GetFloat(nextMap, "time", 0); - color = (string)nextMap["color"]; - float nr = ToColor(color, 0); - float ng = ToColor(color, 1); - float nb = ToColor(color, 2); - float na = ToColor(color, 3); - - if (keyMap.ContainsKey("curve")) { - object curve = keyMap["curve"]; - bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1); - bezier = ReadCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1); - bezier = ReadCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1); - bezier = ReadCurve(curve, timeline, bezier, frame, 3, time, time2, a, na, 1); - } - time = time2; - r = nr; - g = ng; - b = nb; - a = na; - keyMap = nextMap; - } - timelines.Add(timeline); - - } else if (timelineName == "rgb") { - var timeline = new RGBTimeline(frames, frames * 3, slotIndex); - - var keyMapEnumerator = values.GetEnumerator(); - keyMapEnumerator.MoveNext(); - var keyMap = (Dictionary)keyMapEnumerator.Current; - float time = GetFloat(keyMap, "time", 0); - string color = (string)keyMap["color"]; - float r = ToColor(color, 0, 6); - float g = ToColor(color, 1, 6); - float b = ToColor(color, 2, 6); - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, r, g, b); - if (!keyMapEnumerator.MoveNext()) { - timeline.Shrink(bezier); - break; - } - var nextMap = (Dictionary)keyMapEnumerator.Current; - - float time2 = GetFloat(nextMap, "time", 0); - color = (string)nextMap["color"]; - float nr = ToColor(color, 0, 6); - float ng = ToColor(color, 1, 6); - float nb = ToColor(color, 2, 6); - - if (keyMap.ContainsKey("curve")) { - object curve = keyMap["curve"]; - bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1); - bezier = ReadCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1); - bezier = ReadCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1); - } - time = time2; - r = nr; - g = ng; - b = nb; - keyMap = nextMap; - } - timelines.Add(timeline); - - } else if (timelineName == "alpha") { - var keyMapEnumerator = values.GetEnumerator(); - keyMapEnumerator.MoveNext(); - timelines.Add(ReadTimeline(ref keyMapEnumerator, new AlphaTimeline(frames, frames, slotIndex), 0, 1)); - - } else if (timelineName == "rgba2") { - var timeline = new RGBA2Timeline(frames, frames * 7, slotIndex); - - var keyMapEnumerator = values.GetEnumerator(); - keyMapEnumerator.MoveNext(); - var keyMap = (Dictionary)keyMapEnumerator.Current; - float time = GetFloat(keyMap, "time", 0); - string color = (string)keyMap["light"]; - float r = ToColor(color, 0); - float g = ToColor(color, 1); - float b = ToColor(color, 2); - float a = ToColor(color, 3); - color = (string)keyMap["dark"]; - float r2 = ToColor(color, 0, 6); - float g2 = ToColor(color, 1, 6); - float b2 = ToColor(color, 2, 6); - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, r, g, b, a, r2, g2, b2); - if (!keyMapEnumerator.MoveNext()) { - timeline.Shrink(bezier); - break; - } - var nextMap = (Dictionary)keyMapEnumerator.Current; - - float time2 = GetFloat(nextMap, "time", 0); - color = (string)nextMap["light"]; - float nr = ToColor(color, 0); - float ng = ToColor(color, 1); - float nb = ToColor(color, 2); - float na = ToColor(color, 3); - color = (string)nextMap["dark"]; - float nr2 = ToColor(color, 0, 6); - float ng2 = ToColor(color, 1, 6); - float nb2 = ToColor(color, 2, 6); - - if (keyMap.ContainsKey("curve")) { - object curve = keyMap["curve"]; - bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1); - bezier = ReadCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1); - bezier = ReadCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1); - bezier = ReadCurve(curve, timeline, bezier, frame, 3, time, time2, a, na, 1); - bezier = ReadCurve(curve, timeline, bezier, frame, 4, time, time2, r2, nr2, 1); - bezier = ReadCurve(curve, timeline, bezier, frame, 5, time, time2, g2, ng2, 1); - bezier = ReadCurve(curve, 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; - keyMap = nextMap; - } - timelines.Add(timeline); - - } else if (timelineName == "rgb2") { - var timeline = new RGB2Timeline(frames, frames * 6, slotIndex); - - var keyMapEnumerator = values.GetEnumerator(); - keyMapEnumerator.MoveNext(); - var keyMap = (Dictionary)keyMapEnumerator.Current; - float time = GetFloat(keyMap, "time", 0); - string color = (string)keyMap["light"]; - float r = ToColor(color, 0, 6); - float g = ToColor(color, 1, 6); - float b = ToColor(color, 2, 6); - color = (string)keyMap["dark"]; - float r2 = ToColor(color, 0, 6); - float g2 = ToColor(color, 1, 6); - float b2 = ToColor(color, 2, 6); - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, r, g, b, r2, g2, b2); - if (!keyMapEnumerator.MoveNext()) { - timeline.Shrink(bezier); - break; - } - var nextMap = (Dictionary)keyMapEnumerator.Current; - - float time2 = GetFloat(nextMap, "time", 0); - color = (string)nextMap["light"]; - float nr = ToColor(color, 0, 6); - float ng = ToColor(color, 1, 6); - float nb = ToColor(color, 2, 6); - color = (string)nextMap["dark"]; - float nr2 = ToColor(color, 0, 6); - float ng2 = ToColor(color, 1, 6); - float nb2 = ToColor(color, 2, 6); - - if (keyMap.ContainsKey("curve")) { - object curve = keyMap["curve"]; - bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1); - bezier = ReadCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1); - bezier = ReadCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1); - bezier = ReadCurve(curve, timeline, bezier, frame, 3, time, time2, r2, nr2, 1); - bezier = ReadCurve(curve, timeline, bezier, frame, 4, time, time2, g2, ng2, 1); - bezier = ReadCurve(curve, timeline, bezier, frame, 5, time, time2, b2, nb2, 1); - } - time = time2; - r = nr; - g = ng; - b = nb; - r2 = nr2; - g2 = ng2; - b2 = nb2; - keyMap = nextMap; - } - timelines.Add(timeline); - - } else - throw new Exception("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")"); - } - } - } - - // Bone timelines. - if (map.ContainsKey("bones")) { - foreach (KeyValuePair entry in (Dictionary)map["bones"]) { - string boneName = entry.Key; - int boneIndex = -1; - var bones = skeletonData.bones.Items; - for (int i = 0, n = skeletonData.bones.Count; i < n; i++) { - if (bones[i].name == boneName) { - boneIndex = i; - break; - } - } - if (boneIndex == -1) throw new Exception("Bone not found: " + boneName); - var timelineMap = (Dictionary)entry.Value; - foreach (KeyValuePair timelineEntry in timelineMap) { - var values = (List)timelineEntry.Value; - var keyMapEnumerator = values.GetEnumerator(); - if (!keyMapEnumerator.MoveNext()) continue; - int frames = values.Count; - var timelineName = (string)timelineEntry.Key; - if (timelineName == "rotate") - timelines.Add(ReadTimeline(ref keyMapEnumerator, new RotateTimeline(frames, frames, boneIndex), 0, 1)); - else if (timelineName == "translate") { - TranslateTimeline timeline = new TranslateTimeline(frames, frames << 1, boneIndex); - timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, "x", "y", 0, scale)); - } else if (timelineName == "translatex") { - timelines - .Add(ReadTimeline(ref keyMapEnumerator, new TranslateXTimeline(frames, frames, boneIndex), 0, scale)); - } else if (timelineName == "translatey") { - timelines - .Add(ReadTimeline(ref keyMapEnumerator, new TranslateYTimeline(frames, frames, boneIndex), 0, scale)); - } else if (timelineName == "scale") { - ScaleTimeline timeline = new ScaleTimeline(frames, frames << 1, boneIndex); - timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, "x", "y", 1, 1)); - } else if (timelineName == "scalex") - timelines.Add(ReadTimeline(ref keyMapEnumerator, new ScaleXTimeline(frames, frames, boneIndex), 1, 1)); - else if (timelineName == "scaley") - timelines.Add(ReadTimeline(ref keyMapEnumerator, new ScaleYTimeline(frames, frames, boneIndex), 1, 1)); - else if (timelineName == "shear") { - ShearTimeline timeline = new ShearTimeline(frames, frames << 1, boneIndex); - timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, "x", "y", 0, 1)); - } else if (timelineName == "shearx") - timelines.Add(ReadTimeline(ref keyMapEnumerator, new ShearXTimeline(frames, frames, boneIndex), 0, 1)); - else if (timelineName == "sheary") - timelines.Add(ReadTimeline(ref keyMapEnumerator, new ShearYTimeline(frames, frames, boneIndex), 0, 1)); - else - throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")"); - } - } - } - - // IK constraint timelines. - if (map.ContainsKey("ik")) { - foreach (KeyValuePair timelineMap in (Dictionary)map["ik"]) { - var values = (List)timelineMap.Value; - var keyMapEnumerator = values.GetEnumerator(); - if (!keyMapEnumerator.MoveNext()) continue; - var keyMap = (Dictionary)keyMapEnumerator.Current; - IkConstraintData constraint = skeletonData.FindIkConstraint(timelineMap.Key); - IkConstraintTimeline timeline = new IkConstraintTimeline(values.Count, values.Count << 1, - skeletonData.IkConstraints.IndexOf(constraint)); - float time = GetFloat(keyMap, "time", 0); - float mix = GetFloat(keyMap, "mix", 1), softness = GetFloat(keyMap, "softness", 0) * scale; - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, mix, softness, GetBoolean(keyMap, "bendPositive", true) ? 1 : -1, - GetBoolean(keyMap, "compress", false), GetBoolean(keyMap, "stretch", false)); - if (!keyMapEnumerator.MoveNext()) { - timeline.Shrink(bezier); - break; - } - var nextMap = (Dictionary)keyMapEnumerator.Current; - float time2 = GetFloat(nextMap, "time", 0); - float mix2 = GetFloat(nextMap, "mix", 1), softness2 = GetFloat(nextMap, "softness", 0) * scale; - if (keyMap.ContainsKey("curve")) { - object curve = keyMap["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. - if (map.ContainsKey("transform")) { - foreach (KeyValuePair timelineMap in (Dictionary)map["transform"]) { - var values = (List)timelineMap.Value; - var keyMapEnumerator = values.GetEnumerator(); - if (!keyMapEnumerator.MoveNext()) continue; - var keyMap = (Dictionary)keyMapEnumerator.Current; - TransformConstraintData constraint = skeletonData.FindTransformConstraint(timelineMap.Key); - TransformConstraintTimeline timeline = new TransformConstraintTimeline(values.Count, values.Count * 6, - skeletonData.TransformConstraints.IndexOf(constraint)); - float time = GetFloat(keyMap, "time", 0); - float mixRotate = GetFloat(keyMap, "mixRotate", 1), mixShearY = GetFloat(keyMap, "mixShearY", 1); - float mixX = GetFloat(keyMap, "mixX", 1), mixY = GetFloat(keyMap, "mixY", mixX); - float mixScaleX = GetFloat(keyMap, "mixScaleX", 1), mixScaleY = GetFloat(keyMap, "mixScaleY", mixScaleX); - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY); - if (!keyMapEnumerator.MoveNext()) { - timeline.Shrink(bezier); - break; - } - var nextMap = (Dictionary)keyMapEnumerator.Current; - float time2 = GetFloat(nextMap, "time", 0); - float mixRotate2 = GetFloat(nextMap, "mixRotate", 1), mixShearY2 = GetFloat(nextMap, "mixShearY", 1); - float mixX2 = GetFloat(nextMap, "mixX", 1), mixY2 = GetFloat(nextMap, "mixY", mixX2); - float mixScaleX2 = GetFloat(nextMap, "mixScaleX", 1), mixScaleY2 = GetFloat(nextMap, "mixScaleY", mixScaleX2); - if (keyMap.ContainsKey("curve")) { - object curve = keyMap["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; - mixScaleX = mixScaleX2; - keyMap = nextMap; - } - timelines.Add(timeline); - } - } - - // Path constraint timelines. - if (map.ContainsKey("path")) { - foreach (KeyValuePair constraintMap in (Dictionary)map["path"]) { - PathConstraintData constraint = skeletonData.FindPathConstraint(constraintMap.Key); - if (constraint == null) throw new Exception("Path constraint not found: " + constraintMap.Key); - int constraintIndex = skeletonData.pathConstraints.IndexOf(constraint); - var timelineMap = (Dictionary)constraintMap.Value; - foreach (KeyValuePair timelineEntry in timelineMap) { - var values = (List)timelineEntry.Value; - var keyMapEnumerator = values.GetEnumerator(); - if (!keyMapEnumerator.MoveNext()) continue; - - int frames = values.Count; - var timelineName = (string)timelineEntry.Key; - if (timelineName == "position") { - CurveTimeline1 timeline = new PathConstraintPositionTimeline(frames, frames, constraintIndex); - timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, 0, constraint.positionMode == PositionMode.Fixed ? scale : 1)); - } else if (timelineName == "spacing") { - CurveTimeline1 timeline = new PathConstraintSpacingTimeline(frames, frames, constraintIndex); - timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, 0, - constraint.spacingMode == SpacingMode.Length || constraint.spacingMode == SpacingMode.Fixed ? scale : 1)); - } else if (timelineName == "mix") { - PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(frames, frames * 3, constraintIndex); - var keyMap = (Dictionary)keyMapEnumerator.Current; - float time = GetFloat(keyMap, "time", 0); - float mixRotate = GetFloat(keyMap, "mixRotate", 1); - float mixX = GetFloat(keyMap, "mixX", 1), mixY = GetFloat(keyMap, "mixY", mixX); - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, mixRotate, mixX, mixY); - if (!keyMapEnumerator.MoveNext()) { - timeline.Shrink(bezier); - break; - } - var nextMap = (Dictionary)keyMapEnumerator.Current; - float time2 = GetFloat(nextMap, "time", 0); - float mixRotate2 = GetFloat(nextMap, "mixRotate", 1); - float mixX2 = GetFloat(nextMap, "mixX", 1), mixY2 = GetFloat(nextMap, "mixY", mixX2); - if (keyMap.ContainsKey("curve")) { - object curve = keyMap["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); - } - time = time2; - mixRotate = mixRotate2; - mixX = mixX2; - mixY = mixY2; - keyMap = nextMap; - } - timelines.Add(timeline); - } - } - } - } - - // Deform timelines. - if (map.ContainsKey("deform")) { - foreach (KeyValuePair deformMap in (Dictionary)map["deform"]) { - Skin skin = skeletonData.FindSkin(deformMap.Key); - foreach (KeyValuePair slotMap in (Dictionary)deformMap.Value) { - int slotIndex = FindSlotIndex(skeletonData, slotMap.Key); - foreach (KeyValuePair timelineMap in (Dictionary)slotMap.Value) { - var values = (List)timelineMap.Value; - var keyMapEnumerator = values.GetEnumerator(); - if (!keyMapEnumerator.MoveNext()) continue; - var keyMap = (Dictionary)keyMapEnumerator.Current; - VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, timelineMap.Key); - if (attachment == null) throw new Exception("Deform attachment not found: " + timelineMap.Key); - bool weighted = attachment.bones != null; - float[] vertices = attachment.vertices; - int deformLength = weighted ? (vertices.Length / 3) << 1 : vertices.Length; - DeformTimeline timeline = new DeformTimeline(values.Count, values.Count, slotIndex, attachment); - float time = GetFloat(keyMap, "time", 0); - for (int frame = 0, bezier = 0; ; frame++) { - float[] deform; - if (!keyMap.ContainsKey("vertices")) { - deform = weighted ? new float[deformLength] : vertices; - } else { - deform = new float[deformLength]; - int start = GetInt(keyMap, "offset", 0); - float[] verticesValue = GetFloatArray(keyMap, "vertices", 1); - Array.Copy(verticesValue, 0, deform, start, verticesValue.Length); - if (scale != 1) { - for (int i = start, n = i + verticesValue.Length; i < n; i++) - deform[i] *= scale; - } - - if (!weighted) { - for (int i = 0; i < deformLength; i++) - deform[i] += vertices[i]; - } - } - - timeline.SetFrame(frame, time, deform); - if (!keyMapEnumerator.MoveNext()) { - timeline.Shrink(bezier); - break; - } - var nextMap = (Dictionary)keyMapEnumerator.Current; - float time2 = GetFloat(nextMap, "time", 0); - if (keyMap.ContainsKey("curve")) { - object curve = keyMap["curve"]; - bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, 0, 1, 1); - } - time = time2; - keyMap = nextMap; - } - timelines.Add(timeline); - } - } - } - } - - // Draw order timeline. - if (map.ContainsKey("drawOrder")) { - var values = (List)map["drawOrder"]; - var timeline = new DrawOrderTimeline(values.Count); - int slotCount = skeletonData.slots.Count; - int frame = 0; - foreach (Dictionary drawOrderMap in values) { - int[] drawOrder = null; - if (drawOrderMap.ContainsKey("offsets")) { - drawOrder = new int[slotCount]; - for (int i = slotCount - 1; i >= 0; i--) - drawOrder[i] = -1; - var offsets = (List)drawOrderMap["offsets"]; - int[] unchanged = new int[slotCount - offsets.Count]; - int originalIndex = 0, unchangedIndex = 0; - foreach (Dictionary offsetMap in offsets) { - int slotIndex = FindSlotIndex(skeletonData, (string)offsetMap["slot"]); - // Collect unchanged items. - while (originalIndex != slotIndex) - unchanged[unchangedIndex++] = originalIndex++; - // Set changed items. - int index = originalIndex + (int)(float)offsetMap["offset"]; - drawOrder[index] = originalIndex++; - } - // Collect remaining unchanged items. - while (originalIndex < slotCount) - unchanged[unchangedIndex++] = originalIndex++; - // Fill in unchanged items. - for (int i = slotCount - 1; i >= 0; i--) - if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex]; - } - timeline.SetFrame(frame, GetFloat(drawOrderMap, "time", 0), drawOrder); - ++frame; - } - timelines.Add(timeline); - } - - // Event timeline. - if (map.ContainsKey("events")) { - var eventsMap = (List)map["events"]; - var timeline = new EventTimeline(eventsMap.Count); - int frame = 0; - foreach (Dictionary eventMap in eventsMap) { - EventData eventData = skeletonData.FindEvent((string)eventMap["name"]); - if (eventData == null) throw new Exception("Event not found: " + eventMap["name"]); - var e = new Event(GetFloat(eventMap, "time", 0), eventData) { - intValue = GetInt(eventMap, "int", eventData.Int), - floatValue = GetFloat(eventMap, "float", eventData.Float), - stringValue = GetString(eventMap, "string", eventData.String) - }; - if (e.data.AudioPath != null) { - e.volume = GetFloat(eventMap, "volume", eventData.Volume); - e.balance = GetFloat(eventMap, "balance", eventData.Balance); - } - timeline.SetFrame(frame, e); - ++frame; - } - timelines.Add(timeline); - } - timelines.TrimExcess(); - float duration = 0; - var items = timelines.Items; - for (int i = 0, n = timelines.Count; i < n; i++) - duration = Math.Max(duration, items[i].Duration); - skeletonData.animations.Add(new Animation(name, timelines, duration)); - } - - static Timeline ReadTimeline (ref List.Enumerator keyMapEnumerator, CurveTimeline1 timeline, float defaultValue, float scale) { - var keyMap = (Dictionary)keyMapEnumerator.Current; - float time = GetFloat(keyMap, "time", 0); - float value = GetFloat(keyMap, "value", defaultValue) * scale; - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, value); - if (!keyMapEnumerator.MoveNext()) { - timeline.Shrink(bezier); - return timeline; - } - var nextMap = (Dictionary)keyMapEnumerator.Current; - float time2 = GetFloat(nextMap, "time", 0); - float value2 = GetFloat(nextMap, "value", defaultValue) * scale; - if (keyMap.ContainsKey("curve")) { - object curve = keyMap["curve"]; - bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, value, value2, scale); - } - time = time2; - value = value2; - keyMap = nextMap; - } - } - - static Timeline ReadTimeline (ref List.Enumerator keyMapEnumerator, CurveTimeline2 timeline, String name1, String name2, float defaultValue, - float scale) { - - var keyMap = (Dictionary)keyMapEnumerator.Current; - float time = GetFloat(keyMap, "time", 0); - float value1 = GetFloat(keyMap, name1, defaultValue) * scale, value2 = GetFloat(keyMap, name2, defaultValue) * scale; - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, value1, value2); - if (!keyMapEnumerator.MoveNext()) { - timeline.Shrink(bezier); - return timeline; - } - var nextMap = (Dictionary)keyMapEnumerator.Current; - float time2 = GetFloat(nextMap, "time", 0); - float nvalue1 = GetFloat(nextMap, name1, defaultValue) * scale, nvalue2 = GetFloat(nextMap, name2, defaultValue) * scale; - if (keyMap.ContainsKey("curve")) { - object curve = keyMap["curve"]; - 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; - } - } - - static int ReadCurve (object curve, CurveTimeline timeline, int bezier, int frame, int value, float time1, float time2, - float value1, float value2, float scale) { - - string curveString = curve as string; - if (curveString != null) { - if (curveString == "stepped") timeline.SetStepped(frame); - return bezier; - } - var curveValues = (List)curve; - int i = value << 2; - float cx1 = (float)curveValues[i]; - float cy1 = (float)curveValues[i + 1] * scale; - float cx2 = (float)curveValues[i + 2]; - float cy2 = (float)curveValues[i + 3] * scale; - SetBezier(timeline, frame, value, bezier, time1, value1, cx1, cy1, cx2, cy2, time2, value2); - return bezier + 1; - } - - 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) { - timeline.SetBezier(bezier, frame, value, time1, value1, cx1, cy1, cx2, cy2, time2, value2); - } - - static float[] GetFloatArray (Dictionary map, string name, float scale) { - var list = (List)map[name]; - var values = new float[list.Count]; - if (scale == 1) { - for (int i = 0, n = list.Count; i < n; i++) - values[i] = (float)list[i]; - } else { - for (int i = 0, n = list.Count; i < n; i++) - values[i] = (float)list[i] * scale; - } - return values; - } - - static int[] GetIntArray (Dictionary map, string name) { - var list = (List)map[name]; - var values = new int[list.Count]; - for (int i = 0, n = list.Count; i < n; i++) - values[i] = (int)(float)list[i]; - return values; - } - - static float GetFloat (Dictionary map, string name, float defaultValue) { - if (!map.ContainsKey(name)) return defaultValue; - return (float)map[name]; - } - - static int GetInt (Dictionary map, string name, int defaultValue) { - if (!map.ContainsKey(name)) return defaultValue; - return (int)(float)map[name]; - } - - static bool GetBoolean (Dictionary map, string name, bool defaultValue) { - if (!map.ContainsKey(name)) return defaultValue; - return (bool)map[name]; - } - - static string GetString (Dictionary map, string name, string defaultValue) { - if (!map.ContainsKey(name)) return defaultValue; - return (string)map[name]; - } - - static float ToColor (string hexString, int colorIndex, int expectedLength = 8) { - if (hexString.Length != expectedLength) - throw new ArgumentException("Color hexidecimal length must be " + expectedLength + ", recieved: " + hexString, "hexString"); - return Convert.ToInt32(hexString.Substring(colorIndex * 2, 2), 16) / (float)255; - } - } + public SkeletonData ReadSkeletonData(TextReader reader) + { + if (reader == null) throw new ArgumentNullException("reader", "reader cannot be null."); + + float scale = this.scale; + var skeletonData = new SkeletonData(); + + var root = Json.Deserialize(reader) as Dictionary; + if (root == null) throw new Exception("Invalid JSON."); + + // Skeleton. + if (root.ContainsKey("skeleton")) + { + var skeletonMap = (Dictionary)root["skeleton"]; + skeletonData.hash = (string)skeletonMap["hash"]; + skeletonData.version = (string)skeletonMap["spine"]; + skeletonData.x = GetFloat(skeletonMap, "x", 0); + skeletonData.y = GetFloat(skeletonMap, "y", 0); + skeletonData.width = GetFloat(skeletonMap, "width", 0); + skeletonData.height = GetFloat(skeletonMap, "height", 0); + skeletonData.fps = GetFloat(skeletonMap, "fps", 30); + skeletonData.imagesPath = GetString(skeletonMap, "images", null); + skeletonData.audioPath = GetString(skeletonMap, "audio", null); + } + + // Bones. + if (root.ContainsKey("bones")) + { + foreach (Dictionary boneMap in (List)root["bones"]) + { + BoneData parent = null; + if (boneMap.ContainsKey("parent")) + { + parent = skeletonData.FindBone((string)boneMap["parent"]); + if (parent == null) + throw new Exception("Parent bone not found: " + boneMap["parent"]); + } + var data = new BoneData(skeletonData.Bones.Count, (string)boneMap["name"], parent); + data.length = GetFloat(boneMap, "length", 0) * scale; + data.x = GetFloat(boneMap, "x", 0) * scale; + data.y = GetFloat(boneMap, "y", 0) * scale; + data.rotation = GetFloat(boneMap, "rotation", 0); + data.scaleX = GetFloat(boneMap, "scaleX", 1); + data.scaleY = GetFloat(boneMap, "scaleY", 1); + data.shearX = GetFloat(boneMap, "shearX", 0); + data.shearY = GetFloat(boneMap, "shearY", 0); + + string tm = GetString(boneMap, "transform", TransformMode.Normal.ToString()); + data.transformMode = (TransformMode)Enum.Parse(typeof(TransformMode), tm, true); + data.skinRequired = GetBoolean(boneMap, "skin", false); + + skeletonData.bones.Add(data); + } + } + + // Slots. + if (root.ContainsKey("slots")) + { + foreach (Dictionary slotMap in (List)root["slots"]) + { + var slotName = (string)slotMap["name"]; + var boneName = (string)slotMap["bone"]; + BoneData boneData = skeletonData.FindBone(boneName); + if (boneData == null) throw new Exception("Slot bone not found: " + boneName); + var data = new SlotData(skeletonData.Slots.Count, slotName, boneData); + + if (slotMap.ContainsKey("color")) + { + string color = (string)slotMap["color"]; + data.r = ToColor(color, 0); + data.g = ToColor(color, 1); + data.b = ToColor(color, 2); + data.a = ToColor(color, 3); + } + + if (slotMap.ContainsKey("dark")) + { + var color2 = (string)slotMap["dark"]; + data.r2 = ToColor(color2, 0, 6); // expectedLength = 6. ie. "RRGGBB" + data.g2 = ToColor(color2, 1, 6); + data.b2 = ToColor(color2, 2, 6); + data.hasSecondColor = true; + } + + data.attachmentName = GetString(slotMap, "attachment", null); + if (slotMap.ContainsKey("blend")) + data.blendMode = (BlendMode)Enum.Parse(typeof(BlendMode), (string)slotMap["blend"], true); + else + data.blendMode = BlendMode.Normal; + skeletonData.slots.Add(data); + } + } + + // IK constraints. + if (root.ContainsKey("ik")) + { + foreach (Dictionary constraintMap in (List)root["ik"]) + { + IkConstraintData data = new IkConstraintData((string)constraintMap["name"]); + data.order = GetInt(constraintMap, "order", 0); + data.skinRequired = GetBoolean(constraintMap, "skin", false); + + if (constraintMap.ContainsKey("bones")) + { + foreach (string boneName in (List)constraintMap["bones"]) + { + BoneData bone = skeletonData.FindBone(boneName); + if (bone == null) throw new Exception("IK bone not found: " + boneName); + data.bones.Add(bone); + } + } + + string targetName = (string)constraintMap["target"]; + data.target = skeletonData.FindBone(targetName); + if (data.target == null) throw new Exception("IK target bone not found: " + targetName); + data.mix = GetFloat(constraintMap, "mix", 1); + data.softness = GetFloat(constraintMap, "softness", 0) * scale; + data.bendDirection = GetBoolean(constraintMap, "bendPositive", true) ? 1 : -1; + data.compress = GetBoolean(constraintMap, "compress", false); + data.stretch = GetBoolean(constraintMap, "stretch", false); + data.uniform = GetBoolean(constraintMap, "uniform", false); + + skeletonData.ikConstraints.Add(data); + } + } + + // Transform constraints. + if (root.ContainsKey("transform")) + { + foreach (Dictionary constraintMap in (List)root["transform"]) + { + TransformConstraintData data = new TransformConstraintData((string)constraintMap["name"]); + data.order = GetInt(constraintMap, "order", 0); + data.skinRequired = GetBoolean(constraintMap, "skin", false); + + if (constraintMap.ContainsKey("bones")) + { + foreach (string boneName in (List)constraintMap["bones"]) + { + BoneData bone = skeletonData.FindBone(boneName); + if (bone == null) throw new Exception("Transform constraint bone not found: " + boneName); + data.bones.Add(bone); + } + } + + string targetName = (string)constraintMap["target"]; + data.target = skeletonData.FindBone(targetName); + if (data.target == null) throw new Exception("Transform constraint target bone not found: " + targetName); + + data.local = GetBoolean(constraintMap, "local", false); + data.relative = GetBoolean(constraintMap, "relative", false); + + data.offsetRotation = GetFloat(constraintMap, "rotation", 0); + data.offsetX = GetFloat(constraintMap, "x", 0) * scale; + data.offsetY = GetFloat(constraintMap, "y", 0) * scale; + data.offsetScaleX = GetFloat(constraintMap, "scaleX", 0); + data.offsetScaleY = GetFloat(constraintMap, "scaleY", 0); + data.offsetShearY = GetFloat(constraintMap, "shearY", 0); + + data.mixRotate = GetFloat(constraintMap, "mixRotate", 1); + data.mixX = GetFloat(constraintMap, "mixX", 1); + data.mixY = GetFloat(constraintMap, "mixY", data.mixX); + data.mixScaleX = GetFloat(constraintMap, "mixScaleX", 1); + data.mixScaleY = GetFloat(constraintMap, "mixScaleY", data.mixScaleX); + data.mixShearY = GetFloat(constraintMap, "mixShearY", 1); + + skeletonData.transformConstraints.Add(data); + } + } + + // Path constraints. + if (root.ContainsKey("path")) + { + foreach (Dictionary constraintMap in (List)root["path"]) + { + PathConstraintData data = new PathConstraintData((string)constraintMap["name"]); + data.order = GetInt(constraintMap, "order", 0); + data.skinRequired = GetBoolean(constraintMap, "skin", false); + + if (constraintMap.ContainsKey("bones")) + { + foreach (string boneName in (List)constraintMap["bones"]) + { + BoneData bone = skeletonData.FindBone(boneName); + if (bone == null) throw new Exception("Path bone not found: " + boneName); + data.bones.Add(bone); + } + } + + string targetName = (string)constraintMap["target"]; + data.target = skeletonData.FindSlot(targetName); + if (data.target == null) throw new Exception("Path target slot not found: " + targetName); + + data.positionMode = (PositionMode)Enum.Parse(typeof(PositionMode), GetString(constraintMap, "positionMode", "percent"), true); + data.spacingMode = (SpacingMode)Enum.Parse(typeof(SpacingMode), GetString(constraintMap, "spacingMode", "length"), true); + data.rotateMode = (RotateMode)Enum.Parse(typeof(RotateMode), GetString(constraintMap, "rotateMode", "tangent"), true); + data.offsetRotation = GetFloat(constraintMap, "rotation", 0); + data.position = GetFloat(constraintMap, "position", 0); + if (data.positionMode == PositionMode.Fixed) data.position *= scale; + data.spacing = GetFloat(constraintMap, "spacing", 0); + if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale; + data.mixRotate = GetFloat(constraintMap, "mixRotate", 1); + data.mixX = GetFloat(constraintMap, "mixX", 1); + data.mixY = GetFloat(constraintMap, "mixY", data.mixX); + + skeletonData.pathConstraints.Add(data); + } + } + + // Skins. + if (root.ContainsKey("skins")) + { + foreach (Dictionary skinMap in (List)root["skins"]) + { + Skin skin = new Skin((string)skinMap["name"]); + if (skinMap.ContainsKey("bones")) + { + foreach (string entryName in (List)skinMap["bones"]) + { + BoneData bone = skeletonData.FindBone(entryName); + if (bone == null) throw new Exception("Skin bone not found: " + entryName); + skin.bones.Add(bone); + } + } + skin.bones.TrimExcess(); + if (skinMap.ContainsKey("ik")) + { + foreach (string entryName in (List)skinMap["ik"]) + { + IkConstraintData constraint = skeletonData.FindIkConstraint(entryName); + if (constraint == null) throw new Exception("Skin IK constraint not found: " + entryName); + skin.constraints.Add(constraint); + } + } + if (skinMap.ContainsKey("transform")) + { + foreach (string entryName in (List)skinMap["transform"]) + { + TransformConstraintData constraint = skeletonData.FindTransformConstraint(entryName); + if (constraint == null) throw new Exception("Skin transform constraint not found: " + entryName); + skin.constraints.Add(constraint); + } + } + if (skinMap.ContainsKey("path")) + { + foreach (string entryName in (List)skinMap["path"]) + { + PathConstraintData constraint = skeletonData.FindPathConstraint(entryName); + if (constraint == null) throw new Exception("Skin path constraint not found: " + entryName); + skin.constraints.Add(constraint); + } + } + skin.constraints.TrimExcess(); + if (skinMap.ContainsKey("attachments")) + { + foreach (KeyValuePair slotEntry in (Dictionary)skinMap["attachments"]) + { + int slotIndex = FindSlotIndex(skeletonData, slotEntry.Key); + foreach (KeyValuePair entry in ((Dictionary)slotEntry.Value)) + { + try + { + Attachment attachment = ReadAttachment((Dictionary)entry.Value, skin, slotIndex, entry.Key, skeletonData); + if (attachment != null) skin.SetAttachment(slotIndex, entry.Key, attachment); + } + catch (Exception e) + { + throw new Exception("Error reading attachment: " + entry.Key + ", skin: " + skin, e); + } + } + } + } + skeletonData.skins.Add(skin); + if (skin.name == "default") skeletonData.defaultSkin = skin; + } + } + + // Linked meshes. + for (int i = 0, n = linkedMeshes.Count; i < n; i++) + { + LinkedMesh linkedMesh = linkedMeshes[i]; + Skin skin = linkedMesh.skin == null ? skeletonData.defaultSkin : skeletonData.FindSkin(linkedMesh.skin); + if (skin == null) throw new Exception("Slot not found: " + linkedMesh.skin); + Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); + if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); + linkedMesh.mesh.DeformAttachment = linkedMesh.inheritDeform ? (VertexAttachment)parent : linkedMesh.mesh; + linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; + linkedMesh.mesh.UpdateUVs(); + } + linkedMeshes.Clear(); + + // Events. + if (root.ContainsKey("events")) + { + foreach (KeyValuePair entry in (Dictionary)root["events"]) + { + var entryMap = (Dictionary)entry.Value; + var data = new EventData(entry.Key); + data.Int = GetInt(entryMap, "int", 0); + data.Float = GetFloat(entryMap, "float", 0); + data.String = GetString(entryMap, "string", string.Empty); + data.AudioPath = GetString(entryMap, "audio", null); + if (data.AudioPath != null) + { + data.Volume = GetFloat(entryMap, "volume", 1); + data.Balance = GetFloat(entryMap, "balance", 0); + } + skeletonData.events.Add(data); + } + } + + // Animations. + if (root.ContainsKey("animations")) + { + foreach (KeyValuePair entry in (Dictionary)root["animations"]) + { + try + { + ReadAnimation((Dictionary)entry.Value, entry.Key, skeletonData); + } + catch (Exception e) + { + throw new Exception("Error reading animation: " + entry.Key + "\n" + e.Message, e); + } + } + } + + skeletonData.bones.TrimExcess(); + skeletonData.slots.TrimExcess(); + skeletonData.skins.TrimExcess(); + skeletonData.events.TrimExcess(); + skeletonData.animations.TrimExcess(); + skeletonData.ikConstraints.TrimExcess(); + return skeletonData; + } + + private Attachment ReadAttachment(Dictionary map, Skin skin, int slotIndex, string name, SkeletonData skeletonData) + { + float scale = this.scale; + name = GetString(map, "name", name); + + var typeName = GetString(map, "type", "region"); + var type = (AttachmentType)Enum.Parse(typeof(AttachmentType), typeName, true); + + string path = GetString(map, "path", name); + + switch (type) + { + case AttachmentType.Region: + RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); + if (region == null) return null; + region.Path = path; + region.x = GetFloat(map, "x", 0) * scale; + region.y = GetFloat(map, "y", 0) * scale; + region.scaleX = GetFloat(map, "scaleX", 1); + region.scaleY = GetFloat(map, "scaleY", 1); + region.rotation = GetFloat(map, "rotation", 0); + region.width = GetFloat(map, "width", 32) * scale; + region.height = GetFloat(map, "height", 32) * scale; + + if (map.ContainsKey("color")) + { + var color = (string)map["color"]; + region.r = ToColor(color, 0); + region.g = ToColor(color, 1); + region.b = ToColor(color, 2); + region.a = ToColor(color, 3); + } + + region.UpdateOffset(); + return region; + case AttachmentType.Boundingbox: + BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); + if (box == null) return null; + ReadVertices(map, box, GetInt(map, "vertexCount", 0) << 1); + return box; + case AttachmentType.Mesh: + case AttachmentType.Linkedmesh: + { + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); + if (mesh == null) return null; + mesh.Path = path; + + if (map.ContainsKey("color")) + { + var color = (string)map["color"]; + mesh.r = ToColor(color, 0); + mesh.g = ToColor(color, 1); + mesh.b = ToColor(color, 2); + mesh.a = ToColor(color, 3); + } + + mesh.Width = GetFloat(map, "width", 0) * scale; + mesh.Height = GetFloat(map, "height", 0) * scale; + + string parent = GetString(map, "parent", null); + if (parent != null) + { + linkedMeshes.Add(new LinkedMesh(mesh, GetString(map, "skin", null), slotIndex, parent, GetBoolean(map, "deform", true))); + return mesh; + } + + float[] uvs = GetFloatArray(map, "uvs", 1); + ReadVertices(map, mesh, uvs.Length); + mesh.triangles = GetIntArray(map, "triangles"); + mesh.regionUVs = uvs; + mesh.UpdateUVs(); + + if (map.ContainsKey("hull")) mesh.HullLength = GetInt(map, "hull", 0) << 1; + if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges"); + return mesh; + } + case AttachmentType.Path: + { + PathAttachment pathAttachment = attachmentLoader.NewPathAttachment(skin, name); + if (pathAttachment == null) return null; + pathAttachment.closed = GetBoolean(map, "closed", false); + pathAttachment.constantSpeed = GetBoolean(map, "constantSpeed", true); + + int vertexCount = GetInt(map, "vertexCount", 0); + ReadVertices(map, pathAttachment, vertexCount << 1); + + // potential BOZO see Java impl + pathAttachment.lengths = GetFloatArray(map, "lengths", scale); + return pathAttachment; + } + case AttachmentType.Point: + { + PointAttachment point = attachmentLoader.NewPointAttachment(skin, name); + if (point == null) return null; + point.x = GetFloat(map, "x", 0) * scale; + point.y = GetFloat(map, "y", 0) * scale; + point.rotation = GetFloat(map, "rotation", 0); + + //string color = GetString(map, "color", null); + //if (color != null) point.color = color; + return point; + } + case AttachmentType.Clipping: + { + ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name); + if (clip == null) return null; + + string end = GetString(map, "end", null); + if (end != null) + { + SlotData slot = skeletonData.FindSlot(end); + if (slot == null) throw new Exception("Clipping end slot not found: " + end); + clip.EndSlot = slot; + } + + ReadVertices(map, clip, GetInt(map, "vertexCount", 0) << 1); + + //string color = GetString(map, "color", null); + // if (color != null) clip.color = color; + return clip; + } + } + return null; + } + + private void ReadVertices(Dictionary map, VertexAttachment attachment, int verticesLength) + { + attachment.WorldVerticesLength = verticesLength; + float[] vertices = GetFloatArray(map, "vertices", 1); + float scale = Scale; + if (verticesLength == vertices.Length) + { + if (scale != 1) + { + for (int i = 0; i < vertices.Length; i++) + { + vertices[i] *= scale; + } + } + attachment.vertices = vertices; + return; + } + ExposedList weights = new ExposedList(verticesLength * 3 * 3); + ExposedList bones = new ExposedList(verticesLength * 3); + for (int i = 0, n = vertices.Length; i < n;) + { + int boneCount = (int)vertices[i++]; + bones.Add(boneCount); + for (int nn = i + (boneCount << 2); i < nn; i += 4) + { + bones.Add((int)vertices[i]); + weights.Add(vertices[i + 1] * this.Scale); + weights.Add(vertices[i + 2] * this.Scale); + weights.Add(vertices[i + 3]); + } + } + attachment.bones = bones.ToArray(); + attachment.vertices = weights.ToArray(); + } + + private int FindSlotIndex(SkeletonData skeletonData, string slotName) + { + SlotData[] slots = skeletonData.slots.Items; + for (int i = 0, n = skeletonData.slots.Count; i < n; i++) + if (slots[i].name == slotName) return i; + throw new Exception("Slot not found: " + slotName); + } + + private void ReadAnimation(Dictionary map, string name, SkeletonData skeletonData) + { + var scale = this.scale; + var timelines = new ExposedList(); + + // Slot timelines. + if (map.ContainsKey("slots")) + { + foreach (KeyValuePair entry in (Dictionary)map["slots"]) + { + string slotName = entry.Key; + int slotIndex = FindSlotIndex(skeletonData, slotName); + var timelineMap = (Dictionary)entry.Value; + foreach (KeyValuePair timelineEntry in timelineMap) + { + var values = (List)timelineEntry.Value; + int frames = values.Count; + if (frames == 0) continue; + var timelineName = timelineEntry.Key; + if (timelineName == "attachment") + { + var timeline = new AttachmentTimeline(frames, slotIndex); + int frame = 0; + foreach (Dictionary keyMap in values) + { + timeline.SetFrame(frame++, GetFloat(keyMap, "time", 0), (string)keyMap["name"]); + } + timelines.Add(timeline); + + } + else if (timelineName == "rgba") + { + var timeline = new RGBATimeline(frames, frames << 2, slotIndex); + + var keyMapEnumerator = values.GetEnumerator(); + keyMapEnumerator.MoveNext(); + var keyMap = (Dictionary)keyMapEnumerator.Current; + float time = GetFloat(keyMap, "time", 0); + string color = (string)keyMap["color"]; + float r = ToColor(color, 0); + float g = ToColor(color, 1); + float b = ToColor(color, 2); + float a = ToColor(color, 3); + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, r, g, b, a); + if (!keyMapEnumerator.MoveNext()) + { + timeline.Shrink(bezier); + break; + } + var nextMap = (Dictionary)keyMapEnumerator.Current; + + float time2 = GetFloat(nextMap, "time", 0); + color = (string)nextMap["color"]; + float nr = ToColor(color, 0); + float ng = ToColor(color, 1); + float nb = ToColor(color, 2); + float na = ToColor(color, 3); + + if (keyMap.ContainsKey("curve")) + { + object curve = keyMap["curve"]; + bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 3, time, time2, a, na, 1); + } + time = time2; + r = nr; + g = ng; + b = nb; + a = na; + keyMap = nextMap; + } + timelines.Add(timeline); + + } + else if (timelineName == "rgb") + { + var timeline = new RGBTimeline(frames, frames * 3, slotIndex); + + var keyMapEnumerator = values.GetEnumerator(); + keyMapEnumerator.MoveNext(); + var keyMap = (Dictionary)keyMapEnumerator.Current; + float time = GetFloat(keyMap, "time", 0); + string color = (string)keyMap["color"]; + float r = ToColor(color, 0, 6); + float g = ToColor(color, 1, 6); + float b = ToColor(color, 2, 6); + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, r, g, b); + if (!keyMapEnumerator.MoveNext()) + { + timeline.Shrink(bezier); + break; + } + var nextMap = (Dictionary)keyMapEnumerator.Current; + + float time2 = GetFloat(nextMap, "time", 0); + color = (string)nextMap["color"]; + float nr = ToColor(color, 0, 6); + float ng = ToColor(color, 1, 6); + float nb = ToColor(color, 2, 6); + + if (keyMap.ContainsKey("curve")) + { + object curve = keyMap["curve"]; + bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1); + } + time = time2; + r = nr; + g = ng; + b = nb; + keyMap = nextMap; + } + timelines.Add(timeline); + + } + else if (timelineName == "alpha") + { + var keyMapEnumerator = values.GetEnumerator(); + keyMapEnumerator.MoveNext(); + timelines.Add(ReadTimeline(ref keyMapEnumerator, new AlphaTimeline(frames, frames, slotIndex), 0, 1)); + + } + else if (timelineName == "rgba2") + { + var timeline = new RGBA2Timeline(frames, frames * 7, slotIndex); + + var keyMapEnumerator = values.GetEnumerator(); + keyMapEnumerator.MoveNext(); + var keyMap = (Dictionary)keyMapEnumerator.Current; + float time = GetFloat(keyMap, "time", 0); + string color = (string)keyMap["light"]; + float r = ToColor(color, 0); + float g = ToColor(color, 1); + float b = ToColor(color, 2); + float a = ToColor(color, 3); + color = (string)keyMap["dark"]; + float r2 = ToColor(color, 0, 6); + float g2 = ToColor(color, 1, 6); + float b2 = ToColor(color, 2, 6); + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, r, g, b, a, r2, g2, b2); + if (!keyMapEnumerator.MoveNext()) + { + timeline.Shrink(bezier); + break; + } + var nextMap = (Dictionary)keyMapEnumerator.Current; + + float time2 = GetFloat(nextMap, "time", 0); + color = (string)nextMap["light"]; + float nr = ToColor(color, 0); + float ng = ToColor(color, 1); + float nb = ToColor(color, 2); + float na = ToColor(color, 3); + color = (string)nextMap["dark"]; + float nr2 = ToColor(color, 0, 6); + float ng2 = ToColor(color, 1, 6); + float nb2 = ToColor(color, 2, 6); + + if (keyMap.ContainsKey("curve")) + { + object curve = keyMap["curve"]; + bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 3, time, time2, a, na, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 4, time, time2, r2, nr2, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 5, time, time2, g2, ng2, 1); + bezier = ReadCurve(curve, 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; + keyMap = nextMap; + } + timelines.Add(timeline); + + } + else if (timelineName == "rgb2") + { + var timeline = new RGB2Timeline(frames, frames * 6, slotIndex); + + var keyMapEnumerator = values.GetEnumerator(); + keyMapEnumerator.MoveNext(); + var keyMap = (Dictionary)keyMapEnumerator.Current; + float time = GetFloat(keyMap, "time", 0); + string color = (string)keyMap["light"]; + float r = ToColor(color, 0, 6); + float g = ToColor(color, 1, 6); + float b = ToColor(color, 2, 6); + color = (string)keyMap["dark"]; + float r2 = ToColor(color, 0, 6); + float g2 = ToColor(color, 1, 6); + float b2 = ToColor(color, 2, 6); + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, r, g, b, r2, g2, b2); + if (!keyMapEnumerator.MoveNext()) + { + timeline.Shrink(bezier); + break; + } + var nextMap = (Dictionary)keyMapEnumerator.Current; + + float time2 = GetFloat(nextMap, "time", 0); + color = (string)nextMap["light"]; + float nr = ToColor(color, 0, 6); + float ng = ToColor(color, 1, 6); + float nb = ToColor(color, 2, 6); + color = (string)nextMap["dark"]; + float nr2 = ToColor(color, 0, 6); + float ng2 = ToColor(color, 1, 6); + float nb2 = ToColor(color, 2, 6); + + if (keyMap.ContainsKey("curve")) + { + object curve = keyMap["curve"]; + bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 3, time, time2, r2, nr2, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 4, time, time2, g2, ng2, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 5, time, time2, b2, nb2, 1); + } + time = time2; + r = nr; + g = ng; + b = nb; + r2 = nr2; + g2 = ng2; + b2 = nb2; + keyMap = nextMap; + } + timelines.Add(timeline); + + } + else + throw new Exception("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")"); + } + } + } + + // Bone timelines. + if (map.ContainsKey("bones")) + { + foreach (KeyValuePair entry in (Dictionary)map["bones"]) + { + string boneName = entry.Key; + int boneIndex = -1; + var bones = skeletonData.bones.Items; + for (int i = 0, n = skeletonData.bones.Count; i < n; i++) + { + if (bones[i].name == boneName) + { + boneIndex = i; + break; + } + } + if (boneIndex == -1) throw new Exception("Bone not found: " + boneName); + var timelineMap = (Dictionary)entry.Value; + foreach (KeyValuePair timelineEntry in timelineMap) + { + var values = (List)timelineEntry.Value; + var keyMapEnumerator = values.GetEnumerator(); + if (!keyMapEnumerator.MoveNext()) continue; + int frames = values.Count; + var timelineName = timelineEntry.Key; + if (timelineName == "rotate") + timelines.Add(ReadTimeline(ref keyMapEnumerator, new RotateTimeline(frames, frames, boneIndex), 0, 1)); + else if (timelineName == "translate") + { + TranslateTimeline timeline = new TranslateTimeline(frames, frames << 1, boneIndex); + timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, "x", "y", 0, scale)); + } + else if (timelineName == "translatex") + { + timelines + .Add(ReadTimeline(ref keyMapEnumerator, new TranslateXTimeline(frames, frames, boneIndex), 0, scale)); + } + else if (timelineName == "translatey") + { + timelines + .Add(ReadTimeline(ref keyMapEnumerator, new TranslateYTimeline(frames, frames, boneIndex), 0, scale)); + } + else if (timelineName == "scale") + { + ScaleTimeline timeline = new ScaleTimeline(frames, frames << 1, boneIndex); + timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, "x", "y", 1, 1)); + } + else if (timelineName == "scalex") + timelines.Add(ReadTimeline(ref keyMapEnumerator, new ScaleXTimeline(frames, frames, boneIndex), 1, 1)); + else if (timelineName == "scaley") + timelines.Add(ReadTimeline(ref keyMapEnumerator, new ScaleYTimeline(frames, frames, boneIndex), 1, 1)); + else if (timelineName == "shear") + { + ShearTimeline timeline = new ShearTimeline(frames, frames << 1, boneIndex); + timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, "x", "y", 0, 1)); + } + else if (timelineName == "shearx") + timelines.Add(ReadTimeline(ref keyMapEnumerator, new ShearXTimeline(frames, frames, boneIndex), 0, 1)); + else if (timelineName == "sheary") + timelines.Add(ReadTimeline(ref keyMapEnumerator, new ShearYTimeline(frames, frames, boneIndex), 0, 1)); + else + throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")"); + } + } + } + + // IK constraint timelines. + if (map.ContainsKey("ik")) + { + foreach (KeyValuePair timelineMap in (Dictionary)map["ik"]) + { + var values = (List)timelineMap.Value; + var keyMapEnumerator = values.GetEnumerator(); + if (!keyMapEnumerator.MoveNext()) continue; + var keyMap = (Dictionary)keyMapEnumerator.Current; + IkConstraintData constraint = skeletonData.FindIkConstraint(timelineMap.Key); + IkConstraintTimeline timeline = new IkConstraintTimeline(values.Count, values.Count << 1, + skeletonData.IkConstraints.IndexOf(constraint)); + float time = GetFloat(keyMap, "time", 0); + float mix = GetFloat(keyMap, "mix", 1), softness = GetFloat(keyMap, "softness", 0) * scale; + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, mix, softness, GetBoolean(keyMap, "bendPositive", true) ? 1 : -1, + GetBoolean(keyMap, "compress", false), GetBoolean(keyMap, "stretch", false)); + if (!keyMapEnumerator.MoveNext()) + { + timeline.Shrink(bezier); + break; + } + var nextMap = (Dictionary)keyMapEnumerator.Current; + float time2 = GetFloat(nextMap, "time", 0); + float mix2 = GetFloat(nextMap, "mix", 1), softness2 = GetFloat(nextMap, "softness", 0) * scale; + if (keyMap.ContainsKey("curve")) + { + object curve = keyMap["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. + if (map.ContainsKey("transform")) + { + foreach (KeyValuePair timelineMap in (Dictionary)map["transform"]) + { + var values = (List)timelineMap.Value; + var keyMapEnumerator = values.GetEnumerator(); + if (!keyMapEnumerator.MoveNext()) continue; + var keyMap = (Dictionary)keyMapEnumerator.Current; + TransformConstraintData constraint = skeletonData.FindTransformConstraint(timelineMap.Key); + TransformConstraintTimeline timeline = new TransformConstraintTimeline(values.Count, values.Count * 6, + skeletonData.TransformConstraints.IndexOf(constraint)); + float time = GetFloat(keyMap, "time", 0); + float mixRotate = GetFloat(keyMap, "mixRotate", 1), mixShearY = GetFloat(keyMap, "mixShearY", 1); + float mixX = GetFloat(keyMap, "mixX", 1), mixY = GetFloat(keyMap, "mixY", mixX); + float mixScaleX = GetFloat(keyMap, "mixScaleX", 1), mixScaleY = GetFloat(keyMap, "mixScaleY", mixScaleX); + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY); + if (!keyMapEnumerator.MoveNext()) + { + timeline.Shrink(bezier); + break; + } + var nextMap = (Dictionary)keyMapEnumerator.Current; + float time2 = GetFloat(nextMap, "time", 0); + float mixRotate2 = GetFloat(nextMap, "mixRotate", 1), mixShearY2 = GetFloat(nextMap, "mixShearY", 1); + float mixX2 = GetFloat(nextMap, "mixX", 1), mixY2 = GetFloat(nextMap, "mixY", mixX2); + float mixScaleX2 = GetFloat(nextMap, "mixScaleX", 1), mixScaleY2 = GetFloat(nextMap, "mixScaleY", mixScaleX2); + if (keyMap.ContainsKey("curve")) + { + object curve = keyMap["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; + mixScaleX = mixScaleX2; + keyMap = nextMap; + } + timelines.Add(timeline); + } + } + + // Path constraint timelines. + if (map.ContainsKey("path")) + { + foreach (KeyValuePair constraintMap in (Dictionary)map["path"]) + { + PathConstraintData constraint = skeletonData.FindPathConstraint(constraintMap.Key); + if (constraint == null) throw new Exception("Path constraint not found: " + constraintMap.Key); + int constraintIndex = skeletonData.pathConstraints.IndexOf(constraint); + var timelineMap = (Dictionary)constraintMap.Value; + foreach (KeyValuePair timelineEntry in timelineMap) + { + var values = (List)timelineEntry.Value; + var keyMapEnumerator = values.GetEnumerator(); + if (!keyMapEnumerator.MoveNext()) continue; + + int frames = values.Count; + var timelineName = timelineEntry.Key; + if (timelineName == "position") + { + CurveTimeline1 timeline = new PathConstraintPositionTimeline(frames, frames, constraintIndex); + timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, 0, constraint.positionMode == PositionMode.Fixed ? scale : 1)); + } + else if (timelineName == "spacing") + { + CurveTimeline1 timeline = new PathConstraintSpacingTimeline(frames, frames, constraintIndex); + timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, 0, + constraint.spacingMode == SpacingMode.Length || constraint.spacingMode == SpacingMode.Fixed ? scale : 1)); + } + else if (timelineName == "mix") + { + PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(frames, frames * 3, constraintIndex); + var keyMap = (Dictionary)keyMapEnumerator.Current; + float time = GetFloat(keyMap, "time", 0); + float mixRotate = GetFloat(keyMap, "mixRotate", 1); + float mixX = GetFloat(keyMap, "mixX", 1), mixY = GetFloat(keyMap, "mixY", mixX); + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, mixRotate, mixX, mixY); + if (!keyMapEnumerator.MoveNext()) + { + timeline.Shrink(bezier); + break; + } + var nextMap = (Dictionary)keyMapEnumerator.Current; + float time2 = GetFloat(nextMap, "time", 0); + float mixRotate2 = GetFloat(nextMap, "mixRotate", 1); + float mixX2 = GetFloat(nextMap, "mixX", 1), mixY2 = GetFloat(nextMap, "mixY", mixX2); + if (keyMap.ContainsKey("curve")) + { + object curve = keyMap["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); + } + time = time2; + mixRotate = mixRotate2; + mixX = mixX2; + mixY = mixY2; + keyMap = nextMap; + } + timelines.Add(timeline); + } + } + } + } + + // Deform timelines. + if (map.ContainsKey("deform")) + { + foreach (KeyValuePair deformMap in (Dictionary)map["deform"]) + { + Skin skin = skeletonData.FindSkin(deformMap.Key); + foreach (KeyValuePair slotMap in (Dictionary)deformMap.Value) + { + int slotIndex = FindSlotIndex(skeletonData, slotMap.Key); + foreach (KeyValuePair timelineMap in (Dictionary)slotMap.Value) + { + var values = (List)timelineMap.Value; + var keyMapEnumerator = values.GetEnumerator(); + if (!keyMapEnumerator.MoveNext()) continue; + var keyMap = (Dictionary)keyMapEnumerator.Current; + VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, timelineMap.Key); + if (attachment == null) throw new Exception("Deform attachment not found: " + timelineMap.Key); + bool weighted = attachment.bones != null; + float[] vertices = attachment.vertices; + int deformLength = weighted ? (vertices.Length / 3) << 1 : vertices.Length; + DeformTimeline timeline = new DeformTimeline(values.Count, values.Count, slotIndex, attachment); + float time = GetFloat(keyMap, "time", 0); + for (int frame = 0, bezier = 0; ; frame++) + { + float[] deform; + if (!keyMap.ContainsKey("vertices")) + { + deform = weighted ? new float[deformLength] : vertices; + } + else + { + deform = new float[deformLength]; + int start = GetInt(keyMap, "offset", 0); + float[] verticesValue = GetFloatArray(keyMap, "vertices", 1); + Array.Copy(verticesValue, 0, deform, start, verticesValue.Length); + if (scale != 1) + { + for (int i = start, n = i + verticesValue.Length; i < n; i++) + deform[i] *= scale; + } + + if (!weighted) + { + for (int i = 0; i < deformLength; i++) + deform[i] += vertices[i]; + } + } + + timeline.SetFrame(frame, time, deform); + if (!keyMapEnumerator.MoveNext()) + { + timeline.Shrink(bezier); + break; + } + var nextMap = (Dictionary)keyMapEnumerator.Current; + float time2 = GetFloat(nextMap, "time", 0); + if (keyMap.ContainsKey("curve")) + { + object curve = keyMap["curve"]; + bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, 0, 1, 1); + } + time = time2; + keyMap = nextMap; + } + timelines.Add(timeline); + } + } + } + } + + // Draw order timeline. + if (map.ContainsKey("drawOrder")) + { + var values = (List)map["drawOrder"]; + var timeline = new DrawOrderTimeline(values.Count); + int slotCount = skeletonData.slots.Count; + int frame = 0; + foreach (Dictionary drawOrderMap in values) + { + int[] drawOrder = null; + if (drawOrderMap.ContainsKey("offsets")) + { + drawOrder = new int[slotCount]; + for (int i = slotCount - 1; i >= 0; i--) + drawOrder[i] = -1; + var offsets = (List)drawOrderMap["offsets"]; + int[] unchanged = new int[slotCount - offsets.Count]; + int originalIndex = 0, unchangedIndex = 0; + foreach (Dictionary offsetMap in offsets) + { + int slotIndex = FindSlotIndex(skeletonData, (string)offsetMap["slot"]); + // Collect unchanged items. + while (originalIndex != slotIndex) + unchanged[unchangedIndex++] = originalIndex++; + // Set changed items. + int index = originalIndex + (int)(float)offsetMap["offset"]; + drawOrder[index] = originalIndex++; + } + // Collect remaining unchanged items. + while (originalIndex < slotCount) + unchanged[unchangedIndex++] = originalIndex++; + // Fill in unchanged items. + for (int i = slotCount - 1; i >= 0; i--) + if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex]; + } + timeline.SetFrame(frame, GetFloat(drawOrderMap, "time", 0), drawOrder); + ++frame; + } + timelines.Add(timeline); + } + + // Event timeline. + if (map.ContainsKey("events")) + { + var eventsMap = (List)map["events"]; + var timeline = new EventTimeline(eventsMap.Count); + int frame = 0; + foreach (Dictionary eventMap in eventsMap) + { + EventData eventData = skeletonData.FindEvent((string)eventMap["name"]); + if (eventData == null) throw new Exception("Event not found: " + eventMap["name"]); + var e = new Event(GetFloat(eventMap, "time", 0), eventData) + { + intValue = GetInt(eventMap, "int", eventData.Int), + floatValue = GetFloat(eventMap, "float", eventData.Float), + stringValue = GetString(eventMap, "string", eventData.String) + }; + if (e.data.AudioPath != null) + { + e.volume = GetFloat(eventMap, "volume", eventData.Volume); + e.balance = GetFloat(eventMap, "balance", eventData.Balance); + } + timeline.SetFrame(frame, e); + ++frame; + } + timelines.Add(timeline); + } + timelines.TrimExcess(); + float duration = 0; + var items = timelines.Items; + for (int i = 0, n = timelines.Count; i < n; i++) + duration = Math.Max(duration, items[i].Duration); + skeletonData.animations.Add(new Animation(name, timelines, duration)); + } + + static Timeline ReadTimeline(ref List.Enumerator keyMapEnumerator, CurveTimeline1 timeline, float defaultValue, float scale) + { + var keyMap = (Dictionary)keyMapEnumerator.Current; + float time = GetFloat(keyMap, "time", 0); + float value = GetFloat(keyMap, "value", defaultValue) * scale; + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, value); + if (!keyMapEnumerator.MoveNext()) + { + timeline.Shrink(bezier); + return timeline; + } + var nextMap = (Dictionary)keyMapEnumerator.Current; + float time2 = GetFloat(nextMap, "time", 0); + float value2 = GetFloat(nextMap, "value", defaultValue) * scale; + if (keyMap.ContainsKey("curve")) + { + object curve = keyMap["curve"]; + bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, value, value2, scale); + } + time = time2; + value = value2; + keyMap = nextMap; + } + } + + static Timeline ReadTimeline(ref List.Enumerator keyMapEnumerator, CurveTimeline2 timeline, String name1, String name2, float defaultValue, + float scale) + { + + var keyMap = (Dictionary)keyMapEnumerator.Current; + float time = GetFloat(keyMap, "time", 0); + float value1 = GetFloat(keyMap, name1, defaultValue) * scale, value2 = GetFloat(keyMap, name2, defaultValue) * scale; + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, value1, value2); + if (!keyMapEnumerator.MoveNext()) + { + timeline.Shrink(bezier); + return timeline; + } + var nextMap = (Dictionary)keyMapEnumerator.Current; + float time2 = GetFloat(nextMap, "time", 0); + float nvalue1 = GetFloat(nextMap, name1, defaultValue) * scale, nvalue2 = GetFloat(nextMap, name2, defaultValue) * scale; + if (keyMap.ContainsKey("curve")) + { + object curve = keyMap["curve"]; + 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; + } + } + + static int ReadCurve(object curve, CurveTimeline timeline, int bezier, int frame, int value, float time1, float time2, + float value1, float value2, float scale) + { + + string curveString = curve as string; + if (curveString != null) + { + if (curveString == "stepped") timeline.SetStepped(frame); + return bezier; + } + var curveValues = (List)curve; + int i = value << 2; + float cx1 = (float)curveValues[i]; + float cy1 = (float)curveValues[i + 1] * scale; + float cx2 = (float)curveValues[i + 2]; + float cy2 = (float)curveValues[i + 3] * scale; + SetBezier(timeline, frame, value, bezier, time1, value1, cx1, cy1, cx2, cy2, time2, value2); + return bezier + 1; + } + + 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) + { + timeline.SetBezier(bezier, frame, value, time1, value1, cx1, cy1, cx2, cy2, time2, value2); + } + + static float[] GetFloatArray(Dictionary map, string name, float scale) + { + var list = (List)map[name]; + var values = new float[list.Count]; + if (scale == 1) + { + for (int i = 0, n = list.Count; i < n; i++) + values[i] = (float)list[i]; + } + else + { + for (int i = 0, n = list.Count; i < n; i++) + values[i] = (float)list[i] * scale; + } + return values; + } + + static int[] GetIntArray(Dictionary map, string name) + { + var list = (List)map[name]; + var values = new int[list.Count]; + for (int i = 0, n = list.Count; i < n; i++) + values[i] = (int)(float)list[i]; + return values; + } + + static float GetFloat(Dictionary map, string name, float defaultValue) + { + if (!map.ContainsKey(name)) return defaultValue; + return (float)map[name]; + } + + static int GetInt(Dictionary map, string name, int defaultValue) + { + if (!map.ContainsKey(name)) return defaultValue; + return (int)(float)map[name]; + } + + static bool GetBoolean(Dictionary map, string name, bool defaultValue) + { + if (!map.ContainsKey(name)) return defaultValue; + return (bool)map[name]; + } + + static string GetString(Dictionary map, string name, string defaultValue) + { + if (!map.ContainsKey(name)) return defaultValue; + return (string)map[name]; + } + + static float ToColor(string hexString, int colorIndex, int expectedLength = 8) + { + if (hexString.Length != expectedLength) + throw new ArgumentException("Color hexidecimal length must be " + expectedLength + ", recieved: " + hexString, "hexString"); + return Convert.ToInt32(hexString.Substring(colorIndex * 2, 2), 16) / (float)255; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/SkeletonLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/SkeletonLoader.cs index 1645785..f9656fb 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/SkeletonLoader.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/SkeletonLoader.cs @@ -29,64 +29,71 @@ using System; using System.Collections.Generic; -using System.IO; -namespace Spine4_0_31 { +namespace Spine4_0_31 +{ - /// - /// Base class for loading skeleton data from a file. - /// - /// SeeJSON and binary data in the - /// Spine Runtimes Guide. - /// - public abstract class SkeletonLoader { - protected readonly AttachmentLoader attachmentLoader; - protected float scale = 1; - protected readonly List linkedMeshes = new List(); + /// + /// Base class for loading skeleton data from a file. + /// + /// SeeJSON and binary data in the + /// Spine Runtimes Guide. + /// + public abstract class SkeletonLoader + { + protected readonly AttachmentLoader attachmentLoader; + protected float scale = 1; + protected readonly List linkedMeshes = new List(); - /// Creates a skeleton loader that loads attachments using an with the specified atlas. - /// - public SkeletonLoader (params Atlas[] atlasArray) { - attachmentLoader = new AtlasAttachmentLoader(atlasArray); - } + /// Creates a skeleton loader that loads attachments using an with the specified atlas. + /// + public SkeletonLoader(params Atlas[] atlasArray) + { + attachmentLoader = new AtlasAttachmentLoader(atlasArray); + } - /// Creates a skeleton loader that loads attachments using the specified attachment loader. - /// See Loading skeleton data in the - /// Spine Runtimes Guide. - public SkeletonLoader (AttachmentLoader attachmentLoader) { - if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader", "attachmentLoader cannot be null."); - this.attachmentLoader = attachmentLoader; - } + /// Creates a skeleton loader that loads attachments using the specified attachment loader. + /// See Loading skeleton data in the + /// Spine Runtimes Guide. + public SkeletonLoader(AttachmentLoader attachmentLoader) + { + if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader", "attachmentLoader cannot be null."); + this.attachmentLoader = attachmentLoader; + } - /// Scales bone positions, image sizes, and translations as they are loaded. This allows different size images to be used at - /// runtime than were used in Spine. - /// - /// See Scaling in the Spine Runtimes Guide. - /// - public float Scale { - get { return scale; } - set { - if (scale == 0) throw new ArgumentNullException("scale", "scale cannot be 0."); - this.scale = value; - } - } + /// Scales bone positions, image sizes, and translations as they are loaded. This allows different size images to be used at + /// runtime than were used in Spine. + /// + /// See Scaling in the Spine Runtimes Guide. + /// + public float Scale + { + get { return scale; } + set + { + if (scale == 0) throw new ArgumentNullException("scale", "scale cannot be 0."); + this.scale = value; + } + } - public abstract SkeletonData ReadSkeletonData (string path); + public abstract SkeletonData ReadSkeletonData(string path); - protected class LinkedMesh { - internal string parent, skin; - internal int slotIndex; - internal MeshAttachment mesh; - internal bool inheritDeform; + protected class LinkedMesh + { + internal string parent, skin; + internal int slotIndex; + internal MeshAttachment mesh; + internal bool inheritDeform; - public LinkedMesh (MeshAttachment mesh, string skin, int slotIndex, string parent, bool inheritDeform) { - this.mesh = mesh; - this.skin = skin; - this.slotIndex = slotIndex; - this.parent = parent; - this.inheritDeform = inheritDeform; - } - } + public LinkedMesh(MeshAttachment mesh, string skin, int slotIndex, string parent, bool inheritDeform) + { + this.mesh = mesh; + this.skin = skin; + this.slotIndex = slotIndex; + this.parent = parent; + this.inheritDeform = inheritDeform; + } + } - } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Skin.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Skin.cs index b32f2e0..04d7cf6 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Skin.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Skin.cs @@ -29,178 +29,209 @@ *****************************************************************************/ using System; -using System.Collections; using System.Collections.Generic; -namespace Spine4_0_31 { - /// Stores attachments by slot index and attachment name. - /// See SkeletonData , Skeleton , and - /// Runtime skins in the Spine Runtimes Guide. - /// - public class Skin { - internal string name; - // Difference to reference implementation: using Dictionary instead of HashSet. - // Reason is that there is no efficient way to replace or access an already added element, losing any benefits. - private Dictionary attachments = new Dictionary(SkinKeyComparer.Instance); - internal readonly ExposedList bones = new ExposedList(); - internal readonly ExposedList constraints = new ExposedList(); - - public string Name { get { return name; } } - ///Returns all attachments contained in this skin. - public ICollection Attachments { get { return attachments.Values; } } - public ExposedList Bones { get { return bones; } } - public ExposedList Constraints { get { return constraints; } } - - public Skin (string name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; - } - - /// 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. - public void SetAttachment (int slotIndex, string name, Attachment attachment) { - if (attachment == null) throw new ArgumentNullException("attachment", "attachment cannot be null."); - attachments[new SkinKey(slotIndex, name)] = new SkinEntry(slotIndex, name, attachment); - } - - ///Adds all attachments, bones, and constraints from the specified skin to this skin. - public void AddSkin (Skin skin) { - foreach (BoneData data in skin.bones) - if (!bones.Contains(data)) bones.Add(data); - - foreach (ConstraintData data in skin.constraints) - if (!constraints.Contains(data)) constraints.Add(data); - - foreach (var item in skin.attachments) { - SkinEntry entry = item.Value; - SetAttachment(entry.slotIndex, entry.name, entry.attachment); - } - } - - ///Adds all attachments from the specified skin to this skin. Attachments are deep copied. - public void CopySkin (Skin skin) { - foreach (BoneData data in skin.bones) - if (!bones.Contains(data)) bones.Add(data); - - foreach (ConstraintData data in skin.constraints) - if (!constraints.Contains(data)) constraints.Add(data); - - foreach (var item in skin.attachments) { - SkinEntry entry = item.Value; - if (entry.attachment is MeshAttachment) { - SetAttachment(entry.slotIndex, entry.name, - entry.attachment != null ? ((MeshAttachment)entry.attachment).NewLinkedMesh() : null); - } else - SetAttachment(entry.slotIndex, entry.name, entry.attachment != null ? entry.attachment.Copy() : null); - } - } - - /// Returns the attachment for the specified slot index and name, or null. - /// May be null. - public Attachment GetAttachment (int slotIndex, string name) { - SkinEntry entry; - bool containsKey = attachments.TryGetValue(new SkinKey(slotIndex, name), out entry); - return containsKey ? entry.attachment : null; - } - - /// Removes the attachment in the skin for the specified slot index and name, if any. - public void RemoveAttachment (int slotIndex, string name) { - attachments.Remove(new SkinKey(slotIndex, name)); - } - - /// Returns all attachments in this skin for the specified slot index. - /// The target slotIndex. To find the slot index, use and . - public void GetAttachments (int slotIndex, List attachments) { - if (slotIndex < 0) throw new ArgumentException("slotIndex must be >= 0."); - if (attachments == null) throw new ArgumentNullException("attachments", "attachments cannot be null."); - foreach (var item in this.attachments) { - SkinEntry entry = item.Value; - if (entry.slotIndex == slotIndex) attachments.Add(entry); - } - } - - ///Clears all attachments, bones, and constraints. - public void Clear () { - attachments.Clear(); - bones.Clear(); - constraints.Clear(); - } - - override public string ToString () { - return name; - } - - /// Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached. - internal void AttachAll (Skeleton skeleton, Skin oldSkin) { - Slot[] slots = skeleton.slots.Items; - foreach (var item in oldSkin.attachments) { - SkinEntry entry = item.Value; - int slotIndex = entry.slotIndex; - Slot slot = slots[slotIndex]; - if (slot.Attachment == entry.attachment) { - Attachment attachment = GetAttachment(slotIndex, entry.name); - if (attachment != null) slot.Attachment = attachment; - } - } - } - - /// Stores an entry in the skin consisting of the slot index, name, and attachment. - public struct SkinEntry { - internal readonly int slotIndex; - internal readonly string name; - internal readonly Attachment attachment; - - public SkinEntry (int slotIndex, string name, Attachment attachment) { - this.slotIndex = slotIndex; - this.name = name; - this.attachment = attachment; - } - - public int SlotIndex { - get { - return slotIndex; - } - } - - /// The name the attachment is associated with, equivalent to the skin placeholder name in the Spine editor. - public String Name { - get { - return name; - } - } - - public Attachment Attachment { - get { - return attachment; - } - } - } - - private struct SkinKey { - internal readonly int slotIndex; - internal readonly string name; - internal readonly int hashCode; - - public SkinKey (int slotIndex, string name) { - if (slotIndex < 0) throw new ArgumentException("slotIndex must be >= 0."); - if (name == null) throw new ArgumentNullException("name", "name cannot be null"); - this.slotIndex = slotIndex; - this.name = name; - this.hashCode = name.GetHashCode() + slotIndex * 37; - } - } - - class SkinKeyComparer : IEqualityComparer { - internal static readonly SkinKeyComparer Instance = new SkinKeyComparer(); - - bool IEqualityComparer.Equals (SkinKey e1, SkinKey e2) { - return e1.slotIndex == e2.slotIndex && string.Equals(e1.name, e2.name, StringComparison.Ordinal); - } - - int IEqualityComparer.GetHashCode (SkinKey e) { - return e.hashCode; - } - } - } +namespace Spine4_0_31 +{ + /// Stores attachments by slot index and attachment name. + /// See SkeletonData , Skeleton , and + /// Runtime skins in the Spine Runtimes Guide. + /// + public class Skin + { + internal string name; + // Difference to reference implementation: using Dictionary instead of HashSet. + // Reason is that there is no efficient way to replace or access an already added element, losing any benefits. + private Dictionary attachments = new Dictionary(SkinKeyComparer.Instance); + internal readonly ExposedList bones = new ExposedList(); + internal readonly ExposedList constraints = new ExposedList(); + + public string Name { get { return name; } } + ///Returns all attachments contained in this skin. + public ICollection Attachments { get { return attachments.Values; } } + public ExposedList Bones { get { return bones; } } + public ExposedList Constraints { get { return constraints; } } + + public Skin(string name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + } + + /// 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. + public void SetAttachment(int slotIndex, string name, Attachment attachment) + { + if (attachment == null) throw new ArgumentNullException("attachment", "attachment cannot be null."); + attachments[new SkinKey(slotIndex, name)] = new SkinEntry(slotIndex, name, attachment); + } + + ///Adds all attachments, bones, and constraints from the specified skin to this skin. + public void AddSkin(Skin skin) + { + foreach (BoneData data in skin.bones) + if (!bones.Contains(data)) bones.Add(data); + + foreach (ConstraintData data in skin.constraints) + if (!constraints.Contains(data)) constraints.Add(data); + + foreach (var item in skin.attachments) + { + SkinEntry entry = item.Value; + SetAttachment(entry.slotIndex, entry.name, entry.attachment); + } + } + + ///Adds all attachments from the specified skin to this skin. Attachments are deep copied. + public void CopySkin(Skin skin) + { + foreach (BoneData data in skin.bones) + if (!bones.Contains(data)) bones.Add(data); + + foreach (ConstraintData data in skin.constraints) + if (!constraints.Contains(data)) constraints.Add(data); + + foreach (var item in skin.attachments) + { + SkinEntry entry = item.Value; + if (entry.attachment is MeshAttachment) + { + SetAttachment(entry.slotIndex, entry.name, + entry.attachment != null ? ((MeshAttachment)entry.attachment).NewLinkedMesh() : null); + } + else + SetAttachment(entry.slotIndex, entry.name, entry.attachment != null ? entry.attachment.Copy() : null); + } + } + + /// Returns the attachment for the specified slot index and name, or null. + /// May be null. + public Attachment GetAttachment(int slotIndex, string name) + { + SkinEntry entry; + bool containsKey = attachments.TryGetValue(new SkinKey(slotIndex, name), out entry); + return containsKey ? entry.attachment : null; + } + + /// Removes the attachment in the skin for the specified slot index and name, if any. + public void RemoveAttachment(int slotIndex, string name) + { + attachments.Remove(new SkinKey(slotIndex, name)); + } + + /// Returns all attachments in this skin for the specified slot index. + /// The target slotIndex. To find the slot index, use and . + public void GetAttachments(int slotIndex, List attachments) + { + if (slotIndex < 0) throw new ArgumentException("slotIndex must be >= 0."); + if (attachments == null) throw new ArgumentNullException("attachments", "attachments cannot be null."); + foreach (var item in this.attachments) + { + SkinEntry entry = item.Value; + if (entry.slotIndex == slotIndex) attachments.Add(entry); + } + } + + ///Clears all attachments, bones, and constraints. + public void Clear() + { + attachments.Clear(); + bones.Clear(); + constraints.Clear(); + } + + override public string ToString() + { + return name; + } + + /// Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached. + internal void AttachAll(Skeleton skeleton, Skin oldSkin) + { + Slot[] slots = skeleton.slots.Items; + foreach (var item in oldSkin.attachments) + { + SkinEntry entry = item.Value; + int slotIndex = entry.slotIndex; + Slot slot = slots[slotIndex]; + if (slot.Attachment == entry.attachment) + { + Attachment attachment = GetAttachment(slotIndex, entry.name); + if (attachment != null) slot.Attachment = attachment; + } + } + } + + /// Stores an entry in the skin consisting of the slot index, name, and attachment. + public struct SkinEntry + { + internal readonly int slotIndex; + internal readonly string name; + internal readonly Attachment attachment; + + public SkinEntry(int slotIndex, string name, Attachment attachment) + { + this.slotIndex = slotIndex; + this.name = name; + this.attachment = attachment; + } + + public int SlotIndex + { + get + { + return slotIndex; + } + } + + /// The name the attachment is associated with, equivalent to the skin placeholder name in the Spine editor. + public String Name + { + get + { + return name; + } + } + + public Attachment Attachment + { + get + { + return attachment; + } + } + } + + private struct SkinKey + { + internal readonly int slotIndex; + internal readonly string name; + internal readonly int hashCode; + + public SkinKey(int slotIndex, string name) + { + if (slotIndex < 0) throw new ArgumentException("slotIndex must be >= 0."); + if (name == null) throw new ArgumentNullException("name", "name cannot be null"); + this.slotIndex = slotIndex; + this.name = name; + this.hashCode = name.GetHashCode() + slotIndex * 37; + } + } + + class SkinKeyComparer : IEqualityComparer + { + internal static readonly SkinKeyComparer Instance = new SkinKeyComparer(); + + bool IEqualityComparer.Equals(SkinKey e1, SkinKey e2) + { + return e1.slotIndex == e2.slotIndex && string.Equals(e1.name, e2.name, StringComparison.Ordinal); + } + + int IEqualityComparer.GetHashCode(SkinKey e) + { + return e.hashCode; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Slot.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Slot.cs index 4a118d4..ce0692e 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Slot.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Slot.cs @@ -29,172 +29,193 @@ using System; -namespace Spine4_0_31 { - - /// - /// Stores a slot's current pose. Slots organize attachments for purposes and provide a place to store - /// state for an attachment.State cannot be stored in an attachment itself because attachments are stateless and may be shared - /// across multiple skeletons. - /// - public class Slot { - internal SlotData data; - internal Bone bone; - internal float r, g, b, a; - internal float r2, g2, b2; - internal bool hasSecondColor; - internal Attachment attachment; - internal float attachmentTime; - internal ExposedList deform = new ExposedList(); - internal int attachmentState; - - public Slot (SlotData data, Bone bone) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); - this.data = data; - this.bone = bone; - - // darkColor = data.darkColor == null ? null : new Color(); - if (data.hasSecondColor) { - r2 = g2 = b2 = 0; - } - - SetToSetupPose(); - } - - /// Copy constructor. - public Slot (Slot slot, Bone bone) { - if (slot == null) throw new ArgumentNullException("slot", "slot cannot be null."); - if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); - data = slot.data; - this.bone = bone; - r = slot.r; - g = slot.g; - b = slot.b; - a = slot.a; - - // darkColor = slot.darkColor == null ? null : new Color(slot.darkColor); - if (slot.hasSecondColor) { - r2 = slot.r2; - g2 = slot.g2; - b2 = slot.b2; - } else { - r2 = g2 = b2 = 0; - } - hasSecondColor = slot.hasSecondColor; - - attachment = slot.attachment; - attachmentTime = slot.attachmentTime; - deform.AddRange(slot.deform); - } - - /// The slot's setup pose data. - public SlotData Data { get { return data; } } - /// The bone this slot belongs to. - public Bone Bone { get { return bone; } } - /// The skeleton this slot belongs to. - public Skeleton Skeleton { get { return bone.skeleton; } } - /// The color used to tint the slot's attachment. If is set, this is used as the light color for two - /// color tinting. - public float R { get { return r; } set { r = value; } } - /// The color used to tint the slot's attachment. If is set, this is used as the light color for two - /// color tinting. - public float G { get { return g; } set { g = value; } } - /// The color used to tint the slot's attachment. If is set, this is used as the light color for two - /// color tinting. - public float B { get { return b; } set { b = value; } } - /// The color used to tint the slot's attachment. If is set, this is used as the light color for two - /// color tinting. - public float A { get { return a; } set { a = value; } } - - public void ClampColor () { - r = MathUtils.Clamp(r, 0, 1); - g = MathUtils.Clamp(g, 0, 1); - b = MathUtils.Clamp(b, 0, 1); - a = MathUtils.Clamp(a, 0, 1); - } - - /// The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used. - /// - public float R2 { get { return r2; } set { r2 = value; } } - /// The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used. - /// - public float G2 { get { return g2; } set { g2 = value; } } - /// The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used. - /// - public float B2 { get { return b2; } set { b2 = value; } } - /// Whether R2 G2 B2 are used to tint the slot's attachment for two color tinting. False if two color tinting is not used. - public bool HasSecondColor { get { return data.hasSecondColor; } set { data.hasSecondColor = value; } } - - public void ClampSecondColor () { - r2 = MathUtils.Clamp(r2, 0, 1); - g2 = MathUtils.Clamp(g2, 0, 1); - b2 = MathUtils.Clamp(b2, 0, 1); - } - - public Attachment Attachment { - /// The current attachment for the slot, or null if the slot has no attachment. - get { return attachment; } - /// - /// Sets the slot's attachment and, if the attachment changed, resets and clears the . - /// The deform is not cleared if the old attachment has the same as the specified - /// attachment. - /// May be null. - set { - if (attachment == value) return; - if (!(value is VertexAttachment) || !(this.attachment is VertexAttachment) - || ((VertexAttachment)value).DeformAttachment != ((VertexAttachment)this.attachment).DeformAttachment) { - deform.Clear(); - } - this.attachment = value; - attachmentTime = bone.skeleton.time; - } - } - - /// The time that has elapsed since the last time the attachment was set or cleared. Relies on Skeleton - /// - public float AttachmentTime { - get { return bone.skeleton.time - attachmentTime; } - set { attachmentTime = bone.skeleton.time - value; } - } - - /// Vertices to deform the slot's attachment. For an unweighted mesh, the entries are local positions for each vertex. For a - /// weighted mesh, the entries are an offset for each vertex which will be added to the mesh's local vertex positions. - /// - /// See and . - public ExposedList Deform { - get { - return deform; - } - set { - if (deform == null) throw new ArgumentNullException("deform", "deform cannot be null."); - deform = value; - } - } - - /// Sets this slot to the setup pose. - public void SetToSetupPose () { - r = data.r; - g = data.g; - b = data.b; - a = data.a; - - // if (darkColor != null) darkColor.set(data.darkColor); - if (HasSecondColor) { - r2 = data.r2; - g2 = data.g2; - b2 = data.b2; - } - - if (data.attachmentName == null) - Attachment = null; - else { - attachment = null; - Attachment = bone.skeleton.GetAttachment(data.index, data.attachmentName); - } - } - - override public string ToString () { - return data.name; - } - } +namespace Spine4_0_31 +{ + + /// + /// Stores a slot's current pose. Slots organize attachments for purposes and provide a place to store + /// state for an attachment.State cannot be stored in an attachment itself because attachments are stateless and may be shared + /// across multiple skeletons. + /// + public class Slot + { + internal SlotData data; + internal Bone bone; + internal float r, g, b, a; + internal float r2, g2, b2; + internal bool hasSecondColor; + internal Attachment attachment; + internal float attachmentTime; + internal ExposedList deform = new ExposedList(); + internal int attachmentState; + + public Slot(SlotData data, Bone bone) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); + this.data = data; + this.bone = bone; + + // darkColor = data.darkColor == null ? null : new Color(); + if (data.hasSecondColor) + { + r2 = g2 = b2 = 0; + } + + SetToSetupPose(); + } + + /// Copy constructor. + public Slot(Slot slot, Bone bone) + { + if (slot == null) throw new ArgumentNullException("slot", "slot cannot be null."); + if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); + data = slot.data; + this.bone = bone; + r = slot.r; + g = slot.g; + b = slot.b; + a = slot.a; + + // darkColor = slot.darkColor == null ? null : new Color(slot.darkColor); + if (slot.hasSecondColor) + { + r2 = slot.r2; + g2 = slot.g2; + b2 = slot.b2; + } + else + { + r2 = g2 = b2 = 0; + } + hasSecondColor = slot.hasSecondColor; + + attachment = slot.attachment; + attachmentTime = slot.attachmentTime; + deform.AddRange(slot.deform); + } + + /// The slot's setup pose data. + public SlotData Data { get { return data; } } + /// The bone this slot belongs to. + public Bone Bone { get { return bone; } } + /// The skeleton this slot belongs to. + public Skeleton Skeleton { get { return bone.skeleton; } } + /// The color used to tint the slot's attachment. If is set, this is used as the light color for two + /// color tinting. + public float R { get { return r; } set { r = value; } } + /// The color used to tint the slot's attachment. If is set, this is used as the light color for two + /// color tinting. + public float G { get { return g; } set { g = value; } } + /// The color used to tint the slot's attachment. If is set, this is used as the light color for two + /// color tinting. + public float B { get { return b; } set { b = value; } } + /// The color used to tint the slot's attachment. If is set, this is used as the light color for two + /// color tinting. + public float A { get { return a; } set { a = value; } } + + public void ClampColor() + { + r = MathUtils.Clamp(r, 0, 1); + g = MathUtils.Clamp(g, 0, 1); + b = MathUtils.Clamp(b, 0, 1); + a = MathUtils.Clamp(a, 0, 1); + } + + /// The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used. + /// + public float R2 { get { return r2; } set { r2 = value; } } + /// The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used. + /// + public float G2 { get { return g2; } set { g2 = value; } } + /// The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used. + /// + public float B2 { get { return b2; } set { b2 = value; } } + /// Whether R2 G2 B2 are used to tint the slot's attachment for two color tinting. False if two color tinting is not used. + public bool HasSecondColor { get { return data.hasSecondColor; } set { data.hasSecondColor = value; } } + + public void ClampSecondColor() + { + r2 = MathUtils.Clamp(r2, 0, 1); + g2 = MathUtils.Clamp(g2, 0, 1); + b2 = MathUtils.Clamp(b2, 0, 1); + } + + public Attachment Attachment + { + /// The current attachment for the slot, or null if the slot has no attachment. + get { return attachment; } + /// + /// Sets the slot's attachment and, if the attachment changed, resets and clears the . + /// The deform is not cleared if the old attachment has the same as the specified + /// attachment. + /// May be null. + set + { + if (attachment == value) return; + if (!(value is VertexAttachment) || !(this.attachment is VertexAttachment) + || ((VertexAttachment)value).DeformAttachment != ((VertexAttachment)this.attachment).DeformAttachment) + { + deform.Clear(); + } + this.attachment = value; + attachmentTime = bone.skeleton.time; + } + } + + /// The time that has elapsed since the last time the attachment was set or cleared. Relies on Skeleton + /// + public float AttachmentTime + { + get { return bone.skeleton.time - attachmentTime; } + set { attachmentTime = bone.skeleton.time - value; } + } + + /// Vertices to deform the slot's attachment. For an unweighted mesh, the entries are local positions for each vertex. For a + /// weighted mesh, the entries are an offset for each vertex which will be added to the mesh's local vertex positions. + /// + /// See and . + public ExposedList Deform + { + get + { + return deform; + } + set + { + if (deform == null) throw new ArgumentNullException("deform", "deform cannot be null."); + deform = value; + } + } + + /// Sets this slot to the setup pose. + public void SetToSetupPose() + { + r = data.r; + g = data.g; + b = data.b; + a = data.a; + + // if (darkColor != null) darkColor.set(data.darkColor); + if (HasSecondColor) + { + r2 = data.r2; + g2 = data.g2; + b2 = data.b2; + } + + if (data.attachmentName == null) + Attachment = null; + else + { + attachment = null; + Attachment = bone.skeleton.GetAttachment(data.index, data.attachmentName); + } + } + + override public string ToString() + { + return data.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/SlotData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/SlotData.cs index 703ce24..2b5ecf6 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/SlotData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/SlotData.cs @@ -29,49 +29,53 @@ using System; -namespace Spine4_0_31 { - public class SlotData { - internal int index; - internal string name; - internal BoneData boneData; - internal float r = 1, g = 1, b = 1, a = 1; - internal float r2 = 0, g2 = 0, b2 = 0; - internal bool hasSecondColor = false; - internal string attachmentName; - internal BlendMode blendMode; +namespace Spine4_0_31 +{ + public class SlotData + { + internal int index; + internal string name; + internal BoneData boneData; + internal float r = 1, g = 1, b = 1, a = 1; + internal float r2 = 0, g2 = 0, b2 = 0; + internal bool hasSecondColor = false; + internal string attachmentName; + internal BlendMode blendMode; - /// The index of the slot in . - public int Index { get { return index; } } - /// The name of the slot, which is unique across all slots in the skeleton. - public string Name { get { return name; } } - /// The bone this slot belongs to. - public BoneData BoneData { get { return boneData; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } + /// The index of the slot in . + public int Index { get { return index; } } + /// The name of the slot, which is unique across all slots in the skeleton. + public string Name { get { return name; } } + /// The bone this slot belongs to. + public BoneData BoneData { get { return boneData; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } - public float R2 { get { return r2; } set { r2 = value; } } - public float G2 { get { return g2; } set { g2 = value; } } - public float B2 { get { return b2; } set { b2 = value; } } - public bool HasSecondColor { get { return hasSecondColor; } set { hasSecondColor = value; } } + public float R2 { get { return r2; } set { r2 = value; } } + public float G2 { get { return g2; } set { g2 = value; } } + public float B2 { get { return b2; } set { b2 = value; } } + public bool HasSecondColor { get { return hasSecondColor; } set { hasSecondColor = value; } } - /// The name of the attachment that is visible for this slot in the setup pose, or null if no attachment is visible. - public String AttachmentName { get { return attachmentName; } set { attachmentName = value; } } - /// The blend mode for drawing the slot's attachment. - public BlendMode BlendMode { get { return blendMode; } set { blendMode = value; } } + /// The name of the attachment that is visible for this slot in the setup pose, or null if no attachment is visible. + public String AttachmentName { get { return attachmentName; } set { attachmentName = value; } } + /// The blend mode for drawing the slot's attachment. + public BlendMode BlendMode { get { return blendMode; } set { blendMode = value; } } - public SlotData (int index, String name, BoneData boneData) { - if (index < 0) throw new ArgumentException("index must be >= 0.", "index"); - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - if (boneData == null) throw new ArgumentNullException("boneData", "boneData cannot be null."); - this.index = index; - this.name = name; - this.boneData = boneData; - } + public SlotData(int index, String name, BoneData boneData) + { + if (index < 0) throw new ArgumentException("index must be >= 0.", "index"); + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + if (boneData == null) throw new ArgumentNullException("boneData", "boneData cannot be null."); + this.index = index; + this.name = name; + this.boneData = boneData; + } - override public string ToString () { - return name; - } - } + override public string ToString() + { + return name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/TransformConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/TransformConstraint.cs index 11ed469..300bf42 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/TransformConstraint.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/TransformConstraint.cs @@ -29,283 +29,312 @@ using System; -namespace Spine4_0_31 { - /// - /// - /// Stores the current pose for a transform constraint. A transform constraint adjusts the world transform of the constrained - /// bones to match that of the target bone. - /// - /// See Transform constraints in the Spine User Guide. - /// - public class TransformConstraint : IUpdatable { - internal TransformConstraintData data; - internal ExposedList bones; - internal Bone target; - internal float mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY; - - internal bool active; - - public TransformConstraint (TransformConstraintData data, Skeleton skeleton) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.data = data; - mixRotate = data.mixRotate; - mixX = data.mixX; - mixY = data.mixY; - mixScaleX = data.mixScaleX; - mixScaleY = data.mixScaleY; - mixShearY = data.mixShearY; - bones = new ExposedList(); - foreach (BoneData boneData in data.bones) - bones.Add(skeleton.FindBone(boneData.name)); - - target = skeleton.FindBone(data.target.name); - } - - /// Copy constructor. - public TransformConstraint (TransformConstraint constraint, Skeleton skeleton) { - if (constraint == null) throw new ArgumentNullException("constraint cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); - data = constraint.data; - bones = new ExposedList(constraint.Bones.Count); - foreach (Bone bone in constraint.Bones) - bones.Add(skeleton.Bones.Items[bone.data.index]); - target = skeleton.Bones.Items[constraint.target.data.index]; - mixRotate = constraint.mixRotate; - mixX = constraint.mixX; - mixY = constraint.mixY; - mixScaleX = constraint.mixScaleX; - mixScaleY = constraint.mixScaleY; - mixShearY = constraint.mixShearY; - } - - public void Update () { - if (mixRotate == 0 && mixX == 0 && mixY == 0 && mixScaleX == 0 && mixScaleX == 0 && mixShearY == 0) return; - if (data.local) { - if (data.relative) - ApplyRelativeLocal(); - else - ApplyAbsoluteLocal(); - } else { - if (data.relative) - ApplyRelativeWorld(); - else - ApplyAbsoluteWorld(); - } - } - - void ApplyAbsoluteWorld () { - float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX, - mixScaleY = this.mixScaleY, mixShearY = this.mixShearY; - bool translate = mixX != 0 || mixY != 0; - - Bone target = this.target; - float ta = target.a, tb = target.b, tc = target.c, td = target.d; - float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad; - float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect; - - var bones = this.bones.Items; - for (int i = 0, n = this.bones.Count; i < n; i++) { - Bone bone = bones[i]; - - if (mixRotate != 0) { - float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - float r = MathUtils.Atan2(tc, ta) - MathUtils.Atan2(c, a) + offsetRotation; - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) // - r += MathUtils.PI2; - r *= mixRotate; - float cos = MathUtils.Cos(r), sin = MathUtils.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; //Vector2 temp = this.temp; - target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY)); - bone.worldX += (tx - bone.worldX) * mixX; - bone.worldY += (ty - bone.worldY) * mixY; - } - - if (mixScaleX != 0) { - float s = (float)Math.Sqrt(bone.a * bone.a + bone.c * bone.c); - if (s != 0) s = (s + ((float)Math.Sqrt(ta * ta + tc * tc) - s + data.offsetScaleX) * mixScaleX) / s; - bone.a *= s; - bone.c *= s; - } - if (mixScaleY != 0) { - float s = (float)Math.Sqrt(bone.b * bone.b + bone.d * bone.d); - if (s != 0) s = (s + ((float)Math.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 = MathUtils.Atan2(d, b); - float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta) - (by - MathUtils.Atan2(bone.c, bone.a)); - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) // - r += MathUtils.PI2; - r = by + (r + offsetShearY) * mixShearY; - float s = (float)Math.Sqrt(b * b + d * d); - bone.b = MathUtils.Cos(r) * s; - bone.d = MathUtils.Sin(r) * s; - } - - bone.UpdateAppliedTransform(); - } - } - - void ApplyRelativeWorld () { - float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX, - mixScaleY = this.mixScaleY, mixShearY = this.mixShearY; - bool translate = mixX != 0 || mixY != 0; - - Bone target = this.target; - float ta = target.a, tb = target.b, tc = target.c, td = target.d; - float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad; - float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect; - - var bones = this.bones.Items; - for (int i = 0, n = this.bones.Count; i < n; i++) { - Bone bone = bones[i]; - - if (mixRotate != 0) { - float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - float r = MathUtils.Atan2(tc, ta) + offsetRotation; - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) // - r += MathUtils.PI2; - r *= mixRotate; - float cos = MathUtils.Cos(r), sin = MathUtils.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; //Vector2 temp = this.temp; - target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY)); - bone.worldX += tx * mixX; - bone.worldY += ty * mixY; - } - - if (mixScaleX != 0) { - float s = ((float)Math.Sqrt(ta * ta + tc * tc) - 1 + data.offsetScaleX) * mixScaleX + 1; - bone.a *= s; - bone.c *= s; - } - if (mixScaleY != 0) { - float s = ((float)Math.Sqrt(tb * tb + td * td) - 1 + data.offsetScaleY) * mixScaleY + 1; - bone.b *= s; - bone.d *= s; - } - - if (mixShearY > 0) { - float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta); - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) // - r += MathUtils.PI2; - float b = bone.b, d = bone.d; - r = MathUtils.Atan2(d, b) + (r - MathUtils.PI / 2 + offsetShearY) * mixShearY; - float s = (float)Math.Sqrt(b * b + d * d); - bone.b = MathUtils.Cos(r) * s; - bone.d = MathUtils.Sin(r) * s; - } - - bone.UpdateAppliedTransform(); - } - } - - void ApplyAbsoluteLocal () { - float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX, - mixScaleY = this.mixScaleY, mixShearY = this.mixShearY; - - Bone target = this.target; - - var bones = this.bones.Items; - for (int i = 0, n = this.bones.Count; i < n; i++) { - Bone bone = bones[i]; - - float rotation = bone.arotation; - if (mixRotate != 0) { - float r = target.arotation - rotation + data.offsetRotation; - r -= (16384 - (int)(16384.499999999996 - r / 360)) * 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 -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; - shearY += r * mixShearY; - } - - bone.UpdateWorldTransform(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY); - } - } - - void ApplyRelativeLocal () { - float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX, - mixScaleY = this.mixScaleY, mixShearY = this.mixShearY; - - Bone target = this.target; - - var bones = this.bones.Items; - for (int i = 0, n = this.bones.Count; i < n; i++) { - Bone bone = bones[i]; - - 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); - } - } - - /// The bones that will be modified by this transform constraint. - public ExposedList Bones { get { return bones; } } - /// The target bone whose world transform will be copied to the constrained bones. - public Bone Target { get { return target; } set { target = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. - public float MixRotate { get { return mixRotate; } set { mixRotate = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. - public float MixX { get { return mixX; } set { mixX = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. - public float MixY { get { return mixY; } set { mixY = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained scale X. - public float MixScaleX { get { return mixScaleX; } set { mixScaleX = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained scale Y. - public float MixScaleY { get { return mixScaleY; } set { mixScaleY = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained shear Y. - public float MixShearY { get { return mixShearY; } set { mixShearY = value; } } - public bool Active { get { return active; } } - /// The transform constraint's setup pose data. - public TransformConstraintData Data { get { return data; } } - - override public string ToString () { - return data.name; - } - } +namespace Spine4_0_31 +{ + /// + /// + /// Stores the current pose for a transform constraint. A transform constraint adjusts the world transform of the constrained + /// bones to match that of the target bone. + /// + /// See Transform constraints in the Spine User Guide. + /// + public class TransformConstraint : IUpdatable + { + internal TransformConstraintData data; + internal ExposedList bones; + internal Bone target; + internal float mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY; + + internal bool active; + + public TransformConstraint(TransformConstraintData data, Skeleton skeleton) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + mixRotate = data.mixRotate; + mixX = data.mixX; + mixY = data.mixY; + mixScaleX = data.mixScaleX; + mixScaleY = data.mixScaleY; + mixShearY = data.mixShearY; + bones = new ExposedList(); + foreach (BoneData boneData in data.bones) + bones.Add(skeleton.FindBone(boneData.name)); + + target = skeleton.FindBone(data.target.name); + } + + /// Copy constructor. + public TransformConstraint(TransformConstraint constraint, Skeleton skeleton) + { + if (constraint == null) throw new ArgumentNullException("constraint cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); + data = constraint.data; + bones = new ExposedList(constraint.Bones.Count); + foreach (Bone bone in constraint.Bones) + bones.Add(skeleton.Bones.Items[bone.data.index]); + target = skeleton.Bones.Items[constraint.target.data.index]; + mixRotate = constraint.mixRotate; + mixX = constraint.mixX; + mixY = constraint.mixY; + mixScaleX = constraint.mixScaleX; + mixScaleY = constraint.mixScaleY; + mixShearY = constraint.mixShearY; + } + + public void Update() + { + if (mixRotate == 0 && mixX == 0 && mixY == 0 && mixScaleX == 0 && mixScaleX == 0 && mixShearY == 0) return; + if (data.local) + { + if (data.relative) + ApplyRelativeLocal(); + else + ApplyAbsoluteLocal(); + } + else + { + if (data.relative) + ApplyRelativeWorld(); + else + ApplyAbsoluteWorld(); + } + } + + void ApplyAbsoluteWorld() + { + float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX, + mixScaleY = this.mixScaleY, mixShearY = this.mixShearY; + bool translate = mixX != 0 || mixY != 0; + + Bone target = this.target; + float ta = target.a, tb = target.b, tc = target.c, td = target.d; + float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad; + float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect; + + var bones = this.bones.Items; + for (int i = 0, n = this.bones.Count; i < n; i++) + { + Bone bone = bones[i]; + + if (mixRotate != 0) + { + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + float r = MathUtils.Atan2(tc, ta) - MathUtils.Atan2(c, a) + offsetRotation; + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) // + r += MathUtils.PI2; + r *= mixRotate; + float cos = MathUtils.Cos(r), sin = MathUtils.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; //Vector2 temp = this.temp; + target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY)); + bone.worldX += (tx - bone.worldX) * mixX; + bone.worldY += (ty - bone.worldY) * mixY; + } + + if (mixScaleX != 0) + { + float s = (float)Math.Sqrt(bone.a * bone.a + bone.c * bone.c); + if (s != 0) s = (s + ((float)Math.Sqrt(ta * ta + tc * tc) - s + data.offsetScaleX) * mixScaleX) / s; + bone.a *= s; + bone.c *= s; + } + if (mixScaleY != 0) + { + float s = (float)Math.Sqrt(bone.b * bone.b + bone.d * bone.d); + if (s != 0) s = (s + ((float)Math.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 = MathUtils.Atan2(d, b); + float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta) - (by - MathUtils.Atan2(bone.c, bone.a)); + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) // + r += MathUtils.PI2; + r = by + (r + offsetShearY) * mixShearY; + float s = (float)Math.Sqrt(b * b + d * d); + bone.b = MathUtils.Cos(r) * s; + bone.d = MathUtils.Sin(r) * s; + } + + bone.UpdateAppliedTransform(); + } + } + + void ApplyRelativeWorld() + { + float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX, + mixScaleY = this.mixScaleY, mixShearY = this.mixShearY; + bool translate = mixX != 0 || mixY != 0; + + Bone target = this.target; + float ta = target.a, tb = target.b, tc = target.c, td = target.d; + float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad; + float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect; + + var bones = this.bones.Items; + for (int i = 0, n = this.bones.Count; i < n; i++) + { + Bone bone = bones[i]; + + if (mixRotate != 0) + { + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + float r = MathUtils.Atan2(tc, ta) + offsetRotation; + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) // + r += MathUtils.PI2; + r *= mixRotate; + float cos = MathUtils.Cos(r), sin = MathUtils.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; //Vector2 temp = this.temp; + target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY)); + bone.worldX += tx * mixX; + bone.worldY += ty * mixY; + } + + if (mixScaleX != 0) + { + float s = ((float)Math.Sqrt(ta * ta + tc * tc) - 1 + data.offsetScaleX) * mixScaleX + 1; + bone.a *= s; + bone.c *= s; + } + if (mixScaleY != 0) + { + float s = ((float)Math.Sqrt(tb * tb + td * td) - 1 + data.offsetScaleY) * mixScaleY + 1; + bone.b *= s; + bone.d *= s; + } + + if (mixShearY > 0) + { + float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta); + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) // + r += MathUtils.PI2; + float b = bone.b, d = bone.d; + r = MathUtils.Atan2(d, b) + (r - MathUtils.PI / 2 + offsetShearY) * mixShearY; + float s = (float)Math.Sqrt(b * b + d * d); + bone.b = MathUtils.Cos(r) * s; + bone.d = MathUtils.Sin(r) * s; + } + + bone.UpdateAppliedTransform(); + } + } + + void ApplyAbsoluteLocal() + { + float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX, + mixScaleY = this.mixScaleY, mixShearY = this.mixShearY; + + Bone target = this.target; + + var bones = this.bones.Items; + for (int i = 0, n = this.bones.Count; i < n; i++) + { + Bone bone = bones[i]; + + float rotation = bone.arotation; + if (mixRotate != 0) + { + float r = target.arotation - rotation + data.offsetRotation; + r -= (16384 - (int)(16384.499999999996 - r / 360)) * 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 -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; + shearY += r * mixShearY; + } + + bone.UpdateWorldTransform(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY); + } + } + + void ApplyRelativeLocal() + { + float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX, + mixScaleY = this.mixScaleY, mixShearY = this.mixShearY; + + Bone target = this.target; + + var bones = this.bones.Items; + for (int i = 0, n = this.bones.Count; i < n; i++) + { + Bone bone = bones[i]; + + 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); + } + } + + /// The bones that will be modified by this transform constraint. + public ExposedList Bones { get { return bones; } } + /// The target bone whose world transform will be copied to the constrained bones. + public Bone Target { get { return target; } set { target = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. + public float MixRotate { get { return mixRotate; } set { mixRotate = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. + public float MixX { get { return mixX; } set { mixX = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. + public float MixY { get { return mixY; } set { mixY = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained scale X. + public float MixScaleX { get { return mixScaleX; } set { mixScaleX = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained scale Y. + public float MixScaleY { get { return mixScaleY; } set { mixScaleY = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained shear Y. + public float MixShearY { get { return mixShearY; } set { mixShearY = value; } } + public bool Active { get { return active; } } + /// The transform constraint's setup pose data. + public TransformConstraintData Data { get { return data; } } + + override public string ToString() + { + return data.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/TransformConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/TransformConstraintData.cs index dae2224..206ed3d 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/TransformConstraintData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/TransformConstraintData.cs @@ -27,42 +27,43 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; +namespace Spine4_0_31 +{ + public class TransformConstraintData : ConstraintData + { + internal ExposedList bones = new ExposedList(); + internal BoneData target; + internal float mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY; + internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY; + internal bool relative, local; -namespace Spine4_0_31 { - public class TransformConstraintData : ConstraintData { - internal ExposedList bones = new ExposedList(); - internal BoneData target; - internal float mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY; - internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY; - internal bool relative, local; + public ExposedList Bones { get { return bones; } } + public BoneData Target { get { return target; } set { target = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. + public float MixRotate { get { return mixRotate; } set { mixRotate = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. + public float MixX { get { return mixX; } set { mixX = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. + public float MixY { get { return mixY; } set { mixY = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained scale X. + public float MixScaleX { get { return mixScaleX; } set { mixScaleX = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained scale Y. + public float MixScaleY { get { return mixScaleY; } set { mixScaleY = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained shear Y. + public float MixShearY { get { return mixShearY; } set { mixShearY = value; } } - public ExposedList Bones { get { return bones; } } - public BoneData Target { get { return target; } set { target = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. - public float MixRotate { get { return mixRotate; } set { mixRotate = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. - public float MixX { get { return mixX; } set { mixX = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. - public float MixY { get { return mixY; } set { mixY = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained scale X. - public float MixScaleX { get { return mixScaleX; } set { mixScaleX = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained scale Y. - public float MixScaleY { get { return mixScaleY; } set { mixScaleY = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained shear Y. - public float MixShearY { get { return mixShearY; } set { mixShearY = value; } } + public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } + public float OffsetX { get { return offsetX; } set { offsetX = value; } } + public float OffsetY { get { return offsetY; } set { offsetY = value; } } + public float OffsetScaleX { get { return offsetScaleX; } set { offsetScaleX = value; } } + public float OffsetScaleY { get { return offsetScaleY; } set { offsetScaleY = value; } } + public float OffsetShearY { get { return offsetShearY; } set { offsetShearY = value; } } - public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } - public float OffsetX { get { return offsetX; } set { offsetX = value; } } - public float OffsetY { get { return offsetY; } set { offsetY = value; } } - public float OffsetScaleX { get { return offsetScaleX; } set { offsetScaleX = value; } } - public float OffsetScaleY { get { return offsetScaleY; } set { offsetScaleY = value; } } - public float OffsetShearY { get { return offsetShearY; } set { offsetShearY = value; } } + public bool Relative { get { return relative; } set { relative = value; } } + public bool Local { get { return local; } set { local = value; } } - public bool Relative { get { return relative; } set { relative = value; } } - public bool Local { get { return local; } set { local = value; } } - - public TransformConstraintData (string name) : base(name) { - } - } + public TransformConstraintData(string name) : base(name) + { + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Triangulator.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Triangulator.cs index 0177d3d..593d60e 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Triangulator.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/Triangulator.cs @@ -29,247 +29,276 @@ using System; -namespace Spine4_0_31 { - public class Triangulator { - private readonly ExposedList> convexPolygons = new ExposedList>(); - private readonly ExposedList> convexPolygonsIndices = new ExposedList>(); - - private readonly ExposedList indicesArray = new ExposedList(); - private readonly ExposedList isConcaveArray = new ExposedList(); - private readonly ExposedList triangles = new ExposedList(); - - private readonly Pool> polygonPool = new Pool>(); - private readonly Pool> polygonIndicesPool = new Pool>(); - - public ExposedList Triangulate (ExposedList verticesArray) { - var vertices = verticesArray.Items; - int vertexCount = verticesArray.Count >> 1; - - var indicesArray = this.indicesArray; - indicesArray.Clear(); - int[] indices = indicesArray.Resize(vertexCount).Items; - for (int i = 0; i < vertexCount; i++) - indices[i] = i; - - var isConcaveArray = this.isConcaveArray; - bool[] isConcave = isConcaveArray.Resize(vertexCount).Items; - for (int i = 0, n = vertexCount; i < n; ++i) - isConcave[i] = IsConcave(i, vertexCount, vertices, indices); - - var triangles = this.triangles; - triangles.Clear(); - triangles.EnsureCapacity(Math.Max(0, vertexCount - 2) << 2); - - while (vertexCount > 3) { - // Find ear tip. - int previous = vertexCount - 1, i = 0, next = 1; - - // outer: - while (true) { - if (!isConcave[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 (int ii = (next + 1) % vertexCount; ii != previous; ii = (ii + 1) % vertexCount) { - if (!isConcave[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 (!isConcave[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]); - indicesArray.RemoveAt(i); - isConcaveArray.RemoveAt(i); - vertexCount--; - - int previousIndex = (vertexCount + i - 1) % vertexCount; - int nextIndex = i == vertexCount ? 0 : i; - isConcave[previousIndex] = IsConcave(previousIndex, vertexCount, vertices, indices); - isConcave[nextIndex] = IsConcave(nextIndex, vertexCount, vertices, indices); - } - - if (vertexCount == 3) { - triangles.Add(indices[2]); - triangles.Add(indices[0]); - triangles.Add(indices[1]); - } - - return triangles; - } - - public ExposedList> Decompose (ExposedList verticesArray, ExposedList triangles) { - var vertices = verticesArray.Items; - var convexPolygons = this.convexPolygons; - for (int i = 0, n = convexPolygons.Count; i < n; i++) - polygonPool.Free(convexPolygons.Items[i]); - convexPolygons.Clear(); - - var convexPolygonsIndices = this.convexPolygonsIndices; - for (int i = 0, n = convexPolygonsIndices.Count; i < n; i++) - polygonIndicesPool.Free(convexPolygonsIndices.Items[i]); - convexPolygonsIndices.Clear(); - - var polygonIndices = polygonIndicesPool.Obtain(); - polygonIndices.Clear(); - - var polygon = polygonPool.Obtain(); - polygon.Clear(); - - // Merge subsequent triangles if they form a triangle fan. - int fanBaseIndex = -1, lastWinding = 0; - int[] trianglesItems = triangles.Items; - for (int i = 0, n = triangles.Count; i < n; i += 3) { - int t1 = trianglesItems[i] << 1, t2 = trianglesItems[i + 1] << 1, t3 = trianglesItems[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). - var merged = false; - if (fanBaseIndex == t1) { - int o = polygon.Count - 4; - float[] p = polygon.Items; - 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.Count > 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.Count > 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 (int i = 0, n = convexPolygons.Count; i < n; i++) { - polygonIndices = convexPolygonsIndices.Items[i]; - if (polygonIndices.Count == 0) continue; - int firstIndex = polygonIndices.Items[0]; - int lastIndex = polygonIndices.Items[polygonIndices.Count - 1]; - - polygon = convexPolygons.Items[i]; - int o = polygon.Count - 4; - float[] p = polygon.Items; - 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 winding = Winding(prevPrevX, prevPrevY, prevX, prevY, firstX, firstY); - - for (int ii = 0; ii < n; ii++) { - if (ii == i) continue; - var otherIndices = convexPolygonsIndices.Items[ii]; - if (otherIndices.Count != 3) continue; - int otherFirstIndex = otherIndices.Items[0]; - int otherSecondIndex = otherIndices.Items[1]; - int otherLastIndex = otherIndices.Items[2]; - - var otherPoly = convexPolygons.Items[ii]; - float x3 = otherPoly.Items[otherPoly.Count - 2], y3 = otherPoly.Items[otherPoly.Count - 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 == winding && winding2 == winding) { - 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 = convexPolygons.Count - 1; i >= 0; i--) { - polygon = convexPolygons.Items[i]; - if (polygon.Count == 0) { - convexPolygons.RemoveAt(i); - polygonPool.Free(polygon); - polygonIndices = convexPolygonsIndices.Items[i]; - convexPolygonsIndices.RemoveAt(i); - polygonIndicesPool.Free(polygonIndices); - } - } - - return convexPolygons; - } - - static private bool IsConcave (int index, int vertexCount, float[] vertices, int[] 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]); - } - - static private bool PositiveArea (float p1x, float p1y, float p2x, float p2y, float p3x, float p3y) { - return p1x * (p3y - p2y) + p2x * (p1y - p3y) + p3x * (p2y - p1y) >= 0; - } - - static private int 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; - } - } +namespace Spine4_0_31 +{ + public class Triangulator + { + private readonly ExposedList> convexPolygons = new ExposedList>(); + private readonly ExposedList> convexPolygonsIndices = new ExposedList>(); + + private readonly ExposedList indicesArray = new ExposedList(); + private readonly ExposedList isConcaveArray = new ExposedList(); + private readonly ExposedList triangles = new ExposedList(); + + private readonly Pool> polygonPool = new Pool>(); + private readonly Pool> polygonIndicesPool = new Pool>(); + + public ExposedList Triangulate(ExposedList verticesArray) + { + var vertices = verticesArray.Items; + int vertexCount = verticesArray.Count >> 1; + + var indicesArray = this.indicesArray; + indicesArray.Clear(); + int[] indices = indicesArray.Resize(vertexCount).Items; + for (int i = 0; i < vertexCount; i++) + indices[i] = i; + + var isConcaveArray = this.isConcaveArray; + bool[] isConcave = isConcaveArray.Resize(vertexCount).Items; + for (int i = 0, n = vertexCount; i < n; ++i) + isConcave[i] = IsConcave(i, vertexCount, vertices, indices); + + var triangles = this.triangles; + triangles.Clear(); + triangles.EnsureCapacity(Math.Max(0, vertexCount - 2) << 2); + + while (vertexCount > 3) + { + // Find ear tip. + int previous = vertexCount - 1, i = 0, next = 1; + + // outer: + while (true) + { + if (!isConcave[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 (int ii = (next + 1) % vertexCount; ii != previous; ii = (ii + 1) % vertexCount) + { + if (!isConcave[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 (!isConcave[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]); + indicesArray.RemoveAt(i); + isConcaveArray.RemoveAt(i); + vertexCount--; + + int previousIndex = (vertexCount + i - 1) % vertexCount; + int nextIndex = i == vertexCount ? 0 : i; + isConcave[previousIndex] = IsConcave(previousIndex, vertexCount, vertices, indices); + isConcave[nextIndex] = IsConcave(nextIndex, vertexCount, vertices, indices); + } + + if (vertexCount == 3) + { + triangles.Add(indices[2]); + triangles.Add(indices[0]); + triangles.Add(indices[1]); + } + + return triangles; + } + + public ExposedList> Decompose(ExposedList verticesArray, ExposedList triangles) + { + var vertices = verticesArray.Items; + var convexPolygons = this.convexPolygons; + for (int i = 0, n = convexPolygons.Count; i < n; i++) + polygonPool.Free(convexPolygons.Items[i]); + convexPolygons.Clear(); + + var convexPolygonsIndices = this.convexPolygonsIndices; + for (int i = 0, n = convexPolygonsIndices.Count; i < n; i++) + polygonIndicesPool.Free(convexPolygonsIndices.Items[i]); + convexPolygonsIndices.Clear(); + + var polygonIndices = polygonIndicesPool.Obtain(); + polygonIndices.Clear(); + + var polygon = polygonPool.Obtain(); + polygon.Clear(); + + // Merge subsequent triangles if they form a triangle fan. + int fanBaseIndex = -1, lastWinding = 0; + int[] trianglesItems = triangles.Items; + for (int i = 0, n = triangles.Count; i < n; i += 3) + { + int t1 = trianglesItems[i] << 1, t2 = trianglesItems[i + 1] << 1, t3 = trianglesItems[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). + var merged = false; + if (fanBaseIndex == t1) + { + int o = polygon.Count - 4; + float[] p = polygon.Items; + 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.Count > 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.Count > 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 (int i = 0, n = convexPolygons.Count; i < n; i++) + { + polygonIndices = convexPolygonsIndices.Items[i]; + if (polygonIndices.Count == 0) continue; + int firstIndex = polygonIndices.Items[0]; + int lastIndex = polygonIndices.Items[polygonIndices.Count - 1]; + + polygon = convexPolygons.Items[i]; + int o = polygon.Count - 4; + float[] p = polygon.Items; + 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 winding = Winding(prevPrevX, prevPrevY, prevX, prevY, firstX, firstY); + + for (int ii = 0; ii < n; ii++) + { + if (ii == i) continue; + var otherIndices = convexPolygonsIndices.Items[ii]; + if (otherIndices.Count != 3) continue; + int otherFirstIndex = otherIndices.Items[0]; + int otherSecondIndex = otherIndices.Items[1]; + int otherLastIndex = otherIndices.Items[2]; + + var otherPoly = convexPolygons.Items[ii]; + float x3 = otherPoly.Items[otherPoly.Count - 2], y3 = otherPoly.Items[otherPoly.Count - 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 == winding && winding2 == winding) + { + 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 = convexPolygons.Count - 1; i >= 0; i--) + { + polygon = convexPolygons.Items[i]; + if (polygon.Count == 0) + { + convexPolygons.RemoveAt(i); + polygonPool.Free(polygon); + polygonIndices = convexPolygonsIndices.Items[i]; + convexPolygonsIndices.RemoveAt(i); + polygonIndicesPool.Free(polygonIndices); + } + } + + return convexPolygons; + } + + static private bool IsConcave(int index, int vertexCount, float[] vertices, int[] 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]); + } + + static private bool PositiveArea(float p1x, float p1y, float p2x, float p2y, float p3x, float p3y) + { + return p1x * (p3y - p2y) + p2x * (p1y - p3y) + p3x * (p2y - p1y) >= 0; + } + + static private int 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/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/XnaLoader/MeshBatcher.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/XnaLoader/MeshBatcher.cs index 44d0a1e..f8e4f7a 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/XnaLoader/MeshBatcher.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/XnaLoader/MeshBatcher.cs @@ -27,169 +27,185 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; -namespace Spine4_0_31 { - public struct VertexPositionColorTextureColor : IVertexType { - public Vector3 Position; - public Color Color; - public Vector2 TextureCoordinate; - public Color Color2; - - public readonly static VertexDeclaration VertexDeclaration = new VertexDeclaration - ( - new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0), - new VertexElement(12, VertexElementFormat.Color, VertexElementUsage.Color, 0), - new VertexElement(16, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0), - new VertexElement(24, VertexElementFormat.Color, VertexElementUsage.Color, 1) - ); - - VertexDeclaration IVertexType.VertexDeclaration { - get { return VertexDeclaration; } - } - } - - // #region License - // /* - // Microsoft Public License (Ms-PL) - // MonoGame - Copyright � 2009 The MonoGame Team - // - // All rights reserved. - // - // This license governs use of the accompanying software. If you use the software, you accept this license. If you do not - // accept the license, do not use the software. - // - // 1. Definitions - // The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under - // U.S. copyright law. - // - // A "contribution" is the original software, or any additions or changes to the software. - // A "contributor" is any person that distributes its contribution under this license. - // "Licensed patents" are a contributor's patent claims that read directly on its contribution. - // - // 2. Grant of Rights - // (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, - // each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. - // (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, - // each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. - // - // 3. Conditions and Limitations - // (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. - // (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, - // your patent license from such contributor to the software ends automatically. - // (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution - // notices that are present in the software. - // (D) If you distribute any portion of the software in source code form, you may do so only under this license by including - // a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object - // code form, you may only do so under a license that complies with this license. - // (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees - // or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent - // permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular - // purpose and non-infringement. - // */ - // #endregion License - // - - /// Draws batched meshes. - public class MeshBatcher { - private readonly List items; - private readonly Queue freeItems; - private VertexPositionColorTextureColor[] vertexArray = { }; - private short[] triangles = { }; - - public MeshBatcher () { - items = new List(256); - freeItems = new Queue(256); - EnsureCapacity(256, 512); - } - - /// Returns a pooled MeshItem. - public MeshItem NextItem (int vertexCount, int triangleCount) { - MeshItem item = freeItems.Count > 0 ? freeItems.Dequeue() : new MeshItem(); - if (item.vertices.Length < vertexCount) item.vertices = new VertexPositionColorTextureColor[vertexCount]; - if (item.triangles.Length < triangleCount) item.triangles = new int[triangleCount]; - item.vertexCount = vertexCount; - item.triangleCount = triangleCount; - items.Add(item); - return item; - } - - private void EnsureCapacity (int vertexCount, int triangleCount) { - if (vertexArray.Length < vertexCount) vertexArray = new VertexPositionColorTextureColor[vertexCount]; - if (triangles.Length < triangleCount) triangles = new short[triangleCount]; - } - - public void Draw (GraphicsDevice device) { - if (items.Count == 0) return; - - int itemCount = items.Count; - int vertexCount = 0, triangleCount = 0; - for (int i = 0; i < itemCount; i++) { - MeshItem item = items[i]; - vertexCount += item.vertexCount; - triangleCount += item.triangleCount; - } - EnsureCapacity(vertexCount, triangleCount); - - vertexCount = 0; - triangleCount = 0; - Texture2D lastTexture = null; - for (int i = 0; i < itemCount; i++) { - MeshItem item = items[i]; - int itemVertexCount = item.vertexCount; - - if (item.texture != lastTexture || vertexCount + itemVertexCount > short.MaxValue) { - FlushVertexArray(device, vertexCount, triangleCount); - vertexCount = 0; - triangleCount = 0; - lastTexture = item.texture; - device.Textures[0] = lastTexture; - if (item.textureLayers != null) { - for (int layer = 1; layer < item.textureLayers.Length; ++layer) - device.Textures[layer] = item.textureLayers[layer]; - } - } - - int[] itemTriangles = item.triangles; - int itemTriangleCount = item.triangleCount; - for (int ii = 0, t = triangleCount; ii < itemTriangleCount; ii++, t++) - triangles[t] = (short)(itemTriangles[ii] + vertexCount); - triangleCount += itemTriangleCount; - - Array.Copy(item.vertices, 0, vertexArray, vertexCount, itemVertexCount); - vertexCount += itemVertexCount; - } - FlushVertexArray(device, vertexCount, triangleCount); - } - - public void AfterLastDrawPass () { - int itemCount = items.Count; - for (int i = 0; i < itemCount; i++) { - var item = items[i]; - item.texture = null; - freeItems.Enqueue(item); - } - items.Clear(); - } - - private void FlushVertexArray (GraphicsDevice device, int vertexCount, int triangleCount) { - if (vertexCount == 0) return; - device.DrawUserIndexedPrimitives( - PrimitiveType.TriangleList, - vertexArray, 0, vertexCount, - triangles, 0, triangleCount / 3, - VertexPositionColorTextureColor.VertexDeclaration); - } - } - - public class MeshItem { - public Texture2D texture = null; - public Texture2D[] textureLayers = null; - public int vertexCount, triangleCount; - public VertexPositionColorTextureColor[] vertices = { }; - public int[] triangles = { }; - } +namespace Spine4_0_31 +{ + public struct VertexPositionColorTextureColor : IVertexType + { + public Vector3 Position; + public Color Color; + public Vector2 TextureCoordinate; + public Color Color2; + + public readonly static VertexDeclaration VertexDeclaration = new VertexDeclaration + ( + new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0), + new VertexElement(12, VertexElementFormat.Color, VertexElementUsage.Color, 0), + new VertexElement(16, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0), + new VertexElement(24, VertexElementFormat.Color, VertexElementUsage.Color, 1) + ); + + VertexDeclaration IVertexType.VertexDeclaration + { + get { return VertexDeclaration; } + } + } + + // #region License + // /* + // Microsoft Public License (Ms-PL) + // MonoGame - Copyright � 2009 The MonoGame Team + // + // All rights reserved. + // + // This license governs use of the accompanying software. If you use the software, you accept this license. If you do not + // accept the license, do not use the software. + // + // 1. Definitions + // The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under + // U.S. copyright law. + // + // A "contribution" is the original software, or any additions or changes to the software. + // A "contributor" is any person that distributes its contribution under this license. + // "Licensed patents" are a contributor's patent claims that read directly on its contribution. + // + // 2. Grant of Rights + // (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, + // each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. + // (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, + // each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. + // + // 3. Conditions and Limitations + // (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. + // (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, + // your patent license from such contributor to the software ends automatically. + // (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution + // notices that are present in the software. + // (D) If you distribute any portion of the software in source code form, you may do so only under this license by including + // a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object + // code form, you may only do so under a license that complies with this license. + // (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees + // or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent + // permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular + // purpose and non-infringement. + // */ + // #endregion License + // + + /// Draws batched meshes. + public class MeshBatcher + { + private readonly List items; + private readonly Queue freeItems; + private VertexPositionColorTextureColor[] vertexArray = { }; + private short[] triangles = { }; + + public MeshBatcher() + { + items = new List(256); + freeItems = new Queue(256); + EnsureCapacity(256, 512); + } + + /// Returns a pooled MeshItem. + public MeshItem NextItem(int vertexCount, int triangleCount) + { + MeshItem item = freeItems.Count > 0 ? freeItems.Dequeue() : new MeshItem(); + if (item.vertices.Length < vertexCount) item.vertices = new VertexPositionColorTextureColor[vertexCount]; + if (item.triangles.Length < triangleCount) item.triangles = new int[triangleCount]; + item.vertexCount = vertexCount; + item.triangleCount = triangleCount; + items.Add(item); + return item; + } + + private void EnsureCapacity(int vertexCount, int triangleCount) + { + if (vertexArray.Length < vertexCount) vertexArray = new VertexPositionColorTextureColor[vertexCount]; + if (triangles.Length < triangleCount) triangles = new short[triangleCount]; + } + + public void Draw(GraphicsDevice device) + { + if (items.Count == 0) return; + + int itemCount = items.Count; + int vertexCount = 0, triangleCount = 0; + for (int i = 0; i < itemCount; i++) + { + MeshItem item = items[i]; + vertexCount += item.vertexCount; + triangleCount += item.triangleCount; + } + EnsureCapacity(vertexCount, triangleCount); + + vertexCount = 0; + triangleCount = 0; + Texture2D lastTexture = null; + for (int i = 0; i < itemCount; i++) + { + MeshItem item = items[i]; + int itemVertexCount = item.vertexCount; + + if (item.texture != lastTexture || vertexCount + itemVertexCount > short.MaxValue) + { + FlushVertexArray(device, vertexCount, triangleCount); + vertexCount = 0; + triangleCount = 0; + lastTexture = item.texture; + device.Textures[0] = lastTexture; + if (item.textureLayers != null) + { + for (int layer = 1; layer < item.textureLayers.Length; ++layer) + device.Textures[layer] = item.textureLayers[layer]; + } + } + + int[] itemTriangles = item.triangles; + int itemTriangleCount = item.triangleCount; + for (int ii = 0, t = triangleCount; ii < itemTriangleCount; ii++, t++) + triangles[t] = (short)(itemTriangles[ii] + vertexCount); + triangleCount += itemTriangleCount; + + Array.Copy(item.vertices, 0, vertexArray, vertexCount, itemVertexCount); + vertexCount += itemVertexCount; + } + FlushVertexArray(device, vertexCount, triangleCount); + } + + public void AfterLastDrawPass() + { + int itemCount = items.Count; + for (int i = 0; i < itemCount; i++) + { + var item = items[i]; + item.texture = null; + freeItems.Enqueue(item); + } + items.Clear(); + } + + private void FlushVertexArray(GraphicsDevice device, int vertexCount, int triangleCount) + { + if (vertexCount == 0) return; + device.DrawUserIndexedPrimitives( + PrimitiveType.TriangleList, + vertexArray, 0, vertexCount, + triangles, 0, triangleCount / 3, + VertexPositionColorTextureColor.VertexDeclaration); + } + } + + public class MeshItem + { + public Texture2D texture = null; + public Texture2D[] textureLayers = null; + public int vertexCount, triangleCount; + public VertexPositionColorTextureColor[] vertices = { }; + public int[] triangles = { }; + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/XnaLoader/ShapeRenderer.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/XnaLoader/ShapeRenderer.cs index c64544d..3c22cb8 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/XnaLoader/ShapeRenderer.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/XnaLoader/ShapeRenderer.cs @@ -27,139 +27,156 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Spine4_0_31 { - /// - /// Batch drawing of lines and shapes that can be derived from lines. - /// - /// Call drawing methods in between Begin()/End() - /// - public class ShapeRenderer { - GraphicsDevice device; - List vertices = new List(); - Color color = Color.White; - BasicEffect effect; - public BasicEffect Effect { get { return effect; } set { effect = value; } } - - public ShapeRenderer (GraphicsDevice device) { - this.device = device; - this.effect = new BasicEffect(device); - effect.World = Matrix.Identity; - effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); - effect.TextureEnabled = false; - effect.VertexColorEnabled = true; - } - - public void SetColor (Color color) { - this.color = color; - } - - public void Begin () { - device.RasterizerState = new RasterizerState(); - device.BlendState = BlendState.AlphaBlend; - } - - public void Line (float x1, float y1, float x2, float y2) { - vertices.Add(new VertexPositionColor(new Vector3(x1, y1, 0), color)); - vertices.Add(new VertexPositionColor(new Vector3(x2, y2, 0), color)); - } - - /** Calls {@link #circle(float, float, float, int)} by estimating the number of segments needed for a smooth circle. */ - public void Circle (float x, float y, float radius) { - Circle(x, y, radius, Math.Max(1, (int)(6 * (float)Math.Pow(radius, 1.0f / 3.0f)))); - } - - /** Draws a circle using {@link ShapeType#Line} or {@link ShapeType#Filled}. */ - public void Circle (float x, float y, float radius, int segments) { - if (segments <= 0) throw new ArgumentException("segments must be > 0."); - float angle = 2 * MathUtils.PI / segments; - float cos = MathUtils.Cos(angle); - float sin = MathUtils.Sin(angle); - float cx = radius, cy = 0; - float temp = 0; - - for (int i = 0; i < segments; i++) { - vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); - temp = cx; - cx = cos * cx - sin * cy; - cy = sin * temp + cos * cy; - vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); - } - vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); - - temp = cx; - cx = radius; - cy = 0; - vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); - } - - public void Triangle (float x1, float y1, float x2, float y2, float x3, float y3) { - vertices.Add(new VertexPositionColor(new Vector3(x1, y1, 0), color)); - vertices.Add(new VertexPositionColor(new Vector3(x2, y2, 0), color)); - - vertices.Add(new VertexPositionColor(new Vector3(x2, y2, 0), color)); - vertices.Add(new VertexPositionColor(new Vector3(x3, y3, 0), color)); - - vertices.Add(new VertexPositionColor(new Vector3(x3, y3, 0), color)); - vertices.Add(new VertexPositionColor(new Vector3(x1, y1, 0), color)); - } - - public void X (float x, float y, float len) { - Line(x + len, y + len, x - len, y - len); - Line(x - len, y + len, x + len, y - len); - } - - public void Polygon (float[] polygonVertices, int offset, int count) { - if (count < 3) throw new ArgumentException("Polygon must contain at least 3 vertices"); - - offset <<= 1; - - var firstX = polygonVertices[offset]; - var firstY = polygonVertices[offset + 1]; - var last = offset + count; - - for (int i = offset, n = offset + count; i < n; i += 2) { - var x1 = polygonVertices[i]; - var y1 = polygonVertices[i + 1]; - - var x2 = 0f; - var y2 = 0f; - - if (i + 2 >= last) { - x2 = firstX; - y2 = firstY; - } else { - x2 = polygonVertices[i + 2]; - y2 = polygonVertices[i + 3]; - } - - Line(x1, y1, x2, y2); - } - } - - public void Rect (float x, float y, float width, float height) { - Line(x, y, x + width, y); - Line(x + width, y, x + width, y + height); - Line(x + width, y + height, x, y + height); - Line(x, y + height, x, y); - } - - public void End () { - if (vertices.Count == 0) return; - var verticesArray = vertices.ToArray(); - - foreach (EffectPass pass in effect.CurrentTechnique.Passes) { - pass.Apply(); - device.DrawUserPrimitives(PrimitiveType.LineList, verticesArray, 0, verticesArray.Length / 2); - } - - vertices.Clear(); - } - } +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Spine4_0_31 +{ + /// + /// Batch drawing of lines and shapes that can be derived from lines. + /// + /// Call drawing methods in between Begin()/End() + /// + public class ShapeRenderer + { + GraphicsDevice device; + List vertices = new List(); + Color color = Color.White; + BasicEffect effect; + public BasicEffect Effect { get { return effect; } set { effect = value; } } + + public ShapeRenderer(GraphicsDevice device) + { + this.device = device; + this.effect = new BasicEffect(device); + effect.World = Matrix.Identity; + effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); + effect.TextureEnabled = false; + effect.VertexColorEnabled = true; + } + + public void SetColor(Color color) + { + this.color = color; + } + + public void Begin() + { + device.RasterizerState = new RasterizerState(); + device.BlendState = BlendState.AlphaBlend; + } + + public void Line(float x1, float y1, float x2, float y2) + { + vertices.Add(new VertexPositionColor(new Vector3(x1, y1, 0), color)); + vertices.Add(new VertexPositionColor(new Vector3(x2, y2, 0), color)); + } + + /** Calls {@link #circle(float, float, float, int)} by estimating the number of segments needed for a smooth circle. */ + public void Circle(float x, float y, float radius) + { + Circle(x, y, radius, Math.Max(1, (int)(6 * (float)Math.Pow(radius, 1.0f / 3.0f)))); + } + + /** Draws a circle using {@link ShapeType#Line} or {@link ShapeType#Filled}. */ + public void Circle(float x, float y, float radius, int segments) + { + if (segments <= 0) throw new ArgumentException("segments must be > 0."); + float angle = 2 * MathUtils.PI / segments; + float cos = MathUtils.Cos(angle); + float sin = MathUtils.Sin(angle); + float cx = radius, cy = 0; + float temp = 0; + + for (int i = 0; i < segments; i++) + { + vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); + temp = cx; + cx = cos * cx - sin * cy; + cy = sin * temp + cos * cy; + vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); + } + vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); + + temp = cx; + cx = radius; + cy = 0; + vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); + } + + public void Triangle(float x1, float y1, float x2, float y2, float x3, float y3) + { + vertices.Add(new VertexPositionColor(new Vector3(x1, y1, 0), color)); + vertices.Add(new VertexPositionColor(new Vector3(x2, y2, 0), color)); + + vertices.Add(new VertexPositionColor(new Vector3(x2, y2, 0), color)); + vertices.Add(new VertexPositionColor(new Vector3(x3, y3, 0), color)); + + vertices.Add(new VertexPositionColor(new Vector3(x3, y3, 0), color)); + vertices.Add(new VertexPositionColor(new Vector3(x1, y1, 0), color)); + } + + public void X(float x, float y, float len) + { + Line(x + len, y + len, x - len, y - len); + Line(x - len, y + len, x + len, y - len); + } + + public void Polygon(float[] polygonVertices, int offset, int count) + { + if (count < 3) throw new ArgumentException("Polygon must contain at least 3 vertices"); + + offset <<= 1; + + var firstX = polygonVertices[offset]; + var firstY = polygonVertices[offset + 1]; + var last = offset + count; + + for (int i = offset, n = offset + count; i < n; i += 2) + { + var x1 = polygonVertices[i]; + var y1 = polygonVertices[i + 1]; + + var x2 = 0f; + var y2 = 0f; + + if (i + 2 >= last) + { + x2 = firstX; + y2 = firstY; + } + else + { + x2 = polygonVertices[i + 2]; + y2 = polygonVertices[i + 3]; + } + + Line(x1, y1, x2, y2); + } + } + + public void Rect(float x, float y, float width, float height) + { + Line(x, y, x + width, y); + Line(x + width, y, x + width, y + height); + Line(x + width, y + height, x, y + height); + Line(x, y + height, x, y); + } + + public void End() + { + if (vertices.Count == 0) return; + var verticesArray = vertices.ToArray(); + + foreach (EffectPass pass in effect.CurrentTechnique.Passes) + { + pass.Apply(); + device.DrawUserPrimitives(PrimitiveType.LineList, verticesArray, 0, verticesArray.Length / 2); + } + + vertices.Clear(); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/XnaLoader/SkeletonRenderer.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/XnaLoader/SkeletonRenderer.cs index 82659ab..1474f3e 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/XnaLoader/SkeletonRenderer.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/XnaLoader/SkeletonRenderer.cs @@ -29,128 +29,141 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; -using System; -using System.Collections.Generic; -namespace Spine4_0_31 { - /// Draws region and mesh attachments. - public class SkeletonRenderer { - private const int TL = 0; - private const int TR = 1; - private const int BL = 2; - private const int BR = 3; +namespace Spine4_0_31 +{ + /// Draws region and mesh attachments. + public class SkeletonRenderer + { + private const int TL = 0; + private const int TR = 1; + private const int BL = 2; + private const int BR = 3; - SkeletonClipping clipper = new SkeletonClipping(); - GraphicsDevice device; - MeshBatcher batcher; - public MeshBatcher Batcher { get { return batcher; } } - RasterizerState rasterizerState; - float[] vertices = new float[8]; - int[] quadTriangles = { 0, 1, 2, 2, 3, 0 }; - BlendState defaultBlendState; + SkeletonClipping clipper = new SkeletonClipping(); + GraphicsDevice device; + MeshBatcher batcher; + public MeshBatcher Batcher { get { return batcher; } } + RasterizerState rasterizerState; + float[] vertices = new float[8]; + int[] quadTriangles = { 0, 1, 2, 2, 3, 0 }; + BlendState defaultBlendState; - Effect effect; - public Effect Effect { get { return effect; } set { effect = value; } } - public IVertexEffect VertexEffect { get; set; } + Effect effect; + public Effect Effect { get { return effect; } set { effect = value; } } + public IVertexEffect VertexEffect { get; set; } - private bool premultipliedAlpha; - public bool PremultipliedAlpha { get { return premultipliedAlpha; } set { premultipliedAlpha = value; } } + private bool premultipliedAlpha; + public bool PremultipliedAlpha { get { return premultipliedAlpha; } set { premultipliedAlpha = value; } } - /// Attachments are rendered back to front in the x/y plane by the SkeletonRenderer. - /// Each attachment is offset by a customizable z-spacing value on the z-axis to avoid z-fighting - /// in shaders with ZWrite enabled. Typical values lie in the range [-0.1, 0]. - private float zSpacing = 0.0f; - public float ZSpacing { get { return zSpacing; } set { zSpacing = value; } } + /// Attachments are rendered back to front in the x/y plane by the SkeletonRenderer. + /// Each attachment is offset by a customizable z-spacing value on the z-axis to avoid z-fighting + /// in shaders with ZWrite enabled. Typical values lie in the range [-0.1, 0]. + private float zSpacing = 0.0f; + public float ZSpacing { get { return zSpacing; } set { zSpacing = value; } } - /// A Z position offset added at each vertex. - private float z = 0.0f; - public float Z { get { return z; } set { z = value; } } + /// A Z position offset added at each vertex. + private float z = 0.0f; + public float Z { get { return z; } set { z = value; } } - public SkeletonRenderer (GraphicsDevice device) { - this.device = device; + public SkeletonRenderer(GraphicsDevice device) + { + this.device = device; - batcher = new MeshBatcher(); + batcher = new MeshBatcher(); - var basicEffect = new BasicEffect(device); - basicEffect.World = Matrix.Identity; - basicEffect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); - basicEffect.TextureEnabled = true; - basicEffect.VertexColorEnabled = true; - effect = basicEffect; + var basicEffect = new BasicEffect(device); + basicEffect.World = Matrix.Identity; + basicEffect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); + basicEffect.TextureEnabled = true; + basicEffect.VertexColorEnabled = true; + effect = basicEffect; - rasterizerState = new RasterizerState(); - rasterizerState.CullMode = CullMode.None; + rasterizerState = new RasterizerState(); + rasterizerState.CullMode = CullMode.None; - Bone.yDown = true; - } + Bone.yDown = true; + } - public void Begin () { - defaultBlendState = premultipliedAlpha ? BlendState.AlphaBlend : BlendState.NonPremultiplied; + public void Begin() + { + defaultBlendState = premultipliedAlpha ? BlendState.AlphaBlend : BlendState.NonPremultiplied; - device.RasterizerState = rasterizerState; - device.BlendState = defaultBlendState; - } + device.RasterizerState = rasterizerState; + device.BlendState = defaultBlendState; + } - public void End () { - foreach (EffectPass pass in effect.CurrentTechnique.Passes) { - pass.Apply(); - batcher.Draw(device); - } - batcher.AfterLastDrawPass(); - } + public void End() + { + foreach (EffectPass pass in effect.CurrentTechnique.Passes) + { + pass.Apply(); + batcher.Draw(device); + } + batcher.AfterLastDrawPass(); + } - public void Draw (Skeleton skeleton) { - var drawOrder = skeleton.DrawOrder; - var drawOrderItems = skeleton.DrawOrder.Items; - float skeletonR = skeleton.R, skeletonG = skeleton.G, skeletonB = skeleton.B, skeletonA = skeleton.A; - Color color = new Color(); + public void Draw(Skeleton skeleton) + { + var drawOrder = skeleton.DrawOrder; + var drawOrderItems = skeleton.DrawOrder.Items; + float skeletonR = skeleton.R, skeletonG = skeleton.G, skeletonB = skeleton.B, skeletonA = skeleton.A; + Color color = new Color(); - if (VertexEffect != null) VertexEffect.Begin(skeleton); + if (VertexEffect != null) VertexEffect.Begin(skeleton); - for (int i = 0, n = drawOrder.Count; i < n; i++) { - Slot slot = drawOrderItems[i]; - Attachment attachment = slot.Attachment; - float attachmentZOffset = z + zSpacing * i; + for (int i = 0, n = drawOrder.Count; i < n; i++) + { + Slot slot = drawOrderItems[i]; + Attachment attachment = slot.Attachment; + float attachmentZOffset = z + zSpacing * i; - float attachmentColorR, attachmentColorG, attachmentColorB, attachmentColorA; - object textureObject = null; - int verticesCount = 0; - float[] vertices = this.vertices; - int indicesCount = 0; - int[] indices = null; - float[] uvs = null; + float attachmentColorR, attachmentColorG, attachmentColorB, attachmentColorA; + object textureObject = null; + int verticesCount = 0; + float[] vertices = this.vertices; + int indicesCount = 0; + int[] indices = null; + float[] uvs = null; - if (attachment is RegionAttachment) { - RegionAttachment regionAttachment = (RegionAttachment)attachment; - attachmentColorR = regionAttachment.R; attachmentColorG = regionAttachment.G; attachmentColorB = regionAttachment.B; attachmentColorA = regionAttachment.A; - AtlasRegion region = (AtlasRegion)regionAttachment.RendererObject; - textureObject = region.page.rendererObject; - verticesCount = 4; - regionAttachment.ComputeWorldVertices(slot.Bone, vertices, 0, 2); - indicesCount = 6; - indices = quadTriangles; - uvs = regionAttachment.UVs; - } else if (attachment is MeshAttachment) { - MeshAttachment mesh = (MeshAttachment)attachment; - attachmentColorR = mesh.R; attachmentColorG = mesh.G; attachmentColorB = mesh.B; attachmentColorA = mesh.A; - AtlasRegion region = (AtlasRegion)mesh.RendererObject; - textureObject = region.page.rendererObject; - int vertexCount = mesh.WorldVerticesLength; - if (vertices.Length < vertexCount) vertices = new float[vertexCount]; - verticesCount = vertexCount >> 1; - mesh.ComputeWorldVertices(slot, vertices); - indicesCount = mesh.Triangles.Length; - indices = mesh.Triangles; - uvs = mesh.UVs; - } else if (attachment is ClippingAttachment) { - ClippingAttachment clip = (ClippingAttachment)attachment; - clipper.ClipStart(slot, clip); - continue; - } else { - continue; - } + if (attachment is RegionAttachment) + { + RegionAttachment regionAttachment = (RegionAttachment)attachment; + attachmentColorR = regionAttachment.R; attachmentColorG = regionAttachment.G; attachmentColorB = regionAttachment.B; attachmentColorA = regionAttachment.A; + AtlasRegion region = (AtlasRegion)regionAttachment.RendererObject; + textureObject = region.page.rendererObject; + verticesCount = 4; + regionAttachment.ComputeWorldVertices(slot.Bone, vertices, 0, 2); + indicesCount = 6; + indices = quadTriangles; + uvs = regionAttachment.UVs; + } + else if (attachment is MeshAttachment) + { + MeshAttachment mesh = (MeshAttachment)attachment; + attachmentColorR = mesh.R; attachmentColorG = mesh.G; attachmentColorB = mesh.B; attachmentColorA = mesh.A; + AtlasRegion region = (AtlasRegion)mesh.RendererObject; + textureObject = region.page.rendererObject; + int vertexCount = mesh.WorldVerticesLength; + if (vertices.Length < vertexCount) vertices = new float[vertexCount]; + verticesCount = vertexCount >> 1; + mesh.ComputeWorldVertices(slot, vertices); + indicesCount = mesh.Triangles.Length; + indices = mesh.Triangles; + uvs = mesh.UVs; + } + else if (attachment is ClippingAttachment) + { + ClippingAttachment clip = (ClippingAttachment)attachment; + clipper.ClipStart(slot, clip); + continue; + } + else + { + continue; + } - // set blend state + // set blend state BlendState blendState = new BlendState(); Blend blendSrc; Blend blendDst; @@ -202,75 +215,87 @@ public void Draw (Skeleton skeleton) { break; } - if (device.BlendState != blendState) { + if (device.BlendState != blendState) + { End(); device.BlendState = blendState; - } + } - // calculate color - float a = skeletonA * slot.A * attachmentColorA; - if (premultipliedAlpha) { - color = new Color( - skeletonR * slot.R * attachmentColorR * a, - skeletonG * slot.G * attachmentColorG * a, - skeletonB * slot.B * attachmentColorB * a, a); - } else { - color = new Color( - skeletonR * slot.R * attachmentColorR, - skeletonG * slot.G * attachmentColorG, - skeletonB * slot.B * attachmentColorB, a); - } + // calculate color + float a = skeletonA * slot.A * attachmentColorA; + if (premultipliedAlpha) + { + color = new Color( + skeletonR * slot.R * attachmentColorR * a, + skeletonG * slot.G * attachmentColorG * a, + skeletonB * slot.B * attachmentColorB * a, a); + } + else + { + color = new Color( + skeletonR * slot.R * attachmentColorR, + skeletonG * slot.G * attachmentColorG, + skeletonB * slot.B * attachmentColorB, a); + } - Color darkColor = new Color(); - if (slot.HasSecondColor) { - if (premultipliedAlpha) { - darkColor = new Color(slot.R2 * a, slot.G2 * a, slot.B2 * a); - } else { - darkColor = new Color(slot.R2 * a, slot.G2 * a, slot.B2 * a); - } - } - darkColor.A = premultipliedAlpha ? (byte)255 : (byte)0; + Color darkColor = new Color(); + if (slot.HasSecondColor) + { + if (premultipliedAlpha) + { + darkColor = new Color(slot.R2 * a, slot.G2 * a, slot.B2 * a); + } + else + { + darkColor = new Color(slot.R2 * a, slot.G2 * a, slot.B2 * a); + } + } + darkColor.A = premultipliedAlpha ? (byte)255 : (byte)0; - // clip - if (clipper.IsClipping) { - clipper.ClipTriangles(vertices, verticesCount << 1, indices, indicesCount, uvs); - vertices = clipper.ClippedVertices.Items; - verticesCount = clipper.ClippedVertices.Count >> 1; - indices = clipper.ClippedTriangles.Items; - indicesCount = clipper.ClippedTriangles.Count; - uvs = clipper.ClippedUVs.Items; - } + // clip + if (clipper.IsClipping) + { + clipper.ClipTriangles(vertices, verticesCount << 1, indices, indicesCount, uvs); + vertices = clipper.ClippedVertices.Items; + verticesCount = clipper.ClippedVertices.Count >> 1; + indices = clipper.ClippedTriangles.Items; + indicesCount = clipper.ClippedTriangles.Count; + uvs = clipper.ClippedUVs.Items; + } - if (verticesCount == 0 || indicesCount == 0) - continue; + if (verticesCount == 0 || indicesCount == 0) + continue; - // submit to batch - MeshItem item = batcher.NextItem(verticesCount, indicesCount); - if (textureObject is Texture2D) - item.texture = (Texture2D)textureObject; - else { - item.textureLayers = (Texture2D[])textureObject; - item.texture = item.textureLayers[0]; - } - for (int ii = 0, nn = indicesCount; ii < nn; ii++) { - item.triangles[ii] = indices[ii]; - } - VertexPositionColorTextureColor[] itemVertices = item.vertices; - for (int ii = 0, v = 0, nn = verticesCount << 1; v < nn; ii++, v += 2) { - itemVertices[ii].Color = color; - itemVertices[ii].Color2 = darkColor; - itemVertices[ii].Position.X = vertices[v]; - itemVertices[ii].Position.Y = vertices[v + 1]; - itemVertices[ii].Position.Z = attachmentZOffset; - itemVertices[ii].TextureCoordinate.X = uvs[v]; - itemVertices[ii].TextureCoordinate.Y = uvs[v + 1]; - if (VertexEffect != null) VertexEffect.Transform(ref itemVertices[ii]); - } + // submit to batch + MeshItem item = batcher.NextItem(verticesCount, indicesCount); + if (textureObject is Texture2D) + item.texture = (Texture2D)textureObject; + else + { + item.textureLayers = (Texture2D[])textureObject; + item.texture = item.textureLayers[0]; + } + for (int ii = 0, nn = indicesCount; ii < nn; ii++) + { + item.triangles[ii] = indices[ii]; + } + VertexPositionColorTextureColor[] itemVertices = item.vertices; + for (int ii = 0, v = 0, nn = verticesCount << 1; v < nn; ii++, v += 2) + { + itemVertices[ii].Color = color; + itemVertices[ii].Color2 = darkColor; + itemVertices[ii].Position.X = vertices[v]; + itemVertices[ii].Position.Y = vertices[v + 1]; + itemVertices[ii].Position.Z = attachmentZOffset; + itemVertices[ii].TextureCoordinate.X = uvs[v]; + itemVertices[ii].TextureCoordinate.Y = uvs[v + 1]; + if (VertexEffect != null) VertexEffect.Transform(ref itemVertices[ii]); + } - clipper.ClipEnd(slot); - } - clipper.ClipEnd(); - if (VertexEffect != null) VertexEffect.End(); - } - } + clipper.ClipEnd(slot); + } + clipper.ClipEnd(); + if (VertexEffect != null) VertexEffect.End(); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/XnaLoader/VertexEffect.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/XnaLoader/VertexEffect.cs index 10fe9ea..9190629 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/XnaLoader/VertexEffect.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/XnaLoader/VertexEffect.cs @@ -28,70 +28,80 @@ *****************************************************************************/ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -namespace Spine4_0_31 { - public interface IVertexEffect { - void Begin (Skeleton skeleton); - void Transform (ref VertexPositionColorTextureColor vertex); - void End (); - } +namespace Spine4_0_31 +{ + public interface IVertexEffect + { + void Begin(Skeleton skeleton); + void Transform(ref VertexPositionColorTextureColor vertex); + void End(); + } - public class JitterEffect : IVertexEffect { - public float JitterX { get; set; } - public float JitterY { get; set; } + public class JitterEffect : IVertexEffect + { + public float JitterX { get; set; } + public float JitterY { get; set; } - public JitterEffect (float jitterX, float jitterY) { - JitterX = jitterX; - JitterY = jitterY; - } + public JitterEffect(float jitterX, float jitterY) + { + JitterX = jitterX; + JitterY = jitterY; + } - public void Begin (Skeleton skeleton) { - } + public void Begin(Skeleton skeleton) + { + } - public void End () { - } + public void End() + { + } - public void Transform (ref VertexPositionColorTextureColor vertex) { - vertex.Position.X += MathUtils.RandomTriangle(-JitterX, JitterY); - vertex.Position.Y += MathUtils.RandomTriangle(-JitterX, JitterY); - } - } + public void Transform(ref VertexPositionColorTextureColor vertex) + { + vertex.Position.X += MathUtils.RandomTriangle(-JitterX, JitterY); + vertex.Position.Y += MathUtils.RandomTriangle(-JitterX, JitterY); + } + } - public class SwirlEffect : IVertexEffect { - private float worldX, worldY, angle; + public class SwirlEffect : IVertexEffect + { + private float worldX, worldY, angle; - public float Radius { get; set; } - public float Angle { get { return angle; } set { angle = value * MathUtils.DegRad; } } - public float CenterX { get; set; } - public float CenterY { get; set; } - public IInterpolation Interpolation { get; set; } + public float Radius { get; set; } + public float Angle { get { return angle; } set { angle = value * MathUtils.DegRad; } } + public float CenterX { get; set; } + public float CenterY { get; set; } + public IInterpolation Interpolation { get; set; } - public SwirlEffect (float radius) { - Radius = radius; - Interpolation = IInterpolation.Pow2; - } + public SwirlEffect(float radius) + { + Radius = radius; + Interpolation = IInterpolation.Pow2; + } - public void Begin (Skeleton skeleton) { - worldX = skeleton.X + CenterX; - worldY = skeleton.Y + CenterY; - } + public void Begin(Skeleton skeleton) + { + worldX = skeleton.X + CenterX; + worldY = skeleton.Y + CenterY; + } - public void End () { - } + public void End() + { + } - public void Transform (ref VertexPositionColorTextureColor vertex) { - float x = vertex.Position.X - worldX; - float y = vertex.Position.Y - worldY; - float dist = (float)Math.Sqrt(x * x + y * y); - if (dist < Radius) { - float theta = Interpolation.Apply(0, angle, (Radius - dist) / Radius); - float cos = MathUtils.Cos(theta), sin = MathUtils.Sin(theta); - vertex.Position.X = cos * x - sin * y + worldX; - vertex.Position.Y = sin * x + cos * y + worldY; - } - } - } + public void Transform(ref VertexPositionColorTextureColor vertex) + { + float x = vertex.Position.X - worldX; + float y = vertex.Position.Y - worldY; + float dist = (float)Math.Sqrt(x * x + y * y); + if (dist < Radius) + { + float theta = Interpolation.Apply(0, angle, (Radius - dist) / Radius); + float cos = MathUtils.Cos(theta), sin = MathUtils.Sin(theta); + vertex.Position.X = cos * x - sin * y + worldX; + vertex.Position.Y = sin * x + cos * y + worldY; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/XnaLoader/XnaTextureLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/XnaLoader/XnaTextureLoader.cs index 2604b74..d4f0f7b 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/XnaLoader/XnaTextureLoader.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.31/XnaLoader/XnaTextureLoader.cs @@ -27,10 +27,8 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; using System; -using System.IO; +using Microsoft.Xna.Framework.Graphics; namespace Spine4_0_31 { diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Animation.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Animation.cs index ba689da..cb1aa6e 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Animation.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Animation.cs @@ -30,2557 +30,2946 @@ using System; using System.Collections.Generic; -namespace Spine4_0_64 { - - /// - /// Stores a list of timelines to animate a skeleton's pose over time. - public class Animation { - internal String name; - internal ExposedList timelines; - internal HashSet timelineIds; - internal float duration; - - public Animation (string name, ExposedList timelines, float duration) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - - this.name = name; - SetTimelines(timelines); - this.duration = duration; - } - - public ExposedList Timelines { - get { return timelines; } - set { SetTimelines(value); } - } - - public void SetTimelines (ExposedList timelines) { - if (timelines == null) throw new ArgumentNullException("timelines", "timelines cannot be null."); - this.timelines = timelines; - // Note: avoiding reallocations by adding all hash set entries at - // once (EnsureCapacity() is only available in newer .Net versions). - int idCount = 0; - int timelinesCount = timelines.Count; - Timeline[] timelinesItems = timelines.Items; - for (int t = 0; t < timelinesCount; ++t) - idCount += timelinesItems[t].PropertyIds.Length; - string[] propertyIds = new string[idCount]; - int currentId = 0; - for (int t = 0; t < timelinesCount; ++t) { - var ids = timelinesItems[t].PropertyIds; - for (int i = 0, idsLength = ids.Length; i < idsLength; ++i) - propertyIds[currentId++] = ids[i]; - } - this.timelineIds = new HashSet(propertyIds); - } - - /// The duration of the animation in seconds, which is usually the highest time of all frames in the timeline. The duration is - /// used to know when it has completed and when it should loop back to the start. - public float Duration { get { return duration; } set { duration = value; } } - - /// The animation's name, which is unique across all animations in the skeleton. - public string Name { get { return name; } } - - /// Returns true if this animation contains a timeline with any of the specified property IDs. - public bool HasTimeline (string[] propertyIds) { - foreach (string id in propertyIds) - if (timelineIds.Contains(id)) return true; - return false; - } - - /// Applies the animation's timelines to the specified skeleton. - /// - /// The skeleton the animation is being applied to. This provides access to the bones, slots, and other skeleton - /// components the timelines may change. - /// The last time in seconds this animation was applied. Some timelines trigger only at specific times rather - /// than every frame. Pass -1 the first time an animation is applied to ensure frame 0 is triggered. - /// The time in seconds the skeleton is being posed for. Most timelines find the frame before and the frame after - /// this time and interpolate between the frame values. If beyond the and loop is - /// true then the animation will repeat, else the last frame will be applied. - /// If true, the animation repeats after the . - /// If any events are fired, they are added to this list. Can be null to ignore fired events or if no timelines - /// fire events. - /// 0 applies the current or setup values (depending on blend). 1 applies the timeline values. Between - /// 0 and 1 applies values between the current or setup values and the timeline values. 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 (layering). - /// Controls how mixing is applied when alpha < 1. - /// Indicates whether the timelines are mixing in or out. Used by timelines which perform instant transitions, - /// such as or . - public void Apply (Skeleton skeleton, float lastTime, float time, bool loop, ExposedList events, float alpha, - MixBlend blend, MixDirection direction) { - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - - if (loop && duration != 0) { - time %= duration; - if (lastTime > 0) lastTime %= duration; - } - - var timelines = this.timelines.Items; - for (int i = 0, n = this.timelines.Count; i < n; i++) - timelines[i].Apply(skeleton, lastTime, time, events, alpha, blend, direction); - } - - override public string ToString () { - return name; - } - } - - /// - /// Controls how timeline values are mixed with setup pose values or current pose values when a timeline is applied with - /// alpha < 1. - /// - public enum MixBlend { - /// Transitions from the setup value to the timeline value (the current value is not used). Before the first frame, the - /// setup value is set. - Setup, - - /// - /// - /// Transitions from the current value to the timeline value. Before the first frame, transitions from the current value to - /// the setup value. Timelines which perform instant transitions, such as or - /// , use the setup value before the first frame. - /// - /// First is intended for the first animations applied, not for animations layered on top of those. - /// - First, - - /// - /// - /// Transitions from the current value to the timeline value. No change is made before the first frame (the current value is - /// kept until the first frame). - /// - /// Replace is intended for animations layered on top of others, not for the first animations applied. - /// - Replace, - - /// - /// - /// Transitions from the current value to the current value plus the timeline value. No change is made before the first frame - /// (the current value is kept until the first frame). - /// - /// Add is intended for animations layered on top of others, not for the first animations applied. Properties - /// set by additive animations must be set manually or by another animation before applying the additive animations, else the - /// property values will increase each time the additive animations are applied. - /// - /// - Add - } - - /// - /// Indicates whether a timeline's alpha is mixing out over time toward 0 (the setup or current pose value) or - /// mixing in toward 1 (the timeline's value). Some timelines use this to decide how values are applied. - /// - public enum MixDirection { - In, - Out - } - - internal enum Property { - Rotate = 0, X, Y, ScaleX, ScaleY, ShearX, ShearY, // - RGB, Alpha, RGB2, // - Attachment, Deform, // - Event, DrawOrder, // - IkConstraint, TransformConstraint, // - PathConstraintPosition, PathConstraintSpacing, PathConstraintMix - } - - /// - /// The base class for all timelines. - public abstract class Timeline { - private readonly string[] propertyIds; - internal readonly float[] frames; - - /// Unique identifiers for the properties the timeline modifies. - public Timeline (int frameCount, params string[] propertyIds) { - if (propertyIds == null) throw new System.ArgumentNullException("propertyIds", "propertyIds cannot be null."); - this.propertyIds = propertyIds; - frames = new float[frameCount * FrameEntries]; - } - - /// Uniquely encodes both the type of this timeline and the skeleton properties that it affects. - public string[] PropertyIds { - get { return propertyIds; } - } - - /// The time in seconds and any other values for each frame. - public float[] Frames { - get { return frames; } - } - - /// The number of entries stored per frame. - public virtual int FrameEntries { - get { return 1; } - } - - /// The number of frames for this timeline. - public int FrameCount { - get { return frames.Length / FrameEntries; } - } - - public float Duration { - get { - return frames[frames.Length - FrameEntries]; - } - } - - /// Applies this timeline to the skeleton. - /// The skeleton the timeline is being applied to. This provides access to the bones, slots, and other - /// skeleton components the timeline may change. - /// The time this timeline was last applied. Timelines such as trigger only - /// at specific times rather than every frame. In that case, the timeline triggers everything between - /// lastTime (exclusive) and time (inclusive). Pass -1 the first time an animation is - /// applied to ensure frame 0 is triggered. - /// The time in seconds that the skeleton is being posed for. Most timelines find the frame before and the frame - /// after this time and interpolate between the frame values.If beyond the last frame, the last frame will be - /// applied. - /// If any events are fired, they are added to this list. Can be null to ignore fired events or if the timeline - /// does not fire events. - /// 0 applies the current or setup value (depending on blend). 1 applies the timeline value. - /// Between 0 and 1 applies a value between the current or setup value 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 (layering). - /// Controls how mixing is applied when alpha < 1. - /// Indicates whether the timeline is mixing in or out. Used by timelines which perform instant transitions, - /// such as or , and other such as . - public abstract void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, - MixBlend blend, MixDirection direction); - - /// Search using a stride of 1. - /// Must be >= the first value in frames. - /// The index of the first value <= time. - internal static int Search (float[] frames, float time) { - int n = frames.Length; - for (int i = 1; i < n; i++) - if (frames[i] > time) return i - 1; - return n - 1; - } - - /// Search using the specified stride. - /// Must be >= the first value in frames. - /// The index of the first value <= time. - internal static int Search (float[] frames, float time, int step) { - int n = frames.Length; - for (int i = step; i < n; i += step) - if (frames[i] > time) return i - step; - return n - step; - } - } - - /// An interface for timelines which change the property of a bone. - public interface IBoneTimeline { - /// The index of the bone in that will be changed when this timeline is applied. - int BoneIndex { get; } - } - - /// An interface for timelines which change the property of a slot. - public interface ISlotTimeline { - /// The index of the slot in that will be changed when this timeline is applied. - int SlotIndex { get; } - } - - /// The base class for timelines that interpolate between frame values using stepped, linear, or a Bezier curve. - public abstract class CurveTimeline : Timeline { - public const int LINEAR = 0, STEPPED = 1, BEZIER = 2, BEZIER_SIZE = 18; - - internal float[] curves; - /// The number of key frames for this timeline. - - /// The maximum number of Bezier curves. See . - /// Unique identifiers for the properties the timeline modifies. - public CurveTimeline (int frameCount, int bezierCount, params string[] propertyIds) - : base(frameCount, propertyIds) { - curves = new float[frameCount + bezierCount * BEZIER_SIZE]; - curves[frameCount - 1] = STEPPED; - } - - /// Sets the specified frame to linear interpolation. - /// Between 0 and frameCount - 1, inclusive. - public void SetLinear (int frame) { - curves[frame] = LINEAR; - } - - /// Sets the specified frame to stepped interpolation. - /// Between 0 and frameCount - 1, inclusive. - public void SetStepped (int frame) { - curves[frame] = STEPPED; - } - - /// Returns the interpolation type for the specified frame. - /// Between 0 and frameCount - 1, inclusive. - /// , or + the index of the Bezier segments. - public float GetCurveType (int frame) { - return (int)curves[frame]; - } - - /// Shrinks the storage for Bezier curves, for use when bezierCount (specified in the constructor) was larger - /// than the actual number of Bezier curves. - public void Shrink (int bezierCount) { - int size = FrameCount + bezierCount * BEZIER_SIZE; - if (curves.Length > size) { - float[] newCurves = new float[size]; - Array.Copy(curves, 0, newCurves, 0, size); - curves = newCurves; - } - } - - /// - /// Stores the segments for the specified Bezier curve. For timelines that modify multiple values, there may be more than - /// one curve per frame. - /// The ordinal of this Bezier curve for this timeline, between 0 and bezierCount - 1 (specified - /// in the constructor), inclusive. - /// Between 0 and frameCount - 1, inclusive. - /// The index of the value for the frame this curve is used for. - /// The time for the first key. - /// The value for the first key. - /// The time for the first Bezier handle. - /// The value for the first Bezier handle. - /// The time of the second Bezier handle. - /// The value for the second Bezier handle. - /// The time for the second key. - /// The value for the second key. - public void SetBezier (int bezier, int frame, int value, float time1, float value1, float cx1, float cy1, float cx2, - float cy2, float time2, float value2) { - - float[] curves = this.curves; - int i = FrameCount + bezier * BEZIER_SIZE; - if (value == 0) curves[frame] = BEZIER + i; - float tmpx = (time1 - cx1 * 2 + cx2) * 0.03f, tmpy = (value1 - cy1 * 2 + cy2) * 0.03f; - float dddx = ((cx1 - cx2) * 3 - time1 + time2) * 0.006f, dddy = ((cy1 - cy2) * 3 - value1 + value2) * 0.006f; - float ddx = tmpx * 2 + dddx, ddy = tmpy * 2 + dddy; - float dx = (cx1 - time1) * 0.3f + tmpx + dddx * 0.16666667f, dy = (cy1 - value1) * 0.3f + tmpy + dddy * 0.16666667f; - float x = time1 + dx, y = value1 + dy; - for (int 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; - } - } - - /// - /// Returns the Bezier interpolated value for the specified time. - /// The index into for the values of the frame before time. - /// The offset from frameIndex to the value this curve is used for. - /// The index of the Bezier segments. See . - public float GetBezierValue (float time, int frameIndex, int valueOffset, int i) { - float[] curves = this.curves; - if (curves[i] > time) { - float x = frames[frameIndex], y = frames[frameIndex + valueOffset]; - return y + (time - x) / (curves[i] - x) * (curves[i + 1] - y); - } - int 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 += FrameEntries; - { // scope added to prevent compile error "float x and y declared in enclosing scope" - float x = curves[n - 2], y = curves[n - 1]; - return y + (time - x) / (frames[frameIndex] - x) * (frames[frameIndex + valueOffset] - y); - } - } - } - - /// The base class for a that sets one property. - public abstract class CurveTimeline1 : CurveTimeline { - public const int ENTRIES = 2; - internal const int VALUE = 1; - - /// The maximum number of Bezier curves. See . - /// Unique identifiers for the properties the timeline modifies. - public CurveTimeline1 (int frameCount, int bezierCount, string propertyId) - : base(frameCount, bezierCount, propertyId) { - } - - public override int FrameEntries { - get { return ENTRIES; } - } - - /// Sets the time and value for the specified frame. - /// Between 0 and frameCount, inclusive. - /// The frame time in seconds - public void SetFrame (int frame, float time, float value) { - frame <<= 1; - frames[frame] = time; - frames[frame + VALUE] = value; - } - - /// Returns the interpolated value for the specified time. - public float GetCurveValue (float time) { - float[] frames = this.frames; - int i = frames.Length - 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 LINEAR: - float before = frames[i], value = frames[i + VALUE]; - return value + (time - before) / (frames[i + ENTRIES] - before) * (frames[i + ENTRIES + VALUE] - value); - case STEPPED: - return frames[i + VALUE]; - } - return GetBezierValue(time, i, VALUE, curveType - BEZIER); - } - } - - /// The base class for a which sets two properties. - public abstract class CurveTimeline2 : CurveTimeline { - public const int ENTRIES = 3; - internal const int VALUE1 = 1, VALUE2 = 2; - - /// The maximum number of Bezier curves. See . - /// Unique identifiers for the properties the timeline modifies. - public CurveTimeline2 (int frameCount, int bezierCount, string propertyId1, string propertyId2) - : base(frameCount, bezierCount, propertyId1, propertyId2) { - } - - public override int FrameEntries { - get { return ENTRIES; } - } - - /// Sets the time and values for the specified frame. - /// Between 0 and frameCount, inclusive. - /// The frame time in seconds. - public void SetFrame (int frame, float time, float value1, float value2) { - frame *= ENTRIES; - frames[frame] = time; - frames[frame + VALUE1] = value1; - frames[frame + VALUE2] = value2; - } - } - - /// Changes a bone's local . - public class RotateTimeline : CurveTimeline1, IBoneTimeline { - readonly int boneIndex; - - public RotateTimeline (int frameCount, int bezierCount, int boneIndex) - : base(frameCount, bezierCount, (int)Property.Rotate + "|" + boneIndex) { - this.boneIndex = boneIndex; - } - - public int BoneIndex { - get { - return boneIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - if (!bone.active) return; - - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case MixBlend.Setup: - bone.rotation = bone.data.rotation; - return; - case MixBlend.First: - bone.rotation += (bone.data.rotation - bone.rotation) * alpha; - return; - } - return; - } - - float r = GetCurveValue(time); - switch (blend) { - case MixBlend.Setup: - bone.rotation = bone.data.rotation + r * alpha; - break; - case MixBlend.First: - case MixBlend.Replace: - r += bone.data.rotation - bone.rotation; - goto case MixBlend.Add; // Fall through. - case MixBlend.Add: - bone.rotation += r * alpha; - break; - } - } - } - - /// Changes a bone's local and . - public class TranslateTimeline : CurveTimeline2, IBoneTimeline { - readonly int boneIndex; - - public TranslateTimeline (int frameCount, int bezierCount, int boneIndex) - : base(frameCount, bezierCount, // - (int)Property.X + "|" + boneIndex, // - (int)Property.Y + "|" + boneIndex) { - this.boneIndex = boneIndex; - } - - public int BoneIndex { - get { - return boneIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - if (!bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - 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; - return; - } - return; - } - - float x, y; - GetCurveValue(out x, out y, time); // note: reference implementation has code inlined - - 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; - break; - } - } - - public void GetCurveValue (out float x, out float y, float time) { - int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; - switch (curveType) { - case LINEAR: - float before = frames[i]; - x = frames[i + VALUE1]; - y = frames[i + VALUE2]; - float t = (time - before) / (frames[i + ENTRIES] - before); - x += (frames[i + ENTRIES + VALUE1] - x) * t; - y += (frames[i + ENTRIES + VALUE2] - y) * t; - break; - case STEPPED: - x = frames[i + VALUE1]; - y = frames[i + VALUE2]; - break; - default: - x = GetBezierValue(time, i, VALUE1, curveType - BEZIER); - y = GetBezierValue(time, i, VALUE2, curveType + BEZIER_SIZE - BEZIER); - break; - } - } - } - - /// Changes a bone's local . - public class TranslateXTimeline : CurveTimeline1, IBoneTimeline { - readonly int boneIndex; - - public TranslateXTimeline (int frameCount, int bezierCount, int boneIndex) - : base(frameCount, bezierCount, (int)Property.X + "|" + boneIndex) { - this.boneIndex = boneIndex; - } - - public int BoneIndex { - get { - return boneIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - if (!bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case MixBlend.Setup: - bone.x = bone.data.x; - return; - case MixBlend.First: - bone.x += (bone.data.x - bone.x) * alpha; - return; - } - return; - } - - float x = GetCurveValue(time); - switch (blend) { - case MixBlend.Setup: - bone.x = bone.data.x + x * alpha; - break; - case MixBlend.First: - case MixBlend.Replace: - bone.x += (bone.data.x + x - bone.x) * alpha; - break; - case MixBlend.Add: - bone.x += x * alpha; - break; - } - } - } - - /// Changes a bone's local . - public class TranslateYTimeline : CurveTimeline1, IBoneTimeline { - readonly int boneIndex; - - public TranslateYTimeline (int frameCount, int bezierCount, int boneIndex) - : base(frameCount, bezierCount, (int)Property.Y + "|" + boneIndex) { - this.boneIndex = boneIndex; - } - - public int BoneIndex { - get { - return boneIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - if (!bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case MixBlend.Setup: - bone.y = bone.data.y; - return; - case MixBlend.First: - bone.y += (bone.data.y - bone.y) * alpha; - return; - } - return; - } - - float y = GetCurveValue(time); - switch (blend) { - case MixBlend.Setup: - bone.y = bone.data.y + y * alpha; - break; - case MixBlend.First: - case MixBlend.Replace: - bone.y += (bone.data.y + y - bone.y) * alpha; - break; - case MixBlend.Add: - bone.y += y * alpha; - break; - } - } - } - - /// Changes a bone's local and . - public class ScaleTimeline : CurveTimeline2, IBoneTimeline { - readonly int boneIndex; - - public ScaleTimeline (int frameCount, int bezierCount, int boneIndex) - : base(frameCount, bezierCount, // - (int)Property.ScaleX + "|" + boneIndex, // - (int)Property.ScaleY + "|" + boneIndex) { - this.boneIndex = boneIndex; - } - - public int BoneIndex { - get { - return boneIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - if (!bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - 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; - return; - } - return; - } - - float x, y; - int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; - switch (curveType) { - case LINEAR: - float before = frames[i]; - x = frames[i + VALUE1]; - y = frames[i + VALUE2]; - float t = (time - before) / (frames[i + ENTRIES] - before); - x += (frames[i + ENTRIES + VALUE1] - x) * t; - y += (frames[i + ENTRIES + VALUE2] - y) * t; - break; - case STEPPED: - x = frames[i + VALUE1]; - y = frames[i + VALUE2]; - break; - default: - x = GetBezierValue(time, i, VALUE1, curveType - BEZIER); - y = GetBezierValue(time, i, VALUE2, curveType + BEZIER_SIZE - BEZIER); - break; - } - 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 { - // Mixing out uses sign of setup or current pose, else use sign of key. - float bx, by; - if (direction == MixDirection.Out) { - switch (blend) { - case MixBlend.Setup: - bx = bone.data.scaleX; - by = bone.data.scaleY; - bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha; - bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha; - break; - case MixBlend.First: - case MixBlend.Replace: - bx = bone.scaleX; - by = bone.scaleY; - bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha; - bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha; - break; - case MixBlend.Add: - bone.scaleX += (x - bone.data.scaleX) * alpha; - bone.scaleY += (y - bone.data.scaleY) * alpha; - break; - } - } else { - switch (blend) { - case MixBlend.Setup: - bx = Math.Abs(bone.data.scaleX) * Math.Sign(x); - by = Math.Abs(bone.data.scaleY) * Math.Sign(y); - bone.scaleX = bx + (x - bx) * alpha; - bone.scaleY = by + (y - by) * alpha; - break; - case MixBlend.First: - case MixBlend.Replace: - bx = Math.Abs(bone.scaleX) * Math.Sign(x); - by = Math.Abs(bone.scaleY) * Math.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; - break; - } - } - } - } - } - - /// Changes a bone's local . - public class ScaleXTimeline : CurveTimeline1, IBoneTimeline { - readonly int boneIndex; - - public ScaleXTimeline (int frameCount, int bezierCount, int boneIndex) - : base(frameCount, bezierCount, (int)Property.ScaleX + "|" + boneIndex) { - this.boneIndex = boneIndex; - } - - public int BoneIndex { - get { - return boneIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - if (!bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case MixBlend.Setup: - bone.scaleX = bone.data.scaleX; - return; - case MixBlend.First: - bone.scaleX += (bone.data.scaleX - bone.scaleX) * alpha; - return; - } - return; - } - - float x = GetCurveValue(time) * bone.data.scaleX; - if (alpha == 1) { - if (blend == MixBlend.Add) - bone.scaleX += x - bone.data.scaleX; - else - bone.scaleX = x; - } else { - // Mixing out uses sign of setup or current pose, else use sign of key. - float bx; - if (direction == MixDirection.Out) { - switch (blend) { - case MixBlend.Setup: - bx = bone.data.scaleX; - bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha; - break; - case MixBlend.First: - case MixBlend.Replace: - bx = bone.scaleX; - bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha; - break; - case MixBlend.Add: - bone.scaleX += (x - bone.data.scaleX) * alpha; - break; - } - } else { - switch (blend) { - case MixBlend.Setup: - bx = Math.Abs(bone.data.scaleX) * Math.Sign(x); - bone.scaleX = bx + (x - bx) * alpha; - break; - case MixBlend.First: - case MixBlend.Replace: - bx = Math.Abs(bone.scaleX) * Math.Sign(x); - bone.scaleX = bx + (x - bx) * alpha; - break; - case MixBlend.Add: - bone.scaleX += (x - bone.data.scaleX) * alpha; - break; - } - } - } - } - } - - /// Changes a bone's local . - public class ScaleYTimeline : CurveTimeline1, IBoneTimeline { - readonly int boneIndex; - - public ScaleYTimeline (int frameCount, int bezierCount, int boneIndex) - : base(frameCount, bezierCount, (int)Property.ScaleY + "|" + boneIndex) { - this.boneIndex = boneIndex; - } - - public int BoneIndex { - get { - return boneIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - if (!bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case MixBlend.Setup: - bone.scaleY = bone.data.scaleY; - return; - case MixBlend.First: - bone.scaleY += (bone.data.scaleY - bone.scaleY) * alpha; - return; - } - return; - } - - float y = GetCurveValue(time) * bone.data.scaleY; - if (alpha == 1) { - if (blend == MixBlend.Add) - bone.scaleY += y - bone.data.scaleY; - else - bone.scaleY = y; - } else { - // Mixing out uses sign of setup or current pose, else use sign of key. - float by; - if (direction == MixDirection.Out) { - switch (blend) { - case MixBlend.Setup: - by = bone.data.scaleY; - bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha; - break; - case MixBlend.First: - case MixBlend.Replace: - by = bone.scaleY; - bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha; - break; - case MixBlend.Add: - bone.scaleY += (y - bone.data.scaleY) * alpha; - break; - } - } else { - switch (blend) { - case MixBlend.Setup: - by = Math.Abs(bone.data.scaleY) * Math.Sign(y); - bone.scaleY = by + (y - by) * alpha; - break; - case MixBlend.First: - case MixBlend.Replace: - by = Math.Abs(bone.scaleY) * Math.Sign(y); - bone.scaleY = by + (y - by) * alpha; - break; - case MixBlend.Add: - bone.scaleY += (y - bone.data.scaleY) * alpha; - break; - } - } - } - } - } - - /// Changes a bone's local and . - public class ShearTimeline : CurveTimeline2, IBoneTimeline { - readonly int boneIndex; - - public ShearTimeline (int frameCount, int bezierCount, int boneIndex) - : base(frameCount, bezierCount, // - (int)Property.ShearX + "|" + boneIndex, // - (int)Property.ShearY + "|" + boneIndex) { - this.boneIndex = boneIndex; - } - - public int BoneIndex { - get { - return boneIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - if (!bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - 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; - return; - } - return; - } - - float x, y; - int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; - switch (curveType) { - case LINEAR: - float before = frames[i]; - x = frames[i + VALUE1]; - y = frames[i + VALUE2]; - float t = (time - before) / (frames[i + ENTRIES] - before); - x += (frames[i + ENTRIES + VALUE1] - x) * t; - y += (frames[i + ENTRIES + VALUE2] - y) * t; - break; - case STEPPED: - x = frames[i + VALUE1]; - y = frames[i + VALUE2]; - break; - default: - x = GetBezierValue(time, i, VALUE1, curveType - BEZIER); - y = GetBezierValue(time, i, VALUE2, curveType + BEZIER_SIZE - BEZIER); - break; - } - - 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; - break; - } - } - } - - /// Changes a bone's local . - public class ShearXTimeline : CurveTimeline1, IBoneTimeline { - readonly int boneIndex; - - public ShearXTimeline (int frameCount, int bezierCount, int boneIndex) - : base(frameCount, bezierCount, (int)Property.ShearX + "|" + boneIndex) { - this.boneIndex = boneIndex; - } - - public int BoneIndex { - get { - return boneIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - if (!bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case MixBlend.Setup: - bone.shearX = bone.data.shearX; - return; - case MixBlend.First: - bone.shearX += (bone.data.shearX - bone.shearX) * alpha; - return; - } - return; - } - - float x = GetCurveValue(time); - switch (blend) { - case MixBlend.Setup: - bone.shearX = bone.data.shearX + x * alpha; - break; - case MixBlend.First: - case MixBlend.Replace: - bone.shearX += (bone.data.shearX + x - bone.shearX) * alpha; - break; - case MixBlend.Add: - bone.shearX += x * alpha; - break; - } - } - } - - /// Changes a bone's local . - public class ShearYTimeline : CurveTimeline1, IBoneTimeline { - readonly int boneIndex; - - public ShearYTimeline (int frameCount, int bezierCount, int boneIndex) - : base(frameCount, bezierCount, (int)Property.ShearY + "|" + boneIndex) { - this.boneIndex = boneIndex; - } - - public int BoneIndex { - get { - return boneIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - if (!bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case MixBlend.Setup: - bone.shearY = bone.data.shearY; - return; - case MixBlend.First: - bone.shearY += (bone.data.shearY - bone.shearY) * alpha; - return; - } - return; - } - - float y = GetCurveValue(time); - switch (blend) { - case MixBlend.Setup: - bone.shearY = bone.data.shearY + y * alpha; - break; - case MixBlend.First: - case MixBlend.Replace: - bone.shearY += (bone.data.shearY + y - bone.shearY) * alpha; - break; - case MixBlend.Add: - bone.shearY += y * alpha; - break; - } - } - } - - /// Changes a slot's . - public class RGBATimeline : CurveTimeline, ISlotTimeline { - public const int ENTRIES = 5; - protected const int R = 1, G = 2, B = 3, A = 4; - - readonly int slotIndex; - - public RGBATimeline (int frameCount, int bezierCount, int slotIndex) - : base(frameCount, bezierCount, // - (int)Property.RGB + "|" + slotIndex, // - (int)Property.Alpha + "|" + slotIndex) { - this.slotIndex = slotIndex; - } - public override int FrameEntries { - get { return ENTRIES; } - } - - public int SlotIndex { - get { - return slotIndex; - } - } - - /// Sets the time and color for the specified frame. - /// Between 0 and frameCount, inclusive. - /// The frame time in seconds. - public void 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; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Slot slot = skeleton.slots.Items[slotIndex]; - if (!slot.bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - var setup = slot.data; - switch (blend) { - case MixBlend.Setup: - slot.r = setup.r; - slot.g = setup.g; - slot.b = setup.b; - slot.a = setup.a; - return; - case MixBlend.First: - slot.r += (setup.r - slot.r) * alpha; - slot.g += (setup.g - slot.g) * alpha; - slot.b += (setup.b - slot.b) * alpha; - slot.a += (setup.a - slot.a) * alpha; - slot.ClampColor(); - return; - } - return; - } - - float r, g, b, a; - int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; - switch (curveType) { - case LINEAR: - float before = frames[i]; - r = frames[i + R]; - g = frames[i + G]; - b = frames[i + B]; - a = frames[i + A]; - float t = (time - before) / (frames[i + ENTRIES] - before); - r += (frames[i + ENTRIES + R] - r) * t; - g += (frames[i + ENTRIES + G] - g) * t; - b += (frames[i + ENTRIES + B] - b) * t; - a += (frames[i + ENTRIES + A] - a) * t; - break; - case STEPPED: - r = frames[i + R]; - g = frames[i + G]; - b = frames[i + B]; - a = frames[i + A]; - break; - default: - r = GetBezierValue(time, i, R, curveType - BEZIER); - g = GetBezierValue(time, i, G, curveType + BEZIER_SIZE - BEZIER); - b = GetBezierValue(time, i, B, curveType + BEZIER_SIZE * 2 - BEZIER); - a = GetBezierValue(time, i, A, curveType + BEZIER_SIZE * 3 - BEZIER); - break; - } - - if (alpha == 1) { - slot.r = r; - slot.g = g; - slot.b = b; - slot.a = a; - } else { - float br, bg, bb, ba; - if (blend == MixBlend.Setup) { - br = slot.data.r; - bg = slot.data.g; - bb = slot.data.b; - ba = slot.data.a; - } else { - br = slot.r; - bg = slot.g; - bb = slot.b; - ba = slot.a; - } - slot.r = br + (r - br) * alpha; - slot.g = bg + (g - bg) * alpha; - slot.b = bb + (b - bb) * alpha; - slot.a = ba + (a - ba) * alpha; - } - slot.ClampColor(); - } - } - - /// Changes the RGB for a slot's . - public class RGBTimeline : CurveTimeline, ISlotTimeline { - public const int ENTRIES = 4; - protected const int R = 1, G = 2, B = 3; - - readonly int slotIndex; - - public RGBTimeline (int frameCount, int bezierCount, int slotIndex) - : base(frameCount, bezierCount, // - (int)Property.RGB + "|" + slotIndex) { - this.slotIndex = slotIndex; - } - - public override int FrameEntries { - get { return ENTRIES; } - } - - public int SlotIndex { - get { - return slotIndex; - } - } - - /// Sets the time and color for the specified frame. - /// Between 0 and frameCount, inclusive. - /// The frame time in seconds. - public void SetFrame (int frame, float time, float r, float g, float b) { - frame <<= 2; - frames[frame] = time; - frames[frame + R] = r; - frames[frame + G] = g; - frames[frame + B] = b; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Slot slot = skeleton.slots.Items[slotIndex]; - if (!slot.bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - var setup = slot.data; - switch (blend) { - case MixBlend.Setup: - slot.r = setup.r; - slot.g = setup.g; - slot.b = setup.b; - return; - case MixBlend.First: - slot.r += (setup.r - slot.r) * alpha; - slot.g += (setup.g - slot.g) * alpha; - slot.b += (setup.b - slot.b) * alpha; - slot.ClampColor(); - return; - } - return; - } - - float r, g, b; - int i = Search(frames, time, ENTRIES), curveType = (int)curves[i >> 2]; - switch (curveType) { - case LINEAR: - float before = frames[i]; - r = frames[i + R]; - g = frames[i + G]; - b = frames[i + B]; - float t = (time - before) / (frames[i + ENTRIES] - before); - r += (frames[i + ENTRIES + R] - r) * t; - g += (frames[i + ENTRIES + G] - g) * t; - b += (frames[i + ENTRIES + B] - b) * t; - break; - case STEPPED: - r = frames[i + R]; - g = frames[i + G]; - b = frames[i + B]; - break; - default: - r = GetBezierValue(time, i, R, curveType - BEZIER); - g = GetBezierValue(time, i, G, curveType + BEZIER_SIZE - BEZIER); - b = GetBezierValue(time, i, B, curveType + BEZIER_SIZE * 2 - BEZIER); - break; - } - - if (alpha == 1) { - slot.r = r; - slot.g = g; - slot.b = b; - } else { - float br, bg, bb; - if (blend == MixBlend.Setup) { - var setup = slot.data; - br = setup.r; - bg = setup.g; - bb = setup.b; - } else { - br = slot.r; - bg = slot.g; - bb = slot.b; - } - slot.r = br + (r - br) * alpha; - slot.g = bg + (g - bg) * alpha; - slot.b = bb + (b - bb) * alpha; - } - slot.ClampColor(); - } - } - - /// Changes the alpha for a slot's . - public class AlphaTimeline : CurveTimeline1, ISlotTimeline { - readonly int slotIndex; - - public AlphaTimeline (int frameCount, int bezierCount, int slotIndex) - : base(frameCount, bezierCount, (int)Property.Alpha + "|" + slotIndex) { - this.slotIndex = slotIndex; - } - - public int SlotIndex { - get { - return slotIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Slot slot = skeleton.slots.Items[slotIndex]; - if (!slot.bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - var setup = slot.data; - switch (blend) { - case MixBlend.Setup: - slot.a = setup.a; - return; - case MixBlend.First: - slot.a += (setup.a - slot.a) * alpha; - slot.ClampColor(); - return; - } - return; - } - - float a = GetCurveValue(time); - if (alpha == 1) - slot.a = a; - else { - if (blend == MixBlend.Setup) slot.a = slot.data.a; - slot.a += (a - slot.a) * alpha; - } - slot.ClampColor(); - } - } - - /// Changes a slot's and for two color tinting. - public class RGBA2Timeline : CurveTimeline, ISlotTimeline { - public const int ENTRIES = 8; - protected const int R = 1, G = 2, B = 3, A = 4, R2 = 5, G2 = 6, B2 = 7; - - readonly int slotIndex; - - public RGBA2Timeline (int frameCount, int bezierCount, int slotIndex) - : base(frameCount, bezierCount, // - (int)Property.RGB + "|" + slotIndex, // - (int)Property.Alpha + "|" + slotIndex, // - (int)Property.RGB2 + "|" + slotIndex) { - this.slotIndex = slotIndex; - } - - public override int FrameEntries { - get { - return ENTRIES; - } - } - - /// - /// The index of the slot in that will be changed when this timeline is applied. The - /// must have a dark color available. - public int SlotIndex { - get { - return slotIndex; - } - } - - /// Sets the time, light color, and dark color for the specified frame. - /// Between 0 and frameCount, inclusive. - /// The frame time in seconds. - public void SetFrame (int frame, float time, float r, float g, float b, float a, float r2, float g2, float b2) { - frame <<= 3; - 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; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Slot slot = skeleton.slots.Items[slotIndex]; - if (!slot.bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - SlotData setup = slot.data; - switch (blend) { - case MixBlend.Setup: - slot.r = setup.r; - slot.g = setup.g; - slot.b = setup.b; - slot.a = setup.a; - slot.ClampColor(); - slot.r2 = setup.r2; - slot.g2 = setup.g2; - slot.b2 = setup.b2; - slot.ClampSecondColor(); - return; - case MixBlend.First: - slot.r += (slot.r - setup.r) * alpha; - slot.g += (slot.g - setup.g) * alpha; - slot.b += (slot.b - setup.b) * alpha; - slot.a += (slot.a - setup.a) * alpha; - slot.ClampColor(); - slot.r2 += (slot.r2 - setup.r2) * alpha; - slot.g2 += (slot.g2 - setup.g2) * alpha; - slot.b2 += (slot.b2 - setup.b2) * alpha; - slot.ClampSecondColor(); - return; - } - return; - } - - float r, g, b, a, r2, g2, b2; - int i = Search(frames, time, ENTRIES), curveType = (int)curves[i >> 3]; - switch (curveType) { - case LINEAR: - float before = frames[i]; - r = frames[i + R]; - g = frames[i + G]; - b = frames[i + B]; - a = frames[i + A]; - r2 = frames[i + R2]; - g2 = frames[i + G2]; - b2 = frames[i + B2]; - float t = (time - before) / (frames[i + ENTRIES] - before); - r += (frames[i + ENTRIES + R] - r) * t; - g += (frames[i + ENTRIES + G] - g) * t; - b += (frames[i + ENTRIES + B] - b) * t; - a += (frames[i + ENTRIES + A] - a) * t; - r2 += (frames[i + ENTRIES + R2] - r2) * t; - g2 += (frames[i + ENTRIES + G2] - g2) * t; - b2 += (frames[i + ENTRIES + B2] - b2) * t; - break; - case STEPPED: - r = frames[i + R]; - g = frames[i + G]; - b = frames[i + B]; - a = frames[i + A]; - r2 = frames[i + R2]; - g2 = frames[i + G2]; - b2 = frames[i + B2]; - break; - default: - r = GetBezierValue(time, i, R, curveType - BEZIER); - g = GetBezierValue(time, i, G, curveType + BEZIER_SIZE - BEZIER); - b = GetBezierValue(time, i, B, curveType + BEZIER_SIZE * 2 - BEZIER); - a = GetBezierValue(time, i, A, curveType + BEZIER_SIZE * 3 - BEZIER); - r2 = GetBezierValue(time, i, R2, curveType + BEZIER_SIZE * 4 - BEZIER); - g2 = GetBezierValue(time, i, G2, curveType + BEZIER_SIZE * 5 - BEZIER); - b2 = GetBezierValue(time, i, B2, curveType + BEZIER_SIZE * 6 - BEZIER); - break; - } - - if (alpha == 1) { - slot.r = r; - slot.g = g; - slot.b = b; - slot.a = a; - slot.r2 = r2; - slot.g2 = g2; - slot.b2 = b2; - } else { - float br, bg, bb, ba, br2, bg2, bb2; - if (blend == MixBlend.Setup) { - br = slot.data.r; - bg = slot.data.g; - bb = slot.data.b; - ba = slot.data.a; - br2 = slot.data.r2; - bg2 = slot.data.g2; - bb2 = slot.data.b2; - } else { - br = slot.r; - bg = slot.g; - bb = slot.b; - ba = slot.a; - br2 = slot.r2; - bg2 = slot.g2; - bb2 = slot.b2; - } - slot.r = br + (r - br) * alpha; - slot.g = bg + (g - bg) * alpha; - slot.b = bb + (b - bb) * alpha; - slot.a = ba + (a - ba) * alpha; - slot.r2 = br2 + (r2 - br2) * alpha; - slot.g2 = bg2 + (g2 - bg2) * alpha; - slot.b2 = bb2 + (b2 - bb2) * alpha; - } - slot.ClampColor(); - slot.ClampSecondColor(); - } - } - - /// Changes the RGB for a slot's and for two color tinting. - public class RGB2Timeline : CurveTimeline, ISlotTimeline { - public const int ENTRIES = 7; - protected const int R = 1, G = 2, B = 3, R2 = 4, G2 = 5, B2 = 6; - - readonly int slotIndex; - - public RGB2Timeline (int frameCount, int bezierCount, int slotIndex) - : base(frameCount, bezierCount, // - (int)Property.RGB + "|" + slotIndex, // - (int)Property.RGB2 + "|" + slotIndex) { - this.slotIndex = slotIndex; - } - - public override int FrameEntries { - get { - return ENTRIES; - } - } - - /// - /// The index of the slot in that will be changed when this timeline is applied. The - /// must have a dark color available. - public int SlotIndex { - get { - return slotIndex; - } - } - - /// Sets the time, light color, and dark color for the specified frame. - /// Between 0 and frameCount, inclusive. - /// The frame time in seconds. - public void 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; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Slot slot = skeleton.slots.Items[slotIndex]; - if (!slot.bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - SlotData setup = slot.data; - switch (blend) { - case MixBlend.Setup: - slot.r = setup.r; - slot.g = setup.g; - slot.b = setup.b; - slot.ClampColor(); - slot.r2 = setup.r2; - slot.g2 = setup.g2; - slot.b2 = setup.b2; - slot.ClampSecondColor(); - return; - case MixBlend.First: - slot.r += (slot.r - setup.r) * alpha; - slot.g += (slot.g - setup.g) * alpha; - slot.b += (slot.b - setup.b) * alpha; - slot.ClampColor(); - slot.r2 += (slot.r2 - setup.r2) * alpha; - slot.g2 += (slot.g2 - setup.g2) * alpha; - slot.b2 += (slot.b2 - setup.b2) * alpha; - slot.ClampSecondColor(); - return; - } - return; - } - - float r, g, b, r2, g2, b2; - int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; - switch (curveType) { - case LINEAR: - float before = frames[i]; - r = frames[i + R]; - g = frames[i + G]; - b = frames[i + B]; - r2 = frames[i + R2]; - g2 = frames[i + G2]; - b2 = frames[i + B2]; - float t = (time - before) / (frames[i + ENTRIES] - before); - r += (frames[i + ENTRIES + R] - r) * t; - g += (frames[i + ENTRIES + G] - g) * t; - b += (frames[i + ENTRIES + B] - b) * t; - r2 += (frames[i + ENTRIES + R2] - r2) * t; - g2 += (frames[i + ENTRIES + G2] - g2) * t; - b2 += (frames[i + ENTRIES + B2] - b2) * t; - break; - case STEPPED: - r = frames[i + R]; - g = frames[i + G]; - b = frames[i + B]; - r2 = frames[i + R2]; - g2 = frames[i + G2]; - b2 = frames[i + B2]; - break; - default: - r = GetBezierValue(time, i, R, curveType - BEZIER); - g = GetBezierValue(time, i, G, curveType + BEZIER_SIZE - BEZIER); - b = GetBezierValue(time, i, B, curveType + BEZIER_SIZE * 2 - BEZIER); - r2 = GetBezierValue(time, i, R2, curveType + BEZIER_SIZE * 3 - BEZIER); - g2 = GetBezierValue(time, i, G2, curveType + BEZIER_SIZE * 4 - BEZIER); - b2 = GetBezierValue(time, i, B2, curveType + BEZIER_SIZE * 5 - BEZIER); - break; - } - - if (alpha == 1) { - slot.r = r; - slot.g = g; - slot.b = b; - slot.r2 = r2; - slot.g2 = g2; - slot.b2 = b2; - } else { - float br, bg, bb, br2, bg2, bb2; - if (blend == MixBlend.Setup) { - SlotData setup = slot.data; - br = setup.r; - bg = setup.g; - bb = setup.b; - br2 = setup.r2; - bg2 = setup.g2; - bb2 = setup.b2; - } else { - br = slot.r; - bg = slot.g; - bb = slot.b; - br2 = slot.r2; - bg2 = slot.g2; - bb2 = slot.b2; - } - slot.r = br + (r - br) * alpha; - slot.g = bg + (g - bg) * alpha; - slot.b = bb + (b - bb) * alpha; - slot.r2 = br2 + (r2 - br2) * alpha; - slot.g2 = bg2 + (g2 - bg2) * alpha; - slot.b2 = bb2 + (b2 - bb2) * alpha; - } - slot.ClampColor(); - slot.ClampSecondColor(); - } - } - - /// Changes a slot's . - public class AttachmentTimeline : Timeline, ISlotTimeline { - readonly int slotIndex; - readonly string[] attachmentNames; - - public AttachmentTimeline (int frameCount, int slotIndex) - : base(frameCount, (int)Property.Attachment + "|" + slotIndex) { - this.slotIndex = slotIndex; - attachmentNames = new String[frameCount]; - } - - public int SlotIndex { - get { - return slotIndex; - } - } - - /// The attachment name for each frame. May contain null values to clear the attachment. - public string[] AttachmentNames { - get { - return attachmentNames; - } - } - - /// Sets the time and attachment name for the specified frame. - /// Between 0 and frameCount, inclusive. - /// The frame time in seconds. - public void SetFrame (int frame, float time, String attachmentName) { - frames[frame] = time; - attachmentNames[frame] = attachmentName; - } - - public override void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Slot slot = skeleton.slots.Items[slotIndex]; - if (!slot.bone.active) return; - - if (direction == MixDirection.Out) { - if (blend == MixBlend.Setup) SetAttachment(skeleton, slot, slot.data.attachmentName); - return; - } - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - if (blend == MixBlend.Setup || blend == MixBlend.First) SetAttachment(skeleton, slot, slot.data.attachmentName); - return; - } - - SetAttachment(skeleton, slot, attachmentNames[Search(frames, time)]); - } - - private void SetAttachment (Skeleton skeleton, Slot slot, string attachmentName) { - slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); - } - } - - /// Changes a slot's to deform a . - public class DeformTimeline : CurveTimeline, ISlotTimeline { - readonly int slotIndex; - readonly VertexAttachment attachment; - internal float[][] vertices; - - public DeformTimeline (int frameCount, int bezierCount, int slotIndex, VertexAttachment attachment) - : base(frameCount, bezierCount, (int)Property.Deform + "|" + slotIndex + "|" + attachment.Id) { - this.slotIndex = slotIndex; - this.attachment = attachment; - vertices = new float[frameCount][]; - } - - public int SlotIndex { - get { - return slotIndex; - } - } - /// The attachment that will be deformed. - /// - public VertexAttachment Attachment { - get { - return attachment; - } - } - - /// The vertices for each frame. - public float[][] Vertices { - get { - return vertices; - } - } - - /// Sets the time and vertices for the specified frame. - /// Between 0 and frameCount, inclusive. - /// The frame time in seconds. - /// Vertex positions for an unweighted VertexAttachment, or deform offsets if it has weights. - public void SetFrame (int frame, float time, float[] vertices) { - frames[frame] = time; - this.vertices[frame] = vertices; - } - - /// Ignored (0 is used for a deform timeline). - /// Ignored (1 is used for a deform timeline). - public void setBezier (int bezier, int frame, int value, float time1, float value1, float cx1, float cy1, float cx2, - float cy2, float time2, float value2) { - float[] curves = this.curves; - int i = FrameCount + bezier * BEZIER_SIZE; - if (value == 0) curves[frame] = BEZIER + i; - float tmpx = (time1 - cx1 * 2 + cx2) * 0.03f, tmpy = cy2 * 0.03f - cy1 * 0.06f; - float dddx = ((cx1 - cx2) * 3 - time1 + time2) * 0.006f, dddy = (cy1 - cy2 + 0.33333333f) * 0.018f; - float ddx = tmpx * 2 + dddx, ddy = tmpy * 2 + dddy; - float dx = (cx1 - time1) * 0.3f + tmpx + dddx * 0.16666667f, dy = cy1 * 0.3f + tmpy + dddy * 0.16666667f; - float x = time1 + dx, y = dy; - for (int 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; - } - } - - /// Returns the interpolated percentage for the specified time. - /// The frame before time. - private float GetCurvePercent (float time, int frame) { - float[] curves = this.curves; - int i = (int)curves[frame]; - switch (i) { - case LINEAR: - float x = frames[frame]; - return (time - x) / (frames[frame + FrameEntries] - x); - case STEPPED: - return 0; - } - i -= BEZIER; - if (curves[i] > time) { - float x = frames[frame]; - return curves[i + 1] * (time - x) / (curves[i] - x); - } - int 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); - } - } - { // scope added to prevent compile error "float x and y declared in enclosing scope" - float x = curves[n - 2], y = curves[n - 1]; - return y + (1 - y) * (time - x) / (frames[frame + FrameEntries] - x); - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - - Slot slot = skeleton.slots.Items[slotIndex]; - if (!slot.bone.active) return; - var vertexAttachment = slot.attachment as VertexAttachment; - if (vertexAttachment == null || vertexAttachment.DeformAttachment != attachment) return; - - var deformArray = slot.Deform; - if (deformArray.Count == 0) blend = MixBlend.Setup; - - float[][] vertices = this.vertices; - int vertexCount = vertices[0].Length; - - float[] deform; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case MixBlend.Setup: - deformArray.Clear(); - return; - case MixBlend.First: - if (alpha == 1) { - deformArray.Clear(); - return; - } - - // Ensure size and preemptively set count. - if (deformArray.Capacity < vertexCount) deformArray.Capacity = vertexCount; - deformArray.Count = vertexCount; - deform = deformArray.Items; - - if (vertexAttachment.bones == null) { - // Unweighted vertex positions. - float[] setupVertices = vertexAttachment.vertices; - for (int i = 0; i < vertexCount; i++) - deform[i] += (setupVertices[i] - deform[i]) * alpha; - } else { - // Weighted deform offsets. - alpha = 1 - alpha; - for (int i = 0; i < vertexCount; i++) - deform[i] *= alpha; - } - return; - } - return; - } - - // Ensure size and preemptively set count. - if (deformArray.Capacity < vertexCount) deformArray.Capacity = vertexCount; - deformArray.Count = vertexCount; - deform = deformArray.Items; - - if (time >= frames[frames.Length - 1]) { // Time is after last frame. - float[] lastVertices = vertices[frames.Length - 1]; - if (alpha == 1) { - if (blend == MixBlend.Add) { - if (vertexAttachment.bones == null) { - // Unweighted vertex positions, no alpha. - float[] setupVertices = vertexAttachment.vertices; - for (int i = 0; i < vertexCount; i++) - deform[i] += lastVertices[i] - setupVertices[i]; - } else { - // Weighted deform offsets, no alpha. - for (int i = 0; i < vertexCount; i++) - deform[i] += lastVertices[i]; - } - } else { - // Vertex positions or deform offsets, no alpha. - Array.Copy(lastVertices, 0, deform, 0, vertexCount); - } - } else { - switch (blend) { - case MixBlend.Setup: { - if (vertexAttachment.bones == null) { - // Unweighted vertex positions, with alpha. - float[] setupVertices = vertexAttachment.vertices; - for (int i = 0; i < vertexCount; i++) { - float setup = setupVertices[i]; - deform[i] = setup + (lastVertices[i] - setup) * alpha; - } - } else { - // Weighted deform offsets, with alpha. - for (int 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 (int i = 0; i < vertexCount; i++) - deform[i] += (lastVertices[i] - deform[i]) * alpha; - break; - case MixBlend.Add: - if (vertexAttachment.bones == null) { - // Unweighted vertex positions, no alpha. - float[] setupVertices = vertexAttachment.vertices; - for (int i = 0; i < vertexCount; i++) - deform[i] += (lastVertices[i] - setupVertices[i]) * alpha; - } else { - // Weighted deform offsets, alpha. - for (int i = 0; i < vertexCount; i++) - deform[i] += lastVertices[i] * alpha; - } - break; - } - } - return; - } - - int frame = Search(frames, time); - float percent = GetCurvePercent(time, frame); - float[] prevVertices = vertices[frame]; - float[] nextVertices = vertices[frame + 1]; - - if (alpha == 1) { - if (blend == MixBlend.Add) { - if (vertexAttachment.bones == null) { - // Unweighted vertex positions, no alpha. - float[] setupVertices = vertexAttachment.vertices; - for (int 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 (int 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 (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - deform[i] = prev + (nextVertices[i] - prev) * percent; - } - } - } else { - switch (blend) { - case MixBlend.Setup: { - if (vertexAttachment.bones == null) { - // Unweighted vertex positions, with alpha. - float[] setupVertices = vertexAttachment.vertices; - for (int 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 (int 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 (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - deform[i] += (prev + (nextVertices[i] - prev) * percent - deform[i]) * alpha; - } - break; - } - case MixBlend.Add: - if (vertexAttachment.bones == null) { - // Unweighted vertex positions, with alpha. - float[] setupVertices = vertexAttachment.vertices; - for (int 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 (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - deform[i] += (prev + (nextVertices[i] - prev) * percent) * alpha; - } - } - break; - } - } - } - } - - /// Fires an when specific animation times are reached. - public class EventTimeline : Timeline { - readonly static string[] propertyIds = { ((int)Property.Event).ToString() }; - readonly Event[] events; - - public EventTimeline (int frameCount) - : base(frameCount, propertyIds) { - events = new Event[frameCount]; - } - - /// The event for each frame. - public Event[] Events { - get { - return events; - } - } - - /// Sets the time and event for the specified frame. - /// Between 0 and frameCount, inclusive. - public void SetFrame (int frame, Event e) { - frames[frame] = e.time; - events[frame] = e; - } - - /// Fires events for frames > lastTime and <= time. - public override void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, - MixBlend blend, MixDirection direction) { - - if (firedEvents == null) return; - - float[] frames = this.frames; - int frameCount = frames.Length; - - if (lastTime > time) { // Fire events after last time for looped animations. - Apply(skeleton, lastTime, int.MaxValue, firedEvents, alpha, blend, direction); - lastTime = -1f; - } else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame. - return; - if (time < frames[0]) return; // Time is before first frame. - - int i; - if (lastTime < frames[0]) - i = 0; - else { - i = Search(frames, lastTime) + 1; - float frameTime = frames[i]; - while (i > 0) { // Fire multiple events with the same frame. - if (frames[i - 1] != frameTime) break; - i--; - } - } - for (; i < frameCount && time >= frames[i]; i++) - firedEvents.Add(events[i]); - } - } - - /// Changes a skeleton's . - public class DrawOrderTimeline : Timeline { - static readonly string[] propertyIds = { ((int)Property.DrawOrder).ToString() }; - - readonly int[][] drawOrders; - - public DrawOrderTimeline (int frameCount) - : base(frameCount, propertyIds) { - drawOrders = new int[frameCount][]; - } - - /// The draw order for each frame. - /// . - public int[][] DrawOrders { - get { - return drawOrders; - } - } - - /// Sets the time and draw order for the specified frame. - /// Between 0 and frameCount, inclusive. - /// The frame time in seconds. - /// For each slot in , the index of the slot in the new draw order. May be null to use - /// setup pose draw order. - public void SetFrame (int frame, float time, int[] drawOrder) { - frames[frame] = time; - drawOrders[frame] = drawOrder; - } - - public override void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - - if (direction == MixDirection.Out) { - if (blend == MixBlend.Setup) Array.Copy(skeleton.slots.Items, 0, skeleton.drawOrder.Items, 0, skeleton.slots.Count); - return; - } - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - if (blend == MixBlend.Setup || blend == MixBlend.First) Array.Copy(skeleton.slots.Items, 0, skeleton.drawOrder.Items, 0, skeleton.slots.Count); - return; - } - - int[] drawOrderToSetupIndex = drawOrders[Search(frames, time)]; - if (drawOrderToSetupIndex == null) - Array.Copy(skeleton.slots.Items, 0, skeleton.drawOrder.Items, 0, skeleton.slots.Count); - else { - Slot[] slots = skeleton.slots.Items; - Slot[] drawOrder = skeleton.drawOrder.Items; - for (int i = 0, n = drawOrderToSetupIndex.Length; i < n; i++) - drawOrder[i] = slots[drawOrderToSetupIndex[i]]; - } - } - } - - /// Changes an IK constraint's , , - /// , , and . - public class IkConstraintTimeline : CurveTimeline { - public const int ENTRIES = 6; - private const int MIX = 1, SOFTNESS = 2, BEND_DIRECTION = 3, COMPRESS = 4, STRETCH = 5; - - readonly int ikConstraintIndex; - - public IkConstraintTimeline (int frameCount, int bezierCount, int ikConstraintIndex) - : base(frameCount, bezierCount, (int)Property.IkConstraint + "|" + ikConstraintIndex) { - this.ikConstraintIndex = ikConstraintIndex; - } - - public override int FrameEntries { - get { - return ENTRIES; - } - } - - /// The index of the IK constraint slot in that will be changed when this timeline is - /// applied. - public int IkConstraintIndex { - get { - return ikConstraintIndex; - } - } - - /// Sets the time, mix, softness, bend direction, compress, and stretch for the specified frame. - /// Between 0 and frameCount, inclusive. - /// The frame time in seconds. - /// 1 or -1. - public void 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] = bendDirection; - frames[frame + COMPRESS] = compress ? 1 : 0; - frames[frame + STRETCH] = stretch ? 1 : 0; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - IkConstraint constraint = skeleton.ikConstraints.Items[ikConstraintIndex]; - if (!constraint.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - 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; - } - return; - } - - float mix, softness; - int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; - switch (curveType) { - case LINEAR: - float before = frames[i]; - mix = frames[i + MIX]; - softness = frames[i + SOFTNESS]; - float t = (time - before) / (frames[i + ENTRIES] - before); - mix += (frames[i + ENTRIES + MIX] - mix) * t; - softness += (frames[i + ENTRIES + SOFTNESS] - softness) * t; - break; - case STEPPED: - mix = frames[i + MIX]; - softness = frames[i + SOFTNESS]; - break; - default: - mix = GetBezierValue(time, i, MIX, curveType - BEZIER); - softness = GetBezierValue(time, i, SOFTNESS, curveType + BEZIER_SIZE - BEZIER); - break; - } - - 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 = (int)frames[i + BEND_DIRECTION]; - constraint.compress = frames[i + COMPRESS] != 0; - constraint.stretch = frames[i + STRETCH] != 0; - } - } else { - constraint.mix += (mix - constraint.mix) * alpha; - constraint.softness += (softness - constraint.softness) * alpha; - if (direction == MixDirection.In) { - constraint.bendDirection = (int)frames[i + BEND_DIRECTION]; - constraint.compress = frames[i + COMPRESS] != 0; - constraint.stretch = frames[i + STRETCH] != 0; - } - } - } - } - - /// Changes a transform constraint's mixes. - public class TransformConstraintTimeline : CurveTimeline { - public const int ENTRIES = 7; - private const int ROTATE = 1, X = 2, Y = 3, SCALEX = 4, SCALEY = 5, SHEARY = 6; - - readonly int transformConstraintIndex; - - public TransformConstraintTimeline (int frameCount, int bezierCount, int transformConstraintIndex) - : base(frameCount, bezierCount, (int)Property.TransformConstraint + "|" + transformConstraintIndex) { - this.transformConstraintIndex = transformConstraintIndex; - } - - public override int FrameEntries { - get { - return ENTRIES; - } - } - - /// The index of the transform constraint slot in that will be changed when this - /// timeline is applied. - public int TransformConstraintIndex { - get { - return transformConstraintIndex; - } - } - - /// Sets the time, rotate mix, translate mix, scale mix, and shear mix for the specified frame. - /// Between 0 and frameCount, inclusive. - /// The frame time in seconds. - public void SetFrame (int 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; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - TransformConstraint constraint = skeleton.transformConstraints.Items[transformConstraintIndex]; - if (!constraint.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - TransformConstraintData data = constraint.data; - 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; - } - return; - } - - float rotate, x, y, scaleX, scaleY, shearY; - int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; - switch (curveType) { - case 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 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); - break; - } - - if (blend == MixBlend.Setup) { - TransformConstraintData 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; - 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; - } - } - } - - /// Changes a path constraint's . - public class PathConstraintPositionTimeline : CurveTimeline1 { - readonly int pathConstraintIndex; - - public PathConstraintPositionTimeline (int frameCount, int bezierCount, int pathConstraintIndex) - : base(frameCount, bezierCount, (int)Property.PathConstraintPosition + "|" + pathConstraintIndex) { - this.pathConstraintIndex = pathConstraintIndex; - } - - /// The index of the path constraint slot in that will be changed when this timeline - /// is applied. - public int PathConstraintIndex { - get { - return pathConstraintIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; - if (!constraint.active) return; - - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case MixBlend.Setup: - constraint.position = constraint.data.position; - return; - case MixBlend.First: - constraint.position += (constraint.data.position - constraint.position) * alpha; - return; - } - return; - } - - float position = GetCurveValue(time); - if (blend == MixBlend.Setup) - constraint.position = constraint.data.position + (position - constraint.data.position) * alpha; - else - constraint.position += (position - constraint.position) * alpha; - } - } - - /// Changes a path constraint's . - public class PathConstraintSpacingTimeline : CurveTimeline1 { - readonly int pathConstraintIndex; - - public PathConstraintSpacingTimeline (int frameCount, int bezierCount, int pathConstraintIndex) - : base(frameCount, bezierCount, (int)Property.PathConstraintSpacing + "|" + pathConstraintIndex) { - this.pathConstraintIndex = pathConstraintIndex; - } - - /// The index of the path constraint slot in that will be changed when this timeline - /// is applied. - public int PathConstraintIndex { - get { - return pathConstraintIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, MixBlend blend, - MixDirection direction) { - - PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; - if (!constraint.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case MixBlend.Setup: - constraint.spacing = constraint.data.spacing; - return; - case MixBlend.First: - constraint.spacing += (constraint.data.spacing - constraint.spacing) * alpha; - return; - } - return; - } - - float spacing = GetCurveValue(time); - if (blend == MixBlend.Setup) - constraint.spacing = constraint.data.spacing + (spacing - constraint.data.spacing) * alpha; - else - constraint.spacing += (spacing - constraint.spacing) * alpha; - } - } - - /// Changes a transform constraint's , , and - /// . - public class PathConstraintMixTimeline : CurveTimeline { - public const int ENTRIES = 4; - private const int ROTATE = 1, X = 2, Y = 3; - - readonly int pathConstraintIndex; - - public PathConstraintMixTimeline (int frameCount, int bezierCount, int pathConstraintIndex) - : base(frameCount, bezierCount, (int)Property.PathConstraintMix + "|" + pathConstraintIndex) { - this.pathConstraintIndex = pathConstraintIndex; - } - - public override int FrameEntries { - get { return ENTRIES; } - } - - /// The index of the path constraint slot in that will be changed when this timeline - /// is applied. - public int PathConstraintIndex { - get { - return pathConstraintIndex; - } - } - - /// Sets the time and color for the specified frame. - /// Between 0 and frameCount, inclusive. - /// The frame time in seconds. - public void SetFrame (int frame, float time, float mixRotate, float mixX, float mixY) { - frame <<= 2; - frames[frame] = time; - frames[frame + ROTATE] = mixRotate; - frames[frame + X] = mixX; - frames[frame + Y] = mixY; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; - if (!constraint.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - 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; - return; - } - return; - } - - float rotate, x, y; - int i = Search(frames, time, ENTRIES), 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); - break; - } - - 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; - } - } - } +namespace Spine4_0_64 +{ + + /// + /// Stores a list of timelines to animate a skeleton's pose over time. + public class Animation + { + internal String name; + internal ExposedList timelines; + internal HashSet timelineIds; + internal float duration; + + public Animation(string name, ExposedList timelines, float duration) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + + this.name = name; + SetTimelines(timelines); + this.duration = duration; + } + + public ExposedList Timelines + { + get { return timelines; } + set { SetTimelines(value); } + } + + public void SetTimelines(ExposedList timelines) + { + if (timelines == null) throw new ArgumentNullException("timelines", "timelines cannot be null."); + this.timelines = timelines; + // Note: avoiding reallocations by adding all hash set entries at + // once (EnsureCapacity() is only available in newer .Net versions). + int idCount = 0; + int timelinesCount = timelines.Count; + Timeline[] timelinesItems = timelines.Items; + for (int t = 0; t < timelinesCount; ++t) + idCount += timelinesItems[t].PropertyIds.Length; + string[] propertyIds = new string[idCount]; + int currentId = 0; + for (int t = 0; t < timelinesCount; ++t) + { + var ids = timelinesItems[t].PropertyIds; + for (int i = 0, idsLength = ids.Length; i < idsLength; ++i) + propertyIds[currentId++] = ids[i]; + } + this.timelineIds = new HashSet(propertyIds); + } + + /// The duration of the animation in seconds, which is usually the highest time of all frames in the timeline. The duration is + /// used to know when it has completed and when it should loop back to the start. + public float Duration { get { return duration; } set { duration = value; } } + + /// The animation's name, which is unique across all animations in the skeleton. + public string Name { get { return name; } } + + /// Returns true if this animation contains a timeline with any of the specified property IDs. + public bool HasTimeline(string[] propertyIds) + { + foreach (string id in propertyIds) + if (timelineIds.Contains(id)) return true; + return false; + } + + /// Applies the animation's timelines to the specified skeleton. + /// + /// The skeleton the animation is being applied to. This provides access to the bones, slots, and other skeleton + /// components the timelines may change. + /// The last time in seconds this animation was applied. Some timelines trigger only at specific times rather + /// than every frame. Pass -1 the first time an animation is applied to ensure frame 0 is triggered. + /// The time in seconds the skeleton is being posed for. Most timelines find the frame before and the frame after + /// this time and interpolate between the frame values. If beyond the and loop is + /// true then the animation will repeat, else the last frame will be applied. + /// If true, the animation repeats after the . + /// If any events are fired, they are added to this list. Can be null to ignore fired events or if no timelines + /// fire events. + /// 0 applies the current or setup values (depending on blend). 1 applies the timeline values. Between + /// 0 and 1 applies values between the current or setup values and the timeline values. 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 (layering). + /// Controls how mixing is applied when alpha < 1. + /// Indicates whether the timelines are mixing in or out. Used by timelines which perform instant transitions, + /// such as or . + public void Apply(Skeleton skeleton, float lastTime, float time, bool loop, ExposedList events, float alpha, + MixBlend blend, MixDirection direction) + { + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + + if (loop && duration != 0) + { + time %= duration; + if (lastTime > 0) lastTime %= duration; + } + + var timelines = this.timelines.Items; + for (int i = 0, n = this.timelines.Count; i < n; i++) + timelines[i].Apply(skeleton, lastTime, time, events, alpha, blend, direction); + } + + override public string ToString() + { + return name; + } + } + + /// + /// Controls how timeline values are mixed with setup pose values or current pose values when a timeline is applied with + /// alpha < 1. + /// + public enum MixBlend + { + /// Transitions from the setup value to the timeline value (the current value is not used). Before the first frame, the + /// setup value is set. + Setup, + + /// + /// + /// Transitions from the current value to the timeline value. Before the first frame, transitions from the current value to + /// the setup value. Timelines which perform instant transitions, such as or + /// , use the setup value before the first frame. + /// + /// First is intended for the first animations applied, not for animations layered on top of those. + /// + First, + + /// + /// + /// Transitions from the current value to the timeline value. No change is made before the first frame (the current value is + /// kept until the first frame). + /// + /// Replace is intended for animations layered on top of others, not for the first animations applied. + /// + Replace, + + /// + /// + /// Transitions from the current value to the current value plus the timeline value. No change is made before the first frame + /// (the current value is kept until the first frame). + /// + /// Add is intended for animations layered on top of others, not for the first animations applied. Properties + /// set by additive animations must be set manually or by another animation before applying the additive animations, else the + /// property values will increase each time the additive animations are applied. + /// + /// + Add + } + + /// + /// Indicates whether a timeline's alpha is mixing out over time toward 0 (the setup or current pose value) or + /// mixing in toward 1 (the timeline's value). Some timelines use this to decide how values are applied. + /// + public enum MixDirection + { + In, + Out + } + + internal enum Property + { + Rotate = 0, X, Y, ScaleX, ScaleY, ShearX, ShearY, // + RGB, Alpha, RGB2, // + Attachment, Deform, // + Event, DrawOrder, // + IkConstraint, TransformConstraint, // + PathConstraintPosition, PathConstraintSpacing, PathConstraintMix + } + + /// + /// The base class for all timelines. + public abstract class Timeline + { + private readonly string[] propertyIds; + internal readonly float[] frames; + + /// Unique identifiers for the properties the timeline modifies. + public Timeline(int frameCount, params string[] propertyIds) + { + if (propertyIds == null) throw new System.ArgumentNullException("propertyIds", "propertyIds cannot be null."); + this.propertyIds = propertyIds; + frames = new float[frameCount * FrameEntries]; + } + + /// Uniquely encodes both the type of this timeline and the skeleton properties that it affects. + public string[] PropertyIds + { + get { return propertyIds; } + } + + /// The time in seconds and any other values for each frame. + public float[] Frames + { + get { return frames; } + } + + /// The number of entries stored per frame. + public virtual int FrameEntries + { + get { return 1; } + } + + /// The number of frames for this timeline. + public int FrameCount + { + get { return frames.Length / FrameEntries; } + } + + public float Duration + { + get + { + return frames[frames.Length - FrameEntries]; + } + } + + /// Applies this timeline to the skeleton. + /// The skeleton the timeline is being applied to. This provides access to the bones, slots, and other + /// skeleton components the timeline may change. + /// The time this timeline was last applied. Timelines such as trigger only + /// at specific times rather than every frame. In that case, the timeline triggers everything between + /// lastTime (exclusive) and time (inclusive). Pass -1 the first time an animation is + /// applied to ensure frame 0 is triggered. + /// The time in seconds that the skeleton is being posed for. Most timelines find the frame before and the frame + /// after this time and interpolate between the frame values.If beyond the last frame, the last frame will be + /// applied. + /// If any events are fired, they are added to this list. Can be null to ignore fired events or if the timeline + /// does not fire events. + /// 0 applies the current or setup value (depending on blend). 1 applies the timeline value. + /// Between 0 and 1 applies a value between the current or setup value 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 (layering). + /// Controls how mixing is applied when alpha < 1. + /// Indicates whether the timeline is mixing in or out. Used by timelines which perform instant transitions, + /// such as or , and other such as . + public abstract void Apply(Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, + MixBlend blend, MixDirection direction); + + /// Search using a stride of 1. + /// Must be >= the first value in frames. + /// The index of the first value <= time. + internal static int Search(float[] frames, float time) + { + int n = frames.Length; + for (int i = 1; i < n; i++) + if (frames[i] > time) return i - 1; + return n - 1; + } + + /// Search using the specified stride. + /// Must be >= the first value in frames. + /// The index of the first value <= time. + internal static int Search(float[] frames, float time, int step) + { + int n = frames.Length; + for (int i = step; i < n; i += step) + if (frames[i] > time) return i - step; + return n - step; + } + } + + /// An interface for timelines which change the property of a bone. + public interface IBoneTimeline + { + /// The index of the bone in that will be changed when this timeline is applied. + int BoneIndex { get; } + } + + /// An interface for timelines which change the property of a slot. + public interface ISlotTimeline + { + /// The index of the slot in that will be changed when this timeline is applied. + int SlotIndex { get; } + } + + /// The base class for timelines that interpolate between frame values using stepped, linear, or a Bezier curve. + public abstract class CurveTimeline : Timeline + { + public const int LINEAR = 0, STEPPED = 1, BEZIER = 2, BEZIER_SIZE = 18; + + internal float[] curves; + /// The number of key frames for this timeline. + + /// The maximum number of Bezier curves. See . + /// Unique identifiers for the properties the timeline modifies. + public CurveTimeline(int frameCount, int bezierCount, params string[] propertyIds) + : base(frameCount, propertyIds) + { + curves = new float[frameCount + bezierCount * BEZIER_SIZE]; + curves[frameCount - 1] = STEPPED; + } + + /// Sets the specified frame to linear interpolation. + /// Between 0 and frameCount - 1, inclusive. + public void SetLinear(int frame) + { + curves[frame] = LINEAR; + } + + /// Sets the specified frame to stepped interpolation. + /// Between 0 and frameCount - 1, inclusive. + public void SetStepped(int frame) + { + curves[frame] = STEPPED; + } + + /// Returns the interpolation type for the specified frame. + /// Between 0 and frameCount - 1, inclusive. + /// , or + the index of the Bezier segments. + public float GetCurveType(int frame) + { + return (int)curves[frame]; + } + + /// Shrinks the storage for Bezier curves, for use when bezierCount (specified in the constructor) was larger + /// than the actual number of Bezier curves. + public void Shrink(int bezierCount) + { + int size = FrameCount + bezierCount * BEZIER_SIZE; + if (curves.Length > size) + { + float[] newCurves = new float[size]; + Array.Copy(curves, 0, newCurves, 0, size); + curves = newCurves; + } + } + + /// + /// Stores the segments for the specified Bezier curve. For timelines that modify multiple values, there may be more than + /// one curve per frame. + /// The ordinal of this Bezier curve for this timeline, between 0 and bezierCount - 1 (specified + /// in the constructor), inclusive. + /// Between 0 and frameCount - 1, inclusive. + /// The index of the value for the frame this curve is used for. + /// The time for the first key. + /// The value for the first key. + /// The time for the first Bezier handle. + /// The value for the first Bezier handle. + /// The time of the second Bezier handle. + /// The value for the second Bezier handle. + /// The time for the second key. + /// The value for the second key. + public void SetBezier(int bezier, int frame, int value, float time1, float value1, float cx1, float cy1, float cx2, + float cy2, float time2, float value2) + { + + float[] curves = this.curves; + int i = FrameCount + bezier * BEZIER_SIZE; + if (value == 0) curves[frame] = BEZIER + i; + float tmpx = (time1 - cx1 * 2 + cx2) * 0.03f, tmpy = (value1 - cy1 * 2 + cy2) * 0.03f; + float dddx = ((cx1 - cx2) * 3 - time1 + time2) * 0.006f, dddy = ((cy1 - cy2) * 3 - value1 + value2) * 0.006f; + float ddx = tmpx * 2 + dddx, ddy = tmpy * 2 + dddy; + float dx = (cx1 - time1) * 0.3f + tmpx + dddx * 0.16666667f, dy = (cy1 - value1) * 0.3f + tmpy + dddy * 0.16666667f; + float x = time1 + dx, y = value1 + dy; + for (int 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; + } + } + + /// + /// Returns the Bezier interpolated value for the specified time. + /// The index into for the values of the frame before time. + /// The offset from frameIndex to the value this curve is used for. + /// The index of the Bezier segments. See . + public float GetBezierValue(float time, int frameIndex, int valueOffset, int i) + { + float[] curves = this.curves; + if (curves[i] > time) + { + float x = frames[frameIndex], y = frames[frameIndex + valueOffset]; + return y + (time - x) / (curves[i] - x) * (curves[i + 1] - y); + } + int 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 += FrameEntries; + { // scope added to prevent compile error "float x and y declared in enclosing scope" + float x = curves[n - 2], y = curves[n - 1]; + return y + (time - x) / (frames[frameIndex] - x) * (frames[frameIndex + valueOffset] - y); + } + } + } + + /// The base class for a that sets one property. + public abstract class CurveTimeline1 : CurveTimeline + { + public const int ENTRIES = 2; + internal const int VALUE = 1; + + /// The maximum number of Bezier curves. See . + /// Unique identifiers for the properties the timeline modifies. + public CurveTimeline1(int frameCount, int bezierCount, string propertyId) + : base(frameCount, bezierCount, propertyId) + { + } + + public override int FrameEntries + { + get { return ENTRIES; } + } + + /// Sets the time and value for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds + public void SetFrame(int frame, float time, float value) + { + frame <<= 1; + frames[frame] = time; + frames[frame + VALUE] = value; + } + + /// Returns the interpolated value for the specified time. + public float GetCurveValue(float time) + { + float[] frames = this.frames; + int i = frames.Length - 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 LINEAR: + float before = frames[i], value = frames[i + VALUE]; + return value + (time - before) / (frames[i + ENTRIES] - before) * (frames[i + ENTRIES + VALUE] - value); + case STEPPED: + return frames[i + VALUE]; + } + return GetBezierValue(time, i, VALUE, curveType - BEZIER); + } + } + + /// The base class for a which sets two properties. + public abstract class CurveTimeline2 : CurveTimeline + { + public const int ENTRIES = 3; + internal const int VALUE1 = 1, VALUE2 = 2; + + /// The maximum number of Bezier curves. See . + /// Unique identifiers for the properties the timeline modifies. + public CurveTimeline2(int frameCount, int bezierCount, string propertyId1, string propertyId2) + : base(frameCount, bezierCount, propertyId1, propertyId2) + { + } + + public override int FrameEntries + { + get { return ENTRIES; } + } + + /// Sets the time and values for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds. + public void SetFrame(int frame, float time, float value1, float value2) + { + frame *= ENTRIES; + frames[frame] = time; + frames[frame + VALUE1] = value1; + frames[frame + VALUE2] = value2; + } + } + + /// Changes a bone's local . + public class RotateTimeline : CurveTimeline1, IBoneTimeline + { + readonly int boneIndex; + + public RotateTimeline(int frameCount, int bezierCount, int boneIndex) + : base(frameCount, bezierCount, (int)Property.Rotate + "|" + boneIndex) + { + this.boneIndex = boneIndex; + } + + public int BoneIndex + { + get + { + return boneIndex; + } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + if (!bone.active) return; + + if (time < frames[0]) + { // Time is before first frame. + switch (blend) + { + case MixBlend.Setup: + bone.rotation = bone.data.rotation; + return; + case MixBlend.First: + bone.rotation += (bone.data.rotation - bone.rotation) * alpha; + return; + } + return; + } + + float r = GetCurveValue(time); + switch (blend) + { + case MixBlend.Setup: + bone.rotation = bone.data.rotation + r * alpha; + break; + case MixBlend.First: + case MixBlend.Replace: + r += bone.data.rotation - bone.rotation; + goto case MixBlend.Add; // Fall through. + case MixBlend.Add: + bone.rotation += r * alpha; + break; + } + } + } + + /// Changes a bone's local and . + public class TranslateTimeline : CurveTimeline2, IBoneTimeline + { + readonly int boneIndex; + + public TranslateTimeline(int frameCount, int bezierCount, int boneIndex) + : base(frameCount, bezierCount, // + (int)Property.X + "|" + boneIndex, // + (int)Property.Y + "|" + boneIndex) + { + this.boneIndex = boneIndex; + } + + public int BoneIndex + { + get + { + return boneIndex; + } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + if (!bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + 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; + return; + } + return; + } + + float x, y; + GetCurveValue(out x, out y, time); // note: reference implementation has code inlined + + 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; + break; + } + } + + public void GetCurveValue(out float x, out float y, float time) + { + int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; + switch (curveType) + { + case LINEAR: + float before = frames[i]; + x = frames[i + VALUE1]; + y = frames[i + VALUE2]; + float t = (time - before) / (frames[i + ENTRIES] - before); + x += (frames[i + ENTRIES + VALUE1] - x) * t; + y += (frames[i + ENTRIES + VALUE2] - y) * t; + break; + case STEPPED: + x = frames[i + VALUE1]; + y = frames[i + VALUE2]; + break; + default: + x = GetBezierValue(time, i, VALUE1, curveType - BEZIER); + y = GetBezierValue(time, i, VALUE2, curveType + BEZIER_SIZE - BEZIER); + break; + } + } + } + + /// Changes a bone's local . + public class TranslateXTimeline : CurveTimeline1, IBoneTimeline + { + readonly int boneIndex; + + public TranslateXTimeline(int frameCount, int bezierCount, int boneIndex) + : base(frameCount, bezierCount, (int)Property.X + "|" + boneIndex) + { + this.boneIndex = boneIndex; + } + + public int BoneIndex + { + get + { + return boneIndex; + } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + if (!bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + switch (blend) + { + case MixBlend.Setup: + bone.x = bone.data.x; + return; + case MixBlend.First: + bone.x += (bone.data.x - bone.x) * alpha; + return; + } + return; + } + + float x = GetCurveValue(time); + switch (blend) + { + case MixBlend.Setup: + bone.x = bone.data.x + x * alpha; + break; + case MixBlend.First: + case MixBlend.Replace: + bone.x += (bone.data.x + x - bone.x) * alpha; + break; + case MixBlend.Add: + bone.x += x * alpha; + break; + } + } + } + + /// Changes a bone's local . + public class TranslateYTimeline : CurveTimeline1, IBoneTimeline + { + readonly int boneIndex; + + public TranslateYTimeline(int frameCount, int bezierCount, int boneIndex) + : base(frameCount, bezierCount, (int)Property.Y + "|" + boneIndex) + { + this.boneIndex = boneIndex; + } + + public int BoneIndex + { + get + { + return boneIndex; + } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + if (!bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + switch (blend) + { + case MixBlend.Setup: + bone.y = bone.data.y; + return; + case MixBlend.First: + bone.y += (bone.data.y - bone.y) * alpha; + return; + } + return; + } + + float y = GetCurveValue(time); + switch (blend) + { + case MixBlend.Setup: + bone.y = bone.data.y + y * alpha; + break; + case MixBlend.First: + case MixBlend.Replace: + bone.y += (bone.data.y + y - bone.y) * alpha; + break; + case MixBlend.Add: + bone.y += y * alpha; + break; + } + } + } + + /// Changes a bone's local and . + public class ScaleTimeline : CurveTimeline2, IBoneTimeline + { + readonly int boneIndex; + + public ScaleTimeline(int frameCount, int bezierCount, int boneIndex) + : base(frameCount, bezierCount, // + (int)Property.ScaleX + "|" + boneIndex, // + (int)Property.ScaleY + "|" + boneIndex) + { + this.boneIndex = boneIndex; + } + + public int BoneIndex + { + get + { + return boneIndex; + } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + if (!bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + 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; + return; + } + return; + } + + float x, y; + int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; + switch (curveType) + { + case LINEAR: + float before = frames[i]; + x = frames[i + VALUE1]; + y = frames[i + VALUE2]; + float t = (time - before) / (frames[i + ENTRIES] - before); + x += (frames[i + ENTRIES + VALUE1] - x) * t; + y += (frames[i + ENTRIES + VALUE2] - y) * t; + break; + case STEPPED: + x = frames[i + VALUE1]; + y = frames[i + VALUE2]; + break; + default: + x = GetBezierValue(time, i, VALUE1, curveType - BEZIER); + y = GetBezierValue(time, i, VALUE2, curveType + BEZIER_SIZE - BEZIER); + break; + } + 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 + { + // Mixing out uses sign of setup or current pose, else use sign of key. + float bx, by; + if (direction == MixDirection.Out) + { + switch (blend) + { + case MixBlend.Setup: + bx = bone.data.scaleX; + by = bone.data.scaleY; + bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha; + bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha; + break; + case MixBlend.First: + case MixBlend.Replace: + bx = bone.scaleX; + by = bone.scaleY; + bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha; + bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha; + break; + case MixBlend.Add: + bone.scaleX += (x - bone.data.scaleX) * alpha; + bone.scaleY += (y - bone.data.scaleY) * alpha; + break; + } + } + else + { + switch (blend) + { + case MixBlend.Setup: + bx = Math.Abs(bone.data.scaleX) * Math.Sign(x); + by = Math.Abs(bone.data.scaleY) * Math.Sign(y); + bone.scaleX = bx + (x - bx) * alpha; + bone.scaleY = by + (y - by) * alpha; + break; + case MixBlend.First: + case MixBlend.Replace: + bx = Math.Abs(bone.scaleX) * Math.Sign(x); + by = Math.Abs(bone.scaleY) * Math.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; + break; + } + } + } + } + } + + /// Changes a bone's local . + public class ScaleXTimeline : CurveTimeline1, IBoneTimeline + { + readonly int boneIndex; + + public ScaleXTimeline(int frameCount, int bezierCount, int boneIndex) + : base(frameCount, bezierCount, (int)Property.ScaleX + "|" + boneIndex) + { + this.boneIndex = boneIndex; + } + + public int BoneIndex + { + get + { + return boneIndex; + } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + if (!bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + switch (blend) + { + case MixBlend.Setup: + bone.scaleX = bone.data.scaleX; + return; + case MixBlend.First: + bone.scaleX += (bone.data.scaleX - bone.scaleX) * alpha; + return; + } + return; + } + + float x = GetCurveValue(time) * bone.data.scaleX; + if (alpha == 1) + { + if (blend == MixBlend.Add) + bone.scaleX += x - bone.data.scaleX; + else + bone.scaleX = x; + } + else + { + // Mixing out uses sign of setup or current pose, else use sign of key. + float bx; + if (direction == MixDirection.Out) + { + switch (blend) + { + case MixBlend.Setup: + bx = bone.data.scaleX; + bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha; + break; + case MixBlend.First: + case MixBlend.Replace: + bx = bone.scaleX; + bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha; + break; + case MixBlend.Add: + bone.scaleX += (x - bone.data.scaleX) * alpha; + break; + } + } + else + { + switch (blend) + { + case MixBlend.Setup: + bx = Math.Abs(bone.data.scaleX) * Math.Sign(x); + bone.scaleX = bx + (x - bx) * alpha; + break; + case MixBlend.First: + case MixBlend.Replace: + bx = Math.Abs(bone.scaleX) * Math.Sign(x); + bone.scaleX = bx + (x - bx) * alpha; + break; + case MixBlend.Add: + bone.scaleX += (x - bone.data.scaleX) * alpha; + break; + } + } + } + } + } + + /// Changes a bone's local . + public class ScaleYTimeline : CurveTimeline1, IBoneTimeline + { + readonly int boneIndex; + + public ScaleYTimeline(int frameCount, int bezierCount, int boneIndex) + : base(frameCount, bezierCount, (int)Property.ScaleY + "|" + boneIndex) + { + this.boneIndex = boneIndex; + } + + public int BoneIndex + { + get + { + return boneIndex; + } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + if (!bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + switch (blend) + { + case MixBlend.Setup: + bone.scaleY = bone.data.scaleY; + return; + case MixBlend.First: + bone.scaleY += (bone.data.scaleY - bone.scaleY) * alpha; + return; + } + return; + } + + float y = GetCurveValue(time) * bone.data.scaleY; + if (alpha == 1) + { + if (blend == MixBlend.Add) + bone.scaleY += y - bone.data.scaleY; + else + bone.scaleY = y; + } + else + { + // Mixing out uses sign of setup or current pose, else use sign of key. + float by; + if (direction == MixDirection.Out) + { + switch (blend) + { + case MixBlend.Setup: + by = bone.data.scaleY; + bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha; + break; + case MixBlend.First: + case MixBlend.Replace: + by = bone.scaleY; + bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha; + break; + case MixBlend.Add: + bone.scaleY += (y - bone.data.scaleY) * alpha; + break; + } + } + else + { + switch (blend) + { + case MixBlend.Setup: + by = Math.Abs(bone.data.scaleY) * Math.Sign(y); + bone.scaleY = by + (y - by) * alpha; + break; + case MixBlend.First: + case MixBlend.Replace: + by = Math.Abs(bone.scaleY) * Math.Sign(y); + bone.scaleY = by + (y - by) * alpha; + break; + case MixBlend.Add: + bone.scaleY += (y - bone.data.scaleY) * alpha; + break; + } + } + } + } + } + + /// Changes a bone's local and . + public class ShearTimeline : CurveTimeline2, IBoneTimeline + { + readonly int boneIndex; + + public ShearTimeline(int frameCount, int bezierCount, int boneIndex) + : base(frameCount, bezierCount, // + (int)Property.ShearX + "|" + boneIndex, // + (int)Property.ShearY + "|" + boneIndex) + { + this.boneIndex = boneIndex; + } + + public int BoneIndex + { + get + { + return boneIndex; + } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + if (!bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + 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; + return; + } + return; + } + + float x, y; + int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; + switch (curveType) + { + case LINEAR: + float before = frames[i]; + x = frames[i + VALUE1]; + y = frames[i + VALUE2]; + float t = (time - before) / (frames[i + ENTRIES] - before); + x += (frames[i + ENTRIES + VALUE1] - x) * t; + y += (frames[i + ENTRIES + VALUE2] - y) * t; + break; + case STEPPED: + x = frames[i + VALUE1]; + y = frames[i + VALUE2]; + break; + default: + x = GetBezierValue(time, i, VALUE1, curveType - BEZIER); + y = GetBezierValue(time, i, VALUE2, curveType + BEZIER_SIZE - BEZIER); + break; + } + + 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; + break; + } + } + } + + /// Changes a bone's local . + public class ShearXTimeline : CurveTimeline1, IBoneTimeline + { + readonly int boneIndex; + + public ShearXTimeline(int frameCount, int bezierCount, int boneIndex) + : base(frameCount, bezierCount, (int)Property.ShearX + "|" + boneIndex) + { + this.boneIndex = boneIndex; + } + + public int BoneIndex + { + get + { + return boneIndex; + } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + if (!bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + switch (blend) + { + case MixBlend.Setup: + bone.shearX = bone.data.shearX; + return; + case MixBlend.First: + bone.shearX += (bone.data.shearX - bone.shearX) * alpha; + return; + } + return; + } + + float x = GetCurveValue(time); + switch (blend) + { + case MixBlend.Setup: + bone.shearX = bone.data.shearX + x * alpha; + break; + case MixBlend.First: + case MixBlend.Replace: + bone.shearX += (bone.data.shearX + x - bone.shearX) * alpha; + break; + case MixBlend.Add: + bone.shearX += x * alpha; + break; + } + } + } + + /// Changes a bone's local . + public class ShearYTimeline : CurveTimeline1, IBoneTimeline + { + readonly int boneIndex; + + public ShearYTimeline(int frameCount, int bezierCount, int boneIndex) + : base(frameCount, bezierCount, (int)Property.ShearY + "|" + boneIndex) + { + this.boneIndex = boneIndex; + } + + public int BoneIndex + { + get + { + return boneIndex; + } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + if (!bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + switch (blend) + { + case MixBlend.Setup: + bone.shearY = bone.data.shearY; + return; + case MixBlend.First: + bone.shearY += (bone.data.shearY - bone.shearY) * alpha; + return; + } + return; + } + + float y = GetCurveValue(time); + switch (blend) + { + case MixBlend.Setup: + bone.shearY = bone.data.shearY + y * alpha; + break; + case MixBlend.First: + case MixBlend.Replace: + bone.shearY += (bone.data.shearY + y - bone.shearY) * alpha; + break; + case MixBlend.Add: + bone.shearY += y * alpha; + break; + } + } + } + + /// Changes a slot's . + public class RGBATimeline : CurveTimeline, ISlotTimeline + { + public const int ENTRIES = 5; + protected const int R = 1, G = 2, B = 3, A = 4; + + readonly int slotIndex; + + public RGBATimeline(int frameCount, int bezierCount, int slotIndex) + : base(frameCount, bezierCount, // + (int)Property.RGB + "|" + slotIndex, // + (int)Property.Alpha + "|" + slotIndex) + { + this.slotIndex = slotIndex; + } + public override int FrameEntries + { + get { return ENTRIES; } + } + + public int SlotIndex + { + get + { + return slotIndex; + } + } + + /// Sets the time and color for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds. + public void 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; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Slot slot = skeleton.slots.Items[slotIndex]; + if (!slot.bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + var setup = slot.data; + switch (blend) + { + case MixBlend.Setup: + slot.r = setup.r; + slot.g = setup.g; + slot.b = setup.b; + slot.a = setup.a; + return; + case MixBlend.First: + slot.r += (setup.r - slot.r) * alpha; + slot.g += (setup.g - slot.g) * alpha; + slot.b += (setup.b - slot.b) * alpha; + slot.a += (setup.a - slot.a) * alpha; + slot.ClampColor(); + return; + } + return; + } + + float r, g, b, a; + int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; + switch (curveType) + { + case LINEAR: + float before = frames[i]; + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + a = frames[i + A]; + float t = (time - before) / (frames[i + ENTRIES] - before); + r += (frames[i + ENTRIES + R] - r) * t; + g += (frames[i + ENTRIES + G] - g) * t; + b += (frames[i + ENTRIES + B] - b) * t; + a += (frames[i + ENTRIES + A] - a) * t; + break; + case STEPPED: + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + a = frames[i + A]; + break; + default: + r = GetBezierValue(time, i, R, curveType - BEZIER); + g = GetBezierValue(time, i, G, curveType + BEZIER_SIZE - BEZIER); + b = GetBezierValue(time, i, B, curveType + BEZIER_SIZE * 2 - BEZIER); + a = GetBezierValue(time, i, A, curveType + BEZIER_SIZE * 3 - BEZIER); + break; + } + + if (alpha == 1) + { + slot.r = r; + slot.g = g; + slot.b = b; + slot.a = a; + } + else + { + float br, bg, bb, ba; + if (blend == MixBlend.Setup) + { + br = slot.data.r; + bg = slot.data.g; + bb = slot.data.b; + ba = slot.data.a; + } + else + { + br = slot.r; + bg = slot.g; + bb = slot.b; + ba = slot.a; + } + slot.r = br + (r - br) * alpha; + slot.g = bg + (g - bg) * alpha; + slot.b = bb + (b - bb) * alpha; + slot.a = ba + (a - ba) * alpha; + } + slot.ClampColor(); + } + } + + /// Changes the RGB for a slot's . + public class RGBTimeline : CurveTimeline, ISlotTimeline + { + public const int ENTRIES = 4; + protected const int R = 1, G = 2, B = 3; + + readonly int slotIndex; + + public RGBTimeline(int frameCount, int bezierCount, int slotIndex) + : base(frameCount, bezierCount, // + (int)Property.RGB + "|" + slotIndex) + { + this.slotIndex = slotIndex; + } + + public override int FrameEntries + { + get { return ENTRIES; } + } + + public int SlotIndex + { + get + { + return slotIndex; + } + } + + /// Sets the time and color for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds. + public void SetFrame(int frame, float time, float r, float g, float b) + { + frame <<= 2; + frames[frame] = time; + frames[frame + R] = r; + frames[frame + G] = g; + frames[frame + B] = b; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Slot slot = skeleton.slots.Items[slotIndex]; + if (!slot.bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + var setup = slot.data; + switch (blend) + { + case MixBlend.Setup: + slot.r = setup.r; + slot.g = setup.g; + slot.b = setup.b; + return; + case MixBlend.First: + slot.r += (setup.r - slot.r) * alpha; + slot.g += (setup.g - slot.g) * alpha; + slot.b += (setup.b - slot.b) * alpha; + slot.ClampColor(); + return; + } + return; + } + + float r, g, b; + int i = Search(frames, time, ENTRIES), curveType = (int)curves[i >> 2]; + switch (curveType) + { + case LINEAR: + float before = frames[i]; + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + float t = (time - before) / (frames[i + ENTRIES] - before); + r += (frames[i + ENTRIES + R] - r) * t; + g += (frames[i + ENTRIES + G] - g) * t; + b += (frames[i + ENTRIES + B] - b) * t; + break; + case STEPPED: + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + break; + default: + r = GetBezierValue(time, i, R, curveType - BEZIER); + g = GetBezierValue(time, i, G, curveType + BEZIER_SIZE - BEZIER); + b = GetBezierValue(time, i, B, curveType + BEZIER_SIZE * 2 - BEZIER); + break; + } + + if (alpha == 1) + { + slot.r = r; + slot.g = g; + slot.b = b; + } + else + { + float br, bg, bb; + if (blend == MixBlend.Setup) + { + var setup = slot.data; + br = setup.r; + bg = setup.g; + bb = setup.b; + } + else + { + br = slot.r; + bg = slot.g; + bb = slot.b; + } + slot.r = br + (r - br) * alpha; + slot.g = bg + (g - bg) * alpha; + slot.b = bb + (b - bb) * alpha; + } + slot.ClampColor(); + } + } + + /// Changes the alpha for a slot's . + public class AlphaTimeline : CurveTimeline1, ISlotTimeline + { + readonly int slotIndex; + + public AlphaTimeline(int frameCount, int bezierCount, int slotIndex) + : base(frameCount, bezierCount, (int)Property.Alpha + "|" + slotIndex) + { + this.slotIndex = slotIndex; + } + + public int SlotIndex + { + get + { + return slotIndex; + } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Slot slot = skeleton.slots.Items[slotIndex]; + if (!slot.bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + var setup = slot.data; + switch (blend) + { + case MixBlend.Setup: + slot.a = setup.a; + return; + case MixBlend.First: + slot.a += (setup.a - slot.a) * alpha; + slot.ClampColor(); + return; + } + return; + } + + float a = GetCurveValue(time); + if (alpha == 1) + slot.a = a; + else + { + if (blend == MixBlend.Setup) slot.a = slot.data.a; + slot.a += (a - slot.a) * alpha; + } + slot.ClampColor(); + } + } + + /// Changes a slot's and for two color tinting. + public class RGBA2Timeline : CurveTimeline, ISlotTimeline + { + public const int ENTRIES = 8; + protected const int R = 1, G = 2, B = 3, A = 4, R2 = 5, G2 = 6, B2 = 7; + + readonly int slotIndex; + + public RGBA2Timeline(int frameCount, int bezierCount, int slotIndex) + : base(frameCount, bezierCount, // + (int)Property.RGB + "|" + slotIndex, // + (int)Property.Alpha + "|" + slotIndex, // + (int)Property.RGB2 + "|" + slotIndex) + { + this.slotIndex = slotIndex; + } + + public override int FrameEntries + { + get + { + return ENTRIES; + } + } + + /// + /// The index of the slot in that will be changed when this timeline is applied. The + /// must have a dark color available. + public int SlotIndex + { + get + { + return slotIndex; + } + } + + /// Sets the time, light color, and dark color for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds. + public void SetFrame(int frame, float time, float r, float g, float b, float a, float r2, float g2, float b2) + { + frame <<= 3; + 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; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Slot slot = skeleton.slots.Items[slotIndex]; + if (!slot.bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + SlotData setup = slot.data; + switch (blend) + { + case MixBlend.Setup: + slot.r = setup.r; + slot.g = setup.g; + slot.b = setup.b; + slot.a = setup.a; + slot.ClampColor(); + slot.r2 = setup.r2; + slot.g2 = setup.g2; + slot.b2 = setup.b2; + slot.ClampSecondColor(); + return; + case MixBlend.First: + slot.r += (slot.r - setup.r) * alpha; + slot.g += (slot.g - setup.g) * alpha; + slot.b += (slot.b - setup.b) * alpha; + slot.a += (slot.a - setup.a) * alpha; + slot.ClampColor(); + slot.r2 += (slot.r2 - setup.r2) * alpha; + slot.g2 += (slot.g2 - setup.g2) * alpha; + slot.b2 += (slot.b2 - setup.b2) * alpha; + slot.ClampSecondColor(); + return; + } + return; + } + + float r, g, b, a, r2, g2, b2; + int i = Search(frames, time, ENTRIES), curveType = (int)curves[i >> 3]; + switch (curveType) + { + case LINEAR: + float before = frames[i]; + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + a = frames[i + A]; + r2 = frames[i + R2]; + g2 = frames[i + G2]; + b2 = frames[i + B2]; + float t = (time - before) / (frames[i + ENTRIES] - before); + r += (frames[i + ENTRIES + R] - r) * t; + g += (frames[i + ENTRIES + G] - g) * t; + b += (frames[i + ENTRIES + B] - b) * t; + a += (frames[i + ENTRIES + A] - a) * t; + r2 += (frames[i + ENTRIES + R2] - r2) * t; + g2 += (frames[i + ENTRIES + G2] - g2) * t; + b2 += (frames[i + ENTRIES + B2] - b2) * t; + break; + case STEPPED: + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + a = frames[i + A]; + r2 = frames[i + R2]; + g2 = frames[i + G2]; + b2 = frames[i + B2]; + break; + default: + r = GetBezierValue(time, i, R, curveType - BEZIER); + g = GetBezierValue(time, i, G, curveType + BEZIER_SIZE - BEZIER); + b = GetBezierValue(time, i, B, curveType + BEZIER_SIZE * 2 - BEZIER); + a = GetBezierValue(time, i, A, curveType + BEZIER_SIZE * 3 - BEZIER); + r2 = GetBezierValue(time, i, R2, curveType + BEZIER_SIZE * 4 - BEZIER); + g2 = GetBezierValue(time, i, G2, curveType + BEZIER_SIZE * 5 - BEZIER); + b2 = GetBezierValue(time, i, B2, curveType + BEZIER_SIZE * 6 - BEZIER); + break; + } + + if (alpha == 1) + { + slot.r = r; + slot.g = g; + slot.b = b; + slot.a = a; + slot.r2 = r2; + slot.g2 = g2; + slot.b2 = b2; + } + else + { + float br, bg, bb, ba, br2, bg2, bb2; + if (blend == MixBlend.Setup) + { + br = slot.data.r; + bg = slot.data.g; + bb = slot.data.b; + ba = slot.data.a; + br2 = slot.data.r2; + bg2 = slot.data.g2; + bb2 = slot.data.b2; + } + else + { + br = slot.r; + bg = slot.g; + bb = slot.b; + ba = slot.a; + br2 = slot.r2; + bg2 = slot.g2; + bb2 = slot.b2; + } + slot.r = br + (r - br) * alpha; + slot.g = bg + (g - bg) * alpha; + slot.b = bb + (b - bb) * alpha; + slot.a = ba + (a - ba) * alpha; + slot.r2 = br2 + (r2 - br2) * alpha; + slot.g2 = bg2 + (g2 - bg2) * alpha; + slot.b2 = bb2 + (b2 - bb2) * alpha; + } + slot.ClampColor(); + slot.ClampSecondColor(); + } + } + + /// Changes the RGB for a slot's and for two color tinting. + public class RGB2Timeline : CurveTimeline, ISlotTimeline + { + public const int ENTRIES = 7; + protected const int R = 1, G = 2, B = 3, R2 = 4, G2 = 5, B2 = 6; + + readonly int slotIndex; + + public RGB2Timeline(int frameCount, int bezierCount, int slotIndex) + : base(frameCount, bezierCount, // + (int)Property.RGB + "|" + slotIndex, // + (int)Property.RGB2 + "|" + slotIndex) + { + this.slotIndex = slotIndex; + } + + public override int FrameEntries + { + get + { + return ENTRIES; + } + } + + /// + /// The index of the slot in that will be changed when this timeline is applied. The + /// must have a dark color available. + public int SlotIndex + { + get + { + return slotIndex; + } + } + + /// Sets the time, light color, and dark color for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds. + public void 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; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Slot slot = skeleton.slots.Items[slotIndex]; + if (!slot.bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + SlotData setup = slot.data; + switch (blend) + { + case MixBlend.Setup: + slot.r = setup.r; + slot.g = setup.g; + slot.b = setup.b; + slot.ClampColor(); + slot.r2 = setup.r2; + slot.g2 = setup.g2; + slot.b2 = setup.b2; + slot.ClampSecondColor(); + return; + case MixBlend.First: + slot.r += (slot.r - setup.r) * alpha; + slot.g += (slot.g - setup.g) * alpha; + slot.b += (slot.b - setup.b) * alpha; + slot.ClampColor(); + slot.r2 += (slot.r2 - setup.r2) * alpha; + slot.g2 += (slot.g2 - setup.g2) * alpha; + slot.b2 += (slot.b2 - setup.b2) * alpha; + slot.ClampSecondColor(); + return; + } + return; + } + + float r, g, b, r2, g2, b2; + int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; + switch (curveType) + { + case LINEAR: + float before = frames[i]; + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + r2 = frames[i + R2]; + g2 = frames[i + G2]; + b2 = frames[i + B2]; + float t = (time - before) / (frames[i + ENTRIES] - before); + r += (frames[i + ENTRIES + R] - r) * t; + g += (frames[i + ENTRIES + G] - g) * t; + b += (frames[i + ENTRIES + B] - b) * t; + r2 += (frames[i + ENTRIES + R2] - r2) * t; + g2 += (frames[i + ENTRIES + G2] - g2) * t; + b2 += (frames[i + ENTRIES + B2] - b2) * t; + break; + case STEPPED: + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + r2 = frames[i + R2]; + g2 = frames[i + G2]; + b2 = frames[i + B2]; + break; + default: + r = GetBezierValue(time, i, R, curveType - BEZIER); + g = GetBezierValue(time, i, G, curveType + BEZIER_SIZE - BEZIER); + b = GetBezierValue(time, i, B, curveType + BEZIER_SIZE * 2 - BEZIER); + r2 = GetBezierValue(time, i, R2, curveType + BEZIER_SIZE * 3 - BEZIER); + g2 = GetBezierValue(time, i, G2, curveType + BEZIER_SIZE * 4 - BEZIER); + b2 = GetBezierValue(time, i, B2, curveType + BEZIER_SIZE * 5 - BEZIER); + break; + } + + if (alpha == 1) + { + slot.r = r; + slot.g = g; + slot.b = b; + slot.r2 = r2; + slot.g2 = g2; + slot.b2 = b2; + } + else + { + float br, bg, bb, br2, bg2, bb2; + if (blend == MixBlend.Setup) + { + SlotData setup = slot.data; + br = setup.r; + bg = setup.g; + bb = setup.b; + br2 = setup.r2; + bg2 = setup.g2; + bb2 = setup.b2; + } + else + { + br = slot.r; + bg = slot.g; + bb = slot.b; + br2 = slot.r2; + bg2 = slot.g2; + bb2 = slot.b2; + } + slot.r = br + (r - br) * alpha; + slot.g = bg + (g - bg) * alpha; + slot.b = bb + (b - bb) * alpha; + slot.r2 = br2 + (r2 - br2) * alpha; + slot.g2 = bg2 + (g2 - bg2) * alpha; + slot.b2 = bb2 + (b2 - bb2) * alpha; + } + slot.ClampColor(); + slot.ClampSecondColor(); + } + } + + /// Changes a slot's . + public class AttachmentTimeline : Timeline, ISlotTimeline + { + readonly int slotIndex; + readonly string[] attachmentNames; + + public AttachmentTimeline(int frameCount, int slotIndex) + : base(frameCount, (int)Property.Attachment + "|" + slotIndex) + { + this.slotIndex = slotIndex; + attachmentNames = new String[frameCount]; + } + + public int SlotIndex + { + get + { + return slotIndex; + } + } + + /// The attachment name for each frame. May contain null values to clear the attachment. + public string[] AttachmentNames + { + get + { + return attachmentNames; + } + } + + /// Sets the time and attachment name for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds. + public void SetFrame(int frame, float time, String attachmentName) + { + frames[frame] = time; + attachmentNames[frame] = attachmentName; + } + + public override void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Slot slot = skeleton.slots.Items[slotIndex]; + if (!slot.bone.active) return; + + if (direction == MixDirection.Out) + { + if (blend == MixBlend.Setup) SetAttachment(skeleton, slot, slot.data.attachmentName); + return; + } + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + if (blend == MixBlend.Setup || blend == MixBlend.First) SetAttachment(skeleton, slot, slot.data.attachmentName); + return; + } + + SetAttachment(skeleton, slot, attachmentNames[Search(frames, time)]); + } + + private void SetAttachment(Skeleton skeleton, Slot slot, string attachmentName) + { + slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); + } + } + + /// Changes a slot's to deform a . + public class DeformTimeline : CurveTimeline, ISlotTimeline + { + readonly int slotIndex; + readonly VertexAttachment attachment; + internal float[][] vertices; + + public DeformTimeline(int frameCount, int bezierCount, int slotIndex, VertexAttachment attachment) + : base(frameCount, bezierCount, (int)Property.Deform + "|" + slotIndex + "|" + attachment.Id) + { + this.slotIndex = slotIndex; + this.attachment = attachment; + vertices = new float[frameCount][]; + } + + public int SlotIndex + { + get + { + return slotIndex; + } + } + /// The attachment that will be deformed. + /// + public VertexAttachment Attachment + { + get + { + return attachment; + } + } + + /// The vertices for each frame. + public float[][] Vertices + { + get + { + return vertices; + } + } + + /// Sets the time and vertices for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds. + /// Vertex positions for an unweighted VertexAttachment, or deform offsets if it has weights. + public void SetFrame(int frame, float time, float[] vertices) + { + frames[frame] = time; + this.vertices[frame] = vertices; + } + + /// Ignored (0 is used for a deform timeline). + /// Ignored (1 is used for a deform timeline). + public void setBezier(int bezier, int frame, int value, float time1, float value1, float cx1, float cy1, float cx2, + float cy2, float time2, float value2) + { + float[] curves = this.curves; + int i = FrameCount + bezier * BEZIER_SIZE; + if (value == 0) curves[frame] = BEZIER + i; + float tmpx = (time1 - cx1 * 2 + cx2) * 0.03f, tmpy = cy2 * 0.03f - cy1 * 0.06f; + float dddx = ((cx1 - cx2) * 3 - time1 + time2) * 0.006f, dddy = (cy1 - cy2 + 0.33333333f) * 0.018f; + float ddx = tmpx * 2 + dddx, ddy = tmpy * 2 + dddy; + float dx = (cx1 - time1) * 0.3f + tmpx + dddx * 0.16666667f, dy = cy1 * 0.3f + tmpy + dddy * 0.16666667f; + float x = time1 + dx, y = dy; + for (int 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; + } + } + + /// Returns the interpolated percentage for the specified time. + /// The frame before time. + private float GetCurvePercent(float time, int frame) + { + float[] curves = this.curves; + int i = (int)curves[frame]; + switch (i) + { + case LINEAR: + float x = frames[frame]; + return (time - x) / (frames[frame + FrameEntries] - x); + case STEPPED: + return 0; + } + i -= BEZIER; + if (curves[i] > time) + { + float x = frames[frame]; + return curves[i + 1] * (time - x) / (curves[i] - x); + } + int 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); + } + } + { // scope added to prevent compile error "float x and y declared in enclosing scope" + float x = curves[n - 2], y = curves[n - 1]; + return y + (1 - y) * (time - x) / (frames[frame + FrameEntries] - x); + } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + + Slot slot = skeleton.slots.Items[slotIndex]; + if (!slot.bone.active) return; + var vertexAttachment = slot.attachment as VertexAttachment; + if (vertexAttachment == null || vertexAttachment.DeformAttachment != attachment) return; + + var deformArray = slot.Deform; + if (deformArray.Count == 0) blend = MixBlend.Setup; + + float[][] vertices = this.vertices; + int vertexCount = vertices[0].Length; + + float[] deform; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + switch (blend) + { + case MixBlend.Setup: + deformArray.Clear(); + return; + case MixBlend.First: + if (alpha == 1) + { + deformArray.Clear(); + return; + } + + // Ensure size and preemptively set count. + if (deformArray.Capacity < vertexCount) deformArray.Capacity = vertexCount; + deformArray.Count = vertexCount; + deform = deformArray.Items; + + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions. + float[] setupVertices = vertexAttachment.vertices; + for (int i = 0; i < vertexCount; i++) + deform[i] += (setupVertices[i] - deform[i]) * alpha; + } + else + { + // Weighted deform offsets. + alpha = 1 - alpha; + for (int i = 0; i < vertexCount; i++) + deform[i] *= alpha; + } + return; + } + return; + } + + // Ensure size and preemptively set count. + if (deformArray.Capacity < vertexCount) deformArray.Capacity = vertexCount; + deformArray.Count = vertexCount; + deform = deformArray.Items; + + if (time >= frames[frames.Length - 1]) + { // Time is after last frame. + float[] lastVertices = vertices[frames.Length - 1]; + if (alpha == 1) + { + if (blend == MixBlend.Add) + { + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions, no alpha. + float[] setupVertices = vertexAttachment.vertices; + for (int i = 0; i < vertexCount; i++) + deform[i] += lastVertices[i] - setupVertices[i]; + } + else + { + // Weighted deform offsets, no alpha. + for (int i = 0; i < vertexCount; i++) + deform[i] += lastVertices[i]; + } + } + else + { + // Vertex positions or deform offsets, no alpha. + Array.Copy(lastVertices, 0, deform, 0, vertexCount); + } + } + else + { + switch (blend) + { + case MixBlend.Setup: + { + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions, with alpha. + float[] setupVertices = vertexAttachment.vertices; + for (int i = 0; i < vertexCount; i++) + { + float setup = setupVertices[i]; + deform[i] = setup + (lastVertices[i] - setup) * alpha; + } + } + else + { + // Weighted deform offsets, with alpha. + for (int 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 (int i = 0; i < vertexCount; i++) + deform[i] += (lastVertices[i] - deform[i]) * alpha; + break; + case MixBlend.Add: + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions, no alpha. + float[] setupVertices = vertexAttachment.vertices; + for (int i = 0; i < vertexCount; i++) + deform[i] += (lastVertices[i] - setupVertices[i]) * alpha; + } + else + { + // Weighted deform offsets, alpha. + for (int i = 0; i < vertexCount; i++) + deform[i] += lastVertices[i] * alpha; + } + break; + } + } + return; + } + + int frame = Search(frames, time); + float percent = GetCurvePercent(time, frame); + float[] prevVertices = vertices[frame]; + float[] nextVertices = vertices[frame + 1]; + + if (alpha == 1) + { + if (blend == MixBlend.Add) + { + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions, no alpha. + float[] setupVertices = vertexAttachment.vertices; + for (int 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 (int 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 (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i]; + deform[i] = prev + (nextVertices[i] - prev) * percent; + } + } + } + else + { + switch (blend) + { + case MixBlend.Setup: + { + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions, with alpha. + float[] setupVertices = vertexAttachment.vertices; + for (int 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 (int 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 (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i]; + deform[i] += (prev + (nextVertices[i] - prev) * percent - deform[i]) * alpha; + } + break; + } + case MixBlend.Add: + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions, with alpha. + float[] setupVertices = vertexAttachment.vertices; + for (int 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 (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i]; + deform[i] += (prev + (nextVertices[i] - prev) * percent) * alpha; + } + } + break; + } + } + } + } + + /// Fires an when specific animation times are reached. + public class EventTimeline : Timeline + { + readonly static string[] propertyIds = { ((int)Property.Event).ToString() }; + readonly Event[] events; + + public EventTimeline(int frameCount) + : base(frameCount, propertyIds) + { + events = new Event[frameCount]; + } + + /// The event for each frame. + public Event[] Events + { + get + { + return events; + } + } + + /// Sets the time and event for the specified frame. + /// Between 0 and frameCount, inclusive. + public void SetFrame(int frame, Event e) + { + frames[frame] = e.time; + events[frame] = e; + } + + /// Fires events for frames > lastTime and <= time. + public override void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, + MixBlend blend, MixDirection direction) + { + + if (firedEvents == null) return; + + float[] frames = this.frames; + int frameCount = frames.Length; + + if (lastTime > time) + { // Fire events after last time for looped animations. + Apply(skeleton, lastTime, int.MaxValue, firedEvents, alpha, blend, direction); + lastTime = -1f; + } + else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame. + return; + if (time < frames[0]) return; // Time is before first frame. + + int i; + if (lastTime < frames[0]) + i = 0; + else + { + i = Search(frames, lastTime) + 1; + float frameTime = frames[i]; + while (i > 0) + { // Fire multiple events with the same frame. + if (frames[i - 1] != frameTime) break; + i--; + } + } + for (; i < frameCount && time >= frames[i]; i++) + firedEvents.Add(events[i]); + } + } + + /// Changes a skeleton's . + public class DrawOrderTimeline : Timeline + { + static readonly string[] propertyIds = { ((int)Property.DrawOrder).ToString() }; + + readonly int[][] drawOrders; + + public DrawOrderTimeline(int frameCount) + : base(frameCount, propertyIds) + { + drawOrders = new int[frameCount][]; + } + + /// The draw order for each frame. + /// . + public int[][] DrawOrders + { + get + { + return drawOrders; + } + } + + /// Sets the time and draw order for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds. + /// For each slot in , the index of the slot in the new draw order. May be null to use + /// setup pose draw order. + public void SetFrame(int frame, float time, int[] drawOrder) + { + frames[frame] = time; + drawOrders[frame] = drawOrder; + } + + public override void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + + if (direction == MixDirection.Out) + { + if (blend == MixBlend.Setup) Array.Copy(skeleton.slots.Items, 0, skeleton.drawOrder.Items, 0, skeleton.slots.Count); + return; + } + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + if (blend == MixBlend.Setup || blend == MixBlend.First) Array.Copy(skeleton.slots.Items, 0, skeleton.drawOrder.Items, 0, skeleton.slots.Count); + return; + } + + int[] drawOrderToSetupIndex = drawOrders[Search(frames, time)]; + if (drawOrderToSetupIndex == null) + Array.Copy(skeleton.slots.Items, 0, skeleton.drawOrder.Items, 0, skeleton.slots.Count); + else + { + Slot[] slots = skeleton.slots.Items; + Slot[] drawOrder = skeleton.drawOrder.Items; + for (int i = 0, n = drawOrderToSetupIndex.Length; i < n; i++) + drawOrder[i] = slots[drawOrderToSetupIndex[i]]; + } + } + } + + /// Changes an IK constraint's , , + /// , , and . + public class IkConstraintTimeline : CurveTimeline + { + public const int ENTRIES = 6; + private const int MIX = 1, SOFTNESS = 2, BEND_DIRECTION = 3, COMPRESS = 4, STRETCH = 5; + + readonly int ikConstraintIndex; + + public IkConstraintTimeline(int frameCount, int bezierCount, int ikConstraintIndex) + : base(frameCount, bezierCount, (int)Property.IkConstraint + "|" + ikConstraintIndex) + { + this.ikConstraintIndex = ikConstraintIndex; + } + + public override int FrameEntries + { + get + { + return ENTRIES; + } + } + + /// The index of the IK constraint slot in that will be changed when this timeline is + /// applied. + public int IkConstraintIndex + { + get + { + return ikConstraintIndex; + } + } + + /// Sets the time, mix, softness, bend direction, compress, and stretch for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds. + /// 1 or -1. + public void 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] = bendDirection; + frames[frame + COMPRESS] = compress ? 1 : 0; + frames[frame + STRETCH] = stretch ? 1 : 0; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + IkConstraint constraint = skeleton.ikConstraints.Items[ikConstraintIndex]; + if (!constraint.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + 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; + } + return; + } + + float mix, softness; + int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; + switch (curveType) + { + case LINEAR: + float before = frames[i]; + mix = frames[i + MIX]; + softness = frames[i + SOFTNESS]; + float t = (time - before) / (frames[i + ENTRIES] - before); + mix += (frames[i + ENTRIES + MIX] - mix) * t; + softness += (frames[i + ENTRIES + SOFTNESS] - softness) * t; + break; + case STEPPED: + mix = frames[i + MIX]; + softness = frames[i + SOFTNESS]; + break; + default: + mix = GetBezierValue(time, i, MIX, curveType - BEZIER); + softness = GetBezierValue(time, i, SOFTNESS, curveType + BEZIER_SIZE - BEZIER); + break; + } + + 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 = (int)frames[i + BEND_DIRECTION]; + constraint.compress = frames[i + COMPRESS] != 0; + constraint.stretch = frames[i + STRETCH] != 0; + } + } + else + { + constraint.mix += (mix - constraint.mix) * alpha; + constraint.softness += (softness - constraint.softness) * alpha; + if (direction == MixDirection.In) + { + constraint.bendDirection = (int)frames[i + BEND_DIRECTION]; + constraint.compress = frames[i + COMPRESS] != 0; + constraint.stretch = frames[i + STRETCH] != 0; + } + } + } + } + + /// Changes a transform constraint's mixes. + public class TransformConstraintTimeline : CurveTimeline + { + public const int ENTRIES = 7; + private const int ROTATE = 1, X = 2, Y = 3, SCALEX = 4, SCALEY = 5, SHEARY = 6; + + readonly int transformConstraintIndex; + + public TransformConstraintTimeline(int frameCount, int bezierCount, int transformConstraintIndex) + : base(frameCount, bezierCount, (int)Property.TransformConstraint + "|" + transformConstraintIndex) + { + this.transformConstraintIndex = transformConstraintIndex; + } + + public override int FrameEntries + { + get + { + return ENTRIES; + } + } + + /// The index of the transform constraint slot in that will be changed when this + /// timeline is applied. + public int TransformConstraintIndex + { + get + { + return transformConstraintIndex; + } + } + + /// Sets the time, rotate mix, translate mix, scale mix, and shear mix for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds. + public void SetFrame(int 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; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + TransformConstraint constraint = skeleton.transformConstraints.Items[transformConstraintIndex]; + if (!constraint.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + TransformConstraintData data = constraint.data; + 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; + } + return; + } + + float rotate, x, y, scaleX, scaleY, shearY; + int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; + switch (curveType) + { + case 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 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); + break; + } + + if (blend == MixBlend.Setup) + { + TransformConstraintData 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; + 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; + } + } + } + + /// Changes a path constraint's . + public class PathConstraintPositionTimeline : CurveTimeline1 + { + readonly int pathConstraintIndex; + + public PathConstraintPositionTimeline(int frameCount, int bezierCount, int pathConstraintIndex) + : base(frameCount, bezierCount, (int)Property.PathConstraintPosition + "|" + pathConstraintIndex) + { + this.pathConstraintIndex = pathConstraintIndex; + } + + /// The index of the path constraint slot in that will be changed when this timeline + /// is applied. + public int PathConstraintIndex + { + get + { + return pathConstraintIndex; + } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; + if (!constraint.active) return; + + if (time < frames[0]) + { // Time is before first frame. + switch (blend) + { + case MixBlend.Setup: + constraint.position = constraint.data.position; + return; + case MixBlend.First: + constraint.position += (constraint.data.position - constraint.position) * alpha; + return; + } + return; + } + + float position = GetCurveValue(time); + if (blend == MixBlend.Setup) + constraint.position = constraint.data.position + (position - constraint.data.position) * alpha; + else + constraint.position += (position - constraint.position) * alpha; + } + } + + /// Changes a path constraint's . + public class PathConstraintSpacingTimeline : CurveTimeline1 + { + readonly int pathConstraintIndex; + + public PathConstraintSpacingTimeline(int frameCount, int bezierCount, int pathConstraintIndex) + : base(frameCount, bezierCount, (int)Property.PathConstraintSpacing + "|" + pathConstraintIndex) + { + this.pathConstraintIndex = pathConstraintIndex; + } + + /// The index of the path constraint slot in that will be changed when this timeline + /// is applied. + public int PathConstraintIndex + { + get + { + return pathConstraintIndex; + } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, MixBlend blend, + MixDirection direction) + { + + PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; + if (!constraint.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + switch (blend) + { + case MixBlend.Setup: + constraint.spacing = constraint.data.spacing; + return; + case MixBlend.First: + constraint.spacing += (constraint.data.spacing - constraint.spacing) * alpha; + return; + } + return; + } + + float spacing = GetCurveValue(time); + if (blend == MixBlend.Setup) + constraint.spacing = constraint.data.spacing + (spacing - constraint.data.spacing) * alpha; + else + constraint.spacing += (spacing - constraint.spacing) * alpha; + } + } + + /// Changes a transform constraint's , , and + /// . + public class PathConstraintMixTimeline : CurveTimeline + { + public const int ENTRIES = 4; + private const int ROTATE = 1, X = 2, Y = 3; + + readonly int pathConstraintIndex; + + public PathConstraintMixTimeline(int frameCount, int bezierCount, int pathConstraintIndex) + : base(frameCount, bezierCount, (int)Property.PathConstraintMix + "|" + pathConstraintIndex) + { + this.pathConstraintIndex = pathConstraintIndex; + } + + public override int FrameEntries + { + get { return ENTRIES; } + } + + /// The index of the path constraint slot in that will be changed when this timeline + /// is applied. + public int PathConstraintIndex + { + get + { + return pathConstraintIndex; + } + } + + /// Sets the time and color for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds. + public void SetFrame(int frame, float time, float mixRotate, float mixX, float mixY) + { + frame <<= 2; + frames[frame] = time; + frames[frame + ROTATE] = mixRotate; + frames[frame + X] = mixX; + frames[frame + Y] = mixY; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; + if (!constraint.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + 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; + return; + } + return; + } + + float rotate, x, y; + int i = Search(frames, time, ENTRIES), 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); + break; + } + + 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; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/AnimationState.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/AnimationState.cs index 7a07b5a..4cd0625 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/AnimationState.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/AnimationState.cs @@ -30,1414 +30,1570 @@ using System; using System.Collections.Generic; -namespace Spine4_0_64 { - - /// - /// - /// Applies animations over time, queues animations for later playback, mixes (crossfading) between animations, and applies - /// multiple animations on top of each other (layering). - /// - /// See Applying Animations in the Spine Runtimes Guide. - /// - public class AnimationState { - internal static readonly Animation EmptyAnimation = new Animation("", new ExposedList(), 0); - - /// 1) A previously applied timeline has set this property. - /// Result: Mix from the current pose to the timeline pose. - internal const int Subsequent = 0; - /// 1) This is the first timeline to set this property. - /// 2) The next track entry applied after this one does not have a timeline to set this property. - /// Result: Mix from the setup pose to the timeline pose. - internal const int First = 1; - /// 1) A previously applied timeline has set this property.
- /// 2) The next track entry to be applied does have a timeline to set this property.
- /// 3) The next track entry after that one does not have a timeline to set this property.
- /// Result: Mix from the current pose to the timeline pose, but do not mix out. This avoids "dipping" when crossfading - /// animations that key the same property. A subsequent timeline will set this property using a mix. - internal const int HoldSubsequent = 2; - /// 1) This is the first timeline to set this property. - /// 2) The next track entry to be applied does have a timeline to set this property. - /// 3) The next track entry after that one does not have a timeline to set this property. - /// Result: Mix from the setup pose to the timeline pose, but do not mix out. This avoids "dipping" when crossfading animations - /// that key the same property. A subsequent timeline will set this property using a mix. - internal const int HoldFirst = 3; - /// 1) This is the first timeline to set this property. - /// 2) The next track entry to be applied does have a timeline to set this property. - /// 3) The next track entry after that one does have a timeline to set this property. - /// 4) timelineHoldMix stores the first subsequent track entry that does not have a timeline to set this property. - /// Result: The same as HOLD except the mix percentage from the timelineHoldMix track entry is used. This handles when more than - /// 2 track entries in a row have a timeline that sets the same property. - /// Eg, A -> B -> C -> D where A, B, and C have a timeline setting same property, but D does not. When A is applied, to avoid - /// "dipping" A is not mixed out, however D (the first entry that doesn't set the property) mixing in is used to mix out A - /// (which affects B and C). Without using D to mix out, A would be applied fully until mixing completes, then snap to the mixed - /// out position. - internal const int HoldMix = 4; - - internal const int Setup = 1, Current = 2; - - protected AnimationStateData data; - private readonly ExposedList tracks = new ExposedList(); - private readonly ExposedList events = new ExposedList(); - // difference to libgdx reference: delegates are used for event callbacks instead of 'final SnapshotArray listeners'. - internal void OnStart (TrackEntry entry) { if (Start != null) Start(entry); } - internal void OnInterrupt (TrackEntry entry) { if (Interrupt != null) Interrupt(entry); } - internal void OnEnd (TrackEntry entry) { if (End != null) End(entry); } - internal void OnDispose (TrackEntry entry) { if (Dispose != null) Dispose(entry); } - internal void OnComplete (TrackEntry entry) { if (Complete != null) Complete(entry); } - internal void OnEvent (TrackEntry entry, Event e) { if (Event != null) Event(entry, e); } - - public delegate void TrackEntryDelegate (TrackEntry trackEntry); - /// See - /// API Reference documentation pages here for details. Usage in C# and spine-unity is explained - /// here - /// on the spine-unity documentation pages. - public event TrackEntryDelegate Start, Interrupt, End, Dispose, Complete; - - public delegate void TrackEntryEventDelegate (TrackEntry trackEntry, Event e); - public event TrackEntryEventDelegate Event; - - public void AssignEventSubscribersFrom (AnimationState src) { - Event = src.Event; - Start = src.Start; - Interrupt = src.Interrupt; - End = src.End; - Dispose = src.Dispose; - Complete = src.Complete; - } - - public void AddEventSubscribersFrom (AnimationState src) { - Event += src.Event; - Start += src.Start; - Interrupt += src.Interrupt; - End += src.End; - Dispose += src.Dispose; - Complete += src.Complete; - } - - // end of difference - private readonly EventQueue queue; // Initialized by constructor. - private readonly HashSet propertyIds = new HashSet(); - private bool animationsChanged; - private float timeScale = 1; - private int unkeyedState; - - private readonly Pool trackEntryPool = new Pool(); - - public AnimationState (AnimationStateData data) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - this.data = data; - this.queue = new EventQueue( - this, - delegate { this.animationsChanged = true; }, - trackEntryPool - ); - } - - /// - /// Increments the track entry , setting queued animations as current if needed. - /// delta time - public void Update (float delta) { - delta *= timeScale; - TrackEntry[] tracksItems = tracks.Items; - for (int i = 0, n = tracks.Count; i < n; i++) { - TrackEntry current = tracksItems[i]; - if (current == null) continue; - - 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. - tracksItems[i] = null; - queue.End(current); - ClearNext(current); - continue; - } - if (current.mixingFrom != null && UpdateMixingFrom(current, 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(); - } - - /// Returns true when all mixing from entries are complete. - private bool 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; - - // Require mixTime > 0 to ensure the mixing from entry was applied at least once. - if (to.mixTime > 0 && to.mixTime >= to.mixDuration) { - // Require totalAlpha == 0 to ensure mixing is complete, unless mixDuration == 0 (the transition is a single frame). - if (from.totalAlpha == 0 || to.mixDuration == 0) { - to.mixingFrom = from.mixingFrom; - if (from.mixingFrom != null) from.mixingFrom.mixingTo = to; - to.interruptAlpha = from.interruptAlpha; - queue.End(from); - } - return finished; - } - - from.trackTime += delta * from.timeScale; - to.mixTime += delta; - return false; - } - - /// - /// Poses the skeleton using the track entry animations. The animation state is not changed, so can be applied to multiple - /// skeletons to pose them identically. - /// True if any animations were applied. - public bool Apply (Skeleton skeleton) { - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - if (animationsChanged) AnimationsChanged(); - - ExposedList events = this.events; - bool applied = false; - TrackEntry[] tracksItems = tracks.Items; - for (int i = 0, n = tracks.Count; i < n; i++) { - TrackEntry current = tracksItems[i]; - if (current == null || current.delay > 0) continue; - applied = true; - - // Track 0 animations aren't for layering, so do not show the previously applied animations before the first key. - MixBlend blend = i == 0 ? MixBlend.First : current.mixBlend; - - // Apply mixing from entries first. - float mix = current.alpha; - if (current.mixingFrom != null) - mix *= ApplyMixingFrom(current, skeleton, blend); - else if (current.trackTime >= current.trackEnd && current.next == null) // - mix = 0; // Set to setup pose the last time the entry will be applied. - - // Apply current entry. - float animationLast = current.animationLast, animationTime = current.AnimationTime, applyTime = animationTime; - ExposedList applyEvents = events; - if (current.reverse) { - applyTime = current.animation.duration - applyTime; - applyEvents = null; - } - - int timelineCount = current.animation.timelines.Count; - Timeline[] timelines = current.animation.timelines.Items; - if ((i == 0 && mix == 1) || blend == MixBlend.Add) { - for (int ii = 0; ii < timelineCount; ii++) { - Timeline timeline = timelines[ii]; - if (timeline is AttachmentTimeline) - ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, applyTime, blend, true); - else - timeline.Apply(skeleton, animationLast, applyTime, applyEvents, mix, blend, MixDirection.In); - } - } else { - int[] timelineMode = current.timelineMode.Items; - - bool firstFrame = current.timelinesRotation.Count != timelineCount << 1; - if (firstFrame) current.timelinesRotation.Resize(timelineCount << 1); - float[] timelinesRotation = current.timelinesRotation.Items; - - for (int ii = 0; ii < timelineCount; ii++) { - Timeline timeline = timelines[ii]; - MixBlend timelineBlend = timelineMode[ii] == AnimationState.Subsequent ? blend : MixBlend.Setup; - var rotateTimeline = timeline as RotateTimeline; - if (rotateTimeline != null) - ApplyRotateTimeline(rotateTimeline, skeleton, applyTime, mix, timelineBlend, timelinesRotation, - ii << 1, firstFrame); - else if (timeline is AttachmentTimeline) - ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, applyTime, blend, true); - else - timeline.Apply(skeleton, animationLast, applyTime, applyEvents, mix, timelineBlend, MixDirection.In); - } - } - QueueEvents(current, animationTime); - events.Clear(false); - current.nextAnimationLast = animationTime; - current.nextTrackLast = current.trackTime; - } - - // Set slots attachments to the setup pose, if needed. This occurs if an animation that is mixing out sets attachments so - // subsequent timelines see any deform, but the subsequent timelines don't set an attachment (eg they are also mixing out or - // the time is before the first key). - int setupState = unkeyedState + Setup; - Slot[] slots = skeleton.slots.Items; - for (int i = 0, n = skeleton.slots.Count; i < n; i++) { - Slot slot = slots[i]; - if (slot.attachmentState == setupState) { - string attachmentName = slot.data.attachmentName; - slot.Attachment = (attachmentName == null ? null : skeleton.GetAttachment(slot.data.index, attachmentName)); - } - } - unkeyedState += 2; // Increasing after each use avoids the need to reset attachmentState for every slot. - - queue.Drain(); - return applied; - } - - /// Version of only applying and updating time at - /// EventTimelines for lightweight off-screen updates. - /// When set to false, only animation times of TrackEntries are updated. - // Note: This method is not part of the libgdx reference implementation. - public bool ApplyEventTimelinesOnly (Skeleton skeleton, bool issueEvents = true) { - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - - ExposedList events = this.events; - bool applied = false; - TrackEntry[] tracksItems = tracks.Items; - for (int i = 0, n = tracks.Count; i < n; i++) { - TrackEntry current = tracksItems[i]; - if (current == null || current.delay > 0) continue; - applied = true; - - // Apply mixing from entries first. - if (current.mixingFrom != null) ApplyMixingFromEventTimelinesOnly(current, skeleton, issueEvents); - - // Apply current entry. - float animationLast = current.animationLast, animationTime = current.AnimationTime; - - if (issueEvents) { - int timelineCount = current.animation.timelines.Count; - Timeline[] timelines = current.animation.timelines.Items; - for (int ii = 0; ii < timelineCount; ii++) { - Timeline timeline = timelines[ii]; - if (timeline is EventTimeline) - timeline.Apply(skeleton, animationLast, animationTime, events, 1.0f, MixBlend.Setup, MixDirection.In); - } - QueueEvents(current, animationTime); - events.Clear(false); - } - current.nextAnimationLast = animationTime; - current.nextTrackLast = current.trackTime; - } - - if (issueEvents) - queue.Drain(); - return applied; - } - - private float 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; // Tracks > 0 are transparent and can't reset to setup pose. - } else { - mix = to.mixTime / to.mixDuration; - if (mix > 1) mix = 1; - if (blend != MixBlend.First) blend = from.mixBlend; // Track 0 ignores track mix blend. - } - - bool attachments = mix < from.attachmentThreshold, drawOrder = mix < from.drawOrderThreshold; - int timelineCount = from.animation.timelines.Count; - Timeline[] timelines = from.animation.timelines.Items; - float alphaHold = from.alpha * to.interruptAlpha, alphaMix = alphaHold * (1 - mix); - float animationLast = from.animationLast, animationTime = from.AnimationTime, applyTime = animationTime; - ExposedList events = null; - if (from.reverse) - applyTime = from.animation.duration - applyTime; - else { - if (mix < from.eventThreshold) events = this.events; - } - - if (blend == MixBlend.Add) { - for (int i = 0; i < timelineCount; i++) - timelines[i].Apply(skeleton, animationLast, applyTime, events, alphaMix, blend, MixDirection.Out); - } else { - int[] timelineMode = from.timelineMode.Items; - TrackEntry[] timelineHoldMix = from.timelineHoldMix.Items; - - bool firstFrame = from.timelinesRotation.Count != timelineCount << 1; - if (firstFrame) from.timelinesRotation.Resize(timelineCount << 1); - float[] timelinesRotation = from.timelinesRotation.Items; - - from.totalAlpha = 0; - for (int i = 0; i < timelineCount; i++) { - Timeline timeline = timelines[i]; - MixDirection direction = MixDirection.Out; - MixBlend timelineBlend; - float alpha; - switch (timelineMode[i]) { - case AnimationState.Subsequent: - if (!drawOrder && timeline is DrawOrderTimeline) continue; - timelineBlend = blend; - alpha = alphaMix; - break; - case AnimationState.First: - timelineBlend = MixBlend.Setup; - alpha = alphaMix; - break; - case AnimationState.HoldSubsequent: - timelineBlend = blend; - alpha = alphaHold; - break; - case AnimationState.HoldFirst: - timelineBlend = MixBlend.Setup; - alpha = alphaHold; - break; - default: // HoldMix - timelineBlend = MixBlend.Setup; - TrackEntry holdMix = timelineHoldMix[i]; - alpha = alphaHold * Math.Max(0, 1 - holdMix.mixTime / holdMix.mixDuration); - break; - } - from.totalAlpha += alpha; - var rotateTimeline = timeline as RotateTimeline; - if (rotateTimeline != null) { - ApplyRotateTimeline(rotateTimeline, skeleton, applyTime, alpha, timelineBlend, timelinesRotation, i << 1, - firstFrame); - } else if (timeline is AttachmentTimeline) { - ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, applyTime, timelineBlend, attachments); - } else { - if (drawOrder && timeline is DrawOrderTimeline && timelineBlend == MixBlend.Setup) - direction = MixDirection.In; - timeline.Apply(skeleton, animationLast, applyTime, events, alpha, timelineBlend, direction); - } - } - } - - if (to.mixDuration > 0) QueueEvents(from, animationTime); - this.events.Clear(false); - from.nextAnimationLast = animationTime; - from.nextTrackLast = from.trackTime; - - return mix; - } - - /// Version of only applying and updating time at - /// EventTimelines for lightweight off-screen updates. - /// When set to false, only animation times of TrackEntries are updated. - // Note: This method is not part of the libgdx reference implementation. - private float ApplyMixingFromEventTimelinesOnly (TrackEntry to, Skeleton skeleton, bool issueEvents) { - TrackEntry from = to.mixingFrom; - if (from.mixingFrom != null) ApplyMixingFromEventTimelinesOnly(from, skeleton, issueEvents); - - - float mix; - if (to.mixDuration == 0) { // Single frame mix to undo mixingFrom changes. - mix = 1; - } else { - mix = to.mixTime / to.mixDuration; - if (mix > 1) mix = 1; - } - - ExposedList eventBuffer = mix < from.eventThreshold ? this.events : null; - if (eventBuffer == null) return mix; - - float animationLast = from.animationLast, animationTime = from.AnimationTime; - if (issueEvents) { - int timelineCount = from.animation.timelines.Count; - Timeline[] timelines = from.animation.timelines.Items; - for (int i = 0; i < timelineCount; i++) { - Timeline timeline = timelines[i]; - if (timeline is EventTimeline) - timeline.Apply(skeleton, animationLast, animationTime, eventBuffer, 0, MixBlend.Setup, MixDirection.Out); - } - - if (to.mixDuration > 0) QueueEvents(from, animationTime); - this.events.Clear(false); - } - from.nextAnimationLast = animationTime; - from.nextTrackLast = from.trackTime; - - return mix; - } - - /// Applies the attachment timeline and sets . - /// False when: 1) the attachment timeline is mixing out, 2) mix < attachmentThreshold, and 3) the timeline - /// is not the last timeline to set the slot's attachment. In that case the timeline is applied only so subsequent - /// timelines see any deform. - private void ApplyAttachmentTimeline (AttachmentTimeline timeline, Skeleton skeleton, float time, MixBlend blend, - bool attachments) { - - Slot slot = skeleton.slots.Items[timeline.SlotIndex]; - if (!slot.bone.active) return; - - float[] frames = timeline.frames; - if (time < frames[0]) { // Time is before first frame. - if (blend == MixBlend.Setup || blend == MixBlend.First) - SetAttachment(skeleton, slot, slot.data.attachmentName, attachments); - } else - SetAttachment(skeleton, slot, timeline.AttachmentNames[Timeline.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.attachmentState <= unkeyedState) slot.attachmentState = unkeyedState + Setup; - } - - private void SetAttachment (Skeleton skeleton, Slot slot, String attachmentName, bool attachments) { - slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slot.data.index, attachmentName); - if (attachments) slot.attachmentState = unkeyedState + Current; - } - - /// - /// Applies the rotate timeline, mixing with the current pose while keeping the same rotation direction chosen as the shortest - /// the first time the mixing was applied. - static private void ApplyRotateTimeline (RotateTimeline timeline, Skeleton skeleton, float time, float alpha, MixBlend blend, - float[] timelinesRotation, int i, bool firstFrame) { - - if (firstFrame) timelinesRotation[i] = 0; - - if (alpha == 1) { - timeline.Apply(skeleton, 0, time, null, 1, blend, MixDirection.In); - return; - } - - Bone bone = skeleton.bones.Items[timeline.BoneIndex]; - if (!bone.active) return; - - float[] frames = timeline.frames; - float r1, r2; - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case MixBlend.Setup: - bone.rotation = bone.data.rotation; - goto default; // Fall through. - default: - return; - case MixBlend.First: - r1 = bone.rotation; - r2 = bone.data.rotation; - break; - } - } else { - r1 = blend == MixBlend.Setup ? bone.data.rotation : bone.rotation; - r2 = bone.data.rotation + timeline.GetCurveValue(time); - } - - // Mix between rotations using the direction of the shortest route on the first frame. - float total, diff = r2 - r1; - diff -= (16384 - (int)(16384.499999999996 - diff / 360)) * 360; - if (diff == 0) { - total = timelinesRotation[i]; - } else { - float lastTotal, lastDiff; - if (firstFrame) { - lastTotal = 0; - lastDiff = diff; - } else { - lastTotal = timelinesRotation[i]; // Angle and direction of mix, including loops. - lastDiff = timelinesRotation[i + 1]; // Difference between bones. - } - bool current = diff > 0, dir = lastTotal >= 0; - // Detect cross at 0 (not 180). - if (Math.Sign(lastDiff) != Math.Sign(diff) && Math.Abs(lastDiff) <= 90) { - // A cross after a 360 rotation is a loop. - if (Math.Abs(lastTotal) > 180) lastTotal += 360 * Math.Sign(lastTotal); - dir = current; - } - total = diff + lastTotal - lastTotal % 360; // Store loops as part of lastTotal. - if (dir != current) total += 360 * Math.Sign(lastTotal); - timelinesRotation[i] = total; - } - timelinesRotation[i + 1] = diff; - bone.rotation = r1 + total * alpha; - } - - private void QueueEvents (TrackEntry entry, float animationTime) { - float animationStart = entry.animationStart, animationEnd = entry.animationEnd; - float duration = animationEnd - animationStart; - float trackLastWrapped = entry.trackLast % duration; - - // Queue events before complete. - Event[] eventsItems = this.events.Items; - int i = 0, n = events.Count; - for (; i < n; i++) { - Event e = eventsItems[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) - complete = duration == 0 || (trackLastWrapped > entry.trackTime % duration); - else - complete = animationTime >= animationEnd && entry.animationLast < animationEnd; - if (complete) queue.Complete(entry); - - // Queue events after complete. - for (; i < n; i++) { - Event e = eventsItems[i]; - if (e.time < animationStart) continue; // Discard events outside animation start/end. - queue.Event(entry, eventsItems[i]); - } - } - - /// - /// Removes all animations from all tracks, leaving skeletons in their current pose. - /// - /// It may be desired to use to mix the skeletons back to the setup pose, - /// rather than leaving them in their current pose. - /// - public void ClearTracks () { - bool oldDrainDisabled = queue.drainDisabled; - queue.drainDisabled = true; - for (int i = 0, n = tracks.Count; i < n; i++) { - ClearTrack(i); - } - tracks.Clear(); - queue.drainDisabled = oldDrainDisabled; - queue.Drain(); - } - - /// - /// Removes all animations from the track, leaving skeletons in their current pose. - /// - /// It may be desired to use to mix the skeletons back to the setup pose, - /// rather than leaving them in their current pose. - /// - public void ClearTrack (int trackIndex) { - if (trackIndex >= tracks.Count) return; - TrackEntry current = tracks.Items[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.Items[current.trackIndex] = null; - - queue.Drain(); - } - - /// Sets the active TrackEntry for a given track number. - private void SetCurrent (int index, TrackEntry current, bool interrupt) { - TrackEntry from = ExpandToIndex(index); - tracks.Items[index] = current; - current.previous = null; - - if (from != null) { - if (interrupt) queue.Interrupt(from); - current.mixingFrom = from; - from.mixingTo = current; - current.mixTime = 0; - - // Store the interrupted mix percentage. - if (from.mixingFrom != null && from.mixDuration > 0) - current.interruptAlpha *= Math.Min(1, from.mixTime / from.mixDuration); - - from.timelinesRotation.Clear(); // Reset rotation for mixing out, in case entry was mixed in. - } - - queue.Start(current); // triggers AnimationsChanged - } - - /// Sets an animation by name. - public TrackEntry SetAnimation (int trackIndex, string animationName, bool loop) { - Animation animation = data.skeletonData.FindAnimation(animationName); - if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName"); - return SetAnimation(trackIndex, animation, loop); - } - - /// Sets the current animation for a track, discarding any queued animations. If the formerly current track entry was never - /// applied to a skeleton, it is replaced (not mixed from). - /// 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 determines when the track is cleared. - /// A track entry to allow further customization of animation playback. References to the track entry must not be kept - /// after the event occurs. - public TrackEntry SetAnimation (int trackIndex, Animation animation, bool loop) { - if (animation == null) throw new ArgumentNullException("animation", "animation cannot be 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.Items[trackIndex] = current.mixingFrom; - queue.Interrupt(current); - queue.End(current); - ClearNext(current); - current = current.mixingFrom; - interrupt = false; // mixingFrom is current again, but don't interrupt it twice. - } else - ClearNext(current); - } - TrackEntry entry = NewTrackEntry(trackIndex, animation, loop, current); - SetCurrent(trackIndex, entry, interrupt); - queue.Drain(); - return entry; - } - - /// Queues an animation by name. - /// - public TrackEntry AddAnimation (int trackIndex, string animationName, bool loop, float delay) { - Animation animation = data.skeletonData.FindAnimation(animationName); - if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName"); - return AddAnimation(trackIndex, animation, loop, delay); - } - - /// Adds an animation to be played after the current or last queued animation for a track. If the track is empty, it is - /// equivalent to calling . - /// - /// If > 0, sets . If <= 0, the delay set is the duration of the previous track entry - /// minus any mix duration (from the plus the specified Delay (ie the mix - /// ends at (Delay = 0) or before (Delay < 0) the previous track entry duration). If the - /// previous entry is looping, its next loop completion is used instead of its duration. - /// - /// A track entry to allow further customization of animation playback. References to the track entry must not be kept - /// after the event occurs. - public TrackEntry AddAnimation (int trackIndex, Animation animation, bool loop, float delay) { - if (animation == null) throw new ArgumentNullException("animation", "animation cannot be 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(); - } else { - last.next = entry; - entry.previous = last; - if (delay <= 0) delay += last.TrackComplete - entry.mixDuration; - } - - entry.delay = delay; - return entry; - } - - /// - /// Sets an empty animation for a track, discarding any queued animations, and sets the track entry's - /// . An empty animation has no timelines and serves as a placeholder for mixing in or out. - /// - /// Mixing out is done by setting an empty animation with a mix duration using either , - /// , or . Mixing to an empty animation causes - /// the previous animation to be applied less and less over the mix duration. Properties keyed in the previous animation - /// transition to the value from lower tracks or to the setup pose value if no lower tracks key the property. A mix duration of - /// 0 still mixes out over one frame. - /// - /// Mixing in is done by first setting an empty animation, then adding an animation using - /// with the desired delay (an empty animation has a duration of 0) and on - /// the returned track entry, set the . Mixing from an empty animation causes the new - /// animation to be applied more and more over the mix duration. Properties keyed in the new animation transition from the value - /// from lower tracks or from the setup pose value if no lower tracks key the property to the value keyed in the new - /// animation. - public TrackEntry SetEmptyAnimation (int trackIndex, float mixDuration) { - TrackEntry entry = SetAnimation(trackIndex, AnimationState.EmptyAnimation, false); - entry.mixDuration = mixDuration; - entry.trackEnd = mixDuration; - return entry; - } - - /// - /// Adds an empty animation to be played after the current or last queued animation for a track, and sets the track entry's - /// . If the track is empty, it is equivalent to calling - /// . - /// - /// Track number. - /// Mix duration. - /// If > 0, sets . If <= 0, the delay set is the duration of the previous track entry - /// minus any mix duration plus the specified Delay (ie the mix ends at (Delay = 0) or - /// before (Delay < 0) the previous track entry duration). If the previous entry is looping, its next - /// loop completion is used instead of its duration. - /// A track entry to allow further customization of animation playback. References to the track entry must not be kept - /// after the event occurs. - /// - public TrackEntry AddEmptyAnimation (int trackIndex, float mixDuration, float delay) { - TrackEntry entry = AddAnimation(trackIndex, AnimationState.EmptyAnimation, false, delay); - if (delay <= 0) entry.delay += entry.mixDuration - mixDuration; - entry.mixDuration = mixDuration; - entry.trackEnd = mixDuration; - return entry; - } - - /// - /// Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix - /// duration. - public void SetEmptyAnimations (float mixDuration) { - bool oldDrainDisabled = queue.drainDisabled; - queue.drainDisabled = true; - TrackEntry[] tracksItems = tracks.Items; - for (int i = 0, n = tracks.Count; i < n; i++) { - TrackEntry current = tracksItems[i]; - if (current != null) SetEmptyAnimation(current.trackIndex, mixDuration); - } - queue.drainDisabled = oldDrainDisabled; - queue.Drain(); - } - - private TrackEntry ExpandToIndex (int index) { - if (index < tracks.Count) return tracks.Items[index]; - tracks.Resize(index + 1); - return null; - } - - /// Object-pooling version of new TrackEntry. Obtain an unused TrackEntry from the pool and clear/initialize its values. - /// May be null. - private TrackEntry NewTrackEntry (int trackIndex, Animation animation, bool loop, TrackEntry last) { - TrackEntry entry = trackEntryPool.Obtain(); - entry.trackIndex = trackIndex; - entry.animation = animation; - entry.loop = loop; - entry.holdPrevious = false; - - entry.eventThreshold = 0; - entry.attachmentThreshold = 0; - entry.drawOrderThreshold = 0; - - entry.animationStart = 0; - entry.animationEnd = animation.Duration; - entry.animationLast = -1; - entry.nextAnimationLast = -1; - - entry.delay = 0; - entry.trackTime = 0; - entry.trackLast = -1; - entry.nextTrackLast = -1; - entry.trackEnd = float.MaxValue; - entry.timeScale = 1; - - entry.alpha = 1; - entry.interruptAlpha = 1; - entry.mixTime = 0; - entry.mixDuration = last == null ? 0 : data.GetMix(last.animation, animation); - entry.mixBlend = MixBlend.Replace; - return entry; - } - - /// Removes the next entry and all entries after it for the specified entry. - public void ClearNext (TrackEntry entry) { - TrackEntry next = entry.next; - while (next != null) { - queue.Dispose(next); - next = next.next; - } - entry.next = null; - } - - private void AnimationsChanged () { - animationsChanged = false; - - // Process in the order that animations are applied. - propertyIds.Clear(); - int n = tracks.Count; - TrackEntry[] tracksItems = tracks.Items; - for (int i = 0; i < n; i++) { - TrackEntry entry = tracksItems[i]; - if (entry == null) continue; - while (entry.mixingFrom != null) // Move to last entry, then iterate in reverse. - entry = entry.mixingFrom; - do { - if (entry.mixingTo == null || entry.mixBlend != MixBlend.Add) ComputeHold(entry); - entry = entry.mixingTo; - } while (entry != null); - } - } - - private void ComputeHold (TrackEntry entry) { - TrackEntry to = entry.mixingTo; - Timeline[] timelines = entry.animation.timelines.Items; - int timelinesCount = entry.animation.timelines.Count; - int[] timelineMode = entry.timelineMode.Resize(timelinesCount).Items; - entry.timelineHoldMix.Clear(); - TrackEntry[] timelineHoldMix = entry.timelineHoldMix.Resize(timelinesCount).Items; - HashSet propertyIds = this.propertyIds; - - if (to != null && to.holdPrevious) { - for (int i = 0; i < timelinesCount; i++) - timelineMode[i] = propertyIds.AddAll(timelines[i].PropertyIds) ? AnimationState.HoldFirst : AnimationState.HoldSubsequent; - - return; - } - - // outer: - for (int i = 0; i < timelinesCount; i++) { - Timeline timeline = timelines[i]; - String[] ids = timeline.PropertyIds; - if (!propertyIds.AddAll(ids)) - timelineMode[i] = AnimationState.Subsequent; - else if (to == null || timeline is AttachmentTimeline || timeline is DrawOrderTimeline - || timeline is EventTimeline || !to.animation.HasTimeline(ids)) { - timelineMode[i] = AnimationState.First; - } else { - for (TrackEntry next = to.mixingTo; next != null; next = next.mixingTo) { - if (next.animation.HasTimeline(ids)) continue; - if (next.mixDuration > 0) { - timelineMode[i] = AnimationState.HoldMix; - timelineHoldMix[i] = next; - goto continue_outer; // continue outer; - } - break; - } - timelineMode[i] = AnimationState.HoldFirst; - } - continue_outer: { } - } - } - - /// The track entry for the animation currently playing on the track, or null if no animation is currently playing. - public TrackEntry GetCurrent (int trackIndex) { - if (trackIndex >= tracks.Count) return null; - return tracks.Items[trackIndex]; - } - - /// Discards all listener notifications that have not yet been delivered. This can be useful to call from an - /// AnimationState event subscriber when it is known that further notifications that may have been already queued for delivery - /// are not wanted because new animations are being set. - public void ClearListenerNotifications () { - queue.Clear(); - } - - /// - /// Multiplier for the delta time when the animation state is updated, causing time for all animations and mixes to play slower - /// or faster. Defaults to 1. - /// - /// See TrackEntry for affecting a single animation. - /// - public float TimeScale { get { return timeScale; } set { timeScale = value; } } - - /// The AnimationStateData to look up mix durations. - public AnimationStateData Data { - get { - return data; - } - set { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - this.data = value; - } - } - - /// A list of tracks that have animations, which may contain nulls. - public ExposedList Tracks { get { return tracks; } } - - override public string ToString () { - var buffer = new System.Text.StringBuilder(); - TrackEntry[] tracksItems = tracks.Items; - for (int i = 0, n = tracks.Count; i < n; i++) { - TrackEntry entry = tracksItems[i]; - if (entry == null) continue; - if (buffer.Length > 0) buffer.Append(", "); - buffer.Append(entry.ToString()); - } - if (buffer.Length == 0) return ""; - return buffer.ToString(); - } - } - - /// - /// - /// Stores settings and other state for the playback of an animation on an track. - /// - /// References to a track entry must not be kept after the event occurs. - /// - public class TrackEntry : Pool.IPoolable { - internal Animation animation; - - internal TrackEntry previous, next, mixingFrom, mixingTo; - // difference to libgdx reference: delegates are used for event callbacks instead of 'AnimationStateListener listener'. - /// See - /// API Reference documentation pages here for details. Usage in C# and spine-unity is explained - /// here - /// on the spine-unity documentation pages. - public event AnimationState.TrackEntryDelegate Start, Interrupt, End, Dispose, Complete; - public event AnimationState.TrackEntryEventDelegate Event; - internal void OnStart () { if (Start != null) Start(this); } - internal void OnInterrupt () { if (Interrupt != null) Interrupt(this); } - internal void OnEnd () { if (End != null) End(this); } - internal void OnDispose () { if (Dispose != null) Dispose(this); } - internal void OnComplete () { if (Complete != null) Complete(this); } - internal void OnEvent (Event e) { if (Event != null) Event(this, e); } - - internal int trackIndex; - - internal bool loop, holdPrevious, reverse; - internal float eventThreshold, attachmentThreshold, drawOrderThreshold; - internal float animationStart, animationEnd, animationLast, nextAnimationLast; - internal float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale = 1f; - internal float alpha, mixTime, mixDuration, interruptAlpha, totalAlpha; - internal MixBlend mixBlend = MixBlend.Replace; - internal readonly ExposedList timelineMode = new ExposedList(); - internal readonly ExposedList timelineHoldMix = new ExposedList(); - internal readonly ExposedList timelinesRotation = new ExposedList(); - - // IPoolable.Reset() - public void Reset () { - previous = null; - next = null; - mixingFrom = null; - mixingTo = null; - animation = null; - // replaces 'listener = null;' since delegates are used for event callbacks - Start = null; - Interrupt = null; - End = null; - Dispose = null; - Complete = null; - Event = null; - timelineMode.Clear(); - timelineHoldMix.Clear(); - timelinesRotation.Clear(); - } - - /// The index of the track where this entry is either current or queued. - /// - public int TrackIndex { get { return trackIndex; } } - - /// The animation to apply for this track entry. - public Animation Animation { get { return animation; } } - - /// - /// If true, the animation will repeat. If false it will not, instead its last frame is applied if played beyond its - /// duration. - public bool Loop { get { return loop; } set { loop = value; } } - - /// - /// - /// Seconds to postpone playing the animation. When this track entry is the current track entry, Delay - /// postpones incrementing the . When this track entry is queued, Delay is the time from - /// the start of the previous animation to when this track entry will become the current track entry (ie when the previous - /// track entry >= this track entry's Delay). - /// - /// affects the delay. - /// - /// When using with a delay <= 0, the delay - /// is set using the mix duration from the . If is set afterward, the delay - /// may need to be adjusted. - public float Delay { get { return delay; } set { delay = value; } } - - /// - /// Current time in seconds this track entry has been the current track entry. The track time determines - /// . The track time can be set to start the animation at a time other than 0, without affecting - /// looping. - public float TrackTime { get { return trackTime; } set { trackTime = value; } } - - /// - /// - /// The track time in seconds when this animation will be removed from the track. Defaults to the highest possible float - /// value, meaning the animation will be applied until a new animation is set or the track is cleared. If the track end time - /// is reached, no other animations are queued for playback, and mixing from any previous animations is complete, then the - /// properties keyed by the animation are set to the setup pose and the track is cleared. - /// - /// It may be desired to use rather than have the animation - /// abruptly cease being applied. - /// - public float TrackEnd { get { return trackEnd; } set { trackEnd = value; } } - - /// - /// If this track entry is non-looping, the track time in seconds when is reached, or the current - /// if it has already been reached. If this track entry is looping, the track time when this - /// animation will reach its next (the next loop completion). - public float TrackComplete { - get { - 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. - } - } - - /// - /// - /// Seconds when this animation starts, both initially and after looping. Defaults to 0. - /// - /// When changing the AnimationStart time, it often makes sense to set to the same - /// value to prevent timeline keys before the start time from triggering. - /// - public float AnimationStart { get { return animationStart; } set { animationStart = value; } } - - /// - /// Seconds for the last frame of this animation. Non-looping animations won't play past this time. Looping animations will - /// loop back to at this time. Defaults to the animation . - /// - public float AnimationEnd { get { return animationEnd; } set { animationEnd = value; } } - - /// - /// 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 AnimationLast time (exclusive) and - /// AnimationTime (inclusive). Defaults to -1 to ensure triggers on frame 0 happen the first time this animation - /// is applied. - public float AnimationLast { - get { return animationLast; } - set { - animationLast = value; - nextAnimationLast = value; - } - } - - /// - /// Uses to compute the AnimationTime, which is between - /// and . When the TrackTime is 0, the AnimationTime is equal to the - /// AnimationStart time. - /// - public float AnimationTime { - get { - if (loop) { - float duration = animationEnd - animationStart; - if (duration == 0) return animationStart; - return (trackTime % duration) + animationStart; - } - return Math.Min(trackTime + animationStart, animationEnd); - } - } - - /// - /// - /// Multiplier for the delta time when this track entry is updated, causing time for this animation to pass slower or - /// faster. Defaults to 1. - /// - /// Values < 0 are not supported. To play an animation in reverse, use . - /// - /// is not affected by track entry time scale, so may need to be adjusted to - /// match the animation speed. - /// - /// When using with a Delay <= 0, the - /// is set using the mix duration from the , assuming time scale to be 1. If - /// the time scale is not 1, the delay may need to be adjusted. - /// - /// See AnimationState for affecting all animations. - /// - public float TimeScale { get { return timeScale; } set { timeScale = value; } } - - /// - /// - /// Values < 1 mix this animation with the skeleton's current pose (usually the pose resulting from lower tracks). Defaults - /// to 1, which overwrites the skeleton's current pose with this animation. - /// - /// Typically track 0 is used to completely pose the skeleton, then alpha is 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. - /// - public float Alpha { get { return alpha; } set { alpha = value; } } - - public float InterruptAlpha { get { return interruptAlpha; } } - - /// - /// When the mix percentage ( / ) is less than the - /// EventThreshold, event timelines are applied while this animation is being mixed out. Defaults to 0, so event - /// timelines are not applied while this animation is being mixed out. - /// - public float EventThreshold { get { return eventThreshold; } set { eventThreshold = value; } } - - /// - /// When the mix percentage ( / ) is less than the - /// AttachmentThreshold, attachment timelines are applied while this animation is being mixed out. Defaults to - /// 0, so attachment timelines are not applied while this animation is being mixed out. - /// - public float AttachmentThreshold { get { return attachmentThreshold; } set { attachmentThreshold = value; } } - - /// - /// When the mix percentage ( / ) is less than the - /// DrawOrderThreshold, draw order timelines are applied while this animation is being mixed out. Defaults to 0, - /// so draw order timelines are not applied while this animation is being mixed out. - /// - public float DrawOrderThreshold { get { return drawOrderThreshold; } set { drawOrderThreshold = value; } } - - /// - /// The animation queued to start after this animation, or null if there is none. next makes up a doubly linked - /// list. - /// - /// See to truncate the list. - public TrackEntry Next { get { return next; } } - - /// - /// The animation queued to play before this animation, or null. previous makes up a doubly linked list. - public TrackEntry Previous { get { return previous; } } - - /// - /// Returns true if at least one loop has been completed. - /// - public bool IsComplete { - get { return trackTime >= animationEnd - animationStart; } - } - - /// - /// Seconds from 0 to the when mixing from the previous animation to this animation. May be - /// slightly more than MixDuration when the mix is complete. - public float MixTime { get { return mixTime; } set { mixTime = value; } } - - /// - /// - /// 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 MixDuration can be set manually rather than use the value from - /// . In that case, the MixDuration can be set for a new - /// track entry only before is first called. - /// - /// When using with a Delay <= 0, the - /// is set using the mix duration from the . If mixDuration is set - /// afterward, the delay may need to be adjusted. For example: - /// entry.Delay = entry.previous.TrackComplete - entry.MixDuration; - /// - public float MixDuration { get { return mixDuration; } set { mixDuration = value; } } - - /// - /// - /// Controls how properties keyed in the animation are mixed with lower tracks. Defaults to . - /// - /// Track entries on track 0 ignore this setting and always use . - /// - /// The MixBlend can be set for a new track entry only before is first - /// called. - /// - public MixBlend MixBlend { get { return mixBlend; } set { mixBlend = value; } } - - /// - /// 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 linked list. - public TrackEntry MixingFrom { get { return mixingFrom; } } - - /// - /// The track entry for the next animation when mixing from this animation to the next animation, or null if no mixing is - /// currently occuring. When mixing to multiple animations, MixingTo makes up a linked list. - public TrackEntry MixingTo { get { return mixingTo; } } - - /// - /// - /// 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. - /// - public bool HoldPrevious { get { return holdPrevious; } set { holdPrevious = value; } } - - /// - /// If true, the animation will be applied in reverse. Events are not fired when an animation is applied in reverse. - public bool Reverse { get { return reverse; } set { reverse = value; } } - - /// Returns true if this entry is for the empty animation. See , - /// , and . - public bool IsEmptyAnimation { get { return animation == AnimationState.EmptyAnimation; } } - - /// - /// - /// 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 and starting animations on other tracks. - /// - /// Mixing with 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. - /// - public void ResetRotationDirections () { - timelinesRotation.Clear(); - } - - override public string ToString () { - return animation == null ? "" : animation.name; - } - - // Note: This method is required by SpineAnimationStateMixerBehaviour, - // which is part of the timeline extension package. Thus the internal member variable - // nextTrackLast is not accessible. We favor providing this method - // over exposing nextTrackLast as public property, which would rather confuse users. - public void AllowImmediateQueue () { - if (nextTrackLast < 0) nextTrackLast = 0; - } - } - - class EventQueue { - private readonly List eventQueueEntries = new List(); - internal bool drainDisabled; - - private readonly AnimationState state; - private readonly Pool trackEntryPool; - internal event Action AnimationsChanged; - - internal EventQueue (AnimationState state, Action HandleAnimationsChanged, Pool trackEntryPool) { - this.state = state; - this.AnimationsChanged += HandleAnimationsChanged; - this.trackEntryPool = trackEntryPool; - } - - internal void Start (TrackEntry entry) { - eventQueueEntries.Add(new EventQueueEntry(EventType.Start, entry)); - if (AnimationsChanged != null) AnimationsChanged(); - } - - internal void Interrupt (TrackEntry entry) { - eventQueueEntries.Add(new EventQueueEntry(EventType.Interrupt, entry)); - } - - internal void End (TrackEntry entry) { - eventQueueEntries.Add(new EventQueueEntry(EventType.End, entry)); - if (AnimationsChanged != null) AnimationsChanged(); - } - - internal void Dispose (TrackEntry entry) { - eventQueueEntries.Add(new EventQueueEntry(EventType.Dispose, entry)); - } - - internal void Complete (TrackEntry entry) { - eventQueueEntries.Add(new EventQueueEntry(EventType.Complete, entry)); - } - - internal void Event (TrackEntry entry, Event e) { - eventQueueEntries.Add(new EventQueueEntry(EventType.Event, entry, e)); - } - - /// Raises all events in the queue and drains the queue. - internal void Drain () { - if (drainDisabled) return; - drainDisabled = true; - - List eventQueueEntries = this.eventQueueEntries; - AnimationState state = this.state; - - // Don't cache eventQueueEntries.Count so callbacks can queue their own events (eg, call SetAnimation in AnimationState_Complete). - for (int i = 0; i < eventQueueEntries.Count; i++) { - EventQueueEntry queueEntry = eventQueueEntries[i]; - TrackEntry trackEntry = queueEntry.entry; - - switch (queueEntry.type) { - case EventType.Start: - trackEntry.OnStart(); - state.OnStart(trackEntry); - break; - case EventType.Interrupt: - trackEntry.OnInterrupt(); - state.OnInterrupt(trackEntry); - break; - case EventType.End: - trackEntry.OnEnd(); - state.OnEnd(trackEntry); - goto case EventType.Dispose; // Fall through. (C#) - case EventType.Dispose: - trackEntry.OnDispose(); - state.OnDispose(trackEntry); - trackEntryPool.Free(trackEntry); - break; - case EventType.Complete: - trackEntry.OnComplete(); - state.OnComplete(trackEntry); - break; - case EventType.Event: - trackEntry.OnEvent(queueEntry.e); - state.OnEvent(trackEntry, queueEntry.e); - break; - } - } - eventQueueEntries.Clear(); - - drainDisabled = false; - } - - internal void Clear () { - eventQueueEntries.Clear(); - } - - struct EventQueueEntry { - public EventType type; - public TrackEntry entry; - public Event e; - - public EventQueueEntry (EventType eventType, TrackEntry trackEntry, Event e = null) { - this.type = eventType; - this.entry = trackEntry; - this.e = e; - } - } - - enum EventType { - Start, Interrupt, End, Dispose, Complete, Event - } - } - - class Pool where T : class, new() { - public readonly int max; - readonly Stack freeObjects; - - public int Count { get { return freeObjects.Count; } } - public int Peak { get; private set; } - - public Pool (int initialCapacity = 16, int max = int.MaxValue) { - freeObjects = new Stack(initialCapacity); - this.max = max; - } - - public T Obtain () { - return freeObjects.Count == 0 ? new T() : freeObjects.Pop(); - } - - public void Free (T obj) { - if (obj == null) throw new ArgumentNullException("obj", "obj cannot be null"); - if (freeObjects.Count < max) { - freeObjects.Push(obj); - Peak = Math.Max(Peak, freeObjects.Count); - } - Reset(obj); - } - - public void Clear () { - freeObjects.Clear(); - } - - protected void Reset (T obj) { - var poolable = obj as IPoolable; - if (poolable != null) poolable.Reset(); - } - - public interface IPoolable { - void Reset (); - } - } - - public static class HashSetExtensions { - public static bool AddAll (this HashSet set, T[] addSet) { - bool anyItemAdded = false; - for (int i = 0, n = addSet.Length; i < n; ++i) { - T item = addSet[i]; - anyItemAdded |= set.Add(item); - } - return anyItemAdded; - } - } +namespace Spine4_0_64 +{ + + /// + /// + /// Applies animations over time, queues animations for later playback, mixes (crossfading) between animations, and applies + /// multiple animations on top of each other (layering). + /// + /// See Applying Animations in the Spine Runtimes Guide. + /// + public class AnimationState + { + internal static readonly Animation EmptyAnimation = new Animation("", new ExposedList(), 0); + + /// 1) A previously applied timeline has set this property. + /// Result: Mix from the current pose to the timeline pose. + internal const int Subsequent = 0; + /// 1) This is the first timeline to set this property. + /// 2) The next track entry applied after this one does not have a timeline to set this property. + /// Result: Mix from the setup pose to the timeline pose. + internal const int First = 1; + /// 1) A previously applied timeline has set this property.
+ /// 2) The next track entry to be applied does have a timeline to set this property.
+ /// 3) The next track entry after that one does not have a timeline to set this property.
+ /// Result: Mix from the current pose to the timeline pose, but do not mix out. This avoids "dipping" when crossfading + /// animations that key the same property. A subsequent timeline will set this property using a mix. + internal const int HoldSubsequent = 2; + /// 1) This is the first timeline to set this property. + /// 2) The next track entry to be applied does have a timeline to set this property. + /// 3) The next track entry after that one does not have a timeline to set this property. + /// Result: Mix from the setup pose to the timeline pose, but do not mix out. This avoids "dipping" when crossfading animations + /// that key the same property. A subsequent timeline will set this property using a mix. + internal const int HoldFirst = 3; + /// 1) This is the first timeline to set this property. + /// 2) The next track entry to be applied does have a timeline to set this property. + /// 3) The next track entry after that one does have a timeline to set this property. + /// 4) timelineHoldMix stores the first subsequent track entry that does not have a timeline to set this property. + /// Result: The same as HOLD except the mix percentage from the timelineHoldMix track entry is used. This handles when more than + /// 2 track entries in a row have a timeline that sets the same property. + /// Eg, A -> B -> C -> D where A, B, and C have a timeline setting same property, but D does not. When A is applied, to avoid + /// "dipping" A is not mixed out, however D (the first entry that doesn't set the property) mixing in is used to mix out A + /// (which affects B and C). Without using D to mix out, A would be applied fully until mixing completes, then snap to the mixed + /// out position. + internal const int HoldMix = 4; + + internal const int Setup = 1, Current = 2; + + protected AnimationStateData data; + private readonly ExposedList tracks = new ExposedList(); + private readonly ExposedList events = new ExposedList(); + // difference to libgdx reference: delegates are used for event callbacks instead of 'final SnapshotArray listeners'. + internal void OnStart(TrackEntry entry) { if (Start != null) Start(entry); } + internal void OnInterrupt(TrackEntry entry) { if (Interrupt != null) Interrupt(entry); } + internal void OnEnd(TrackEntry entry) { if (End != null) End(entry); } + internal void OnDispose(TrackEntry entry) { if (Dispose != null) Dispose(entry); } + internal void OnComplete(TrackEntry entry) { if (Complete != null) Complete(entry); } + internal void OnEvent(TrackEntry entry, Event e) { if (Event != null) Event(entry, e); } + + public delegate void TrackEntryDelegate(TrackEntry trackEntry); + /// See + /// API Reference documentation pages here for details. Usage in C# and spine-unity is explained + /// here + /// on the spine-unity documentation pages. + public event TrackEntryDelegate Start, Interrupt, End, Dispose, Complete; + + public delegate void TrackEntryEventDelegate(TrackEntry trackEntry, Event e); + public event TrackEntryEventDelegate Event; + + public void AssignEventSubscribersFrom(AnimationState src) + { + Event = src.Event; + Start = src.Start; + Interrupt = src.Interrupt; + End = src.End; + Dispose = src.Dispose; + Complete = src.Complete; + } + + public void AddEventSubscribersFrom(AnimationState src) + { + Event += src.Event; + Start += src.Start; + Interrupt += src.Interrupt; + End += src.End; + Dispose += src.Dispose; + Complete += src.Complete; + } + + // end of difference + private readonly EventQueue queue; // Initialized by constructor. + private readonly HashSet propertyIds = new HashSet(); + private bool animationsChanged; + private float timeScale = 1; + private int unkeyedState; + + private readonly Pool trackEntryPool = new Pool(); + + public AnimationState(AnimationStateData data) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + this.data = data; + this.queue = new EventQueue( + this, + delegate { this.animationsChanged = true; }, + trackEntryPool + ); + } + + /// + /// Increments the track entry , setting queued animations as current if needed. + /// delta time + public void Update(float delta) + { + delta *= timeScale; + TrackEntry[] tracksItems = tracks.Items; + for (int i = 0, n = tracks.Count; i < n; i++) + { + TrackEntry current = tracksItems[i]; + if (current == null) continue; + + 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. + tracksItems[i] = null; + queue.End(current); + ClearNext(current); + continue; + } + if (current.mixingFrom != null && UpdateMixingFrom(current, 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(); + } + + /// Returns true when all mixing from entries are complete. + private bool 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; + + // Require mixTime > 0 to ensure the mixing from entry was applied at least once. + if (to.mixTime > 0 && to.mixTime >= to.mixDuration) + { + // Require totalAlpha == 0 to ensure mixing is complete, unless mixDuration == 0 (the transition is a single frame). + if (from.totalAlpha == 0 || to.mixDuration == 0) + { + to.mixingFrom = from.mixingFrom; + if (from.mixingFrom != null) from.mixingFrom.mixingTo = to; + to.interruptAlpha = from.interruptAlpha; + queue.End(from); + } + return finished; + } + + from.trackTime += delta * from.timeScale; + to.mixTime += delta; + return false; + } + + /// + /// Poses the skeleton using the track entry animations. The animation state is not changed, so can be applied to multiple + /// skeletons to pose them identically. + /// True if any animations were applied. + public bool Apply(Skeleton skeleton) + { + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + if (animationsChanged) AnimationsChanged(); + + ExposedList events = this.events; + bool applied = false; + TrackEntry[] tracksItems = tracks.Items; + for (int i = 0, n = tracks.Count; i < n; i++) + { + TrackEntry current = tracksItems[i]; + if (current == null || current.delay > 0) continue; + applied = true; + + // Track 0 animations aren't for layering, so do not show the previously applied animations before the first key. + MixBlend blend = i == 0 ? MixBlend.First : current.mixBlend; + + // Apply mixing from entries first. + float mix = current.alpha; + if (current.mixingFrom != null) + mix *= ApplyMixingFrom(current, skeleton, blend); + else if (current.trackTime >= current.trackEnd && current.next == null) // + mix = 0; // Set to setup pose the last time the entry will be applied. + + // Apply current entry. + float animationLast = current.animationLast, animationTime = current.AnimationTime, applyTime = animationTime; + ExposedList applyEvents = events; + if (current.reverse) + { + applyTime = current.animation.duration - applyTime; + applyEvents = null; + } + + int timelineCount = current.animation.timelines.Count; + Timeline[] timelines = current.animation.timelines.Items; + if ((i == 0 && mix == 1) || blend == MixBlend.Add) + { + for (int ii = 0; ii < timelineCount; ii++) + { + Timeline timeline = timelines[ii]; + if (timeline is AttachmentTimeline) + ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, applyTime, blend, true); + else + timeline.Apply(skeleton, animationLast, applyTime, applyEvents, mix, blend, MixDirection.In); + } + } + else + { + int[] timelineMode = current.timelineMode.Items; + + bool firstFrame = current.timelinesRotation.Count != timelineCount << 1; + if (firstFrame) current.timelinesRotation.Resize(timelineCount << 1); + float[] timelinesRotation = current.timelinesRotation.Items; + + for (int ii = 0; ii < timelineCount; ii++) + { + Timeline timeline = timelines[ii]; + MixBlend timelineBlend = timelineMode[ii] == AnimationState.Subsequent ? blend : MixBlend.Setup; + var rotateTimeline = timeline as RotateTimeline; + if (rotateTimeline != null) + ApplyRotateTimeline(rotateTimeline, skeleton, applyTime, mix, timelineBlend, timelinesRotation, + ii << 1, firstFrame); + else if (timeline is AttachmentTimeline) + ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, applyTime, blend, true); + else + timeline.Apply(skeleton, animationLast, applyTime, applyEvents, mix, timelineBlend, MixDirection.In); + } + } + QueueEvents(current, animationTime); + events.Clear(false); + current.nextAnimationLast = animationTime; + current.nextTrackLast = current.trackTime; + } + + // Set slots attachments to the setup pose, if needed. This occurs if an animation that is mixing out sets attachments so + // subsequent timelines see any deform, but the subsequent timelines don't set an attachment (eg they are also mixing out or + // the time is before the first key). + int setupState = unkeyedState + Setup; + Slot[] slots = skeleton.slots.Items; + for (int i = 0, n = skeleton.slots.Count; i < n; i++) + { + Slot slot = slots[i]; + if (slot.attachmentState == setupState) + { + string attachmentName = slot.data.attachmentName; + slot.Attachment = (attachmentName == null ? null : skeleton.GetAttachment(slot.data.index, attachmentName)); + } + } + unkeyedState += 2; // Increasing after each use avoids the need to reset attachmentState for every slot. + + queue.Drain(); + return applied; + } + + /// Version of only applying and updating time at + /// EventTimelines for lightweight off-screen updates. + /// When set to false, only animation times of TrackEntries are updated. + // Note: This method is not part of the libgdx reference implementation. + public bool ApplyEventTimelinesOnly(Skeleton skeleton, bool issueEvents = true) + { + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + + ExposedList events = this.events; + bool applied = false; + TrackEntry[] tracksItems = tracks.Items; + for (int i = 0, n = tracks.Count; i < n; i++) + { + TrackEntry current = tracksItems[i]; + if (current == null || current.delay > 0) continue; + applied = true; + + // Apply mixing from entries first. + if (current.mixingFrom != null) ApplyMixingFromEventTimelinesOnly(current, skeleton, issueEvents); + + // Apply current entry. + float animationLast = current.animationLast, animationTime = current.AnimationTime; + + if (issueEvents) + { + int timelineCount = current.animation.timelines.Count; + Timeline[] timelines = current.animation.timelines.Items; + for (int ii = 0; ii < timelineCount; ii++) + { + Timeline timeline = timelines[ii]; + if (timeline is EventTimeline) + timeline.Apply(skeleton, animationLast, animationTime, events, 1.0f, MixBlend.Setup, MixDirection.In); + } + QueueEvents(current, animationTime); + events.Clear(false); + } + current.nextAnimationLast = animationTime; + current.nextTrackLast = current.trackTime; + } + + if (issueEvents) + queue.Drain(); + return applied; + } + + private float 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; // Tracks > 0 are transparent and can't reset to setup pose. + } + else + { + mix = to.mixTime / to.mixDuration; + if (mix > 1) mix = 1; + if (blend != MixBlend.First) blend = from.mixBlend; // Track 0 ignores track mix blend. + } + + bool attachments = mix < from.attachmentThreshold, drawOrder = mix < from.drawOrderThreshold; + int timelineCount = from.animation.timelines.Count; + Timeline[] timelines = from.animation.timelines.Items; + float alphaHold = from.alpha * to.interruptAlpha, alphaMix = alphaHold * (1 - mix); + float animationLast = from.animationLast, animationTime = from.AnimationTime, applyTime = animationTime; + ExposedList events = null; + if (from.reverse) + applyTime = from.animation.duration - applyTime; + else + { + if (mix < from.eventThreshold) events = this.events; + } + + if (blend == MixBlend.Add) + { + for (int i = 0; i < timelineCount; i++) + timelines[i].Apply(skeleton, animationLast, applyTime, events, alphaMix, blend, MixDirection.Out); + } + else + { + int[] timelineMode = from.timelineMode.Items; + TrackEntry[] timelineHoldMix = from.timelineHoldMix.Items; + + bool firstFrame = from.timelinesRotation.Count != timelineCount << 1; + if (firstFrame) from.timelinesRotation.Resize(timelineCount << 1); + float[] timelinesRotation = from.timelinesRotation.Items; + + from.totalAlpha = 0; + for (int i = 0; i < timelineCount; i++) + { + Timeline timeline = timelines[i]; + MixDirection direction = MixDirection.Out; + MixBlend timelineBlend; + float alpha; + switch (timelineMode[i]) + { + case AnimationState.Subsequent: + if (!drawOrder && timeline is DrawOrderTimeline) continue; + timelineBlend = blend; + alpha = alphaMix; + break; + case AnimationState.First: + timelineBlend = MixBlend.Setup; + alpha = alphaMix; + break; + case AnimationState.HoldSubsequent: + timelineBlend = blend; + alpha = alphaHold; + break; + case AnimationState.HoldFirst: + timelineBlend = MixBlend.Setup; + alpha = alphaHold; + break; + default: // HoldMix + timelineBlend = MixBlend.Setup; + TrackEntry holdMix = timelineHoldMix[i]; + alpha = alphaHold * Math.Max(0, 1 - holdMix.mixTime / holdMix.mixDuration); + break; + } + from.totalAlpha += alpha; + var rotateTimeline = timeline as RotateTimeline; + if (rotateTimeline != null) + { + ApplyRotateTimeline(rotateTimeline, skeleton, applyTime, alpha, timelineBlend, timelinesRotation, i << 1, + firstFrame); + } + else if (timeline is AttachmentTimeline) + { + ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, applyTime, timelineBlend, attachments); + } + else + { + if (drawOrder && timeline is DrawOrderTimeline && timelineBlend == MixBlend.Setup) + direction = MixDirection.In; + timeline.Apply(skeleton, animationLast, applyTime, events, alpha, timelineBlend, direction); + } + } + } + + if (to.mixDuration > 0) QueueEvents(from, animationTime); + this.events.Clear(false); + from.nextAnimationLast = animationTime; + from.nextTrackLast = from.trackTime; + + return mix; + } + + /// Version of only applying and updating time at + /// EventTimelines for lightweight off-screen updates. + /// When set to false, only animation times of TrackEntries are updated. + // Note: This method is not part of the libgdx reference implementation. + private float ApplyMixingFromEventTimelinesOnly(TrackEntry to, Skeleton skeleton, bool issueEvents) + { + TrackEntry from = to.mixingFrom; + if (from.mixingFrom != null) ApplyMixingFromEventTimelinesOnly(from, skeleton, issueEvents); + + + float mix; + if (to.mixDuration == 0) + { // Single frame mix to undo mixingFrom changes. + mix = 1; + } + else + { + mix = to.mixTime / to.mixDuration; + if (mix > 1) mix = 1; + } + + ExposedList eventBuffer = mix < from.eventThreshold ? this.events : null; + if (eventBuffer == null) return mix; + + float animationLast = from.animationLast, animationTime = from.AnimationTime; + if (issueEvents) + { + int timelineCount = from.animation.timelines.Count; + Timeline[] timelines = from.animation.timelines.Items; + for (int i = 0; i < timelineCount; i++) + { + Timeline timeline = timelines[i]; + if (timeline is EventTimeline) + timeline.Apply(skeleton, animationLast, animationTime, eventBuffer, 0, MixBlend.Setup, MixDirection.Out); + } + + if (to.mixDuration > 0) QueueEvents(from, animationTime); + this.events.Clear(false); + } + from.nextAnimationLast = animationTime; + from.nextTrackLast = from.trackTime; + + return mix; + } + + /// Applies the attachment timeline and sets . + /// False when: 1) the attachment timeline is mixing out, 2) mix < attachmentThreshold, and 3) the timeline + /// is not the last timeline to set the slot's attachment. In that case the timeline is applied only so subsequent + /// timelines see any deform. + private void ApplyAttachmentTimeline(AttachmentTimeline timeline, Skeleton skeleton, float time, MixBlend blend, + bool attachments) + { + + Slot slot = skeleton.slots.Items[timeline.SlotIndex]; + if (!slot.bone.active) return; + + float[] frames = timeline.frames; + if (time < frames[0]) + { // Time is before first frame. + if (blend == MixBlend.Setup || blend == MixBlend.First) + SetAttachment(skeleton, slot, slot.data.attachmentName, attachments); + } + else + SetAttachment(skeleton, slot, timeline.AttachmentNames[Timeline.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.attachmentState <= unkeyedState) slot.attachmentState = unkeyedState + Setup; + } + + private void SetAttachment(Skeleton skeleton, Slot slot, String attachmentName, bool attachments) + { + slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slot.data.index, attachmentName); + if (attachments) slot.attachmentState = unkeyedState + Current; + } + + /// + /// Applies the rotate timeline, mixing with the current pose while keeping the same rotation direction chosen as the shortest + /// the first time the mixing was applied. + static private void ApplyRotateTimeline(RotateTimeline timeline, Skeleton skeleton, float time, float alpha, MixBlend blend, + float[] timelinesRotation, int i, bool firstFrame) + { + + if (firstFrame) timelinesRotation[i] = 0; + + if (alpha == 1) + { + timeline.Apply(skeleton, 0, time, null, 1, blend, MixDirection.In); + return; + } + + Bone bone = skeleton.bones.Items[timeline.BoneIndex]; + if (!bone.active) return; + + float[] frames = timeline.frames; + float r1, r2; + if (time < frames[0]) + { // Time is before first frame. + switch (blend) + { + case MixBlend.Setup: + bone.rotation = bone.data.rotation; + goto default; // Fall through. + default: + return; + case MixBlend.First: + r1 = bone.rotation; + r2 = bone.data.rotation; + break; + } + } + else + { + r1 = blend == MixBlend.Setup ? bone.data.rotation : bone.rotation; + r2 = bone.data.rotation + timeline.GetCurveValue(time); + } + + // Mix between rotations using the direction of the shortest route on the first frame. + float total, diff = r2 - r1; + diff -= (16384 - (int)(16384.499999999996 - diff / 360)) * 360; + if (diff == 0) + { + total = timelinesRotation[i]; + } + else + { + float lastTotal, lastDiff; + if (firstFrame) + { + lastTotal = 0; + lastDiff = diff; + } + else + { + lastTotal = timelinesRotation[i]; // Angle and direction of mix, including loops. + lastDiff = timelinesRotation[i + 1]; // Difference between bones. + } + bool current = diff > 0, dir = lastTotal >= 0; + // Detect cross at 0 (not 180). + if (Math.Sign(lastDiff) != Math.Sign(diff) && Math.Abs(lastDiff) <= 90) + { + // A cross after a 360 rotation is a loop. + if (Math.Abs(lastTotal) > 180) lastTotal += 360 * Math.Sign(lastTotal); + dir = current; + } + total = diff + lastTotal - lastTotal % 360; // Store loops as part of lastTotal. + if (dir != current) total += 360 * Math.Sign(lastTotal); + timelinesRotation[i] = total; + } + timelinesRotation[i + 1] = diff; + bone.rotation = r1 + total * alpha; + } + + private void QueueEvents(TrackEntry entry, float animationTime) + { + float animationStart = entry.animationStart, animationEnd = entry.animationEnd; + float duration = animationEnd - animationStart; + float trackLastWrapped = entry.trackLast % duration; + + // Queue events before complete. + Event[] eventsItems = this.events.Items; + int i = 0, n = events.Count; + for (; i < n; i++) + { + Event e = eventsItems[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) + complete = duration == 0 || (trackLastWrapped > entry.trackTime % duration); + else + complete = animationTime >= animationEnd && entry.animationLast < animationEnd; + if (complete) queue.Complete(entry); + + // Queue events after complete. + for (; i < n; i++) + { + Event e = eventsItems[i]; + if (e.time < animationStart) continue; // Discard events outside animation start/end. + queue.Event(entry, eventsItems[i]); + } + } + + /// + /// Removes all animations from all tracks, leaving skeletons in their current pose. + /// + /// It may be desired to use to mix the skeletons back to the setup pose, + /// rather than leaving them in their current pose. + /// + public void ClearTracks() + { + bool oldDrainDisabled = queue.drainDisabled; + queue.drainDisabled = true; + for (int i = 0, n = tracks.Count; i < n; i++) + { + ClearTrack(i); + } + tracks.Clear(); + queue.drainDisabled = oldDrainDisabled; + queue.Drain(); + } + + /// + /// Removes all animations from the track, leaving skeletons in their current pose. + /// + /// It may be desired to use to mix the skeletons back to the setup pose, + /// rather than leaving them in their current pose. + /// + public void ClearTrack(int trackIndex) + { + if (trackIndex >= tracks.Count) return; + TrackEntry current = tracks.Items[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.Items[current.trackIndex] = null; + + queue.Drain(); + } + + /// Sets the active TrackEntry for a given track number. + private void SetCurrent(int index, TrackEntry current, bool interrupt) + { + TrackEntry from = ExpandToIndex(index); + tracks.Items[index] = current; + current.previous = null; + + if (from != null) + { + if (interrupt) queue.Interrupt(from); + current.mixingFrom = from; + from.mixingTo = current; + current.mixTime = 0; + + // Store the interrupted mix percentage. + if (from.mixingFrom != null && from.mixDuration > 0) + current.interruptAlpha *= Math.Min(1, from.mixTime / from.mixDuration); + + from.timelinesRotation.Clear(); // Reset rotation for mixing out, in case entry was mixed in. + } + + queue.Start(current); // triggers AnimationsChanged + } + + /// Sets an animation by name. + public TrackEntry SetAnimation(int trackIndex, string animationName, bool loop) + { + Animation animation = data.skeletonData.FindAnimation(animationName); + if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName"); + return SetAnimation(trackIndex, animation, loop); + } + + /// Sets the current animation for a track, discarding any queued animations. If the formerly current track entry was never + /// applied to a skeleton, it is replaced (not mixed from). + /// 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 determines when the track is cleared. + /// A track entry to allow further customization of animation playback. References to the track entry must not be kept + /// after the event occurs. + public TrackEntry SetAnimation(int trackIndex, Animation animation, bool loop) + { + if (animation == null) throw new ArgumentNullException("animation", "animation cannot be 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.Items[trackIndex] = current.mixingFrom; + queue.Interrupt(current); + queue.End(current); + ClearNext(current); + current = current.mixingFrom; + interrupt = false; // mixingFrom is current again, but don't interrupt it twice. + } + else + ClearNext(current); + } + TrackEntry entry = NewTrackEntry(trackIndex, animation, loop, current); + SetCurrent(trackIndex, entry, interrupt); + queue.Drain(); + return entry; + } + + /// Queues an animation by name. + /// + public TrackEntry AddAnimation(int trackIndex, string animationName, bool loop, float delay) + { + Animation animation = data.skeletonData.FindAnimation(animationName); + if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName"); + return AddAnimation(trackIndex, animation, loop, delay); + } + + /// Adds an animation to be played after the current or last queued animation for a track. If the track is empty, it is + /// equivalent to calling . + /// + /// If > 0, sets . If <= 0, the delay set is the duration of the previous track entry + /// minus any mix duration (from the plus the specified Delay (ie the mix + /// ends at (Delay = 0) or before (Delay < 0) the previous track entry duration). If the + /// previous entry is looping, its next loop completion is used instead of its duration. + /// + /// A track entry to allow further customization of animation playback. References to the track entry must not be kept + /// after the event occurs. + public TrackEntry AddAnimation(int trackIndex, Animation animation, bool loop, float delay) + { + if (animation == null) throw new ArgumentNullException("animation", "animation cannot be 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(); + } + else + { + last.next = entry; + entry.previous = last; + if (delay <= 0) delay += last.TrackComplete - entry.mixDuration; + } + + entry.delay = delay; + return entry; + } + + /// + /// Sets an empty animation for a track, discarding any queued animations, and sets the track entry's + /// . An empty animation has no timelines and serves as a placeholder for mixing in or out. + /// + /// Mixing out is done by setting an empty animation with a mix duration using either , + /// , or . Mixing to an empty animation causes + /// the previous animation to be applied less and less over the mix duration. Properties keyed in the previous animation + /// transition to the value from lower tracks or to the setup pose value if no lower tracks key the property. A mix duration of + /// 0 still mixes out over one frame. + /// + /// Mixing in is done by first setting an empty animation, then adding an animation using + /// with the desired delay (an empty animation has a duration of 0) and on + /// the returned track entry, set the . Mixing from an empty animation causes the new + /// animation to be applied more and more over the mix duration. Properties keyed in the new animation transition from the value + /// from lower tracks or from the setup pose value if no lower tracks key the property to the value keyed in the new + /// animation. + public TrackEntry SetEmptyAnimation(int trackIndex, float mixDuration) + { + TrackEntry entry = SetAnimation(trackIndex, AnimationState.EmptyAnimation, false); + entry.mixDuration = mixDuration; + entry.trackEnd = mixDuration; + return entry; + } + + /// + /// Adds an empty animation to be played after the current or last queued animation for a track, and sets the track entry's + /// . If the track is empty, it is equivalent to calling + /// . + /// + /// Track number. + /// Mix duration. + /// If > 0, sets . If <= 0, the delay set is the duration of the previous track entry + /// minus any mix duration plus the specified Delay (ie the mix ends at (Delay = 0) or + /// before (Delay < 0) the previous track entry duration). If the previous entry is looping, its next + /// loop completion is used instead of its duration. + /// A track entry to allow further customization of animation playback. References to the track entry must not be kept + /// after the event occurs. + /// + public TrackEntry AddEmptyAnimation(int trackIndex, float mixDuration, float delay) + { + TrackEntry entry = AddAnimation(trackIndex, AnimationState.EmptyAnimation, false, delay); + if (delay <= 0) entry.delay += entry.mixDuration - mixDuration; + entry.mixDuration = mixDuration; + entry.trackEnd = mixDuration; + return entry; + } + + /// + /// Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix + /// duration. + public void SetEmptyAnimations(float mixDuration) + { + bool oldDrainDisabled = queue.drainDisabled; + queue.drainDisabled = true; + TrackEntry[] tracksItems = tracks.Items; + for (int i = 0, n = tracks.Count; i < n; i++) + { + TrackEntry current = tracksItems[i]; + if (current != null) SetEmptyAnimation(current.trackIndex, mixDuration); + } + queue.drainDisabled = oldDrainDisabled; + queue.Drain(); + } + + private TrackEntry ExpandToIndex(int index) + { + if (index < tracks.Count) return tracks.Items[index]; + tracks.Resize(index + 1); + return null; + } + + /// Object-pooling version of new TrackEntry. Obtain an unused TrackEntry from the pool and clear/initialize its values. + /// May be null. + private TrackEntry NewTrackEntry(int trackIndex, Animation animation, bool loop, TrackEntry last) + { + TrackEntry entry = trackEntryPool.Obtain(); + entry.trackIndex = trackIndex; + entry.animation = animation; + entry.loop = loop; + entry.holdPrevious = false; + + entry.eventThreshold = 0; + entry.attachmentThreshold = 0; + entry.drawOrderThreshold = 0; + + entry.animationStart = 0; + entry.animationEnd = animation.Duration; + entry.animationLast = -1; + entry.nextAnimationLast = -1; + + entry.delay = 0; + entry.trackTime = 0; + entry.trackLast = -1; + entry.nextTrackLast = -1; + entry.trackEnd = float.MaxValue; + entry.timeScale = 1; + + entry.alpha = 1; + entry.interruptAlpha = 1; + entry.mixTime = 0; + entry.mixDuration = last == null ? 0 : data.GetMix(last.animation, animation); + entry.mixBlend = MixBlend.Replace; + return entry; + } + + /// Removes the next entry and all entries after it for the specified entry. + public void ClearNext(TrackEntry entry) + { + TrackEntry next = entry.next; + while (next != null) + { + queue.Dispose(next); + next = next.next; + } + entry.next = null; + } + + private void AnimationsChanged() + { + animationsChanged = false; + + // Process in the order that animations are applied. + propertyIds.Clear(); + int n = tracks.Count; + TrackEntry[] tracksItems = tracks.Items; + for (int i = 0; i < n; i++) + { + TrackEntry entry = tracksItems[i]; + if (entry == null) continue; + while (entry.mixingFrom != null) // Move to last entry, then iterate in reverse. + entry = entry.mixingFrom; + do + { + if (entry.mixingTo == null || entry.mixBlend != MixBlend.Add) ComputeHold(entry); + entry = entry.mixingTo; + } while (entry != null); + } + } + + private void ComputeHold(TrackEntry entry) + { + TrackEntry to = entry.mixingTo; + Timeline[] timelines = entry.animation.timelines.Items; + int timelinesCount = entry.animation.timelines.Count; + int[] timelineMode = entry.timelineMode.Resize(timelinesCount).Items; + entry.timelineHoldMix.Clear(); + TrackEntry[] timelineHoldMix = entry.timelineHoldMix.Resize(timelinesCount).Items; + HashSet propertyIds = this.propertyIds; + + if (to != null && to.holdPrevious) + { + for (int i = 0; i < timelinesCount; i++) + timelineMode[i] = propertyIds.AddAll(timelines[i].PropertyIds) ? AnimationState.HoldFirst : AnimationState.HoldSubsequent; + + return; + } + + // outer: + for (int i = 0; i < timelinesCount; i++) + { + Timeline timeline = timelines[i]; + String[] ids = timeline.PropertyIds; + if (!propertyIds.AddAll(ids)) + timelineMode[i] = AnimationState.Subsequent; + else if (to == null || timeline is AttachmentTimeline || timeline is DrawOrderTimeline + || timeline is EventTimeline || !to.animation.HasTimeline(ids)) + { + timelineMode[i] = AnimationState.First; + } + else + { + for (TrackEntry next = to.mixingTo; next != null; next = next.mixingTo) + { + if (next.animation.HasTimeline(ids)) continue; + if (next.mixDuration > 0) + { + timelineMode[i] = AnimationState.HoldMix; + timelineHoldMix[i] = next; + goto continue_outer; // continue outer; + } + break; + } + timelineMode[i] = AnimationState.HoldFirst; + } + continue_outer: { } + } + } + + /// The track entry for the animation currently playing on the track, or null if no animation is currently playing. + public TrackEntry GetCurrent(int trackIndex) + { + if (trackIndex >= tracks.Count) return null; + return tracks.Items[trackIndex]; + } + + /// Discards all listener notifications that have not yet been delivered. This can be useful to call from an + /// AnimationState event subscriber when it is known that further notifications that may have been already queued for delivery + /// are not wanted because new animations are being set. + public void ClearListenerNotifications() + { + queue.Clear(); + } + + /// + /// Multiplier for the delta time when the animation state is updated, causing time for all animations and mixes to play slower + /// or faster. Defaults to 1. + /// + /// See TrackEntry for affecting a single animation. + /// + public float TimeScale { get { return timeScale; } set { timeScale = value; } } + + /// The AnimationStateData to look up mix durations. + public AnimationStateData Data + { + get + { + return data; + } + set + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + this.data = value; + } + } + + /// A list of tracks that have animations, which may contain nulls. + public ExposedList Tracks { get { return tracks; } } + + override public string ToString() + { + var buffer = new System.Text.StringBuilder(); + TrackEntry[] tracksItems = tracks.Items; + for (int i = 0, n = tracks.Count; i < n; i++) + { + TrackEntry entry = tracksItems[i]; + if (entry == null) continue; + if (buffer.Length > 0) buffer.Append(", "); + buffer.Append(entry.ToString()); + } + if (buffer.Length == 0) return ""; + return buffer.ToString(); + } + } + + /// + /// + /// Stores settings and other state for the playback of an animation on an track. + /// + /// References to a track entry must not be kept after the event occurs. + /// + public class TrackEntry : Pool.IPoolable + { + internal Animation animation; + + internal TrackEntry previous, next, mixingFrom, mixingTo; + // difference to libgdx reference: delegates are used for event callbacks instead of 'AnimationStateListener listener'. + /// See + /// API Reference documentation pages here for details. Usage in C# and spine-unity is explained + /// here + /// on the spine-unity documentation pages. + public event AnimationState.TrackEntryDelegate Start, Interrupt, End, Dispose, Complete; + public event AnimationState.TrackEntryEventDelegate Event; + internal void OnStart() { if (Start != null) Start(this); } + internal void OnInterrupt() { if (Interrupt != null) Interrupt(this); } + internal void OnEnd() { if (End != null) End(this); } + internal void OnDispose() { if (Dispose != null) Dispose(this); } + internal void OnComplete() { if (Complete != null) Complete(this); } + internal void OnEvent(Event e) { if (Event != null) Event(this, e); } + + internal int trackIndex; + + internal bool loop, holdPrevious, reverse; + internal float eventThreshold, attachmentThreshold, drawOrderThreshold; + internal float animationStart, animationEnd, animationLast, nextAnimationLast; + internal float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale = 1f; + internal float alpha, mixTime, mixDuration, interruptAlpha, totalAlpha; + internal MixBlend mixBlend = MixBlend.Replace; + internal readonly ExposedList timelineMode = new ExposedList(); + internal readonly ExposedList timelineHoldMix = new ExposedList(); + internal readonly ExposedList timelinesRotation = new ExposedList(); + + // IPoolable.Reset() + public void Reset() + { + previous = null; + next = null; + mixingFrom = null; + mixingTo = null; + animation = null; + // replaces 'listener = null;' since delegates are used for event callbacks + Start = null; + Interrupt = null; + End = null; + Dispose = null; + Complete = null; + Event = null; + timelineMode.Clear(); + timelineHoldMix.Clear(); + timelinesRotation.Clear(); + } + + /// The index of the track where this entry is either current or queued. + /// + public int TrackIndex { get { return trackIndex; } } + + /// The animation to apply for this track entry. + public Animation Animation { get { return animation; } } + + /// + /// If true, the animation will repeat. If false it will not, instead its last frame is applied if played beyond its + /// duration. + public bool Loop { get { return loop; } set { loop = value; } } + + /// + /// + /// Seconds to postpone playing the animation. When this track entry is the current track entry, Delay + /// postpones incrementing the . When this track entry is queued, Delay is the time from + /// the start of the previous animation to when this track entry will become the current track entry (ie when the previous + /// track entry >= this track entry's Delay). + /// + /// affects the delay. + /// + /// When using with a delay <= 0, the delay + /// is set using the mix duration from the . If is set afterward, the delay + /// may need to be adjusted. + public float Delay { get { return delay; } set { delay = value; } } + + /// + /// Current time in seconds this track entry has been the current track entry. The track time determines + /// . The track time can be set to start the animation at a time other than 0, without affecting + /// looping. + public float TrackTime { get { return trackTime; } set { trackTime = value; } } + + /// + /// + /// The track time in seconds when this animation will be removed from the track. Defaults to the highest possible float + /// value, meaning the animation will be applied until a new animation is set or the track is cleared. If the track end time + /// is reached, no other animations are queued for playback, and mixing from any previous animations is complete, then the + /// properties keyed by the animation are set to the setup pose and the track is cleared. + /// + /// It may be desired to use rather than have the animation + /// abruptly cease being applied. + /// + public float TrackEnd { get { return trackEnd; } set { trackEnd = value; } } + + /// + /// If this track entry is non-looping, the track time in seconds when is reached, or the current + /// if it has already been reached. If this track entry is looping, the track time when this + /// animation will reach its next (the next loop completion). + public float TrackComplete + { + get + { + 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. + } + } + + /// + /// + /// Seconds when this animation starts, both initially and after looping. Defaults to 0. + /// + /// When changing the AnimationStart time, it often makes sense to set to the same + /// value to prevent timeline keys before the start time from triggering. + /// + public float AnimationStart { get { return animationStart; } set { animationStart = value; } } + + /// + /// Seconds for the last frame of this animation. Non-looping animations won't play past this time. Looping animations will + /// loop back to at this time. Defaults to the animation . + /// + public float AnimationEnd { get { return animationEnd; } set { animationEnd = value; } } + + /// + /// 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 AnimationLast time (exclusive) and + /// AnimationTime (inclusive). Defaults to -1 to ensure triggers on frame 0 happen the first time this animation + /// is applied. + public float AnimationLast + { + get { return animationLast; } + set + { + animationLast = value; + nextAnimationLast = value; + } + } + + /// + /// Uses to compute the AnimationTime, which is between + /// and . When the TrackTime is 0, the AnimationTime is equal to the + /// AnimationStart time. + /// + public float AnimationTime + { + get + { + if (loop) + { + float duration = animationEnd - animationStart; + if (duration == 0) return animationStart; + return (trackTime % duration) + animationStart; + } + return Math.Min(trackTime + animationStart, animationEnd); + } + } + + /// + /// + /// Multiplier for the delta time when this track entry is updated, causing time for this animation to pass slower or + /// faster. Defaults to 1. + /// + /// Values < 0 are not supported. To play an animation in reverse, use . + /// + /// is not affected by track entry time scale, so may need to be adjusted to + /// match the animation speed. + /// + /// When using with a Delay <= 0, the + /// is set using the mix duration from the , assuming time scale to be 1. If + /// the time scale is not 1, the delay may need to be adjusted. + /// + /// See AnimationState for affecting all animations. + /// + public float TimeScale { get { return timeScale; } set { timeScale = value; } } + + /// + /// + /// Values < 1 mix this animation with the skeleton's current pose (usually the pose resulting from lower tracks). Defaults + /// to 1, which overwrites the skeleton's current pose with this animation. + /// + /// Typically track 0 is used to completely pose the skeleton, then alpha is 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. + /// + public float Alpha { get { return alpha; } set { alpha = value; } } + + public float InterruptAlpha { get { return interruptAlpha; } } + + /// + /// When the mix percentage ( / ) is less than the + /// EventThreshold, event timelines are applied while this animation is being mixed out. Defaults to 0, so event + /// timelines are not applied while this animation is being mixed out. + /// + public float EventThreshold { get { return eventThreshold; } set { eventThreshold = value; } } + + /// + /// When the mix percentage ( / ) is less than the + /// AttachmentThreshold, attachment timelines are applied while this animation is being mixed out. Defaults to + /// 0, so attachment timelines are not applied while this animation is being mixed out. + /// + public float AttachmentThreshold { get { return attachmentThreshold; } set { attachmentThreshold = value; } } + + /// + /// When the mix percentage ( / ) is less than the + /// DrawOrderThreshold, draw order timelines are applied while this animation is being mixed out. Defaults to 0, + /// so draw order timelines are not applied while this animation is being mixed out. + /// + public float DrawOrderThreshold { get { return drawOrderThreshold; } set { drawOrderThreshold = value; } } + + /// + /// The animation queued to start after this animation, or null if there is none. next makes up a doubly linked + /// list. + /// + /// See to truncate the list. + public TrackEntry Next { get { return next; } } + + /// + /// The animation queued to play before this animation, or null. previous makes up a doubly linked list. + public TrackEntry Previous { get { return previous; } } + + /// + /// Returns true if at least one loop has been completed. + /// + public bool IsComplete + { + get { return trackTime >= animationEnd - animationStart; } + } + + /// + /// Seconds from 0 to the when mixing from the previous animation to this animation. May be + /// slightly more than MixDuration when the mix is complete. + public float MixTime { get { return mixTime; } set { mixTime = value; } } + + /// + /// + /// 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 MixDuration can be set manually rather than use the value from + /// . In that case, the MixDuration can be set for a new + /// track entry only before is first called. + /// + /// When using with a Delay <= 0, the + /// is set using the mix duration from the . If mixDuration is set + /// afterward, the delay may need to be adjusted. For example: + /// entry.Delay = entry.previous.TrackComplete - entry.MixDuration; + /// + public float MixDuration { get { return mixDuration; } set { mixDuration = value; } } + + /// + /// + /// Controls how properties keyed in the animation are mixed with lower tracks. Defaults to . + /// + /// Track entries on track 0 ignore this setting and always use . + /// + /// The MixBlend can be set for a new track entry only before is first + /// called. + /// + public MixBlend MixBlend { get { return mixBlend; } set { mixBlend = value; } } + + /// + /// 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 linked list. + public TrackEntry MixingFrom { get { return mixingFrom; } } + + /// + /// The track entry for the next animation when mixing from this animation to the next animation, or null if no mixing is + /// currently occuring. When mixing to multiple animations, MixingTo makes up a linked list. + public TrackEntry MixingTo { get { return mixingTo; } } + + /// + /// + /// 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. + /// + public bool HoldPrevious { get { return holdPrevious; } set { holdPrevious = value; } } + + /// + /// If true, the animation will be applied in reverse. Events are not fired when an animation is applied in reverse. + public bool Reverse { get { return reverse; } set { reverse = value; } } + + /// Returns true if this entry is for the empty animation. See , + /// , and . + public bool IsEmptyAnimation { get { return animation == AnimationState.EmptyAnimation; } } + + /// + /// + /// 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 and starting animations on other tracks. + /// + /// Mixing with 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. + /// + public void ResetRotationDirections() + { + timelinesRotation.Clear(); + } + + override public string ToString() + { + return animation == null ? "" : animation.name; + } + + // Note: This method is required by SpineAnimationStateMixerBehaviour, + // which is part of the timeline extension package. Thus the internal member variable + // nextTrackLast is not accessible. We favor providing this method + // over exposing nextTrackLast as public property, which would rather confuse users. + public void AllowImmediateQueue() + { + if (nextTrackLast < 0) nextTrackLast = 0; + } + } + + class EventQueue + { + private readonly List eventQueueEntries = new List(); + internal bool drainDisabled; + + private readonly AnimationState state; + private readonly Pool trackEntryPool; + internal event Action AnimationsChanged; + + internal EventQueue(AnimationState state, Action HandleAnimationsChanged, Pool trackEntryPool) + { + this.state = state; + this.AnimationsChanged += HandleAnimationsChanged; + this.trackEntryPool = trackEntryPool; + } + + internal void Start(TrackEntry entry) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.Start, entry)); + if (AnimationsChanged != null) AnimationsChanged(); + } + + internal void Interrupt(TrackEntry entry) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.Interrupt, entry)); + } + + internal void End(TrackEntry entry) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.End, entry)); + if (AnimationsChanged != null) AnimationsChanged(); + } + + internal void Dispose(TrackEntry entry) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.Dispose, entry)); + } + + internal void Complete(TrackEntry entry) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.Complete, entry)); + } + + internal void Event(TrackEntry entry, Event e) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.Event, entry, e)); + } + + /// Raises all events in the queue and drains the queue. + internal void Drain() + { + if (drainDisabled) return; + drainDisabled = true; + + List eventQueueEntries = this.eventQueueEntries; + AnimationState state = this.state; + + // Don't cache eventQueueEntries.Count so callbacks can queue their own events (eg, call SetAnimation in AnimationState_Complete). + for (int i = 0; i < eventQueueEntries.Count; i++) + { + EventQueueEntry queueEntry = eventQueueEntries[i]; + TrackEntry trackEntry = queueEntry.entry; + + switch (queueEntry.type) + { + case EventType.Start: + trackEntry.OnStart(); + state.OnStart(trackEntry); + break; + case EventType.Interrupt: + trackEntry.OnInterrupt(); + state.OnInterrupt(trackEntry); + break; + case EventType.End: + trackEntry.OnEnd(); + state.OnEnd(trackEntry); + goto case EventType.Dispose; // Fall through. (C#) + case EventType.Dispose: + trackEntry.OnDispose(); + state.OnDispose(trackEntry); + trackEntryPool.Free(trackEntry); + break; + case EventType.Complete: + trackEntry.OnComplete(); + state.OnComplete(trackEntry); + break; + case EventType.Event: + trackEntry.OnEvent(queueEntry.e); + state.OnEvent(trackEntry, queueEntry.e); + break; + } + } + eventQueueEntries.Clear(); + + drainDisabled = false; + } + + internal void Clear() + { + eventQueueEntries.Clear(); + } + + struct EventQueueEntry + { + public EventType type; + public TrackEntry entry; + public Event e; + + public EventQueueEntry(EventType eventType, TrackEntry trackEntry, Event e = null) + { + this.type = eventType; + this.entry = trackEntry; + this.e = e; + } + } + + enum EventType + { + Start, Interrupt, End, Dispose, Complete, Event + } + } + + class Pool where T : class, new() + { + public readonly int max; + readonly Stack freeObjects; + + public int Count { get { return freeObjects.Count; } } + public int Peak { get; private set; } + + public Pool(int initialCapacity = 16, int max = int.MaxValue) + { + freeObjects = new Stack(initialCapacity); + this.max = max; + } + + public T Obtain() + { + return freeObjects.Count == 0 ? new T() : freeObjects.Pop(); + } + + public void Free(T obj) + { + if (obj == null) throw new ArgumentNullException("obj", "obj cannot be null"); + if (freeObjects.Count < max) + { + freeObjects.Push(obj); + Peak = Math.Max(Peak, freeObjects.Count); + } + Reset(obj); + } + + public void Clear() + { + freeObjects.Clear(); + } + + protected void Reset(T obj) + { + var poolable = obj as IPoolable; + if (poolable != null) poolable.Reset(); + } + + public interface IPoolable + { + void Reset(); + } + } + + public static class HashSetExtensions + { + public static bool AddAll(this HashSet set, T[] addSet) + { + bool anyItemAdded = false; + for (int i = 0, n = addSet.Length; i < n; ++i) + { + T item = addSet[i]; + anyItemAdded |= set.Add(item); + } + return anyItemAdded; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/AnimationStateData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/AnimationStateData.cs index 9fd9b26..4223876 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/AnimationStateData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/AnimationStateData.cs @@ -30,85 +30,97 @@ using System; using System.Collections.Generic; -namespace Spine4_0_64 { +namespace Spine4_0_64 +{ - /// Stores mix (crossfade) durations to be applied when AnimationState animations are changed. - public class AnimationStateData { - internal SkeletonData skeletonData; - readonly Dictionary animationToMixTime = new Dictionary(AnimationPairComparer.Instance); - internal float defaultMix; + /// Stores mix (crossfade) durations to be applied when AnimationState animations are changed. + public class AnimationStateData + { + internal SkeletonData skeletonData; + readonly Dictionary animationToMixTime = new Dictionary(AnimationPairComparer.Instance); + internal float defaultMix; - /// The SkeletonData to look up animations when they are specified by name. - public SkeletonData SkeletonData { get { return skeletonData; } } + /// The SkeletonData to look up animations when they are specified by name. + public SkeletonData SkeletonData { get { return skeletonData; } } - /// - /// The mix duration to use when no mix duration has been specifically defined between two animations. - public float DefaultMix { get { return defaultMix; } set { defaultMix = value; } } + /// + /// The mix duration to use when no mix duration has been specifically defined between two animations. + public float DefaultMix { get { return defaultMix; } set { defaultMix = value; } } - public AnimationStateData (SkeletonData skeletonData) { - if (skeletonData == null) throw new ArgumentException("skeletonData cannot be null.", "skeletonData"); - this.skeletonData = skeletonData; - } + public AnimationStateData(SkeletonData skeletonData) + { + if (skeletonData == null) throw new ArgumentException("skeletonData cannot be null.", "skeletonData"); + this.skeletonData = skeletonData; + } - /// Sets a mix duration by animation names. - public void SetMix (string fromName, string toName, float duration) { - Animation from = skeletonData.FindAnimation(fromName); - if (from == null) throw new ArgumentException("Animation not found: " + fromName, "fromName"); - Animation to = skeletonData.FindAnimation(toName); - if (to == null) throw new ArgumentException("Animation not found: " + toName, "toName"); - SetMix(from, to, duration); - } + /// Sets a mix duration by animation names. + public void SetMix(string fromName, string toName, float duration) + { + Animation from = skeletonData.FindAnimation(fromName); + if (from == null) throw new ArgumentException("Animation not found: " + fromName, "fromName"); + Animation to = skeletonData.FindAnimation(toName); + if (to == null) throw new ArgumentException("Animation not found: " + toName, "toName"); + SetMix(from, to, duration); + } - /// Sets a mix duration when changing from the specified animation to the other. - /// See TrackEntry.MixDuration. - public void SetMix (Animation from, Animation to, float duration) { - if (from == null) throw new ArgumentNullException("from", "from cannot be null."); - if (to == null) throw new ArgumentNullException("to", "to cannot be null."); - AnimationPair key = new AnimationPair(from, to); - animationToMixTime.Remove(key); - animationToMixTime.Add(key, duration); - } + /// Sets a mix duration when changing from the specified animation to the other. + /// See TrackEntry.MixDuration. + public void SetMix(Animation from, Animation to, float duration) + { + if (from == null) throw new ArgumentNullException("from", "from cannot be null."); + if (to == null) throw new ArgumentNullException("to", "to cannot be null."); + AnimationPair key = new AnimationPair(from, to); + animationToMixTime.Remove(key); + animationToMixTime.Add(key, 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. - /// - public float GetMix (Animation from, Animation to) { - if (from == null) throw new ArgumentNullException("from", "from cannot be null."); - if (to == null) throw new ArgumentNullException("to", "to cannot be null."); - AnimationPair key = new AnimationPair(from, to); - float duration; - if (animationToMixTime.TryGetValue(key, out duration)) return duration; - return defaultMix; - } + /// + /// The mix duration to use when changing from the specified animation to the other, + /// or the DefaultMix if no mix duration has been set. + /// + public float GetMix(Animation from, Animation to) + { + if (from == null) throw new ArgumentNullException("from", "from cannot be null."); + if (to == null) throw new ArgumentNullException("to", "to cannot be null."); + AnimationPair key = new AnimationPair(from, to); + float duration; + if (animationToMixTime.TryGetValue(key, out duration)) return duration; + return defaultMix; + } - public struct AnimationPair { - public readonly Animation a1; - public readonly Animation a2; + public struct AnimationPair + { + public readonly Animation a1; + public readonly Animation a2; - public AnimationPair (Animation a1, Animation a2) { - this.a1 = a1; - this.a2 = a2; - } + public AnimationPair(Animation a1, Animation a2) + { + this.a1 = a1; + this.a2 = a2; + } - public override string ToString () { - return a1.name + "->" + a2.name; - } - } + public override string ToString() + { + return a1.name + "->" + a2.name; + } + } - // Avoids boxing in the dictionary. - public class AnimationPairComparer : IEqualityComparer { - public static readonly AnimationPairComparer Instance = new AnimationPairComparer(); + // Avoids boxing in the dictionary. + public class AnimationPairComparer : IEqualityComparer + { + public static readonly AnimationPairComparer Instance = new AnimationPairComparer(); - bool IEqualityComparer.Equals (AnimationPair x, AnimationPair y) { - return ReferenceEquals(x.a1, y.a1) && ReferenceEquals(x.a2, y.a2); - } + bool IEqualityComparer.Equals(AnimationPair x, AnimationPair y) + { + return ReferenceEquals(x.a1, y.a1) && ReferenceEquals(x.a2, y.a2); + } - int IEqualityComparer.GetHashCode (AnimationPair obj) { - // from Tuple.CombineHashCodes // return (((h1 << 5) + h1) ^ h2); - int h1 = obj.a1.GetHashCode(); - return (((h1 << 5) + h1) ^ obj.a2.GetHashCode()); - } - } - } + int IEqualityComparer.GetHashCode(AnimationPair obj) + { + // from Tuple.CombineHashCodes // return (((h1 << 5) + h1) ^ h2); + int h1 = obj.a1.GetHashCode(); + return (((h1 << 5) + h1) ^ obj.a2.GetHashCode()); + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Atlas.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Atlas.cs index 906b8c8..64b4861 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Atlas.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Atlas.cs @@ -35,31 +35,34 @@ using System.Collections.Generic; using System.Globalization; using System.IO; -using System.Reflection; #if WINDOWS_STOREAPP using System.Threading.Tasks; using Windows.Storage; #endif -namespace Spine4_0_64 { - public class Atlas : IEnumerable { - readonly List pages = new List(); - List regions = new List(); - TextureLoader textureLoader; +namespace Spine4_0_64 +{ + public class Atlas : IEnumerable + { + readonly List pages = new List(); + List regions = new List(); + TextureLoader textureLoader; - #region IEnumerable implementation - public IEnumerator GetEnumerator () { - return regions.GetEnumerator(); - } + #region IEnumerable implementation + public IEnumerator GetEnumerator() + { + return regions.GetEnumerator(); + } - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () { - return regions.GetEnumerator(); - } - #endregion + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return regions.GetEnumerator(); + } + #endregion - public List Regions { get { return regions; } } - public List Pages { get { return pages; } } + public List Regions { get { return regions; } } + public List Pages { get { return pages; } } #if !(IS_UNITY) #if WINDOWS_STOREAPP @@ -82,283 +85,334 @@ public Atlas(string path, TextureLoader textureLoader) { this.ReadFile(path, textureLoader).Wait(); } #else - public Atlas (string path, TextureLoader textureLoader) { + public Atlas(string path, TextureLoader textureLoader) + { #if WINDOWS_PHONE Stream stream = Microsoft.Xna.Framework.TitleContainer.OpenStream(path); using (StreamReader reader = new StreamReader(stream)) { #else - using (StreamReader reader = new StreamReader(path)) { + using (StreamReader reader = new StreamReader(path)) + { #endif // WINDOWS_PHONE - try { - Atlas atlas = new Atlas(reader, Path.GetDirectoryName(path), textureLoader); - this.pages = atlas.pages; - this.regions = atlas.regions; - this.textureLoader = atlas.textureLoader; - } catch (Exception ex) { - throw new Exception("Error reading atlas file: " + path, ex); - } - } - } + try + { + Atlas atlas = new Atlas(reader, Path.GetDirectoryName(path), textureLoader); + this.pages = atlas.pages; + this.regions = atlas.regions; + this.textureLoader = atlas.textureLoader; + } + catch (Exception ex) + { + throw new Exception("Error reading atlas file: " + path, ex); + } + } + } #endif // WINDOWS_STOREAPP #endif - public Atlas (List pages, List regions) { - if (pages == null) throw new ArgumentNullException("pages", "pages cannot be null."); - if (regions == null) throw new ArgumentNullException("regions", "regions cannot be null."); - this.pages = pages; - this.regions = regions; - this.textureLoader = null; - } + public Atlas(List pages, List regions) + { + if (pages == null) throw new ArgumentNullException("pages", "pages cannot be null."); + if (regions == null) throw new ArgumentNullException("regions", "regions cannot be null."); + this.pages = pages; + this.regions = regions; + this.textureLoader = null; + } - public Atlas (TextReader reader, string imagesDir, TextureLoader textureLoader) { - if (reader == null) throw new ArgumentNullException("reader", "reader cannot be null."); - if (imagesDir == null) throw new ArgumentNullException("imagesDir", "imagesDir cannot be null."); - if (textureLoader == null) throw new ArgumentNullException("textureLoader", "textureLoader cannot be null."); - this.textureLoader = textureLoader; + public Atlas(TextReader reader, string imagesDir, TextureLoader textureLoader) + { + if (reader == null) throw new ArgumentNullException("reader", "reader cannot be null."); + if (imagesDir == null) throw new ArgumentNullException("imagesDir", "imagesDir cannot be null."); + if (textureLoader == null) throw new ArgumentNullException("textureLoader", "textureLoader cannot be null."); + this.textureLoader = textureLoader; - string[] entry = new string[5]; - AtlasPage page = null; - AtlasRegion region = null; + string[] entry = new string[5]; + AtlasPage page = null; + AtlasRegion region = null; - var pageFields = new Dictionary(5); - pageFields.Add("size", () => { - page.width = int.Parse(entry[1], CultureInfo.InvariantCulture); - page.height = int.Parse(entry[2], CultureInfo.InvariantCulture); - }); - pageFields.Add("format", () => { - page.format = (Format)Enum.Parse(typeof(Format), entry[1], false); - }); - pageFields.Add("filter", () => { - page.minFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), entry[1], false); - page.magFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), entry[2], false); - }); - pageFields.Add("repeat", () => { - if (entry[1].IndexOf('x') != -1) page.uWrap = TextureWrap.Repeat; - if (entry[1].IndexOf('y') != -1) page.vWrap = TextureWrap.Repeat; - }); - pageFields.Add("pma", () => { - page.pma = entry[1] == "true"; - }); + var pageFields = new Dictionary(5); + pageFields.Add("size", () => + { + page.width = int.Parse(entry[1], CultureInfo.InvariantCulture); + page.height = int.Parse(entry[2], CultureInfo.InvariantCulture); + }); + pageFields.Add("format", () => + { + page.format = (Format)Enum.Parse(typeof(Format), entry[1], false); + }); + pageFields.Add("filter", () => + { + page.minFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), entry[1], false); + page.magFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), entry[2], false); + }); + pageFields.Add("repeat", () => + { + if (entry[1].IndexOf('x') != -1) page.uWrap = TextureWrap.Repeat; + if (entry[1].IndexOf('y') != -1) page.vWrap = TextureWrap.Repeat; + }); + pageFields.Add("pma", () => + { + page.pma = entry[1] == "true"; + }); - var regionFields = new Dictionary(8); - regionFields.Add("xy", () => { // Deprecated, use bounds. - region.x = int.Parse(entry[1], CultureInfo.InvariantCulture); - region.y = int.Parse(entry[2], CultureInfo.InvariantCulture); - }); - regionFields.Add("size", () => { // Deprecated, use bounds. - region.width = int.Parse(entry[1], CultureInfo.InvariantCulture); - region.height = int.Parse(entry[2], CultureInfo.InvariantCulture); - }); - regionFields.Add("bounds", () => { - region.x = int.Parse(entry[1], CultureInfo.InvariantCulture); - region.y = int.Parse(entry[2], CultureInfo.InvariantCulture); - region.width = int.Parse(entry[3], CultureInfo.InvariantCulture); - region.height = int.Parse(entry[4], CultureInfo.InvariantCulture); - }); - regionFields.Add("offset", () => { // Deprecated, use offsets. - region.offsetX = int.Parse(entry[1], CultureInfo.InvariantCulture); - region.offsetY = int.Parse(entry[2], CultureInfo.InvariantCulture); - }); - regionFields.Add("orig", () => { // Deprecated, use offsets. - region.originalWidth = int.Parse(entry[1], CultureInfo.InvariantCulture); - region.originalHeight = int.Parse(entry[2], CultureInfo.InvariantCulture); - }); - regionFields.Add("offsets", () => { - region.offsetX = int.Parse(entry[1], CultureInfo.InvariantCulture); - region.offsetY = int.Parse(entry[2], CultureInfo.InvariantCulture); - region.originalWidth = int.Parse(entry[3], CultureInfo.InvariantCulture); - region.originalHeight = int.Parse(entry[4], CultureInfo.InvariantCulture); - }); - regionFields.Add("rotate", () => { - string value = entry[1]; - if (value == "true") - region.degrees = 90; - else if (value != "false") - region.degrees = int.Parse(value, CultureInfo.InvariantCulture); - }); - regionFields.Add("index", () => { - region.index = int.Parse(entry[1], CultureInfo.InvariantCulture); - }); + var regionFields = new Dictionary(8); + regionFields.Add("xy", () => + { // Deprecated, use bounds. + region.x = int.Parse(entry[1], CultureInfo.InvariantCulture); + region.y = int.Parse(entry[2], CultureInfo.InvariantCulture); + }); + regionFields.Add("size", () => + { // Deprecated, use bounds. + region.width = int.Parse(entry[1], CultureInfo.InvariantCulture); + region.height = int.Parse(entry[2], CultureInfo.InvariantCulture); + }); + regionFields.Add("bounds", () => + { + region.x = int.Parse(entry[1], CultureInfo.InvariantCulture); + region.y = int.Parse(entry[2], CultureInfo.InvariantCulture); + region.width = int.Parse(entry[3], CultureInfo.InvariantCulture); + region.height = int.Parse(entry[4], CultureInfo.InvariantCulture); + }); + regionFields.Add("offset", () => + { // Deprecated, use offsets. + region.offsetX = int.Parse(entry[1], CultureInfo.InvariantCulture); + region.offsetY = int.Parse(entry[2], CultureInfo.InvariantCulture); + }); + regionFields.Add("orig", () => + { // Deprecated, use offsets. + region.originalWidth = int.Parse(entry[1], CultureInfo.InvariantCulture); + region.originalHeight = int.Parse(entry[2], CultureInfo.InvariantCulture); + }); + regionFields.Add("offsets", () => + { + region.offsetX = int.Parse(entry[1], CultureInfo.InvariantCulture); + region.offsetY = int.Parse(entry[2], CultureInfo.InvariantCulture); + region.originalWidth = int.Parse(entry[3], CultureInfo.InvariantCulture); + region.originalHeight = int.Parse(entry[4], CultureInfo.InvariantCulture); + }); + regionFields.Add("rotate", () => + { + string value = entry[1]; + if (value == "true") + region.degrees = 90; + else if (value != "false") + region.degrees = int.Parse(value, CultureInfo.InvariantCulture); + }); + regionFields.Add("index", () => + { + region.index = int.Parse(entry[1], CultureInfo.InvariantCulture); + }); - string line = reader.ReadLine(); - // Ignore empty lines before first entry. - while (line != null && line.Trim().Length == 0) - line = reader.ReadLine(); - // Header entries. - while (true) { - if (line == null || line.Trim().Length == 0) break; - if (ReadEntry(entry, line) == 0) break; // Silently ignore all header fields. - line = reader.ReadLine(); - } - // Page and region entries. - List names = null; - List values = null; - while (true) { - if (line == null) break; - if (line.Trim().Length == 0) { - page = null; - line = reader.ReadLine(); - } else if (page == null) { - page = new AtlasPage(); - page.name = line.Trim(); - while (true) { - if (ReadEntry(entry, line = reader.ReadLine()) == 0) break; - Action field; - if (pageFields.TryGetValue(entry[0], out field)) field(); // Silently ignore unknown page fields. - } - textureLoader.Load(page, Path.Combine(imagesDir, page.name)); - pages.Add(page); - } else { - region = new AtlasRegion(); - region.page = page; - region.name = line; - while (true) { - int count = ReadEntry(entry, line = reader.ReadLine()); - if (count == 0) break; - Action field; - if (regionFields.TryGetValue(entry[0], out field)) - field(); - else { - if (names == null) { - names = new List(8); - values = new List(8); - } - names.Add(entry[0]); - int[] entryValues = new int[count]; - for (int i = 0; i < count; i++) - int.TryParse(entry[i + 1], NumberStyles.Any, CultureInfo.InvariantCulture, out entryValues[i]); // Silently ignore non-integer values. - values.Add(entryValues); - } - } - if (region.originalWidth == 0 && region.originalHeight == 0) { - region.originalWidth = region.width; - region.originalHeight = region.height; - } - if (names != null && names.Count > 0) { - region.names = names.ToArray(); - region.values = values.ToArray(); - names.Clear(); - values.Clear(); - } - region.u = region.x / (float)page.width; - region.v = region.y / (float)page.height; - if (region.degrees == 90) { - region.u2 = (region.x + region.height) / (float)page.width; - region.v2 = (region.y + region.width) / (float)page.height; - } else { - region.u2 = (region.x + region.width) / (float)page.width; - region.v2 = (region.y + region.height) / (float)page.height; - } - regions.Add(region); - } - } - } + string line = reader.ReadLine(); + // Ignore empty lines before first entry. + while (line != null && line.Trim().Length == 0) + line = reader.ReadLine(); + // Header entries. + while (true) + { + if (line == null || line.Trim().Length == 0) break; + if (ReadEntry(entry, line) == 0) break; // Silently ignore all header fields. + line = reader.ReadLine(); + } + // Page and region entries. + List names = null; + List values = null; + while (true) + { + if (line == null) break; + if (line.Trim().Length == 0) + { + page = null; + line = reader.ReadLine(); + } + else if (page == null) + { + page = new AtlasPage(); + page.name = line.Trim(); + while (true) + { + if (ReadEntry(entry, line = reader.ReadLine()) == 0) break; + Action field; + if (pageFields.TryGetValue(entry[0], out field)) field(); // Silently ignore unknown page fields. + } + textureLoader.Load(page, Path.Combine(imagesDir, page.name)); + pages.Add(page); + } + else + { + region = new AtlasRegion(); + region.page = page; + region.name = line; + while (true) + { + int count = ReadEntry(entry, line = reader.ReadLine()); + if (count == 0) break; + Action field; + if (regionFields.TryGetValue(entry[0], out field)) + field(); + else + { + if (names == null) + { + names = new List(8); + values = new List(8); + } + names.Add(entry[0]); + int[] entryValues = new int[count]; + for (int i = 0; i < count; i++) + int.TryParse(entry[i + 1], NumberStyles.Any, CultureInfo.InvariantCulture, out entryValues[i]); // Silently ignore non-integer values. + values.Add(entryValues); + } + } + if (region.originalWidth == 0 && region.originalHeight == 0) + { + region.originalWidth = region.width; + region.originalHeight = region.height; + } + if (names != null && names.Count > 0) + { + region.names = names.ToArray(); + region.values = values.ToArray(); + names.Clear(); + values.Clear(); + } + region.u = region.x / (float)page.width; + region.v = region.y / (float)page.height; + if (region.degrees == 90) + { + region.u2 = (region.x + region.height) / (float)page.width; + region.v2 = (region.y + region.width) / (float)page.height; + } + else + { + region.u2 = (region.x + region.width) / (float)page.width; + region.v2 = (region.y + region.height) / (float)page.height; + } + regions.Add(region); + } + } + } - static private int ReadEntry (string[] entry, string line) { - if (line == null) return 0; - line = line.Trim(); - if (line.Length == 0) return 0; - int colon = line.IndexOf(':'); - if (colon == -1) return 0; - entry[0] = line.Substring(0, colon).Trim(); - for (int i = 1, lastMatch = colon + 1; ; i++) { - int comma = line.IndexOf(',', lastMatch); - if (comma == -1) { - entry[i] = line.Substring(lastMatch).Trim(); - return i; - } - entry[i] = line.Substring(lastMatch, comma - lastMatch).Trim(); - lastMatch = comma + 1; - if (i == 4) return 4; - } - } + static private int ReadEntry(string[] entry, string line) + { + if (line == null) return 0; + line = line.Trim(); + if (line.Length == 0) return 0; + int colon = line.IndexOf(':'); + if (colon == -1) return 0; + entry[0] = line.Substring(0, colon).Trim(); + for (int i = 1, lastMatch = colon + 1; ; i++) + { + int comma = line.IndexOf(',', lastMatch); + if (comma == -1) + { + entry[i] = line.Substring(lastMatch).Trim(); + return i; + } + entry[i] = line.Substring(lastMatch, comma - lastMatch).Trim(); + lastMatch = comma + 1; + if (i == 4) return 4; + } + } - public void FlipV () { - for (int i = 0, n = regions.Count; i < n; i++) { - AtlasRegion region = regions[i]; - region.v = 1 - region.v; - region.v2 = 1 - region.v2; - } - } + public void FlipV() + { + for (int i = 0, n = regions.Count; i < n; i++) + { + AtlasRegion region = regions[i]; + region.v = 1 - region.v; + region.v2 = 1 - region.v2; + } + } - /// 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. - /// The region, or null. - public AtlasRegion FindRegion (string name) { - for (int i = 0, n = regions.Count; i < n; i++) - if (regions[i].name == name) return regions[i]; - return null; - } + /// 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. + /// The region, or null. + public AtlasRegion FindRegion(string name) + { + for (int i = 0, n = regions.Count; i < n; i++) + if (regions[i].name == name) return regions[i]; + return null; + } - public void Dispose () { - if (textureLoader == null) return; - for (int i = 0, n = pages.Count; i < n; i++) - textureLoader.Unload(pages[i].rendererObject); - } - } + public void Dispose() + { + if (textureLoader == null) return; + for (int i = 0, n = pages.Count; i < n; i++) + textureLoader.Unload(pages[i].rendererObject); + } + } - public enum Format { - Alpha, - Intensity, - LuminanceAlpha, - RGB565, - RGBA4444, - RGB888, - RGBA8888 - } + public enum Format + { + Alpha, + Intensity, + LuminanceAlpha, + RGB565, + RGBA4444, + RGB888, + RGBA8888 + } - public enum TextureFilter { - Nearest, - Linear, - MipMap, - MipMapNearestNearest, - MipMapLinearNearest, - MipMapNearestLinear, - MipMapLinearLinear - } + public enum TextureFilter + { + Nearest, + Linear, + MipMap, + MipMapNearestNearest, + MipMapLinearNearest, + MipMapNearestLinear, + MipMapLinearLinear + } - public enum TextureWrap { - MirroredRepeat, - ClampToEdge, - Repeat - } + public enum TextureWrap + { + MirroredRepeat, + ClampToEdge, + Repeat + } - public class AtlasPage { - public string name; - public int width, height; - public Format format = Format.RGBA8888; - public TextureFilter minFilter = TextureFilter.Nearest; - public TextureFilter magFilter = TextureFilter.Nearest; - public TextureWrap uWrap = TextureWrap.ClampToEdge; - public TextureWrap vWrap = TextureWrap.ClampToEdge; - public bool pma; - public object rendererObject; + public class AtlasPage + { + public string name; + public int width, height; + public Format format = Format.RGBA8888; + public TextureFilter minFilter = TextureFilter.Nearest; + public TextureFilter magFilter = TextureFilter.Nearest; + public TextureWrap uWrap = TextureWrap.ClampToEdge; + public TextureWrap vWrap = TextureWrap.ClampToEdge; + public bool pma; + public object rendererObject; - public AtlasPage Clone () { - return MemberwiseClone() as AtlasPage; - } - } + public AtlasPage Clone() + { + return MemberwiseClone() as AtlasPage; + } + } - public class AtlasRegion { - public AtlasPage page; - public string name; - public int x, y, width, height; - public float u, v, u2, v2; - public float offsetX, offsetY; - public int originalWidth, originalHeight; - public int degrees; - public bool rotate; - public int index; - public string[] names; - public int[][] values; + public class AtlasRegion + { + public AtlasPage page; + public string name; + public int x, y, width, height; + public float u, v, u2, v2; + public float offsetX, offsetY; + public int originalWidth, originalHeight; + public int degrees; + public bool rotate; + public int index; + public string[] names; + public int[][] values; - public AtlasRegion Clone () { - return MemberwiseClone() as AtlasRegion; - } - } + public AtlasRegion Clone() + { + return MemberwiseClone() as AtlasRegion; + } + } - public interface TextureLoader { - void Load (AtlasPage page, string path); - void Unload (Object texture); - } + public interface TextureLoader + { + void Load(AtlasPage page, string path); + void Unload(Object texture); + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Attachments/AtlasAttachmentLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Attachments/AtlasAttachmentLoader.cs index db887b7..4d878d8 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Attachments/AtlasAttachmentLoader.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Attachments/AtlasAttachmentLoader.cs @@ -29,80 +29,91 @@ using System; -namespace Spine4_0_64 { +namespace Spine4_0_64 +{ - /// - /// An AttachmentLoader that configures attachments using texture regions from an Atlas. - /// See Loading Skeleton Data in the Spine Runtimes Guide. - /// - public class AtlasAttachmentLoader : AttachmentLoader { - private Atlas[] atlasArray; + /// + /// An AttachmentLoader that configures attachments using texture regions from an Atlas. + /// See Loading Skeleton Data in the Spine Runtimes Guide. + /// + public class AtlasAttachmentLoader : AttachmentLoader + { + private Atlas[] atlasArray; - public AtlasAttachmentLoader (params Atlas[] atlasArray) { - if (atlasArray == null) throw new ArgumentNullException("atlas", "atlas array cannot be null."); - this.atlasArray = atlasArray; - } + public AtlasAttachmentLoader(params Atlas[] atlasArray) + { + if (atlasArray == null) throw new ArgumentNullException("atlas", "atlas array cannot be null."); + this.atlasArray = atlasArray; + } - public RegionAttachment NewRegionAttachment (Skin skin, string name, string path) { - AtlasRegion region = FindRegion(path); - if (region == null) throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name)); - RegionAttachment attachment = new RegionAttachment(name); - attachment.RendererObject = region; - attachment.SetUVs(region.u, region.v, region.u2, region.v2, region.degrees); - attachment.regionOffsetX = region.offsetX; - attachment.regionOffsetY = region.offsetY; - attachment.regionWidth = region.width; - attachment.regionHeight = region.height; - attachment.regionOriginalWidth = region.originalWidth; - attachment.regionOriginalHeight = region.originalHeight; - return attachment; - } + public RegionAttachment NewRegionAttachment(Skin skin, string name, string path) + { + AtlasRegion region = FindRegion(path); + if (region == null) throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name)); + RegionAttachment attachment = new RegionAttachment(name); + attachment.RendererObject = region; + attachment.SetUVs(region.u, region.v, region.u2, region.v2, region.degrees); + attachment.regionOffsetX = region.offsetX; + attachment.regionOffsetY = region.offsetY; + attachment.regionWidth = region.width; + attachment.regionHeight = region.height; + attachment.regionOriginalWidth = region.originalWidth; + attachment.regionOriginalHeight = region.originalHeight; + return attachment; + } - public MeshAttachment NewMeshAttachment (Skin skin, string name, string path) { - AtlasRegion region = FindRegion(path); - if (region == null) throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name)); - MeshAttachment attachment = new MeshAttachment(name); - attachment.RendererObject = region; - attachment.RegionU = region.u; - attachment.RegionV = region.v; - attachment.RegionU2 = region.u2; - attachment.RegionV2 = region.v2; - attachment.RegionDegrees = region.degrees; - attachment.regionOffsetX = region.offsetX; - attachment.regionOffsetY = region.offsetY; - attachment.regionWidth = region.width; - attachment.regionHeight = region.height; - attachment.regionOriginalWidth = region.originalWidth; - attachment.regionOriginalHeight = region.originalHeight; - return attachment; - } + public MeshAttachment NewMeshAttachment(Skin skin, string name, string path) + { + AtlasRegion region = FindRegion(path); + if (region == null) throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name)); + MeshAttachment attachment = new MeshAttachment(name); + attachment.RendererObject = region; + attachment.RegionU = region.u; + attachment.RegionV = region.v; + attachment.RegionU2 = region.u2; + attachment.RegionV2 = region.v2; + attachment.RegionDegrees = region.degrees; + attachment.regionOffsetX = region.offsetX; + attachment.regionOffsetY = region.offsetY; + attachment.regionWidth = region.width; + attachment.regionHeight = region.height; + attachment.regionOriginalWidth = region.originalWidth; + attachment.regionOriginalHeight = region.originalHeight; + return attachment; + } - public BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, string name) { - return new BoundingBoxAttachment(name); - } + public BoundingBoxAttachment NewBoundingBoxAttachment(Skin skin, string name) + { + return new BoundingBoxAttachment(name); + } - public PathAttachment NewPathAttachment (Skin skin, string name) { - return new PathAttachment(name); - } + public PathAttachment NewPathAttachment(Skin skin, string name) + { + return new PathAttachment(name); + } - public PointAttachment NewPointAttachment (Skin skin, string name) { - return new PointAttachment(name); - } + public PointAttachment NewPointAttachment(Skin skin, string name) + { + return new PointAttachment(name); + } - public ClippingAttachment NewClippingAttachment (Skin skin, string name) { - return new ClippingAttachment(name); - } + public ClippingAttachment NewClippingAttachment(Skin skin, string name) + { + return new ClippingAttachment(name); + } - public AtlasRegion FindRegion (string name) { - AtlasRegion region; + public AtlasRegion FindRegion(string name) + { + AtlasRegion region; - for (int i = 0; i < atlasArray.Length; i++) { - region = atlasArray[i].FindRegion(name); - if (region != null) - return region; - } + for (int i = 0; i < atlasArray.Length; i++) + { + region = atlasArray[i].FindRegion(name); + if (region != null) + return region; + } - return null; - } - } + return null; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Attachments/Attachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Attachments/Attachment.cs index 141b4e2..88bb202 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Attachments/Attachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Attachments/Attachment.cs @@ -29,24 +29,29 @@ using System; -namespace Spine4_0_64 { - abstract public class Attachment { - public string Name { get; private set; } +namespace Spine4_0_64 +{ + abstract public class Attachment + { + public string Name { get; private set; } - protected Attachment (string name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null"); - Name = name; - } + protected Attachment(string name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null"); + Name = name; + } - override public string ToString () { - return Name; - } + override public string ToString() + { + return Name; + } - ///Returns a copy of the attachment. - public abstract Attachment Copy (); - } + ///Returns a copy of the attachment. + public abstract Attachment Copy(); + } - public interface IHasRendererObject { - object RendererObject { get; set; } - } + public interface IHasRendererObject + { + object RendererObject { get; set; } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Attachments/AttachmentLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Attachments/AttachmentLoader.cs index 44605ae..8eabdb1 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Attachments/AttachmentLoader.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Attachments/AttachmentLoader.cs @@ -27,22 +27,24 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine4_0_64 { - public interface AttachmentLoader { - /// May be null to not load any attachment. - RegionAttachment NewRegionAttachment (Skin skin, string name, string path); +namespace Spine4_0_64 +{ + public interface AttachmentLoader + { + /// May be null to not load any attachment. + RegionAttachment NewRegionAttachment(Skin skin, string name, string path); - /// May be null to not load any attachment. - MeshAttachment NewMeshAttachment (Skin skin, string name, string path); + /// May be null to not load any attachment. + MeshAttachment NewMeshAttachment(Skin skin, string name, string path); - /// May be null to not load any attachment. - BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, string name); + /// May be null to not load any attachment. + BoundingBoxAttachment NewBoundingBoxAttachment(Skin skin, string name); - /// May be null to not load any attachment - PathAttachment NewPathAttachment (Skin skin, string name); + /// May be null to not load any attachment + PathAttachment NewPathAttachment(Skin skin, string name); - PointAttachment NewPointAttachment (Skin skin, string name); + PointAttachment NewPointAttachment(Skin skin, string name); - ClippingAttachment NewClippingAttachment (Skin skin, string name); - } + ClippingAttachment NewClippingAttachment(Skin skin, string name); + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Attachments/AttachmentType.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Attachments/AttachmentType.cs index 52a9f11..b0d0f82 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Attachments/AttachmentType.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Attachments/AttachmentType.cs @@ -27,8 +27,10 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine4_0_64 { - public enum AttachmentType { - Region, Boundingbox, Mesh, Linkedmesh, Path, Point, Clipping - } +namespace Spine4_0_64 +{ + public enum AttachmentType + { + Region, Boundingbox, Mesh, Linkedmesh, Path, Point, Clipping + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Attachments/BoundingBoxAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Attachments/BoundingBoxAttachment.cs index 6480ff3..2be314d 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Attachments/BoundingBoxAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Attachments/BoundingBoxAttachment.cs @@ -27,19 +27,21 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; +namespace Spine4_0_64 +{ + /// Attachment that has a polygon for bounds checking. + public class BoundingBoxAttachment : VertexAttachment + { + public BoundingBoxAttachment(string name) + : base(name) + { + } -namespace Spine4_0_64 { - /// Attachment that has a polygon for bounds checking. - public class BoundingBoxAttachment : VertexAttachment { - public BoundingBoxAttachment (string name) - : base(name) { - } - - public override Attachment Copy () { - BoundingBoxAttachment copy = new BoundingBoxAttachment(this.Name); - CopyTo(copy); - return copy; - } - } + public override Attachment Copy() + { + BoundingBoxAttachment copy = new BoundingBoxAttachment(this.Name); + CopyTo(copy); + return copy; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Attachments/ClippingAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Attachments/ClippingAttachment.cs index fe6623a..c870cca 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Attachments/ClippingAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Attachments/ClippingAttachment.cs @@ -27,22 +27,24 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; +namespace Spine4_0_64 +{ + public class ClippingAttachment : VertexAttachment + { + internal SlotData endSlot; -namespace Spine4_0_64 { - public class ClippingAttachment : VertexAttachment { - internal SlotData endSlot; + public SlotData EndSlot { get { return endSlot; } set { endSlot = value; } } - public SlotData EndSlot { get { return endSlot; } set { endSlot = value; } } + public ClippingAttachment(string name) : base(name) + { + } - public ClippingAttachment (string name) : base(name) { - } - - public override Attachment Copy () { - ClippingAttachment copy = new ClippingAttachment(this.Name); - CopyTo(copy); - copy.endSlot = endSlot; - return copy; - } - } + public override Attachment Copy() + { + ClippingAttachment copy = new ClippingAttachment(this.Name); + CopyTo(copy); + copy.endSlot = endSlot; + return copy; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Attachments/MeshAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Attachments/MeshAttachment.cs index 6af922d..1eaf092 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Attachments/MeshAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Attachments/MeshAttachment.cs @@ -29,193 +29,214 @@ using System; -namespace Spine4_0_64 { - /// Attachment that displays a texture region using a mesh. - public class MeshAttachment : VertexAttachment, IHasRendererObject { - internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; - private MeshAttachment parentMesh; - internal float[] uvs, regionUVs; - internal int[] triangles; - internal float r = 1, g = 1, b = 1, a = 1; - internal int hulllength; - - public int HullLength { get { return hulllength; } set { hulllength = value; } } - public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } } - /// The UV pair for each vertex, normalized within the entire texture. - public float[] UVs { get { return uvs; } set { uvs = value; } } - public int[] Triangles { get { return triangles; } set { triangles = value; } } - - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } - - public string Path { get; set; } - public object RendererObject { get; set; } - public float RegionU { get; set; } - public float RegionV { get; set; } - public float RegionU2 { get; set; } - public float RegionV2 { get; set; } - public int RegionDegrees { get; set; } - public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } - public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. - public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } - public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. - public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } - public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. - - public MeshAttachment ParentMesh { - get { return parentMesh; } - set { - parentMesh = value; - if (value != null) { - bones = value.bones; - vertices = value.vertices; - worldVerticesLength = value.worldVerticesLength; - regionUVs = value.regionUVs; - triangles = value.triangles; - HullLength = value.HullLength; - Edges = value.Edges; - Width = value.Width; - Height = value.Height; - } - } - } - - // Nonessential. - public int[] Edges { get; set; } - public float Width { get; set; } - public float Height { get; set; } - - public MeshAttachment (string name) - : base(name) { - } - - public void UpdateUVs () { - float[] regionUVs = this.regionUVs; - if (this.uvs == null || this.uvs.Length != regionUVs.Length) this.uvs = new float[regionUVs.Length]; - float[] uvs = this.uvs; - float u = RegionU, v = RegionV, width = 0, height = 0; - - if (RegionDegrees == 90) { - float textureHeight = this.regionWidth / (RegionV2 - RegionV); - float textureWidth = this.regionHeight / (RegionU2 - RegionU); - u -= (RegionOriginalHeight - RegionOffsetY - RegionHeight) / textureWidth; - v -= (RegionOriginalWidth - RegionOffsetX - RegionWidth) / textureHeight; - width = RegionOriginalHeight / textureWidth; - height = RegionOriginalWidth / textureHeight; - - for (int i = 0, n = uvs.Length; i < n; i += 2) { - uvs[i] = u + regionUVs[i + 1] * width; - uvs[i + 1] = v + (1 - regionUVs[i]) * height; - } - } else if (RegionDegrees == 180) { - float textureWidth = this.regionWidth / (RegionU2 - RegionU); - float textureHeight = this.regionHeight / (RegionV2 - RegionV); - u -= (RegionOriginalWidth - RegionOffsetX - RegionWidth) / textureWidth; - v -= RegionOffsetY / textureHeight; - width = RegionOriginalWidth / textureWidth; - height = RegionOriginalHeight / textureHeight; - - for (int i = 0, n = uvs.Length; i < n; i += 2) { - uvs[i] = u + (1 - regionUVs[i]) * width; - uvs[i + 1] = v + (1 - regionUVs[i + 1]) * height; - } - } else if (RegionDegrees == 270) { - float textureWidth = this.regionWidth / (RegionU2 - RegionU); - float textureHeight = this.regionHeight / (RegionV2 - RegionV); - u -= RegionOffsetY / textureWidth; - v -= RegionOffsetX / textureHeight; - width = RegionOriginalHeight / textureWidth; - height = RegionOriginalWidth / textureHeight; - - for (int i = 0, n = uvs.Length; i < n; i += 2) { - uvs[i] = u + (1 - regionUVs[i + 1]) * width; - uvs[i + 1] = v + regionUVs[i] * height; - } - } else { - float textureWidth = this.regionWidth / (RegionU2 - RegionU); - float textureHeight = this.regionHeight / (RegionV2 - RegionV); - u -= RegionOffsetX / textureWidth; - v -= (RegionOriginalHeight - RegionOffsetY - RegionHeight) / textureHeight; - width = RegionOriginalWidth / textureWidth; - height = RegionOriginalHeight / textureHeight; - - for (int i = 0, n = uvs.Length; i < n; i += 2) { - uvs[i] = u + regionUVs[i] * width; - uvs[i + 1] = v + regionUVs[i + 1] * height; - } - } - } - - public override Attachment Copy () { - if (parentMesh != null) return NewLinkedMesh(); - - MeshAttachment copy = new MeshAttachment(this.Name); - copy.RendererObject = RendererObject; - copy.regionOffsetX = regionOffsetX; - copy.regionOffsetY = regionOffsetY; - copy.regionWidth = regionWidth; - copy.regionHeight = regionHeight; - copy.regionOriginalWidth = regionOriginalWidth; - copy.regionOriginalHeight = regionOriginalHeight; - copy.RegionDegrees = RegionDegrees; - copy.RegionU = RegionU; - copy.RegionV = RegionV; - copy.RegionU2 = RegionU2; - copy.RegionV2 = RegionV2; - - copy.Path = Path; - copy.r = r; - copy.g = g; - copy.b = b; - copy.a = a; - - CopyTo(copy); - copy.regionUVs = new float[regionUVs.Length]; - Array.Copy(regionUVs, 0, copy.regionUVs, 0, regionUVs.Length); - copy.uvs = new float[uvs.Length]; - Array.Copy(uvs, 0, copy.uvs, 0, uvs.Length); - copy.triangles = new int[triangles.Length]; - Array.Copy(triangles, 0, copy.triangles, 0, triangles.Length); - copy.HullLength = HullLength; - - // Nonessential. - if (Edges != null) { - copy.Edges = new int[Edges.Length]; - Array.Copy(Edges, 0, copy.Edges, 0, Edges.Length); - } - copy.Width = Width; - copy.Height = Height; - return copy; - } - - ///Returns a new mesh with this mesh set as the . - public MeshAttachment NewLinkedMesh () { - MeshAttachment mesh = new MeshAttachment(Name); - mesh.RendererObject = RendererObject; - mesh.regionOffsetX = regionOffsetX; - mesh.regionOffsetY = regionOffsetY; - mesh.regionWidth = regionWidth; - mesh.regionHeight = regionHeight; - mesh.regionOriginalWidth = regionOriginalWidth; - mesh.regionOriginalHeight = regionOriginalHeight; - mesh.RegionDegrees = RegionDegrees; - mesh.RegionU = RegionU; - mesh.RegionV = RegionV; - mesh.RegionU2 = RegionU2; - mesh.RegionV2 = RegionV2; - - mesh.Path = Path; - mesh.r = r; - mesh.g = g; - mesh.b = b; - mesh.a = a; - - mesh.deformAttachment = deformAttachment; - mesh.ParentMesh = parentMesh != null ? parentMesh : this; - mesh.UpdateUVs(); - return mesh; - } - } +namespace Spine4_0_64 +{ + /// Attachment that displays a texture region using a mesh. + public class MeshAttachment : VertexAttachment, IHasRendererObject + { + internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; + private MeshAttachment parentMesh; + internal float[] uvs, regionUVs; + internal int[] triangles; + internal float r = 1, g = 1, b = 1, a = 1; + internal int hulllength; + + public int HullLength { get { return hulllength; } set { hulllength = value; } } + public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } } + /// The UV pair for each vertex, normalized within the entire texture. + public float[] UVs { get { return uvs; } set { uvs = value; } } + public int[] Triangles { get { return triangles; } set { triangles = value; } } + + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } + + public string Path { get; set; } + public object RendererObject { get; set; } + public float RegionU { get; set; } + public float RegionV { get; set; } + public float RegionU2 { get; set; } + public float RegionV2 { get; set; } + public int RegionDegrees { get; set; } + public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } + public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. + public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } + public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. + public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } + public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. + + public MeshAttachment ParentMesh + { + get { return parentMesh; } + set + { + parentMesh = value; + if (value != null) + { + bones = value.bones; + vertices = value.vertices; + worldVerticesLength = value.worldVerticesLength; + regionUVs = value.regionUVs; + triangles = value.triangles; + HullLength = value.HullLength; + Edges = value.Edges; + Width = value.Width; + Height = value.Height; + } + } + } + + // Nonessential. + public int[] Edges { get; set; } + public float Width { get; set; } + public float Height { get; set; } + + public MeshAttachment(string name) + : base(name) + { + } + + public void UpdateUVs() + { + float[] regionUVs = this.regionUVs; + if (this.uvs == null || this.uvs.Length != regionUVs.Length) this.uvs = new float[regionUVs.Length]; + float[] uvs = this.uvs; + float u = RegionU, v = RegionV, width = 0, height = 0; + + if (RegionDegrees == 90) + { + float textureHeight = this.regionWidth / (RegionV2 - RegionV); + float textureWidth = this.regionHeight / (RegionU2 - RegionU); + u -= (RegionOriginalHeight - RegionOffsetY - RegionHeight) / textureWidth; + v -= (RegionOriginalWidth - RegionOffsetX - RegionWidth) / textureHeight; + width = RegionOriginalHeight / textureWidth; + height = RegionOriginalWidth / textureHeight; + + for (int i = 0, n = uvs.Length; i < n; i += 2) + { + uvs[i] = u + regionUVs[i + 1] * width; + uvs[i + 1] = v + (1 - regionUVs[i]) * height; + } + } + else if (RegionDegrees == 180) + { + float textureWidth = this.regionWidth / (RegionU2 - RegionU); + float textureHeight = this.regionHeight / (RegionV2 - RegionV); + u -= (RegionOriginalWidth - RegionOffsetX - RegionWidth) / textureWidth; + v -= RegionOffsetY / textureHeight; + width = RegionOriginalWidth / textureWidth; + height = RegionOriginalHeight / textureHeight; + + for (int i = 0, n = uvs.Length; i < n; i += 2) + { + uvs[i] = u + (1 - regionUVs[i]) * width; + uvs[i + 1] = v + (1 - regionUVs[i + 1]) * height; + } + } + else if (RegionDegrees == 270) + { + float textureWidth = this.regionWidth / (RegionU2 - RegionU); + float textureHeight = this.regionHeight / (RegionV2 - RegionV); + u -= RegionOffsetY / textureWidth; + v -= RegionOffsetX / textureHeight; + width = RegionOriginalHeight / textureWidth; + height = RegionOriginalWidth / textureHeight; + + for (int i = 0, n = uvs.Length; i < n; i += 2) + { + uvs[i] = u + (1 - regionUVs[i + 1]) * width; + uvs[i + 1] = v + regionUVs[i] * height; + } + } + else + { + float textureWidth = this.regionWidth / (RegionU2 - RegionU); + float textureHeight = this.regionHeight / (RegionV2 - RegionV); + u -= RegionOffsetX / textureWidth; + v -= (RegionOriginalHeight - RegionOffsetY - RegionHeight) / textureHeight; + width = RegionOriginalWidth / textureWidth; + height = RegionOriginalHeight / textureHeight; + + for (int i = 0, n = uvs.Length; i < n; i += 2) + { + uvs[i] = u + regionUVs[i] * width; + uvs[i + 1] = v + regionUVs[i + 1] * height; + } + } + } + + public override Attachment Copy() + { + if (parentMesh != null) return NewLinkedMesh(); + + MeshAttachment copy = new MeshAttachment(this.Name); + copy.RendererObject = RendererObject; + copy.regionOffsetX = regionOffsetX; + copy.regionOffsetY = regionOffsetY; + copy.regionWidth = regionWidth; + copy.regionHeight = regionHeight; + copy.regionOriginalWidth = regionOriginalWidth; + copy.regionOriginalHeight = regionOriginalHeight; + copy.RegionDegrees = RegionDegrees; + copy.RegionU = RegionU; + copy.RegionV = RegionV; + copy.RegionU2 = RegionU2; + copy.RegionV2 = RegionV2; + + copy.Path = Path; + copy.r = r; + copy.g = g; + copy.b = b; + copy.a = a; + + CopyTo(copy); + copy.regionUVs = new float[regionUVs.Length]; + Array.Copy(regionUVs, 0, copy.regionUVs, 0, regionUVs.Length); + copy.uvs = new float[uvs.Length]; + Array.Copy(uvs, 0, copy.uvs, 0, uvs.Length); + copy.triangles = new int[triangles.Length]; + Array.Copy(triangles, 0, copy.triangles, 0, triangles.Length); + copy.HullLength = HullLength; + + // Nonessential. + if (Edges != null) + { + copy.Edges = new int[Edges.Length]; + Array.Copy(Edges, 0, copy.Edges, 0, Edges.Length); + } + copy.Width = Width; + copy.Height = Height; + return copy; + } + + ///Returns a new mesh with this mesh set as the . + public MeshAttachment NewLinkedMesh() + { + MeshAttachment mesh = new MeshAttachment(Name); + mesh.RendererObject = RendererObject; + mesh.regionOffsetX = regionOffsetX; + mesh.regionOffsetY = regionOffsetY; + mesh.regionWidth = regionWidth; + mesh.regionHeight = regionHeight; + mesh.regionOriginalWidth = regionOriginalWidth; + mesh.regionOriginalHeight = regionOriginalHeight; + mesh.RegionDegrees = RegionDegrees; + mesh.RegionU = RegionU; + mesh.RegionV = RegionV; + mesh.RegionU2 = RegionU2; + mesh.RegionV2 = RegionV2; + + mesh.Path = Path; + mesh.r = r; + mesh.g = g; + mesh.b = b; + mesh.a = a; + + mesh.deformAttachment = deformAttachment; + mesh.ParentMesh = parentMesh != null ? parentMesh : this; + mesh.UpdateUVs(); + return mesh; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Attachments/PathAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Attachments/PathAttachment.cs index e45c5e3..8d39e50 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Attachments/PathAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Attachments/PathAttachment.cs @@ -28,33 +28,36 @@ *****************************************************************************/ using System; -using System.Collections.Generic; -namespace Spine4_0_64 { - public class PathAttachment : VertexAttachment { - internal float[] lengths; - internal bool closed, constantSpeed; +namespace Spine4_0_64 +{ + public class PathAttachment : VertexAttachment + { + internal float[] lengths; + internal bool closed, constantSpeed; - /// The length in the setup pose from the start of the path to the end of each curve. - public float[] Lengths { get { return lengths; } set { lengths = value; } } - /// If true, the start and end knots are connected. - public bool Closed { get { return closed; } set { closed = value; } } - /// If true, additional calculations are performed to make computing positions along the path more accurate and movement along - /// the path have a constant speed. - public bool ConstantSpeed { get { return constantSpeed; } set { constantSpeed = value; } } + /// The length in the setup pose from the start of the path to the end of each curve. + public float[] Lengths { get { return lengths; } set { lengths = value; } } + /// If true, the start and end knots are connected. + public bool Closed { get { return closed; } set { closed = value; } } + /// If true, additional calculations are performed to make computing positions along the path more accurate and movement along + /// the path have a constant speed. + public bool ConstantSpeed { get { return constantSpeed; } set { constantSpeed = value; } } - public PathAttachment (String name) - : base(name) { - } + public PathAttachment(String name) + : base(name) + { + } - public override Attachment Copy () { - PathAttachment copy = new PathAttachment(this.Name); - CopyTo(copy); - copy.lengths = new float[lengths.Length]; - Array.Copy(lengths, 0, copy.lengths, 0, lengths.Length); - copy.closed = closed; - copy.constantSpeed = constantSpeed; - return copy; - } - } + public override Attachment Copy() + { + PathAttachment copy = new PathAttachment(this.Name); + CopyTo(copy); + copy.lengths = new float[lengths.Length]; + Array.Copy(lengths, 0, copy.lengths, 0, lengths.Length); + copy.closed = closed; + copy.constantSpeed = constantSpeed; + return copy; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Attachments/PointAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Attachments/PointAttachment.cs index 6e32473..d3b28ca 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Attachments/PointAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Attachments/PointAttachment.cs @@ -27,41 +27,47 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine4_0_64 { - /// - /// 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 Point Attachments in the Spine User Guide. - ///

- public class PointAttachment : Attachment { - internal float x, y, rotation; - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public float Rotation { get { return rotation; } set { rotation = value; } } +namespace Spine4_0_64 +{ + /// + /// 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 Point Attachments in the Spine User Guide. + ///

+ public class PointAttachment : Attachment + { + internal float x, y, rotation; + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public float Rotation { get { return rotation; } set { rotation = value; } } - public PointAttachment (string name) - : base(name) { - } + public PointAttachment(string name) + : base(name) + { + } - public void ComputeWorldPosition (Bone bone, out float ox, out float oy) { - bone.LocalToWorld(this.x, this.y, out ox, out oy); - } + public void ComputeWorldPosition(Bone bone, out float ox, out float oy) + { + bone.LocalToWorld(this.x, this.y, out ox, out oy); + } - public float ComputeWorldRotation (Bone bone) { - float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation); - float ix = cos * bone.a + sin * bone.b; - float iy = cos * bone.c + sin * bone.d; - return MathUtils.Atan2(iy, ix) * MathUtils.RadDeg; - } + public float ComputeWorldRotation(Bone bone) + { + float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation); + float ix = cos * bone.a + sin * bone.b; + float iy = cos * bone.c + sin * bone.d; + return MathUtils.Atan2(iy, ix) * MathUtils.RadDeg; + } - public override Attachment Copy () { - PointAttachment copy = new PointAttachment(this.Name); - copy.x = x; - copy.y = y; - copy.rotation = rotation; - return copy; - } - } + public override Attachment Copy() + { + PointAttachment copy = new PointAttachment(this.Name); + copy.x = x; + copy.y = y; + copy.rotation = rotation; + return copy; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Attachments/RegionAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Attachments/RegionAttachment.cs index 18eb298..b70ac7f 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Attachments/RegionAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Attachments/RegionAttachment.cs @@ -29,166 +29,176 @@ using System; -namespace Spine4_0_64 { - /// Attachment that displays a texture region. - public class RegionAttachment : Attachment, IHasRendererObject { - public const int BLX = 0; - public const int BLY = 1; - public const int ULX = 2; - public const int ULY = 3; - public const int URX = 4; - public const int URY = 5; - public const int BRX = 6; - public const int BRY = 7; - - internal float x, y, rotation, scaleX = 1, scaleY = 1, width, height; - internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; - internal float[] offset = new float[8], uvs = new float[8]; - internal float r = 1, g = 1, b = 1, a = 1; - - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public float Rotation { get { return rotation; } set { rotation = value; } } - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - public float ScaleY { get { return scaleY; } set { scaleY = value; } } - public float Width { get { return width; } set { width = value; } } - public float Height { get { return height; } set { height = value; } } - - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } - - public string Path { get; set; } - public object RendererObject { get; set; } - public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } - public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. - public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } - public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. - public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } - public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. - - public float[] Offset { get { return offset; } } - public float[] UVs { get { return uvs; } } - - public RegionAttachment (string name) - : base(name) { - } - - public void UpdateOffset () { - float regionScaleX = width / regionOriginalWidth * scaleX; - float regionScaleY = height / regionOriginalHeight * scaleY; - float localX = -width / 2 * scaleX + regionOffsetX * regionScaleX; - float localY = -height / 2 * scaleY + regionOffsetY * regionScaleY; - float localX2 = localX + regionWidth * regionScaleX; - float localY2 = localY + regionHeight * regionScaleY; - float cos = MathUtils.CosDeg(this.rotation); - float sin = MathUtils.SinDeg(this.rotation); - float x = this.x, y = this.y; - 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; - float[] offset = this.offset; - offset[BLX] = localXCos - localYSin; - offset[BLY] = localYCos + localXSin; - offset[ULX] = localXCos - localY2Sin; - offset[ULY] = localY2Cos + localXSin; - offset[URX] = localX2Cos - localY2Sin; - offset[URY] = localY2Cos + localX2Sin; - offset[BRX] = localX2Cos - localYSin; - offset[BRY] = localYCos + localX2Sin; - } - - public void SetUVs (float u, float v, float u2, float v2, int degrees) { - float[] uvs = this.uvs; - // UV values differ from spine-libgdx. - if (degrees == 90) { - uvs[URX] = u; - uvs[URY] = v2; - uvs[BRX] = u; - uvs[BRY] = v; - uvs[BLX] = u2; - uvs[BLY] = v; - uvs[ULX] = u2; - uvs[ULY] = v2; - } else { - uvs[ULX] = u; - uvs[ULY] = v2; - uvs[URX] = u; - uvs[URY] = v; - uvs[BRX] = u2; - uvs[BRY] = v; - uvs[BLX] = u2; - uvs[BLY] = v2; - } - } - - /// Transforms the attachment's four vertices to world coordinates. - /// The parent bone. - /// The output world vertices. Must have a length greater than or equal to offset + 8. - /// The worldVertices index to begin writing values. - /// The number of worldVertices entries between the value pairs written. - public void ComputeWorldVertices (Bone bone, float[] worldVertices, int offset, int stride = 2) { - float[] vertexOffset = this.offset; - float bwx = bone.worldX, bwy = bone.worldY; - float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - float offsetX, offsetY; - - // Vertex order is different from RegionAttachment.java - offsetX = vertexOffset[BRX]; // 0 - offsetY = vertexOffset[BRY]; // 1 - worldVertices[offset] = offsetX * a + offsetY * b + bwx; // bl - worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; - offset += stride; - - offsetX = vertexOffset[BLX]; // 2 - offsetY = vertexOffset[BLY]; // 3 - worldVertices[offset] = offsetX * a + offsetY * b + bwx; // ul - worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; - offset += stride; - - offsetX = vertexOffset[ULX]; // 4 - offsetY = vertexOffset[ULY]; // 5 - worldVertices[offset] = offsetX * a + offsetY * b + bwx; // ur - worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; - offset += stride; - - offsetX = vertexOffset[URX]; // 6 - offsetY = vertexOffset[URY]; // 7 - worldVertices[offset] = offsetX * a + offsetY * b + bwx; // br - worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; - //offset += stride; - } - - public override Attachment Copy () { - RegionAttachment copy = new RegionAttachment(this.Name); - copy.RendererObject = RendererObject; - copy.regionOffsetX = regionOffsetX; - copy.regionOffsetY = regionOffsetY; - copy.regionWidth = regionWidth; - copy.regionHeight = regionHeight; - copy.regionOriginalWidth = regionOriginalWidth; - copy.regionOriginalHeight = regionOriginalHeight; - copy.Path = Path; - copy.x = x; - copy.y = y; - copy.scaleX = scaleX; - copy.scaleY = scaleY; - copy.rotation = rotation; - copy.width = width; - copy.height = height; - Array.Copy(uvs, 0, copy.uvs, 0, 8); - Array.Copy(offset, 0, copy.offset, 0, 8); - copy.r = r; - copy.g = g; - copy.b = b; - copy.a = a; - return copy; - } - } +namespace Spine4_0_64 +{ + /// Attachment that displays a texture region. + public class RegionAttachment : Attachment, IHasRendererObject + { + public const int BLX = 0; + public const int BLY = 1; + public const int ULX = 2; + public const int ULY = 3; + public const int URX = 4; + public const int URY = 5; + public const int BRX = 6; + public const int BRY = 7; + + internal float x, y, rotation, scaleX = 1, scaleY = 1, width, height; + internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight; + internal float[] offset = new float[8], uvs = new float[8]; + internal float r = 1, g = 1, b = 1, a = 1; + + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public float Rotation { get { return rotation; } set { rotation = value; } } + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + public float Width { get { return width; } set { width = value; } } + public float Height { get { return height; } set { height = value; } } + + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } + + public string Path { get; set; } + public object RendererObject { get; set; } + public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } } + public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated. + public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } } + public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size. + public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } } + public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size. + + public float[] Offset { get { return offset; } } + public float[] UVs { get { return uvs; } } + + public RegionAttachment(string name) + : base(name) + { + } + + public void UpdateOffset() + { + float regionScaleX = width / regionOriginalWidth * scaleX; + float regionScaleY = height / regionOriginalHeight * scaleY; + float localX = -width / 2 * scaleX + regionOffsetX * regionScaleX; + float localY = -height / 2 * scaleY + regionOffsetY * regionScaleY; + float localX2 = localX + regionWidth * regionScaleX; + float localY2 = localY + regionHeight * regionScaleY; + float cos = MathUtils.CosDeg(this.rotation); + float sin = MathUtils.SinDeg(this.rotation); + float x = this.x, y = this.y; + 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; + float[] offset = this.offset; + offset[BLX] = localXCos - localYSin; + offset[BLY] = localYCos + localXSin; + offset[ULX] = localXCos - localY2Sin; + offset[ULY] = localY2Cos + localXSin; + offset[URX] = localX2Cos - localY2Sin; + offset[URY] = localY2Cos + localX2Sin; + offset[BRX] = localX2Cos - localYSin; + offset[BRY] = localYCos + localX2Sin; + } + + public void SetUVs(float u, float v, float u2, float v2, int degrees) + { + float[] uvs = this.uvs; + // UV values differ from spine-libgdx. + if (degrees == 90) + { + uvs[URX] = u; + uvs[URY] = v2; + uvs[BRX] = u; + uvs[BRY] = v; + uvs[BLX] = u2; + uvs[BLY] = v; + uvs[ULX] = u2; + uvs[ULY] = v2; + } + else + { + uvs[ULX] = u; + uvs[ULY] = v2; + uvs[URX] = u; + uvs[URY] = v; + uvs[BRX] = u2; + uvs[BRY] = v; + uvs[BLX] = u2; + uvs[BLY] = v2; + } + } + + /// Transforms the attachment's four vertices to world coordinates. + /// The parent bone. + /// The output world vertices. Must have a length greater than or equal to offset + 8. + /// The worldVertices index to begin writing values. + /// The number of worldVertices entries between the value pairs written. + public void ComputeWorldVertices(Bone bone, float[] worldVertices, int offset, int stride = 2) + { + float[] vertexOffset = this.offset; + float bwx = bone.worldX, bwy = bone.worldY; + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + float offsetX, offsetY; + + // Vertex order is different from RegionAttachment.java + offsetX = vertexOffset[BRX]; // 0 + offsetY = vertexOffset[BRY]; // 1 + worldVertices[offset] = offsetX * a + offsetY * b + bwx; // bl + worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; + offset += stride; + + offsetX = vertexOffset[BLX]; // 2 + offsetY = vertexOffset[BLY]; // 3 + worldVertices[offset] = offsetX * a + offsetY * b + bwx; // ul + worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; + offset += stride; + + offsetX = vertexOffset[ULX]; // 4 + offsetY = vertexOffset[ULY]; // 5 + worldVertices[offset] = offsetX * a + offsetY * b + bwx; // ur + worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; + offset += stride; + + offsetX = vertexOffset[URX]; // 6 + offsetY = vertexOffset[URY]; // 7 + worldVertices[offset] = offsetX * a + offsetY * b + bwx; // br + worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; + //offset += stride; + } + + public override Attachment Copy() + { + RegionAttachment copy = new RegionAttachment(this.Name); + copy.RendererObject = RendererObject; + copy.regionOffsetX = regionOffsetX; + copy.regionOffsetY = regionOffsetY; + copy.regionWidth = regionWidth; + copy.regionHeight = regionHeight; + copy.regionOriginalWidth = regionOriginalWidth; + copy.regionOriginalHeight = regionOriginalHeight; + copy.Path = Path; + copy.x = x; + copy.y = y; + copy.scaleX = scaleX; + copy.scaleY = scaleY; + copy.rotation = rotation; + copy.width = width; + copy.height = height; + Array.Copy(uvs, 0, copy.uvs, 0, 8); + Array.Copy(offset, 0, copy.offset, 0, 8); + copy.r = r; + copy.g = g; + copy.b = b; + copy.a = a; + return copy; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Attachments/VertexAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Attachments/VertexAttachment.cs index e1c12f1..98892df 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Attachments/VertexAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Attachments/VertexAttachment.cs @@ -29,125 +29,146 @@ using System; -namespace Spine4_0_64 { - /// >An attachment with vertices that are transformed by one or more bones and can be deformed by a slot's - /// . - public abstract class VertexAttachment : Attachment { - static int nextID = 0; - static readonly Object nextIdLock = new Object(); +namespace Spine4_0_64 +{ + /// >An attachment with vertices that are transformed by one or more bones and can be deformed by a slot's + /// . + public abstract class VertexAttachment : Attachment + { + static int nextID = 0; + static readonly Object nextIdLock = new Object(); - internal readonly int id; - internal int[] bones; - internal float[] vertices; - internal int worldVerticesLength; - internal VertexAttachment deformAttachment; + internal readonly int id; + internal int[] bones; + internal float[] vertices; + internal int worldVerticesLength; + internal VertexAttachment deformAttachment; - /// Gets a unique ID for this attachment. - public int Id { get { return id; } } - public int[] Bones { get { return bones; } set { bones = value; } } - public float[] Vertices { get { return vertices; } set { vertices = value; } } - public int WorldVerticesLength { get { return worldVerticesLength; } set { worldVerticesLength = value; } } - ///Deform keys for the deform attachment are also applied to this attachment. - /// May be null if no deform keys should be applied. - public VertexAttachment DeformAttachment { get { return deformAttachment; } set { deformAttachment = value; } } + /// Gets a unique ID for this attachment. + public int Id { get { return id; } } + public int[] Bones { get { return bones; } set { bones = value; } } + public float[] Vertices { get { return vertices; } set { vertices = value; } } + public int WorldVerticesLength { get { return worldVerticesLength; } set { worldVerticesLength = value; } } + ///Deform keys for the deform attachment are also applied to this attachment. + /// May be null if no deform keys should be applied. + public VertexAttachment DeformAttachment { get { return deformAttachment; } set { deformAttachment = value; } } - public VertexAttachment (string name) - : base(name) { + public VertexAttachment(string name) + : base(name) + { - deformAttachment = this; - lock (VertexAttachment.nextIdLock) { - id = VertexAttachment.nextID++; - } - } + deformAttachment = this; + lock (VertexAttachment.nextIdLock) + { + id = VertexAttachment.nextID++; + } + } - public void ComputeWorldVertices (Slot slot, float[] worldVertices) { - ComputeWorldVertices(slot, 0, worldVerticesLength, worldVertices, 0); - } + public void ComputeWorldVertices(Slot slot, float[] worldVertices) + { + ComputeWorldVertices(slot, 0, worldVerticesLength, worldVertices, 0); + } - /// - /// Transforms the attachment's local to world coordinates. If the slot's is - /// not empty, it is used to deform the vertices. - /// - /// See World transforms in the Spine - /// Runtimes Guide. - /// - /// The index of the first value to transform. Each vertex has 2 values, x and y. - /// The number of world vertex values to output. Must be less than or equal to - start. - /// The output world vertices. Must have a length greater than or equal to + . - /// The index to begin writing values. - /// The number of entries between the value pairs written. - public void ComputeWorldVertices (Slot slot, int start, int count, float[] worldVertices, int offset, int stride = 2) { - count = offset + (count >> 1) * stride; - ExposedList deformArray = slot.deform; - float[] vertices = this.vertices; - int[] bones = this.bones; - if (bones == null) { - if (deformArray.Count > 0) vertices = deformArray.Items; - Bone bone = slot.bone; - float x = bone.worldX, y = bone.worldY; - float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - for (int vv = start, w = offset; w < count; vv += 2, w += stride) { - float vx = vertices[vv], 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 (int i = 0; i < start; i += 2) { - int n = bones[v]; - v += n + 1; - skip += n; - } - Bone[] skeletonBones = slot.bone.skeleton.bones.Items; - if (deformArray.Count == 0) { - for (int w = offset, b = skip * 3; w < count; w += stride) { - float wx = 0, wy = 0; - int n = bones[v++]; - n += v; - for (; v < n; v++, b += 3) { - Bone bone = skeletonBones[bones[v]]; - float vx = vertices[b], vy = vertices[b + 1], 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 { - float[] deform = deformArray.Items; - for (int w = offset, b = skip * 3, f = skip << 1; w < count; w += stride) { - float wx = 0, wy = 0; - int n = bones[v++]; - n += v; - for (; v < n; v++, b += 3, f += 2) { - Bone bone = skeletonBones[bones[v]]; - float vx = vertices[b] + deform[f], vy = vertices[b + 1] + deform[f + 1], 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; - } - } - } + /// + /// Transforms the attachment's local to world coordinates. If the slot's is + /// not empty, it is used to deform the vertices. + /// + /// See World transforms in the Spine + /// Runtimes Guide. + /// + /// The index of the first value to transform. Each vertex has 2 values, x and y. + /// The number of world vertex values to output. Must be less than or equal to - start. + /// The output world vertices. Must have a length greater than or equal to + . + /// The index to begin writing values. + /// The number of entries between the value pairs written. + public void ComputeWorldVertices(Slot slot, int start, int count, float[] worldVertices, int offset, int stride = 2) + { + count = offset + (count >> 1) * stride; + ExposedList deformArray = slot.deform; + float[] vertices = this.vertices; + int[] bones = this.bones; + if (bones == null) + { + if (deformArray.Count > 0) vertices = deformArray.Items; + Bone bone = slot.bone; + float x = bone.worldX, y = bone.worldY; + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + for (int vv = start, w = offset; w < count; vv += 2, w += stride) + { + float vx = vertices[vv], 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 (int i = 0; i < start; i += 2) + { + int n = bones[v]; + v += n + 1; + skip += n; + } + Bone[] skeletonBones = slot.bone.skeleton.bones.Items; + if (deformArray.Count == 0) + { + for (int w = offset, b = skip * 3; w < count; w += stride) + { + float wx = 0, wy = 0; + int n = bones[v++]; + n += v; + for (; v < n; v++, b += 3) + { + Bone bone = skeletonBones[bones[v]]; + float vx = vertices[b], vy = vertices[b + 1], 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 + { + float[] deform = deformArray.Items; + for (int w = offset, b = skip * 3, f = skip << 1; w < count; w += stride) + { + float wx = 0, wy = 0; + int n = bones[v++]; + n += v; + for (; v < n; v++, b += 3, f += 2) + { + Bone bone = skeletonBones[bones[v]]; + float vx = vertices[b] + deform[f], vy = vertices[b + 1] + deform[f + 1], 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; + } + } + } - ///Does not copy id (generated) or name (set on construction). - internal void CopyTo (VertexAttachment attachment) { - if (bones != null) { - attachment.bones = new int[bones.Length]; - Array.Copy(bones, 0, attachment.bones, 0, bones.Length); - } else - attachment.bones = null; + ///Does not copy id (generated) or name (set on construction). + internal void CopyTo(VertexAttachment attachment) + { + if (bones != null) + { + attachment.bones = new int[bones.Length]; + Array.Copy(bones, 0, attachment.bones, 0, bones.Length); + } + else + attachment.bones = null; - if (vertices != null) { - attachment.vertices = new float[vertices.Length]; - Array.Copy(vertices, 0, attachment.vertices, 0, vertices.Length); - } else - attachment.vertices = null; + if (vertices != null) + { + attachment.vertices = new float[vertices.Length]; + Array.Copy(vertices, 0, attachment.vertices, 0, vertices.Length); + } + else + attachment.vertices = null; - attachment.worldVerticesLength = worldVerticesLength; - attachment.deformAttachment = deformAttachment; - } - } + attachment.worldVerticesLength = worldVerticesLength; + attachment.deformAttachment = deformAttachment; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/BlendMode.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/BlendMode.cs index ddf8953..2c8fd60 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/BlendMode.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/BlendMode.cs @@ -27,8 +27,10 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine4_0_64 { - public enum BlendMode { - Normal, Additive, Multiply, Screen - } +namespace Spine4_0_64 +{ + public enum BlendMode + { + Normal, Additive, Multiply, Screen + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Bone.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Bone.cs index 641c7d5..182b939 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Bone.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Bone.cs @@ -29,350 +29,381 @@ using System; -namespace Spine4_0_64 { - /// - /// 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. - /// - /// - public class Bone : IUpdatable { - static public bool yDown; - - internal BoneData data; - internal Skeleton skeleton; - internal Bone parent; - internal ExposedList children = new ExposedList(); - internal float x, y, rotation, scaleX, scaleY, shearX, shearY; - internal float ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY; - - internal float a, b, worldX; - internal float c, d, worldY; - - internal bool sorted, active; - - public BoneData Data { get { return data; } } - public Skeleton Skeleton { get { return skeleton; } } - public Bone Parent { get { return parent; } } - public ExposedList Children { get { return children; } } - /// Returns false when the bone has not been computed because is true and the - /// active skin does not contain this bone. - public bool Active { get { return active; } } - /// The local X translation. - public float X { get { return x; } set { x = value; } } - /// The local Y translation. - public float Y { get { return y; } set { y = value; } } - /// The local rotation. - public float Rotation { get { return rotation; } set { rotation = value; } } - - /// The local scaleX. - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - - /// The local scaleY. - public float ScaleY { get { return scaleY; } set { scaleY = value; } } - - /// The local shearX. - public float ShearX { get { return shearX; } set { shearX = value; } } - - /// The local shearY. - public float ShearY { get { return shearY; } set { shearY = value; } } - - /// The rotation, as calculated by any constraints. - public float AppliedRotation { get { return arotation; } set { arotation = value; } } - - /// The applied local x translation. - public float AX { get { return ax; } set { ax = value; } } - - /// The applied local y translation. - public float AY { get { return ay; } set { ay = value; } } - - /// The applied local scaleX. - public float AScaleX { get { return ascaleX; } set { ascaleX = value; } } - - /// The applied local scaleY. - public float AScaleY { get { return ascaleY; } set { ascaleY = value; } } - - /// The applied local shearX. - public float AShearX { get { return ashearX; } set { ashearX = value; } } - - /// The applied local shearY. - public float AShearY { get { return ashearY; } set { ashearY = value; } } - - /// Part of the world transform matrix for the X axis. If changed, should be called. - public float A { get { return a; } set { a = value; } } - /// Part of the world transform matrix for the Y axis. If changed, should be called. - public float B { get { return b; } set { b = value; } } - /// Part of the world transform matrix for the X axis. If changed, should be called. - public float C { get { return c; } set { c = value; } } - /// Part of the world transform matrix for the Y axis. If changed, should be called. - public float D { get { return d; } set { d = value; } } - - /// The world X position. If changed, should be called. - public float WorldX { get { return worldX; } set { worldX = value; } } - /// The world Y position. If changed, should be called. - public float WorldY { get { return worldY; } set { worldY = value; } } - public float WorldRotationX { get { return MathUtils.Atan2(c, a) * MathUtils.RadDeg; } } - public float WorldRotationY { get { return MathUtils.Atan2(d, b) * MathUtils.RadDeg; } } - - /// Returns the magnitide (always positive) of the world scale X. - public float WorldScaleX { get { return (float)Math.Sqrt(a * a + c * c); } } - /// Returns the magnitide (always positive) of the world scale Y. - public float WorldScaleY { get { return (float)Math.Sqrt(b * b + d * d); } } - - /// Copy constructor. Does not copy the bones. - /// May be null. - public Bone (BoneData data, Skeleton skeleton, Bone parent) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.data = data; - this.skeleton = skeleton; - this.parent = parent; - SetToSetupPose(); - } - - /// Computes the world transform using the parent bone and this bone's local applied transform. - public void Update () { - UpdateWorldTransform(ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY); - } - - /// Computes the world transform using the parent bone and this bone's local transform. - public void UpdateWorldTransform () { - UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); - } - - /// Computes the world transform using the parent bone and the specified local transform. The applied transform is set to the - /// specified local transform. Child bones are not updated. - /// - /// See World transforms in the Spine - /// Runtimes Guide. - public void UpdateWorldTransform (float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY) { - ax = x; - ay = y; - arotation = rotation; - ascaleX = scaleX; - ascaleY = scaleY; - ashearX = shearX; - ashearY = shearY; - - Bone parent = this.parent; - if (parent == null) { // Root bone. - float rotationY = rotation + 90 + shearY, sx = skeleton.ScaleX, sy = skeleton.ScaleY; - a = MathUtils.CosDeg(rotation + shearX) * scaleX * sx; - b = MathUtils.CosDeg(rotationY) * scaleY * sx; - c = MathUtils.SinDeg(rotation + shearX) * scaleX * sy; - d = MathUtils.SinDeg(rotationY) * scaleY * sy; - worldX = x * sx + skeleton.x; - worldY = y * sy + skeleton.y; - return; - } - - float 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 (data.transformMode) { - case TransformMode.Normal: { - float rotationY = rotation + 90 + shearY; - float la = MathUtils.CosDeg(rotation + shearX) * scaleX; - float lb = MathUtils.CosDeg(rotationY) * scaleY; - float lc = MathUtils.SinDeg(rotation + shearX) * scaleX; - float ld = MathUtils.SinDeg(rotationY) * scaleY; - a = pa * la + pb * lc; - b = pa * lb + pb * ld; - c = pc * la + pd * lc; - d = pc * lb + pd * ld; - return; - } - case TransformMode.OnlyTranslation: { - float rotationY = rotation + 90 + shearY; - a = MathUtils.CosDeg(rotation + shearX) * scaleX; - b = MathUtils.CosDeg(rotationY) * scaleY; - c = MathUtils.SinDeg(rotation + shearX) * scaleX; - d = MathUtils.SinDeg(rotationY) * scaleY; - break; - } - case TransformMode.NoRotationOrReflection: { - float s = pa * pa + pc * pc, prx; - if (s > 0.0001f) { - s = Math.Abs(pa * pd - pb * pc) / s; - pa /= skeleton.ScaleX; - pc /= skeleton.ScaleY; - pb = pc * s; - pd = pa * s; - prx = MathUtils.Atan2(pc, pa) * MathUtils.RadDeg; - } else { - pa = 0; - pc = 0; - prx = 90 - MathUtils.Atan2(pd, pb) * MathUtils.RadDeg; - } - float rx = rotation + shearX - prx; - float ry = rotation + shearY - prx + 90; - float la = MathUtils.CosDeg(rx) * scaleX; - float lb = MathUtils.CosDeg(ry) * scaleY; - float lc = MathUtils.SinDeg(rx) * scaleX; - float ld = MathUtils.SinDeg(ry) * scaleY; - a = pa * la - pb * lc; - b = pa * lb - pb * ld; - c = pc * la + pd * lc; - d = pc * lb + pd * ld; - break; - } - case TransformMode.NoScale: - case TransformMode.NoScaleOrReflection: { - float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation); - float za = (pa * cos + pb * sin) / skeleton.ScaleX; - float zc = (pc * cos + pd * sin) / skeleton.ScaleY; - float s = (float)Math.Sqrt(za * za + zc * zc); - if (s > 0.00001f) s = 1 / s; - za *= s; - zc *= s; - s = (float)Math.Sqrt(za * za + zc * zc); - if (data.transformMode == TransformMode.NoScale - && (pa * pd - pb * pc < 0) != (skeleton.ScaleX < 0 != skeleton.ScaleY < 0)) s = -s; - - float r = MathUtils.PI / 2 + MathUtils.Atan2(zc, za); - float zb = MathUtils.Cos(r) * s; - float zd = MathUtils.Sin(r) * s; - float la = MathUtils.CosDeg(shearX) * scaleX; - float lb = MathUtils.CosDeg(90 + shearY) * scaleY; - float lc = MathUtils.SinDeg(shearX) * scaleX; - float ld = MathUtils.SinDeg(90 + shearY) * scaleY; - a = za * la + zb * lc; - b = za * lb + zb * ld; - c = zc * la + zd * lc; - d = zc * lb + zd * ld; - break; - } - } - - a *= skeleton.ScaleX; - b *= skeleton.ScaleX; - c *= skeleton.ScaleY; - d *= skeleton.ScaleY; - } - - public void SetToSetupPose () { - BoneData data = this.data; - x = data.x; - y = data.y; - rotation = data.rotation; - scaleX = data.scaleX; - scaleY = data.scaleY; - shearX = data.shearX; - shearY = data.shearY; - } - - /// - /// Computes the applied transform values from the world transform. - /// - /// If the world transform is modified (by a constraint, , etc) then this method should be called so - /// the applied transform matches the world transform. The applied transform may be needed by other code (eg to apply another - /// constraint). - /// - /// Some information is ambiguous in the world transform, such as -1,-1 scale versus 180 rotation. The applied transform after - /// calling this method is equivalent to the local transform used to compute the world transform, but may not be identical. - /// - public void UpdateAppliedTransform () { - Bone parent = this.parent; - if (parent == null) { - ax = worldX - skeleton.x; - ay = worldY - skeleton.y; - arotation = MathUtils.Atan2(c, a) * MathUtils.RadDeg; - ascaleX = (float)Math.Sqrt(a * a + c * c); - ascaleY = (float)Math.Sqrt(b * b + d * d); - ashearX = 0; - ashearY = MathUtils.Atan2(a * b + c * d, a * d - b * c) * MathUtils.RadDeg; - return; - } - float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d; - float pid = 1 / (pa * pd - pb * pc); - float dx = worldX - parent.worldX, dy = worldY - parent.worldY; - ax = (dx * pd * pid - dy * pb * pid); - ay = (dy * pa * pid - dx * pc * pid); - float ia = pid * pd; - float id = pid * pa; - float ib = pid * pb; - float ic = pid * pc; - float ra = ia * a - ib * c; - float rb = ia * b - ib * d; - float rc = id * c - ic * a; - float rd = id * d - ic * b; - ashearX = 0; - ascaleX = (float)Math.Sqrt(ra * ra + rc * rc); - if (ascaleX > 0.0001f) { - float det = ra * rd - rb * rc; - ascaleY = det / ascaleX; - ashearY = MathUtils.Atan2(ra * rb + rc * rd, det) * MathUtils.RadDeg; - arotation = MathUtils.Atan2(rc, ra) * MathUtils.RadDeg; - } else { - ascaleX = 0; - ascaleY = (float)Math.Sqrt(rb * rb + rd * rd); - ashearY = 0; - arotation = 90 - MathUtils.Atan2(rd, rb) * MathUtils.RadDeg; - } - } - - public void WorldToLocal (float worldX, float worldY, out float localX, out float localY) { - float a = this.a, b = this.b, c = this.c, d = this.d; - float det = a * d - b * c; - float x = worldX - this.worldX, y = worldY - this.worldY; - localX = (x * d - y * b) / det; - localY = (y * a - x * c) / det; - } - - public void LocalToWorld (float localX, float localY, out float worldX, out float worldY) { - worldX = localX * a + localY * b + this.worldX; - worldY = localX * c + localY * d + this.worldY; - } - - public float WorldToLocalRotationX { - get { - Bone parent = this.parent; - if (parent == null) return arotation; - float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, a = this.a, c = this.c; - return MathUtils.Atan2(pa * c - pc * a, pd * a - pb * c) * MathUtils.RadDeg; - } - } - - public float WorldToLocalRotationY { - get { - Bone parent = this.parent; - if (parent == null) return arotation; - float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, b = this.b, d = this.d; - return MathUtils.Atan2(pa * d - pc * b, pd * b - pb * d) * MathUtils.RadDeg; - } - } - - public float WorldToLocalRotation (float worldRotation) { - float sin = MathUtils.SinDeg(worldRotation), cos = MathUtils.CosDeg(worldRotation); - return MathUtils.Atan2(a * sin - c * cos, d * cos - b * sin) * MathUtils.RadDeg + rotation - shearX; - } - - public float LocalToWorldRotation (float localRotation) { - localRotation -= rotation - shearX; - float sin = MathUtils.SinDeg(localRotation), cos = MathUtils.CosDeg(localRotation); - return MathUtils.Atan2(cos * c + sin * d, cos * a + sin * b) * MathUtils.RadDeg; - } - - /// - /// Rotates the world transform the specified amount. - /// - /// After changes are made to the world transform, should be called and will - /// need to be called on any child bones, recursively. - /// - public void RotateWorld (float degrees) { - float a = this.a, b = this.b, c = this.c, d = this.d; - float cos = MathUtils.CosDeg(degrees), sin = MathUtils.SinDeg(degrees); - this.a = cos * a - sin * c; - this.b = cos * b - sin * d; - this.c = sin * a + cos * c; - this.d = sin * b + cos * d; - } - - override public string ToString () { - return data.name; - } - } +namespace Spine4_0_64 +{ + /// + /// 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. + /// + /// + public class Bone : IUpdatable + { + static public bool yDown; + + internal BoneData data; + internal Skeleton skeleton; + internal Bone parent; + internal ExposedList children = new ExposedList(); + internal float x, y, rotation, scaleX, scaleY, shearX, shearY; + internal float ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY; + + internal float a, b, worldX; + internal float c, d, worldY; + + internal bool sorted, active; + + public BoneData Data { get { return data; } } + public Skeleton Skeleton { get { return skeleton; } } + public Bone Parent { get { return parent; } } + public ExposedList Children { get { return children; } } + /// Returns false when the bone has not been computed because is true and the + /// active skin does not contain this bone. + public bool Active { get { return active; } } + /// The local X translation. + public float X { get { return x; } set { x = value; } } + /// The local Y translation. + public float Y { get { return y; } set { y = value; } } + /// The local rotation. + public float Rotation { get { return rotation; } set { rotation = value; } } + + /// The local scaleX. + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + + /// The local scaleY. + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + + /// The local shearX. + public float ShearX { get { return shearX; } set { shearX = value; } } + + /// The local shearY. + public float ShearY { get { return shearY; } set { shearY = value; } } + + /// The rotation, as calculated by any constraints. + public float AppliedRotation { get { return arotation; } set { arotation = value; } } + + /// The applied local x translation. + public float AX { get { return ax; } set { ax = value; } } + + /// The applied local y translation. + public float AY { get { return ay; } set { ay = value; } } + + /// The applied local scaleX. + public float AScaleX { get { return ascaleX; } set { ascaleX = value; } } + + /// The applied local scaleY. + public float AScaleY { get { return ascaleY; } set { ascaleY = value; } } + + /// The applied local shearX. + public float AShearX { get { return ashearX; } set { ashearX = value; } } + + /// The applied local shearY. + public float AShearY { get { return ashearY; } set { ashearY = value; } } + + /// Part of the world transform matrix for the X axis. If changed, should be called. + public float A { get { return a; } set { a = value; } } + /// Part of the world transform matrix for the Y axis. If changed, should be called. + public float B { get { return b; } set { b = value; } } + /// Part of the world transform matrix for the X axis. If changed, should be called. + public float C { get { return c; } set { c = value; } } + /// Part of the world transform matrix for the Y axis. If changed, should be called. + public float D { get { return d; } set { d = value; } } + + /// The world X position. If changed, should be called. + public float WorldX { get { return worldX; } set { worldX = value; } } + /// The world Y position. If changed, should be called. + public float WorldY { get { return worldY; } set { worldY = value; } } + public float WorldRotationX { get { return MathUtils.Atan2(c, a) * MathUtils.RadDeg; } } + public float WorldRotationY { get { return MathUtils.Atan2(d, b) * MathUtils.RadDeg; } } + + /// Returns the magnitide (always positive) of the world scale X. + public float WorldScaleX { get { return (float)Math.Sqrt(a * a + c * c); } } + /// Returns the magnitide (always positive) of the world scale Y. + public float WorldScaleY { get { return (float)Math.Sqrt(b * b + d * d); } } + + /// Copy constructor. Does not copy the bones. + /// May be null. + public Bone(BoneData data, Skeleton skeleton, Bone parent) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + this.skeleton = skeleton; + this.parent = parent; + SetToSetupPose(); + } + + /// Computes the world transform using the parent bone and this bone's local applied transform. + public void Update() + { + UpdateWorldTransform(ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY); + } + + /// Computes the world transform using the parent bone and this bone's local transform. + public void UpdateWorldTransform() + { + UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); + } + + /// Computes the world transform using the parent bone and the specified local transform. The applied transform is set to the + /// specified local transform. Child bones are not updated. + /// + /// See World transforms in the Spine + /// Runtimes Guide. + public void UpdateWorldTransform(float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY) + { + ax = x; + ay = y; + arotation = rotation; + ascaleX = scaleX; + ascaleY = scaleY; + ashearX = shearX; + ashearY = shearY; + + Bone parent = this.parent; + if (parent == null) + { // Root bone. + float rotationY = rotation + 90 + shearY, sx = skeleton.ScaleX, sy = skeleton.ScaleY; + a = MathUtils.CosDeg(rotation + shearX) * scaleX * sx; + b = MathUtils.CosDeg(rotationY) * scaleY * sx; + c = MathUtils.SinDeg(rotation + shearX) * scaleX * sy; + d = MathUtils.SinDeg(rotationY) * scaleY * sy; + worldX = x * sx + skeleton.x; + worldY = y * sy + skeleton.y; + return; + } + + float 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 (data.transformMode) + { + case TransformMode.Normal: + { + float rotationY = rotation + 90 + shearY; + float la = MathUtils.CosDeg(rotation + shearX) * scaleX; + float lb = MathUtils.CosDeg(rotationY) * scaleY; + float lc = MathUtils.SinDeg(rotation + shearX) * scaleX; + float ld = MathUtils.SinDeg(rotationY) * scaleY; + a = pa * la + pb * lc; + b = pa * lb + pb * ld; + c = pc * la + pd * lc; + d = pc * lb + pd * ld; + return; + } + case TransformMode.OnlyTranslation: + { + float rotationY = rotation + 90 + shearY; + a = MathUtils.CosDeg(rotation + shearX) * scaleX; + b = MathUtils.CosDeg(rotationY) * scaleY; + c = MathUtils.SinDeg(rotation + shearX) * scaleX; + d = MathUtils.SinDeg(rotationY) * scaleY; + break; + } + case TransformMode.NoRotationOrReflection: + { + float s = pa * pa + pc * pc, prx; + if (s > 0.0001f) + { + s = Math.Abs(pa * pd - pb * pc) / s; + pa /= skeleton.ScaleX; + pc /= skeleton.ScaleY; + pb = pc * s; + pd = pa * s; + prx = MathUtils.Atan2(pc, pa) * MathUtils.RadDeg; + } + else + { + pa = 0; + pc = 0; + prx = 90 - MathUtils.Atan2(pd, pb) * MathUtils.RadDeg; + } + float rx = rotation + shearX - prx; + float ry = rotation + shearY - prx + 90; + float la = MathUtils.CosDeg(rx) * scaleX; + float lb = MathUtils.CosDeg(ry) * scaleY; + float lc = MathUtils.SinDeg(rx) * scaleX; + float ld = MathUtils.SinDeg(ry) * scaleY; + a = pa * la - pb * lc; + b = pa * lb - pb * ld; + c = pc * la + pd * lc; + d = pc * lb + pd * ld; + break; + } + case TransformMode.NoScale: + case TransformMode.NoScaleOrReflection: + { + float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation); + float za = (pa * cos + pb * sin) / skeleton.ScaleX; + float zc = (pc * cos + pd * sin) / skeleton.ScaleY; + float s = (float)Math.Sqrt(za * za + zc * zc); + if (s > 0.00001f) s = 1 / s; + za *= s; + zc *= s; + s = (float)Math.Sqrt(za * za + zc * zc); + if (data.transformMode == TransformMode.NoScale + && (pa * pd - pb * pc < 0) != (skeleton.ScaleX < 0 != skeleton.ScaleY < 0)) s = -s; + + float r = MathUtils.PI / 2 + MathUtils.Atan2(zc, za); + float zb = MathUtils.Cos(r) * s; + float zd = MathUtils.Sin(r) * s; + float la = MathUtils.CosDeg(shearX) * scaleX; + float lb = MathUtils.CosDeg(90 + shearY) * scaleY; + float lc = MathUtils.SinDeg(shearX) * scaleX; + float ld = MathUtils.SinDeg(90 + shearY) * scaleY; + a = za * la + zb * lc; + b = za * lb + zb * ld; + c = zc * la + zd * lc; + d = zc * lb + zd * ld; + break; + } + } + + a *= skeleton.ScaleX; + b *= skeleton.ScaleX; + c *= skeleton.ScaleY; + d *= skeleton.ScaleY; + } + + public void SetToSetupPose() + { + BoneData data = this.data; + x = data.x; + y = data.y; + rotation = data.rotation; + scaleX = data.scaleX; + scaleY = data.scaleY; + shearX = data.shearX; + shearY = data.shearY; + } + + /// + /// Computes the applied transform values from the world transform. + /// + /// If the world transform is modified (by a constraint, , etc) then this method should be called so + /// the applied transform matches the world transform. The applied transform may be needed by other code (eg to apply another + /// constraint). + /// + /// Some information is ambiguous in the world transform, such as -1,-1 scale versus 180 rotation. The applied transform after + /// calling this method is equivalent to the local transform used to compute the world transform, but may not be identical. + /// + public void UpdateAppliedTransform() + { + Bone parent = this.parent; + if (parent == null) + { + ax = worldX - skeleton.x; + ay = worldY - skeleton.y; + arotation = MathUtils.Atan2(c, a) * MathUtils.RadDeg; + ascaleX = (float)Math.Sqrt(a * a + c * c); + ascaleY = (float)Math.Sqrt(b * b + d * d); + ashearX = 0; + ashearY = MathUtils.Atan2(a * b + c * d, a * d - b * c) * MathUtils.RadDeg; + return; + } + float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d; + float pid = 1 / (pa * pd - pb * pc); + float dx = worldX - parent.worldX, dy = worldY - parent.worldY; + ax = (dx * pd * pid - dy * pb * pid); + ay = (dy * pa * pid - dx * pc * pid); + float ia = pid * pd; + float id = pid * pa; + float ib = pid * pb; + float ic = pid * pc; + float ra = ia * a - ib * c; + float rb = ia * b - ib * d; + float rc = id * c - ic * a; + float rd = id * d - ic * b; + ashearX = 0; + ascaleX = (float)Math.Sqrt(ra * ra + rc * rc); + if (ascaleX > 0.0001f) + { + float det = ra * rd - rb * rc; + ascaleY = det / ascaleX; + ashearY = MathUtils.Atan2(ra * rb + rc * rd, det) * MathUtils.RadDeg; + arotation = MathUtils.Atan2(rc, ra) * MathUtils.RadDeg; + } + else + { + ascaleX = 0; + ascaleY = (float)Math.Sqrt(rb * rb + rd * rd); + ashearY = 0; + arotation = 90 - MathUtils.Atan2(rd, rb) * MathUtils.RadDeg; + } + } + + public void WorldToLocal(float worldX, float worldY, out float localX, out float localY) + { + float a = this.a, b = this.b, c = this.c, d = this.d; + float det = a * d - b * c; + float x = worldX - this.worldX, y = worldY - this.worldY; + localX = (x * d - y * b) / det; + localY = (y * a - x * c) / det; + } + + public void LocalToWorld(float localX, float localY, out float worldX, out float worldY) + { + worldX = localX * a + localY * b + this.worldX; + worldY = localX * c + localY * d + this.worldY; + } + + public float WorldToLocalRotationX + { + get + { + Bone parent = this.parent; + if (parent == null) return arotation; + float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, a = this.a, c = this.c; + return MathUtils.Atan2(pa * c - pc * a, pd * a - pb * c) * MathUtils.RadDeg; + } + } + + public float WorldToLocalRotationY + { + get + { + Bone parent = this.parent; + if (parent == null) return arotation; + float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, b = this.b, d = this.d; + return MathUtils.Atan2(pa * d - pc * b, pd * b - pb * d) * MathUtils.RadDeg; + } + } + + public float WorldToLocalRotation(float worldRotation) + { + float sin = MathUtils.SinDeg(worldRotation), cos = MathUtils.CosDeg(worldRotation); + return MathUtils.Atan2(a * sin - c * cos, d * cos - b * sin) * MathUtils.RadDeg + rotation - shearX; + } + + public float LocalToWorldRotation(float localRotation) + { + localRotation -= rotation - shearX; + float sin = MathUtils.SinDeg(localRotation), cos = MathUtils.CosDeg(localRotation); + return MathUtils.Atan2(cos * c + sin * d, cos * a + sin * b) * MathUtils.RadDeg; + } + + /// + /// Rotates the world transform the specified amount. + /// + /// After changes are made to the world transform, should be called and will + /// need to be called on any child bones, recursively. + /// + public void RotateWorld(float degrees) + { + float a = this.a, b = this.b, c = this.c, d = this.d; + float cos = MathUtils.CosDeg(degrees), sin = MathUtils.SinDeg(degrees); + this.a = cos * a - sin * c; + this.b = cos * b - sin * d; + this.c = sin * a + cos * c; + this.d = sin * b + cos * d; + } + + override public string ToString() + { + return data.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/BoneData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/BoneData.cs index ef67d6c..4af2671 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/BoneData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/BoneData.cs @@ -29,77 +29,82 @@ using System; -namespace Spine4_0_64 { - public class BoneData { - internal int index; - internal string name; - internal BoneData parent; - internal float length; - internal float x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY; - internal TransformMode transformMode = TransformMode.Normal; - internal bool skinRequired; - - /// The index of the bone in Skeleton.Bones - public int Index { get { return index; } } - - /// The name of the bone, which is unique across all bones in the skeleton. - public string Name { get { return name; } } - - /// May be null. - public BoneData Parent { get { return parent; } } - - public float Length { get { return length; } set { length = value; } } - - /// Local X translation. - public float X { get { return x; } set { x = value; } } - - /// Local Y translation. - public float Y { get { return y; } set { y = value; } } - - /// Local rotation. - public float Rotation { get { return rotation; } set { rotation = value; } } - - /// Local scaleX. - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - - /// Local scaleY. - public float ScaleY { get { return scaleY; } set { scaleY = value; } } - - /// Local shearX. - public float ShearX { get { return shearX; } set { shearX = value; } } - - /// Local shearY. - public float ShearY { get { return shearY; } set { shearY = value; } } - - /// The transform mode for how parent world transforms affect this bone. - public TransformMode TransformMode { get { return transformMode; } set { transformMode = value; } } - - ///When true, only updates this bone if the contains this - /// bone. - /// - public bool SkinRequired { get { return skinRequired; } set { skinRequired = value; } } - - /// May be null. - public BoneData (int index, string name, BoneData parent) { - if (index < 0) throw new ArgumentException("index must be >= 0", "index"); - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.index = index; - this.name = name; - this.parent = parent; - } - - override public string ToString () { - return name; - } - } - - [Flags] - public enum TransformMode { - //0000 0 Flip Scale Rotation - Normal = 0, // 0000 - OnlyTranslation = 7, // 0111 - NoRotationOrReflection = 1, // 0001 - NoScale = 2, // 0010 - NoScaleOrReflection = 6, // 0110 - } +namespace Spine4_0_64 +{ + public class BoneData + { + internal int index; + internal string name; + internal BoneData parent; + internal float length; + internal float x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY; + internal TransformMode transformMode = TransformMode.Normal; + internal bool skinRequired; + + /// The index of the bone in Skeleton.Bones + public int Index { get { return index; } } + + /// The name of the bone, which is unique across all bones in the skeleton. + public string Name { get { return name; } } + + /// May be null. + public BoneData Parent { get { return parent; } } + + public float Length { get { return length; } set { length = value; } } + + /// Local X translation. + public float X { get { return x; } set { x = value; } } + + /// Local Y translation. + public float Y { get { return y; } set { y = value; } } + + /// Local rotation. + public float Rotation { get { return rotation; } set { rotation = value; } } + + /// Local scaleX. + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + + /// Local scaleY. + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + + /// Local shearX. + public float ShearX { get { return shearX; } set { shearX = value; } } + + /// Local shearY. + public float ShearY { get { return shearY; } set { shearY = value; } } + + /// The transform mode for how parent world transforms affect this bone. + public TransformMode TransformMode { get { return transformMode; } set { transformMode = value; } } + + ///When true, only updates this bone if the contains this + /// bone. + /// + public bool SkinRequired { get { return skinRequired; } set { skinRequired = value; } } + + /// May be null. + public BoneData(int index, string name, BoneData parent) + { + if (index < 0) throw new ArgumentException("index must be >= 0", "index"); + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.index = index; + this.name = name; + this.parent = parent; + } + + override public string ToString() + { + return name; + } + } + + [Flags] + public enum TransformMode + { + //0000 0 Flip Scale Rotation + Normal = 0, // 0000 + OnlyTranslation = 7, // 0111 + NoRotationOrReflection = 1, // 0001 + NoScale = 2, // 0010 + NoScaleOrReflection = 6, // 0110 + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/ConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/ConstraintData.cs index a69181e..28fed48 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/ConstraintData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/ConstraintData.cs @@ -28,34 +28,37 @@ *****************************************************************************/ using System; -using System.Collections.Generic; -namespace Spine4_0_64 { - /// The base class for all constraint datas. - public abstract class ConstraintData { - internal readonly string name; - internal int order; - internal bool skinRequired; +namespace Spine4_0_64 +{ + /// The base class for all constraint datas. + public abstract class ConstraintData + { + internal readonly string name; + internal int order; + internal bool skinRequired; - public ConstraintData (string name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; - } + public ConstraintData(string name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + } - /// The constraint's name, which is unique across all constraints in the skeleton of the same type. - public string Name { get { return name; } } + /// The constraint's name, which is unique across all constraints in the skeleton of the same type. + public string Name { get { return name; } } - ///The ordinal of this constraint for the order a skeleton's constraints will be applied by - /// . - public int Order { get { return order; } set { order = value; } } + ///The ordinal of this constraint for the order a skeleton's constraints will be applied by + /// . + public int Order { get { return order; } set { order = value; } } - ///When true, only updates this constraint if the contains - /// this constraint. - /// - public bool SkinRequired { get { return skinRequired; } set { skinRequired = value; } } + ///When true, only updates this constraint if the contains + /// this constraint. + /// + public bool SkinRequired { get { return skinRequired; } set { skinRequired = value; } } - override public string ToString () { - return name; - } - } + override public string ToString() + { + return name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Event.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Event.cs index feb65f2..e17bd1e 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Event.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Event.cs @@ -29,36 +29,40 @@ using System; -namespace Spine4_0_64 { - /// Stores the current pose values for an Event. - public class Event { - internal readonly EventData data; - internal readonly float time; - internal int intValue; - internal float floatValue; - internal string stringValue; - internal float volume; - internal float balance; +namespace Spine4_0_64 +{ + /// Stores the current pose values for an Event. + public class Event + { + internal readonly EventData data; + internal readonly float time; + internal int intValue; + internal float floatValue; + internal string stringValue; + internal float volume; + internal float balance; - public EventData Data { get { return data; } } - /// The animation time this event was keyed. - public float Time { get { return time; } } + public EventData Data { get { return data; } } + /// The animation time this event was keyed. + public float Time { get { return time; } } - public int Int { get { return intValue; } set { intValue = value; } } - public float Float { get { return floatValue; } set { floatValue = value; } } - public string String { get { return stringValue; } set { stringValue = value; } } + public int Int { get { return intValue; } set { intValue = value; } } + public float Float { get { return floatValue; } set { floatValue = value; } } + public string String { get { return stringValue; } set { stringValue = value; } } - public float Volume { get { return volume; } set { volume = value; } } - public float Balance { get { return balance; } set { balance = value; } } + public float Volume { get { return volume; } set { volume = value; } } + public float Balance { get { return balance; } set { balance = value; } } - public Event (float time, EventData data) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - this.time = time; - this.data = data; - } + public Event(float time, EventData data) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + this.time = time; + this.data = data; + } - override public string ToString () { - return this.data.Name; - } - } + override public string ToString() + { + return this.data.Name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/EventData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/EventData.cs index 26379c5..7d387a4 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/EventData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/EventData.cs @@ -29,28 +29,32 @@ using System; -namespace Spine4_0_64 { - /// Stores the setup pose values for an Event. - public class EventData { - internal string name; +namespace Spine4_0_64 +{ + /// Stores the setup pose values for an Event. + public class EventData + { + internal string name; - /// The name of the event, which is unique across all events in the skeleton. - public string Name { get { return name; } } - public int Int { get; set; } - public float Float { get; set; } - public string @String { get; set; } + /// The name of the event, which is unique across all events in the skeleton. + public string Name { get { return name; } } + public int Int { get; set; } + public float Float { get; set; } + public string @String { get; set; } - public string AudioPath { get; set; } - public float Volume { get; set; } - public float Balance { get; set; } + public string AudioPath { get; set; } + public float Volume { get; set; } + public float Balance { get; set; } - public EventData (string name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; - } + public EventData(string name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + } - override public string ToString () { - return Name; - } - } + override public string ToString() + { + return Name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/ExposedList.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/ExposedList.cs index 8f4ab4f..d6bb41d 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/ExposedList.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/ExposedList.cs @@ -35,603 +35,700 @@ using System.Collections.Generic; using System.Diagnostics; -namespace Spine4_0_64 { - [DebuggerDisplay("Count={Count}")] - public class ExposedList : IEnumerable { - public T[] Items; - public int Count; - private const int DefaultCapacity = 4; - private static readonly T[] EmptyArray = new T[0]; - private int version; - - public ExposedList () { - Items = EmptyArray; - } - - public ExposedList (IEnumerable collection) { - CheckCollection(collection); - - // initialize to needed size (if determinable) - ICollection c = collection as ICollection; - if (c == null) { - Items = EmptyArray; - AddEnumerable(collection); - } else { - Items = new T[c.Count]; - AddCollection(c); - } - } - - public ExposedList (int capacity) { - if (capacity < 0) - throw new ArgumentOutOfRangeException("capacity"); - Items = new T[capacity]; - } - - internal ExposedList (T[] data, int size) { - Items = data; - Count = size; - } - - public void Add (T item) { - // If we check to see if we need to grow before trying to grow - // we can speed things up by 25% - if (Count == Items.Length) - GrowIfNeeded(1); - Items[Count++] = item; - version++; - } - - public void GrowIfNeeded (int addedCount) { - int minimumSize = Count + addedCount; - if (minimumSize > Items.Length) - Capacity = Math.Max(Math.Max(Capacity * 2, DefaultCapacity), minimumSize); - } - - public ExposedList Resize (int newSize) { - int itemsLength = Items.Length; - var oldItems = Items; - if (newSize > itemsLength) { - Array.Resize(ref Items, newSize); - } else if (newSize < itemsLength) { - // Allow nulling of T reference type to allow GC. - for (int i = newSize; i < itemsLength; i++) - oldItems[i] = default(T); - } - Count = newSize; - return this; - } - - public void EnsureCapacity (int min) { - if (Items.Length < min) { - int newCapacity = Items.Length == 0 ? DefaultCapacity : Items.Length * 2; - //if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength; - if (newCapacity < min) newCapacity = min; - Capacity = newCapacity; - } - } - - private void CheckRange (int index, int count) { - if (index < 0) - throw new ArgumentOutOfRangeException("index"); - - if (count < 0) - throw new ArgumentOutOfRangeException("count"); - - if ((uint)index + (uint)count > (uint)Count) - throw new ArgumentException("index and count exceed length of list"); - } - - private void AddCollection (ICollection collection) { - int collectionCount = collection.Count; - if (collectionCount == 0) - return; - - GrowIfNeeded(collectionCount); - collection.CopyTo(Items, Count); - Count += collectionCount; - } - - private void AddEnumerable (IEnumerable enumerable) { - foreach (T t in enumerable) { - Add(t); - } - } - - // Additional overload provided because ExposedList only implements IEnumerable, - // leading to sub-optimal behavior: It grows multiple times as it assumes not - // to know the final size ahead of insertion. - public void AddRange (ExposedList list) { - CheckCollection(list); - - int collectionCount = list.Count; - if (collectionCount == 0) - return; - - GrowIfNeeded(collectionCount); - list.CopyTo(Items, Count); - Count += collectionCount; - - version++; - } - - public void AddRange (IEnumerable collection) { - CheckCollection(collection); - - ICollection c = collection as ICollection; - if (c != null) - AddCollection(c); - else - AddEnumerable(collection); - version++; - } - - public int BinarySearch (T item) { - return Array.BinarySearch(Items, 0, Count, item); - } - - public int BinarySearch (T item, IComparer comparer) { - return Array.BinarySearch(Items, 0, Count, item, comparer); - } - - public int BinarySearch (int index, int count, T item, IComparer comparer) { - CheckRange(index, count); - return Array.BinarySearch(Items, index, count, item, comparer); - } - - public void Clear (bool clearArray = true) { - if (clearArray) - Array.Clear(Items, 0, Items.Length); - - Count = 0; - version++; - } - - public bool Contains (T item) { - return Array.IndexOf(Items, item, 0, Count) != -1; - } - - public ExposedList ConvertAll (Converter converter) { - if (converter == null) - throw new ArgumentNullException("converter"); - ExposedList u = new ExposedList(Count); - u.Count = Count; - T[] items = Items; - TOutput[] uItems = u.Items; - for (int i = 0; i < Count; i++) - uItems[i] = converter(items[i]); - return u; - } - - public void CopyTo (T[] array) { - Array.Copy(Items, 0, array, 0, Count); - } - - public void CopyTo (T[] array, int arrayIndex) { - Array.Copy(Items, 0, array, arrayIndex, Count); - } - - public void CopyTo (int index, T[] array, int arrayIndex, int count) { - CheckRange(index, count); - Array.Copy(Items, index, array, arrayIndex, count); - } - - public bool Exists (Predicate match) { - CheckMatch(match); - return GetIndex(0, Count, match) != -1; - } - - public T Find (Predicate match) { - CheckMatch(match); - int i = GetIndex(0, Count, match); - return (i != -1) ? Items[i] : default(T); - } - - private static void CheckMatch (Predicate match) { - if (match == null) - throw new ArgumentNullException("match"); - } - - public ExposedList FindAll (Predicate match) { - CheckMatch(match); - return FindAllList(match); - } - - private ExposedList FindAllList (Predicate match) { - ExposedList results = new ExposedList(); - for (int i = 0; i < Count; i++) - if (match(Items[i])) - results.Add(Items[i]); - - return results; - } - - public int FindIndex (Predicate match) { - CheckMatch(match); - return GetIndex(0, Count, match); - } - - public int FindIndex (int startIndex, Predicate match) { - CheckMatch(match); - CheckIndex(startIndex); - return GetIndex(startIndex, Count - startIndex, match); - } - - public int FindIndex (int startIndex, int count, Predicate match) { - CheckMatch(match); - CheckRange(startIndex, count); - return GetIndex(startIndex, count, match); - } - - private int GetIndex (int startIndex, int count, Predicate match) { - int end = startIndex + count; - for (int i = startIndex; i < end; i++) - if (match(Items[i])) - return i; - - return -1; - } - - public T FindLast (Predicate match) { - CheckMatch(match); - int i = GetLastIndex(0, Count, match); - return i == -1 ? default(T) : Items[i]; - } - - public int FindLastIndex (Predicate match) { - CheckMatch(match); - return GetLastIndex(0, Count, match); - } - - public int FindLastIndex (int startIndex, Predicate match) { - CheckMatch(match); - CheckIndex(startIndex); - return GetLastIndex(0, startIndex + 1, match); - } - - public int FindLastIndex (int startIndex, int count, Predicate match) { - CheckMatch(match); - int start = startIndex - count + 1; - CheckRange(start, count); - return GetLastIndex(start, count, match); - } - - private int GetLastIndex (int startIndex, int count, Predicate match) { - // unlike FindLastIndex, takes regular params for search range - for (int i = startIndex + count; i != startIndex;) - if (match(Items[--i])) - return i; - return -1; - } - - public void ForEach (Action action) { - if (action == null) - throw new ArgumentNullException("action"); - for (int i = 0; i < Count; i++) - action(Items[i]); - } - - public Enumerator GetEnumerator () { - return new Enumerator(this); - } - - public ExposedList GetRange (int index, int count) { - CheckRange(index, count); - T[] tmpArray = new T[count]; - Array.Copy(Items, index, tmpArray, 0, count); - return new ExposedList(tmpArray, count); - } - - public int IndexOf (T item) { - return Array.IndexOf(Items, item, 0, Count); - } - - public int IndexOf (T item, int index) { - CheckIndex(index); - return Array.IndexOf(Items, item, index, Count - index); - } - - public int IndexOf (T item, int index, int count) { - if (index < 0) - throw new ArgumentOutOfRangeException("index"); - - if (count < 0) - throw new ArgumentOutOfRangeException("count"); - - if ((uint)index + (uint)count > (uint)Count) - throw new ArgumentOutOfRangeException("index and count exceed length of list"); - - return Array.IndexOf(Items, item, index, count); - } - - private void Shift (int start, int delta) { - if (delta < 0) - start -= delta; - - if (start < Count) - Array.Copy(Items, start, Items, start + delta, Count - start); - - Count += delta; - - if (delta < 0) - Array.Clear(Items, Count, -delta); - } - - private void CheckIndex (int index) { - if (index < 0 || (uint)index > (uint)Count) - throw new ArgumentOutOfRangeException("index"); - } - - public void Insert (int index, T item) { - CheckIndex(index); - if (Count == Items.Length) - GrowIfNeeded(1); - Shift(index, 1); - Items[index] = item; - version++; - } - - private void CheckCollection (IEnumerable collection) { - if (collection == null) - throw new ArgumentNullException("collection"); - } - - public void InsertRange (int index, IEnumerable collection) { - CheckCollection(collection); - CheckIndex(index); - if (collection == this) { - T[] buffer = new T[Count]; - CopyTo(buffer, 0); - GrowIfNeeded(Count); - Shift(index, buffer.Length); - Array.Copy(buffer, 0, Items, index, buffer.Length); - } else { - ICollection c = collection as ICollection; - if (c != null) - InsertCollection(index, c); - else - InsertEnumeration(index, collection); - } - version++; - } - - private void InsertCollection (int index, ICollection collection) { - int collectionCount = collection.Count; - GrowIfNeeded(collectionCount); - - Shift(index, collectionCount); - collection.CopyTo(Items, index); - } - - private void InsertEnumeration (int index, IEnumerable enumerable) { - foreach (T t in enumerable) - Insert(index++, t); - } - - public int LastIndexOf (T item) { - return Array.LastIndexOf(Items, item, Count - 1, Count); - } - - public int LastIndexOf (T item, int index) { - CheckIndex(index); - return Array.LastIndexOf(Items, item, index, index + 1); - } - - public int LastIndexOf (T item, int index, int count) { - if (index < 0) - throw new ArgumentOutOfRangeException("index", index, "index is negative"); - - if (count < 0) - throw new ArgumentOutOfRangeException("count", count, "count is negative"); - - if (index - count + 1 < 0) - throw new ArgumentOutOfRangeException("count", count, "count is too large"); - - return Array.LastIndexOf(Items, item, index, count); - } - - public bool Remove (T item) { - int loc = IndexOf(item); - if (loc != -1) - RemoveAt(loc); - - return loc != -1; - } - - public int RemoveAll (Predicate match) { - CheckMatch(match); - int i = 0; - int j = 0; - - // Find the first item to remove - for (i = 0; i < Count; i++) - if (match(Items[i])) - break; - - if (i == Count) - return 0; - - version++; - - // Remove any additional items - for (j = i + 1; j < Count; j++) { - if (!match(Items[j])) - Items[i++] = Items[j]; - } - if (j - i > 0) - Array.Clear(Items, i, j - i); - - Count = i; - return (j - i); - } - - public void RemoveAt (int index) { - if (index < 0 || (uint)index >= (uint)Count) - throw new ArgumentOutOfRangeException("index"); - Shift(index, -1); - Array.Clear(Items, Count, 1); - version++; - } - - // Spine Added Method - // Based on Stack.Pop(); https://referencesource.microsoft.com/#mscorlib/system/collections/stack.cs - /// Pops the last item of the list. If the list is empty, Pop throws an InvalidOperationException. - public T Pop () { - if (Count == 0) - throw new InvalidOperationException("List is empty. Nothing to pop."); - - int i = Count - 1; - T item = Items[i]; - Items[i] = default(T); - Count--; - version++; - return item; - } - - public void RemoveRange (int index, int count) { - CheckRange(index, count); - if (count > 0) { - Shift(index, -count); - Array.Clear(Items, Count, count); - version++; - } - } - - public void Reverse () { - Array.Reverse(Items, 0, Count); - version++; - } - - public void Reverse (int index, int count) { - CheckRange(index, count); - Array.Reverse(Items, index, count); - version++; - } - - public void Sort () { - Array.Sort(Items, 0, Count, Comparer.Default); - version++; - } - - public void Sort (IComparer comparer) { - Array.Sort(Items, 0, Count, comparer); - version++; - } - - public void Sort (Comparison comparison) { - Array.Sort(Items, comparison); - version++; - } - - public void Sort (int index, int count, IComparer comparer) { - CheckRange(index, count); - Array.Sort(Items, index, count, comparer); - version++; - } - - public T[] ToArray () { - T[] t = new T[Count]; - Array.Copy(Items, t, Count); - - return t; - } - - public void TrimExcess () { - Capacity = Count; - } - - public bool TrueForAll (Predicate match) { - CheckMatch(match); - - for (int i = 0; i < Count; i++) - if (!match(Items[i])) - return false; - - return true; - } - - public int Capacity { - get { - return Items.Length; - } - set { - if ((uint)value < (uint)Count) - throw new ArgumentOutOfRangeException(); - - Array.Resize(ref Items, value); - } - } - - #region Interface implementations. - - IEnumerator IEnumerable.GetEnumerator () { - return GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator () { - return GetEnumerator(); - } - - #endregion - - public struct Enumerator : IEnumerator, IDisposable { - private ExposedList l; - private int next; - private int ver; - private T current; - - internal Enumerator (ExposedList l) - : this() { - this.l = l; - ver = l.version; - } - - public void Dispose () { - l = null; - } - - private void VerifyState () { - if (l == null) - throw new ObjectDisposedException(GetType().FullName); - if (ver != l.version) - throw new InvalidOperationException( - "Collection was modified; enumeration operation may not execute."); - } - - public bool MoveNext () { - VerifyState(); - - if (next < 0) - return false; - - if (next < l.Count) { - current = l.Items[next++]; - return true; - } - - next = -1; - return false; - } - - public T Current { - get { - return current; - } - } - - void IEnumerator.Reset () { - VerifyState(); - next = 0; - } - - object IEnumerator.Current { - get { - VerifyState(); - if (next <= 0) - throw new InvalidOperationException(); - return current; - } - } - } - } +namespace Spine4_0_64 +{ + [DebuggerDisplay("Count={Count}")] + public class ExposedList : IEnumerable + { + public T[] Items; + public int Count; + private const int DefaultCapacity = 4; + private static readonly T[] EmptyArray = new T[0]; + private int version; + + public ExposedList() + { + Items = EmptyArray; + } + + public ExposedList(IEnumerable collection) + { + CheckCollection(collection); + + // initialize to needed size (if determinable) + ICollection c = collection as ICollection; + if (c == null) + { + Items = EmptyArray; + AddEnumerable(collection); + } + else + { + Items = new T[c.Count]; + AddCollection(c); + } + } + + public ExposedList(int capacity) + { + if (capacity < 0) + throw new ArgumentOutOfRangeException("capacity"); + Items = new T[capacity]; + } + + internal ExposedList(T[] data, int size) + { + Items = data; + Count = size; + } + + public void Add(T item) + { + // If we check to see if we need to grow before trying to grow + // we can speed things up by 25% + if (Count == Items.Length) + GrowIfNeeded(1); + Items[Count++] = item; + version++; + } + + public void GrowIfNeeded(int addedCount) + { + int minimumSize = Count + addedCount; + if (minimumSize > Items.Length) + Capacity = Math.Max(Math.Max(Capacity * 2, DefaultCapacity), minimumSize); + } + + public ExposedList Resize(int newSize) + { + int itemsLength = Items.Length; + var oldItems = Items; + if (newSize > itemsLength) + { + Array.Resize(ref Items, newSize); + } + else if (newSize < itemsLength) + { + // Allow nulling of T reference type to allow GC. + for (int i = newSize; i < itemsLength; i++) + oldItems[i] = default(T); + } + Count = newSize; + return this; + } + + public void EnsureCapacity(int min) + { + if (Items.Length < min) + { + int newCapacity = Items.Length == 0 ? DefaultCapacity : Items.Length * 2; + //if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength; + if (newCapacity < min) newCapacity = min; + Capacity = newCapacity; + } + } + + private void CheckRange(int index, int count) + { + if (index < 0) + throw new ArgumentOutOfRangeException("index"); + + if (count < 0) + throw new ArgumentOutOfRangeException("count"); + + if ((uint)index + (uint)count > (uint)Count) + throw new ArgumentException("index and count exceed length of list"); + } + + private void AddCollection(ICollection collection) + { + int collectionCount = collection.Count; + if (collectionCount == 0) + return; + + GrowIfNeeded(collectionCount); + collection.CopyTo(Items, Count); + Count += collectionCount; + } + + private void AddEnumerable(IEnumerable enumerable) + { + foreach (T t in enumerable) + { + Add(t); + } + } + + // Additional overload provided because ExposedList only implements IEnumerable, + // leading to sub-optimal behavior: It grows multiple times as it assumes not + // to know the final size ahead of insertion. + public void AddRange(ExposedList list) + { + CheckCollection(list); + + int collectionCount = list.Count; + if (collectionCount == 0) + return; + + GrowIfNeeded(collectionCount); + list.CopyTo(Items, Count); + Count += collectionCount; + + version++; + } + + public void AddRange(IEnumerable collection) + { + CheckCollection(collection); + + ICollection c = collection as ICollection; + if (c != null) + AddCollection(c); + else + AddEnumerable(collection); + version++; + } + + public int BinarySearch(T item) + { + return Array.BinarySearch(Items, 0, Count, item); + } + + public int BinarySearch(T item, IComparer comparer) + { + return Array.BinarySearch(Items, 0, Count, item, comparer); + } + + public int BinarySearch(int index, int count, T item, IComparer comparer) + { + CheckRange(index, count); + return Array.BinarySearch(Items, index, count, item, comparer); + } + + public void Clear(bool clearArray = true) + { + if (clearArray) + Array.Clear(Items, 0, Items.Length); + + Count = 0; + version++; + } + + public bool Contains(T item) + { + return Array.IndexOf(Items, item, 0, Count) != -1; + } + + public ExposedList ConvertAll(Converter converter) + { + if (converter == null) + throw new ArgumentNullException("converter"); + ExposedList u = new ExposedList(Count); + u.Count = Count; + T[] items = Items; + TOutput[] uItems = u.Items; + for (int i = 0; i < Count; i++) + uItems[i] = converter(items[i]); + return u; + } + + public void CopyTo(T[] array) + { + Array.Copy(Items, 0, array, 0, Count); + } + + public void CopyTo(T[] array, int arrayIndex) + { + Array.Copy(Items, 0, array, arrayIndex, Count); + } + + public void CopyTo(int index, T[] array, int arrayIndex, int count) + { + CheckRange(index, count); + Array.Copy(Items, index, array, arrayIndex, count); + } + + public bool Exists(Predicate match) + { + CheckMatch(match); + return GetIndex(0, Count, match) != -1; + } + + public T Find(Predicate match) + { + CheckMatch(match); + int i = GetIndex(0, Count, match); + return (i != -1) ? Items[i] : default(T); + } + + private static void CheckMatch(Predicate match) + { + if (match == null) + throw new ArgumentNullException("match"); + } + + public ExposedList FindAll(Predicate match) + { + CheckMatch(match); + return FindAllList(match); + } + + private ExposedList FindAllList(Predicate match) + { + ExposedList results = new ExposedList(); + for (int i = 0; i < Count; i++) + if (match(Items[i])) + results.Add(Items[i]); + + return results; + } + + public int FindIndex(Predicate match) + { + CheckMatch(match); + return GetIndex(0, Count, match); + } + + public int FindIndex(int startIndex, Predicate match) + { + CheckMatch(match); + CheckIndex(startIndex); + return GetIndex(startIndex, Count - startIndex, match); + } + + public int FindIndex(int startIndex, int count, Predicate match) + { + CheckMatch(match); + CheckRange(startIndex, count); + return GetIndex(startIndex, count, match); + } + + private int GetIndex(int startIndex, int count, Predicate match) + { + int end = startIndex + count; + for (int i = startIndex; i < end; i++) + if (match(Items[i])) + return i; + + return -1; + } + + public T FindLast(Predicate match) + { + CheckMatch(match); + int i = GetLastIndex(0, Count, match); + return i == -1 ? default(T) : Items[i]; + } + + public int FindLastIndex(Predicate match) + { + CheckMatch(match); + return GetLastIndex(0, Count, match); + } + + public int FindLastIndex(int startIndex, Predicate match) + { + CheckMatch(match); + CheckIndex(startIndex); + return GetLastIndex(0, startIndex + 1, match); + } + + public int FindLastIndex(int startIndex, int count, Predicate match) + { + CheckMatch(match); + int start = startIndex - count + 1; + CheckRange(start, count); + return GetLastIndex(start, count, match); + } + + private int GetLastIndex(int startIndex, int count, Predicate match) + { + // unlike FindLastIndex, takes regular params for search range + for (int i = startIndex + count; i != startIndex;) + if (match(Items[--i])) + return i; + return -1; + } + + public void ForEach(Action action) + { + if (action == null) + throw new ArgumentNullException("action"); + for (int i = 0; i < Count; i++) + action(Items[i]); + } + + public Enumerator GetEnumerator() + { + return new Enumerator(this); + } + + public ExposedList GetRange(int index, int count) + { + CheckRange(index, count); + T[] tmpArray = new T[count]; + Array.Copy(Items, index, tmpArray, 0, count); + return new ExposedList(tmpArray, count); + } + + public int IndexOf(T item) + { + return Array.IndexOf(Items, item, 0, Count); + } + + public int IndexOf(T item, int index) + { + CheckIndex(index); + return Array.IndexOf(Items, item, index, Count - index); + } + + public int IndexOf(T item, int index, int count) + { + if (index < 0) + throw new ArgumentOutOfRangeException("index"); + + if (count < 0) + throw new ArgumentOutOfRangeException("count"); + + if ((uint)index + (uint)count > (uint)Count) + throw new ArgumentOutOfRangeException("index and count exceed length of list"); + + return Array.IndexOf(Items, item, index, count); + } + + private void Shift(int start, int delta) + { + if (delta < 0) + start -= delta; + + if (start < Count) + Array.Copy(Items, start, Items, start + delta, Count - start); + + Count += delta; + + if (delta < 0) + Array.Clear(Items, Count, -delta); + } + + private void CheckIndex(int index) + { + if (index < 0 || (uint)index > (uint)Count) + throw new ArgumentOutOfRangeException("index"); + } + + public void Insert(int index, T item) + { + CheckIndex(index); + if (Count == Items.Length) + GrowIfNeeded(1); + Shift(index, 1); + Items[index] = item; + version++; + } + + private void CheckCollection(IEnumerable collection) + { + if (collection == null) + throw new ArgumentNullException("collection"); + } + + public void InsertRange(int index, IEnumerable collection) + { + CheckCollection(collection); + CheckIndex(index); + if (collection == this) + { + T[] buffer = new T[Count]; + CopyTo(buffer, 0); + GrowIfNeeded(Count); + Shift(index, buffer.Length); + Array.Copy(buffer, 0, Items, index, buffer.Length); + } + else + { + ICollection c = collection as ICollection; + if (c != null) + InsertCollection(index, c); + else + InsertEnumeration(index, collection); + } + version++; + } + + private void InsertCollection(int index, ICollection collection) + { + int collectionCount = collection.Count; + GrowIfNeeded(collectionCount); + + Shift(index, collectionCount); + collection.CopyTo(Items, index); + } + + private void InsertEnumeration(int index, IEnumerable enumerable) + { + foreach (T t in enumerable) + Insert(index++, t); + } + + public int LastIndexOf(T item) + { + return Array.LastIndexOf(Items, item, Count - 1, Count); + } + + public int LastIndexOf(T item, int index) + { + CheckIndex(index); + return Array.LastIndexOf(Items, item, index, index + 1); + } + + public int LastIndexOf(T item, int index, int count) + { + if (index < 0) + throw new ArgumentOutOfRangeException("index", index, "index is negative"); + + if (count < 0) + throw new ArgumentOutOfRangeException("count", count, "count is negative"); + + if (index - count + 1 < 0) + throw new ArgumentOutOfRangeException("count", count, "count is too large"); + + return Array.LastIndexOf(Items, item, index, count); + } + + public bool Remove(T item) + { + int loc = IndexOf(item); + if (loc != -1) + RemoveAt(loc); + + return loc != -1; + } + + public int RemoveAll(Predicate match) + { + CheckMatch(match); + int i = 0; + int j = 0; + + // Find the first item to remove + for (i = 0; i < Count; i++) + if (match(Items[i])) + break; + + if (i == Count) + return 0; + + version++; + + // Remove any additional items + for (j = i + 1; j < Count; j++) + { + if (!match(Items[j])) + Items[i++] = Items[j]; + } + if (j - i > 0) + Array.Clear(Items, i, j - i); + + Count = i; + return (j - i); + } + + public void RemoveAt(int index) + { + if (index < 0 || (uint)index >= (uint)Count) + throw new ArgumentOutOfRangeException("index"); + Shift(index, -1); + Array.Clear(Items, Count, 1); + version++; + } + + // Spine Added Method + // Based on Stack.Pop(); https://referencesource.microsoft.com/#mscorlib/system/collections/stack.cs + /// Pops the last item of the list. If the list is empty, Pop throws an InvalidOperationException. + public T Pop() + { + if (Count == 0) + throw new InvalidOperationException("List is empty. Nothing to pop."); + + int i = Count - 1; + T item = Items[i]; + Items[i] = default(T); + Count--; + version++; + return item; + } + + public void RemoveRange(int index, int count) + { + CheckRange(index, count); + if (count > 0) + { + Shift(index, -count); + Array.Clear(Items, Count, count); + version++; + } + } + + public void Reverse() + { + Array.Reverse(Items, 0, Count); + version++; + } + + public void Reverse(int index, int count) + { + CheckRange(index, count); + Array.Reverse(Items, index, count); + version++; + } + + public void Sort() + { + Array.Sort(Items, 0, Count, Comparer.Default); + version++; + } + + public void Sort(IComparer comparer) + { + Array.Sort(Items, 0, Count, comparer); + version++; + } + + public void Sort(Comparison comparison) + { + Array.Sort(Items, comparison); + version++; + } + + public void Sort(int index, int count, IComparer comparer) + { + CheckRange(index, count); + Array.Sort(Items, index, count, comparer); + version++; + } + + public T[] ToArray() + { + T[] t = new T[Count]; + Array.Copy(Items, t, Count); + + return t; + } + + public void TrimExcess() + { + Capacity = Count; + } + + public bool TrueForAll(Predicate match) + { + CheckMatch(match); + + for (int i = 0; i < Count; i++) + if (!match(Items[i])) + return false; + + return true; + } + + public int Capacity + { + get + { + return Items.Length; + } + set + { + if ((uint)value < (uint)Count) + throw new ArgumentOutOfRangeException(); + + Array.Resize(ref Items, value); + } + } + + #region Interface implementations. + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion + + public struct Enumerator : IEnumerator, IDisposable + { + private ExposedList l; + private int next; + private int ver; + private T current; + + internal Enumerator(ExposedList l) + : this() + { + this.l = l; + ver = l.version; + } + + public void Dispose() + { + l = null; + } + + private void VerifyState() + { + if (l == null) + throw new ObjectDisposedException(GetType().FullName); + if (ver != l.version) + throw new InvalidOperationException( + "Collection was modified; enumeration operation may not execute."); + } + + public bool MoveNext() + { + VerifyState(); + + if (next < 0) + return false; + + if (next < l.Count) + { + current = l.Items[next++]; + return true; + } + + next = -1; + return false; + } + + public T Current + { + get + { + return current; + } + } + + void IEnumerator.Reset() + { + VerifyState(); + next = 0; + } + + object IEnumerator.Current + { + get + { + VerifyState(); + if (next <= 0) + throw new InvalidOperationException(); + return current; + } + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/IUpdatable.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/IUpdatable.cs index d2a47be..42dab1f 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/IUpdatable.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/IUpdatable.cs @@ -27,16 +27,18 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine4_0_64 { +namespace Spine4_0_64 +{ - ///The interface for items updated by . - public interface IUpdatable { - void Update (); + ///The interface for items updated by . + public interface IUpdatable + { + void Update(); - ///Returns false when this item has not been updated because a skin is required and the active - /// skin does not contain this item. - /// - /// - bool Active { get; } - } + ///Returns false when this item has not been updated because a skin is required and the active + /// skin does not contain this item. + /// + /// + bool Active { get; } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/IkConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/IkConstraint.cs index 9f33a06..e0e0484 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/IkConstraint.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/IkConstraint.cs @@ -29,340 +29,392 @@ using System; -namespace Spine4_0_64 { - /// - /// - /// Stores the current pose for an IK constraint. An IK constraint adjusts the rotation of 1 or 2 constrained bones so the tip of - /// the last bone is as close to the target bone as possible. - /// - /// See IK constraints in the Spine User Guide. - /// - public class IkConstraint : IUpdatable { - internal IkConstraintData data; - internal ExposedList bones = new ExposedList(); - internal Bone target; - internal int bendDirection; - internal bool compress, stretch; - internal float mix = 1, softness; +namespace Spine4_0_64 +{ + /// + /// + /// Stores the current pose for an IK constraint. An IK constraint adjusts the rotation of 1 or 2 constrained bones so the tip of + /// the last bone is as close to the target bone as possible. + /// + /// See IK constraints in the Spine User Guide. + /// + public class IkConstraint : IUpdatable + { + internal IkConstraintData data; + internal ExposedList bones = new ExposedList(); + internal Bone target; + internal int bendDirection; + internal bool compress, stretch; + internal float mix = 1, softness; - internal bool active; + internal bool active; - public IkConstraint (IkConstraintData data, Skeleton skeleton) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.data = data; - mix = data.mix; - softness = data.softness; - bendDirection = data.bendDirection; - compress = data.compress; - stretch = data.stretch; + public IkConstraint(IkConstraintData data, Skeleton skeleton) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + mix = data.mix; + softness = data.softness; + bendDirection = data.bendDirection; + compress = data.compress; + stretch = data.stretch; - bones = new ExposedList(data.bones.Count); - foreach (BoneData boneData in data.bones) - bones.Add(skeleton.FindBone(boneData.name)); - target = skeleton.FindBone(data.target.name); - } + bones = new ExposedList(data.bones.Count); + foreach (BoneData boneData in data.bones) + bones.Add(skeleton.FindBone(boneData.name)); + target = skeleton.FindBone(data.target.name); + } - /// Copy constructor. - public IkConstraint (IkConstraint constraint, Skeleton skeleton) { - if (constraint == null) throw new ArgumentNullException("constraint cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); - data = constraint.data; - bones = new ExposedList(constraint.Bones.Count); - foreach (Bone bone in constraint.Bones) - bones.Add(skeleton.Bones.Items[bone.data.index]); - target = skeleton.Bones.Items[constraint.target.data.index]; - mix = constraint.mix; - softness = constraint.softness; - bendDirection = constraint.bendDirection; - compress = constraint.compress; - stretch = constraint.stretch; - } + /// Copy constructor. + public IkConstraint(IkConstraint constraint, Skeleton skeleton) + { + if (constraint == null) throw new ArgumentNullException("constraint cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); + data = constraint.data; + bones = new ExposedList(constraint.Bones.Count); + foreach (Bone bone in constraint.Bones) + bones.Add(skeleton.Bones.Items[bone.data.index]); + target = skeleton.Bones.Items[constraint.target.data.index]; + mix = constraint.mix; + softness = constraint.softness; + bendDirection = constraint.bendDirection; + compress = constraint.compress; + stretch = constraint.stretch; + } - public void Update () { - if (mix == 0) return; - Bone target = this.target; - var bones = this.bones.Items; - switch (this.bones.Count) { - case 1: - Apply(bones[0], target.worldX, target.worldY, compress, stretch, data.uniform, mix); - break; - case 2: - Apply(bones[0], bones[1], target.worldX, target.worldY, bendDirection, stretch, data.uniform, softness, mix); - break; - } - } + public void Update() + { + if (mix == 0) return; + Bone target = this.target; + var bones = this.bones.Items; + switch (this.bones.Count) + { + case 1: + Apply(bones[0], target.worldX, target.worldY, compress, stretch, data.uniform, mix); + break; + case 2: + Apply(bones[0], bones[1], target.worldX, target.worldY, bendDirection, stretch, data.uniform, softness, mix); + break; + } + } - /// The bones that will be modified by this IK constraint. - public ExposedList Bones { - get { return bones; } - } + /// The bones that will be modified by this IK constraint. + public ExposedList Bones + { + get { return bones; } + } - /// The bone that is the IK target. - public Bone Target { - get { return target; } - set { target = value; } - } + /// The bone that is the IK target. + public Bone Target + { + get { return target; } + set { target = value; } + } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. - /// - /// For two bone IK: if the parent bone has local nonuniform scale, the child bone's local Y translation is set to 0. - /// - public float Mix { - get { return mix; } - set { mix = value; } - } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. + /// + /// For two bone IK: if the parent bone has local nonuniform scale, the child bone's local Y translation is set to 0. + /// + public float Mix + { + get { return mix; } + set { mix = value; } + } - /// For two bone IK, the target bone's distance from the maximum reach of the bones where rotation begins to slow. The bones - /// will not straighten completely until the target is this far out of range. - public float Softness { - get { return softness; } - set { softness = value; } - } + /// For two bone IK, the target bone's distance from the maximum reach of the bones where rotation begins to slow. The bones + /// will not straighten completely until the target is this far out of range. + public float Softness + { + get { return softness; } + set { softness = value; } + } - /// For two bone IK, controls the bend direction of the IK bones, either 1 or -1. - public int BendDirection { - get { return bendDirection; } - set { bendDirection = value; } - } + /// For two bone IK, controls the bend direction of the IK bones, either 1 or -1. + public int BendDirection + { + get { return bendDirection; } + set { bendDirection = value; } + } - /// For one bone IK, when true and the target is too close, the bone is scaled to reach it. - public bool Compress { - get { return compress; } - set { compress = value; } - } + /// For one bone IK, when true and the target is too close, the bone is scaled to reach it. + public bool Compress + { + get { return compress; } + set { compress = value; } + } - /// When true and the target is out of range, the parent bone is scaled to reach it. - /// - /// For two bone IK: 1) the child bone's local Y translation is set to 0, - /// 2) stretch is not applied if is > 0, - /// and 3) if the parent bone has local nonuniform scale, stretch is not applied. - /// - public bool Stretch { - get { return stretch; } - set { stretch = value; } - } + /// When true and the target is out of range, the parent bone is scaled to reach it. + /// + /// For two bone IK: 1) the child bone's local Y translation is set to 0, + /// 2) stretch is not applied if is > 0, + /// and 3) if the parent bone has local nonuniform scale, stretch is not applied. + /// + public bool Stretch + { + get { return stretch; } + set { stretch = value; } + } - public bool Active { - get { return active; } - } + public bool Active + { + get { return active; } + } - /// The IK constraint's setup pose data. - public IkConstraintData Data { - get { return data; } - } + /// The IK constraint's setup pose data. + public IkConstraintData Data + { + get { return data; } + } - override public string ToString () { - return data.name; - } + override public string ToString() + { + return data.name; + } - /// Applies 1 bone IK. The target is specified in the world coordinate system. - static public void Apply (Bone bone, float targetX, float targetY, bool compress, bool stretch, bool uniform, - float alpha) { - if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); - Bone p = bone.parent; + /// Applies 1 bone IK. The target is specified in the world coordinate system. + static public void Apply(Bone bone, float targetX, float targetY, bool compress, bool stretch, bool uniform, + float alpha) + { + if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); + Bone p = bone.parent; - float pa = p.a, pb = p.b, pc = p.c, pd = p.d; - float rotationIK = -bone.ashearX - bone.arotation; - float tx = 0, ty = 0; + 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.data.transformMode) { - case TransformMode.OnlyTranslation: - tx = targetX - bone.worldX; - ty = targetY - bone.worldY; - break; - case TransformMode.NoRotationOrReflection: { - float s = Math.Abs(pa * pd - pb * pc) / (pa * pa + pc * pc); - float sa = pa / bone.skeleton.ScaleX; - float sc = pc / bone.skeleton.ScaleY; - pb = -sc * s * bone.skeleton.ScaleX; - pd = sa * s * bone.skeleton.ScaleY; - rotationIK += (float)Math.Atan2(sc, sa) * MathUtils.RadDeg; - goto default; // Fall through. - } - default: { - float x = targetX - p.worldX, y = targetY - p.worldY; - float d = pa * pd - pb * pc; - tx = (x * pd - y * pb) / d - bone.ax; - ty = (y * pa - x * pc) / d - bone.ay; - break; - } - } + switch (bone.data.transformMode) + { + case TransformMode.OnlyTranslation: + tx = targetX - bone.worldX; + ty = targetY - bone.worldY; + break; + case TransformMode.NoRotationOrReflection: + { + float s = Math.Abs(pa * pd - pb * pc) / (pa * pa + pc * pc); + float sa = pa / bone.skeleton.ScaleX; + float sc = pc / bone.skeleton.ScaleY; + pb = -sc * s * bone.skeleton.ScaleX; + pd = sa * s * bone.skeleton.ScaleY; + rotationIK += (float)Math.Atan2(sc, sa) * MathUtils.RadDeg; + goto default; // Fall through. + } + default: + { + float x = targetX - p.worldX, y = targetY - p.worldY; + float d = pa * pd - pb * pc; + tx = (x * pd - y * pb) / d - bone.ax; + ty = (y * pa - x * pc) / d - bone.ay; + break; + } + } - rotationIK += (float)Math.Atan2(ty, tx) * MathUtils.RadDeg; - if (bone.ascaleX < 0) rotationIK += 180; - if (rotationIK > 180) - rotationIK -= 360; - else if (rotationIK < -180) // - rotationIK += 360; + rotationIK += (float)Math.Atan2(ty, tx) * MathUtils.RadDeg; + if (bone.ascaleX < 0) rotationIK += 180; + if (rotationIK > 180) + rotationIK -= 360; + else if (rotationIK < -180) // + rotationIK += 360; - float sx = bone.ascaleX, sy = bone.ascaleY; - if (compress || stretch) { - switch (bone.data.transformMode) { - case TransformMode.NoScale: - case TransformMode.NoScaleOrReflection: - tx = targetX - bone.worldX; - ty = targetY - bone.worldY; - break; - } - float b = bone.data.length * sx, dd = (float)Math.Sqrt(tx * tx + ty * ty); - if ((compress && dd < b) || (stretch && dd > b) && b > 0.0001f) { - float s = (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); - } + float sx = bone.ascaleX, sy = bone.ascaleY; + if (compress || stretch) + { + switch (bone.data.transformMode) + { + case TransformMode.NoScale: + case TransformMode.NoScaleOrReflection: + tx = targetX - bone.worldX; + ty = targetY - bone.worldY; + break; + } + float b = bone.data.length * sx, dd = (float)Math.Sqrt(tx * tx + ty * ty); + if ((compress && dd < b) || (stretch && dd > b) && b > 0.0001f) + { + float s = (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); + } - /// Applies 2 bone IK. The target is specified in the world coordinate system. - /// A direct descendant of the parent bone. - static public void Apply (Bone parent, Bone child, float targetX, float targetY, int bendDir, bool stretch, bool uniform, - float softness, float alpha) { - if (parent == null) throw new ArgumentNullException("parent", "parent cannot be null."); - if (child == null) throw new ArgumentNullException("child", "child cannot be null."); - float px = parent.ax, py = parent.ay, psx = parent.ascaleX, psy = parent.ascaleY, sx = psx, sy = psy, csx = child.ascaleX; - int os1, os2, s2; - if (psx < 0) { - psx = -psx; - os1 = 180; - s2 = -1; - } else { - os1 = 0; - s2 = 1; - } - if (psy < 0) { - psy = -psy; - s2 = -s2; - } - if (csx < 0) { - csx = -csx; - os2 = 180; - } else - os2 = 0; - float cx = child.ax, cy, cwx, cwy, a = parent.a, b = parent.b, c = parent.c, d = parent.d; - bool u = Math.Abs(psx - psy) <= 0.0001f; - if (!u || stretch) { - cy = 0; - cwx = a * cx + parent.worldX; - cwy = c * cx + parent.worldY; - } else { - cy = child.ay; - cwx = a * cx + b * cy + parent.worldX; - cwy = c * cx + d * cy + parent.worldY; - } - Bone pp = parent.parent; - a = pp.a; - b = pp.b; - c = pp.c; - d = pp.d; - float id = 1 / (a * d - b * c), x = cwx - pp.worldX, y = cwy - pp.worldY; - float dx = (x * d - y * b) * id - px, dy = (y * a - x * c) * id - py; - float l1 = (float)Math.Sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2; - if (l1 < 0.0001f) { - 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; - float tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py; - float dd = tx * tx + ty * ty; - if (softness != 0) { - softness *= psx * (csx + 1) * 0.5f; - float td = (float)Math.Sqrt(dd), sd = td - l1 - l2 * psx + softness; - if (sd > 0) { - float p = Math.Min(1, sd / (softness * 2)) - 1; - p = (sd - softness * (1 - p * p)) / td; - tx -= p * tx; - ty -= p * ty; - dd = tx * tx + ty * ty; - } - } - if (u) { - l2 *= psx; - float cos = (dd - l1 * l1 - l2 * l2) / (2 * l1 * l2); - if (cos < -1) { - cos = -1; - a2 = MathUtils.PI * bendDir; - } else if (cos > 1) { - cos = 1; - a2 = 0; - if (stretch) { - a = ((float)Math.Sqrt(dd) / (l1 + l2) - 1) * alpha + 1; - sx *= a; - if (uniform) sy *= a; - } - } else - a2 = (float)Math.Acos(cos) * bendDir; - a = l1 + l2 * cos; - b = l2 * (float)Math.Sin(a2); - a1 = (float)Math.Atan2(ty * a - tx * b, tx * a + ty * b); - } else { - a = psx * l2; - b = psy * l2; - float aa = a * a, bb = b * b, ta = (float)Math.Atan2(ty, tx); - c = bb * l1 * l1 + aa * dd - aa * bb; - float c1 = -2 * bb * l1, c2 = bb - aa; - d = c1 * c1 - 4 * c2 * c; - if (d >= 0) { - float q = (float)Math.Sqrt(d); - if (c1 < 0) q = -q; - q = -(c1 + q) * 0.5f; - float r0 = q / c2, r1 = c / q; - float r = Math.Abs(r0) < Math.Abs(r1) ? r0 : r1; - if (r * r <= dd) { - y = (float)Math.Sqrt(dd - r * r) * bendDir; - a1 = ta - (float)Math.Atan2(y, r); - a2 = (float)Math.Atan2(y / psy, (r - l1) / psx); - goto break_outer; // break outer; - } - } - float minAngle = MathUtils.PI, minX = l1 - a, minDist = minX * minX, minY = 0; - float maxAngle = 0, maxX = l1 + a, maxDist = maxX * maxX, maxY = 0; - c = -a * l1 / (aa - bb); - if (c >= -1 && c <= 1) { - c = (float)Math.Acos(c); - x = a * (float)Math.Cos(c) + l1; - y = b * (float)Math.Sin(c); - d = x * x + y * y; - if (d < minDist) { - minAngle = c; - minDist = d; - minX = x; - minY = y; - } - if (d > maxDist) { - maxAngle = c; - maxDist = d; - maxX = x; - maxY = y; - } - } - if (dd <= (minDist + maxDist) * 0.5f) { - a1 = ta - (float)Math.Atan2(minY * bendDir, minX); - a2 = minAngle * bendDir; - } else { - a1 = ta - (float)Math.Atan2(maxY * bendDir, maxX); - a2 = maxAngle * bendDir; - } - } - break_outer: - float os = (float)Math.Atan2(cy, cx) * s2; - float rotation = parent.arotation; - a1 = (a1 - os) * MathUtils.RadDeg + os1 - rotation; - if (a1 > 180) - a1 -= 360; - else if (a1 < -180) - a1 += 360; - parent.UpdateWorldTransform(px, py, rotation + a1 * alpha, sx, sy, 0, 0); - rotation = child.arotation; - a2 = ((a2 + os) * MathUtils.RadDeg - child.ashearX) * s2 + os2 - rotation; - if (a2 > 180) - a2 -= 360; - else if (a2 < -180) - a2 += 360; - child.UpdateWorldTransform(cx, cy, rotation + a2 * alpha, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY); - } - } + /// Applies 2 bone IK. The target is specified in the world coordinate system. + /// A direct descendant of the parent bone. + static public void Apply(Bone parent, Bone child, float targetX, float targetY, int bendDir, bool stretch, bool uniform, + float softness, float alpha) + { + if (parent == null) throw new ArgumentNullException("parent", "parent cannot be null."); + if (child == null) throw new ArgumentNullException("child", "child cannot be null."); + float px = parent.ax, py = parent.ay, psx = parent.ascaleX, psy = parent.ascaleY, sx = psx, sy = psy, csx = child.ascaleX; + int os1, os2, s2; + if (psx < 0) + { + psx = -psx; + os1 = 180; + s2 = -1; + } + else + { + os1 = 0; + s2 = 1; + } + if (psy < 0) + { + psy = -psy; + s2 = -s2; + } + if (csx < 0) + { + csx = -csx; + os2 = 180; + } + else + os2 = 0; + float cx = child.ax, cy, cwx, cwy, a = parent.a, b = parent.b, c = parent.c, d = parent.d; + bool u = Math.Abs(psx - psy) <= 0.0001f; + if (!u || stretch) + { + cy = 0; + cwx = a * cx + parent.worldX; + cwy = c * cx + parent.worldY; + } + else + { + cy = child.ay; + cwx = a * cx + b * cy + parent.worldX; + cwy = c * cx + d * cy + parent.worldY; + } + Bone pp = parent.parent; + a = pp.a; + b = pp.b; + c = pp.c; + d = pp.d; + float id = 1 / (a * d - b * c), x = cwx - pp.worldX, y = cwy - pp.worldY; + float dx = (x * d - y * b) * id - px, dy = (y * a - x * c) * id - py; + float l1 = (float)Math.Sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2; + if (l1 < 0.0001f) + { + 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; + float tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py; + float dd = tx * tx + ty * ty; + if (softness != 0) + { + softness *= psx * (csx + 1) * 0.5f; + float td = (float)Math.Sqrt(dd), sd = td - l1 - l2 * psx + softness; + if (sd > 0) + { + float p = Math.Min(1, sd / (softness * 2)) - 1; + p = (sd - softness * (1 - p * p)) / td; + tx -= p * tx; + ty -= p * ty; + dd = tx * tx + ty * ty; + } + } + if (u) + { + l2 *= psx; + float cos = (dd - l1 * l1 - l2 * l2) / (2 * l1 * l2); + if (cos < -1) + { + cos = -1; + a2 = MathUtils.PI * bendDir; + } + else if (cos > 1) + { + cos = 1; + a2 = 0; + if (stretch) + { + a = ((float)Math.Sqrt(dd) / (l1 + l2) - 1) * alpha + 1; + sx *= a; + if (uniform) sy *= a; + } + } + else + a2 = (float)Math.Acos(cos) * bendDir; + a = l1 + l2 * cos; + b = l2 * (float)Math.Sin(a2); + a1 = (float)Math.Atan2(ty * a - tx * b, tx * a + ty * b); + } + else + { + a = psx * l2; + b = psy * l2; + float aa = a * a, bb = b * b, ta = (float)Math.Atan2(ty, tx); + c = bb * l1 * l1 + aa * dd - aa * bb; + float c1 = -2 * bb * l1, c2 = bb - aa; + d = c1 * c1 - 4 * c2 * c; + if (d >= 0) + { + float q = (float)Math.Sqrt(d); + if (c1 < 0) q = -q; + q = -(c1 + q) * 0.5f; + float r0 = q / c2, r1 = c / q; + float r = Math.Abs(r0) < Math.Abs(r1) ? r0 : r1; + if (r * r <= dd) + { + y = (float)Math.Sqrt(dd - r * r) * bendDir; + a1 = ta - (float)Math.Atan2(y, r); + a2 = (float)Math.Atan2(y / psy, (r - l1) / psx); + goto break_outer; // break outer; + } + } + float minAngle = MathUtils.PI, minX = l1 - a, minDist = minX * minX, minY = 0; + float maxAngle = 0, maxX = l1 + a, maxDist = maxX * maxX, maxY = 0; + c = -a * l1 / (aa - bb); + if (c >= -1 && c <= 1) + { + c = (float)Math.Acos(c); + x = a * (float)Math.Cos(c) + l1; + y = b * (float)Math.Sin(c); + d = x * x + y * y; + if (d < minDist) + { + minAngle = c; + minDist = d; + minX = x; + minY = y; + } + if (d > maxDist) + { + maxAngle = c; + maxDist = d; + maxX = x; + maxY = y; + } + } + if (dd <= (minDist + maxDist) * 0.5f) + { + a1 = ta - (float)Math.Atan2(minY * bendDir, minX); + a2 = minAngle * bendDir; + } + else + { + a1 = ta - (float)Math.Atan2(maxY * bendDir, maxX); + a2 = maxAngle * bendDir; + } + } + break_outer: + float os = (float)Math.Atan2(cy, cx) * s2; + float rotation = parent.arotation; + a1 = (a1 - os) * MathUtils.RadDeg + os1 - rotation; + if (a1 > 180) + a1 -= 360; + else if (a1 < -180) + a1 += 360; + parent.UpdateWorldTransform(px, py, rotation + a1 * alpha, sx, sy, 0, 0); + rotation = child.arotation; + a2 = ((a2 + os) * MathUtils.RadDeg - child.ashearX) * s2 + os2 - rotation; + if (a2 > 180) + a2 -= 360; + else if (a2 < -180) + a2 += 360; + child.UpdateWorldTransform(cx, cy, rotation + a2 * alpha, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/IkConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/IkConstraintData.cs index 3281d1a..788111c 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/IkConstraintData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/IkConstraintData.cs @@ -27,77 +27,85 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; -using System.Collections.Generic; +namespace Spine4_0_64 +{ + /// Stores the setup pose for an IkConstraint. + public class IkConstraintData : ConstraintData + { + internal ExposedList bones = new ExposedList(); + internal BoneData target; + internal int bendDirection = 1; + internal bool compress, stretch, uniform; + internal float mix = 1, softness; -namespace Spine4_0_64 { - /// Stores the setup pose for an IkConstraint. - public class IkConstraintData : ConstraintData { - internal ExposedList bones = new ExposedList(); - internal BoneData target; - internal int bendDirection = 1; - internal bool compress, stretch, uniform; - internal float mix = 1, softness; + public IkConstraintData(string name) : base(name) + { + } - public IkConstraintData (string name) : base(name) { - } + /// The bones that are constrained by this IK Constraint. + public ExposedList Bones + { + get { return bones; } + } - /// The bones that are constrained by this IK Constraint. - public ExposedList Bones { - get { return bones; } - } + /// The bone that is the IK target. + public BoneData Target + { + get { return target; } + set { target = value; } + } - /// The bone that is the IK target. - public BoneData Target { - get { return target; } - set { target = value; } - } + /// + /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. + /// + /// For two bone IK: if the parent bone has local nonuniform scale, the child bone's local Y translation is set to 0. + /// + public float Mix + { + get { return mix; } + set { mix = value; } + } - /// - /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. - /// - /// For two bone IK: if the parent bone has local nonuniform scale, the child bone's local Y translation is set to 0. - /// - public float Mix { - get { return mix; } - set { mix = value; } - } + /// For two bone IK, the target bone's distance from the maximum reach of the bones where rotation begins to slow. The bones + /// will not straighten completely until the target is this far out of range. + public float Softness + { + get { return softness; } + set { softness = value; } + } - /// For two bone IK, the target bone's distance from the maximum reach of the bones where rotation begins to slow. The bones - /// will not straighten completely until the target is this far out of range. - public float Softness { - get { return softness; } - set { softness = value; } - } + /// For two bone IK, controls the bend direction of the IK bones, either 1 or -1. + public int BendDirection + { + get { return bendDirection; } + set { bendDirection = value; } + } - /// For two bone IK, controls the bend direction of the IK bones, either 1 or -1. - public int BendDirection { - get { return bendDirection; } - set { bendDirection = value; } - } + /// For one bone IK, when true and the target is too close, the bone is scaled to reach it. + public bool Compress + { + get { return compress; } + set { compress = value; } + } - /// For one bone IK, when true and the target is too close, the bone is scaled to reach it. - public bool Compress { - get { return compress; } - set { compress = value; } - } + /// When true and the target is out of range, the parent bone is scaled to reach it. + /// + /// For two bone IK: 1) the child bone's local Y translation is set to 0, + /// 2) stretch is not applied if is > 0, + /// and 3) if the parent bone has local nonuniform scale, stretch is not applied. + public bool Stretch + { + get { return stretch; } + set { stretch = value; } + } - /// When true and the target is out of range, the parent bone is scaled to reach it. - /// - /// For two bone IK: 1) the child bone's local Y translation is set to 0, - /// 2) stretch is not applied if is > 0, - /// and 3) if the parent bone has local nonuniform scale, stretch is not applied. - public bool Stretch { - get { return stretch; } - set { stretch = value; } - } - - /// - /// When true and or is used, the bone is scaled on both the X and Y axes. - /// - public bool Uniform { - get { return uniform; } - set { uniform = value; } - } - } + /// + /// When true and or is used, the bone is scaled on both the X and Y axes. + /// + public bool Uniform + { + get { return uniform; } + set { uniform = value; } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Json.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Json.cs index 894ffdc..a344c36 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Json.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Json.cs @@ -28,20 +28,22 @@ *****************************************************************************/ using System; -using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Text; -namespace Spine4_0_64 { - public static class Json { - public static object Deserialize (TextReader text) { - var parser = new SharpJson.JsonDecoder(); - parser.parseNumbersAsFloat = true; - return parser.Decode(text.ReadToEnd()); - } - } +namespace Spine4_0_64 +{ + public static class Json + { + public static object Deserialize(TextReader text) + { + var parser = new SharpJson.JsonDecoder(); + parser.parseNumbersAsFloat = true; + return parser.Decode(text.ReadToEnd()); + } + } } /** @@ -70,448 +72,504 @@ public static object Deserialize (TextReader text) { * 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. */ -namespace Spine4_0_64 { - class Lexer { - public enum Token { - None, - Null, - True, - False, - Colon, - Comma, - String, - Number, - CurlyOpen, - CurlyClose, - SquaredOpen, - SquaredClose, - }; - - public bool hasError { - get { - return !success; - } - } - - public int lineNumber { - get; - private set; - } - - public bool parseNumbersAsFloat { - get; - set; - } - - char[] json; - int index = 0; - bool success = true; - char[] stringBuffer = new char[4096]; - - public Lexer (string text) { - Reset(); - - json = text.ToCharArray(); - parseNumbersAsFloat = false; - } - - public void Reset () { - index = 0; - lineNumber = 1; - success = true; - } - - public string ParseString () { - int idx = 0; - StringBuilder builder = null; - - SkipWhiteSpaces(); - - // " - char c = json[index++]; - - bool failed = false; - bool complete = false; - - while (!complete && !failed) { - if (index == json.Length) - break; - - c = json[index++]; - if (c == '"') { - complete = true; - break; - } else if (c == '\\') { - if (index == json.Length) - break; - - c = json[index++]; - - switch (c) { - case '"': - stringBuffer[idx++] = '"'; - break; - case '\\': - stringBuffer[idx++] = '\\'; - break; - case '/': - stringBuffer[idx++] = '/'; - break; - case 'b': - stringBuffer[idx++] = '\b'; - break; - case 'f': - stringBuffer[idx++] = '\f'; - break; - case 'n': - stringBuffer[idx++] = '\n'; - break; - case 'r': - stringBuffer[idx++] = '\r'; - break; - case 't': - stringBuffer[idx++] = '\t'; - break; - case 'u': - int remainingLength = json.Length - index; - if (remainingLength >= 4) { - var hex = new string(json, index, 4); - - // XXX: handle UTF - stringBuffer[idx++] = (char)Convert.ToInt32(hex, 16); - - // skip 4 chars - index += 4; - } else { - failed = true; - } - break; - } - } else { - stringBuffer[idx++] = c; - } - - if (idx >= stringBuffer.Length) { - if (builder == null) - builder = new StringBuilder(); - - builder.Append(stringBuffer, 0, idx); - idx = 0; - } - } - - if (!complete) { - success = false; - return null; - } - - if (builder != null) - return builder.ToString(); - else - return new string(stringBuffer, 0, idx); - } - - string GetNumberString () { - SkipWhiteSpaces(); - - int lastIndex = GetLastIndexOfNumber(index); - int charLength = (lastIndex - index) + 1; - - var result = new string(json, index, charLength); - - index = lastIndex + 1; - - return result; - } - - public float ParseFloatNumber () { - float number; - var str = GetNumberString(); - - if (!float.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out number)) - return 0; - - return number; - } - - public double ParseDoubleNumber () { - double number; - var str = GetNumberString(); - - if (!double.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out number)) - return 0; - - return number; - } - - int GetLastIndexOfNumber (int index) { - int lastIndex; - - for (lastIndex = index; lastIndex < json.Length; lastIndex++) { - char ch = json[lastIndex]; - - if ((ch < '0' || ch > '9') && ch != '+' && ch != '-' - && ch != '.' && ch != 'e' && ch != 'E') - break; - } - - return lastIndex - 1; - } - - void SkipWhiteSpaces () { - for (; index < json.Length; index++) { - char ch = json[index]; - - if (ch == '\n') - lineNumber++; - - if (!char.IsWhiteSpace(json[index])) - break; - } - } - - public Token LookAhead () { - SkipWhiteSpaces(); - - int savedIndex = index; - return NextToken(json, ref savedIndex); - } - - public Token NextToken () { - SkipWhiteSpaces(); - return NextToken(json, ref index); - } - - static Token NextToken (char[] json, ref int index) { - if (index == json.Length) - return Token.None; - - char c = json[index++]; - - switch (c) { - case '{': - return Token.CurlyOpen; - case '}': - return Token.CurlyClose; - case '[': - return Token.SquaredOpen; - case ']': - return Token.SquaredClose; - case ',': - return Token.Comma; - case '"': - return Token.String; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case '-': - return Token.Number; - case ':': - return Token.Colon; - } - - index--; - - int remainingLength = json.Length - index; - - // false - if (remainingLength >= 5) { - if (json[index] == 'f' && - json[index + 1] == 'a' && - json[index + 2] == 'l' && - json[index + 3] == 's' && - json[index + 4] == 'e') { - index += 5; - return Token.False; - } - } - - // true - if (remainingLength >= 4) { - if (json[index] == 't' && - json[index + 1] == 'r' && - json[index + 2] == 'u' && - json[index + 3] == 'e') { - index += 4; - return Token.True; - } - } - - // null - if (remainingLength >= 4) { - if (json[index] == 'n' && - json[index + 1] == 'u' && - json[index + 2] == 'l' && - json[index + 3] == 'l') { - index += 4; - return Token.Null; - } - } - - return Token.None; - } - } - - public class JsonDecoder { - public string errorMessage { - get; - private set; - } - - public bool parseNumbersAsFloat { - get; - set; - } - - Lexer lexer; - - public JsonDecoder () { - errorMessage = null; - parseNumbersAsFloat = false; - } - - public object Decode (string text) { - errorMessage = null; - - lexer = new Lexer(text); - lexer.parseNumbersAsFloat = parseNumbersAsFloat; - - return ParseValue(); - } - - public static object DecodeText (string text) { - var builder = new JsonDecoder(); - return builder.Decode(text); - } - - IDictionary ParseObject () { - var table = new Dictionary(); - - // { - lexer.NextToken(); - - while (true) { - var token = lexer.LookAhead(); - - switch (token) { - case Lexer.Token.None: - TriggerError("Invalid token"); - return null; - case Lexer.Token.Comma: - lexer.NextToken(); - break; - case Lexer.Token.CurlyClose: - lexer.NextToken(); - return table; - default: - // name - string name = EvalLexer(lexer.ParseString()); - - if (errorMessage != null) - return null; - - // : - token = lexer.NextToken(); - - if (token != Lexer.Token.Colon) { - TriggerError("Invalid token; expected ':'"); - return null; - } - - // value - object value = ParseValue(); - - if (errorMessage != null) - return null; - - table[name] = value; - break; - } - } - - //return null; // Unreachable code - } - - IList ParseArray () { - var array = new List(); - - // [ - lexer.NextToken(); - - while (true) { - var token = lexer.LookAhead(); - - switch (token) { - case Lexer.Token.None: - TriggerError("Invalid token"); - return null; - case Lexer.Token.Comma: - lexer.NextToken(); - break; - case Lexer.Token.SquaredClose: - lexer.NextToken(); - return array; - default: - object value = ParseValue(); - - if (errorMessage != null) - return null; - - array.Add(value); - break; - } - } - - //return null; // Unreachable code - } - - object ParseValue () { - switch (lexer.LookAhead()) { - case Lexer.Token.String: - return EvalLexer(lexer.ParseString()); - case Lexer.Token.Number: - if (parseNumbersAsFloat) - return EvalLexer(lexer.ParseFloatNumber()); - else - return EvalLexer(lexer.ParseDoubleNumber()); - case Lexer.Token.CurlyOpen: - return ParseObject(); - case Lexer.Token.SquaredOpen: - return ParseArray(); - case Lexer.Token.True: - lexer.NextToken(); - return true; - case Lexer.Token.False: - lexer.NextToken(); - return false; - case Lexer.Token.Null: - lexer.NextToken(); - return null; - case Lexer.Token.None: - break; - } - - TriggerError("Unable to parse value"); - return null; - } - - void TriggerError (string message) { - errorMessage = string.Format("Error: '{0}' at line {1}", - message, lexer.lineNumber); - } - - T EvalLexer (T value) { - if (lexer.hasError) - TriggerError("Lexical error ocurred"); - - return value; - } - } +namespace Spine4_0_64 +{ + class Lexer + { + public enum Token + { + None, + Null, + True, + False, + Colon, + Comma, + String, + Number, + CurlyOpen, + CurlyClose, + SquaredOpen, + SquaredClose, + }; + + public bool hasError + { + get + { + return !success; + } + } + + public int lineNumber + { + get; + private set; + } + + public bool parseNumbersAsFloat + { + get; + set; + } + + char[] json; + int index = 0; + bool success = true; + char[] stringBuffer = new char[4096]; + + public Lexer(string text) + { + Reset(); + + json = text.ToCharArray(); + parseNumbersAsFloat = false; + } + + public void Reset() + { + index = 0; + lineNumber = 1; + success = true; + } + + public string ParseString() + { + int idx = 0; + StringBuilder builder = null; + + SkipWhiteSpaces(); + + // " + char c = json[index++]; + + bool failed = false; + bool complete = false; + + while (!complete && !failed) + { + if (index == json.Length) + break; + + c = json[index++]; + if (c == '"') + { + complete = true; + break; + } + else if (c == '\\') + { + if (index == json.Length) + break; + + c = json[index++]; + + switch (c) + { + case '"': + stringBuffer[idx++] = '"'; + break; + case '\\': + stringBuffer[idx++] = '\\'; + break; + case '/': + stringBuffer[idx++] = '/'; + break; + case 'b': + stringBuffer[idx++] = '\b'; + break; + case 'f': + stringBuffer[idx++] = '\f'; + break; + case 'n': + stringBuffer[idx++] = '\n'; + break; + case 'r': + stringBuffer[idx++] = '\r'; + break; + case 't': + stringBuffer[idx++] = '\t'; + break; + case 'u': + int remainingLength = json.Length - index; + if (remainingLength >= 4) + { + var hex = new string(json, index, 4); + + // XXX: handle UTF + stringBuffer[idx++] = (char)Convert.ToInt32(hex, 16); + + // skip 4 chars + index += 4; + } + else + { + failed = true; + } + break; + } + } + else + { + stringBuffer[idx++] = c; + } + + if (idx >= stringBuffer.Length) + { + if (builder == null) + builder = new StringBuilder(); + + builder.Append(stringBuffer, 0, idx); + idx = 0; + } + } + + if (!complete) + { + success = false; + return null; + } + + if (builder != null) + return builder.ToString(); + else + return new string(stringBuffer, 0, idx); + } + + string GetNumberString() + { + SkipWhiteSpaces(); + + int lastIndex = GetLastIndexOfNumber(index); + int charLength = (lastIndex - index) + 1; + + var result = new string(json, index, charLength); + + index = lastIndex + 1; + + return result; + } + + public float ParseFloatNumber() + { + float number; + var str = GetNumberString(); + + if (!float.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out number)) + return 0; + + return number; + } + + public double ParseDoubleNumber() + { + double number; + var str = GetNumberString(); + + if (!double.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out number)) + return 0; + + return number; + } + + int GetLastIndexOfNumber(int index) + { + int lastIndex; + + for (lastIndex = index; lastIndex < json.Length; lastIndex++) + { + char ch = json[lastIndex]; + + if ((ch < '0' || ch > '9') && ch != '+' && ch != '-' + && ch != '.' && ch != 'e' && ch != 'E') + break; + } + + return lastIndex - 1; + } + + void SkipWhiteSpaces() + { + for (; index < json.Length; index++) + { + char ch = json[index]; + + if (ch == '\n') + lineNumber++; + + if (!char.IsWhiteSpace(json[index])) + break; + } + } + + public Token LookAhead() + { + SkipWhiteSpaces(); + + int savedIndex = index; + return NextToken(json, ref savedIndex); + } + + public Token NextToken() + { + SkipWhiteSpaces(); + return NextToken(json, ref index); + } + + static Token NextToken(char[] json, ref int index) + { + if (index == json.Length) + return Token.None; + + char c = json[index++]; + + switch (c) + { + case '{': + return Token.CurlyOpen; + case '}': + return Token.CurlyClose; + case '[': + return Token.SquaredOpen; + case ']': + return Token.SquaredClose; + case ',': + return Token.Comma; + case '"': + return Token.String; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + return Token.Number; + case ':': + return Token.Colon; + } + + index--; + + int remainingLength = json.Length - index; + + // false + if (remainingLength >= 5) + { + if (json[index] == 'f' && + json[index + 1] == 'a' && + json[index + 2] == 'l' && + json[index + 3] == 's' && + json[index + 4] == 'e') + { + index += 5; + return Token.False; + } + } + + // true + if (remainingLength >= 4) + { + if (json[index] == 't' && + json[index + 1] == 'r' && + json[index + 2] == 'u' && + json[index + 3] == 'e') + { + index += 4; + return Token.True; + } + } + + // null + if (remainingLength >= 4) + { + if (json[index] == 'n' && + json[index + 1] == 'u' && + json[index + 2] == 'l' && + json[index + 3] == 'l') + { + index += 4; + return Token.Null; + } + } + + return Token.None; + } + } + + public class JsonDecoder + { + public string errorMessage + { + get; + private set; + } + + public bool parseNumbersAsFloat + { + get; + set; + } + + Lexer lexer; + + public JsonDecoder() + { + errorMessage = null; + parseNumbersAsFloat = false; + } + + public object Decode(string text) + { + errorMessage = null; + + lexer = new Lexer(text); + lexer.parseNumbersAsFloat = parseNumbersAsFloat; + + return ParseValue(); + } + + public static object DecodeText(string text) + { + var builder = new JsonDecoder(); + return builder.Decode(text); + } + + IDictionary ParseObject() + { + var table = new Dictionary(); + + // { + lexer.NextToken(); + + while (true) + { + var token = lexer.LookAhead(); + + switch (token) + { + case Lexer.Token.None: + TriggerError("Invalid token"); + return null; + case Lexer.Token.Comma: + lexer.NextToken(); + break; + case Lexer.Token.CurlyClose: + lexer.NextToken(); + return table; + default: + // name + string name = EvalLexer(lexer.ParseString()); + + if (errorMessage != null) + return null; + + // : + token = lexer.NextToken(); + + if (token != Lexer.Token.Colon) + { + TriggerError("Invalid token; expected ':'"); + return null; + } + + // value + object value = ParseValue(); + + if (errorMessage != null) + return null; + + table[name] = value; + break; + } + } + + //return null; // Unreachable code + } + + IList ParseArray() + { + var array = new List(); + + // [ + lexer.NextToken(); + + while (true) + { + var token = lexer.LookAhead(); + + switch (token) + { + case Lexer.Token.None: + TriggerError("Invalid token"); + return null; + case Lexer.Token.Comma: + lexer.NextToken(); + break; + case Lexer.Token.SquaredClose: + lexer.NextToken(); + return array; + default: + object value = ParseValue(); + + if (errorMessage != null) + return null; + + array.Add(value); + break; + } + } + + //return null; // Unreachable code + } + + object ParseValue() + { + switch (lexer.LookAhead()) + { + case Lexer.Token.String: + return EvalLexer(lexer.ParseString()); + case Lexer.Token.Number: + if (parseNumbersAsFloat) + return EvalLexer(lexer.ParseFloatNumber()); + else + return EvalLexer(lexer.ParseDoubleNumber()); + case Lexer.Token.CurlyOpen: + return ParseObject(); + case Lexer.Token.SquaredOpen: + return ParseArray(); + case Lexer.Token.True: + lexer.NextToken(); + return true; + case Lexer.Token.False: + lexer.NextToken(); + return false; + case Lexer.Token.Null: + lexer.NextToken(); + return null; + case Lexer.Token.None: + break; + } + + TriggerError("Unable to parse value"); + return null; + } + + void TriggerError(string message) + { + errorMessage = string.Format("Error: '{0}' at line {1}", + message, lexer.lineNumber); + } + + T EvalLexer(T value) + { + if (lexer.hasError) + TriggerError("Lexical error ocurred"); + + return value; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/MathUtils.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/MathUtils.cs index 7ccf780..8654180 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/MathUtils.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/MathUtils.cs @@ -31,14 +31,16 @@ using System; -namespace Spine4_0_64 { - public static class MathUtils { - public const float PI = 3.1415927f; - public const float PI2 = PI * 2; - public const float RadDeg = 180f / PI; - public const float DegRad = PI / 180; +namespace Spine4_0_64 +{ + public static class MathUtils + { + public const float PI = 3.1415927f; + public const float PI2 = PI * 2; + public const float RadDeg = 180f / PI; + public const float DegRad = PI / 180; - static Random random = new Random(); + static Random random = new Random(); #if USE_FAST_SIN_COS_ATAN2_APPROXIMATIONS const int SIN_BITS = 14; // 16KB. Adjust for accuracy. @@ -95,79 +97,95 @@ static public float Atan2 (float y, float x) { return y < 0f ? atan - PI : atan; } #else - /// Returns the sine of a given angle in radians. - static public float Sin (float radians) { - return (float)Math.Sin(radians); - } - - /// Returns the cosine of a given angle in radians. - static public float Cos (float radians) { - return (float)Math.Cos(radians); - } - - /// Returns the sine of a given angle in degrees. - static public float SinDeg (float degrees) { - return (float)Math.Sin(degrees * DegRad); - } - - /// Returns the cosine of a given angle in degrees. - static public float CosDeg (float degrees) { - return (float)Math.Cos(degrees * DegRad); - } - - /// Returns the atan2 using Math.Atan2. - static public float Atan2 (float y, float x) { - return (float)Math.Atan2(y, x); - } + /// Returns the sine of a given angle in radians. + static public float Sin(float radians) + { + return (float)Math.Sin(radians); + } + + /// Returns the cosine of a given angle in radians. + static public float Cos(float radians) + { + return (float)Math.Cos(radians); + } + + /// Returns the sine of a given angle in degrees. + static public float SinDeg(float degrees) + { + return (float)Math.Sin(degrees * DegRad); + } + + /// Returns the cosine of a given angle in degrees. + static public float CosDeg(float degrees) + { + return (float)Math.Cos(degrees * DegRad); + } + + /// Returns the atan2 using Math.Atan2. + static public float Atan2(float y, float x) + { + return (float)Math.Atan2(y, x); + } #endif - static public float Clamp (float value, float min, float max) { - if (value < min) return min; - if (value > max) return max; - return value; - } - - static public float RandomTriangle (float min, float max) { - return RandomTriangle(min, max, (min + max) * 0.5f); - } - - static public float RandomTriangle (float min, float max, float mode) { - float u = (float)random.NextDouble(); - float d = max - min; - if (u <= (mode - min) / d) return min + (float)Math.Sqrt(u * d * (mode - min)); - return max - (float)Math.Sqrt((1 - u) * d * (max - mode)); - } - } - - public abstract class IInterpolation { - public static IInterpolation Pow2 = new Pow(2); - public static IInterpolation Pow2Out = new PowOut(2); - - protected abstract float Apply (float a); - - public float Apply (float start, float end, float a) { - return start + (end - start) * Apply(a); - } - } - - public class Pow : IInterpolation { - public float Power { get; set; } - - public Pow (float power) { - Power = power; - } - - protected override float Apply (float a) { - if (a <= 0.5f) return (float)Math.Pow(a * 2, Power) / 2; - return (float)Math.Pow((a - 1) * 2, Power) / (Power % 2 == 0 ? -2 : 2) + 1; - } - } - - public class PowOut : Pow { - public PowOut (float power) : base(power) { - } - - protected override float Apply (float a) { - return (float)Math.Pow(a - 1, Power) * (Power % 2 == 0 ? -1 : 1) + 1; - } - } + static public float Clamp(float value, float min, float max) + { + if (value < min) return min; + if (value > max) return max; + return value; + } + + static public float RandomTriangle(float min, float max) + { + return RandomTriangle(min, max, (min + max) * 0.5f); + } + + static public float RandomTriangle(float min, float max, float mode) + { + float u = (float)random.NextDouble(); + float d = max - min; + if (u <= (mode - min) / d) return min + (float)Math.Sqrt(u * d * (mode - min)); + return max - (float)Math.Sqrt((1 - u) * d * (max - mode)); + } + } + + public abstract class IInterpolation + { + public static IInterpolation Pow2 = new Pow(2); + public static IInterpolation Pow2Out = new PowOut(2); + + protected abstract float Apply(float a); + + public float Apply(float start, float end, float a) + { + return start + (end - start) * Apply(a); + } + } + + public class Pow : IInterpolation + { + public float Power { get; set; } + + public Pow(float power) + { + Power = power; + } + + protected override float Apply(float a) + { + if (a <= 0.5f) return (float)Math.Pow(a * 2, Power) / 2; + return (float)Math.Pow((a - 1) * 2, Power) / (Power % 2 == 0 ? -2 : 2) + 1; + } + } + + public class PowOut : Pow + { + public PowOut(float power) : base(power) + { + } + + protected override float Apply(float a) + { + return (float)Math.Pow(a - 1, Power) * (Power % 2 == 0 ? -1 : 1) + 1; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/PathConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/PathConstraint.cs index 7469b40..6077cf2 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/PathConstraint.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/PathConstraint.cs @@ -29,482 +29,550 @@ using System; -namespace Spine4_0_64 { - - /// - /// - /// Stores the current pose for a path constraint. A path constraint adjusts the rotation, translation, and scale of the - /// constrained bones so they follow a . - /// - /// See Path constraints in the Spine User Guide. - /// - public class PathConstraint : IUpdatable { - const int NONE = -1, BEFORE = -2, AFTER = -3; - const float Epsilon = 0.00001f; - - internal PathConstraintData data; - internal ExposedList bones; - internal Slot target; - internal float position, spacing, mixRotate, mixX, mixY; - - internal bool active; - - internal ExposedList spaces = new ExposedList(), positions = new ExposedList(); - internal ExposedList world = new ExposedList(), curves = new ExposedList(), lengths = new ExposedList(); - internal float[] segments = new float[10]; - - public PathConstraint (PathConstraintData data, Skeleton skeleton) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.data = data; - bones = new ExposedList(data.Bones.Count); - foreach (BoneData boneData in data.bones) - bones.Add(skeleton.FindBone(boneData.name)); - target = skeleton.FindSlot(data.target.name); - position = data.position; - spacing = data.spacing; - mixRotate = data.mixRotate; - mixX = data.mixX; - mixY = data.mixY; - } - - /// Copy constructor. - public PathConstraint (PathConstraint constraint, Skeleton skeleton) { - if (constraint == null) throw new ArgumentNullException("constraint cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); - data = constraint.data; - bones = new ExposedList(constraint.Bones.Count); - foreach (Bone bone in constraint.Bones) - bones.Add(skeleton.Bones.Items[bone.data.index]); - target = skeleton.slots.Items[constraint.target.data.index]; - position = constraint.position; - spacing = constraint.spacing; - mixRotate = constraint.mixRotate; - mixX = constraint.mixX; - mixY = constraint.mixY; - } - - public static void ArraysFill (float[] a, int fromIndex, int toIndex, float val) { - for (int i = fromIndex; i < toIndex; i++) - a[i] = val; - } - - public void Update () { - PathAttachment attachment = target.Attachment as PathAttachment; - if (attachment == null) return; - - float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY; - if (mixRotate == 0 && mixX == 0 && mixY == 0) return; - - PathConstraintData data = this.data; - bool tangents = data.rotateMode == RotateMode.Tangent, scale = data.rotateMode == RotateMode.ChainScale; - int boneCount = this.bones.Count, spacesCount = tangents ? boneCount : boneCount + 1; - Bone[] bonesItems = this.bones.Items; - float[] spaces = this.spaces.Resize(spacesCount).Items, lengths = scale ? this.lengths.Resize(boneCount).Items : null; - float spacing = this.spacing; - switch (data.spacingMode) { - case SpacingMode.Percent: - if (scale) { - for (int i = 0, n = spacesCount - 1; i < n; i++) { - Bone bone = bonesItems[i]; - float setupLength = bone.data.length; - if (setupLength < PathConstraint.Epsilon) - lengths[i] = 0; - else { - float x = setupLength * bone.a, y = setupLength * bone.c; - lengths[i] = (float)Math.Sqrt(x * x + y * y); - } - } - } - ArraysFill(spaces, 1, spacesCount, spacing); - break; - case SpacingMode.Proportional: { - float sum = 0; - for (int i = 0, n = spacesCount - 1; i < n;) { - Bone bone = bonesItems[i]; - float setupLength = bone.data.length; - if (setupLength < PathConstraint.Epsilon) { - if (scale) lengths[i] = 0; - spaces[++i] = spacing; - } else { - float x = setupLength * bone.a, y = setupLength * bone.c; - float length = (float)Math.Sqrt(x * x + y * y); - if (scale) lengths[i] = length; - spaces[++i] = length; - sum += length; - } - } - if (sum > 0) { - sum = spacesCount / sum * spacing; - for (int i = 1; i < spacesCount; i++) - spaces[i] *= sum; - } - break; - } - default: { - bool lengthSpacing = data.spacingMode == SpacingMode.Length; - for (int i = 0, n = spacesCount - 1; i < n;) { - Bone bone = bonesItems[i]; - float setupLength = bone.data.length; - if (setupLength < PathConstraint.Epsilon) { - if (scale) lengths[i] = 0; - spaces[++i] = spacing; - } else { - float x = setupLength * bone.a, y = setupLength * bone.c; - float length = (float)Math.Sqrt(x * x + y * y); - if (scale) lengths[i] = length; - spaces[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length / setupLength; - } - } - break; - } - } - - float[] positions = ComputeWorldPositions(attachment, spacesCount, tangents); - float boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation; - bool tip; - if (offsetRotation == 0) { - tip = data.rotateMode == RotateMode.Chain; - } else { - tip = false; - Bone p = target.bone; - offsetRotation *= p.a * p.d - p.b * p.c > 0 ? MathUtils.DegRad : -MathUtils.DegRad; - } - for (int i = 0, p = 3; i < boneCount; i++, p += 3) { - Bone bone = bonesItems[i]; - bone.worldX += (boneX - bone.worldX) * mixX; - bone.worldY += (boneY - bone.worldY) * mixY; - float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY; - if (scale) { - float length = lengths[i]; - if (length >= PathConstraint.Epsilon) { - float s = ((float)Math.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 = MathUtils.Atan2(dy, dx); - r -= MathUtils.Atan2(c, a); - if (tip) { - cos = MathUtils.Cos(r); - sin = MathUtils.Sin(r); - float length = bone.data.length; - boneX += (length * (cos * a - sin * c) - dx) * mixRotate; - boneY += (length * (sin * a + cos * c) - dy) * mixRotate; - } else - r += offsetRotation; - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) // - r += MathUtils.PI2; - r *= mixRotate; - cos = MathUtils.Cos(r); - sin = MathUtils.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(); - } - } - - float[] ComputeWorldPositions (PathAttachment path, int spacesCount, bool tangents) { - Slot target = this.target; - float position = this.position; - float[] spaces = this.spaces.Items, output = this.positions.Resize(spacesCount * 3 + 2).Items, world; - bool closed = path.Closed; - int verticesLength = path.WorldVerticesLength, curveCount = verticesLength / 6, prevCurve = NONE; - - float pathLength, multiplier; - if (!path.ConstantSpeed) { - float[] lengths = path.Lengths; - curveCount -= closed ? 1 : 2; - pathLength = lengths[curveCount]; - - if (data.positionMode == PositionMode.Percent) position *= pathLength; - - switch (data.spacingMode) { - case SpacingMode.Percent: - multiplier = pathLength; - break; - case SpacingMode.Proportional: - multiplier = pathLength / spacesCount; - break; - default: - multiplier = 1; - break; - } - - world = this.world.Resize(8).Items; - 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 %= pathLength; - if (p < 0) p += pathLength; - curve = 0; - } else if (p < 0) { - if (prevCurve != BEFORE) { - prevCurve = BEFORE; - path.ComputeWorldVertices(target, 2, 4, world, 0, 2); - } - AddBeforePosition(p, world, 0, output, o); - continue; - } else if (p > pathLength) { - if (prevCurve != AFTER) { - prevCurve = AFTER; - path.ComputeWorldVertices(target, verticesLength - 6, 4, world, 0, 2); - } - AddAfterPosition(p - pathLength, world, 0, output, 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, 2); - path.ComputeWorldVertices(target, 0, 4, world, 4, 2); - } else - path.ComputeWorldVertices(target, curve * 6 + 2, 8, world, 0, 2); - } - AddCurvePosition(p, world[0], world[1], world[2], world[3], world[4], world[5], world[6], world[7], output, o, - tangents || (i > 0 && space < PathConstraint.Epsilon)); - } - return output; - } - - // World vertices. - if (closed) { - verticesLength += 2; - world = this.world.Resize(verticesLength).Items; - path.ComputeWorldVertices(target, 2, verticesLength - 4, world, 0, 2); - path.ComputeWorldVertices(target, 0, 2, world, verticesLength - 4, 2); - world[verticesLength - 2] = world[0]; - world[verticesLength - 1] = world[1]; - } else { - curveCount--; - verticesLength -= 4; - world = this.world.Resize(verticesLength).Items; - path.ComputeWorldVertices(target, 2, verticesLength, world, 0, 2); - } - - // Curve lengths. - float[] curves = this.curves.Resize(curveCount).Items; - 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 += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - dfx += ddfx; - dfy += ddfy; - ddfx += dddfx; - ddfy += dddfy; - pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - dfx += ddfx; - dfy += ddfy; - pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - dfx += ddfx + dddfx; - dfy += ddfy + dddfy; - pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - curves[i] = pathLength; - x1 = x2; - y1 = y2; - } - - if (data.positionMode == PositionMode.Percent) position *= pathLength; - - switch (data.spacingMode) { - case SpacingMode.Percent: - multiplier = pathLength; - break; - case SpacingMode.Proportional: - multiplier = pathLength / spacesCount; - break; - default: - multiplier = 1; - break; - } - - float[] segments = this.segments; - 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 %= pathLength; - if (p < 0) p += pathLength; - curve = 0; - } else if (p < 0) { - AddBeforePosition(p, world, 0, output, o); - continue; - } else if (p > pathLength) { - AddAfterPosition(p - pathLength, world, verticesLength - 4, output, 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 = (float)Math.Sqrt(dfx * dfx + dfy * dfy); - segments[0] = curveLength; - for (ii = 1; ii < 8; ii++) { - dfx += ddfx; - dfy += ddfy; - ddfx += dddfx; - ddfy += dddfy; - curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - segments[ii] = curveLength; - } - dfx += ddfx; - dfy += ddfy; - curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - segments[8] = curveLength; - dfx += ddfx + dddfx; - dfy += ddfy + dddfy; - curveLength += (float)Math.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, output, o, tangents || (i > 0 && space < PathConstraint.Epsilon)); - } - return output; - } - - static void AddBeforePosition (float p, float[] temp, int i, float[] output, int o) { - float x1 = temp[i], y1 = temp[i + 1], dx = temp[i + 2] - x1, dy = temp[i + 3] - y1, r = MathUtils.Atan2(dy, dx); - output[o] = x1 + p * MathUtils.Cos(r); - output[o + 1] = y1 + p * MathUtils.Sin(r); - output[o + 2] = r; - } - - static void AddAfterPosition (float p, float[] temp, int i, float[] output, int o) { - float x1 = temp[i + 2], y1 = temp[i + 3], dx = x1 - temp[i], dy = y1 - temp[i + 1], r = MathUtils.Atan2(dy, dx); - output[o] = x1 + p * MathUtils.Cos(r); - output[o + 1] = y1 + p * MathUtils.Sin(r); - output[o + 2] = r; - } - - static void AddCurvePosition (float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2, - float[] output, int o, bool tangents) { - if (p < PathConstraint.Epsilon || float.IsNaN(p)) { - output[o] = x1; - output[o + 1] = y1; - output[o + 2] = (float)Math.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.001f) - output[o + 2] = (float)Math.Atan2(cy1 - y1, cx1 - x1); - else - output[o + 2] = (float)Math.Atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt)); - } - } - - /// The position along the path. - public float Position { get { return position; } set { position = value; } } - /// The spacing between bones. - public float Spacing { get { return spacing; } set { spacing = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotations. - public float MixRotate { get { return mixRotate; } set { mixRotate = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. - public float MixX { get { return mixX; } set { mixX = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. - public float MixY { get { return mixY; } set { mixY = value; } } - /// The bones that will be modified by this path constraint. - public ExposedList Bones { get { return bones; } } - /// The slot whose path attachment will be used to constrained the bones. - public Slot Target { get { return target; } set { target = value; } } - public bool Active { get { return active; } } - /// The path constraint's setup pose data. - public PathConstraintData Data { get { return data; } } - } +namespace Spine4_0_64 +{ + + /// + /// + /// Stores the current pose for a path constraint. A path constraint adjusts the rotation, translation, and scale of the + /// constrained bones so they follow a . + /// + /// See Path constraints in the Spine User Guide. + /// + public class PathConstraint : IUpdatable + { + const int NONE = -1, BEFORE = -2, AFTER = -3; + const float Epsilon = 0.00001f; + + internal PathConstraintData data; + internal ExposedList bones; + internal Slot target; + internal float position, spacing, mixRotate, mixX, mixY; + + internal bool active; + + internal ExposedList spaces = new ExposedList(), positions = new ExposedList(); + internal ExposedList world = new ExposedList(), curves = new ExposedList(), lengths = new ExposedList(); + internal float[] segments = new float[10]; + + public PathConstraint(PathConstraintData data, Skeleton skeleton) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + bones = new ExposedList(data.Bones.Count); + foreach (BoneData boneData in data.bones) + bones.Add(skeleton.FindBone(boneData.name)); + target = skeleton.FindSlot(data.target.name); + position = data.position; + spacing = data.spacing; + mixRotate = data.mixRotate; + mixX = data.mixX; + mixY = data.mixY; + } + + /// Copy constructor. + public PathConstraint(PathConstraint constraint, Skeleton skeleton) + { + if (constraint == null) throw new ArgumentNullException("constraint cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); + data = constraint.data; + bones = new ExposedList(constraint.Bones.Count); + foreach (Bone bone in constraint.Bones) + bones.Add(skeleton.Bones.Items[bone.data.index]); + target = skeleton.slots.Items[constraint.target.data.index]; + position = constraint.position; + spacing = constraint.spacing; + mixRotate = constraint.mixRotate; + mixX = constraint.mixX; + mixY = constraint.mixY; + } + + public static void ArraysFill(float[] a, int fromIndex, int toIndex, float val) + { + for (int i = fromIndex; i < toIndex; i++) + a[i] = val; + } + + public void Update() + { + PathAttachment attachment = target.Attachment as PathAttachment; + if (attachment == null) return; + + float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY; + if (mixRotate == 0 && mixX == 0 && mixY == 0) return; + + PathConstraintData data = this.data; + bool tangents = data.rotateMode == RotateMode.Tangent, scale = data.rotateMode == RotateMode.ChainScale; + int boneCount = this.bones.Count, spacesCount = tangents ? boneCount : boneCount + 1; + Bone[] bonesItems = this.bones.Items; + float[] spaces = this.spaces.Resize(spacesCount).Items, lengths = scale ? this.lengths.Resize(boneCount).Items : null; + float spacing = this.spacing; + switch (data.spacingMode) + { + case SpacingMode.Percent: + if (scale) + { + for (int i = 0, n = spacesCount - 1; i < n; i++) + { + Bone bone = bonesItems[i]; + float setupLength = bone.data.length; + if (setupLength < PathConstraint.Epsilon) + lengths[i] = 0; + else + { + float x = setupLength * bone.a, y = setupLength * bone.c; + lengths[i] = (float)Math.Sqrt(x * x + y * y); + } + } + } + ArraysFill(spaces, 1, spacesCount, spacing); + break; + case SpacingMode.Proportional: + { + float sum = 0; + for (int i = 0, n = spacesCount - 1; i < n;) + { + Bone bone = bonesItems[i]; + float setupLength = bone.data.length; + if (setupLength < PathConstraint.Epsilon) + { + if (scale) lengths[i] = 0; + spaces[++i] = spacing; + } + else + { + float x = setupLength * bone.a, y = setupLength * bone.c; + float length = (float)Math.Sqrt(x * x + y * y); + if (scale) lengths[i] = length; + spaces[++i] = length; + sum += length; + } + } + if (sum > 0) + { + sum = spacesCount / sum * spacing; + for (int i = 1; i < spacesCount; i++) + spaces[i] *= sum; + } + break; + } + default: + { + bool lengthSpacing = data.spacingMode == SpacingMode.Length; + for (int i = 0, n = spacesCount - 1; i < n;) + { + Bone bone = bonesItems[i]; + float setupLength = bone.data.length; + if (setupLength < PathConstraint.Epsilon) + { + if (scale) lengths[i] = 0; + spaces[++i] = spacing; + } + else + { + float x = setupLength * bone.a, y = setupLength * bone.c; + float length = (float)Math.Sqrt(x * x + y * y); + if (scale) lengths[i] = length; + spaces[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length / setupLength; + } + } + break; + } + } + + float[] positions = ComputeWorldPositions(attachment, spacesCount, tangents); + float boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation; + bool tip; + if (offsetRotation == 0) + { + tip = data.rotateMode == RotateMode.Chain; + } + else + { + tip = false; + Bone p = target.bone; + offsetRotation *= p.a * p.d - p.b * p.c > 0 ? MathUtils.DegRad : -MathUtils.DegRad; + } + for (int i = 0, p = 3; i < boneCount; i++, p += 3) + { + Bone bone = bonesItems[i]; + bone.worldX += (boneX - bone.worldX) * mixX; + bone.worldY += (boneY - bone.worldY) * mixY; + float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY; + if (scale) + { + float length = lengths[i]; + if (length >= PathConstraint.Epsilon) + { + float s = ((float)Math.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 = MathUtils.Atan2(dy, dx); + r -= MathUtils.Atan2(c, a); + if (tip) + { + cos = MathUtils.Cos(r); + sin = MathUtils.Sin(r); + float length = bone.data.length; + boneX += (length * (cos * a - sin * c) - dx) * mixRotate; + boneY += (length * (sin * a + cos * c) - dy) * mixRotate; + } + else + r += offsetRotation; + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) // + r += MathUtils.PI2; + r *= mixRotate; + cos = MathUtils.Cos(r); + sin = MathUtils.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(); + } + } + + float[] ComputeWorldPositions(PathAttachment path, int spacesCount, bool tangents) + { + Slot target = this.target; + float position = this.position; + float[] spaces = this.spaces.Items, output = this.positions.Resize(spacesCount * 3 + 2).Items, world; + bool closed = path.Closed; + int verticesLength = path.WorldVerticesLength, curveCount = verticesLength / 6, prevCurve = NONE; + + float pathLength, multiplier; + if (!path.ConstantSpeed) + { + float[] lengths = path.Lengths; + curveCount -= closed ? 1 : 2; + pathLength = lengths[curveCount]; + + if (data.positionMode == PositionMode.Percent) position *= pathLength; + + switch (data.spacingMode) + { + case SpacingMode.Percent: + multiplier = pathLength; + break; + case SpacingMode.Proportional: + multiplier = pathLength / spacesCount; + break; + default: + multiplier = 1; + break; + } + + world = this.world.Resize(8).Items; + 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 %= pathLength; + if (p < 0) p += pathLength; + curve = 0; + } + else if (p < 0) + { + if (prevCurve != BEFORE) + { + prevCurve = BEFORE; + path.ComputeWorldVertices(target, 2, 4, world, 0, 2); + } + AddBeforePosition(p, world, 0, output, o); + continue; + } + else if (p > pathLength) + { + if (prevCurve != AFTER) + { + prevCurve = AFTER; + path.ComputeWorldVertices(target, verticesLength - 6, 4, world, 0, 2); + } + AddAfterPosition(p - pathLength, world, 0, output, 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, 2); + path.ComputeWorldVertices(target, 0, 4, world, 4, 2); + } + else + path.ComputeWorldVertices(target, curve * 6 + 2, 8, world, 0, 2); + } + AddCurvePosition(p, world[0], world[1], world[2], world[3], world[4], world[5], world[6], world[7], output, o, + tangents || (i > 0 && space < PathConstraint.Epsilon)); + } + return output; + } + + // World vertices. + if (closed) + { + verticesLength += 2; + world = this.world.Resize(verticesLength).Items; + path.ComputeWorldVertices(target, 2, verticesLength - 4, world, 0, 2); + path.ComputeWorldVertices(target, 0, 2, world, verticesLength - 4, 2); + world[verticesLength - 2] = world[0]; + world[verticesLength - 1] = world[1]; + } + else + { + curveCount--; + verticesLength -= 4; + world = this.world.Resize(verticesLength).Items; + path.ComputeWorldVertices(target, 2, verticesLength, world, 0, 2); + } + + // Curve lengths. + float[] curves = this.curves.Resize(curveCount).Items; + 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 += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx; + dfy += ddfy; + pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx + dddfx; + dfy += ddfy + dddfy; + pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + curves[i] = pathLength; + x1 = x2; + y1 = y2; + } + + if (data.positionMode == PositionMode.Percent) position *= pathLength; + + switch (data.spacingMode) + { + case SpacingMode.Percent: + multiplier = pathLength; + break; + case SpacingMode.Proportional: + multiplier = pathLength / spacesCount; + break; + default: + multiplier = 1; + break; + } + + float[] segments = this.segments; + 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 %= pathLength; + if (p < 0) p += pathLength; + curve = 0; + } + else if (p < 0) + { + AddBeforePosition(p, world, 0, output, o); + continue; + } + else if (p > pathLength) + { + AddAfterPosition(p - pathLength, world, verticesLength - 4, output, 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 = (float)Math.Sqrt(dfx * dfx + dfy * dfy); + segments[0] = curveLength; + for (ii = 1; ii < 8; ii++) + { + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + segments[ii] = curveLength; + } + dfx += ddfx; + dfy += ddfy; + curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + segments[8] = curveLength; + dfx += ddfx + dddfx; + dfy += ddfy + dddfy; + curveLength += (float)Math.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, output, o, tangents || (i > 0 && space < PathConstraint.Epsilon)); + } + return output; + } + + static void AddBeforePosition(float p, float[] temp, int i, float[] output, int o) + { + float x1 = temp[i], y1 = temp[i + 1], dx = temp[i + 2] - x1, dy = temp[i + 3] - y1, r = MathUtils.Atan2(dy, dx); + output[o] = x1 + p * MathUtils.Cos(r); + output[o + 1] = y1 + p * MathUtils.Sin(r); + output[o + 2] = r; + } + + static void AddAfterPosition(float p, float[] temp, int i, float[] output, int o) + { + float x1 = temp[i + 2], y1 = temp[i + 3], dx = x1 - temp[i], dy = y1 - temp[i + 1], r = MathUtils.Atan2(dy, dx); + output[o] = x1 + p * MathUtils.Cos(r); + output[o + 1] = y1 + p * MathUtils.Sin(r); + output[o + 2] = r; + } + + static void AddCurvePosition(float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2, + float[] output, int o, bool tangents) + { + if (p < PathConstraint.Epsilon || float.IsNaN(p)) + { + output[o] = x1; + output[o + 1] = y1; + output[o + 2] = (float)Math.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.001f) + output[o + 2] = (float)Math.Atan2(cy1 - y1, cx1 - x1); + else + output[o + 2] = (float)Math.Atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt)); + } + } + + /// The position along the path. + public float Position { get { return position; } set { position = value; } } + /// The spacing between bones. + public float Spacing { get { return spacing; } set { spacing = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotations. + public float MixRotate { get { return mixRotate; } set { mixRotate = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. + public float MixX { get { return mixX; } set { mixX = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. + public float MixY { get { return mixY; } set { mixY = value; } } + /// The bones that will be modified by this path constraint. + public ExposedList Bones { get { return bones; } } + /// The slot whose path attachment will be used to constrained the bones. + public Slot Target { get { return target; } set { target = value; } } + public bool Active { get { return active; } } + /// The path constraint's setup pose data. + public PathConstraintData Data { get { return data; } } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/PathConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/PathConstraintData.cs index 99a3587..41cad86 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/PathConstraintData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/PathConstraintData.cs @@ -27,46 +27,50 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; +namespace Spine4_0_64 +{ + public class PathConstraintData : ConstraintData + { + internal ExposedList bones = new ExposedList(); + internal SlotData target; + internal PositionMode positionMode; + internal SpacingMode spacingMode; + internal RotateMode rotateMode; + internal float offsetRotation; + internal float position, spacing, mixRotate, mixX, mixY; -namespace Spine4_0_64 { - public class PathConstraintData : ConstraintData { - internal ExposedList bones = new ExposedList(); - internal SlotData target; - internal PositionMode positionMode; - internal SpacingMode spacingMode; - internal RotateMode rotateMode; - internal float offsetRotation; - internal float position, spacing, mixRotate, mixX, mixY; + public PathConstraintData(string name) : base(name) + { + } - public PathConstraintData (string name) : base(name) { - } + public ExposedList Bones { get { return bones; } } + public SlotData Target { get { return target; } set { target = value; } } + public PositionMode PositionMode { get { return positionMode; } set { positionMode = value; } } + public SpacingMode SpacingMode { get { return spacingMode; } set { spacingMode = value; } } + public RotateMode RotateMode { get { return rotateMode; } set { rotateMode = value; } } + public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } + public float Position { get { return position; } set { position = value; } } + public float Spacing { get { return spacing; } set { spacing = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. + public float RotateMix { get { return mixRotate; } set { mixRotate = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. + public float MixX { get { return mixX; } set { mixX = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. + public float MixY { get { return mixY; } set { mixY = value; } } + } - public ExposedList Bones { get { return bones; } } - public SlotData Target { get { return target; } set { target = value; } } - public PositionMode PositionMode { get { return positionMode; } set { positionMode = value; } } - public SpacingMode SpacingMode { get { return spacingMode; } set { spacingMode = value; } } - public RotateMode RotateMode { get { return rotateMode; } set { rotateMode = value; } } - public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } - public float Position { get { return position; } set { position = value; } } - public float Spacing { get { return spacing; } set { spacing = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. - public float RotateMix { get { return mixRotate; } set { mixRotate = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. - public float MixX { get { return mixX; } set { mixX = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. - public float MixY { get { return mixY; } set { mixY = value; } } - } + public enum PositionMode + { + Fixed, Percent + } - public enum PositionMode { - Fixed, Percent - } + public enum SpacingMode + { + Length, Fixed, Percent, Proportional + } - public enum SpacingMode { - Length, Fixed, Percent, Proportional - } - - public enum RotateMode { - Tangent, Chain, ChainScale - } + public enum RotateMode + { + Tangent, Chain, ChainScale + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Skeleton.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Skeleton.cs index 5fca86a..0457a95 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Skeleton.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Skeleton.cs @@ -29,586 +29,667 @@ using System; -namespace Spine4_0_64 { - public class Skeleton { - internal SkeletonData data; - internal ExposedList bones; - internal ExposedList slots; - internal ExposedList drawOrder; - internal ExposedList ikConstraints; - internal ExposedList transformConstraints; - internal ExposedList pathConstraints; - internal ExposedList updateCache = new ExposedList(); - internal Skin skin; - internal float r = 1, g = 1, b = 1, a = 1; - internal float time; - private float scaleX = 1, scaleY = 1; - internal float x, y; - - public SkeletonData Data { get { return data; } } - public ExposedList Bones { get { return bones; } } - public ExposedList UpdateCacheList { get { return updateCache; } } - public ExposedList Slots { get { return slots; } } - public ExposedList DrawOrder { get { return drawOrder; } } - public ExposedList IkConstraints { get { return ikConstraints; } } - public ExposedList PathConstraints { get { return pathConstraints; } } - public ExposedList TransformConstraints { get { return transformConstraints; } } - - public Skin Skin { - /// The skeleton's current skin. May be null. - get { return skin; } - /// Sets a skin, . - set { SetSkin(value); } - } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } - public float Time { get { return time; } set { time = value; } } - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - public float ScaleY { get { return scaleY * (Bone.yDown ? -1 : 1); } set { scaleY = value; } } - - [Obsolete("Use ScaleX instead. FlipX is when ScaleX is negative.")] - public bool FlipX { get { return scaleX < 0; } set { scaleX = value ? -1f : 1f; } } - - [Obsolete("Use ScaleY instead. FlipY is when ScaleY is negative.")] - public bool FlipY { get { return scaleY < 0; } set { scaleY = value ? -1f : 1f; } } - - /// Returns the root bone, or null if the skeleton has no bones. - public Bone RootBone { - get { return bones.Count == 0 ? null : bones.Items[0]; } - } - - public Skeleton (SkeletonData data) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - this.data = data; - - bones = new ExposedList(data.bones.Count); - Bone[] bonesItems = this.bones.Items; - foreach (BoneData boneData in data.bones) { - Bone bone; - if (boneData.parent == null) { - bone = new Bone(boneData, this, null); - } else { - Bone parent = bonesItems[boneData.parent.index]; - bone = new Bone(boneData, this, parent); - parent.children.Add(bone); - } - this.bones.Add(bone); - } - - slots = new ExposedList(data.slots.Count); - drawOrder = new ExposedList(data.slots.Count); - foreach (SlotData slotData in data.slots) { - Bone bone = bonesItems[slotData.boneData.index]; - Slot slot = new Slot(slotData, bone); - slots.Add(slot); - drawOrder.Add(slot); - } - - ikConstraints = new ExposedList(data.ikConstraints.Count); - foreach (IkConstraintData ikConstraintData in data.ikConstraints) - ikConstraints.Add(new IkConstraint(ikConstraintData, this)); - - transformConstraints = new ExposedList(data.transformConstraints.Count); - foreach (TransformConstraintData transformConstraintData in data.transformConstraints) - transformConstraints.Add(new TransformConstraint(transformConstraintData, this)); - - pathConstraints = new ExposedList(data.pathConstraints.Count); - foreach (PathConstraintData pathConstraintData in data.pathConstraints) - pathConstraints.Add(new PathConstraint(pathConstraintData, this)); - - UpdateCache(); - } - - /// Caches information about bones and constraints. Must be called if the is modified or if bones, constraints, or - /// constraints, or weighted path attachments are added or removed. - public void UpdateCache () { - var updateCache = this.updateCache; - updateCache.Clear(); - - int boneCount = this.bones.Count; - Bone[] bones = this.bones.Items; - for (int i = 0; i < boneCount; i++) { - Bone bone = bones[i]; - bone.sorted = bone.data.skinRequired; - bone.active = !bone.sorted; - } - if (skin != null) { - BoneData[] skinBones = skin.bones.Items; - for (int i = 0, n = skin.bones.Count; i < n; i++) { - var bone = bones[skinBones[i].index]; - do { - bone.sorted = false; - bone.active = true; - bone = bone.parent; - } while (bone != null); - } - } - - int ikCount = this.ikConstraints.Count, transformCount = this.transformConstraints.Count, pathCount = this.pathConstraints.Count; - IkConstraint[] ikConstraints = this.ikConstraints.Items; - TransformConstraint[] transformConstraints = this.transformConstraints.Items; - PathConstraint[] pathConstraints = this.pathConstraints.Items; - int constraintCount = ikCount + transformCount + pathCount; - for (int i = 0; i < constraintCount; i++) { - for (int ii = 0; ii < ikCount; ii++) { - IkConstraint constraint = ikConstraints[ii]; - if (constraint.data.order == i) { - SortIkConstraint(constraint); - goto continue_outer; - } - } - for (int ii = 0; ii < transformCount; ii++) { - TransformConstraint constraint = transformConstraints[ii]; - if (constraint.data.order == i) { - SortTransformConstraint(constraint); - goto continue_outer; - } - } - for (int ii = 0; ii < pathCount; ii++) { - PathConstraint constraint = pathConstraints[ii]; - if (constraint.data.order == i) { - SortPathConstraint(constraint); - goto continue_outer; - } - } - continue_outer: { } - } - - for (int i = 0; i < boneCount; i++) - SortBone(bones[i]); - } - - private void SortIkConstraint (IkConstraint constraint) { - constraint.active = constraint.target.active - && (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data))); - if (!constraint.active) return; - - Bone target = constraint.target; - SortBone(target); - - var constrained = constraint.bones; - Bone parent = constrained.Items[0]; - SortBone(parent); - - if (constrained.Count == 1) { - updateCache.Add(constraint); - SortReset(parent.children); - } else { - Bone child = constrained.Items[constrained.Count - 1]; - SortBone(child); - - updateCache.Add(constraint); - - SortReset(parent.children); - child.sorted = true; - } - } - - private void SortPathConstraint (PathConstraint constraint) { - constraint.active = constraint.target.bone.active - && (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data))); - if (!constraint.active) return; - - Slot slot = constraint.target; - int slotIndex = slot.data.index; - Bone slotBone = slot.bone; - if (skin != null) SortPathConstraintAttachment(skin, slotIndex, slotBone); - if (data.defaultSkin != null && data.defaultSkin != skin) - SortPathConstraintAttachment(data.defaultSkin, slotIndex, slotBone); - - Attachment attachment = slot.attachment; - if (attachment is PathAttachment) SortPathConstraintAttachment(attachment, slotBone); - - var constrained = constraint.bones.Items; - int boneCount = constraint.bones.Count; - for (int i = 0; i < boneCount; i++) - SortBone(constrained[i]); - - updateCache.Add(constraint); - - for (int i = 0; i < boneCount; i++) - SortReset(constrained[i].children); - for (int i = 0; i < boneCount; i++) - constrained[i].sorted = true; - } - - private void SortTransformConstraint (TransformConstraint constraint) { - constraint.active = constraint.target.active - && (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data))); - if (!constraint.active) return; - - SortBone(constraint.target); - - var constrained = constraint.bones.Items; - int boneCount = constraint.bones.Count; - if (constraint.data.local) { - for (int i = 0; i < boneCount; i++) { - Bone child = constrained[i]; - SortBone(child.parent); - SortBone(child); - } - } else { - for (int i = 0; i < boneCount; i++) - SortBone(constrained[i]); - } - - updateCache.Add(constraint); - - for (int i = 0; i < boneCount; i++) - SortReset(constrained[i].children); - for (int i = 0; i < boneCount; i++) - constrained[i].sorted = true; - } - - private void SortPathConstraintAttachment (Skin skin, int slotIndex, Bone slotBone) { - foreach (var entry in skin.Attachments) - if (entry.SlotIndex == slotIndex) SortPathConstraintAttachment(entry.Attachment, slotBone); - } - - private void SortPathConstraintAttachment (Attachment attachment, Bone slotBone) { - if (!(attachment is PathAttachment)) return; - int[] pathBones = ((PathAttachment)attachment).bones; - if (pathBones == null) - SortBone(slotBone); - else { - var bones = this.bones.Items; - for (int i = 0, n = pathBones.Length; i < n;) { - int nn = pathBones[i++]; - nn += i; - while (i < nn) - SortBone(bones[pathBones[i++]]); - } - } - } - - private void SortBone (Bone bone) { - if (bone.sorted) return; - Bone parent = bone.parent; - if (parent != null) SortBone(parent); - bone.sorted = true; - updateCache.Add(bone); - } - - private static void SortReset (ExposedList bones) { - Bone[] bonesItems = bones.Items; - for (int i = 0, n = bones.Count; i < n; i++) { - Bone bone = bonesItems[i]; - if (!bone.active) continue; - if (bone.sorted) SortReset(bone.children); - bone.sorted = false; - } - } - - /// - /// Updates the world transform for each bone and applies all constraints. - /// - /// See World transforms in the Spine - /// Runtimes Guide. - /// - public void UpdateWorldTransform () { - Bone[] bones = this.bones.Items; - for (int i = 0, n = this.bones.Count; 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; - } - - var updateCache = this.updateCache.Items; - for (int i = 0, n = this.updateCache.Count; i < n; i++) - updateCache[i].Update(); - } - - /// - /// Temporarily sets the root bone as a child of the specified bone, then updates the world transform for each bone and applies - /// all constraints. - /// - public void UpdateWorldTransform (Bone parent) { - if (parent == null) throw new ArgumentNullException("parent", "parent cannot be null."); - - // Apply the parent bone transform to the root bone. The root bone always inherits scale, rotation and reflection. - Bone rootBone = this.RootBone; - 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 rotationY = rootBone.rotation + 90 + rootBone.shearY; - float la = MathUtils.CosDeg(rootBone.rotation + rootBone.shearX) * rootBone.scaleX; - float lb = MathUtils.CosDeg(rotationY) * rootBone.scaleY; - float lc = MathUtils.SinDeg(rootBone.rotation + rootBone.shearX) * rootBone.scaleX; - float ld = MathUtils.SinDeg(rotationY) * 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. - var updateCache = this.updateCache.Items; - for (int i = 0, n = this.updateCache.Count; i < n; i++) { - var updatable = updateCache[i]; - if (updatable != rootBone) updatable.Update(); - } - } - - /// Sets the bones, constraints, and slots to their setup pose values. - public void SetToSetupPose () { - SetBonesToSetupPose(); - SetSlotsToSetupPose(); - } - - /// Sets the bones and constraints to their setup pose values. - public void SetBonesToSetupPose () { - var bones = this.bones.Items; - for (int i = 0, n = this.bones.Count; i < n; i++) - bones[i].SetToSetupPose(); - - var ikConstraints = this.ikConstraints.Items; - for (int i = 0, n = this.ikConstraints.Count; i < n; i++) { - IkConstraint constraint = ikConstraints[i]; - IkConstraintData data = constraint.data; - constraint.mix = data.mix; - constraint.softness = data.softness; - constraint.bendDirection = data.bendDirection; - constraint.compress = data.compress; - constraint.stretch = data.stretch; - } - - var transformConstraints = this.transformConstraints.Items; - for (int i = 0, n = this.transformConstraints.Count; i < n; i++) { - TransformConstraint constraint = transformConstraints[i]; - TransformConstraintData data = constraint.data; - constraint.mixRotate = data.mixRotate; - constraint.mixX = data.mixX; - constraint.mixY = data.mixY; - constraint.mixScaleX = data.mixScaleX; - constraint.mixScaleY = data.mixScaleY; - constraint.mixShearY = data.mixShearY; - } - - var pathConstraints = this.pathConstraints.Items; - for (int i = 0, n = this.pathConstraints.Count; i < n; i++) { - PathConstraint constraint = pathConstraints[i]; - PathConstraintData data = constraint.data; - constraint.position = data.position; - constraint.spacing = data.spacing; - constraint.mixRotate = data.mixRotate; - constraint.mixX = data.mixX; - constraint.mixY = data.mixY; - } - } - - public void SetSlotsToSetupPose () { - var slots = this.slots.Items; - int n = this.slots.Count; - Array.Copy(slots, 0, drawOrder.Items, 0, n); - for (int i = 0; i < n; i++) - slots[i].SetToSetupPose(); - } - - /// Finds a bone by comparing each bone's name. It is more efficient to cache the results of this method than to call it - /// repeatedly. - /// May be null. - public Bone FindBone (string boneName) { - if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); - var bones = this.bones.Items; - for (int i = 0, n = this.bones.Count; i < n; i++) { - Bone bone = bones[i]; - if (bone.data.name == boneName) return bone; - } - return null; - } - - /// Finds a slot by comparing each slot's name. It is more efficient to cache the results of this method than to call it - /// repeatedly. - /// May be null. - public Slot FindSlot (string slotName) { - if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); - var slots = this.slots.Items; - for (int i = 0, n = this.slots.Count; i < n; i++) { - Slot slot = slots[i]; - if (slot.data.name == slotName) return slot; - } - return null; - } - - /// Sets a skin by name (). - public void SetSkin (string skinName) { - Skin foundSkin = data.FindSkin(skinName); - if (foundSkin == null) throw new ArgumentException("Skin not found: " + skinName, "skinName"); - SetSkin(foundSkin); - } - - /// - /// Sets the skin used to look up attachments before looking in the . If the - /// skin is changed, is called. - /// - /// 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 - /// . - /// Also, often 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. - /// - /// May be null. - public void SetSkin (Skin newSkin) { - if (newSkin == skin) return; - if (newSkin != null) { - if (skin != null) - newSkin.AttachAll(this, skin); - else { - Slot[] slots = this.slots.Items; - for (int i = 0, n = this.slots.Count; i < n; i++) { - Slot slot = slots[i]; - string name = slot.data.attachmentName; - if (name != null) { - Attachment attachment = newSkin.GetAttachment(i, name); - if (attachment != null) slot.Attachment = attachment; - } - } - } - } - skin = newSkin; - UpdateCache(); - } - - /// Finds an attachment by looking in the and using the slot name and attachment name. - /// May be null. - public Attachment GetAttachment (string slotName, string attachmentName) { - return GetAttachment(data.FindSlot(slotName).index, attachmentName); - } - - /// Finds an attachment by looking in the skin and skeletonData.defaultSkin using the slot index and attachment name.First the skin is checked and if the attachment was not found, the default skin is checked. - /// May be null. - public Attachment GetAttachment (int slotIndex, string attachmentName) { - if (attachmentName == null) throw new ArgumentNullException("attachmentName", "attachmentName cannot be null."); - if (skin != null) { - Attachment attachment = skin.GetAttachment(slotIndex, attachmentName); - if (attachment != null) return attachment; - } - return data.defaultSkin != null ? data.defaultSkin.GetAttachment(slotIndex, attachmentName) : null; - } - - /// A convenience method to set an attachment by finding the slot with FindSlot, finding the attachment with GetAttachment, then setting the slot's slot.Attachment. - /// May be null to clear the slot's attachment. - public void SetAttachment (string slotName, string attachmentName) { - if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); - Slot[] slots = this.slots.Items; - for (int i = 0, n = this.slots.Count; i < n; i++) { - Slot slot = slots[i]; - if (slot.data.name == slotName) { - Attachment attachment = null; - if (attachmentName != null) { - attachment = GetAttachment(i, attachmentName); - if (attachment == null) throw new Exception("Attachment not found: " + attachmentName + ", for slot: " + slotName); - } - slot.Attachment = attachment; - return; - } - } - throw new Exception("Slot not found: " + slotName); - } - - /// Finds an IK constraint by comparing each IK constraint's name. It is more efficient to cache the results of this method - /// than to call it repeatedly. - /// May be null. - public IkConstraint FindIkConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - var ikConstraints = this.ikConstraints.Items; - for (int i = 0, n = this.ikConstraints.Count; i < n; i++) { - IkConstraint ikConstraint = ikConstraints[i]; - if (ikConstraint.data.name == constraintName) return ikConstraint; - } - return null; - } - - /// Finds a transform constraint by comparing each transform constraint's name. It is more efficient to cache the results of - /// this method than to call it repeatedly. - /// May be null. - public TransformConstraint FindTransformConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - var transformConstraints = this.transformConstraints.Items; - for (int i = 0, n = this.transformConstraints.Count; i < n; i++) { - TransformConstraint transformConstraint = transformConstraints[i]; - if (transformConstraint.data.Name == constraintName) return transformConstraint; - } - return null; - } - - /// Finds a path constraint by comparing each path constraint's name. It is more efficient to cache the results of this method - /// than to call it repeatedly. - /// May be null. - public PathConstraint FindPathConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - var pathConstraints = this.pathConstraints.Items; - for (int i = 0, n = this.pathConstraints.Count; i < n; i++) { - PathConstraint constraint = pathConstraints[i]; - if (constraint.data.Name.Equals(constraintName)) return constraint; - } - return null; - } - - public void Update (float delta) { - time += delta; - } - - /// Returns the axis aligned bounding box (AABB) of the region and mesh attachments for the current pose. - /// The horizontal distance between the skeleton origin and the left side of the AABB. - /// The vertical distance between the skeleton origin and the bottom side of the AABB. - /// The width of the AABB - /// The height of the AABB. - /// Reference to hold a float[]. May be a null reference. This method will assign it a new float[] with the appropriate size as needed. - public void GetBounds (out float x, out float y, out float width, out float height, ref float[] vertexBuffer) { - float[] temp = vertexBuffer; - temp = temp ?? new float[8]; - var drawOrder = this.drawOrder.Items; - float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; - for (int i = 0, n = this.drawOrder.Count; i < n; i++) { - Slot slot = drawOrder[i]; - if (!slot.bone.active) continue; - int verticesLength = 0; - float[] vertices = null; - Attachment attachment = slot.attachment; - var regionAttachment = attachment as RegionAttachment; - if (regionAttachment != null) { - verticesLength = 8; - vertices = temp; - if (vertices.Length < 8) vertices = temp = new float[8]; - regionAttachment.ComputeWorldVertices(slot.bone, temp, 0); - } else { - var meshAttachment = attachment as MeshAttachment; - if (meshAttachment != null) { - MeshAttachment mesh = meshAttachment; - verticesLength = mesh.WorldVerticesLength; - vertices = temp; - if (vertices.Length < verticesLength) vertices = temp = new float[verticesLength]; - mesh.ComputeWorldVertices(slot, 0, verticesLength, temp, 0); - } - } - - if (vertices != null) { - for (int ii = 0; ii < verticesLength; ii += 2) { - float vx = vertices[ii], vy = vertices[ii + 1]; - minX = Math.Min(minX, vx); - minY = Math.Min(minY, vy); - maxX = Math.Max(maxX, vx); - maxY = Math.Max(maxY, vy); - } - } - } - x = minX; - y = minY; - width = maxX - minX; - height = maxY - minY; - vertexBuffer = temp; - } - } +namespace Spine4_0_64 +{ + public class Skeleton + { + internal SkeletonData data; + internal ExposedList bones; + internal ExposedList slots; + internal ExposedList drawOrder; + internal ExposedList ikConstraints; + internal ExposedList transformConstraints; + internal ExposedList pathConstraints; + internal ExposedList updateCache = new ExposedList(); + internal Skin skin; + internal float r = 1, g = 1, b = 1, a = 1; + internal float time; + private float scaleX = 1, scaleY = 1; + internal float x, y; + + public SkeletonData Data { get { return data; } } + public ExposedList Bones { get { return bones; } } + public ExposedList UpdateCacheList { get { return updateCache; } } + public ExposedList Slots { get { return slots; } } + public ExposedList DrawOrder { get { return drawOrder; } } + public ExposedList IkConstraints { get { return ikConstraints; } } + public ExposedList PathConstraints { get { return pathConstraints; } } + public ExposedList TransformConstraints { get { return transformConstraints; } } + + public Skin Skin + { + /// The skeleton's current skin. May be null. + get { return skin; } + /// Sets a skin, . + set { SetSkin(value); } + } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } + public float Time { get { return time; } set { time = value; } } + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + public float ScaleY { get { return scaleY * (Bone.yDown ? -1 : 1); } set { scaleY = value; } } + + [Obsolete("Use ScaleX instead. FlipX is when ScaleX is negative.")] + public bool FlipX { get { return scaleX < 0; } set { scaleX = value ? -1f : 1f; } } + + [Obsolete("Use ScaleY instead. FlipY is when ScaleY is negative.")] + public bool FlipY { get { return scaleY < 0; } set { scaleY = value ? -1f : 1f; } } + + /// Returns the root bone, or null if the skeleton has no bones. + public Bone RootBone + { + get { return bones.Count == 0 ? null : bones.Items[0]; } + } + + public Skeleton(SkeletonData data) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + this.data = data; + + bones = new ExposedList(data.bones.Count); + Bone[] bonesItems = this.bones.Items; + foreach (BoneData boneData in data.bones) + { + Bone bone; + if (boneData.parent == null) + { + bone = new Bone(boneData, this, null); + } + else + { + Bone parent = bonesItems[boneData.parent.index]; + bone = new Bone(boneData, this, parent); + parent.children.Add(bone); + } + this.bones.Add(bone); + } + + slots = new ExposedList(data.slots.Count); + drawOrder = new ExposedList(data.slots.Count); + foreach (SlotData slotData in data.slots) + { + Bone bone = bonesItems[slotData.boneData.index]; + Slot slot = new Slot(slotData, bone); + slots.Add(slot); + drawOrder.Add(slot); + } + + ikConstraints = new ExposedList(data.ikConstraints.Count); + foreach (IkConstraintData ikConstraintData in data.ikConstraints) + ikConstraints.Add(new IkConstraint(ikConstraintData, this)); + + transformConstraints = new ExposedList(data.transformConstraints.Count); + foreach (TransformConstraintData transformConstraintData in data.transformConstraints) + transformConstraints.Add(new TransformConstraint(transformConstraintData, this)); + + pathConstraints = new ExposedList(data.pathConstraints.Count); + foreach (PathConstraintData pathConstraintData in data.pathConstraints) + pathConstraints.Add(new PathConstraint(pathConstraintData, this)); + + UpdateCache(); + } + + /// Caches information about bones and constraints. Must be called if the is modified or if bones, constraints, or + /// constraints, or weighted path attachments are added or removed. + public void UpdateCache() + { + var updateCache = this.updateCache; + updateCache.Clear(); + + int boneCount = this.bones.Count; + Bone[] bones = this.bones.Items; + for (int i = 0; i < boneCount; i++) + { + Bone bone = bones[i]; + bone.sorted = bone.data.skinRequired; + bone.active = !bone.sorted; + } + if (skin != null) + { + BoneData[] skinBones = skin.bones.Items; + for (int i = 0, n = skin.bones.Count; i < n; i++) + { + var bone = bones[skinBones[i].index]; + do + { + bone.sorted = false; + bone.active = true; + bone = bone.parent; + } while (bone != null); + } + } + + int ikCount = this.ikConstraints.Count, transformCount = this.transformConstraints.Count, pathCount = this.pathConstraints.Count; + IkConstraint[] ikConstraints = this.ikConstraints.Items; + TransformConstraint[] transformConstraints = this.transformConstraints.Items; + PathConstraint[] pathConstraints = this.pathConstraints.Items; + int constraintCount = ikCount + transformCount + pathCount; + for (int i = 0; i < constraintCount; i++) + { + for (int ii = 0; ii < ikCount; ii++) + { + IkConstraint constraint = ikConstraints[ii]; + if (constraint.data.order == i) + { + SortIkConstraint(constraint); + goto continue_outer; + } + } + for (int ii = 0; ii < transformCount; ii++) + { + TransformConstraint constraint = transformConstraints[ii]; + if (constraint.data.order == i) + { + SortTransformConstraint(constraint); + goto continue_outer; + } + } + for (int ii = 0; ii < pathCount; ii++) + { + PathConstraint constraint = pathConstraints[ii]; + if (constraint.data.order == i) + { + SortPathConstraint(constraint); + goto continue_outer; + } + } + continue_outer: { } + } + + for (int i = 0; i < boneCount; i++) + SortBone(bones[i]); + } + + private void SortIkConstraint(IkConstraint constraint) + { + constraint.active = constraint.target.active + && (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data))); + if (!constraint.active) return; + + Bone target = constraint.target; + SortBone(target); + + var constrained = constraint.bones; + Bone parent = constrained.Items[0]; + SortBone(parent); + + if (constrained.Count == 1) + { + updateCache.Add(constraint); + SortReset(parent.children); + } + else + { + Bone child = constrained.Items[constrained.Count - 1]; + SortBone(child); + + updateCache.Add(constraint); + + SortReset(parent.children); + child.sorted = true; + } + } + + private void SortPathConstraint(PathConstraint constraint) + { + constraint.active = constraint.target.bone.active + && (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data))); + if (!constraint.active) return; + + Slot slot = constraint.target; + int slotIndex = slot.data.index; + Bone slotBone = slot.bone; + if (skin != null) SortPathConstraintAttachment(skin, slotIndex, slotBone); + if (data.defaultSkin != null && data.defaultSkin != skin) + SortPathConstraintAttachment(data.defaultSkin, slotIndex, slotBone); + + Attachment attachment = slot.attachment; + if (attachment is PathAttachment) SortPathConstraintAttachment(attachment, slotBone); + + var constrained = constraint.bones.Items; + int boneCount = constraint.bones.Count; + for (int i = 0; i < boneCount; i++) + SortBone(constrained[i]); + + updateCache.Add(constraint); + + for (int i = 0; i < boneCount; i++) + SortReset(constrained[i].children); + for (int i = 0; i < boneCount; i++) + constrained[i].sorted = true; + } + + private void SortTransformConstraint(TransformConstraint constraint) + { + constraint.active = constraint.target.active + && (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data))); + if (!constraint.active) return; + + SortBone(constraint.target); + + var constrained = constraint.bones.Items; + int boneCount = constraint.bones.Count; + if (constraint.data.local) + { + for (int i = 0; i < boneCount; i++) + { + Bone child = constrained[i]; + SortBone(child.parent); + SortBone(child); + } + } + else + { + for (int i = 0; i < boneCount; i++) + SortBone(constrained[i]); + } + + updateCache.Add(constraint); + + for (int i = 0; i < boneCount; i++) + SortReset(constrained[i].children); + for (int i = 0; i < boneCount; i++) + constrained[i].sorted = true; + } + + private void SortPathConstraintAttachment(Skin skin, int slotIndex, Bone slotBone) + { + foreach (var entry in skin.Attachments) + if (entry.SlotIndex == slotIndex) SortPathConstraintAttachment(entry.Attachment, slotBone); + } + + private void SortPathConstraintAttachment(Attachment attachment, Bone slotBone) + { + if (!(attachment is PathAttachment)) return; + int[] pathBones = ((PathAttachment)attachment).bones; + if (pathBones == null) + SortBone(slotBone); + else + { + var bones = this.bones.Items; + for (int i = 0, n = pathBones.Length; i < n;) + { + int nn = pathBones[i++]; + nn += i; + while (i < nn) + SortBone(bones[pathBones[i++]]); + } + } + } + + private void SortBone(Bone bone) + { + if (bone.sorted) return; + Bone parent = bone.parent; + if (parent != null) SortBone(parent); + bone.sorted = true; + updateCache.Add(bone); + } + + private static void SortReset(ExposedList bones) + { + Bone[] bonesItems = bones.Items; + for (int i = 0, n = bones.Count; i < n; i++) + { + Bone bone = bonesItems[i]; + if (!bone.active) continue; + if (bone.sorted) SortReset(bone.children); + bone.sorted = false; + } + } + + /// + /// Updates the world transform for each bone and applies all constraints. + /// + /// See World transforms in the Spine + /// Runtimes Guide. + /// + public void UpdateWorldTransform() + { + Bone[] bones = this.bones.Items; + for (int i = 0, n = this.bones.Count; 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; + } + + var updateCache = this.updateCache.Items; + for (int i = 0, n = this.updateCache.Count; i < n; i++) + updateCache[i].Update(); + } + + /// + /// Temporarily sets the root bone as a child of the specified bone, then updates the world transform for each bone and applies + /// all constraints. + /// + public void UpdateWorldTransform(Bone parent) + { + if (parent == null) throw new ArgumentNullException("parent", "parent cannot be null."); + + // Apply the parent bone transform to the root bone. The root bone always inherits scale, rotation and reflection. + Bone rootBone = this.RootBone; + 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 rotationY = rootBone.rotation + 90 + rootBone.shearY; + float la = MathUtils.CosDeg(rootBone.rotation + rootBone.shearX) * rootBone.scaleX; + float lb = MathUtils.CosDeg(rotationY) * rootBone.scaleY; + float lc = MathUtils.SinDeg(rootBone.rotation + rootBone.shearX) * rootBone.scaleX; + float ld = MathUtils.SinDeg(rotationY) * 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. + var updateCache = this.updateCache.Items; + for (int i = 0, n = this.updateCache.Count; i < n; i++) + { + var updatable = updateCache[i]; + if (updatable != rootBone) updatable.Update(); + } + } + + /// Sets the bones, constraints, and slots to their setup pose values. + public void SetToSetupPose() + { + SetBonesToSetupPose(); + SetSlotsToSetupPose(); + } + + /// Sets the bones and constraints to their setup pose values. + public void SetBonesToSetupPose() + { + var bones = this.bones.Items; + for (int i = 0, n = this.bones.Count; i < n; i++) + bones[i].SetToSetupPose(); + + var ikConstraints = this.ikConstraints.Items; + for (int i = 0, n = this.ikConstraints.Count; i < n; i++) + { + IkConstraint constraint = ikConstraints[i]; + IkConstraintData data = constraint.data; + constraint.mix = data.mix; + constraint.softness = data.softness; + constraint.bendDirection = data.bendDirection; + constraint.compress = data.compress; + constraint.stretch = data.stretch; + } + + var transformConstraints = this.transformConstraints.Items; + for (int i = 0, n = this.transformConstraints.Count; i < n; i++) + { + TransformConstraint constraint = transformConstraints[i]; + TransformConstraintData data = constraint.data; + constraint.mixRotate = data.mixRotate; + constraint.mixX = data.mixX; + constraint.mixY = data.mixY; + constraint.mixScaleX = data.mixScaleX; + constraint.mixScaleY = data.mixScaleY; + constraint.mixShearY = data.mixShearY; + } + + var pathConstraints = this.pathConstraints.Items; + for (int i = 0, n = this.pathConstraints.Count; i < n; i++) + { + PathConstraint constraint = pathConstraints[i]; + PathConstraintData data = constraint.data; + constraint.position = data.position; + constraint.spacing = data.spacing; + constraint.mixRotate = data.mixRotate; + constraint.mixX = data.mixX; + constraint.mixY = data.mixY; + } + } + + public void SetSlotsToSetupPose() + { + var slots = this.slots.Items; + int n = this.slots.Count; + Array.Copy(slots, 0, drawOrder.Items, 0, n); + for (int i = 0; i < n; i++) + slots[i].SetToSetupPose(); + } + + /// Finds a bone by comparing each bone's name. It is more efficient to cache the results of this method than to call it + /// repeatedly. + /// May be null. + public Bone FindBone(string boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); + var bones = this.bones.Items; + for (int i = 0, n = this.bones.Count; i < n; i++) + { + Bone bone = bones[i]; + if (bone.data.name == boneName) return bone; + } + return null; + } + + /// Finds a slot by comparing each slot's name. It is more efficient to cache the results of this method than to call it + /// repeatedly. + /// May be null. + public Slot FindSlot(string slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + var slots = this.slots.Items; + for (int i = 0, n = this.slots.Count; i < n; i++) + { + Slot slot = slots[i]; + if (slot.data.name == slotName) return slot; + } + return null; + } + + /// Sets a skin by name (). + public void SetSkin(string skinName) + { + Skin foundSkin = data.FindSkin(skinName); + if (foundSkin == null) throw new ArgumentException("Skin not found: " + skinName, "skinName"); + SetSkin(foundSkin); + } + + /// + /// Sets the skin used to look up attachments before looking in the . If the + /// skin is changed, is called. + /// + /// 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 + /// . + /// Also, often 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. + /// + /// May be null. + public void SetSkin(Skin newSkin) + { + if (newSkin == skin) return; + if (newSkin != null) + { + if (skin != null) + newSkin.AttachAll(this, skin); + else + { + Slot[] slots = this.slots.Items; + for (int i = 0, n = this.slots.Count; i < n; i++) + { + Slot slot = slots[i]; + string name = slot.data.attachmentName; + if (name != null) + { + Attachment attachment = newSkin.GetAttachment(i, name); + if (attachment != null) slot.Attachment = attachment; + } + } + } + } + skin = newSkin; + UpdateCache(); + } + + /// Finds an attachment by looking in the and using the slot name and attachment name. + /// May be null. + public Attachment GetAttachment(string slotName, string attachmentName) + { + return GetAttachment(data.FindSlot(slotName).index, attachmentName); + } + + /// Finds an attachment by looking in the skin and skeletonData.defaultSkin using the slot index and attachment name.First the skin is checked and if the attachment was not found, the default skin is checked. + /// May be null. + public Attachment GetAttachment(int slotIndex, string attachmentName) + { + if (attachmentName == null) throw new ArgumentNullException("attachmentName", "attachmentName cannot be null."); + if (skin != null) + { + Attachment attachment = skin.GetAttachment(slotIndex, attachmentName); + if (attachment != null) return attachment; + } + return data.defaultSkin != null ? data.defaultSkin.GetAttachment(slotIndex, attachmentName) : null; + } + + /// A convenience method to set an attachment by finding the slot with FindSlot, finding the attachment with GetAttachment, then setting the slot's slot.Attachment. + /// May be null to clear the slot's attachment. + public void SetAttachment(string slotName, string attachmentName) + { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + Slot[] slots = this.slots.Items; + for (int i = 0, n = this.slots.Count; i < n; i++) + { + Slot slot = slots[i]; + if (slot.data.name == slotName) + { + Attachment attachment = null; + if (attachmentName != null) + { + attachment = GetAttachment(i, attachmentName); + if (attachment == null) throw new Exception("Attachment not found: " + attachmentName + ", for slot: " + slotName); + } + slot.Attachment = attachment; + return; + } + } + throw new Exception("Slot not found: " + slotName); + } + + /// Finds an IK constraint by comparing each IK constraint's name. It is more efficient to cache the results of this method + /// than to call it repeatedly. + /// May be null. + public IkConstraint FindIkConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + var ikConstraints = this.ikConstraints.Items; + for (int i = 0, n = this.ikConstraints.Count; i < n; i++) + { + IkConstraint ikConstraint = ikConstraints[i]; + if (ikConstraint.data.name == constraintName) return ikConstraint; + } + return null; + } + + /// Finds a transform constraint by comparing each transform constraint's name. It is more efficient to cache the results of + /// this method than to call it repeatedly. + /// May be null. + public TransformConstraint FindTransformConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + var transformConstraints = this.transformConstraints.Items; + for (int i = 0, n = this.transformConstraints.Count; i < n; i++) + { + TransformConstraint transformConstraint = transformConstraints[i]; + if (transformConstraint.data.Name == constraintName) return transformConstraint; + } + return null; + } + + /// Finds a path constraint by comparing each path constraint's name. It is more efficient to cache the results of this method + /// than to call it repeatedly. + /// May be null. + public PathConstraint FindPathConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + var pathConstraints = this.pathConstraints.Items; + for (int i = 0, n = this.pathConstraints.Count; i < n; i++) + { + PathConstraint constraint = pathConstraints[i]; + if (constraint.data.Name.Equals(constraintName)) return constraint; + } + return null; + } + + public void Update(float delta) + { + time += delta; + } + + /// Returns the axis aligned bounding box (AABB) of the region and mesh attachments for the current pose. + /// The horizontal distance between the skeleton origin and the left side of the AABB. + /// The vertical distance between the skeleton origin and the bottom side of the AABB. + /// The width of the AABB + /// The height of the AABB. + /// Reference to hold a float[]. May be a null reference. This method will assign it a new float[] with the appropriate size as needed. + public void GetBounds(out float x, out float y, out float width, out float height, ref float[] vertexBuffer) + { + float[] temp = vertexBuffer; + temp = temp ?? new float[8]; + var drawOrder = this.drawOrder.Items; + float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; + for (int i = 0, n = this.drawOrder.Count; i < n; i++) + { + Slot slot = drawOrder[i]; + if (!slot.bone.active) continue; + int verticesLength = 0; + float[] vertices = null; + Attachment attachment = slot.attachment; + var regionAttachment = attachment as RegionAttachment; + if (regionAttachment != null) + { + verticesLength = 8; + vertices = temp; + if (vertices.Length < 8) vertices = temp = new float[8]; + regionAttachment.ComputeWorldVertices(slot.bone, temp, 0); + } + else + { + var meshAttachment = attachment as MeshAttachment; + if (meshAttachment != null) + { + MeshAttachment mesh = meshAttachment; + verticesLength = mesh.WorldVerticesLength; + vertices = temp; + if (vertices.Length < verticesLength) vertices = temp = new float[verticesLength]; + mesh.ComputeWorldVertices(slot, 0, verticesLength, temp, 0); + } + } + + if (vertices != null) + { + for (int ii = 0; ii < verticesLength; ii += 2) + { + float vx = vertices[ii], vy = vertices[ii + 1]; + minX = Math.Min(minX, vx); + minY = Math.Min(minY, vy); + maxX = Math.Max(maxX, vx); + maxY = Math.Max(maxY, vy); + } + } + } + x = minX; + y = minY; + width = maxX - minX; + height = maxY - minY; + vertexBuffer = temp; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/SkeletonBinary.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/SkeletonBinary.cs index b8ce817..41e7198 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/SkeletonBinary.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/SkeletonBinary.cs @@ -32,7 +32,6 @@ #endif using System; -using System.Collections.Generic; using System.IO; using System.Runtime.Serialization; @@ -41,41 +40,45 @@ using Windows.Storage; #endif -namespace Spine4_0_64 { - public class SkeletonBinary : SkeletonLoader { - public const int BONE_ROTATE = 0; - public const int BONE_TRANSLATE = 1; - public const int BONE_TRANSLATEX = 2; - public const int BONE_TRANSLATEY = 3; - public const int BONE_SCALE = 4; - public const int BONE_SCALEX = 5; - public const int BONE_SCALEY = 6; - public const int BONE_SHEAR = 7; - public const int BONE_SHEARX = 8; - public const int BONE_SHEARY = 9; - - public const int SLOT_ATTACHMENT = 0; - public const int SLOT_RGBA = 1; - public const int SLOT_RGB = 2; - public const int SLOT_RGBA2 = 3; - public const int SLOT_RGB2 = 4; - public const int SLOT_ALPHA = 5; - - public const int PATH_POSITION = 0; - public const int PATH_SPACING = 1; - public const int PATH_MIX = 2; - - public const int CURVE_LINEAR = 0; - public const int CURVE_STEPPED = 1; - public const int CURVE_BEZIER = 2; - - public SkeletonBinary (AttachmentLoader attachmentLoader) - : base(attachmentLoader) { - } - - public SkeletonBinary (params Atlas[] atlasArray) - : base(atlasArray) { - } +namespace Spine4_0_64 +{ + public class SkeletonBinary : SkeletonLoader + { + public const int BONE_ROTATE = 0; + public const int BONE_TRANSLATE = 1; + public const int BONE_TRANSLATEX = 2; + public const int BONE_TRANSLATEY = 3; + public const int BONE_SCALE = 4; + public const int BONE_SCALEX = 5; + public const int BONE_SCALEY = 6; + public const int BONE_SHEAR = 7; + public const int BONE_SHEARX = 8; + public const int BONE_SHEARY = 9; + + public const int SLOT_ATTACHMENT = 0; + public const int SLOT_RGBA = 1; + public const int SLOT_RGB = 2; + public const int SLOT_RGBA2 = 3; + public const int SLOT_RGB2 = 4; + public const int SLOT_ALPHA = 5; + + public const int PATH_POSITION = 0; + public const int PATH_SPACING = 1; + public const int PATH_MIX = 2; + + public const int CURVE_LINEAR = 0; + public const int CURVE_STEPPED = 1; + public const int CURVE_BEZIER = 2; + + public SkeletonBinary(AttachmentLoader attachmentLoader) + : base(attachmentLoader) + { + } + + public SkeletonBinary(params Atlas[] atlasArray) + : base(atlasArray) + { + } #if !ISUNITY && WINDOWS_STOREAPP private async Task ReadFile(string path) { @@ -91,1135 +94,1261 @@ public override SkeletonData ReadSkeletonData (string path) { return this.ReadFile(path).Result; } #else - public override SkeletonData ReadSkeletonData (string path) { + public override SkeletonData ReadSkeletonData(string path) + { #if WINDOWS_PHONE using (var input = new BufferedStream(Microsoft.Xna.Framework.TitleContainer.OpenStream(path))) { #else - using (var input = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) { + using (var input = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) + { #endif - SkeletonData skeletonData = ReadSkeletonData(input); - skeletonData.name = Path.GetFileNameWithoutExtension(path); - return skeletonData; - } - } + SkeletonData skeletonData = ReadSkeletonData(input); + skeletonData.name = Path.GetFileNameWithoutExtension(path); + return skeletonData; + } + } #endif // WINDOWS_STOREAPP - public static readonly TransformMode[] TransformModeValues = { - TransformMode.Normal, - TransformMode.OnlyTranslation, - TransformMode.NoRotationOrReflection, - TransformMode.NoScale, - TransformMode.NoScaleOrReflection - }; - - /// Returns the version string of binary skeleton data. - public static string GetVersionString (Stream file) { - if (file == null) throw new ArgumentNullException("file"); - - SkeletonInput input = new SkeletonInput(file); - return input.GetVersionString(); - } - - public SkeletonData ReadSkeletonData (Stream file) { - if (file == null) throw new ArgumentNullException("file"); - float scale = this.scale; - - var skeletonData = new SkeletonData(); - SkeletonInput input = new SkeletonInput(file); - - long hash = input.ReadLong(); - skeletonData.hash = hash == 0 ? null : hash.ToString(); - skeletonData.version = input.ReadString(); - if (skeletonData.version.Length == 0) skeletonData.version = null; - // early return for old 3.8 format instead of reading past the end - if (skeletonData.version.Length > 13) return null; - skeletonData.x = input.ReadFloat(); - skeletonData.y = input.ReadFloat(); - skeletonData.width = input.ReadFloat(); - skeletonData.height = input.ReadFloat(); - - bool nonessential = input.ReadBoolean(); - - if (nonessential) { - skeletonData.fps = input.ReadFloat(); - - skeletonData.imagesPath = input.ReadString(); - if (string.IsNullOrEmpty(skeletonData.imagesPath)) skeletonData.imagesPath = null; - - skeletonData.audioPath = input.ReadString(); - if (string.IsNullOrEmpty(skeletonData.audioPath)) skeletonData.audioPath = null; - } - - int n; - Object[] o; - - // Strings. - o = input.strings = new String[n = input.ReadInt(true)]; - for (int i = 0; i < n; i++) - o[i] = input.ReadString(); - - // Bones. - var bones = skeletonData.bones.Resize(n = input.ReadInt(true)).Items; - for (int i = 0; i < n; i++) { - String name = input.ReadString(); - BoneData parent = i == 0 ? null : bones[input.ReadInt(true)]; - BoneData data = new BoneData(i, name, parent); - data.rotation = input.ReadFloat(); - data.x = input.ReadFloat() * scale; - data.y = input.ReadFloat() * scale; - data.scaleX = input.ReadFloat(); - data.scaleY = input.ReadFloat(); - data.shearX = input.ReadFloat(); - data.shearY = input.ReadFloat(); - data.Length = input.ReadFloat() * scale; - data.transformMode = TransformModeValues[input.ReadInt(true)]; - data.skinRequired = input.ReadBoolean(); - if (nonessential) input.ReadInt(); // Skip bone color. - bones[i] = data; - } - - // Slots. - var slots = skeletonData.slots.Resize(n = input.ReadInt(true)).Items; - for (int i = 0; i < n; i++) { - String slotName = input.ReadString(); - BoneData boneData = bones[input.ReadInt(true)]; - SlotData slotData = new SlotData(i, slotName, boneData); - int color = input.ReadInt(); - slotData.r = ((color & 0xff000000) >> 24) / 255f; - slotData.g = ((color & 0x00ff0000) >> 16) / 255f; - slotData.b = ((color & 0x0000ff00) >> 8) / 255f; - slotData.a = ((color & 0x000000ff)) / 255f; - - int darkColor = input.ReadInt(); // 0x00rrggbb - if (darkColor != -1) { - slotData.hasSecondColor = true; - slotData.r2 = ((darkColor & 0x00ff0000) >> 16) / 255f; - slotData.g2 = ((darkColor & 0x0000ff00) >> 8) / 255f; - slotData.b2 = ((darkColor & 0x000000ff)) / 255f; - } - - slotData.attachmentName = input.ReadStringRef(); - slotData.blendMode = (BlendMode)input.ReadInt(true); - slots[i] = slotData; - } - - // IK constraints. - o = skeletonData.ikConstraints.Resize(n = input.ReadInt(true)).Items; - for (int i = 0, nn; i < n; i++) { - IkConstraintData data = new IkConstraintData(input.ReadString()); - data.order = input.ReadInt(true); - data.skinRequired = input.ReadBoolean(); - var constraintBones = data.bones.Resize(nn = input.ReadInt(true)).Items; - for (int ii = 0; ii < nn; ii++) - constraintBones[ii] = bones[input.ReadInt(true)]; - data.target = bones[input.ReadInt(true)]; - data.mix = input.ReadFloat(); - data.softness = input.ReadFloat() * scale; - data.bendDirection = input.ReadSByte(); - data.compress = input.ReadBoolean(); - data.stretch = input.ReadBoolean(); - data.uniform = input.ReadBoolean(); - o[i] = data; - } - - // Transform constraints. - o = skeletonData.transformConstraints.Resize(n = input.ReadInt(true)).Items; - for (int i = 0, nn; i < n; i++) { - TransformConstraintData data = new TransformConstraintData(input.ReadString()); - data.order = input.ReadInt(true); - data.skinRequired = input.ReadBoolean(); - var constraintBones = data.bones.Resize(nn = input.ReadInt(true)).Items; - for (int ii = 0; ii < nn; ii++) - constraintBones[ii] = bones[input.ReadInt(true)]; - data.target = bones[input.ReadInt(true)]; - data.local = input.ReadBoolean(); - data.relative = input.ReadBoolean(); - data.offsetRotation = input.ReadFloat(); - data.offsetX = input.ReadFloat() * scale; - data.offsetY = input.ReadFloat() * scale; - data.offsetScaleX = input.ReadFloat(); - data.offsetScaleY = input.ReadFloat(); - data.offsetShearY = input.ReadFloat(); - data.mixRotate = input.ReadFloat(); - data.mixX = input.ReadFloat(); - data.mixY = input.ReadFloat(); - data.mixScaleX = input.ReadFloat(); - data.mixScaleY = input.ReadFloat(); - data.mixShearY = input.ReadFloat(); - o[i] = data; - } - - // Path constraints - o = skeletonData.pathConstraints.Resize(n = input.ReadInt(true)).Items; - for (int i = 0, nn; i < n; i++) { - PathConstraintData data = new PathConstraintData(input.ReadString()); - data.order = input.ReadInt(true); - data.skinRequired = input.ReadBoolean(); - Object[] constraintBones = data.bones.Resize(nn = input.ReadInt(true)).Items; - for (int ii = 0; ii < nn; ii++) - constraintBones[ii] = bones[input.ReadInt(true)]; - data.target = slots[input.ReadInt(true)]; - data.positionMode = (PositionMode)Enum.GetValues(typeof(PositionMode)).GetValue(input.ReadInt(true)); - data.spacingMode = (SpacingMode)Enum.GetValues(typeof(SpacingMode)).GetValue(input.ReadInt(true)); - data.rotateMode = (RotateMode)Enum.GetValues(typeof(RotateMode)).GetValue(input.ReadInt(true)); - data.offsetRotation = input.ReadFloat(); - data.position = input.ReadFloat(); - if (data.positionMode == PositionMode.Fixed) data.position *= scale; - data.spacing = input.ReadFloat(); - if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale; - data.mixRotate = input.ReadFloat(); - data.mixX = input.ReadFloat(); - data.mixY = input.ReadFloat(); - o[i] = data; - } - - // Default skin. - Skin defaultSkin = ReadSkin(input, skeletonData, true, nonessential); - if (defaultSkin != null) { - skeletonData.defaultSkin = defaultSkin; - skeletonData.skins.Add(defaultSkin); - } - - // Skins. - { - int i = skeletonData.skins.Count; - o = skeletonData.skins.Resize(n = i + input.ReadInt(true)).Items; - for (; i < n; i++) - o[i] = ReadSkin(input, skeletonData, false, nonessential); - } - - // Linked meshes. - n = linkedMeshes.Count; - for (int i = 0; i < n; i++) { - LinkedMesh linkedMesh = linkedMeshes[i]; - Skin skin = linkedMesh.skin == null ? skeletonData.DefaultSkin : skeletonData.FindSkin(linkedMesh.skin); - if (skin == null) throw new Exception("Skin not found: " + linkedMesh.skin); - Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); - if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); - linkedMesh.mesh.DeformAttachment = linkedMesh.inheritDeform ? (VertexAttachment)parent : linkedMesh.mesh; - linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; - linkedMesh.mesh.UpdateUVs(); - } - linkedMeshes.Clear(); - - // Events. - o = skeletonData.events.Resize(n = input.ReadInt(true)).Items; - for (int i = 0; i < n; i++) { - EventData data = new EventData(input.ReadStringRef()); - data.Int = input.ReadInt(false); - data.Float = input.ReadFloat(); - data.String = input.ReadString(); - data.AudioPath = input.ReadString(); - if (data.AudioPath != null) { - data.Volume = input.ReadFloat(); - data.Balance = input.ReadFloat(); - } - o[i] = data; - } - - // Animations. - o = skeletonData.animations.Resize(n = input.ReadInt(true)).Items; - for (int i = 0; i < n; i++) - o[i] = ReadAnimation(input.ReadString(), input, skeletonData); - - return skeletonData; - } - - /// May be null. - private Skin ReadSkin (SkeletonInput input, SkeletonData skeletonData, bool defaultSkin, bool nonessential) { - - Skin skin; - int slotCount; - - if (defaultSkin) { - slotCount = input.ReadInt(true); - if (slotCount == 0) return null; - skin = new Skin("default"); - } else { - skin = new Skin(input.ReadStringRef()); - Object[] bones = skin.bones.Resize(input.ReadInt(true)).Items; - var bonesItems = skeletonData.bones.Items; - for (int i = 0, n = skin.bones.Count; i < n; i++) - bones[i] = bonesItems[input.ReadInt(true)]; - - var ikConstraintsItems = skeletonData.ikConstraints.Items; - for (int i = 0, n = input.ReadInt(true); i < n; i++) - skin.constraints.Add(ikConstraintsItems[input.ReadInt(true)]); - var transformConstraintsItems = skeletonData.transformConstraints.Items; - for (int i = 0, n = input.ReadInt(true); i < n; i++) - skin.constraints.Add(transformConstraintsItems[input.ReadInt(true)]); - var pathConstraintsItems = skeletonData.pathConstraints.Items; - for (int i = 0, n = input.ReadInt(true); i < n; i++) - skin.constraints.Add(pathConstraintsItems[input.ReadInt(true)]); - skin.constraints.TrimExcess(); - - slotCount = input.ReadInt(true); - } - for (int i = 0; i < slotCount; i++) { - int slotIndex = input.ReadInt(true); - for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) { - String name = input.ReadStringRef(); - Attachment attachment = ReadAttachment(input, skeletonData, skin, slotIndex, name, nonessential); - if (attachment != null) skin.SetAttachment(slotIndex, name, attachment); - } - } - return skin; - } - - private Attachment ReadAttachment (SkeletonInput input, SkeletonData skeletonData, Skin skin, int slotIndex, - String attachmentName, bool nonessential) { - float scale = this.scale; - - String name = input.ReadStringRef(); - if (name == null) name = attachmentName; - - switch ((AttachmentType)input.ReadByte()) { - case AttachmentType.Region: { - String path = input.ReadStringRef(); - float rotation = input.ReadFloat(); - float x = input.ReadFloat(); - float y = input.ReadFloat(); - float scaleX = input.ReadFloat(); - float scaleY = input.ReadFloat(); - float width = input.ReadFloat(); - float height = input.ReadFloat(); - int color = input.ReadInt(); - - if (path == null) path = name; - RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); - if (region == null) return null; - region.Path = path; - region.x = x * scale; - region.y = y * scale; - region.scaleX = scaleX; - region.scaleY = scaleY; - region.rotation = rotation; - region.width = width * scale; - region.height = height * scale; - region.r = ((color & 0xff000000) >> 24) / 255f; - region.g = ((color & 0x00ff0000) >> 16) / 255f; - region.b = ((color & 0x0000ff00) >> 8) / 255f; - region.a = ((color & 0x000000ff)) / 255f; - region.UpdateOffset(); - return region; - } - case AttachmentType.Boundingbox: { - int vertexCount = input.ReadInt(true); - Vertices vertices = ReadVertices(input, vertexCount); - if (nonessential) input.ReadInt(); //int color = nonessential ? input.ReadInt() : 0; // Avoid unused local warning. - - BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); - if (box == null) return null; - box.worldVerticesLength = vertexCount << 1; - box.vertices = vertices.vertices; - box.bones = vertices.bones; - // skipped porting: if (nonessential) Color.rgba8888ToColor(box.getColor(), color); - return box; - } - case AttachmentType.Mesh: { - String path = input.ReadStringRef(); - int color = input.ReadInt(); - int vertexCount = input.ReadInt(true); - float[] uvs = ReadFloatArray(input, vertexCount << 1, 1); - int[] triangles = ReadShortArray(input); - Vertices vertices = ReadVertices(input, vertexCount); - int hullLength = input.ReadInt(true); - int[] edges = null; - float width = 0, height = 0; - if (nonessential) { - edges = ReadShortArray(input); - width = input.ReadFloat(); - height = input.ReadFloat(); - } - - if (path == null) path = name; - MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); - if (mesh == null) return null; - mesh.Path = path; - mesh.r = ((color & 0xff000000) >> 24) / 255f; - mesh.g = ((color & 0x00ff0000) >> 16) / 255f; - mesh.b = ((color & 0x0000ff00) >> 8) / 255f; - mesh.a = ((color & 0x000000ff)) / 255f; - mesh.bones = vertices.bones; - mesh.vertices = vertices.vertices; - mesh.WorldVerticesLength = vertexCount << 1; - mesh.triangles = triangles; - mesh.regionUVs = uvs; - mesh.UpdateUVs(); - mesh.HullLength = hullLength << 1; - if (nonessential) { - mesh.Edges = edges; - mesh.Width = width * scale; - mesh.Height = height * scale; - } - return mesh; - } - case AttachmentType.Linkedmesh: { - String path = input.ReadStringRef(); - int color = input.ReadInt(); - String skinName = input.ReadStringRef(); - String parent = input.ReadStringRef(); - bool inheritDeform = input.ReadBoolean(); - float width = 0, height = 0; - if (nonessential) { - width = input.ReadFloat(); - height = input.ReadFloat(); - } - - if (path == null) path = name; - MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); - if (mesh == null) return null; - mesh.Path = path; - mesh.r = ((color & 0xff000000) >> 24) / 255f; - mesh.g = ((color & 0x00ff0000) >> 16) / 255f; - mesh.b = ((color & 0x0000ff00) >> 8) / 255f; - mesh.a = ((color & 0x000000ff)) / 255f; - if (nonessential) { - mesh.Width = width * scale; - mesh.Height = height * scale; - } - linkedMeshes.Add(new SkeletonJson.LinkedMesh(mesh, skinName, slotIndex, parent, inheritDeform)); - return mesh; - } - case AttachmentType.Path: { - bool closed = input.ReadBoolean(); - bool constantSpeed = input.ReadBoolean(); - int vertexCount = input.ReadInt(true); - Vertices vertices = ReadVertices(input, vertexCount); - float[] lengths = new float[vertexCount / 3]; - for (int i = 0, n = lengths.Length; i < n; i++) - lengths[i] = input.ReadFloat() * scale; - if (nonessential) input.ReadInt(); //int color = nonessential ? input.ReadInt() : 0; - - PathAttachment path = attachmentLoader.NewPathAttachment(skin, name); - if (path == null) return null; - path.closed = closed; - path.constantSpeed = constantSpeed; - path.worldVerticesLength = vertexCount << 1; - path.vertices = vertices.vertices; - path.bones = vertices.bones; - path.lengths = lengths; - // skipped porting: if (nonessential) Color.rgba8888ToColor(path.getColor(), color); - return path; - } - case AttachmentType.Point: { - float rotation = input.ReadFloat(); - float x = input.ReadFloat(); - float y = input.ReadFloat(); - if (nonessential) input.ReadInt(); //int color = nonessential ? input.ReadInt() : 0; - - PointAttachment point = attachmentLoader.NewPointAttachment(skin, name); - if (point == null) return null; - point.x = x * scale; - point.y = y * scale; - point.rotation = rotation; - // skipped porting: if (nonessential) point.color = color; - return point; - } - case AttachmentType.Clipping: { - int endSlotIndex = input.ReadInt(true); - int vertexCount = input.ReadInt(true); - Vertices vertices = ReadVertices(input, vertexCount); - if (nonessential) input.ReadInt(); - - ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name); - if (clip == null) return null; - clip.EndSlot = skeletonData.slots.Items[endSlotIndex]; - clip.worldVerticesLength = vertexCount << 1; - clip.vertices = vertices.vertices; - clip.bones = vertices.bones; - // skipped porting: if (nonessential) Color.rgba8888ToColor(clip.getColor(), color); - return clip; - } - } - return null; - } - - private Vertices ReadVertices (SkeletonInput input, int vertexCount) { - float scale = this.scale; - int verticesLength = vertexCount << 1; - Vertices vertices = new Vertices(); - if (!input.ReadBoolean()) { - vertices.vertices = ReadFloatArray(input, verticesLength, scale); - return vertices; - } - var weights = new ExposedList(verticesLength * 3 * 3); - var bonesArray = new ExposedList(verticesLength * 3); - for (int i = 0; i < vertexCount; i++) { - int boneCount = input.ReadInt(true); - bonesArray.Add(boneCount); - for (int ii = 0; ii < boneCount; ii++) { - bonesArray.Add(input.ReadInt(true)); - weights.Add(input.ReadFloat() * scale); - weights.Add(input.ReadFloat() * scale); - weights.Add(input.ReadFloat()); - } - } - - vertices.vertices = weights.ToArray(); - vertices.bones = bonesArray.ToArray(); - return vertices; - } - - private float[] ReadFloatArray (SkeletonInput input, int n, float scale) { - float[] array = new float[n]; - if (scale == 1) { - for (int i = 0; i < n; i++) - array[i] = input.ReadFloat(); - } else { - for (int i = 0; i < n; i++) - array[i] = input.ReadFloat() * scale; - } - return array; - } - - private int[] ReadShortArray (SkeletonInput input) { - int n = input.ReadInt(true); - int[] array = new int[n]; - for (int i = 0; i < n; i++) - array[i] = (input.ReadByte() << 8) | input.ReadByte(); - return array; - } - - /// SerializationException will be thrown when a Vertex attachment is not found. - /// Throws IOException when a read operation fails. - private Animation ReadAnimation (String name, SkeletonInput input, SkeletonData skeletonData) { - var timelines = new ExposedList(input.ReadInt(true)); - float scale = this.scale; - - // Slot timelines. - for (int i = 0, n = input.ReadInt(true); i < n; i++) { - int slotIndex = input.ReadInt(true); - for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) { - int timelineType = input.ReadByte(), frameCount = input.ReadInt(true), frameLast = frameCount - 1; - switch (timelineType) { - case SLOT_ATTACHMENT: { - AttachmentTimeline timeline = new AttachmentTimeline(frameCount, slotIndex); - for (int frame = 0; frame < frameCount; frame++) - timeline.SetFrame(frame, input.ReadFloat(), input.ReadStringRef()); - timelines.Add(timeline); - break; - } - case SLOT_RGBA: { - RGBATimeline timeline = new RGBATimeline(frameCount, input.ReadInt(true), slotIndex); - float time = input.ReadFloat(); - float r = input.Read() / 255f, g = input.Read() / 255f; - float b = input.Read() / 255f, a = input.Read() / 255f; - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, r, g, b, a); - if (frame == frameLast) break; - float time2 = input.ReadFloat(); - float r2 = input.Read() / 255f, g2 = input.Read() / 255f; - float b2 = input.Read() / 255f, a2 = input.Read() / 255f; - switch (input.ReadByte()) { - 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); - break; - } - time = time2; - r = r2; - g = g2; - b = b2; - a = a2; - } - timelines.Add(timeline); - break; - } - case SLOT_RGB: { - RGBTimeline timeline = new RGBTimeline(frameCount, input.ReadInt(true), slotIndex); - float time = input.ReadFloat(); - float r = input.Read() / 255f, g = input.Read() / 255f, b = input.Read() / 255f; - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, r, g, b); - if (frame == frameLast) break; - float time2 = input.ReadFloat(); - float r2 = input.Read() / 255f, g2 = input.Read() / 255f, b2 = input.Read() / 255f; - switch (input.ReadByte()) { - 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); - break; - } - time = time2; - r = r2; - g = g2; - b = b2; - } - timelines.Add(timeline); - break; - } - case SLOT_RGBA2: { - RGBA2Timeline timeline = new RGBA2Timeline(frameCount, input.ReadInt(true), slotIndex); - float time = input.ReadFloat(); - float r = input.Read() / 255f, g = input.Read() / 255f; - float b = input.Read() / 255f, a = input.Read() / 255f; - float r2 = input.Read() / 255f, g2 = input.Read() / 255f, b2 = input.Read() / 255f; - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, r, g, b, a, r2, g2, b2); - if (frame == frameLast) break; - float time2 = input.ReadFloat(); - float nr = input.Read() / 255f, ng = input.Read() / 255f; - float nb = input.Read() / 255f, na = input.Read() / 255f; - float nr2 = input.Read() / 255f, ng2 = input.Read() / 255f, nb2 = input.Read() / 255f; - switch (input.ReadByte()) { - 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); - break; - } - time = time2; - r = nr; - g = ng; - b = nb; - a = na; - r2 = nr2; - g2 = ng2; - b2 = nb2; - } - timelines.Add(timeline); - break; - } - case SLOT_RGB2: { - RGB2Timeline timeline = new RGB2Timeline(frameCount, input.ReadInt(true), slotIndex); - float time = input.ReadFloat(); - float r = input.Read() / 255f, g = input.Read() / 255f, b = input.Read() / 255f; - float r2 = input.Read() / 255f, g2 = input.Read() / 255f, b2 = input.Read() / 255f; - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, r, g, b, r2, g2, b2); - if (frame == frameLast) break; - float time2 = input.ReadFloat(); - float nr = input.Read() / 255f, ng = input.Read() / 255f, nb = input.Read() / 255f; - float nr2 = input.Read() / 255f, ng2 = input.Read() / 255f, nb2 = input.Read() / 255f; - switch (input.ReadByte()) { - 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); - break; - } - time = time2; - r = nr; - g = ng; - b = nb; - r2 = nr2; - g2 = ng2; - b2 = nb2; - } - timelines.Add(timeline); - break; - } - case SLOT_ALPHA: { - AlphaTimeline timeline = new AlphaTimeline(frameCount, input.ReadInt(true), slotIndex); - float time = input.ReadFloat(), a = input.Read() / 255f; - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, a); - if (frame == frameLast) break; - float time2 = input.ReadFloat(); - float a2 = input.Read() / 255f; - switch (input.ReadByte()) { - case CURVE_STEPPED: - timeline.SetStepped(frame); - break; - case CURVE_BEZIER: - SetBezier(input, timeline, bezier++, frame, 0, time, time2, a, a2, 1); - break; - } - time = time2; - a = a2; - } - timelines.Add(timeline); - break; - } - } - } - } - - // Bone timelines. - for (int i = 0, n = input.ReadInt(true); i < n; i++) { - int boneIndex = input.ReadInt(true); - for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) { - int type = input.ReadByte(), frameCount = input.ReadInt(true), bezierCount = input.ReadInt(true); - switch (type) { - case BONE_ROTATE: - timelines.Add(ReadTimeline(input, new RotateTimeline(frameCount, bezierCount, boneIndex), 1)); - break; - case BONE_TRANSLATE: - timelines.Add(ReadTimeline(input, new TranslateTimeline(frameCount, bezierCount, boneIndex), scale)); - break; - case BONE_TRANSLATEX: - timelines.Add(ReadTimeline(input, new TranslateXTimeline(frameCount, bezierCount, boneIndex), scale)); - break; - case BONE_TRANSLATEY: - timelines.Add(ReadTimeline(input, new TranslateYTimeline(frameCount, bezierCount, boneIndex), scale)); - break; - case BONE_SCALE: - timelines.Add(ReadTimeline(input, new ScaleTimeline(frameCount, bezierCount, boneIndex), 1)); - break; - case BONE_SCALEX: - timelines.Add(ReadTimeline(input, new ScaleXTimeline(frameCount, bezierCount, boneIndex), 1)); - break; - case BONE_SCALEY: - timelines.Add(ReadTimeline(input, new ScaleYTimeline(frameCount, bezierCount, boneIndex), 1)); - break; - case BONE_SHEAR: - timelines.Add(ReadTimeline(input, new ShearTimeline(frameCount, bezierCount, boneIndex), 1)); - break; - case BONE_SHEARX: - timelines.Add(ReadTimeline(input, new ShearXTimeline(frameCount, bezierCount, boneIndex), 1)); - break; - case BONE_SHEARY: - timelines.Add(ReadTimeline(input, new ShearYTimeline(frameCount, bezierCount, boneIndex), 1)); - break; - } - } - } - - // IK constraint timelines. - for (int i = 0, n = input.ReadInt(true); i < n; i++) { - int index = input.ReadInt(true), frameCount = input.ReadInt(true), frameLast = frameCount - 1; - IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount, input.ReadInt(true), index); - float time = input.ReadFloat(), mix = input.ReadFloat(), softness = input.ReadFloat() * scale; - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, mix, softness, input.ReadSByte(), input.ReadBoolean(), input.ReadBoolean()); - if (frame == frameLast) break; - float time2 = input.ReadFloat(), mix2 = input.ReadFloat(), softness2 = input.ReadFloat() * scale; - switch (input.ReadByte()) { - case CURVE_STEPPED: - timeline.SetStepped(frame); - break; - case CURVE_BEZIER: - SetBezier(input, timeline, bezier++, frame, 0, time, time2, mix, mix2, 1); - SetBezier(input, timeline, bezier++, frame, 1, time, time2, softness, softness2, scale); - break; - } - time = time2; - mix = mix2; - softness = softness2; - } - timelines.Add(timeline); - } - - // Transform constraint timelines. - for (int i = 0, n = input.ReadInt(true); i < n; i++) { - int index = input.ReadInt(true), frameCount = input.ReadInt(true), frameLast = frameCount - 1; - TransformConstraintTimeline timeline = new TransformConstraintTimeline(frameCount, input.ReadInt(true), index); - float time = input.ReadFloat(), mixRotate = input.ReadFloat(), mixX = input.ReadFloat(), mixY = input.ReadFloat(), - mixScaleX = input.ReadFloat(), mixScaleY = input.ReadFloat(), mixShearY = input.ReadFloat(); - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY); - if (frame == frameLast) break; - float time2 = input.ReadFloat(), mixRotate2 = input.ReadFloat(), mixX2 = input.ReadFloat(), mixY2 = input.ReadFloat(), - mixScaleX2 = input.ReadFloat(), mixScaleY2 = input.ReadFloat(), mixShearY2 = input.ReadFloat(); - switch (input.ReadByte()) { - 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); - break; - } - 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 = input.ReadInt(true); i < n; i++) { - int index = input.ReadInt(true); - PathConstraintData data = skeletonData.pathConstraints.Items[index]; - for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) { - switch (input.ReadByte()) { - case PATH_POSITION: - timelines - .Add(ReadTimeline(input, new PathConstraintPositionTimeline(input.ReadInt(true), input.ReadInt(true), index), - data.positionMode == PositionMode.Fixed ? scale : 1)); - break; - case PATH_SPACING: - timelines - .Add(ReadTimeline(input, new PathConstraintSpacingTimeline(input.ReadInt(true), input.ReadInt(true), index), - data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed ? scale : 1)); - break; - case PATH_MIX: - PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(input.ReadInt(true), input.ReadInt(true), - index); - float time = input.ReadFloat(), mixRotate = input.ReadFloat(), mixX = input.ReadFloat(), mixY = input.ReadFloat(); - for (int frame = 0, bezier = 0, frameLast = timeline.FrameCount - 1; ; frame++) { - timeline.SetFrame(frame, time, mixRotate, mixX, mixY); - if (frame == frameLast) break; - float time2 = input.ReadFloat(), mixRotate2 = input.ReadFloat(), mixX2 = input.ReadFloat(), - mixY2 = input.ReadFloat(); - switch (input.ReadByte()) { - 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); - break; - } - time = time2; - mixRotate = mixRotate2; - mixX = mixX2; - mixY = mixY2; - } - timelines.Add(timeline); - break; - } - } - } - - // Deform timelines. - for (int i = 0, n = input.ReadInt(true); i < n; i++) { - Skin skin = skeletonData.skins.Items[input.ReadInt(true)]; - for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) { - int slotIndex = input.ReadInt(true); - for (int iii = 0, nnn = input.ReadInt(true); iii < nnn; iii++) { - String attachmentName = input.ReadStringRef(); - VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, attachmentName); - if (attachment == null) throw new SerializationException("Vertex attachment not found: " + attachmentName); - bool weighted = attachment.Bones != null; - float[] vertices = attachment.Vertices; - int deformLength = weighted ? (vertices.Length / 3) << 1 : vertices.Length; - - int frameCount = input.ReadInt(true), frameLast = frameCount - 1; - DeformTimeline timeline = new DeformTimeline(frameCount, input.ReadInt(true), slotIndex, attachment); - - float time = input.ReadFloat(); - for (int frame = 0, bezier = 0; ; frame++) { - float[] deform; - int end = input.ReadInt(true); - if (end == 0) - deform = weighted ? new float[deformLength] : vertices; - else { - deform = new float[deformLength]; - int start = input.ReadInt(true); - end += start; - if (scale == 1) { - for (int v = start; v < end; v++) - deform[v] = input.ReadFloat(); - } else { - for (int v = start; v < end; v++) - deform[v] = input.ReadFloat() * scale; - } - if (!weighted) { - for (int v = 0, vn = deform.Length; v < vn; v++) - deform[v] += vertices[v]; - } - } - timeline.SetFrame(frame, time, deform); - if (frame == frameLast) break; - float time2 = input.ReadFloat(); - switch (input.ReadByte()) { - case CURVE_STEPPED: - timeline.SetStepped(frame); - break; - case CURVE_BEZIER: - SetBezier(input, timeline, bezier++, frame, 0, time, time2, 0, 1, 1); - break; - } - time = time2; - } - timelines.Add(timeline); - } - } - } - - // Draw order timeline. - int drawOrderCount = input.ReadInt(true); - if (drawOrderCount > 0) { - DrawOrderTimeline timeline = new DrawOrderTimeline(drawOrderCount); - int slotCount = skeletonData.slots.Count; - for (int i = 0; i < drawOrderCount; i++) { - float time = input.ReadFloat(); - int offsetCount = input.ReadInt(true); - int[] drawOrder = new int[slotCount]; - for (int ii = slotCount - 1; ii >= 0; ii--) - drawOrder[ii] = -1; - int[] unchanged = new int[slotCount - offsetCount]; - int originalIndex = 0, unchangedIndex = 0; - for (int ii = 0; ii < offsetCount; ii++) { - int slotIndex = input.ReadInt(true); - // Collect unchanged items. - while (originalIndex != slotIndex) - unchanged[unchangedIndex++] = originalIndex++; - // Set changed items. - drawOrder[originalIndex + input.ReadInt(true)] = originalIndex++; - } - // Collect remaining unchanged items. - while (originalIndex < slotCount) - unchanged[unchangedIndex++] = originalIndex++; - // Fill in unchanged items. - for (int ii = 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 = input.ReadInt(true); - if (eventCount > 0) { - EventTimeline timeline = new EventTimeline(eventCount); - for (int i = 0; i < eventCount; i++) { - float time = input.ReadFloat(); - EventData eventData = skeletonData.events.Items[input.ReadInt(true)]; - Event e = new Event(time, eventData); - e.intValue = input.ReadInt(false); - e.floatValue = input.ReadFloat(); - e.stringValue = input.ReadBoolean() ? input.ReadString() : eventData.String; - if (e.Data.AudioPath != null) { - e.volume = input.ReadFloat(); - e.balance = input.ReadFloat(); - } - timeline.SetFrame(i, e); - } - timelines.Add(timeline); - } - - float duration = 0; - var items = timelines.Items; - for (int i = 0, n = timelines.Count; i < n; i++) - duration = Math.Max(duration, items[i].Duration); - return new Animation(name, timelines, duration); - } - - /// Throws IOException when a read operation fails. - private Timeline ReadTimeline (SkeletonInput input, CurveTimeline1 timeline, float scale) { - float time = input.ReadFloat(), value = input.ReadFloat() * scale; - for (int frame = 0, bezier = 0, frameLast = timeline.FrameCount - 1; ; frame++) { - timeline.SetFrame(frame, time, value); - if (frame == frameLast) break; - float time2 = input.ReadFloat(), value2 = input.ReadFloat() * scale; - switch (input.ReadByte()) { - case CURVE_STEPPED: - timeline.SetStepped(frame); - break; - case CURVE_BEZIER: - SetBezier(input, timeline, bezier++, frame, 0, time, time2, value, value2, scale); - break; - } - time = time2; - value = value2; - } - return timeline; - } - - /// Throws IOException when a read operation fails. - private Timeline ReadTimeline (SkeletonInput input, CurveTimeline2 timeline, float scale) { - float time = input.ReadFloat(), value1 = input.ReadFloat() * scale, value2 = input.ReadFloat() * scale; - for (int frame = 0, bezier = 0, frameLast = timeline.FrameCount - 1; ; frame++) { - timeline.SetFrame(frame, time, value1, value2); - if (frame == frameLast) break; - float time2 = input.ReadFloat(), nvalue1 = input.ReadFloat() * scale, nvalue2 = input.ReadFloat() * scale; - switch (input.ReadByte()) { - 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); - break; - } - time = time2; - value1 = nvalue1; - value2 = nvalue2; - } - return timeline; - } - - /// Throws IOException when a read operation fails. - void SetBezier (SkeletonInput input, CurveTimeline timeline, int bezier, int frame, int value, float time1, float time2, - float value1, float value2, float scale) { - timeline.SetBezier(bezier, frame, value, time1, value1, input.ReadFloat(), input.ReadFloat() * scale, input.ReadFloat(), - input.ReadFloat() * scale, time2, value2); - } - - internal class Vertices { - public int[] bones; - public float[] vertices; - } - - internal class SkeletonInput { - private byte[] chars = new byte[32]; - private byte[] bytesBigEndian = new byte[8]; - internal string[] strings; - Stream input; - - public SkeletonInput (Stream input) { - this.input = input; - } - - public int Read () { - return input.ReadByte(); - } - - public byte ReadByte () { - return (byte)input.ReadByte(); - } - - public sbyte ReadSByte () { - int value = input.ReadByte(); - if (value == -1) throw new EndOfStreamException(); - return (sbyte)value; - } - - public bool ReadBoolean () { - return input.ReadByte() != 0; - } - - public float ReadFloat () { - input.Read(bytesBigEndian, 0, 4); - chars[3] = bytesBigEndian[0]; - chars[2] = bytesBigEndian[1]; - chars[1] = bytesBigEndian[2]; - chars[0] = bytesBigEndian[3]; - return BitConverter.ToSingle(chars, 0); - } - - public int ReadInt () { - input.Read(bytesBigEndian, 0, 4); - return (bytesBigEndian[0] << 24) - + (bytesBigEndian[1] << 16) - + (bytesBigEndian[2] << 8) - + bytesBigEndian[3]; - } - - public long ReadLong () { - input.Read(bytesBigEndian, 0, 8); - return ((long)(bytesBigEndian[0]) << 56) - + ((long)(bytesBigEndian[1]) << 48) - + ((long)(bytesBigEndian[2]) << 40) - + ((long)(bytesBigEndian[3]) << 32) - + ((long)(bytesBigEndian[4]) << 24) - + ((long)(bytesBigEndian[5]) << 16) - + ((long)(bytesBigEndian[6]) << 8) - + (long)(bytesBigEndian[7]); - } - - public int ReadInt (bool optimizePositive) { - int b = input.ReadByte(); - int result = b & 0x7F; - if ((b & 0x80) != 0) { - b = input.ReadByte(); - result |= (b & 0x7F) << 7; - if ((b & 0x80) != 0) { - b = input.ReadByte(); - result |= (b & 0x7F) << 14; - if ((b & 0x80) != 0) { - b = input.ReadByte(); - result |= (b & 0x7F) << 21; - if ((b & 0x80) != 0) result |= (input.ReadByte() & 0x7F) << 28; - } - } - } - return optimizePositive ? result : ((result >> 1) ^ -(result & 1)); - } - - public string ReadString () { - int byteCount = ReadInt(true); - switch (byteCount) { - case 0: - return null; - case 1: - return ""; - } - byteCount--; - byte[] buffer = this.chars; - if (buffer.Length < byteCount) buffer = new byte[byteCount]; - ReadFully(buffer, 0, byteCount); - return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount); - } - - ///May be null. - public String ReadStringRef () { - int index = ReadInt(true); - return index == 0 ? null : strings[index - 1]; - } - - public void ReadFully (byte[] buffer, int offset, int length) { - while (length > 0) { - int count = input.Read(buffer, offset, length); - if (count <= 0) throw new EndOfStreamException(); - offset += count; - length -= count; - } - } - - /// Returns the version string of binary skeleton data. - public string GetVersionString () { - try { - // try reading 4.0+ format - var initialPosition = input.Position; - ReadLong(); // long hash - - var stringPosition = input.Position; - int stringByteCount = ReadInt(true); - input.Position = stringPosition; - if (stringByteCount <= 13) { - string version = ReadString(); - if (char.IsDigit(version[0])) - return version; - } - // fallback to old version format - input.Position = initialPosition; - return GetVersionStringOld3X(); - } catch (Exception e) { - throw new ArgumentException("Stream does not contain valid binary Skeleton Data.\n" + e, "input"); - } - } - - /// Returns old 3.8 and earlier format version string of binary skeleton data. - public string GetVersionStringOld3X () { - // Hash. - int byteCount = ReadInt(true); - if (byteCount > 1) input.Position += byteCount - 1; - - // Version. - byteCount = ReadInt(true); - if (byteCount > 1 && byteCount <= 13) { - byteCount--; - var buffer = new byte[byteCount]; - ReadFully(buffer, 0, byteCount); - return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount); - } - throw new ArgumentException("Stream does not contain valid binary Skeleton Data."); - } - } - } + public static readonly TransformMode[] TransformModeValues = { + TransformMode.Normal, + TransformMode.OnlyTranslation, + TransformMode.NoRotationOrReflection, + TransformMode.NoScale, + TransformMode.NoScaleOrReflection + }; + + /// Returns the version string of binary skeleton data. + public static string GetVersionString(Stream file) + { + if (file == null) throw new ArgumentNullException("file"); + + SkeletonInput input = new SkeletonInput(file); + return input.GetVersionString(); + } + + public SkeletonData ReadSkeletonData(Stream file) + { + if (file == null) throw new ArgumentNullException("file"); + float scale = this.scale; + + var skeletonData = new SkeletonData(); + SkeletonInput input = new SkeletonInput(file); + + long hash = input.ReadLong(); + skeletonData.hash = hash == 0 ? null : hash.ToString(); + skeletonData.version = input.ReadString(); + if (skeletonData.version.Length == 0) skeletonData.version = null; + // early return for old 3.8 format instead of reading past the end + if (skeletonData.version.Length > 13) return null; + skeletonData.x = input.ReadFloat(); + skeletonData.y = input.ReadFloat(); + skeletonData.width = input.ReadFloat(); + skeletonData.height = input.ReadFloat(); + + bool nonessential = input.ReadBoolean(); + + if (nonessential) + { + skeletonData.fps = input.ReadFloat(); + + skeletonData.imagesPath = input.ReadString(); + if (string.IsNullOrEmpty(skeletonData.imagesPath)) skeletonData.imagesPath = null; + + skeletonData.audioPath = input.ReadString(); + if (string.IsNullOrEmpty(skeletonData.audioPath)) skeletonData.audioPath = null; + } + + int n; + Object[] o; + + // Strings. + o = input.strings = new String[n = input.ReadInt(true)]; + for (int i = 0; i < n; i++) + o[i] = input.ReadString(); + + // Bones. + var bones = skeletonData.bones.Resize(n = input.ReadInt(true)).Items; + for (int i = 0; i < n; i++) + { + String name = input.ReadString(); + BoneData parent = i == 0 ? null : bones[input.ReadInt(true)]; + BoneData data = new BoneData(i, name, parent); + data.rotation = input.ReadFloat(); + data.x = input.ReadFloat() * scale; + data.y = input.ReadFloat() * scale; + data.scaleX = input.ReadFloat(); + data.scaleY = input.ReadFloat(); + data.shearX = input.ReadFloat(); + data.shearY = input.ReadFloat(); + data.Length = input.ReadFloat() * scale; + data.transformMode = TransformModeValues[input.ReadInt(true)]; + data.skinRequired = input.ReadBoolean(); + if (nonessential) input.ReadInt(); // Skip bone color. + bones[i] = data; + } + + // Slots. + var slots = skeletonData.slots.Resize(n = input.ReadInt(true)).Items; + for (int i = 0; i < n; i++) + { + String slotName = input.ReadString(); + BoneData boneData = bones[input.ReadInt(true)]; + SlotData slotData = new SlotData(i, slotName, boneData); + int color = input.ReadInt(); + slotData.r = ((color & 0xff000000) >> 24) / 255f; + slotData.g = ((color & 0x00ff0000) >> 16) / 255f; + slotData.b = ((color & 0x0000ff00) >> 8) / 255f; + slotData.a = ((color & 0x000000ff)) / 255f; + + int darkColor = input.ReadInt(); // 0x00rrggbb + if (darkColor != -1) + { + slotData.hasSecondColor = true; + slotData.r2 = ((darkColor & 0x00ff0000) >> 16) / 255f; + slotData.g2 = ((darkColor & 0x0000ff00) >> 8) / 255f; + slotData.b2 = ((darkColor & 0x000000ff)) / 255f; + } + + slotData.attachmentName = input.ReadStringRef(); + slotData.blendMode = (BlendMode)input.ReadInt(true); + slots[i] = slotData; + } + + // IK constraints. + o = skeletonData.ikConstraints.Resize(n = input.ReadInt(true)).Items; + for (int i = 0, nn; i < n; i++) + { + IkConstraintData data = new IkConstraintData(input.ReadString()); + data.order = input.ReadInt(true); + data.skinRequired = input.ReadBoolean(); + var constraintBones = data.bones.Resize(nn = input.ReadInt(true)).Items; + for (int ii = 0; ii < nn; ii++) + constraintBones[ii] = bones[input.ReadInt(true)]; + data.target = bones[input.ReadInt(true)]; + data.mix = input.ReadFloat(); + data.softness = input.ReadFloat() * scale; + data.bendDirection = input.ReadSByte(); + data.compress = input.ReadBoolean(); + data.stretch = input.ReadBoolean(); + data.uniform = input.ReadBoolean(); + o[i] = data; + } + + // Transform constraints. + o = skeletonData.transformConstraints.Resize(n = input.ReadInt(true)).Items; + for (int i = 0, nn; i < n; i++) + { + TransformConstraintData data = new TransformConstraintData(input.ReadString()); + data.order = input.ReadInt(true); + data.skinRequired = input.ReadBoolean(); + var constraintBones = data.bones.Resize(nn = input.ReadInt(true)).Items; + for (int ii = 0; ii < nn; ii++) + constraintBones[ii] = bones[input.ReadInt(true)]; + data.target = bones[input.ReadInt(true)]; + data.local = input.ReadBoolean(); + data.relative = input.ReadBoolean(); + data.offsetRotation = input.ReadFloat(); + data.offsetX = input.ReadFloat() * scale; + data.offsetY = input.ReadFloat() * scale; + data.offsetScaleX = input.ReadFloat(); + data.offsetScaleY = input.ReadFloat(); + data.offsetShearY = input.ReadFloat(); + data.mixRotate = input.ReadFloat(); + data.mixX = input.ReadFloat(); + data.mixY = input.ReadFloat(); + data.mixScaleX = input.ReadFloat(); + data.mixScaleY = input.ReadFloat(); + data.mixShearY = input.ReadFloat(); + o[i] = data; + } + + // Path constraints + o = skeletonData.pathConstraints.Resize(n = input.ReadInt(true)).Items; + for (int i = 0, nn; i < n; i++) + { + PathConstraintData data = new PathConstraintData(input.ReadString()); + data.order = input.ReadInt(true); + data.skinRequired = input.ReadBoolean(); + Object[] constraintBones = data.bones.Resize(nn = input.ReadInt(true)).Items; + for (int ii = 0; ii < nn; ii++) + constraintBones[ii] = bones[input.ReadInt(true)]; + data.target = slots[input.ReadInt(true)]; + data.positionMode = (PositionMode)Enum.GetValues(typeof(PositionMode)).GetValue(input.ReadInt(true)); + data.spacingMode = (SpacingMode)Enum.GetValues(typeof(SpacingMode)).GetValue(input.ReadInt(true)); + data.rotateMode = (RotateMode)Enum.GetValues(typeof(RotateMode)).GetValue(input.ReadInt(true)); + data.offsetRotation = input.ReadFloat(); + data.position = input.ReadFloat(); + if (data.positionMode == PositionMode.Fixed) data.position *= scale; + data.spacing = input.ReadFloat(); + if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale; + data.mixRotate = input.ReadFloat(); + data.mixX = input.ReadFloat(); + data.mixY = input.ReadFloat(); + o[i] = data; + } + + // Default skin. + Skin defaultSkin = ReadSkin(input, skeletonData, true, nonessential); + if (defaultSkin != null) + { + skeletonData.defaultSkin = defaultSkin; + skeletonData.skins.Add(defaultSkin); + } + + // Skins. + { + int i = skeletonData.skins.Count; + o = skeletonData.skins.Resize(n = i + input.ReadInt(true)).Items; + for (; i < n; i++) + o[i] = ReadSkin(input, skeletonData, false, nonessential); + } + + // Linked meshes. + n = linkedMeshes.Count; + for (int i = 0; i < n; i++) + { + LinkedMesh linkedMesh = linkedMeshes[i]; + Skin skin = linkedMesh.skin == null ? skeletonData.DefaultSkin : skeletonData.FindSkin(linkedMesh.skin); + if (skin == null) throw new Exception("Skin not found: " + linkedMesh.skin); + Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); + if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); + linkedMesh.mesh.DeformAttachment = linkedMesh.inheritDeform ? (VertexAttachment)parent : linkedMesh.mesh; + linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; + linkedMesh.mesh.UpdateUVs(); + } + linkedMeshes.Clear(); + + // Events. + o = skeletonData.events.Resize(n = input.ReadInt(true)).Items; + for (int i = 0; i < n; i++) + { + EventData data = new EventData(input.ReadStringRef()); + data.Int = input.ReadInt(false); + data.Float = input.ReadFloat(); + data.String = input.ReadString(); + data.AudioPath = input.ReadString(); + if (data.AudioPath != null) + { + data.Volume = input.ReadFloat(); + data.Balance = input.ReadFloat(); + } + o[i] = data; + } + + // Animations. + o = skeletonData.animations.Resize(n = input.ReadInt(true)).Items; + for (int i = 0; i < n; i++) + o[i] = ReadAnimation(input.ReadString(), input, skeletonData); + + return skeletonData; + } + + /// May be null. + private Skin ReadSkin(SkeletonInput input, SkeletonData skeletonData, bool defaultSkin, bool nonessential) + { + + Skin skin; + int slotCount; + + if (defaultSkin) + { + slotCount = input.ReadInt(true); + if (slotCount == 0) return null; + skin = new Skin("default"); + } + else + { + skin = new Skin(input.ReadStringRef()); + Object[] bones = skin.bones.Resize(input.ReadInt(true)).Items; + var bonesItems = skeletonData.bones.Items; + for (int i = 0, n = skin.bones.Count; i < n; i++) + bones[i] = bonesItems[input.ReadInt(true)]; + + var ikConstraintsItems = skeletonData.ikConstraints.Items; + for (int i = 0, n = input.ReadInt(true); i < n; i++) + skin.constraints.Add(ikConstraintsItems[input.ReadInt(true)]); + var transformConstraintsItems = skeletonData.transformConstraints.Items; + for (int i = 0, n = input.ReadInt(true); i < n; i++) + skin.constraints.Add(transformConstraintsItems[input.ReadInt(true)]); + var pathConstraintsItems = skeletonData.pathConstraints.Items; + for (int i = 0, n = input.ReadInt(true); i < n; i++) + skin.constraints.Add(pathConstraintsItems[input.ReadInt(true)]); + skin.constraints.TrimExcess(); + + slotCount = input.ReadInt(true); + } + for (int i = 0; i < slotCount; i++) + { + int slotIndex = input.ReadInt(true); + for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) + { + String name = input.ReadStringRef(); + Attachment attachment = ReadAttachment(input, skeletonData, skin, slotIndex, name, nonessential); + if (attachment != null) skin.SetAttachment(slotIndex, name, attachment); + } + } + return skin; + } + + private Attachment ReadAttachment(SkeletonInput input, SkeletonData skeletonData, Skin skin, int slotIndex, + String attachmentName, bool nonessential) + { + float scale = this.scale; + + String name = input.ReadStringRef(); + if (name == null) name = attachmentName; + + switch ((AttachmentType)input.ReadByte()) + { + case AttachmentType.Region: + { + String path = input.ReadStringRef(); + float rotation = input.ReadFloat(); + float x = input.ReadFloat(); + float y = input.ReadFloat(); + float scaleX = input.ReadFloat(); + float scaleY = input.ReadFloat(); + float width = input.ReadFloat(); + float height = input.ReadFloat(); + int color = input.ReadInt(); + + if (path == null) path = name; + RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); + if (region == null) return null; + region.Path = path; + region.x = x * scale; + region.y = y * scale; + region.scaleX = scaleX; + region.scaleY = scaleY; + region.rotation = rotation; + region.width = width * scale; + region.height = height * scale; + region.r = ((color & 0xff000000) >> 24) / 255f; + region.g = ((color & 0x00ff0000) >> 16) / 255f; + region.b = ((color & 0x0000ff00) >> 8) / 255f; + region.a = ((color & 0x000000ff)) / 255f; + region.UpdateOffset(); + return region; + } + case AttachmentType.Boundingbox: + { + int vertexCount = input.ReadInt(true); + Vertices vertices = ReadVertices(input, vertexCount); + if (nonessential) input.ReadInt(); //int color = nonessential ? input.ReadInt() : 0; // Avoid unused local warning. + + BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); + if (box == null) return null; + box.worldVerticesLength = vertexCount << 1; + box.vertices = vertices.vertices; + box.bones = vertices.bones; + // skipped porting: if (nonessential) Color.rgba8888ToColor(box.getColor(), color); + return box; + } + case AttachmentType.Mesh: + { + String path = input.ReadStringRef(); + int color = input.ReadInt(); + int vertexCount = input.ReadInt(true); + float[] uvs = ReadFloatArray(input, vertexCount << 1, 1); + int[] triangles = ReadShortArray(input); + Vertices vertices = ReadVertices(input, vertexCount); + int hullLength = input.ReadInt(true); + int[] edges = null; + float width = 0, height = 0; + if (nonessential) + { + edges = ReadShortArray(input); + width = input.ReadFloat(); + height = input.ReadFloat(); + } + + if (path == null) path = name; + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); + if (mesh == null) return null; + mesh.Path = path; + mesh.r = ((color & 0xff000000) >> 24) / 255f; + mesh.g = ((color & 0x00ff0000) >> 16) / 255f; + mesh.b = ((color & 0x0000ff00) >> 8) / 255f; + mesh.a = ((color & 0x000000ff)) / 255f; + mesh.bones = vertices.bones; + mesh.vertices = vertices.vertices; + mesh.WorldVerticesLength = vertexCount << 1; + mesh.triangles = triangles; + mesh.regionUVs = uvs; + mesh.UpdateUVs(); + mesh.HullLength = hullLength << 1; + if (nonessential) + { + mesh.Edges = edges; + mesh.Width = width * scale; + mesh.Height = height * scale; + } + return mesh; + } + case AttachmentType.Linkedmesh: + { + String path = input.ReadStringRef(); + int color = input.ReadInt(); + String skinName = input.ReadStringRef(); + String parent = input.ReadStringRef(); + bool inheritDeform = input.ReadBoolean(); + float width = 0, height = 0; + if (nonessential) + { + width = input.ReadFloat(); + height = input.ReadFloat(); + } + + if (path == null) path = name; + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); + if (mesh == null) return null; + mesh.Path = path; + mesh.r = ((color & 0xff000000) >> 24) / 255f; + mesh.g = ((color & 0x00ff0000) >> 16) / 255f; + mesh.b = ((color & 0x0000ff00) >> 8) / 255f; + mesh.a = ((color & 0x000000ff)) / 255f; + if (nonessential) + { + mesh.Width = width * scale; + mesh.Height = height * scale; + } + linkedMeshes.Add(new SkeletonJson.LinkedMesh(mesh, skinName, slotIndex, parent, inheritDeform)); + return mesh; + } + case AttachmentType.Path: + { + bool closed = input.ReadBoolean(); + bool constantSpeed = input.ReadBoolean(); + int vertexCount = input.ReadInt(true); + Vertices vertices = ReadVertices(input, vertexCount); + float[] lengths = new float[vertexCount / 3]; + for (int i = 0, n = lengths.Length; i < n; i++) + lengths[i] = input.ReadFloat() * scale; + if (nonessential) input.ReadInt(); //int color = nonessential ? input.ReadInt() : 0; + + PathAttachment path = attachmentLoader.NewPathAttachment(skin, name); + if (path == null) return null; + path.closed = closed; + path.constantSpeed = constantSpeed; + path.worldVerticesLength = vertexCount << 1; + path.vertices = vertices.vertices; + path.bones = vertices.bones; + path.lengths = lengths; + // skipped porting: if (nonessential) Color.rgba8888ToColor(path.getColor(), color); + return path; + } + case AttachmentType.Point: + { + float rotation = input.ReadFloat(); + float x = input.ReadFloat(); + float y = input.ReadFloat(); + if (nonessential) input.ReadInt(); //int color = nonessential ? input.ReadInt() : 0; + + PointAttachment point = attachmentLoader.NewPointAttachment(skin, name); + if (point == null) return null; + point.x = x * scale; + point.y = y * scale; + point.rotation = rotation; + // skipped porting: if (nonessential) point.color = color; + return point; + } + case AttachmentType.Clipping: + { + int endSlotIndex = input.ReadInt(true); + int vertexCount = input.ReadInt(true); + Vertices vertices = ReadVertices(input, vertexCount); + if (nonessential) input.ReadInt(); + + ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name); + if (clip == null) return null; + clip.EndSlot = skeletonData.slots.Items[endSlotIndex]; + clip.worldVerticesLength = vertexCount << 1; + clip.vertices = vertices.vertices; + clip.bones = vertices.bones; + // skipped porting: if (nonessential) Color.rgba8888ToColor(clip.getColor(), color); + return clip; + } + } + return null; + } + + private Vertices ReadVertices(SkeletonInput input, int vertexCount) + { + float scale = this.scale; + int verticesLength = vertexCount << 1; + Vertices vertices = new Vertices(); + if (!input.ReadBoolean()) + { + vertices.vertices = ReadFloatArray(input, verticesLength, scale); + return vertices; + } + var weights = new ExposedList(verticesLength * 3 * 3); + var bonesArray = new ExposedList(verticesLength * 3); + for (int i = 0; i < vertexCount; i++) + { + int boneCount = input.ReadInt(true); + bonesArray.Add(boneCount); + for (int ii = 0; ii < boneCount; ii++) + { + bonesArray.Add(input.ReadInt(true)); + weights.Add(input.ReadFloat() * scale); + weights.Add(input.ReadFloat() * scale); + weights.Add(input.ReadFloat()); + } + } + + vertices.vertices = weights.ToArray(); + vertices.bones = bonesArray.ToArray(); + return vertices; + } + + private float[] ReadFloatArray(SkeletonInput input, int n, float scale) + { + float[] array = new float[n]; + if (scale == 1) + { + for (int i = 0; i < n; i++) + array[i] = input.ReadFloat(); + } + else + { + for (int i = 0; i < n; i++) + array[i] = input.ReadFloat() * scale; + } + return array; + } + + private int[] ReadShortArray(SkeletonInput input) + { + int n = input.ReadInt(true); + int[] array = new int[n]; + for (int i = 0; i < n; i++) + array[i] = (input.ReadByte() << 8) | input.ReadByte(); + return array; + } + + /// SerializationException will be thrown when a Vertex attachment is not found. + /// Throws IOException when a read operation fails. + private Animation ReadAnimation(String name, SkeletonInput input, SkeletonData skeletonData) + { + var timelines = new ExposedList(input.ReadInt(true)); + float scale = this.scale; + + // Slot timelines. + for (int i = 0, n = input.ReadInt(true); i < n; i++) + { + int slotIndex = input.ReadInt(true); + for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) + { + int timelineType = input.ReadByte(), frameCount = input.ReadInt(true), frameLast = frameCount - 1; + switch (timelineType) + { + case SLOT_ATTACHMENT: + { + AttachmentTimeline timeline = new AttachmentTimeline(frameCount, slotIndex); + for (int frame = 0; frame < frameCount; frame++) + timeline.SetFrame(frame, input.ReadFloat(), input.ReadStringRef()); + timelines.Add(timeline); + break; + } + case SLOT_RGBA: + { + RGBATimeline timeline = new RGBATimeline(frameCount, input.ReadInt(true), slotIndex); + float time = input.ReadFloat(); + float r = input.Read() / 255f, g = input.Read() / 255f; + float b = input.Read() / 255f, a = input.Read() / 255f; + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, r, g, b, a); + if (frame == frameLast) break; + float time2 = input.ReadFloat(); + float r2 = input.Read() / 255f, g2 = input.Read() / 255f; + float b2 = input.Read() / 255f, a2 = input.Read() / 255f; + switch (input.ReadByte()) + { + 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); + break; + } + time = time2; + r = r2; + g = g2; + b = b2; + a = a2; + } + timelines.Add(timeline); + break; + } + case SLOT_RGB: + { + RGBTimeline timeline = new RGBTimeline(frameCount, input.ReadInt(true), slotIndex); + float time = input.ReadFloat(); + float r = input.Read() / 255f, g = input.Read() / 255f, b = input.Read() / 255f; + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, r, g, b); + if (frame == frameLast) break; + float time2 = input.ReadFloat(); + float r2 = input.Read() / 255f, g2 = input.Read() / 255f, b2 = input.Read() / 255f; + switch (input.ReadByte()) + { + 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); + break; + } + time = time2; + r = r2; + g = g2; + b = b2; + } + timelines.Add(timeline); + break; + } + case SLOT_RGBA2: + { + RGBA2Timeline timeline = new RGBA2Timeline(frameCount, input.ReadInt(true), slotIndex); + float time = input.ReadFloat(); + float r = input.Read() / 255f, g = input.Read() / 255f; + float b = input.Read() / 255f, a = input.Read() / 255f; + float r2 = input.Read() / 255f, g2 = input.Read() / 255f, b2 = input.Read() / 255f; + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, r, g, b, a, r2, g2, b2); + if (frame == frameLast) break; + float time2 = input.ReadFloat(); + float nr = input.Read() / 255f, ng = input.Read() / 255f; + float nb = input.Read() / 255f, na = input.Read() / 255f; + float nr2 = input.Read() / 255f, ng2 = input.Read() / 255f, nb2 = input.Read() / 255f; + switch (input.ReadByte()) + { + 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); + break; + } + time = time2; + r = nr; + g = ng; + b = nb; + a = na; + r2 = nr2; + g2 = ng2; + b2 = nb2; + } + timelines.Add(timeline); + break; + } + case SLOT_RGB2: + { + RGB2Timeline timeline = new RGB2Timeline(frameCount, input.ReadInt(true), slotIndex); + float time = input.ReadFloat(); + float r = input.Read() / 255f, g = input.Read() / 255f, b = input.Read() / 255f; + float r2 = input.Read() / 255f, g2 = input.Read() / 255f, b2 = input.Read() / 255f; + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, r, g, b, r2, g2, b2); + if (frame == frameLast) break; + float time2 = input.ReadFloat(); + float nr = input.Read() / 255f, ng = input.Read() / 255f, nb = input.Read() / 255f; + float nr2 = input.Read() / 255f, ng2 = input.Read() / 255f, nb2 = input.Read() / 255f; + switch (input.ReadByte()) + { + 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); + break; + } + time = time2; + r = nr; + g = ng; + b = nb; + r2 = nr2; + g2 = ng2; + b2 = nb2; + } + timelines.Add(timeline); + break; + } + case SLOT_ALPHA: + { + AlphaTimeline timeline = new AlphaTimeline(frameCount, input.ReadInt(true), slotIndex); + float time = input.ReadFloat(), a = input.Read() / 255f; + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, a); + if (frame == frameLast) break; + float time2 = input.ReadFloat(); + float a2 = input.Read() / 255f; + switch (input.ReadByte()) + { + case CURVE_STEPPED: + timeline.SetStepped(frame); + break; + case CURVE_BEZIER: + SetBezier(input, timeline, bezier++, frame, 0, time, time2, a, a2, 1); + break; + } + time = time2; + a = a2; + } + timelines.Add(timeline); + break; + } + } + } + } + + // Bone timelines. + for (int i = 0, n = input.ReadInt(true); i < n; i++) + { + int boneIndex = input.ReadInt(true); + for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) + { + int type = input.ReadByte(), frameCount = input.ReadInt(true), bezierCount = input.ReadInt(true); + switch (type) + { + case BONE_ROTATE: + timelines.Add(ReadTimeline(input, new RotateTimeline(frameCount, bezierCount, boneIndex), 1)); + break; + case BONE_TRANSLATE: + timelines.Add(ReadTimeline(input, new TranslateTimeline(frameCount, bezierCount, boneIndex), scale)); + break; + case BONE_TRANSLATEX: + timelines.Add(ReadTimeline(input, new TranslateXTimeline(frameCount, bezierCount, boneIndex), scale)); + break; + case BONE_TRANSLATEY: + timelines.Add(ReadTimeline(input, new TranslateYTimeline(frameCount, bezierCount, boneIndex), scale)); + break; + case BONE_SCALE: + timelines.Add(ReadTimeline(input, new ScaleTimeline(frameCount, bezierCount, boneIndex), 1)); + break; + case BONE_SCALEX: + timelines.Add(ReadTimeline(input, new ScaleXTimeline(frameCount, bezierCount, boneIndex), 1)); + break; + case BONE_SCALEY: + timelines.Add(ReadTimeline(input, new ScaleYTimeline(frameCount, bezierCount, boneIndex), 1)); + break; + case BONE_SHEAR: + timelines.Add(ReadTimeline(input, new ShearTimeline(frameCount, bezierCount, boneIndex), 1)); + break; + case BONE_SHEARX: + timelines.Add(ReadTimeline(input, new ShearXTimeline(frameCount, bezierCount, boneIndex), 1)); + break; + case BONE_SHEARY: + timelines.Add(ReadTimeline(input, new ShearYTimeline(frameCount, bezierCount, boneIndex), 1)); + break; + } + } + } + + // IK constraint timelines. + for (int i = 0, n = input.ReadInt(true); i < n; i++) + { + int index = input.ReadInt(true), frameCount = input.ReadInt(true), frameLast = frameCount - 1; + IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount, input.ReadInt(true), index); + float time = input.ReadFloat(), mix = input.ReadFloat(), softness = input.ReadFloat() * scale; + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, mix, softness, input.ReadSByte(), input.ReadBoolean(), input.ReadBoolean()); + if (frame == frameLast) break; + float time2 = input.ReadFloat(), mix2 = input.ReadFloat(), softness2 = input.ReadFloat() * scale; + switch (input.ReadByte()) + { + case CURVE_STEPPED: + timeline.SetStepped(frame); + break; + case CURVE_BEZIER: + SetBezier(input, timeline, bezier++, frame, 0, time, time2, mix, mix2, 1); + SetBezier(input, timeline, bezier++, frame, 1, time, time2, softness, softness2, scale); + break; + } + time = time2; + mix = mix2; + softness = softness2; + } + timelines.Add(timeline); + } + + // Transform constraint timelines. + for (int i = 0, n = input.ReadInt(true); i < n; i++) + { + int index = input.ReadInt(true), frameCount = input.ReadInt(true), frameLast = frameCount - 1; + TransformConstraintTimeline timeline = new TransformConstraintTimeline(frameCount, input.ReadInt(true), index); + float time = input.ReadFloat(), mixRotate = input.ReadFloat(), mixX = input.ReadFloat(), mixY = input.ReadFloat(), + mixScaleX = input.ReadFloat(), mixScaleY = input.ReadFloat(), mixShearY = input.ReadFloat(); + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY); + if (frame == frameLast) break; + float time2 = input.ReadFloat(), mixRotate2 = input.ReadFloat(), mixX2 = input.ReadFloat(), mixY2 = input.ReadFloat(), + mixScaleX2 = input.ReadFloat(), mixScaleY2 = input.ReadFloat(), mixShearY2 = input.ReadFloat(); + switch (input.ReadByte()) + { + 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); + break; + } + 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 = input.ReadInt(true); i < n; i++) + { + int index = input.ReadInt(true); + PathConstraintData data = skeletonData.pathConstraints.Items[index]; + for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) + { + switch (input.ReadByte()) + { + case PATH_POSITION: + timelines + .Add(ReadTimeline(input, new PathConstraintPositionTimeline(input.ReadInt(true), input.ReadInt(true), index), + data.positionMode == PositionMode.Fixed ? scale : 1)); + break; + case PATH_SPACING: + timelines + .Add(ReadTimeline(input, new PathConstraintSpacingTimeline(input.ReadInt(true), input.ReadInt(true), index), + data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed ? scale : 1)); + break; + case PATH_MIX: + PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(input.ReadInt(true), input.ReadInt(true), + index); + float time = input.ReadFloat(), mixRotate = input.ReadFloat(), mixX = input.ReadFloat(), mixY = input.ReadFloat(); + for (int frame = 0, bezier = 0, frameLast = timeline.FrameCount - 1; ; frame++) + { + timeline.SetFrame(frame, time, mixRotate, mixX, mixY); + if (frame == frameLast) break; + float time2 = input.ReadFloat(), mixRotate2 = input.ReadFloat(), mixX2 = input.ReadFloat(), + mixY2 = input.ReadFloat(); + switch (input.ReadByte()) + { + 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); + break; + } + time = time2; + mixRotate = mixRotate2; + mixX = mixX2; + mixY = mixY2; + } + timelines.Add(timeline); + break; + } + } + } + + // Deform timelines. + for (int i = 0, n = input.ReadInt(true); i < n; i++) + { + Skin skin = skeletonData.skins.Items[input.ReadInt(true)]; + for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) + { + int slotIndex = input.ReadInt(true); + for (int iii = 0, nnn = input.ReadInt(true); iii < nnn; iii++) + { + String attachmentName = input.ReadStringRef(); + VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, attachmentName); + if (attachment == null) throw new SerializationException("Vertex attachment not found: " + attachmentName); + bool weighted = attachment.Bones != null; + float[] vertices = attachment.Vertices; + int deformLength = weighted ? (vertices.Length / 3) << 1 : vertices.Length; + + int frameCount = input.ReadInt(true), frameLast = frameCount - 1; + DeformTimeline timeline = new DeformTimeline(frameCount, input.ReadInt(true), slotIndex, attachment); + + float time = input.ReadFloat(); + for (int frame = 0, bezier = 0; ; frame++) + { + float[] deform; + int end = input.ReadInt(true); + if (end == 0) + deform = weighted ? new float[deformLength] : vertices; + else + { + deform = new float[deformLength]; + int start = input.ReadInt(true); + end += start; + if (scale == 1) + { + for (int v = start; v < end; v++) + deform[v] = input.ReadFloat(); + } + else + { + for (int v = start; v < end; v++) + deform[v] = input.ReadFloat() * scale; + } + if (!weighted) + { + for (int v = 0, vn = deform.Length; v < vn; v++) + deform[v] += vertices[v]; + } + } + timeline.SetFrame(frame, time, deform); + if (frame == frameLast) break; + float time2 = input.ReadFloat(); + switch (input.ReadByte()) + { + case CURVE_STEPPED: + timeline.SetStepped(frame); + break; + case CURVE_BEZIER: + SetBezier(input, timeline, bezier++, frame, 0, time, time2, 0, 1, 1); + break; + } + time = time2; + } + timelines.Add(timeline); + } + } + } + + // Draw order timeline. + int drawOrderCount = input.ReadInt(true); + if (drawOrderCount > 0) + { + DrawOrderTimeline timeline = new DrawOrderTimeline(drawOrderCount); + int slotCount = skeletonData.slots.Count; + for (int i = 0; i < drawOrderCount; i++) + { + float time = input.ReadFloat(); + int offsetCount = input.ReadInt(true); + int[] drawOrder = new int[slotCount]; + for (int ii = slotCount - 1; ii >= 0; ii--) + drawOrder[ii] = -1; + int[] unchanged = new int[slotCount - offsetCount]; + int originalIndex = 0, unchangedIndex = 0; + for (int ii = 0; ii < offsetCount; ii++) + { + int slotIndex = input.ReadInt(true); + // Collect unchanged items. + while (originalIndex != slotIndex) + unchanged[unchangedIndex++] = originalIndex++; + // Set changed items. + drawOrder[originalIndex + input.ReadInt(true)] = originalIndex++; + } + // Collect remaining unchanged items. + while (originalIndex < slotCount) + unchanged[unchangedIndex++] = originalIndex++; + // Fill in unchanged items. + for (int ii = 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 = input.ReadInt(true); + if (eventCount > 0) + { + EventTimeline timeline = new EventTimeline(eventCount); + for (int i = 0; i < eventCount; i++) + { + float time = input.ReadFloat(); + EventData eventData = skeletonData.events.Items[input.ReadInt(true)]; + Event e = new Event(time, eventData); + e.intValue = input.ReadInt(false); + e.floatValue = input.ReadFloat(); + e.stringValue = input.ReadBoolean() ? input.ReadString() : eventData.String; + if (e.Data.AudioPath != null) + { + e.volume = input.ReadFloat(); + e.balance = input.ReadFloat(); + } + timeline.SetFrame(i, e); + } + timelines.Add(timeline); + } + + float duration = 0; + var items = timelines.Items; + for (int i = 0, n = timelines.Count; i < n; i++) + duration = Math.Max(duration, items[i].Duration); + return new Animation(name, timelines, duration); + } + + /// Throws IOException when a read operation fails. + private Timeline ReadTimeline(SkeletonInput input, CurveTimeline1 timeline, float scale) + { + float time = input.ReadFloat(), value = input.ReadFloat() * scale; + for (int frame = 0, bezier = 0, frameLast = timeline.FrameCount - 1; ; frame++) + { + timeline.SetFrame(frame, time, value); + if (frame == frameLast) break; + float time2 = input.ReadFloat(), value2 = input.ReadFloat() * scale; + switch (input.ReadByte()) + { + case CURVE_STEPPED: + timeline.SetStepped(frame); + break; + case CURVE_BEZIER: + SetBezier(input, timeline, bezier++, frame, 0, time, time2, value, value2, scale); + break; + } + time = time2; + value = value2; + } + return timeline; + } + + /// Throws IOException when a read operation fails. + private Timeline ReadTimeline(SkeletonInput input, CurveTimeline2 timeline, float scale) + { + float time = input.ReadFloat(), value1 = input.ReadFloat() * scale, value2 = input.ReadFloat() * scale; + for (int frame = 0, bezier = 0, frameLast = timeline.FrameCount - 1; ; frame++) + { + timeline.SetFrame(frame, time, value1, value2); + if (frame == frameLast) break; + float time2 = input.ReadFloat(), nvalue1 = input.ReadFloat() * scale, nvalue2 = input.ReadFloat() * scale; + switch (input.ReadByte()) + { + 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); + break; + } + time = time2; + value1 = nvalue1; + value2 = nvalue2; + } + return timeline; + } + + /// Throws IOException when a read operation fails. + void SetBezier(SkeletonInput input, CurveTimeline timeline, int bezier, int frame, int value, float time1, float time2, + float value1, float value2, float scale) + { + timeline.SetBezier(bezier, frame, value, time1, value1, input.ReadFloat(), input.ReadFloat() * scale, input.ReadFloat(), + input.ReadFloat() * scale, time2, value2); + } + + internal class Vertices + { + public int[] bones; + public float[] vertices; + } + + internal class SkeletonInput + { + private byte[] chars = new byte[32]; + private byte[] bytesBigEndian = new byte[8]; + internal string[] strings; + Stream input; + + public SkeletonInput(Stream input) + { + this.input = input; + } + + public int Read() + { + return input.ReadByte(); + } + + public byte ReadByte() + { + return (byte)input.ReadByte(); + } + + public sbyte ReadSByte() + { + int value = input.ReadByte(); + if (value == -1) throw new EndOfStreamException(); + return (sbyte)value; + } + + public bool ReadBoolean() + { + return input.ReadByte() != 0; + } + + public float ReadFloat() + { + input.Read(bytesBigEndian, 0, 4); + chars[3] = bytesBigEndian[0]; + chars[2] = bytesBigEndian[1]; + chars[1] = bytesBigEndian[2]; + chars[0] = bytesBigEndian[3]; + return BitConverter.ToSingle(chars, 0); + } + + public int ReadInt() + { + input.Read(bytesBigEndian, 0, 4); + return (bytesBigEndian[0] << 24) + + (bytesBigEndian[1] << 16) + + (bytesBigEndian[2] << 8) + + bytesBigEndian[3]; + } + + public long ReadLong() + { + input.Read(bytesBigEndian, 0, 8); + return ((long)(bytesBigEndian[0]) << 56) + + ((long)(bytesBigEndian[1]) << 48) + + ((long)(bytesBigEndian[2]) << 40) + + ((long)(bytesBigEndian[3]) << 32) + + ((long)(bytesBigEndian[4]) << 24) + + ((long)(bytesBigEndian[5]) << 16) + + ((long)(bytesBigEndian[6]) << 8) + + bytesBigEndian[7]; + } + + public int ReadInt(bool optimizePositive) + { + int b = input.ReadByte(); + int result = b & 0x7F; + if ((b & 0x80) != 0) + { + b = input.ReadByte(); + result |= (b & 0x7F) << 7; + if ((b & 0x80) != 0) + { + b = input.ReadByte(); + result |= (b & 0x7F) << 14; + if ((b & 0x80) != 0) + { + b = input.ReadByte(); + result |= (b & 0x7F) << 21; + if ((b & 0x80) != 0) result |= (input.ReadByte() & 0x7F) << 28; + } + } + } + return optimizePositive ? result : ((result >> 1) ^ -(result & 1)); + } + + public string ReadString() + { + int byteCount = ReadInt(true); + switch (byteCount) + { + case 0: + return null; + case 1: + return ""; + } + byteCount--; + byte[] buffer = this.chars; + if (buffer.Length < byteCount) buffer = new byte[byteCount]; + ReadFully(buffer, 0, byteCount); + return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount); + } + + ///May be null. + public String ReadStringRef() + { + int index = ReadInt(true); + return index == 0 ? null : strings[index - 1]; + } + + public void ReadFully(byte[] buffer, int offset, int length) + { + while (length > 0) + { + int count = input.Read(buffer, offset, length); + if (count <= 0) throw new EndOfStreamException(); + offset += count; + length -= count; + } + } + + /// Returns the version string of binary skeleton data. + public string GetVersionString() + { + try + { + // try reading 4.0+ format + var initialPosition = input.Position; + ReadLong(); // long hash + + var stringPosition = input.Position; + int stringByteCount = ReadInt(true); + input.Position = stringPosition; + if (stringByteCount <= 13) + { + string version = ReadString(); + if (char.IsDigit(version[0])) + return version; + } + // fallback to old version format + input.Position = initialPosition; + return GetVersionStringOld3X(); + } + catch (Exception e) + { + throw new ArgumentException("Stream does not contain valid binary Skeleton Data.\n" + e, "input"); + } + } + + /// Returns old 3.8 and earlier format version string of binary skeleton data. + public string GetVersionStringOld3X() + { + // Hash. + int byteCount = ReadInt(true); + if (byteCount > 1) input.Position += byteCount - 1; + + // Version. + byteCount = ReadInt(true); + if (byteCount > 1 && byteCount <= 13) + { + byteCount--; + var buffer = new byte[byteCount]; + ReadFully(buffer, 0, byteCount); + return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount); + } + throw new ArgumentException("Stream does not contain valid binary Skeleton Data."); + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/SkeletonBounds.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/SkeletonBounds.cs index e1f811c..b2eba6e 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/SkeletonBounds.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/SkeletonBounds.cs @@ -29,205 +29,232 @@ using System; -namespace Spine4_0_64 { - - /// - /// 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. - /// - public class SkeletonBounds { - private ExposedList polygonPool = new ExposedList(); - private float minX, minY, maxX, maxY; - - public ExposedList BoundingBoxes { get; private set; } - public ExposedList Polygons { get; private set; } - public float MinX { get { return minX; } set { minX = value; } } - public float MinY { get { return minY; } set { minY = value; } } - public float MaxX { get { return maxX; } set { maxX = value; } } - public float MaxY { get { return maxY; } set { maxY = value; } } - public float Width { get { return maxX - minX; } } - public float Height { get { return maxY - minY; } } - - public SkeletonBounds () { - BoundingBoxes = new ExposedList(); - Polygons = new ExposedList(); - } - - /// - /// Clears any previous polygons, finds all visible bounding box attachments, - /// and computes the world vertices for each bounding box's polygon. - /// The skeleton. - /// - /// If true, the axis aligned bounding box containing all the polygons is computed. - /// If false, the SkeletonBounds AABB methods will always return true. - /// - public void Update (Skeleton skeleton, bool updateAabb) { - ExposedList boundingBoxes = BoundingBoxes; - ExposedList polygons = Polygons; - Slot[] slots = skeleton.slots.Items; - int slotCount = skeleton.slots.Count; - - boundingBoxes.Clear(); - for (int i = 0, n = polygons.Count; i < n; i++) - polygonPool.Add(polygons.Items[i]); - polygons.Clear(); - - for (int i = 0; i < slotCount; i++) { - Slot slot = slots[i]; - if (!slot.bone.active) continue; - BoundingBoxAttachment boundingBox = slot.attachment as BoundingBoxAttachment; - if (boundingBox == null) continue; - boundingBoxes.Add(boundingBox); - - Polygon polygon = null; - int poolCount = polygonPool.Count; - if (poolCount > 0) { - polygon = polygonPool.Items[poolCount - 1]; - polygonPool.RemoveAt(poolCount - 1); - } else - polygon = new Polygon(); - polygons.Add(polygon); - - int count = boundingBox.worldVerticesLength; - polygon.Count = count; - if (polygon.Vertices.Length < count) polygon.Vertices = new float[count]; - boundingBox.ComputeWorldVertices(slot, polygon.Vertices); - } - - if (updateAabb) { - AabbCompute(); - } else { - minX = int.MinValue; - minY = int.MinValue; - maxX = int.MaxValue; - maxY = int.MaxValue; - } - } - - private void AabbCompute () { - float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; - Polygon[] polygons = Polygons.Items; - for (int i = 0, n = Polygons.Count; i < n; i++) { - Polygon polygon = polygons[i]; - float[] vertices = polygon.Vertices; - for (int ii = 0, nn = polygon.Count; ii < nn; ii += 2) { - float x = vertices[ii]; - float y = vertices[ii + 1]; - minX = Math.Min(minX, x); - minY = Math.Min(minY, y); - maxX = Math.Max(maxX, x); - maxY = Math.Max(maxY, y); - } - } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; - } - - /// Returns true if the axis aligned bounding box contains the point. - public bool AabbContainsPoint (float x, float y) { - return x >= minX && x <= maxX && y >= minY && y <= maxY; - } - - /// Returns true if the axis aligned bounding box intersects the line segment. - public bool AabbIntersectsSegment (float x1, float y1, float x2, float y2) { - float minX = this.minX; - float minY = this.minY; - float maxX = this.maxX; - float maxY = this.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; - } - - /// Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds. - public bool AabbIntersectsSkeleton (SkeletonBounds bounds) { - return minX < bounds.maxX && maxX > bounds.minX && minY < bounds.maxY && maxY > bounds.minY; - } - - /// Returns true if the polygon contains the point. - public bool ContainsPoint (Polygon polygon, float x, float y) { - float[] 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; - } - - /// 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 returns true. - public BoundingBoxAttachment ContainsPoint (float x, float y) { - Polygon[] polygons = Polygons.Items; - for (int i = 0, n = Polygons.Count; i < n; i++) - if (ContainsPoint(polygons[i], x, y)) return BoundingBoxes.Items[i]; - return null; - } - - /// 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 returns true. - public BoundingBoxAttachment IntersectsSegment (float x1, float y1, float x2, float y2) { - Polygon[] polygons = Polygons.Items; - for (int i = 0, n = Polygons.Count; i < n; i++) - if (IntersectsSegment(polygons[i], x1, y1, x2, y2)) return BoundingBoxes.Items[i]; - return null; - } - - /// Returns true if the polygon contains the line segment. - public bool IntersectsSegment (Polygon polygon, float x1, float y1, float x2, float y2) { - float[] vertices = polygon.Vertices; - int 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 (int 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; - } - - public Polygon GetPolygon (BoundingBoxAttachment attachment) { - int index = BoundingBoxes.IndexOf(attachment); - return index == -1 ? null : Polygons.Items[index]; - } - } - - public class Polygon { - public float[] Vertices { get; set; } - public int Count { get; set; } - - public Polygon () { - Vertices = new float[16]; - } - } +namespace Spine4_0_64 +{ + + /// + /// 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. + /// + public class SkeletonBounds + { + private ExposedList polygonPool = new ExposedList(); + private float minX, minY, maxX, maxY; + + public ExposedList BoundingBoxes { get; private set; } + public ExposedList Polygons { get; private set; } + public float MinX { get { return minX; } set { minX = value; } } + public float MinY { get { return minY; } set { minY = value; } } + public float MaxX { get { return maxX; } set { maxX = value; } } + public float MaxY { get { return maxY; } set { maxY = value; } } + public float Width { get { return maxX - minX; } } + public float Height { get { return maxY - minY; } } + + public SkeletonBounds() + { + BoundingBoxes = new ExposedList(); + Polygons = new ExposedList(); + } + + /// + /// Clears any previous polygons, finds all visible bounding box attachments, + /// and computes the world vertices for each bounding box's polygon. + /// The skeleton. + /// + /// If true, the axis aligned bounding box containing all the polygons is computed. + /// If false, the SkeletonBounds AABB methods will always return true. + /// + public void Update(Skeleton skeleton, bool updateAabb) + { + ExposedList boundingBoxes = BoundingBoxes; + ExposedList polygons = Polygons; + Slot[] slots = skeleton.slots.Items; + int slotCount = skeleton.slots.Count; + + boundingBoxes.Clear(); + for (int i = 0, n = polygons.Count; i < n; i++) + polygonPool.Add(polygons.Items[i]); + polygons.Clear(); + + for (int i = 0; i < slotCount; i++) + { + Slot slot = slots[i]; + if (!slot.bone.active) continue; + BoundingBoxAttachment boundingBox = slot.attachment as BoundingBoxAttachment; + if (boundingBox == null) continue; + boundingBoxes.Add(boundingBox); + + Polygon polygon = null; + int poolCount = polygonPool.Count; + if (poolCount > 0) + { + polygon = polygonPool.Items[poolCount - 1]; + polygonPool.RemoveAt(poolCount - 1); + } + else + polygon = new Polygon(); + polygons.Add(polygon); + + int count = boundingBox.worldVerticesLength; + polygon.Count = count; + if (polygon.Vertices.Length < count) polygon.Vertices = new float[count]; + boundingBox.ComputeWorldVertices(slot, polygon.Vertices); + } + + if (updateAabb) + { + AabbCompute(); + } + else + { + minX = int.MinValue; + minY = int.MinValue; + maxX = int.MaxValue; + maxY = int.MaxValue; + } + } + + private void AabbCompute() + { + float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; + Polygon[] polygons = Polygons.Items; + for (int i = 0, n = Polygons.Count; i < n; i++) + { + Polygon polygon = polygons[i]; + float[] vertices = polygon.Vertices; + for (int ii = 0, nn = polygon.Count; ii < nn; ii += 2) + { + float x = vertices[ii]; + float y = vertices[ii + 1]; + minX = Math.Min(minX, x); + minY = Math.Min(minY, y); + maxX = Math.Max(maxX, x); + maxY = Math.Max(maxY, y); + } + } + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /// Returns true if the axis aligned bounding box contains the point. + public bool AabbContainsPoint(float x, float y) + { + return x >= minX && x <= maxX && y >= minY && y <= maxY; + } + + /// Returns true if the axis aligned bounding box intersects the line segment. + public bool AabbIntersectsSegment(float x1, float y1, float x2, float y2) + { + float minX = this.minX; + float minY = this.minY; + float maxX = this.maxX; + float maxY = this.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; + } + + /// Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds. + public bool AabbIntersectsSkeleton(SkeletonBounds bounds) + { + return minX < bounds.maxX && maxX > bounds.minX && minY < bounds.maxY && maxY > bounds.minY; + } + + /// Returns true if the polygon contains the point. + public bool ContainsPoint(Polygon polygon, float x, float y) + { + float[] 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; + } + + /// 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 returns true. + public BoundingBoxAttachment ContainsPoint(float x, float y) + { + Polygon[] polygons = Polygons.Items; + for (int i = 0, n = Polygons.Count; i < n; i++) + if (ContainsPoint(polygons[i], x, y)) return BoundingBoxes.Items[i]; + return null; + } + + /// 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 returns true. + public BoundingBoxAttachment IntersectsSegment(float x1, float y1, float x2, float y2) + { + Polygon[] polygons = Polygons.Items; + for (int i = 0, n = Polygons.Count; i < n; i++) + if (IntersectsSegment(polygons[i], x1, y1, x2, y2)) return BoundingBoxes.Items[i]; + return null; + } + + /// Returns true if the polygon contains the line segment. + public bool IntersectsSegment(Polygon polygon, float x1, float y1, float x2, float y2) + { + float[] vertices = polygon.Vertices; + int 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 (int 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; + } + + public Polygon GetPolygon(BoundingBoxAttachment attachment) + { + int index = BoundingBoxes.IndexOf(attachment); + return index == -1 ? null : Polygons.Items[index]; + } + } + + public class Polygon + { + public float[] Vertices { get; set; } + public int Count { get; set; } + + public Polygon() + { + Vertices = new float[16]; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/SkeletonClipping.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/SkeletonClipping.cs index 1172fbc..29f9349 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/SkeletonClipping.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/SkeletonClipping.cs @@ -29,264 +29,300 @@ using System; -namespace Spine4_0_64 { - public class SkeletonClipping { - internal readonly Triangulator triangulator = new Triangulator(); - internal readonly ExposedList clippingPolygon = new ExposedList(); - internal readonly ExposedList clipOutput = new ExposedList(128); - internal readonly ExposedList clippedVertices = new ExposedList(128); - internal readonly ExposedList clippedTriangles = new ExposedList(128); - internal readonly ExposedList clippedUVs = new ExposedList(128); - internal readonly ExposedList scratch = new ExposedList(); - - internal ClippingAttachment clipAttachment; - internal ExposedList> clippingPolygons; - - public ExposedList ClippedVertices { get { return clippedVertices; } } - public ExposedList ClippedTriangles { get { return clippedTriangles; } } - public ExposedList ClippedUVs { get { return clippedUVs; } } - - public bool IsClipping { get { return clipAttachment != null; } } - - public int ClipStart (Slot slot, ClippingAttachment clip) { - if (clipAttachment != null) return 0; - clipAttachment = clip; - - int n = clip.worldVerticesLength; - float[] vertices = clippingPolygon.Resize(n).Items; - clip.ComputeWorldVertices(slot, 0, n, vertices, 0, 2); - MakeClockwise(clippingPolygon); - clippingPolygons = triangulator.Decompose(clippingPolygon, triangulator.Triangulate(clippingPolygon)); - foreach (var polygon in clippingPolygons) { - MakeClockwise(polygon); - polygon.Add(polygon.Items[0]); - polygon.Add(polygon.Items[1]); - } - return clippingPolygons.Count; - } - - public void ClipEnd (Slot slot) { - if (clipAttachment != null && clipAttachment.endSlot == slot.data) ClipEnd(); - } - - public void ClipEnd () { - if (clipAttachment == null) return; - clipAttachment = null; - clippingPolygons = null; - clippedVertices.Clear(); - clippedTriangles.Clear(); - clippingPolygon.Clear(); - } - - public void ClipTriangles (float[] vertices, int verticesLength, int[] triangles, int trianglesLength, float[] uvs) { - ExposedList clipOutput = this.clipOutput, clippedVertices = this.clippedVertices; - var clippedTriangles = this.clippedTriangles; - var polygons = clippingPolygons.Items; - int polygonsCount = clippingPolygons.Count; - - int index = 0; - clippedVertices.Clear(); - clippedUVs.Clear(); - clippedTriangles.Clear(); - //outer: - for (int i = 0; i < trianglesLength; i += 3) { - int vertexOffset = triangles[i] << 1; - float x1 = vertices[vertexOffset], y1 = vertices[vertexOffset + 1]; - float u1 = uvs[vertexOffset], v1 = uvs[vertexOffset + 1]; - - vertexOffset = triangles[i + 1] << 1; - float x2 = vertices[vertexOffset], y2 = vertices[vertexOffset + 1]; - float u2 = uvs[vertexOffset], v2 = uvs[vertexOffset + 1]; - - vertexOffset = triangles[i + 2] << 1; - float x3 = vertices[vertexOffset], y3 = vertices[vertexOffset + 1]; - float u3 = uvs[vertexOffset], v3 = uvs[vertexOffset + 1]; - - for (int p = 0; p < polygonsCount; p++) { - int s = clippedVertices.Count; - if (Clip(x1, y1, x2, y2, x3, y3, polygons[p], clipOutput)) { - int clipOutputLength = clipOutput.Count; - 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)); - - int clipOutputCount = clipOutputLength >> 1; - float[] clipOutputItems = clipOutput.Items; - float[] clippedVerticesItems = clippedVertices.Resize(s + clipOutputCount * 2).Items; - float[] clippedUVsItems = clippedUVs.Resize(s + clipOutputCount * 2).Items; - for (int ii = 0; ii < clipOutputLength; ii += 2) { - float x = clipOutputItems[ii], y = clipOutputItems[ii + 1]; - clippedVerticesItems[s] = x; - clippedVerticesItems[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; - clippedUVsItems[s] = u1 * a + u2 * b + u3 * c; - clippedUVsItems[s + 1] = v1 * a + v2 * b + v3 * c; - s += 2; - } - - s = clippedTriangles.Count; - int[] clippedTrianglesItems = clippedTriangles.Resize(s + 3 * (clipOutputCount - 2)).Items; - clipOutputCount--; - for (int ii = 1; ii < clipOutputCount; ii++) { - clippedTrianglesItems[s] = index; - clippedTrianglesItems[s + 1] = index + ii; - clippedTrianglesItems[s + 2] = index + ii + 1; - s += 3; - } - index += clipOutputCount + 1; - } else { - float[] clippedVerticesItems = clippedVertices.Resize(s + 3 * 2).Items; - float[] clippedUVsItems = clippedUVs.Resize(s + 3 * 2).Items; - clippedVerticesItems[s] = x1; - clippedVerticesItems[s + 1] = y1; - clippedVerticesItems[s + 2] = x2; - clippedVerticesItems[s + 3] = y2; - clippedVerticesItems[s + 4] = x3; - clippedVerticesItems[s + 5] = y3; - - clippedUVsItems[s] = u1; - clippedUVsItems[s + 1] = v1; - clippedUVsItems[s + 2] = u2; - clippedUVsItems[s + 3] = v2; - clippedUVsItems[s + 4] = u3; - clippedUVsItems[s + 5] = v3; - - s = clippedTriangles.Count; - int[] clippedTrianglesItems = clippedTriangles.Resize(s + 3).Items; - clippedTrianglesItems[s] = index; - clippedTrianglesItems[s + 1] = index + 1; - clippedTrianglesItems[s + 2] = index + 2; - index += 3; - break; //continue outer; - } - } - } - - } - - /** Clips the input triangle against the convex, clockwise clipping area. If the triangle lies entirely within the clipping +namespace Spine4_0_64 +{ + public class SkeletonClipping + { + internal readonly Triangulator triangulator = new Triangulator(); + internal readonly ExposedList clippingPolygon = new ExposedList(); + internal readonly ExposedList clipOutput = new ExposedList(128); + internal readonly ExposedList clippedVertices = new ExposedList(128); + internal readonly ExposedList clippedTriangles = new ExposedList(128); + internal readonly ExposedList clippedUVs = new ExposedList(128); + internal readonly ExposedList scratch = new ExposedList(); + + internal ClippingAttachment clipAttachment; + internal ExposedList> clippingPolygons; + + public ExposedList ClippedVertices { get { return clippedVertices; } } + public ExposedList ClippedTriangles { get { return clippedTriangles; } } + public ExposedList ClippedUVs { get { return clippedUVs; } } + + public bool IsClipping { get { return clipAttachment != null; } } + + public int ClipStart(Slot slot, ClippingAttachment clip) + { + if (clipAttachment != null) return 0; + clipAttachment = clip; + + int n = clip.worldVerticesLength; + float[] vertices = clippingPolygon.Resize(n).Items; + clip.ComputeWorldVertices(slot, 0, n, vertices, 0, 2); + MakeClockwise(clippingPolygon); + clippingPolygons = triangulator.Decompose(clippingPolygon, triangulator.Triangulate(clippingPolygon)); + foreach (var polygon in clippingPolygons) + { + MakeClockwise(polygon); + polygon.Add(polygon.Items[0]); + polygon.Add(polygon.Items[1]); + } + return clippingPolygons.Count; + } + + public void ClipEnd(Slot slot) + { + if (clipAttachment != null && clipAttachment.endSlot == slot.data) ClipEnd(); + } + + public void ClipEnd() + { + if (clipAttachment == null) return; + clipAttachment = null; + clippingPolygons = null; + clippedVertices.Clear(); + clippedTriangles.Clear(); + clippingPolygon.Clear(); + } + + public void ClipTriangles(float[] vertices, int verticesLength, int[] triangles, int trianglesLength, float[] uvs) + { + ExposedList clipOutput = this.clipOutput, clippedVertices = this.clippedVertices; + var clippedTriangles = this.clippedTriangles; + var polygons = clippingPolygons.Items; + int polygonsCount = clippingPolygons.Count; + + int index = 0; + clippedVertices.Clear(); + clippedUVs.Clear(); + clippedTriangles.Clear(); + //outer: + for (int i = 0; i < trianglesLength; i += 3) + { + int vertexOffset = triangles[i] << 1; + float x1 = vertices[vertexOffset], y1 = vertices[vertexOffset + 1]; + float u1 = uvs[vertexOffset], v1 = uvs[vertexOffset + 1]; + + vertexOffset = triangles[i + 1] << 1; + float x2 = vertices[vertexOffset], y2 = vertices[vertexOffset + 1]; + float u2 = uvs[vertexOffset], v2 = uvs[vertexOffset + 1]; + + vertexOffset = triangles[i + 2] << 1; + float x3 = vertices[vertexOffset], y3 = vertices[vertexOffset + 1]; + float u3 = uvs[vertexOffset], v3 = uvs[vertexOffset + 1]; + + for (int p = 0; p < polygonsCount; p++) + { + int s = clippedVertices.Count; + if (Clip(x1, y1, x2, y2, x3, y3, polygons[p], clipOutput)) + { + int clipOutputLength = clipOutput.Count; + 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)); + + int clipOutputCount = clipOutputLength >> 1; + float[] clipOutputItems = clipOutput.Items; + float[] clippedVerticesItems = clippedVertices.Resize(s + clipOutputCount * 2).Items; + float[] clippedUVsItems = clippedUVs.Resize(s + clipOutputCount * 2).Items; + for (int ii = 0; ii < clipOutputLength; ii += 2) + { + float x = clipOutputItems[ii], y = clipOutputItems[ii + 1]; + clippedVerticesItems[s] = x; + clippedVerticesItems[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; + clippedUVsItems[s] = u1 * a + u2 * b + u3 * c; + clippedUVsItems[s + 1] = v1 * a + v2 * b + v3 * c; + s += 2; + } + + s = clippedTriangles.Count; + int[] clippedTrianglesItems = clippedTriangles.Resize(s + 3 * (clipOutputCount - 2)).Items; + clipOutputCount--; + for (int ii = 1; ii < clipOutputCount; ii++) + { + clippedTrianglesItems[s] = index; + clippedTrianglesItems[s + 1] = index + ii; + clippedTrianglesItems[s + 2] = index + ii + 1; + s += 3; + } + index += clipOutputCount + 1; + } + else + { + float[] clippedVerticesItems = clippedVertices.Resize(s + 3 * 2).Items; + float[] clippedUVsItems = clippedUVs.Resize(s + 3 * 2).Items; + clippedVerticesItems[s] = x1; + clippedVerticesItems[s + 1] = y1; + clippedVerticesItems[s + 2] = x2; + clippedVerticesItems[s + 3] = y2; + clippedVerticesItems[s + 4] = x3; + clippedVerticesItems[s + 5] = y3; + + clippedUVsItems[s] = u1; + clippedUVsItems[s + 1] = v1; + clippedUVsItems[s + 2] = u2; + clippedUVsItems[s + 3] = v2; + clippedUVsItems[s + 4] = u3; + clippedUVsItems[s + 5] = v3; + + s = clippedTriangles.Count; + int[] clippedTrianglesItems = clippedTriangles.Resize(s + 3).Items; + clippedTrianglesItems[s] = index; + clippedTrianglesItems[s + 1] = index + 1; + clippedTrianglesItems[s + 2] = index + 2; + index += 3; + break; //continue outer; + } + } + } + + } + + /** 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. */ - internal bool Clip (float x1, float y1, float x2, float y2, float x3, float y3, ExposedList clippingArea, ExposedList output) { - var originalOutput = output; - var clipped = false; - - // Avoid copy at the end. - ExposedList input = null; - if (clippingArea.Count % 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(); - - float[] clippingVertices = clippingArea.Items; - int clippingVerticesLast = clippingArea.Count - 4; - for (int i = 0; ; i += 2) { - float edgeX = clippingVertices[i], edgeY = clippingVertices[i + 1]; - float edgeX2 = clippingVertices[i + 2], edgeY2 = clippingVertices[i + 3]; - float deltaX = edgeX - edgeX2, deltaY = edgeY - edgeY2; - - float[] inputVertices = input.Items; - int inputVerticesLength = input.Count - 2, outputStart = output.Count; - for (int ii = 0; ii < inputVerticesLength; ii += 2) { - float inputX = inputVertices[ii], inputY = inputVertices[ii + 1]; - float inputX2 = inputVertices[ii + 2], inputY2 = inputVertices[ii + 3]; - bool side2 = deltaX * (inputY2 - edgeY2) - deltaY * (inputX2 - edgeX2) > 0; - if (deltaX * (inputY - edgeY2) - deltaY * (inputX - edgeX2) > 0) { - if (side2) { // v1 inside, v2 inside - output.Add(inputX2); - output.Add(inputY2); - continue; - } - // v1 inside, v2 outside - float c0 = inputY2 - inputY, c2 = inputX2 - inputX; - float s = c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY); - if (Math.Abs(s) > 0.000001f) { - float ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / s; - output.Add(edgeX + (edgeX2 - edgeX) * ua); - output.Add(edgeY + (edgeY2 - edgeY) * ua); - } else { - output.Add(edgeX); - output.Add(edgeY); - } - } else if (side2) { // v1 outside, v2 inside - float c0 = inputY2 - inputY, c2 = inputX2 - inputX; - float s = c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY); - if (Math.Abs(s) > 0.000001f) { - float ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / s; - output.Add(edgeX + (edgeX2 - edgeX) * ua); - output.Add(edgeY + (edgeY2 - edgeY) * ua); - } else { - output.Add(edgeX); - output.Add(edgeY); - } - output.Add(inputX2); - output.Add(inputY2); - } - clipped = true; - } - - if (outputStart == output.Count) { // All edges outside. - originalOutput.Clear(); - return true; - } - - output.Add(output.Items[0]); - output.Add(output.Items[1]); - - if (i == clippingVerticesLast) break; - var temp = output; - output = input; - output.Clear(); - input = temp; - } - - if (originalOutput != output) { - originalOutput.Clear(); - for (int i = 0, n = output.Count - 2; i < n; i++) - originalOutput.Add(output.Items[i]); - } else - originalOutput.Resize(originalOutput.Count - 2); - - return clipped; - } - - public static void MakeClockwise (ExposedList polygon) { - float[] vertices = polygon.Items; - int verticeslength = polygon.Count; - - float area = vertices[verticeslength - 2] * vertices[1] - vertices[0] * vertices[verticeslength - 1], p1x, p1y, p2x, p2y; - for (int i = 0, n = verticeslength - 3; i < n; i += 2) { - p1x = vertices[i]; - p1y = vertices[i + 1]; - p2x = vertices[i + 2]; - p2y = vertices[i + 3]; - area += p1x * p2y - p2x * p1y; - } - if (area < 0) return; - - for (int i = 0, lastX = verticeslength - 2, n = verticeslength >> 1; i < n; i += 2) { - float x = vertices[i], y = vertices[i + 1]; - int other = lastX - i; - vertices[i] = vertices[other]; - vertices[i + 1] = vertices[other + 1]; - vertices[other] = x; - vertices[other + 1] = y; - } - } - } + internal bool Clip(float x1, float y1, float x2, float y2, float x3, float y3, ExposedList clippingArea, ExposedList output) + { + var originalOutput = output; + var clipped = false; + + // Avoid copy at the end. + ExposedList input = null; + if (clippingArea.Count % 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(); + + float[] clippingVertices = clippingArea.Items; + int clippingVerticesLast = clippingArea.Count - 4; + for (int i = 0; ; i += 2) + { + float edgeX = clippingVertices[i], edgeY = clippingVertices[i + 1]; + float edgeX2 = clippingVertices[i + 2], edgeY2 = clippingVertices[i + 3]; + float deltaX = edgeX - edgeX2, deltaY = edgeY - edgeY2; + + float[] inputVertices = input.Items; + int inputVerticesLength = input.Count - 2, outputStart = output.Count; + for (int ii = 0; ii < inputVerticesLength; ii += 2) + { + float inputX = inputVertices[ii], inputY = inputVertices[ii + 1]; + float inputX2 = inputVertices[ii + 2], inputY2 = inputVertices[ii + 3]; + bool side2 = deltaX * (inputY2 - edgeY2) - deltaY * (inputX2 - edgeX2) > 0; + if (deltaX * (inputY - edgeY2) - deltaY * (inputX - edgeX2) > 0) + { + if (side2) + { // v1 inside, v2 inside + output.Add(inputX2); + output.Add(inputY2); + continue; + } + // v1 inside, v2 outside + float c0 = inputY2 - inputY, c2 = inputX2 - inputX; + float s = c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY); + if (Math.Abs(s) > 0.000001f) + { + float ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / s; + output.Add(edgeX + (edgeX2 - edgeX) * ua); + output.Add(edgeY + (edgeY2 - edgeY) * ua); + } + else + { + output.Add(edgeX); + output.Add(edgeY); + } + } + else if (side2) + { // v1 outside, v2 inside + float c0 = inputY2 - inputY, c2 = inputX2 - inputX; + float s = c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY); + if (Math.Abs(s) > 0.000001f) + { + float ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / s; + output.Add(edgeX + (edgeX2 - edgeX) * ua); + output.Add(edgeY + (edgeY2 - edgeY) * ua); + } + else + { + output.Add(edgeX); + output.Add(edgeY); + } + output.Add(inputX2); + output.Add(inputY2); + } + clipped = true; + } + + if (outputStart == output.Count) + { // All edges outside. + originalOutput.Clear(); + return true; + } + + output.Add(output.Items[0]); + output.Add(output.Items[1]); + + if (i == clippingVerticesLast) break; + var temp = output; + output = input; + output.Clear(); + input = temp; + } + + if (originalOutput != output) + { + originalOutput.Clear(); + for (int i = 0, n = output.Count - 2; i < n; i++) + originalOutput.Add(output.Items[i]); + } + else + originalOutput.Resize(originalOutput.Count - 2); + + return clipped; + } + + public static void MakeClockwise(ExposedList polygon) + { + float[] vertices = polygon.Items; + int verticeslength = polygon.Count; + + float area = vertices[verticeslength - 2] * vertices[1] - vertices[0] * vertices[verticeslength - 1], p1x, p1y, p2x, p2y; + for (int i = 0, n = verticeslength - 3; i < n; i += 2) + { + p1x = vertices[i]; + p1y = vertices[i + 1]; + p2x = vertices[i + 2]; + p2y = vertices[i + 3]; + area += p1x * p2y - p2x * p1y; + } + if (area < 0) return; + + for (int i = 0, lastX = verticeslength - 2, n = verticeslength >> 1; i < n; i += 2) + { + float x = vertices[i], y = vertices[i + 1]; + int other = lastX - i; + vertices[i] = vertices[other]; + vertices[i + 1] = vertices[other + 1]; + vertices[other] = x; + vertices[other + 1] = y; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/SkeletonData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/SkeletonData.cs index 20b6b19..8ec9a35 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/SkeletonData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/SkeletonData.cs @@ -29,177 +29,194 @@ using System; -namespace Spine4_0_64 { - - /// Stores the setup pose and all of the stateless data for a skeleton. - public class SkeletonData { - internal string name; - internal ExposedList bones = new ExposedList(); // Ordered parents first - internal ExposedList slots = new ExposedList(); // Setup pose draw order. - internal ExposedList skins = new ExposedList(); - internal Skin defaultSkin; - internal ExposedList events = new ExposedList(); - internal ExposedList animations = new ExposedList(); - internal ExposedList ikConstraints = new ExposedList(); - internal ExposedList transformConstraints = new ExposedList(); - internal ExposedList pathConstraints = new ExposedList(); - internal float x, y, width, height; - internal string version, hash; - - // Nonessential. - internal float fps; - internal string imagesPath, audioPath; - - ///The skeleton's name, which by default is the name of the skeleton data file when possible, or null when a name hasn't been - ///set. - public string Name { get { return name; } set { name = value; } } - - /// The skeleton's bones, sorted parent first. The root bone is always the first bone. - public ExposedList Bones { get { return bones; } } - - public ExposedList Slots { get { return slots; } } - - /// All skins, including the default skin. - public ExposedList Skins { get { return skins; } set { skins = value; } } - - /// - /// The skeleton's default skin. - /// By default this skin contains all attachments that were not in a skin in Spine. - /// - /// May be null. - public Skin DefaultSkin { get { return defaultSkin; } set { defaultSkin = value; } } - - public ExposedList Events { get { return events; } set { events = value; } } - public ExposedList Animations { get { return animations; } set { animations = value; } } - public ExposedList IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } } - public ExposedList TransformConstraints { get { return transformConstraints; } set { transformConstraints = value; } } - public ExposedList PathConstraints { get { return pathConstraints; } set { pathConstraints = value; } } - - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public float Width { get { return width; } set { width = value; } } - public float Height { get { return height; } set { height = value; } } - /// The Spine version used to export this data, or null. - public string Version { get { return version; } set { version = value; } } - - ///The skeleton data hash. This value will change if any of the skeleton data has changed. - ///May be null. - public string Hash { get { return hash; } set { hash = value; } } - - public string ImagesPath { get { return imagesPath; } set { imagesPath = value; } } - - /// The path to the audio directory as defined in Spine. Available only when nonessential data was exported. - /// May be null. - public string AudioPath { get { return audioPath; } set { audioPath = value; } } - - /// The dopesheet FPS in Spine, or zero if nonessential data was not exported. - public float Fps { get { return fps; } set { fps = value; } } - - // --- Bones. - - /// - /// 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. - /// May be null. - public BoneData FindBone (string boneName) { - if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); - var bones = this.bones.Items; - for (int i = 0, n = this.bones.Count; i < n; i++) { - BoneData bone = bones[i]; - if (bone.name == boneName) return bone; - } - return null; - } - - // --- Slots. - - /// May be null. - public SlotData FindSlot (string slotName) { - if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); - var slots = this.slots.Items; - for (int i = 0, n = this.slots.Count; i < n; i++) { - SlotData slot = slots[i]; - if (slot.name == slotName) return slot; - } - return null; - } - - // --- Skins. - - /// May be null. - public Skin FindSkin (string skinName) { - if (skinName == null) throw new ArgumentNullException("skinName", "skinName cannot be null."); - foreach (Skin skin in skins) - if (skin.name == skinName) return skin; - return null; - } - - // --- Events. - - /// May be null. - public EventData FindEvent (string eventDataName) { - if (eventDataName == null) throw new ArgumentNullException("eventDataName", "eventDataName cannot be null."); - foreach (EventData eventData in events) - if (eventData.name == eventDataName) return eventData; - return null; - } - - // --- Animations. - - /// May be null. - public Animation FindAnimation (string animationName) { - if (animationName == null) throw new ArgumentNullException("animationName", "animationName cannot be null."); - var animations = this.animations.Items; - for (int i = 0, n = this.animations.Count; i < n; i++) { - Animation animation = animations[i]; - if (animation.name == animationName) return animation; - } - return null; - } - - // --- IK constraints. - - /// May be null. - public IkConstraintData FindIkConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - var ikConstraints = this.ikConstraints.Items; - for (int i = 0, n = this.ikConstraints.Count; i < n; i++) { - IkConstraintData ikConstraint = ikConstraints[i]; - if (ikConstraint.name == constraintName) return ikConstraint; - } - return null; - } - - // --- Transform constraints. - - /// May be null. - public TransformConstraintData FindTransformConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - var transformConstraints = this.transformConstraints.Items; - for (int i = 0, n = this.transformConstraints.Count; i < n; i++) { - TransformConstraintData transformConstraint = transformConstraints[i]; - if (transformConstraint.name == constraintName) return transformConstraint; - } - return null; - } - - // --- Path constraints. - - /// May be null. - public PathConstraintData FindPathConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - var pathConstraints = this.pathConstraints.Items; - for (int i = 0, n = this.pathConstraints.Count; i < n; i++) { - PathConstraintData constraint = pathConstraints[i]; - if (constraint.name.Equals(constraintName)) return constraint; - } - return null; - } - - // --- - - override public string ToString () { - return name ?? base.ToString(); - } - } +namespace Spine4_0_64 +{ + + /// Stores the setup pose and all of the stateless data for a skeleton. + public class SkeletonData + { + internal string name; + internal ExposedList bones = new ExposedList(); // Ordered parents first + internal ExposedList slots = new ExposedList(); // Setup pose draw order. + internal ExposedList skins = new ExposedList(); + internal Skin defaultSkin; + internal ExposedList events = new ExposedList(); + internal ExposedList animations = new ExposedList(); + internal ExposedList ikConstraints = new ExposedList(); + internal ExposedList transformConstraints = new ExposedList(); + internal ExposedList pathConstraints = new ExposedList(); + internal float x, y, width, height; + internal string version, hash; + + // Nonessential. + internal float fps; + internal string imagesPath, audioPath; + + ///The skeleton's name, which by default is the name of the skeleton data file when possible, or null when a name hasn't been + ///set. + public string Name { get { return name; } set { name = value; } } + + /// The skeleton's bones, sorted parent first. The root bone is always the first bone. + public ExposedList Bones { get { return bones; } } + + public ExposedList Slots { get { return slots; } } + + /// All skins, including the default skin. + public ExposedList Skins { get { return skins; } set { skins = value; } } + + /// + /// The skeleton's default skin. + /// By default this skin contains all attachments that were not in a skin in Spine. + /// + /// May be null. + public Skin DefaultSkin { get { return defaultSkin; } set { defaultSkin = value; } } + + public ExposedList Events { get { return events; } set { events = value; } } + public ExposedList Animations { get { return animations; } set { animations = value; } } + public ExposedList IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } } + public ExposedList TransformConstraints { get { return transformConstraints; } set { transformConstraints = value; } } + public ExposedList PathConstraints { get { return pathConstraints; } set { pathConstraints = value; } } + + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public float Width { get { return width; } set { width = value; } } + public float Height { get { return height; } set { height = value; } } + /// The Spine version used to export this data, or null. + public string Version { get { return version; } set { version = value; } } + + ///The skeleton data hash. This value will change if any of the skeleton data has changed. + ///May be null. + public string Hash { get { return hash; } set { hash = value; } } + + public string ImagesPath { get { return imagesPath; } set { imagesPath = value; } } + + /// The path to the audio directory as defined in Spine. Available only when nonessential data was exported. + /// May be null. + public string AudioPath { get { return audioPath; } set { audioPath = value; } } + + /// The dopesheet FPS in Spine, or zero if nonessential data was not exported. + public float Fps { get { return fps; } set { fps = value; } } + + // --- Bones. + + /// + /// 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. + /// May be null. + public BoneData FindBone(string boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); + var bones = this.bones.Items; + for (int i = 0, n = this.bones.Count; i < n; i++) + { + BoneData bone = bones[i]; + if (bone.name == boneName) return bone; + } + return null; + } + + // --- Slots. + + /// May be null. + public SlotData FindSlot(string slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + var slots = this.slots.Items; + for (int i = 0, n = this.slots.Count; i < n; i++) + { + SlotData slot = slots[i]; + if (slot.name == slotName) return slot; + } + return null; + } + + // --- Skins. + + /// May be null. + public Skin FindSkin(string skinName) + { + if (skinName == null) throw new ArgumentNullException("skinName", "skinName cannot be null."); + foreach (Skin skin in skins) + if (skin.name == skinName) return skin; + return null; + } + + // --- Events. + + /// May be null. + public EventData FindEvent(string eventDataName) + { + if (eventDataName == null) throw new ArgumentNullException("eventDataName", "eventDataName cannot be null."); + foreach (EventData eventData in events) + if (eventData.name == eventDataName) return eventData; + return null; + } + + // --- Animations. + + /// May be null. + public Animation FindAnimation(string animationName) + { + if (animationName == null) throw new ArgumentNullException("animationName", "animationName cannot be null."); + var animations = this.animations.Items; + for (int i = 0, n = this.animations.Count; i < n; i++) + { + Animation animation = animations[i]; + if (animation.name == animationName) return animation; + } + return null; + } + + // --- IK constraints. + + /// May be null. + public IkConstraintData FindIkConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + var ikConstraints = this.ikConstraints.Items; + for (int i = 0, n = this.ikConstraints.Count; i < n; i++) + { + IkConstraintData ikConstraint = ikConstraints[i]; + if (ikConstraint.name == constraintName) return ikConstraint; + } + return null; + } + + // --- Transform constraints. + + /// May be null. + public TransformConstraintData FindTransformConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + var transformConstraints = this.transformConstraints.Items; + for (int i = 0, n = this.transformConstraints.Count; i < n; i++) + { + TransformConstraintData transformConstraint = transformConstraints[i]; + if (transformConstraint.name == constraintName) return transformConstraint; + } + return null; + } + + // --- Path constraints. + + /// May be null. + public PathConstraintData FindPathConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + var pathConstraints = this.pathConstraints.Items; + for (int i = 0, n = this.pathConstraints.Count; i < n; i++) + { + PathConstraintData constraint = pathConstraints[i]; + if (constraint.name.Equals(constraintName)) return constraint; + } + return null; + } + + // --- + + override public string ToString() + { + return name ?? base.ToString(); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/SkeletonJson.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/SkeletonJson.cs index f9081e9..d94fe2b 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/SkeletonJson.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/SkeletonJson.cs @@ -40,26 +40,30 @@ using Windows.Storage; #endif -namespace Spine4_0_64 { - - /// - /// Loads skeleton data in the Spine JSON format. - /// - /// JSON is human readable but the binary format is much smaller on disk and faster to load. See . - /// - /// See Spine JSON format and - /// JSON and binary data in the Spine - /// Runtimes Guide. - /// - public class SkeletonJson : SkeletonLoader { - - public SkeletonJson (AttachmentLoader attachmentLoader) - : base(attachmentLoader) { - } - - public SkeletonJson (params Atlas[] atlasArray) - : base(atlasArray) { - } +namespace Spine4_0_64 +{ + + /// + /// Loads skeleton data in the Spine JSON format. + /// + /// JSON is human readable but the binary format is much smaller on disk and faster to load. See . + /// + /// See Spine JSON format and + /// JSON and binary data in the Spine + /// Runtimes Guide. + /// + public class SkeletonJson : SkeletonLoader + { + + public SkeletonJson(AttachmentLoader attachmentLoader) + : base(attachmentLoader) + { + } + + public SkeletonJson(params Atlas[] atlasArray) + : base(atlasArray) + { + } #if !IS_UNITY && WINDOWS_STOREAPP private async Task ReadFile(string path) { @@ -76,1118 +80,1289 @@ public override SkeletonData ReadSkeletonData (string path) { return this.ReadFile(path).Result; } #else - public override SkeletonData ReadSkeletonData (string path) { + public override SkeletonData ReadSkeletonData(string path) + { #if WINDOWS_PHONE using (var reader = new StreamReader(Microsoft.Xna.Framework.TitleContainer.OpenStream(path))) { #else - using (var reader = new StreamReader(new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))) { + using (var reader = new StreamReader(new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))) + { #endif - SkeletonData skeletonData = ReadSkeletonData(reader); - skeletonData.name = Path.GetFileNameWithoutExtension(path); - return skeletonData; - } - } + SkeletonData skeletonData = ReadSkeletonData(reader); + skeletonData.name = Path.GetFileNameWithoutExtension(path); + return skeletonData; + } + } #endif - public SkeletonData ReadSkeletonData (TextReader reader) { - if (reader == null) throw new ArgumentNullException("reader", "reader cannot be null."); - - float scale = this.scale; - var skeletonData = new SkeletonData(); - - var root = Json.Deserialize(reader) as Dictionary; - if (root == null) throw new Exception("Invalid JSON."); - - // Skeleton. - if (root.ContainsKey("skeleton")) { - var skeletonMap = (Dictionary)root["skeleton"]; - skeletonData.hash = (string)skeletonMap["hash"]; - skeletonData.version = (string)skeletonMap["spine"]; - skeletonData.x = GetFloat(skeletonMap, "x", 0); - skeletonData.y = GetFloat(skeletonMap, "y", 0); - skeletonData.width = GetFloat(skeletonMap, "width", 0); - skeletonData.height = GetFloat(skeletonMap, "height", 0); - skeletonData.fps = GetFloat(skeletonMap, "fps", 30); - skeletonData.imagesPath = GetString(skeletonMap, "images", null); - skeletonData.audioPath = GetString(skeletonMap, "audio", null); - } - - // Bones. - if (root.ContainsKey("bones")) { - foreach (Dictionary boneMap in (List)root["bones"]) { - BoneData parent = null; - if (boneMap.ContainsKey("parent")) { - parent = skeletonData.FindBone((string)boneMap["parent"]); - if (parent == null) - throw new Exception("Parent bone not found: " + boneMap["parent"]); - } - var data = new BoneData(skeletonData.Bones.Count, (string)boneMap["name"], parent); - data.length = GetFloat(boneMap, "length", 0) * scale; - data.x = GetFloat(boneMap, "x", 0) * scale; - data.y = GetFloat(boneMap, "y", 0) * scale; - data.rotation = GetFloat(boneMap, "rotation", 0); - data.scaleX = GetFloat(boneMap, "scaleX", 1); - data.scaleY = GetFloat(boneMap, "scaleY", 1); - data.shearX = GetFloat(boneMap, "shearX", 0); - data.shearY = GetFloat(boneMap, "shearY", 0); - - string tm = GetString(boneMap, "transform", TransformMode.Normal.ToString()); - data.transformMode = (TransformMode)Enum.Parse(typeof(TransformMode), tm, true); - data.skinRequired = GetBoolean(boneMap, "skin", false); - - skeletonData.bones.Add(data); - } - } - - // Slots. - if (root.ContainsKey("slots")) { - foreach (Dictionary slotMap in (List)root["slots"]) { - var slotName = (string)slotMap["name"]; - var boneName = (string)slotMap["bone"]; - BoneData boneData = skeletonData.FindBone(boneName); - if (boneData == null) throw new Exception("Slot bone not found: " + boneName); - var data = new SlotData(skeletonData.Slots.Count, slotName, boneData); - - if (slotMap.ContainsKey("color")) { - string color = (string)slotMap["color"]; - data.r = ToColor(color, 0); - data.g = ToColor(color, 1); - data.b = ToColor(color, 2); - data.a = ToColor(color, 3); - } - - if (slotMap.ContainsKey("dark")) { - var color2 = (string)slotMap["dark"]; - data.r2 = ToColor(color2, 0, 6); // expectedLength = 6. ie. "RRGGBB" - data.g2 = ToColor(color2, 1, 6); - data.b2 = ToColor(color2, 2, 6); - data.hasSecondColor = true; - } - - data.attachmentName = GetString(slotMap, "attachment", null); - if (slotMap.ContainsKey("blend")) - data.blendMode = (BlendMode)Enum.Parse(typeof(BlendMode), (string)slotMap["blend"], true); - else - data.blendMode = BlendMode.Normal; - skeletonData.slots.Add(data); - } - } - - // IK constraints. - if (root.ContainsKey("ik")) { - foreach (Dictionary constraintMap in (List)root["ik"]) { - IkConstraintData data = new IkConstraintData((string)constraintMap["name"]); - data.order = GetInt(constraintMap, "order", 0); - data.skinRequired = GetBoolean(constraintMap, "skin", false); - - if (constraintMap.ContainsKey("bones")) { - foreach (string boneName in (List)constraintMap["bones"]) { - BoneData bone = skeletonData.FindBone(boneName); - if (bone == null) throw new Exception("IK bone not found: " + boneName); - data.bones.Add(bone); - } - } - - string targetName = (string)constraintMap["target"]; - data.target = skeletonData.FindBone(targetName); - if (data.target == null) throw new Exception("IK target bone not found: " + targetName); - data.mix = GetFloat(constraintMap, "mix", 1); - data.softness = GetFloat(constraintMap, "softness", 0) * scale; - data.bendDirection = GetBoolean(constraintMap, "bendPositive", true) ? 1 : -1; - data.compress = GetBoolean(constraintMap, "compress", false); - data.stretch = GetBoolean(constraintMap, "stretch", false); - data.uniform = GetBoolean(constraintMap, "uniform", false); - - skeletonData.ikConstraints.Add(data); - } - } - - // Transform constraints. - if (root.ContainsKey("transform")) { - foreach (Dictionary constraintMap in (List)root["transform"]) { - TransformConstraintData data = new TransformConstraintData((string)constraintMap["name"]); - data.order = GetInt(constraintMap, "order", 0); - data.skinRequired = GetBoolean(constraintMap, "skin", false); - - if (constraintMap.ContainsKey("bones")) { - foreach (string boneName in (List)constraintMap["bones"]) { - BoneData bone = skeletonData.FindBone(boneName); - if (bone == null) throw new Exception("Transform constraint bone not found: " + boneName); - data.bones.Add(bone); - } - } - - string targetName = (string)constraintMap["target"]; - data.target = skeletonData.FindBone(targetName); - if (data.target == null) throw new Exception("Transform constraint target bone not found: " + targetName); - - data.local = GetBoolean(constraintMap, "local", false); - data.relative = GetBoolean(constraintMap, "relative", false); - - data.offsetRotation = GetFloat(constraintMap, "rotation", 0); - data.offsetX = GetFloat(constraintMap, "x", 0) * scale; - data.offsetY = GetFloat(constraintMap, "y", 0) * scale; - data.offsetScaleX = GetFloat(constraintMap, "scaleX", 0); - data.offsetScaleY = GetFloat(constraintMap, "scaleY", 0); - data.offsetShearY = GetFloat(constraintMap, "shearY", 0); - - data.mixRotate = GetFloat(constraintMap, "mixRotate", 1); - data.mixX = GetFloat(constraintMap, "mixX", 1); - data.mixY = GetFloat(constraintMap, "mixY", data.mixX); - data.mixScaleX = GetFloat(constraintMap, "mixScaleX", 1); - data.mixScaleY = GetFloat(constraintMap, "mixScaleY", data.mixScaleX); - data.mixShearY = GetFloat(constraintMap, "mixShearY", 1); - - skeletonData.transformConstraints.Add(data); - } - } - - // Path constraints. - if (root.ContainsKey("path")) { - foreach (Dictionary constraintMap in (List)root["path"]) { - PathConstraintData data = new PathConstraintData((string)constraintMap["name"]); - data.order = GetInt(constraintMap, "order", 0); - data.skinRequired = GetBoolean(constraintMap, "skin", false); - - if (constraintMap.ContainsKey("bones")) { - foreach (string boneName in (List)constraintMap["bones"]) { - BoneData bone = skeletonData.FindBone(boneName); - if (bone == null) throw new Exception("Path bone not found: " + boneName); - data.bones.Add(bone); - } - } - - string targetName = (string)constraintMap["target"]; - data.target = skeletonData.FindSlot(targetName); - if (data.target == null) throw new Exception("Path target slot not found: " + targetName); - - data.positionMode = (PositionMode)Enum.Parse(typeof(PositionMode), GetString(constraintMap, "positionMode", "percent"), true); - data.spacingMode = (SpacingMode)Enum.Parse(typeof(SpacingMode), GetString(constraintMap, "spacingMode", "length"), true); - data.rotateMode = (RotateMode)Enum.Parse(typeof(RotateMode), GetString(constraintMap, "rotateMode", "tangent"), true); - data.offsetRotation = GetFloat(constraintMap, "rotation", 0); - data.position = GetFloat(constraintMap, "position", 0); - if (data.positionMode == PositionMode.Fixed) data.position *= scale; - data.spacing = GetFloat(constraintMap, "spacing", 0); - if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale; - data.mixRotate = GetFloat(constraintMap, "mixRotate", 1); - data.mixX = GetFloat(constraintMap, "mixX", 1); - data.mixY = GetFloat(constraintMap, "mixY", data.mixX); - - skeletonData.pathConstraints.Add(data); - } - } - - // Skins. - if (root.ContainsKey("skins")) { - foreach (Dictionary skinMap in (List)root["skins"]) { - Skin skin = new Skin((string)skinMap["name"]); - if (skinMap.ContainsKey("bones")) { - foreach (string entryName in (List)skinMap["bones"]) { - BoneData bone = skeletonData.FindBone(entryName); - if (bone == null) throw new Exception("Skin bone not found: " + entryName); - skin.bones.Add(bone); - } - } - skin.bones.TrimExcess(); - if (skinMap.ContainsKey("ik")) { - foreach (string entryName in (List)skinMap["ik"]) { - IkConstraintData constraint = skeletonData.FindIkConstraint(entryName); - if (constraint == null) throw new Exception("Skin IK constraint not found: " + entryName); - skin.constraints.Add(constraint); - } - } - if (skinMap.ContainsKey("transform")) { - foreach (string entryName in (List)skinMap["transform"]) { - TransformConstraintData constraint = skeletonData.FindTransformConstraint(entryName); - if (constraint == null) throw new Exception("Skin transform constraint not found: " + entryName); - skin.constraints.Add(constraint); - } - } - if (skinMap.ContainsKey("path")) { - foreach (string entryName in (List)skinMap["path"]) { - PathConstraintData constraint = skeletonData.FindPathConstraint(entryName); - if (constraint == null) throw new Exception("Skin path constraint not found: " + entryName); - skin.constraints.Add(constraint); - } - } - skin.constraints.TrimExcess(); - if (skinMap.ContainsKey("attachments")) { - foreach (KeyValuePair slotEntry in (Dictionary)skinMap["attachments"]) { - int slotIndex = FindSlotIndex(skeletonData, slotEntry.Key); - foreach (KeyValuePair entry in ((Dictionary)slotEntry.Value)) { - try { - Attachment attachment = ReadAttachment((Dictionary)entry.Value, skin, slotIndex, entry.Key, skeletonData); - if (attachment != null) skin.SetAttachment(slotIndex, entry.Key, attachment); - } catch (Exception e) { - throw new Exception("Error reading attachment: " + entry.Key + ", skin: " + skin, e); - } - } - } - } - skeletonData.skins.Add(skin); - if (skin.name == "default") skeletonData.defaultSkin = skin; - } - } - - // Linked meshes. - for (int i = 0, n = linkedMeshes.Count; i < n; i++) { - LinkedMesh linkedMesh = linkedMeshes[i]; - Skin skin = linkedMesh.skin == null ? skeletonData.defaultSkin : skeletonData.FindSkin(linkedMesh.skin); - if (skin == null) throw new Exception("Slot not found: " + linkedMesh.skin); - Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); - if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); - linkedMesh.mesh.DeformAttachment = linkedMesh.inheritDeform ? (VertexAttachment)parent : linkedMesh.mesh; - linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; - linkedMesh.mesh.UpdateUVs(); - } - linkedMeshes.Clear(); - - // Events. - if (root.ContainsKey("events")) { - foreach (KeyValuePair entry in (Dictionary)root["events"]) { - var entryMap = (Dictionary)entry.Value; - var data = new EventData(entry.Key); - data.Int = GetInt(entryMap, "int", 0); - data.Float = GetFloat(entryMap, "float", 0); - data.String = GetString(entryMap, "string", string.Empty); - data.AudioPath = GetString(entryMap, "audio", null); - if (data.AudioPath != null) { - data.Volume = GetFloat(entryMap, "volume", 1); - data.Balance = GetFloat(entryMap, "balance", 0); - } - skeletonData.events.Add(data); - } - } - - // Animations. - if (root.ContainsKey("animations")) { - foreach (KeyValuePair entry in (Dictionary)root["animations"]) { - try { - ReadAnimation((Dictionary)entry.Value, entry.Key, skeletonData); - } catch (Exception e) { - throw new Exception("Error reading animation: " + entry.Key + "\n" + e.Message, e); - } - } - } - - skeletonData.bones.TrimExcess(); - skeletonData.slots.TrimExcess(); - skeletonData.skins.TrimExcess(); - skeletonData.events.TrimExcess(); - skeletonData.animations.TrimExcess(); - skeletonData.ikConstraints.TrimExcess(); - return skeletonData; - } - - private Attachment ReadAttachment (Dictionary map, Skin skin, int slotIndex, string name, SkeletonData skeletonData) { - float scale = this.scale; - name = GetString(map, "name", name); - - var typeName = GetString(map, "type", "region"); - var type = (AttachmentType)Enum.Parse(typeof(AttachmentType), typeName, true); - - string path = GetString(map, "path", name); - - switch (type) { - case AttachmentType.Region: - RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); - if (region == null) return null; - region.Path = path; - region.x = GetFloat(map, "x", 0) * scale; - region.y = GetFloat(map, "y", 0) * scale; - region.scaleX = GetFloat(map, "scaleX", 1); - region.scaleY = GetFloat(map, "scaleY", 1); - region.rotation = GetFloat(map, "rotation", 0); - region.width = GetFloat(map, "width", 32) * scale; - region.height = GetFloat(map, "height", 32) * scale; - - if (map.ContainsKey("color")) { - var color = (string)map["color"]; - region.r = ToColor(color, 0); - region.g = ToColor(color, 1); - region.b = ToColor(color, 2); - region.a = ToColor(color, 3); - } - - region.UpdateOffset(); - return region; - case AttachmentType.Boundingbox: - BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); - if (box == null) return null; - ReadVertices(map, box, GetInt(map, "vertexCount", 0) << 1); - return box; - case AttachmentType.Mesh: - case AttachmentType.Linkedmesh: { - MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); - if (mesh == null) return null; - mesh.Path = path; - - if (map.ContainsKey("color")) { - var color = (string)map["color"]; - mesh.r = ToColor(color, 0); - mesh.g = ToColor(color, 1); - mesh.b = ToColor(color, 2); - mesh.a = ToColor(color, 3); - } - - mesh.Width = GetFloat(map, "width", 0) * scale; - mesh.Height = GetFloat(map, "height", 0) * scale; - - string parent = GetString(map, "parent", null); - if (parent != null) { - linkedMeshes.Add(new LinkedMesh(mesh, GetString(map, "skin", null), slotIndex, parent, GetBoolean(map, "deform", true))); - return mesh; - } - - float[] uvs = GetFloatArray(map, "uvs", 1); - ReadVertices(map, mesh, uvs.Length); - mesh.triangles = GetIntArray(map, "triangles"); - mesh.regionUVs = uvs; - mesh.UpdateUVs(); - - if (map.ContainsKey("hull")) mesh.HullLength = GetInt(map, "hull", 0) << 1; - if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges"); - return mesh; - } - case AttachmentType.Path: { - PathAttachment pathAttachment = attachmentLoader.NewPathAttachment(skin, name); - if (pathAttachment == null) return null; - pathAttachment.closed = GetBoolean(map, "closed", false); - pathAttachment.constantSpeed = GetBoolean(map, "constantSpeed", true); - - int vertexCount = GetInt(map, "vertexCount", 0); - ReadVertices(map, pathAttachment, vertexCount << 1); - - // potential BOZO see Java impl - pathAttachment.lengths = GetFloatArray(map, "lengths", scale); - return pathAttachment; - } - case AttachmentType.Point: { - PointAttachment point = attachmentLoader.NewPointAttachment(skin, name); - if (point == null) return null; - point.x = GetFloat(map, "x", 0) * scale; - point.y = GetFloat(map, "y", 0) * scale; - point.rotation = GetFloat(map, "rotation", 0); - - //string color = GetString(map, "color", null); - //if (color != null) point.color = color; - return point; - } - case AttachmentType.Clipping: { - ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name); - if (clip == null) return null; - - string end = GetString(map, "end", null); - if (end != null) { - SlotData slot = skeletonData.FindSlot(end); - if (slot == null) throw new Exception("Clipping end slot not found: " + end); - clip.EndSlot = slot; - } - - ReadVertices(map, clip, GetInt(map, "vertexCount", 0) << 1); - - //string color = GetString(map, "color", null); - // if (color != null) clip.color = color; - return clip; - } - } - return null; - } - - private void ReadVertices (Dictionary map, VertexAttachment attachment, int verticesLength) { - attachment.WorldVerticesLength = verticesLength; - float[] vertices = GetFloatArray(map, "vertices", 1); - float scale = Scale; - if (verticesLength == vertices.Length) { - if (scale != 1) { - for (int i = 0; i < vertices.Length; i++) { - vertices[i] *= scale; - } - } - attachment.vertices = vertices; - return; - } - ExposedList weights = new ExposedList(verticesLength * 3 * 3); - ExposedList bones = new ExposedList(verticesLength * 3); - for (int i = 0, n = vertices.Length; i < n;) { - int boneCount = (int)vertices[i++]; - bones.Add(boneCount); - for (int nn = i + (boneCount << 2); i < nn; i += 4) { - bones.Add((int)vertices[i]); - weights.Add(vertices[i + 1] * this.Scale); - weights.Add(vertices[i + 2] * this.Scale); - weights.Add(vertices[i + 3]); - } - } - attachment.bones = bones.ToArray(); - attachment.vertices = weights.ToArray(); - } - - private int FindSlotIndex (SkeletonData skeletonData, string slotName) { - SlotData[] slots = skeletonData.slots.Items; - for (int i = 0, n = skeletonData.slots.Count; i < n; i++) - if (slots[i].name == slotName) return i; - throw new Exception("Slot not found: " + slotName); - } - - private void ReadAnimation (Dictionary map, string name, SkeletonData skeletonData) { - var scale = this.scale; - var timelines = new ExposedList(); - - // Slot timelines. - if (map.ContainsKey("slots")) { - foreach (KeyValuePair entry in (Dictionary)map["slots"]) { - string slotName = entry.Key; - int slotIndex = FindSlotIndex(skeletonData, slotName); - var timelineMap = (Dictionary)entry.Value; - foreach (KeyValuePair timelineEntry in timelineMap) { - var values = (List)timelineEntry.Value; - int frames = values.Count; - if (frames == 0) continue; - var timelineName = (string)timelineEntry.Key; - if (timelineName == "attachment") { - var timeline = new AttachmentTimeline(frames, slotIndex); - int frame = 0; - foreach (Dictionary keyMap in values) { - timeline.SetFrame(frame++, GetFloat(keyMap, "time", 0), (string)keyMap["name"]); - } - timelines.Add(timeline); - - } else if (timelineName == "rgba") { - var timeline = new RGBATimeline(frames, frames << 2, slotIndex); - - var keyMapEnumerator = values.GetEnumerator(); - keyMapEnumerator.MoveNext(); - var keyMap = (Dictionary)keyMapEnumerator.Current; - float time = GetFloat(keyMap, "time", 0); - string color = (string)keyMap["color"]; - float r = ToColor(color, 0); - float g = ToColor(color, 1); - float b = ToColor(color, 2); - float a = ToColor(color, 3); - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, r, g, b, a); - if (!keyMapEnumerator.MoveNext()) { - timeline.Shrink(bezier); - break; - } - var nextMap = (Dictionary)keyMapEnumerator.Current; - - float time2 = GetFloat(nextMap, "time", 0); - color = (string)nextMap["color"]; - float nr = ToColor(color, 0); - float ng = ToColor(color, 1); - float nb = ToColor(color, 2); - float na = ToColor(color, 3); - - if (keyMap.ContainsKey("curve")) { - object curve = keyMap["curve"]; - bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1); - bezier = ReadCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1); - bezier = ReadCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1); - bezier = ReadCurve(curve, timeline, bezier, frame, 3, time, time2, a, na, 1); - } - time = time2; - r = nr; - g = ng; - b = nb; - a = na; - keyMap = nextMap; - } - timelines.Add(timeline); - - } else if (timelineName == "rgb") { - var timeline = new RGBTimeline(frames, frames * 3, slotIndex); - - var keyMapEnumerator = values.GetEnumerator(); - keyMapEnumerator.MoveNext(); - var keyMap = (Dictionary)keyMapEnumerator.Current; - float time = GetFloat(keyMap, "time", 0); - string color = (string)keyMap["color"]; - float r = ToColor(color, 0, 6); - float g = ToColor(color, 1, 6); - float b = ToColor(color, 2, 6); - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, r, g, b); - if (!keyMapEnumerator.MoveNext()) { - timeline.Shrink(bezier); - break; - } - var nextMap = (Dictionary)keyMapEnumerator.Current; - - float time2 = GetFloat(nextMap, "time", 0); - color = (string)nextMap["color"]; - float nr = ToColor(color, 0, 6); - float ng = ToColor(color, 1, 6); - float nb = ToColor(color, 2, 6); - - if (keyMap.ContainsKey("curve")) { - object curve = keyMap["curve"]; - bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1); - bezier = ReadCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1); - bezier = ReadCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1); - } - time = time2; - r = nr; - g = ng; - b = nb; - keyMap = nextMap; - } - timelines.Add(timeline); - - } else if (timelineName == "alpha") { - var keyMapEnumerator = values.GetEnumerator(); - keyMapEnumerator.MoveNext(); - timelines.Add(ReadTimeline(ref keyMapEnumerator, new AlphaTimeline(frames, frames, slotIndex), 0, 1)); - - } else if (timelineName == "rgba2") { - var timeline = new RGBA2Timeline(frames, frames * 7, slotIndex); - - var keyMapEnumerator = values.GetEnumerator(); - keyMapEnumerator.MoveNext(); - var keyMap = (Dictionary)keyMapEnumerator.Current; - float time = GetFloat(keyMap, "time", 0); - string color = (string)keyMap["light"]; - float r = ToColor(color, 0); - float g = ToColor(color, 1); - float b = ToColor(color, 2); - float a = ToColor(color, 3); - color = (string)keyMap["dark"]; - float r2 = ToColor(color, 0, 6); - float g2 = ToColor(color, 1, 6); - float b2 = ToColor(color, 2, 6); - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, r, g, b, a, r2, g2, b2); - if (!keyMapEnumerator.MoveNext()) { - timeline.Shrink(bezier); - break; - } - var nextMap = (Dictionary)keyMapEnumerator.Current; - - float time2 = GetFloat(nextMap, "time", 0); - color = (string)nextMap["light"]; - float nr = ToColor(color, 0); - float ng = ToColor(color, 1); - float nb = ToColor(color, 2); - float na = ToColor(color, 3); - color = (string)nextMap["dark"]; - float nr2 = ToColor(color, 0, 6); - float ng2 = ToColor(color, 1, 6); - float nb2 = ToColor(color, 2, 6); - - if (keyMap.ContainsKey("curve")) { - object curve = keyMap["curve"]; - bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1); - bezier = ReadCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1); - bezier = ReadCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1); - bezier = ReadCurve(curve, timeline, bezier, frame, 3, time, time2, a, na, 1); - bezier = ReadCurve(curve, timeline, bezier, frame, 4, time, time2, r2, nr2, 1); - bezier = ReadCurve(curve, timeline, bezier, frame, 5, time, time2, g2, ng2, 1); - bezier = ReadCurve(curve, 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; - keyMap = nextMap; - } - timelines.Add(timeline); - - } else if (timelineName == "rgb2") { - var timeline = new RGB2Timeline(frames, frames * 6, slotIndex); - - var keyMapEnumerator = values.GetEnumerator(); - keyMapEnumerator.MoveNext(); - var keyMap = (Dictionary)keyMapEnumerator.Current; - float time = GetFloat(keyMap, "time", 0); - string color = (string)keyMap["light"]; - float r = ToColor(color, 0, 6); - float g = ToColor(color, 1, 6); - float b = ToColor(color, 2, 6); - color = (string)keyMap["dark"]; - float r2 = ToColor(color, 0, 6); - float g2 = ToColor(color, 1, 6); - float b2 = ToColor(color, 2, 6); - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, r, g, b, r2, g2, b2); - if (!keyMapEnumerator.MoveNext()) { - timeline.Shrink(bezier); - break; - } - var nextMap = (Dictionary)keyMapEnumerator.Current; - - float time2 = GetFloat(nextMap, "time", 0); - color = (string)nextMap["light"]; - float nr = ToColor(color, 0, 6); - float ng = ToColor(color, 1, 6); - float nb = ToColor(color, 2, 6); - color = (string)nextMap["dark"]; - float nr2 = ToColor(color, 0, 6); - float ng2 = ToColor(color, 1, 6); - float nb2 = ToColor(color, 2, 6); - - if (keyMap.ContainsKey("curve")) { - object curve = keyMap["curve"]; - bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1); - bezier = ReadCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1); - bezier = ReadCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1); - bezier = ReadCurve(curve, timeline, bezier, frame, 3, time, time2, r2, nr2, 1); - bezier = ReadCurve(curve, timeline, bezier, frame, 4, time, time2, g2, ng2, 1); - bezier = ReadCurve(curve, timeline, bezier, frame, 5, time, time2, b2, nb2, 1); - } - time = time2; - r = nr; - g = ng; - b = nb; - r2 = nr2; - g2 = ng2; - b2 = nb2; - keyMap = nextMap; - } - timelines.Add(timeline); - - } else - throw new Exception("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")"); - } - } - } - - // Bone timelines. - if (map.ContainsKey("bones")) { - foreach (KeyValuePair entry in (Dictionary)map["bones"]) { - string boneName = entry.Key; - int boneIndex = -1; - var bones = skeletonData.bones.Items; - for (int i = 0, n = skeletonData.bones.Count; i < n; i++) { - if (bones[i].name == boneName) { - boneIndex = i; - break; - } - } - if (boneIndex == -1) throw new Exception("Bone not found: " + boneName); - var timelineMap = (Dictionary)entry.Value; - foreach (KeyValuePair timelineEntry in timelineMap) { - var values = (List)timelineEntry.Value; - var keyMapEnumerator = values.GetEnumerator(); - if (!keyMapEnumerator.MoveNext()) continue; - int frames = values.Count; - var timelineName = (string)timelineEntry.Key; - if (timelineName == "rotate") - timelines.Add(ReadTimeline(ref keyMapEnumerator, new RotateTimeline(frames, frames, boneIndex), 0, 1)); - else if (timelineName == "translate") { - TranslateTimeline timeline = new TranslateTimeline(frames, frames << 1, boneIndex); - timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, "x", "y", 0, scale)); - } else if (timelineName == "translatex") { - timelines - .Add(ReadTimeline(ref keyMapEnumerator, new TranslateXTimeline(frames, frames, boneIndex), 0, scale)); - } else if (timelineName == "translatey") { - timelines - .Add(ReadTimeline(ref keyMapEnumerator, new TranslateYTimeline(frames, frames, boneIndex), 0, scale)); - } else if (timelineName == "scale") { - ScaleTimeline timeline = new ScaleTimeline(frames, frames << 1, boneIndex); - timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, "x", "y", 1, 1)); - } else if (timelineName == "scalex") - timelines.Add(ReadTimeline(ref keyMapEnumerator, new ScaleXTimeline(frames, frames, boneIndex), 1, 1)); - else if (timelineName == "scaley") - timelines.Add(ReadTimeline(ref keyMapEnumerator, new ScaleYTimeline(frames, frames, boneIndex), 1, 1)); - else if (timelineName == "shear") { - ShearTimeline timeline = new ShearTimeline(frames, frames << 1, boneIndex); - timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, "x", "y", 0, 1)); - } else if (timelineName == "shearx") - timelines.Add(ReadTimeline(ref keyMapEnumerator, new ShearXTimeline(frames, frames, boneIndex), 0, 1)); - else if (timelineName == "sheary") - timelines.Add(ReadTimeline(ref keyMapEnumerator, new ShearYTimeline(frames, frames, boneIndex), 0, 1)); - else - throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")"); - } - } - } - - // IK constraint timelines. - if (map.ContainsKey("ik")) { - foreach (KeyValuePair timelineMap in (Dictionary)map["ik"]) { - var values = (List)timelineMap.Value; - var keyMapEnumerator = values.GetEnumerator(); - if (!keyMapEnumerator.MoveNext()) continue; - var keyMap = (Dictionary)keyMapEnumerator.Current; - IkConstraintData constraint = skeletonData.FindIkConstraint(timelineMap.Key); - IkConstraintTimeline timeline = new IkConstraintTimeline(values.Count, values.Count << 1, - skeletonData.IkConstraints.IndexOf(constraint)); - float time = GetFloat(keyMap, "time", 0); - float mix = GetFloat(keyMap, "mix", 1), softness = GetFloat(keyMap, "softness", 0) * scale; - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, mix, softness, GetBoolean(keyMap, "bendPositive", true) ? 1 : -1, - GetBoolean(keyMap, "compress", false), GetBoolean(keyMap, "stretch", false)); - if (!keyMapEnumerator.MoveNext()) { - timeline.Shrink(bezier); - break; - } - var nextMap = (Dictionary)keyMapEnumerator.Current; - float time2 = GetFloat(nextMap, "time", 0); - float mix2 = GetFloat(nextMap, "mix", 1), softness2 = GetFloat(nextMap, "softness", 0) * scale; - if (keyMap.ContainsKey("curve")) { - object curve = keyMap["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. - if (map.ContainsKey("transform")) { - foreach (KeyValuePair timelineMap in (Dictionary)map["transform"]) { - var values = (List)timelineMap.Value; - var keyMapEnumerator = values.GetEnumerator(); - if (!keyMapEnumerator.MoveNext()) continue; - var keyMap = (Dictionary)keyMapEnumerator.Current; - TransformConstraintData constraint = skeletonData.FindTransformConstraint(timelineMap.Key); - TransformConstraintTimeline timeline = new TransformConstraintTimeline(values.Count, values.Count * 6, - skeletonData.TransformConstraints.IndexOf(constraint)); - float time = GetFloat(keyMap, "time", 0); - float mixRotate = GetFloat(keyMap, "mixRotate", 1), mixShearY = GetFloat(keyMap, "mixShearY", 1); - float mixX = GetFloat(keyMap, "mixX", 1), mixY = GetFloat(keyMap, "mixY", mixX); - float mixScaleX = GetFloat(keyMap, "mixScaleX", 1), mixScaleY = GetFloat(keyMap, "mixScaleY", mixScaleX); - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY); - if (!keyMapEnumerator.MoveNext()) { - timeline.Shrink(bezier); - break; - } - var nextMap = (Dictionary)keyMapEnumerator.Current; - float time2 = GetFloat(nextMap, "time", 0); - float mixRotate2 = GetFloat(nextMap, "mixRotate", 1), mixShearY2 = GetFloat(nextMap, "mixShearY", 1); - float mixX2 = GetFloat(nextMap, "mixX", 1), mixY2 = GetFloat(nextMap, "mixY", mixX2); - float mixScaleX2 = GetFloat(nextMap, "mixScaleX", 1), mixScaleY2 = GetFloat(nextMap, "mixScaleY", mixScaleX2); - if (keyMap.ContainsKey("curve")) { - object curve = keyMap["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; - mixScaleX = mixScaleX2; - keyMap = nextMap; - } - timelines.Add(timeline); - } - } - - // Path constraint timelines. - if (map.ContainsKey("path")) { - foreach (KeyValuePair constraintMap in (Dictionary)map["path"]) { - PathConstraintData constraint = skeletonData.FindPathConstraint(constraintMap.Key); - if (constraint == null) throw new Exception("Path constraint not found: " + constraintMap.Key); - int constraintIndex = skeletonData.pathConstraints.IndexOf(constraint); - var timelineMap = (Dictionary)constraintMap.Value; - foreach (KeyValuePair timelineEntry in timelineMap) { - var values = (List)timelineEntry.Value; - var keyMapEnumerator = values.GetEnumerator(); - if (!keyMapEnumerator.MoveNext()) continue; - - int frames = values.Count; - var timelineName = (string)timelineEntry.Key; - if (timelineName == "position") { - CurveTimeline1 timeline = new PathConstraintPositionTimeline(frames, frames, constraintIndex); - timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, 0, constraint.positionMode == PositionMode.Fixed ? scale : 1)); - } else if (timelineName == "spacing") { - CurveTimeline1 timeline = new PathConstraintSpacingTimeline(frames, frames, constraintIndex); - timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, 0, - constraint.spacingMode == SpacingMode.Length || constraint.spacingMode == SpacingMode.Fixed ? scale : 1)); - } else if (timelineName == "mix") { - PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(frames, frames * 3, constraintIndex); - var keyMap = (Dictionary)keyMapEnumerator.Current; - float time = GetFloat(keyMap, "time", 0); - float mixRotate = GetFloat(keyMap, "mixRotate", 1); - float mixX = GetFloat(keyMap, "mixX", 1), mixY = GetFloat(keyMap, "mixY", mixX); - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, mixRotate, mixX, mixY); - if (!keyMapEnumerator.MoveNext()) { - timeline.Shrink(bezier); - break; - } - var nextMap = (Dictionary)keyMapEnumerator.Current; - float time2 = GetFloat(nextMap, "time", 0); - float mixRotate2 = GetFloat(nextMap, "mixRotate", 1); - float mixX2 = GetFloat(nextMap, "mixX", 1), mixY2 = GetFloat(nextMap, "mixY", mixX2); - if (keyMap.ContainsKey("curve")) { - object curve = keyMap["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); - } - time = time2; - mixRotate = mixRotate2; - mixX = mixX2; - mixY = mixY2; - keyMap = nextMap; - } - timelines.Add(timeline); - } - } - } - } - - // Deform timelines. - if (map.ContainsKey("deform")) { - foreach (KeyValuePair deformMap in (Dictionary)map["deform"]) { - Skin skin = skeletonData.FindSkin(deformMap.Key); - foreach (KeyValuePair slotMap in (Dictionary)deformMap.Value) { - int slotIndex = FindSlotIndex(skeletonData, slotMap.Key); - foreach (KeyValuePair timelineMap in (Dictionary)slotMap.Value) { - var values = (List)timelineMap.Value; - var keyMapEnumerator = values.GetEnumerator(); - if (!keyMapEnumerator.MoveNext()) continue; - var keyMap = (Dictionary)keyMapEnumerator.Current; - VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, timelineMap.Key); - if (attachment == null) throw new Exception("Deform attachment not found: " + timelineMap.Key); - bool weighted = attachment.bones != null; - float[] vertices = attachment.vertices; - int deformLength = weighted ? (vertices.Length / 3) << 1 : vertices.Length; - DeformTimeline timeline = new DeformTimeline(values.Count, values.Count, slotIndex, attachment); - float time = GetFloat(keyMap, "time", 0); - for (int frame = 0, bezier = 0; ; frame++) { - float[] deform; - if (!keyMap.ContainsKey("vertices")) { - deform = weighted ? new float[deformLength] : vertices; - } else { - deform = new float[deformLength]; - int start = GetInt(keyMap, "offset", 0); - float[] verticesValue = GetFloatArray(keyMap, "vertices", 1); - Array.Copy(verticesValue, 0, deform, start, verticesValue.Length); - if (scale != 1) { - for (int i = start, n = i + verticesValue.Length; i < n; i++) - deform[i] *= scale; - } - - if (!weighted) { - for (int i = 0; i < deformLength; i++) - deform[i] += vertices[i]; - } - } - - timeline.SetFrame(frame, time, deform); - if (!keyMapEnumerator.MoveNext()) { - timeline.Shrink(bezier); - break; - } - var nextMap = (Dictionary)keyMapEnumerator.Current; - float time2 = GetFloat(nextMap, "time", 0); - if (keyMap.ContainsKey("curve")) { - object curve = keyMap["curve"]; - bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, 0, 1, 1); - } - time = time2; - keyMap = nextMap; - } - timelines.Add(timeline); - } - } - } - } - - // Draw order timeline. - if (map.ContainsKey("drawOrder")) { - var values = (List)map["drawOrder"]; - var timeline = new DrawOrderTimeline(values.Count); - int slotCount = skeletonData.slots.Count; - int frame = 0; - foreach (Dictionary drawOrderMap in values) { - int[] drawOrder = null; - if (drawOrderMap.ContainsKey("offsets")) { - drawOrder = new int[slotCount]; - for (int i = slotCount - 1; i >= 0; i--) - drawOrder[i] = -1; - var offsets = (List)drawOrderMap["offsets"]; - int[] unchanged = new int[slotCount - offsets.Count]; - int originalIndex = 0, unchangedIndex = 0; - foreach (Dictionary offsetMap in offsets) { - int slotIndex = FindSlotIndex(skeletonData, (string)offsetMap["slot"]); - // Collect unchanged items. - while (originalIndex != slotIndex) - unchanged[unchangedIndex++] = originalIndex++; - // Set changed items. - int index = originalIndex + (int)(float)offsetMap["offset"]; - drawOrder[index] = originalIndex++; - } - // Collect remaining unchanged items. - while (originalIndex < slotCount) - unchanged[unchangedIndex++] = originalIndex++; - // Fill in unchanged items. - for (int i = slotCount - 1; i >= 0; i--) - if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex]; - } - timeline.SetFrame(frame, GetFloat(drawOrderMap, "time", 0), drawOrder); - ++frame; - } - timelines.Add(timeline); - } - - // Event timeline. - if (map.ContainsKey("events")) { - var eventsMap = (List)map["events"]; - var timeline = new EventTimeline(eventsMap.Count); - int frame = 0; - foreach (Dictionary eventMap in eventsMap) { - EventData eventData = skeletonData.FindEvent((string)eventMap["name"]); - if (eventData == null) throw new Exception("Event not found: " + eventMap["name"]); - var e = new Event(GetFloat(eventMap, "time", 0), eventData) { - intValue = GetInt(eventMap, "int", eventData.Int), - floatValue = GetFloat(eventMap, "float", eventData.Float), - stringValue = GetString(eventMap, "string", eventData.String) - }; - if (e.data.AudioPath != null) { - e.volume = GetFloat(eventMap, "volume", eventData.Volume); - e.balance = GetFloat(eventMap, "balance", eventData.Balance); - } - timeline.SetFrame(frame, e); - ++frame; - } - timelines.Add(timeline); - } - timelines.TrimExcess(); - float duration = 0; - var items = timelines.Items; - for (int i = 0, n = timelines.Count; i < n; i++) - duration = Math.Max(duration, items[i].Duration); - skeletonData.animations.Add(new Animation(name, timelines, duration)); - } - - static Timeline ReadTimeline (ref List.Enumerator keyMapEnumerator, CurveTimeline1 timeline, float defaultValue, float scale) { - var keyMap = (Dictionary)keyMapEnumerator.Current; - float time = GetFloat(keyMap, "time", 0); - float value = GetFloat(keyMap, "value", defaultValue) * scale; - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, value); - if (!keyMapEnumerator.MoveNext()) { - timeline.Shrink(bezier); - return timeline; - } - var nextMap = (Dictionary)keyMapEnumerator.Current; - float time2 = GetFloat(nextMap, "time", 0); - float value2 = GetFloat(nextMap, "value", defaultValue) * scale; - if (keyMap.ContainsKey("curve")) { - object curve = keyMap["curve"]; - bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, value, value2, scale); - } - time = time2; - value = value2; - keyMap = nextMap; - } - } - - static Timeline ReadTimeline (ref List.Enumerator keyMapEnumerator, CurveTimeline2 timeline, String name1, String name2, float defaultValue, - float scale) { - - var keyMap = (Dictionary)keyMapEnumerator.Current; - float time = GetFloat(keyMap, "time", 0); - float value1 = GetFloat(keyMap, name1, defaultValue) * scale, value2 = GetFloat(keyMap, name2, defaultValue) * scale; - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, value1, value2); - if (!keyMapEnumerator.MoveNext()) { - timeline.Shrink(bezier); - return timeline; - } - var nextMap = (Dictionary)keyMapEnumerator.Current; - float time2 = GetFloat(nextMap, "time", 0); - float nvalue1 = GetFloat(nextMap, name1, defaultValue) * scale, nvalue2 = GetFloat(nextMap, name2, defaultValue) * scale; - if (keyMap.ContainsKey("curve")) { - object curve = keyMap["curve"]; - 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; - } - } - - static int ReadCurve (object curve, CurveTimeline timeline, int bezier, int frame, int value, float time1, float time2, - float value1, float value2, float scale) { - - string curveString = curve as string; - if (curveString != null) { - if (curveString == "stepped") timeline.SetStepped(frame); - return bezier; - } - var curveValues = (List)curve; - int i = value << 2; - float cx1 = (float)curveValues[i]; - float cy1 = (float)curveValues[i + 1] * scale; - float cx2 = (float)curveValues[i + 2]; - float cy2 = (float)curveValues[i + 3] * scale; - SetBezier(timeline, frame, value, bezier, time1, value1, cx1, cy1, cx2, cy2, time2, value2); - return bezier + 1; - } - - 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) { - timeline.SetBezier(bezier, frame, value, time1, value1, cx1, cy1, cx2, cy2, time2, value2); - } - - static float[] GetFloatArray (Dictionary map, string name, float scale) { - var list = (List)map[name]; - var values = new float[list.Count]; - if (scale == 1) { - for (int i = 0, n = list.Count; i < n; i++) - values[i] = (float)list[i]; - } else { - for (int i = 0, n = list.Count; i < n; i++) - values[i] = (float)list[i] * scale; - } - return values; - } - - static int[] GetIntArray (Dictionary map, string name) { - var list = (List)map[name]; - var values = new int[list.Count]; - for (int i = 0, n = list.Count; i < n; i++) - values[i] = (int)(float)list[i]; - return values; - } - - static float GetFloat (Dictionary map, string name, float defaultValue) { - if (!map.ContainsKey(name)) return defaultValue; - return (float)map[name]; - } - - static int GetInt (Dictionary map, string name, int defaultValue) { - if (!map.ContainsKey(name)) return defaultValue; - return (int)(float)map[name]; - } - - static bool GetBoolean (Dictionary map, string name, bool defaultValue) { - if (!map.ContainsKey(name)) return defaultValue; - return (bool)map[name]; - } - - static string GetString (Dictionary map, string name, string defaultValue) { - if (!map.ContainsKey(name)) return defaultValue; - return (string)map[name]; - } - - static float ToColor (string hexString, int colorIndex, int expectedLength = 8) { - if (hexString.Length != expectedLength) - throw new ArgumentException("Color hexidecimal length must be " + expectedLength + ", recieved: " + hexString, "hexString"); - return Convert.ToInt32(hexString.Substring(colorIndex * 2, 2), 16) / (float)255; - } - } + public SkeletonData ReadSkeletonData(TextReader reader) + { + if (reader == null) throw new ArgumentNullException("reader", "reader cannot be null."); + + float scale = this.scale; + var skeletonData = new SkeletonData(); + + var root = Json.Deserialize(reader) as Dictionary; + if (root == null) throw new Exception("Invalid JSON."); + + // Skeleton. + if (root.ContainsKey("skeleton")) + { + var skeletonMap = (Dictionary)root["skeleton"]; + skeletonData.hash = (string)skeletonMap["hash"]; + skeletonData.version = (string)skeletonMap["spine"]; + skeletonData.x = GetFloat(skeletonMap, "x", 0); + skeletonData.y = GetFloat(skeletonMap, "y", 0); + skeletonData.width = GetFloat(skeletonMap, "width", 0); + skeletonData.height = GetFloat(skeletonMap, "height", 0); + skeletonData.fps = GetFloat(skeletonMap, "fps", 30); + skeletonData.imagesPath = GetString(skeletonMap, "images", null); + skeletonData.audioPath = GetString(skeletonMap, "audio", null); + } + + // Bones. + if (root.ContainsKey("bones")) + { + foreach (Dictionary boneMap in (List)root["bones"]) + { + BoneData parent = null; + if (boneMap.ContainsKey("parent")) + { + parent = skeletonData.FindBone((string)boneMap["parent"]); + if (parent == null) + throw new Exception("Parent bone not found: " + boneMap["parent"]); + } + var data = new BoneData(skeletonData.Bones.Count, (string)boneMap["name"], parent); + data.length = GetFloat(boneMap, "length", 0) * scale; + data.x = GetFloat(boneMap, "x", 0) * scale; + data.y = GetFloat(boneMap, "y", 0) * scale; + data.rotation = GetFloat(boneMap, "rotation", 0); + data.scaleX = GetFloat(boneMap, "scaleX", 1); + data.scaleY = GetFloat(boneMap, "scaleY", 1); + data.shearX = GetFloat(boneMap, "shearX", 0); + data.shearY = GetFloat(boneMap, "shearY", 0); + + string tm = GetString(boneMap, "transform", TransformMode.Normal.ToString()); + data.transformMode = (TransformMode)Enum.Parse(typeof(TransformMode), tm, true); + data.skinRequired = GetBoolean(boneMap, "skin", false); + + skeletonData.bones.Add(data); + } + } + + // Slots. + if (root.ContainsKey("slots")) + { + foreach (Dictionary slotMap in (List)root["slots"]) + { + var slotName = (string)slotMap["name"]; + var boneName = (string)slotMap["bone"]; + BoneData boneData = skeletonData.FindBone(boneName); + if (boneData == null) throw new Exception("Slot bone not found: " + boneName); + var data = new SlotData(skeletonData.Slots.Count, slotName, boneData); + + if (slotMap.ContainsKey("color")) + { + string color = (string)slotMap["color"]; + data.r = ToColor(color, 0); + data.g = ToColor(color, 1); + data.b = ToColor(color, 2); + data.a = ToColor(color, 3); + } + + if (slotMap.ContainsKey("dark")) + { + var color2 = (string)slotMap["dark"]; + data.r2 = ToColor(color2, 0, 6); // expectedLength = 6. ie. "RRGGBB" + data.g2 = ToColor(color2, 1, 6); + data.b2 = ToColor(color2, 2, 6); + data.hasSecondColor = true; + } + + data.attachmentName = GetString(slotMap, "attachment", null); + if (slotMap.ContainsKey("blend")) + data.blendMode = (BlendMode)Enum.Parse(typeof(BlendMode), (string)slotMap["blend"], true); + else + data.blendMode = BlendMode.Normal; + skeletonData.slots.Add(data); + } + } + + // IK constraints. + if (root.ContainsKey("ik")) + { + foreach (Dictionary constraintMap in (List)root["ik"]) + { + IkConstraintData data = new IkConstraintData((string)constraintMap["name"]); + data.order = GetInt(constraintMap, "order", 0); + data.skinRequired = GetBoolean(constraintMap, "skin", false); + + if (constraintMap.ContainsKey("bones")) + { + foreach (string boneName in (List)constraintMap["bones"]) + { + BoneData bone = skeletonData.FindBone(boneName); + if (bone == null) throw new Exception("IK bone not found: " + boneName); + data.bones.Add(bone); + } + } + + string targetName = (string)constraintMap["target"]; + data.target = skeletonData.FindBone(targetName); + if (data.target == null) throw new Exception("IK target bone not found: " + targetName); + data.mix = GetFloat(constraintMap, "mix", 1); + data.softness = GetFloat(constraintMap, "softness", 0) * scale; + data.bendDirection = GetBoolean(constraintMap, "bendPositive", true) ? 1 : -1; + data.compress = GetBoolean(constraintMap, "compress", false); + data.stretch = GetBoolean(constraintMap, "stretch", false); + data.uniform = GetBoolean(constraintMap, "uniform", false); + + skeletonData.ikConstraints.Add(data); + } + } + + // Transform constraints. + if (root.ContainsKey("transform")) + { + foreach (Dictionary constraintMap in (List)root["transform"]) + { + TransformConstraintData data = new TransformConstraintData((string)constraintMap["name"]); + data.order = GetInt(constraintMap, "order", 0); + data.skinRequired = GetBoolean(constraintMap, "skin", false); + + if (constraintMap.ContainsKey("bones")) + { + foreach (string boneName in (List)constraintMap["bones"]) + { + BoneData bone = skeletonData.FindBone(boneName); + if (bone == null) throw new Exception("Transform constraint bone not found: " + boneName); + data.bones.Add(bone); + } + } + + string targetName = (string)constraintMap["target"]; + data.target = skeletonData.FindBone(targetName); + if (data.target == null) throw new Exception("Transform constraint target bone not found: " + targetName); + + data.local = GetBoolean(constraintMap, "local", false); + data.relative = GetBoolean(constraintMap, "relative", false); + + data.offsetRotation = GetFloat(constraintMap, "rotation", 0); + data.offsetX = GetFloat(constraintMap, "x", 0) * scale; + data.offsetY = GetFloat(constraintMap, "y", 0) * scale; + data.offsetScaleX = GetFloat(constraintMap, "scaleX", 0); + data.offsetScaleY = GetFloat(constraintMap, "scaleY", 0); + data.offsetShearY = GetFloat(constraintMap, "shearY", 0); + + data.mixRotate = GetFloat(constraintMap, "mixRotate", 1); + data.mixX = GetFloat(constraintMap, "mixX", 1); + data.mixY = GetFloat(constraintMap, "mixY", data.mixX); + data.mixScaleX = GetFloat(constraintMap, "mixScaleX", 1); + data.mixScaleY = GetFloat(constraintMap, "mixScaleY", data.mixScaleX); + data.mixShearY = GetFloat(constraintMap, "mixShearY", 1); + + skeletonData.transformConstraints.Add(data); + } + } + + // Path constraints. + if (root.ContainsKey("path")) + { + foreach (Dictionary constraintMap in (List)root["path"]) + { + PathConstraintData data = new PathConstraintData((string)constraintMap["name"]); + data.order = GetInt(constraintMap, "order", 0); + data.skinRequired = GetBoolean(constraintMap, "skin", false); + + if (constraintMap.ContainsKey("bones")) + { + foreach (string boneName in (List)constraintMap["bones"]) + { + BoneData bone = skeletonData.FindBone(boneName); + if (bone == null) throw new Exception("Path bone not found: " + boneName); + data.bones.Add(bone); + } + } + + string targetName = (string)constraintMap["target"]; + data.target = skeletonData.FindSlot(targetName); + if (data.target == null) throw new Exception("Path target slot not found: " + targetName); + + data.positionMode = (PositionMode)Enum.Parse(typeof(PositionMode), GetString(constraintMap, "positionMode", "percent"), true); + data.spacingMode = (SpacingMode)Enum.Parse(typeof(SpacingMode), GetString(constraintMap, "spacingMode", "length"), true); + data.rotateMode = (RotateMode)Enum.Parse(typeof(RotateMode), GetString(constraintMap, "rotateMode", "tangent"), true); + data.offsetRotation = GetFloat(constraintMap, "rotation", 0); + data.position = GetFloat(constraintMap, "position", 0); + if (data.positionMode == PositionMode.Fixed) data.position *= scale; + data.spacing = GetFloat(constraintMap, "spacing", 0); + if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale; + data.mixRotate = GetFloat(constraintMap, "mixRotate", 1); + data.mixX = GetFloat(constraintMap, "mixX", 1); + data.mixY = GetFloat(constraintMap, "mixY", data.mixX); + + skeletonData.pathConstraints.Add(data); + } + } + + // Skins. + if (root.ContainsKey("skins")) + { + foreach (Dictionary skinMap in (List)root["skins"]) + { + Skin skin = new Skin((string)skinMap["name"]); + if (skinMap.ContainsKey("bones")) + { + foreach (string entryName in (List)skinMap["bones"]) + { + BoneData bone = skeletonData.FindBone(entryName); + if (bone == null) throw new Exception("Skin bone not found: " + entryName); + skin.bones.Add(bone); + } + } + skin.bones.TrimExcess(); + if (skinMap.ContainsKey("ik")) + { + foreach (string entryName in (List)skinMap["ik"]) + { + IkConstraintData constraint = skeletonData.FindIkConstraint(entryName); + if (constraint == null) throw new Exception("Skin IK constraint not found: " + entryName); + skin.constraints.Add(constraint); + } + } + if (skinMap.ContainsKey("transform")) + { + foreach (string entryName in (List)skinMap["transform"]) + { + TransformConstraintData constraint = skeletonData.FindTransformConstraint(entryName); + if (constraint == null) throw new Exception("Skin transform constraint not found: " + entryName); + skin.constraints.Add(constraint); + } + } + if (skinMap.ContainsKey("path")) + { + foreach (string entryName in (List)skinMap["path"]) + { + PathConstraintData constraint = skeletonData.FindPathConstraint(entryName); + if (constraint == null) throw new Exception("Skin path constraint not found: " + entryName); + skin.constraints.Add(constraint); + } + } + skin.constraints.TrimExcess(); + if (skinMap.ContainsKey("attachments")) + { + foreach (KeyValuePair slotEntry in (Dictionary)skinMap["attachments"]) + { + int slotIndex = FindSlotIndex(skeletonData, slotEntry.Key); + foreach (KeyValuePair entry in ((Dictionary)slotEntry.Value)) + { + try + { + Attachment attachment = ReadAttachment((Dictionary)entry.Value, skin, slotIndex, entry.Key, skeletonData); + if (attachment != null) skin.SetAttachment(slotIndex, entry.Key, attachment); + } + catch (Exception e) + { + throw new Exception("Error reading attachment: " + entry.Key + ", skin: " + skin, e); + } + } + } + } + skeletonData.skins.Add(skin); + if (skin.name == "default") skeletonData.defaultSkin = skin; + } + } + + // Linked meshes. + for (int i = 0, n = linkedMeshes.Count; i < n; i++) + { + LinkedMesh linkedMesh = linkedMeshes[i]; + Skin skin = linkedMesh.skin == null ? skeletonData.defaultSkin : skeletonData.FindSkin(linkedMesh.skin); + if (skin == null) throw new Exception("Slot not found: " + linkedMesh.skin); + Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); + if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); + linkedMesh.mesh.DeformAttachment = linkedMesh.inheritDeform ? (VertexAttachment)parent : linkedMesh.mesh; + linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; + linkedMesh.mesh.UpdateUVs(); + } + linkedMeshes.Clear(); + + // Events. + if (root.ContainsKey("events")) + { + foreach (KeyValuePair entry in (Dictionary)root["events"]) + { + var entryMap = (Dictionary)entry.Value; + var data = new EventData(entry.Key); + data.Int = GetInt(entryMap, "int", 0); + data.Float = GetFloat(entryMap, "float", 0); + data.String = GetString(entryMap, "string", string.Empty); + data.AudioPath = GetString(entryMap, "audio", null); + if (data.AudioPath != null) + { + data.Volume = GetFloat(entryMap, "volume", 1); + data.Balance = GetFloat(entryMap, "balance", 0); + } + skeletonData.events.Add(data); + } + } + + // Animations. + if (root.ContainsKey("animations")) + { + foreach (KeyValuePair entry in (Dictionary)root["animations"]) + { + try + { + ReadAnimation((Dictionary)entry.Value, entry.Key, skeletonData); + } + catch (Exception e) + { + throw new Exception("Error reading animation: " + entry.Key + "\n" + e.Message, e); + } + } + } + + skeletonData.bones.TrimExcess(); + skeletonData.slots.TrimExcess(); + skeletonData.skins.TrimExcess(); + skeletonData.events.TrimExcess(); + skeletonData.animations.TrimExcess(); + skeletonData.ikConstraints.TrimExcess(); + return skeletonData; + } + + private Attachment ReadAttachment(Dictionary map, Skin skin, int slotIndex, string name, SkeletonData skeletonData) + { + float scale = this.scale; + name = GetString(map, "name", name); + + var typeName = GetString(map, "type", "region"); + var type = (AttachmentType)Enum.Parse(typeof(AttachmentType), typeName, true); + + string path = GetString(map, "path", name); + + switch (type) + { + case AttachmentType.Region: + RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path); + if (region == null) return null; + region.Path = path; + region.x = GetFloat(map, "x", 0) * scale; + region.y = GetFloat(map, "y", 0) * scale; + region.scaleX = GetFloat(map, "scaleX", 1); + region.scaleY = GetFloat(map, "scaleY", 1); + region.rotation = GetFloat(map, "rotation", 0); + region.width = GetFloat(map, "width", 32) * scale; + region.height = GetFloat(map, "height", 32) * scale; + + if (map.ContainsKey("color")) + { + var color = (string)map["color"]; + region.r = ToColor(color, 0); + region.g = ToColor(color, 1); + region.b = ToColor(color, 2); + region.a = ToColor(color, 3); + } + + region.UpdateOffset(); + return region; + case AttachmentType.Boundingbox: + BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); + if (box == null) return null; + ReadVertices(map, box, GetInt(map, "vertexCount", 0) << 1); + return box; + case AttachmentType.Mesh: + case AttachmentType.Linkedmesh: + { + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path); + if (mesh == null) return null; + mesh.Path = path; + + if (map.ContainsKey("color")) + { + var color = (string)map["color"]; + mesh.r = ToColor(color, 0); + mesh.g = ToColor(color, 1); + mesh.b = ToColor(color, 2); + mesh.a = ToColor(color, 3); + } + + mesh.Width = GetFloat(map, "width", 0) * scale; + mesh.Height = GetFloat(map, "height", 0) * scale; + + string parent = GetString(map, "parent", null); + if (parent != null) + { + linkedMeshes.Add(new LinkedMesh(mesh, GetString(map, "skin", null), slotIndex, parent, GetBoolean(map, "deform", true))); + return mesh; + } + + float[] uvs = GetFloatArray(map, "uvs", 1); + ReadVertices(map, mesh, uvs.Length); + mesh.triangles = GetIntArray(map, "triangles"); + mesh.regionUVs = uvs; + mesh.UpdateUVs(); + + if (map.ContainsKey("hull")) mesh.HullLength = GetInt(map, "hull", 0) << 1; + if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges"); + return mesh; + } + case AttachmentType.Path: + { + PathAttachment pathAttachment = attachmentLoader.NewPathAttachment(skin, name); + if (pathAttachment == null) return null; + pathAttachment.closed = GetBoolean(map, "closed", false); + pathAttachment.constantSpeed = GetBoolean(map, "constantSpeed", true); + + int vertexCount = GetInt(map, "vertexCount", 0); + ReadVertices(map, pathAttachment, vertexCount << 1); + + // potential BOZO see Java impl + pathAttachment.lengths = GetFloatArray(map, "lengths", scale); + return pathAttachment; + } + case AttachmentType.Point: + { + PointAttachment point = attachmentLoader.NewPointAttachment(skin, name); + if (point == null) return null; + point.x = GetFloat(map, "x", 0) * scale; + point.y = GetFloat(map, "y", 0) * scale; + point.rotation = GetFloat(map, "rotation", 0); + + //string color = GetString(map, "color", null); + //if (color != null) point.color = color; + return point; + } + case AttachmentType.Clipping: + { + ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name); + if (clip == null) return null; + + string end = GetString(map, "end", null); + if (end != null) + { + SlotData slot = skeletonData.FindSlot(end); + if (slot == null) throw new Exception("Clipping end slot not found: " + end); + clip.EndSlot = slot; + } + + ReadVertices(map, clip, GetInt(map, "vertexCount", 0) << 1); + + //string color = GetString(map, "color", null); + // if (color != null) clip.color = color; + return clip; + } + } + return null; + } + + private void ReadVertices(Dictionary map, VertexAttachment attachment, int verticesLength) + { + attachment.WorldVerticesLength = verticesLength; + float[] vertices = GetFloatArray(map, "vertices", 1); + float scale = Scale; + if (verticesLength == vertices.Length) + { + if (scale != 1) + { + for (int i = 0; i < vertices.Length; i++) + { + vertices[i] *= scale; + } + } + attachment.vertices = vertices; + return; + } + ExposedList weights = new ExposedList(verticesLength * 3 * 3); + ExposedList bones = new ExposedList(verticesLength * 3); + for (int i = 0, n = vertices.Length; i < n;) + { + int boneCount = (int)vertices[i++]; + bones.Add(boneCount); + for (int nn = i + (boneCount << 2); i < nn; i += 4) + { + bones.Add((int)vertices[i]); + weights.Add(vertices[i + 1] * this.Scale); + weights.Add(vertices[i + 2] * this.Scale); + weights.Add(vertices[i + 3]); + } + } + attachment.bones = bones.ToArray(); + attachment.vertices = weights.ToArray(); + } + + private int FindSlotIndex(SkeletonData skeletonData, string slotName) + { + SlotData[] slots = skeletonData.slots.Items; + for (int i = 0, n = skeletonData.slots.Count; i < n; i++) + if (slots[i].name == slotName) return i; + throw new Exception("Slot not found: " + slotName); + } + + private void ReadAnimation(Dictionary map, string name, SkeletonData skeletonData) + { + var scale = this.scale; + var timelines = new ExposedList(); + + // Slot timelines. + if (map.ContainsKey("slots")) + { + foreach (KeyValuePair entry in (Dictionary)map["slots"]) + { + string slotName = entry.Key; + int slotIndex = FindSlotIndex(skeletonData, slotName); + var timelineMap = (Dictionary)entry.Value; + foreach (KeyValuePair timelineEntry in timelineMap) + { + var values = (List)timelineEntry.Value; + int frames = values.Count; + if (frames == 0) continue; + var timelineName = timelineEntry.Key; + if (timelineName == "attachment") + { + var timeline = new AttachmentTimeline(frames, slotIndex); + int frame = 0; + foreach (Dictionary keyMap in values) + { + timeline.SetFrame(frame++, GetFloat(keyMap, "time", 0), (string)keyMap["name"]); + } + timelines.Add(timeline); + + } + else if (timelineName == "rgba") + { + var timeline = new RGBATimeline(frames, frames << 2, slotIndex); + + var keyMapEnumerator = values.GetEnumerator(); + keyMapEnumerator.MoveNext(); + var keyMap = (Dictionary)keyMapEnumerator.Current; + float time = GetFloat(keyMap, "time", 0); + string color = (string)keyMap["color"]; + float r = ToColor(color, 0); + float g = ToColor(color, 1); + float b = ToColor(color, 2); + float a = ToColor(color, 3); + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, r, g, b, a); + if (!keyMapEnumerator.MoveNext()) + { + timeline.Shrink(bezier); + break; + } + var nextMap = (Dictionary)keyMapEnumerator.Current; + + float time2 = GetFloat(nextMap, "time", 0); + color = (string)nextMap["color"]; + float nr = ToColor(color, 0); + float ng = ToColor(color, 1); + float nb = ToColor(color, 2); + float na = ToColor(color, 3); + + if (keyMap.ContainsKey("curve")) + { + object curve = keyMap["curve"]; + bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 3, time, time2, a, na, 1); + } + time = time2; + r = nr; + g = ng; + b = nb; + a = na; + keyMap = nextMap; + } + timelines.Add(timeline); + + } + else if (timelineName == "rgb") + { + var timeline = new RGBTimeline(frames, frames * 3, slotIndex); + + var keyMapEnumerator = values.GetEnumerator(); + keyMapEnumerator.MoveNext(); + var keyMap = (Dictionary)keyMapEnumerator.Current; + float time = GetFloat(keyMap, "time", 0); + string color = (string)keyMap["color"]; + float r = ToColor(color, 0, 6); + float g = ToColor(color, 1, 6); + float b = ToColor(color, 2, 6); + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, r, g, b); + if (!keyMapEnumerator.MoveNext()) + { + timeline.Shrink(bezier); + break; + } + var nextMap = (Dictionary)keyMapEnumerator.Current; + + float time2 = GetFloat(nextMap, "time", 0); + color = (string)nextMap["color"]; + float nr = ToColor(color, 0, 6); + float ng = ToColor(color, 1, 6); + float nb = ToColor(color, 2, 6); + + if (keyMap.ContainsKey("curve")) + { + object curve = keyMap["curve"]; + bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1); + } + time = time2; + r = nr; + g = ng; + b = nb; + keyMap = nextMap; + } + timelines.Add(timeline); + + } + else if (timelineName == "alpha") + { + var keyMapEnumerator = values.GetEnumerator(); + keyMapEnumerator.MoveNext(); + timelines.Add(ReadTimeline(ref keyMapEnumerator, new AlphaTimeline(frames, frames, slotIndex), 0, 1)); + + } + else if (timelineName == "rgba2") + { + var timeline = new RGBA2Timeline(frames, frames * 7, slotIndex); + + var keyMapEnumerator = values.GetEnumerator(); + keyMapEnumerator.MoveNext(); + var keyMap = (Dictionary)keyMapEnumerator.Current; + float time = GetFloat(keyMap, "time", 0); + string color = (string)keyMap["light"]; + float r = ToColor(color, 0); + float g = ToColor(color, 1); + float b = ToColor(color, 2); + float a = ToColor(color, 3); + color = (string)keyMap["dark"]; + float r2 = ToColor(color, 0, 6); + float g2 = ToColor(color, 1, 6); + float b2 = ToColor(color, 2, 6); + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, r, g, b, a, r2, g2, b2); + if (!keyMapEnumerator.MoveNext()) + { + timeline.Shrink(bezier); + break; + } + var nextMap = (Dictionary)keyMapEnumerator.Current; + + float time2 = GetFloat(nextMap, "time", 0); + color = (string)nextMap["light"]; + float nr = ToColor(color, 0); + float ng = ToColor(color, 1); + float nb = ToColor(color, 2); + float na = ToColor(color, 3); + color = (string)nextMap["dark"]; + float nr2 = ToColor(color, 0, 6); + float ng2 = ToColor(color, 1, 6); + float nb2 = ToColor(color, 2, 6); + + if (keyMap.ContainsKey("curve")) + { + object curve = keyMap["curve"]; + bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 3, time, time2, a, na, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 4, time, time2, r2, nr2, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 5, time, time2, g2, ng2, 1); + bezier = ReadCurve(curve, 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; + keyMap = nextMap; + } + timelines.Add(timeline); + + } + else if (timelineName == "rgb2") + { + var timeline = new RGB2Timeline(frames, frames * 6, slotIndex); + + var keyMapEnumerator = values.GetEnumerator(); + keyMapEnumerator.MoveNext(); + var keyMap = (Dictionary)keyMapEnumerator.Current; + float time = GetFloat(keyMap, "time", 0); + string color = (string)keyMap["light"]; + float r = ToColor(color, 0, 6); + float g = ToColor(color, 1, 6); + float b = ToColor(color, 2, 6); + color = (string)keyMap["dark"]; + float r2 = ToColor(color, 0, 6); + float g2 = ToColor(color, 1, 6); + float b2 = ToColor(color, 2, 6); + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, r, g, b, r2, g2, b2); + if (!keyMapEnumerator.MoveNext()) + { + timeline.Shrink(bezier); + break; + } + var nextMap = (Dictionary)keyMapEnumerator.Current; + + float time2 = GetFloat(nextMap, "time", 0); + color = (string)nextMap["light"]; + float nr = ToColor(color, 0, 6); + float ng = ToColor(color, 1, 6); + float nb = ToColor(color, 2, 6); + color = (string)nextMap["dark"]; + float nr2 = ToColor(color, 0, 6); + float ng2 = ToColor(color, 1, 6); + float nb2 = ToColor(color, 2, 6); + + if (keyMap.ContainsKey("curve")) + { + object curve = keyMap["curve"]; + bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 3, time, time2, r2, nr2, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 4, time, time2, g2, ng2, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 5, time, time2, b2, nb2, 1); + } + time = time2; + r = nr; + g = ng; + b = nb; + r2 = nr2; + g2 = ng2; + b2 = nb2; + keyMap = nextMap; + } + timelines.Add(timeline); + + } + else + throw new Exception("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")"); + } + } + } + + // Bone timelines. + if (map.ContainsKey("bones")) + { + foreach (KeyValuePair entry in (Dictionary)map["bones"]) + { + string boneName = entry.Key; + int boneIndex = -1; + var bones = skeletonData.bones.Items; + for (int i = 0, n = skeletonData.bones.Count; i < n; i++) + { + if (bones[i].name == boneName) + { + boneIndex = i; + break; + } + } + if (boneIndex == -1) throw new Exception("Bone not found: " + boneName); + var timelineMap = (Dictionary)entry.Value; + foreach (KeyValuePair timelineEntry in timelineMap) + { + var values = (List)timelineEntry.Value; + var keyMapEnumerator = values.GetEnumerator(); + if (!keyMapEnumerator.MoveNext()) continue; + int frames = values.Count; + var timelineName = timelineEntry.Key; + if (timelineName == "rotate") + timelines.Add(ReadTimeline(ref keyMapEnumerator, new RotateTimeline(frames, frames, boneIndex), 0, 1)); + else if (timelineName == "translate") + { + TranslateTimeline timeline = new TranslateTimeline(frames, frames << 1, boneIndex); + timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, "x", "y", 0, scale)); + } + else if (timelineName == "translatex") + { + timelines + .Add(ReadTimeline(ref keyMapEnumerator, new TranslateXTimeline(frames, frames, boneIndex), 0, scale)); + } + else if (timelineName == "translatey") + { + timelines + .Add(ReadTimeline(ref keyMapEnumerator, new TranslateYTimeline(frames, frames, boneIndex), 0, scale)); + } + else if (timelineName == "scale") + { + ScaleTimeline timeline = new ScaleTimeline(frames, frames << 1, boneIndex); + timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, "x", "y", 1, 1)); + } + else if (timelineName == "scalex") + timelines.Add(ReadTimeline(ref keyMapEnumerator, new ScaleXTimeline(frames, frames, boneIndex), 1, 1)); + else if (timelineName == "scaley") + timelines.Add(ReadTimeline(ref keyMapEnumerator, new ScaleYTimeline(frames, frames, boneIndex), 1, 1)); + else if (timelineName == "shear") + { + ShearTimeline timeline = new ShearTimeline(frames, frames << 1, boneIndex); + timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, "x", "y", 0, 1)); + } + else if (timelineName == "shearx") + timelines.Add(ReadTimeline(ref keyMapEnumerator, new ShearXTimeline(frames, frames, boneIndex), 0, 1)); + else if (timelineName == "sheary") + timelines.Add(ReadTimeline(ref keyMapEnumerator, new ShearYTimeline(frames, frames, boneIndex), 0, 1)); + else + throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")"); + } + } + } + + // IK constraint timelines. + if (map.ContainsKey("ik")) + { + foreach (KeyValuePair timelineMap in (Dictionary)map["ik"]) + { + var values = (List)timelineMap.Value; + var keyMapEnumerator = values.GetEnumerator(); + if (!keyMapEnumerator.MoveNext()) continue; + var keyMap = (Dictionary)keyMapEnumerator.Current; + IkConstraintData constraint = skeletonData.FindIkConstraint(timelineMap.Key); + IkConstraintTimeline timeline = new IkConstraintTimeline(values.Count, values.Count << 1, + skeletonData.IkConstraints.IndexOf(constraint)); + float time = GetFloat(keyMap, "time", 0); + float mix = GetFloat(keyMap, "mix", 1), softness = GetFloat(keyMap, "softness", 0) * scale; + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, mix, softness, GetBoolean(keyMap, "bendPositive", true) ? 1 : -1, + GetBoolean(keyMap, "compress", false), GetBoolean(keyMap, "stretch", false)); + if (!keyMapEnumerator.MoveNext()) + { + timeline.Shrink(bezier); + break; + } + var nextMap = (Dictionary)keyMapEnumerator.Current; + float time2 = GetFloat(nextMap, "time", 0); + float mix2 = GetFloat(nextMap, "mix", 1), softness2 = GetFloat(nextMap, "softness", 0) * scale; + if (keyMap.ContainsKey("curve")) + { + object curve = keyMap["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. + if (map.ContainsKey("transform")) + { + foreach (KeyValuePair timelineMap in (Dictionary)map["transform"]) + { + var values = (List)timelineMap.Value; + var keyMapEnumerator = values.GetEnumerator(); + if (!keyMapEnumerator.MoveNext()) continue; + var keyMap = (Dictionary)keyMapEnumerator.Current; + TransformConstraintData constraint = skeletonData.FindTransformConstraint(timelineMap.Key); + TransformConstraintTimeline timeline = new TransformConstraintTimeline(values.Count, values.Count * 6, + skeletonData.TransformConstraints.IndexOf(constraint)); + float time = GetFloat(keyMap, "time", 0); + float mixRotate = GetFloat(keyMap, "mixRotate", 1), mixShearY = GetFloat(keyMap, "mixShearY", 1); + float mixX = GetFloat(keyMap, "mixX", 1), mixY = GetFloat(keyMap, "mixY", mixX); + float mixScaleX = GetFloat(keyMap, "mixScaleX", 1), mixScaleY = GetFloat(keyMap, "mixScaleY", mixScaleX); + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY); + if (!keyMapEnumerator.MoveNext()) + { + timeline.Shrink(bezier); + break; + } + var nextMap = (Dictionary)keyMapEnumerator.Current; + float time2 = GetFloat(nextMap, "time", 0); + float mixRotate2 = GetFloat(nextMap, "mixRotate", 1), mixShearY2 = GetFloat(nextMap, "mixShearY", 1); + float mixX2 = GetFloat(nextMap, "mixX", 1), mixY2 = GetFloat(nextMap, "mixY", mixX2); + float mixScaleX2 = GetFloat(nextMap, "mixScaleX", 1), mixScaleY2 = GetFloat(nextMap, "mixScaleY", mixScaleX2); + if (keyMap.ContainsKey("curve")) + { + object curve = keyMap["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; + mixScaleX = mixScaleX2; + keyMap = nextMap; + } + timelines.Add(timeline); + } + } + + // Path constraint timelines. + if (map.ContainsKey("path")) + { + foreach (KeyValuePair constraintMap in (Dictionary)map["path"]) + { + PathConstraintData constraint = skeletonData.FindPathConstraint(constraintMap.Key); + if (constraint == null) throw new Exception("Path constraint not found: " + constraintMap.Key); + int constraintIndex = skeletonData.pathConstraints.IndexOf(constraint); + var timelineMap = (Dictionary)constraintMap.Value; + foreach (KeyValuePair timelineEntry in timelineMap) + { + var values = (List)timelineEntry.Value; + var keyMapEnumerator = values.GetEnumerator(); + if (!keyMapEnumerator.MoveNext()) continue; + + int frames = values.Count; + var timelineName = timelineEntry.Key; + if (timelineName == "position") + { + CurveTimeline1 timeline = new PathConstraintPositionTimeline(frames, frames, constraintIndex); + timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, 0, constraint.positionMode == PositionMode.Fixed ? scale : 1)); + } + else if (timelineName == "spacing") + { + CurveTimeline1 timeline = new PathConstraintSpacingTimeline(frames, frames, constraintIndex); + timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, 0, + constraint.spacingMode == SpacingMode.Length || constraint.spacingMode == SpacingMode.Fixed ? scale : 1)); + } + else if (timelineName == "mix") + { + PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(frames, frames * 3, constraintIndex); + var keyMap = (Dictionary)keyMapEnumerator.Current; + float time = GetFloat(keyMap, "time", 0); + float mixRotate = GetFloat(keyMap, "mixRotate", 1); + float mixX = GetFloat(keyMap, "mixX", 1), mixY = GetFloat(keyMap, "mixY", mixX); + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, mixRotate, mixX, mixY); + if (!keyMapEnumerator.MoveNext()) + { + timeline.Shrink(bezier); + break; + } + var nextMap = (Dictionary)keyMapEnumerator.Current; + float time2 = GetFloat(nextMap, "time", 0); + float mixRotate2 = GetFloat(nextMap, "mixRotate", 1); + float mixX2 = GetFloat(nextMap, "mixX", 1), mixY2 = GetFloat(nextMap, "mixY", mixX2); + if (keyMap.ContainsKey("curve")) + { + object curve = keyMap["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); + } + time = time2; + mixRotate = mixRotate2; + mixX = mixX2; + mixY = mixY2; + keyMap = nextMap; + } + timelines.Add(timeline); + } + } + } + } + + // Deform timelines. + if (map.ContainsKey("deform")) + { + foreach (KeyValuePair deformMap in (Dictionary)map["deform"]) + { + Skin skin = skeletonData.FindSkin(deformMap.Key); + foreach (KeyValuePair slotMap in (Dictionary)deformMap.Value) + { + int slotIndex = FindSlotIndex(skeletonData, slotMap.Key); + foreach (KeyValuePair timelineMap in (Dictionary)slotMap.Value) + { + var values = (List)timelineMap.Value; + var keyMapEnumerator = values.GetEnumerator(); + if (!keyMapEnumerator.MoveNext()) continue; + var keyMap = (Dictionary)keyMapEnumerator.Current; + VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, timelineMap.Key); + if (attachment == null) throw new Exception("Deform attachment not found: " + timelineMap.Key); + bool weighted = attachment.bones != null; + float[] vertices = attachment.vertices; + int deformLength = weighted ? (vertices.Length / 3) << 1 : vertices.Length; + DeformTimeline timeline = new DeformTimeline(values.Count, values.Count, slotIndex, attachment); + float time = GetFloat(keyMap, "time", 0); + for (int frame = 0, bezier = 0; ; frame++) + { + float[] deform; + if (!keyMap.ContainsKey("vertices")) + { + deform = weighted ? new float[deformLength] : vertices; + } + else + { + deform = new float[deformLength]; + int start = GetInt(keyMap, "offset", 0); + float[] verticesValue = GetFloatArray(keyMap, "vertices", 1); + Array.Copy(verticesValue, 0, deform, start, verticesValue.Length); + if (scale != 1) + { + for (int i = start, n = i + verticesValue.Length; i < n; i++) + deform[i] *= scale; + } + + if (!weighted) + { + for (int i = 0; i < deformLength; i++) + deform[i] += vertices[i]; + } + } + + timeline.SetFrame(frame, time, deform); + if (!keyMapEnumerator.MoveNext()) + { + timeline.Shrink(bezier); + break; + } + var nextMap = (Dictionary)keyMapEnumerator.Current; + float time2 = GetFloat(nextMap, "time", 0); + if (keyMap.ContainsKey("curve")) + { + object curve = keyMap["curve"]; + bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, 0, 1, 1); + } + time = time2; + keyMap = nextMap; + } + timelines.Add(timeline); + } + } + } + } + + // Draw order timeline. + if (map.ContainsKey("drawOrder")) + { + var values = (List)map["drawOrder"]; + var timeline = new DrawOrderTimeline(values.Count); + int slotCount = skeletonData.slots.Count; + int frame = 0; + foreach (Dictionary drawOrderMap in values) + { + int[] drawOrder = null; + if (drawOrderMap.ContainsKey("offsets")) + { + drawOrder = new int[slotCount]; + for (int i = slotCount - 1; i >= 0; i--) + drawOrder[i] = -1; + var offsets = (List)drawOrderMap["offsets"]; + int[] unchanged = new int[slotCount - offsets.Count]; + int originalIndex = 0, unchangedIndex = 0; + foreach (Dictionary offsetMap in offsets) + { + int slotIndex = FindSlotIndex(skeletonData, (string)offsetMap["slot"]); + // Collect unchanged items. + while (originalIndex != slotIndex) + unchanged[unchangedIndex++] = originalIndex++; + // Set changed items. + int index = originalIndex + (int)(float)offsetMap["offset"]; + drawOrder[index] = originalIndex++; + } + // Collect remaining unchanged items. + while (originalIndex < slotCount) + unchanged[unchangedIndex++] = originalIndex++; + // Fill in unchanged items. + for (int i = slotCount - 1; i >= 0; i--) + if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex]; + } + timeline.SetFrame(frame, GetFloat(drawOrderMap, "time", 0), drawOrder); + ++frame; + } + timelines.Add(timeline); + } + + // Event timeline. + if (map.ContainsKey("events")) + { + var eventsMap = (List)map["events"]; + var timeline = new EventTimeline(eventsMap.Count); + int frame = 0; + foreach (Dictionary eventMap in eventsMap) + { + EventData eventData = skeletonData.FindEvent((string)eventMap["name"]); + if (eventData == null) throw new Exception("Event not found: " + eventMap["name"]); + var e = new Event(GetFloat(eventMap, "time", 0), eventData) + { + intValue = GetInt(eventMap, "int", eventData.Int), + floatValue = GetFloat(eventMap, "float", eventData.Float), + stringValue = GetString(eventMap, "string", eventData.String) + }; + if (e.data.AudioPath != null) + { + e.volume = GetFloat(eventMap, "volume", eventData.Volume); + e.balance = GetFloat(eventMap, "balance", eventData.Balance); + } + timeline.SetFrame(frame, e); + ++frame; + } + timelines.Add(timeline); + } + timelines.TrimExcess(); + float duration = 0; + var items = timelines.Items; + for (int i = 0, n = timelines.Count; i < n; i++) + duration = Math.Max(duration, items[i].Duration); + skeletonData.animations.Add(new Animation(name, timelines, duration)); + } + + static Timeline ReadTimeline(ref List.Enumerator keyMapEnumerator, CurveTimeline1 timeline, float defaultValue, float scale) + { + var keyMap = (Dictionary)keyMapEnumerator.Current; + float time = GetFloat(keyMap, "time", 0); + float value = GetFloat(keyMap, "value", defaultValue) * scale; + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, value); + if (!keyMapEnumerator.MoveNext()) + { + timeline.Shrink(bezier); + return timeline; + } + var nextMap = (Dictionary)keyMapEnumerator.Current; + float time2 = GetFloat(nextMap, "time", 0); + float value2 = GetFloat(nextMap, "value", defaultValue) * scale; + if (keyMap.ContainsKey("curve")) + { + object curve = keyMap["curve"]; + bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, value, value2, scale); + } + time = time2; + value = value2; + keyMap = nextMap; + } + } + + static Timeline ReadTimeline(ref List.Enumerator keyMapEnumerator, CurveTimeline2 timeline, String name1, String name2, float defaultValue, + float scale) + { + + var keyMap = (Dictionary)keyMapEnumerator.Current; + float time = GetFloat(keyMap, "time", 0); + float value1 = GetFloat(keyMap, name1, defaultValue) * scale, value2 = GetFloat(keyMap, name2, defaultValue) * scale; + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, value1, value2); + if (!keyMapEnumerator.MoveNext()) + { + timeline.Shrink(bezier); + return timeline; + } + var nextMap = (Dictionary)keyMapEnumerator.Current; + float time2 = GetFloat(nextMap, "time", 0); + float nvalue1 = GetFloat(nextMap, name1, defaultValue) * scale, nvalue2 = GetFloat(nextMap, name2, defaultValue) * scale; + if (keyMap.ContainsKey("curve")) + { + object curve = keyMap["curve"]; + 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; + } + } + + static int ReadCurve(object curve, CurveTimeline timeline, int bezier, int frame, int value, float time1, float time2, + float value1, float value2, float scale) + { + + string curveString = curve as string; + if (curveString != null) + { + if (curveString == "stepped") timeline.SetStepped(frame); + return bezier; + } + var curveValues = (List)curve; + int i = value << 2; + float cx1 = (float)curveValues[i]; + float cy1 = (float)curveValues[i + 1] * scale; + float cx2 = (float)curveValues[i + 2]; + float cy2 = (float)curveValues[i + 3] * scale; + SetBezier(timeline, frame, value, bezier, time1, value1, cx1, cy1, cx2, cy2, time2, value2); + return bezier + 1; + } + + 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) + { + timeline.SetBezier(bezier, frame, value, time1, value1, cx1, cy1, cx2, cy2, time2, value2); + } + + static float[] GetFloatArray(Dictionary map, string name, float scale) + { + var list = (List)map[name]; + var values = new float[list.Count]; + if (scale == 1) + { + for (int i = 0, n = list.Count; i < n; i++) + values[i] = (float)list[i]; + } + else + { + for (int i = 0, n = list.Count; i < n; i++) + values[i] = (float)list[i] * scale; + } + return values; + } + + static int[] GetIntArray(Dictionary map, string name) + { + var list = (List)map[name]; + var values = new int[list.Count]; + for (int i = 0, n = list.Count; i < n; i++) + values[i] = (int)(float)list[i]; + return values; + } + + static float GetFloat(Dictionary map, string name, float defaultValue) + { + if (!map.ContainsKey(name)) return defaultValue; + return (float)map[name]; + } + + static int GetInt(Dictionary map, string name, int defaultValue) + { + if (!map.ContainsKey(name)) return defaultValue; + return (int)(float)map[name]; + } + + static bool GetBoolean(Dictionary map, string name, bool defaultValue) + { + if (!map.ContainsKey(name)) return defaultValue; + return (bool)map[name]; + } + + static string GetString(Dictionary map, string name, string defaultValue) + { + if (!map.ContainsKey(name)) return defaultValue; + return (string)map[name]; + } + + static float ToColor(string hexString, int colorIndex, int expectedLength = 8) + { + if (hexString.Length != expectedLength) + throw new ArgumentException("Color hexidecimal length must be " + expectedLength + ", recieved: " + hexString, "hexString"); + return Convert.ToInt32(hexString.Substring(colorIndex * 2, 2), 16) / (float)255; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/SkeletonLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/SkeletonLoader.cs index b4d0025..d8ce0c5 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/SkeletonLoader.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/SkeletonLoader.cs @@ -29,64 +29,71 @@ using System; using System.Collections.Generic; -using System.IO; -namespace Spine4_0_64 { +namespace Spine4_0_64 +{ - /// - /// Base class for loading skeleton data from a file. - /// - /// SeeJSON and binary data in the - /// Spine Runtimes Guide. - /// - public abstract class SkeletonLoader { - protected readonly AttachmentLoader attachmentLoader; - protected float scale = 1; - protected readonly List linkedMeshes = new List(); + /// + /// Base class for loading skeleton data from a file. + /// + /// SeeJSON and binary data in the + /// Spine Runtimes Guide. + /// + public abstract class SkeletonLoader + { + protected readonly AttachmentLoader attachmentLoader; + protected float scale = 1; + protected readonly List linkedMeshes = new List(); - /// Creates a skeleton loader that loads attachments using an with the specified atlas. - /// - public SkeletonLoader (params Atlas[] atlasArray) { - attachmentLoader = new AtlasAttachmentLoader(atlasArray); - } + /// Creates a skeleton loader that loads attachments using an with the specified atlas. + /// + public SkeletonLoader(params Atlas[] atlasArray) + { + attachmentLoader = new AtlasAttachmentLoader(atlasArray); + } - /// Creates a skeleton loader that loads attachments using the specified attachment loader. - /// See Loading skeleton data in the - /// Spine Runtimes Guide. - public SkeletonLoader (AttachmentLoader attachmentLoader) { - if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader", "attachmentLoader cannot be null."); - this.attachmentLoader = attachmentLoader; - } + /// Creates a skeleton loader that loads attachments using the specified attachment loader. + /// See Loading skeleton data in the + /// Spine Runtimes Guide. + public SkeletonLoader(AttachmentLoader attachmentLoader) + { + if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader", "attachmentLoader cannot be null."); + this.attachmentLoader = attachmentLoader; + } - /// Scales bone positions, image sizes, and translations as they are loaded. This allows different size images to be used at - /// runtime than were used in Spine. - /// - /// See Scaling in the Spine Runtimes Guide. - /// - public float Scale { - get { return scale; } - set { - if (scale == 0) throw new ArgumentNullException("scale", "scale cannot be 0."); - this.scale = value; - } - } + /// Scales bone positions, image sizes, and translations as they are loaded. This allows different size images to be used at + /// runtime than were used in Spine. + /// + /// See Scaling in the Spine Runtimes Guide. + /// + public float Scale + { + get { return scale; } + set + { + if (scale == 0) throw new ArgumentNullException("scale", "scale cannot be 0."); + this.scale = value; + } + } - public abstract SkeletonData ReadSkeletonData (string path); + public abstract SkeletonData ReadSkeletonData(string path); - protected class LinkedMesh { - internal string parent, skin; - internal int slotIndex; - internal MeshAttachment mesh; - internal bool inheritDeform; + protected class LinkedMesh + { + internal string parent, skin; + internal int slotIndex; + internal MeshAttachment mesh; + internal bool inheritDeform; - public LinkedMesh (MeshAttachment mesh, string skin, int slotIndex, string parent, bool inheritDeform) { - this.mesh = mesh; - this.skin = skin; - this.slotIndex = slotIndex; - this.parent = parent; - this.inheritDeform = inheritDeform; - } - } + public LinkedMesh(MeshAttachment mesh, string skin, int slotIndex, string parent, bool inheritDeform) + { + this.mesh = mesh; + this.skin = skin; + this.slotIndex = slotIndex; + this.parent = parent; + this.inheritDeform = inheritDeform; + } + } - } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Skin.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Skin.cs index 2065d11..7d703b3 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Skin.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Skin.cs @@ -29,178 +29,209 @@ *****************************************************************************/ using System; -using System.Collections; using System.Collections.Generic; -namespace Spine4_0_64 { - /// Stores attachments by slot index and attachment name. - /// See SkeletonData , Skeleton , and - /// Runtime skins in the Spine Runtimes Guide. - /// - public class Skin { - internal string name; - // Difference to reference implementation: using Dictionary instead of HashSet. - // Reason is that there is no efficient way to replace or access an already added element, losing any benefits. - private Dictionary attachments = new Dictionary(SkinKeyComparer.Instance); - internal readonly ExposedList bones = new ExposedList(); - internal readonly ExposedList constraints = new ExposedList(); - - public string Name { get { return name; } } - ///Returns all attachments contained in this skin. - public ICollection Attachments { get { return attachments.Values; } } - public ExposedList Bones { get { return bones; } } - public ExposedList Constraints { get { return constraints; } } - - public Skin (string name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; - } - - /// 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. - public void SetAttachment (int slotIndex, string name, Attachment attachment) { - if (attachment == null) throw new ArgumentNullException("attachment", "attachment cannot be null."); - attachments[new SkinKey(slotIndex, name)] = new SkinEntry(slotIndex, name, attachment); - } - - ///Adds all attachments, bones, and constraints from the specified skin to this skin. - public void AddSkin (Skin skin) { - foreach (BoneData data in skin.bones) - if (!bones.Contains(data)) bones.Add(data); - - foreach (ConstraintData data in skin.constraints) - if (!constraints.Contains(data)) constraints.Add(data); - - foreach (var item in skin.attachments) { - SkinEntry entry = item.Value; - SetAttachment(entry.slotIndex, entry.name, entry.attachment); - } - } - - ///Adds all attachments from the specified skin to this skin. Attachments are deep copied. - public void CopySkin (Skin skin) { - foreach (BoneData data in skin.bones) - if (!bones.Contains(data)) bones.Add(data); - - foreach (ConstraintData data in skin.constraints) - if (!constraints.Contains(data)) constraints.Add(data); - - foreach (var item in skin.attachments) { - SkinEntry entry = item.Value; - if (entry.attachment is MeshAttachment) { - SetAttachment(entry.slotIndex, entry.name, - entry.attachment != null ? ((MeshAttachment)entry.attachment).NewLinkedMesh() : null); - } else - SetAttachment(entry.slotIndex, entry.name, entry.attachment != null ? entry.attachment.Copy() : null); - } - } - - /// Returns the attachment for the specified slot index and name, or null. - /// May be null. - public Attachment GetAttachment (int slotIndex, string name) { - SkinEntry entry; - bool containsKey = attachments.TryGetValue(new SkinKey(slotIndex, name), out entry); - return containsKey ? entry.attachment : null; - } - - /// Removes the attachment in the skin for the specified slot index and name, if any. - public void RemoveAttachment (int slotIndex, string name) { - attachments.Remove(new SkinKey(slotIndex, name)); - } - - /// Returns all attachments in this skin for the specified slot index. - /// The target slotIndex. To find the slot index, use and . - public void GetAttachments (int slotIndex, List attachments) { - if (slotIndex < 0) throw new ArgumentException("slotIndex must be >= 0."); - if (attachments == null) throw new ArgumentNullException("attachments", "attachments cannot be null."); - foreach (var item in this.attachments) { - SkinEntry entry = item.Value; - if (entry.slotIndex == slotIndex) attachments.Add(entry); - } - } - - ///Clears all attachments, bones, and constraints. - public void Clear () { - attachments.Clear(); - bones.Clear(); - constraints.Clear(); - } - - override public string ToString () { - return name; - } - - /// Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached. - internal void AttachAll (Skeleton skeleton, Skin oldSkin) { - Slot[] slots = skeleton.slots.Items; - foreach (var item in oldSkin.attachments) { - SkinEntry entry = item.Value; - int slotIndex = entry.slotIndex; - Slot slot = slots[slotIndex]; - if (slot.Attachment == entry.attachment) { - Attachment attachment = GetAttachment(slotIndex, entry.name); - if (attachment != null) slot.Attachment = attachment; - } - } - } - - /// Stores an entry in the skin consisting of the slot index, name, and attachment. - public struct SkinEntry { - internal readonly int slotIndex; - internal readonly string name; - internal readonly Attachment attachment; - - public SkinEntry (int slotIndex, string name, Attachment attachment) { - this.slotIndex = slotIndex; - this.name = name; - this.attachment = attachment; - } - - public int SlotIndex { - get { - return slotIndex; - } - } - - /// The name the attachment is associated with, equivalent to the skin placeholder name in the Spine editor. - public String Name { - get { - return name; - } - } - - public Attachment Attachment { - get { - return attachment; - } - } - } - - private struct SkinKey { - internal readonly int slotIndex; - internal readonly string name; - internal readonly int hashCode; - - public SkinKey (int slotIndex, string name) { - if (slotIndex < 0) throw new ArgumentException("slotIndex must be >= 0."); - if (name == null) throw new ArgumentNullException("name", "name cannot be null"); - this.slotIndex = slotIndex; - this.name = name; - this.hashCode = name.GetHashCode() + slotIndex * 37; - } - } - - class SkinKeyComparer : IEqualityComparer { - internal static readonly SkinKeyComparer Instance = new SkinKeyComparer(); - - bool IEqualityComparer.Equals (SkinKey e1, SkinKey e2) { - return e1.slotIndex == e2.slotIndex && string.Equals(e1.name, e2.name, StringComparison.Ordinal); - } - - int IEqualityComparer.GetHashCode (SkinKey e) { - return e.hashCode; - } - } - } +namespace Spine4_0_64 +{ + /// Stores attachments by slot index and attachment name. + /// See SkeletonData , Skeleton , and + /// Runtime skins in the Spine Runtimes Guide. + /// + public class Skin + { + internal string name; + // Difference to reference implementation: using Dictionary instead of HashSet. + // Reason is that there is no efficient way to replace or access an already added element, losing any benefits. + private Dictionary attachments = new Dictionary(SkinKeyComparer.Instance); + internal readonly ExposedList bones = new ExposedList(); + internal readonly ExposedList constraints = new ExposedList(); + + public string Name { get { return name; } } + ///Returns all attachments contained in this skin. + public ICollection Attachments { get { return attachments.Values; } } + public ExposedList Bones { get { return bones; } } + public ExposedList Constraints { get { return constraints; } } + + public Skin(string name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + } + + /// 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. + public void SetAttachment(int slotIndex, string name, Attachment attachment) + { + if (attachment == null) throw new ArgumentNullException("attachment", "attachment cannot be null."); + attachments[new SkinKey(slotIndex, name)] = new SkinEntry(slotIndex, name, attachment); + } + + ///Adds all attachments, bones, and constraints from the specified skin to this skin. + public void AddSkin(Skin skin) + { + foreach (BoneData data in skin.bones) + if (!bones.Contains(data)) bones.Add(data); + + foreach (ConstraintData data in skin.constraints) + if (!constraints.Contains(data)) constraints.Add(data); + + foreach (var item in skin.attachments) + { + SkinEntry entry = item.Value; + SetAttachment(entry.slotIndex, entry.name, entry.attachment); + } + } + + ///Adds all attachments from the specified skin to this skin. Attachments are deep copied. + public void CopySkin(Skin skin) + { + foreach (BoneData data in skin.bones) + if (!bones.Contains(data)) bones.Add(data); + + foreach (ConstraintData data in skin.constraints) + if (!constraints.Contains(data)) constraints.Add(data); + + foreach (var item in skin.attachments) + { + SkinEntry entry = item.Value; + if (entry.attachment is MeshAttachment) + { + SetAttachment(entry.slotIndex, entry.name, + entry.attachment != null ? ((MeshAttachment)entry.attachment).NewLinkedMesh() : null); + } + else + SetAttachment(entry.slotIndex, entry.name, entry.attachment != null ? entry.attachment.Copy() : null); + } + } + + /// Returns the attachment for the specified slot index and name, or null. + /// May be null. + public Attachment GetAttachment(int slotIndex, string name) + { + SkinEntry entry; + bool containsKey = attachments.TryGetValue(new SkinKey(slotIndex, name), out entry); + return containsKey ? entry.attachment : null; + } + + /// Removes the attachment in the skin for the specified slot index and name, if any. + public void RemoveAttachment(int slotIndex, string name) + { + attachments.Remove(new SkinKey(slotIndex, name)); + } + + /// Returns all attachments in this skin for the specified slot index. + /// The target slotIndex. To find the slot index, use and . + public void GetAttachments(int slotIndex, List attachments) + { + if (slotIndex < 0) throw new ArgumentException("slotIndex must be >= 0."); + if (attachments == null) throw new ArgumentNullException("attachments", "attachments cannot be null."); + foreach (var item in this.attachments) + { + SkinEntry entry = item.Value; + if (entry.slotIndex == slotIndex) attachments.Add(entry); + } + } + + ///Clears all attachments, bones, and constraints. + public void Clear() + { + attachments.Clear(); + bones.Clear(); + constraints.Clear(); + } + + override public string ToString() + { + return name; + } + + /// Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached. + internal void AttachAll(Skeleton skeleton, Skin oldSkin) + { + Slot[] slots = skeleton.slots.Items; + foreach (var item in oldSkin.attachments) + { + SkinEntry entry = item.Value; + int slotIndex = entry.slotIndex; + Slot slot = slots[slotIndex]; + if (slot.Attachment == entry.attachment) + { + Attachment attachment = GetAttachment(slotIndex, entry.name); + if (attachment != null) slot.Attachment = attachment; + } + } + } + + /// Stores an entry in the skin consisting of the slot index, name, and attachment. + public struct SkinEntry + { + internal readonly int slotIndex; + internal readonly string name; + internal readonly Attachment attachment; + + public SkinEntry(int slotIndex, string name, Attachment attachment) + { + this.slotIndex = slotIndex; + this.name = name; + this.attachment = attachment; + } + + public int SlotIndex + { + get + { + return slotIndex; + } + } + + /// The name the attachment is associated with, equivalent to the skin placeholder name in the Spine editor. + public String Name + { + get + { + return name; + } + } + + public Attachment Attachment + { + get + { + return attachment; + } + } + } + + private struct SkinKey + { + internal readonly int slotIndex; + internal readonly string name; + internal readonly int hashCode; + + public SkinKey(int slotIndex, string name) + { + if (slotIndex < 0) throw new ArgumentException("slotIndex must be >= 0."); + if (name == null) throw new ArgumentNullException("name", "name cannot be null"); + this.slotIndex = slotIndex; + this.name = name; + this.hashCode = name.GetHashCode() + slotIndex * 37; + } + } + + class SkinKeyComparer : IEqualityComparer + { + internal static readonly SkinKeyComparer Instance = new SkinKeyComparer(); + + bool IEqualityComparer.Equals(SkinKey e1, SkinKey e2) + { + return e1.slotIndex == e2.slotIndex && string.Equals(e1.name, e2.name, StringComparison.Ordinal); + } + + int IEqualityComparer.GetHashCode(SkinKey e) + { + return e.hashCode; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Slot.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Slot.cs index 81392e6..f42d004 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Slot.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Slot.cs @@ -29,172 +29,193 @@ using System; -namespace Spine4_0_64 { - - /// - /// Stores a slot's current pose. Slots organize attachments for purposes and provide a place to store - /// state for an attachment.State cannot be stored in an attachment itself because attachments are stateless and may be shared - /// across multiple skeletons. - /// - public class Slot { - internal SlotData data; - internal Bone bone; - internal float r, g, b, a; - internal float r2, g2, b2; - internal bool hasSecondColor; - internal Attachment attachment; - internal float attachmentTime; - internal ExposedList deform = new ExposedList(); - internal int attachmentState; - - public Slot (SlotData data, Bone bone) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); - this.data = data; - this.bone = bone; - - // darkColor = data.darkColor == null ? null : new Color(); - if (data.hasSecondColor) { - r2 = g2 = b2 = 0; - } - - SetToSetupPose(); - } - - /// Copy constructor. - public Slot (Slot slot, Bone bone) { - if (slot == null) throw new ArgumentNullException("slot", "slot cannot be null."); - if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); - data = slot.data; - this.bone = bone; - r = slot.r; - g = slot.g; - b = slot.b; - a = slot.a; - - // darkColor = slot.darkColor == null ? null : new Color(slot.darkColor); - if (slot.hasSecondColor) { - r2 = slot.r2; - g2 = slot.g2; - b2 = slot.b2; - } else { - r2 = g2 = b2 = 0; - } - hasSecondColor = slot.hasSecondColor; - - attachment = slot.attachment; - attachmentTime = slot.attachmentTime; - deform.AddRange(slot.deform); - } - - /// The slot's setup pose data. - public SlotData Data { get { return data; } } - /// The bone this slot belongs to. - public Bone Bone { get { return bone; } } - /// The skeleton this slot belongs to. - public Skeleton Skeleton { get { return bone.skeleton; } } - /// The color used to tint the slot's attachment. If is set, this is used as the light color for two - /// color tinting. - public float R { get { return r; } set { r = value; } } - /// The color used to tint the slot's attachment. If is set, this is used as the light color for two - /// color tinting. - public float G { get { return g; } set { g = value; } } - /// The color used to tint the slot's attachment. If is set, this is used as the light color for two - /// color tinting. - public float B { get { return b; } set { b = value; } } - /// The color used to tint the slot's attachment. If is set, this is used as the light color for two - /// color tinting. - public float A { get { return a; } set { a = value; } } - - public void ClampColor () { - r = MathUtils.Clamp(r, 0, 1); - g = MathUtils.Clamp(g, 0, 1); - b = MathUtils.Clamp(b, 0, 1); - a = MathUtils.Clamp(a, 0, 1); - } - - /// The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used. - /// - public float R2 { get { return r2; } set { r2 = value; } } - /// The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used. - /// - public float G2 { get { return g2; } set { g2 = value; } } - /// The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used. - /// - public float B2 { get { return b2; } set { b2 = value; } } - /// Whether R2 G2 B2 are used to tint the slot's attachment for two color tinting. False if two color tinting is not used. - public bool HasSecondColor { get { return data.hasSecondColor; } set { data.hasSecondColor = value; } } - - public void ClampSecondColor () { - r2 = MathUtils.Clamp(r2, 0, 1); - g2 = MathUtils.Clamp(g2, 0, 1); - b2 = MathUtils.Clamp(b2, 0, 1); - } - - public Attachment Attachment { - /// The current attachment for the slot, or null if the slot has no attachment. - get { return attachment; } - /// - /// Sets the slot's attachment and, if the attachment changed, resets and clears the . - /// The deform is not cleared if the old attachment has the same as the specified - /// attachment. - /// May be null. - set { - if (attachment == value) return; - if (!(value is VertexAttachment) || !(this.attachment is VertexAttachment) - || ((VertexAttachment)value).DeformAttachment != ((VertexAttachment)this.attachment).DeformAttachment) { - deform.Clear(); - } - this.attachment = value; - attachmentTime = bone.skeleton.time; - } - } - - /// The time that has elapsed since the last time the attachment was set or cleared. Relies on Skeleton - /// - public float AttachmentTime { - get { return bone.skeleton.time - attachmentTime; } - set { attachmentTime = bone.skeleton.time - value; } - } - - /// Vertices to deform the slot's attachment. For an unweighted mesh, the entries are local positions for each vertex. For a - /// weighted mesh, the entries are an offset for each vertex which will be added to the mesh's local vertex positions. - /// - /// See and . - public ExposedList Deform { - get { - return deform; - } - set { - if (deform == null) throw new ArgumentNullException("deform", "deform cannot be null."); - deform = value; - } - } - - /// Sets this slot to the setup pose. - public void SetToSetupPose () { - r = data.r; - g = data.g; - b = data.b; - a = data.a; - - // if (darkColor != null) darkColor.set(data.darkColor); - if (HasSecondColor) { - r2 = data.r2; - g2 = data.g2; - b2 = data.b2; - } - - if (data.attachmentName == null) - Attachment = null; - else { - attachment = null; - Attachment = bone.skeleton.GetAttachment(data.index, data.attachmentName); - } - } - - override public string ToString () { - return data.name; - } - } +namespace Spine4_0_64 +{ + + /// + /// Stores a slot's current pose. Slots organize attachments for purposes and provide a place to store + /// state for an attachment.State cannot be stored in an attachment itself because attachments are stateless and may be shared + /// across multiple skeletons. + /// + public class Slot + { + internal SlotData data; + internal Bone bone; + internal float r, g, b, a; + internal float r2, g2, b2; + internal bool hasSecondColor; + internal Attachment attachment; + internal float attachmentTime; + internal ExposedList deform = new ExposedList(); + internal int attachmentState; + + public Slot(SlotData data, Bone bone) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); + this.data = data; + this.bone = bone; + + // darkColor = data.darkColor == null ? null : new Color(); + if (data.hasSecondColor) + { + r2 = g2 = b2 = 0; + } + + SetToSetupPose(); + } + + /// Copy constructor. + public Slot(Slot slot, Bone bone) + { + if (slot == null) throw new ArgumentNullException("slot", "slot cannot be null."); + if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); + data = slot.data; + this.bone = bone; + r = slot.r; + g = slot.g; + b = slot.b; + a = slot.a; + + // darkColor = slot.darkColor == null ? null : new Color(slot.darkColor); + if (slot.hasSecondColor) + { + r2 = slot.r2; + g2 = slot.g2; + b2 = slot.b2; + } + else + { + r2 = g2 = b2 = 0; + } + hasSecondColor = slot.hasSecondColor; + + attachment = slot.attachment; + attachmentTime = slot.attachmentTime; + deform.AddRange(slot.deform); + } + + /// The slot's setup pose data. + public SlotData Data { get { return data; } } + /// The bone this slot belongs to. + public Bone Bone { get { return bone; } } + /// The skeleton this slot belongs to. + public Skeleton Skeleton { get { return bone.skeleton; } } + /// The color used to tint the slot's attachment. If is set, this is used as the light color for two + /// color tinting. + public float R { get { return r; } set { r = value; } } + /// The color used to tint the slot's attachment. If is set, this is used as the light color for two + /// color tinting. + public float G { get { return g; } set { g = value; } } + /// The color used to tint the slot's attachment. If is set, this is used as the light color for two + /// color tinting. + public float B { get { return b; } set { b = value; } } + /// The color used to tint the slot's attachment. If is set, this is used as the light color for two + /// color tinting. + public float A { get { return a; } set { a = value; } } + + public void ClampColor() + { + r = MathUtils.Clamp(r, 0, 1); + g = MathUtils.Clamp(g, 0, 1); + b = MathUtils.Clamp(b, 0, 1); + a = MathUtils.Clamp(a, 0, 1); + } + + /// The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used. + /// + public float R2 { get { return r2; } set { r2 = value; } } + /// The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used. + /// + public float G2 { get { return g2; } set { g2 = value; } } + /// The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used. + /// + public float B2 { get { return b2; } set { b2 = value; } } + /// Whether R2 G2 B2 are used to tint the slot's attachment for two color tinting. False if two color tinting is not used. + public bool HasSecondColor { get { return data.hasSecondColor; } set { data.hasSecondColor = value; } } + + public void ClampSecondColor() + { + r2 = MathUtils.Clamp(r2, 0, 1); + g2 = MathUtils.Clamp(g2, 0, 1); + b2 = MathUtils.Clamp(b2, 0, 1); + } + + public Attachment Attachment + { + /// The current attachment for the slot, or null if the slot has no attachment. + get { return attachment; } + /// + /// Sets the slot's attachment and, if the attachment changed, resets and clears the . + /// The deform is not cleared if the old attachment has the same as the specified + /// attachment. + /// May be null. + set + { + if (attachment == value) return; + if (!(value is VertexAttachment) || !(this.attachment is VertexAttachment) + || ((VertexAttachment)value).DeformAttachment != ((VertexAttachment)this.attachment).DeformAttachment) + { + deform.Clear(); + } + this.attachment = value; + attachmentTime = bone.skeleton.time; + } + } + + /// The time that has elapsed since the last time the attachment was set or cleared. Relies on Skeleton + /// + public float AttachmentTime + { + get { return bone.skeleton.time - attachmentTime; } + set { attachmentTime = bone.skeleton.time - value; } + } + + /// Vertices to deform the slot's attachment. For an unweighted mesh, the entries are local positions for each vertex. For a + /// weighted mesh, the entries are an offset for each vertex which will be added to the mesh's local vertex positions. + /// + /// See and . + public ExposedList Deform + { + get + { + return deform; + } + set + { + if (deform == null) throw new ArgumentNullException("deform", "deform cannot be null."); + deform = value; + } + } + + /// Sets this slot to the setup pose. + public void SetToSetupPose() + { + r = data.r; + g = data.g; + b = data.b; + a = data.a; + + // if (darkColor != null) darkColor.set(data.darkColor); + if (HasSecondColor) + { + r2 = data.r2; + g2 = data.g2; + b2 = data.b2; + } + + if (data.attachmentName == null) + Attachment = null; + else + { + attachment = null; + Attachment = bone.skeleton.GetAttachment(data.index, data.attachmentName); + } + } + + override public string ToString() + { + return data.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/SlotData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/SlotData.cs index 309e78d..ad82d51 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/SlotData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/SlotData.cs @@ -29,49 +29,53 @@ using System; -namespace Spine4_0_64 { - public class SlotData { - internal int index; - internal string name; - internal BoneData boneData; - internal float r = 1, g = 1, b = 1, a = 1; - internal float r2 = 0, g2 = 0, b2 = 0; - internal bool hasSecondColor = false; - internal string attachmentName; - internal BlendMode blendMode; +namespace Spine4_0_64 +{ + public class SlotData + { + internal int index; + internal string name; + internal BoneData boneData; + internal float r = 1, g = 1, b = 1, a = 1; + internal float r2 = 0, g2 = 0, b2 = 0; + internal bool hasSecondColor = false; + internal string attachmentName; + internal BlendMode blendMode; - /// The index of the slot in . - public int Index { get { return index; } } - /// The name of the slot, which is unique across all slots in the skeleton. - public string Name { get { return name; } } - /// The bone this slot belongs to. - public BoneData BoneData { get { return boneData; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } + /// The index of the slot in . + public int Index { get { return index; } } + /// The name of the slot, which is unique across all slots in the skeleton. + public string Name { get { return name; } } + /// The bone this slot belongs to. + public BoneData BoneData { get { return boneData; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } - public float R2 { get { return r2; } set { r2 = value; } } - public float G2 { get { return g2; } set { g2 = value; } } - public float B2 { get { return b2; } set { b2 = value; } } - public bool HasSecondColor { get { return hasSecondColor; } set { hasSecondColor = value; } } + public float R2 { get { return r2; } set { r2 = value; } } + public float G2 { get { return g2; } set { g2 = value; } } + public float B2 { get { return b2; } set { b2 = value; } } + public bool HasSecondColor { get { return hasSecondColor; } set { hasSecondColor = value; } } - /// The name of the attachment that is visible for this slot in the setup pose, or null if no attachment is visible. - public String AttachmentName { get { return attachmentName; } set { attachmentName = value; } } - /// The blend mode for drawing the slot's attachment. - public BlendMode BlendMode { get { return blendMode; } set { blendMode = value; } } + /// The name of the attachment that is visible for this slot in the setup pose, or null if no attachment is visible. + public String AttachmentName { get { return attachmentName; } set { attachmentName = value; } } + /// The blend mode for drawing the slot's attachment. + public BlendMode BlendMode { get { return blendMode; } set { blendMode = value; } } - public SlotData (int index, String name, BoneData boneData) { - if (index < 0) throw new ArgumentException("index must be >= 0.", "index"); - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - if (boneData == null) throw new ArgumentNullException("boneData", "boneData cannot be null."); - this.index = index; - this.name = name; - this.boneData = boneData; - } + public SlotData(int index, String name, BoneData boneData) + { + if (index < 0) throw new ArgumentException("index must be >= 0.", "index"); + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + if (boneData == null) throw new ArgumentNullException("boneData", "boneData cannot be null."); + this.index = index; + this.name = name; + this.boneData = boneData; + } - override public string ToString () { - return name; - } - } + override public string ToString() + { + return name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/TransformConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/TransformConstraint.cs index 6de599a..ef35ab5 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/TransformConstraint.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/TransformConstraint.cs @@ -29,283 +29,312 @@ using System; -namespace Spine4_0_64 { - /// - /// - /// Stores the current pose for a transform constraint. A transform constraint adjusts the world transform of the constrained - /// bones to match that of the target bone. - /// - /// See Transform constraints in the Spine User Guide. - /// - public class TransformConstraint : IUpdatable { - internal TransformConstraintData data; - internal ExposedList bones; - internal Bone target; - internal float mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY; - - internal bool active; - - public TransformConstraint (TransformConstraintData data, Skeleton skeleton) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.data = data; - mixRotate = data.mixRotate; - mixX = data.mixX; - mixY = data.mixY; - mixScaleX = data.mixScaleX; - mixScaleY = data.mixScaleY; - mixShearY = data.mixShearY; - bones = new ExposedList(); - foreach (BoneData boneData in data.bones) - bones.Add(skeleton.FindBone(boneData.name)); - - target = skeleton.FindBone(data.target.name); - } - - /// Copy constructor. - public TransformConstraint (TransformConstraint constraint, Skeleton skeleton) { - if (constraint == null) throw new ArgumentNullException("constraint cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); - data = constraint.data; - bones = new ExposedList(constraint.Bones.Count); - foreach (Bone bone in constraint.Bones) - bones.Add(skeleton.Bones.Items[bone.data.index]); - target = skeleton.Bones.Items[constraint.target.data.index]; - mixRotate = constraint.mixRotate; - mixX = constraint.mixX; - mixY = constraint.mixY; - mixScaleX = constraint.mixScaleX; - mixScaleY = constraint.mixScaleY; - mixShearY = constraint.mixShearY; - } - - public void Update () { - if (mixRotate == 0 && mixX == 0 && mixY == 0 && mixScaleX == 0 && mixScaleX == 0 && mixShearY == 0) return; - if (data.local) { - if (data.relative) - ApplyRelativeLocal(); - else - ApplyAbsoluteLocal(); - } else { - if (data.relative) - ApplyRelativeWorld(); - else - ApplyAbsoluteWorld(); - } - } - - void ApplyAbsoluteWorld () { - float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX, - mixScaleY = this.mixScaleY, mixShearY = this.mixShearY; - bool translate = mixX != 0 || mixY != 0; - - Bone target = this.target; - float ta = target.a, tb = target.b, tc = target.c, td = target.d; - float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad; - float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect; - - var bones = this.bones.Items; - for (int i = 0, n = this.bones.Count; i < n; i++) { - Bone bone = bones[i]; - - if (mixRotate != 0) { - float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - float r = MathUtils.Atan2(tc, ta) - MathUtils.Atan2(c, a) + offsetRotation; - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) // - r += MathUtils.PI2; - r *= mixRotate; - float cos = MathUtils.Cos(r), sin = MathUtils.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; //Vector2 temp = this.temp; - target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY)); - bone.worldX += (tx - bone.worldX) * mixX; - bone.worldY += (ty - bone.worldY) * mixY; - } - - if (mixScaleX != 0) { - float s = (float)Math.Sqrt(bone.a * bone.a + bone.c * bone.c); - if (s != 0) s = (s + ((float)Math.Sqrt(ta * ta + tc * tc) - s + data.offsetScaleX) * mixScaleX) / s; - bone.a *= s; - bone.c *= s; - } - if (mixScaleY != 0) { - float s = (float)Math.Sqrt(bone.b * bone.b + bone.d * bone.d); - if (s != 0) s = (s + ((float)Math.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 = MathUtils.Atan2(d, b); - float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta) - (by - MathUtils.Atan2(bone.c, bone.a)); - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) // - r += MathUtils.PI2; - r = by + (r + offsetShearY) * mixShearY; - float s = (float)Math.Sqrt(b * b + d * d); - bone.b = MathUtils.Cos(r) * s; - bone.d = MathUtils.Sin(r) * s; - } - - bone.UpdateAppliedTransform(); - } - } - - void ApplyRelativeWorld () { - float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX, - mixScaleY = this.mixScaleY, mixShearY = this.mixShearY; - bool translate = mixX != 0 || mixY != 0; - - Bone target = this.target; - float ta = target.a, tb = target.b, tc = target.c, td = target.d; - float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad; - float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect; - - var bones = this.bones.Items; - for (int i = 0, n = this.bones.Count; i < n; i++) { - Bone bone = bones[i]; - - if (mixRotate != 0) { - float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - float r = MathUtils.Atan2(tc, ta) + offsetRotation; - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) // - r += MathUtils.PI2; - r *= mixRotate; - float cos = MathUtils.Cos(r), sin = MathUtils.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; //Vector2 temp = this.temp; - target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY)); - bone.worldX += tx * mixX; - bone.worldY += ty * mixY; - } - - if (mixScaleX != 0) { - float s = ((float)Math.Sqrt(ta * ta + tc * tc) - 1 + data.offsetScaleX) * mixScaleX + 1; - bone.a *= s; - bone.c *= s; - } - if (mixScaleY != 0) { - float s = ((float)Math.Sqrt(tb * tb + td * td) - 1 + data.offsetScaleY) * mixScaleY + 1; - bone.b *= s; - bone.d *= s; - } - - if (mixShearY > 0) { - float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta); - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) // - r += MathUtils.PI2; - float b = bone.b, d = bone.d; - r = MathUtils.Atan2(d, b) + (r - MathUtils.PI / 2 + offsetShearY) * mixShearY; - float s = (float)Math.Sqrt(b * b + d * d); - bone.b = MathUtils.Cos(r) * s; - bone.d = MathUtils.Sin(r) * s; - } - - bone.UpdateAppliedTransform(); - } - } - - void ApplyAbsoluteLocal () { - float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX, - mixScaleY = this.mixScaleY, mixShearY = this.mixShearY; - - Bone target = this.target; - - var bones = this.bones.Items; - for (int i = 0, n = this.bones.Count; i < n; i++) { - Bone bone = bones[i]; - - float rotation = bone.arotation; - if (mixRotate != 0) { - float r = target.arotation - rotation + data.offsetRotation; - r -= (16384 - (int)(16384.499999999996 - r / 360)) * 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 -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; - shearY += r * mixShearY; - } - - bone.UpdateWorldTransform(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY); - } - } - - void ApplyRelativeLocal () { - float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX, - mixScaleY = this.mixScaleY, mixShearY = this.mixShearY; - - Bone target = this.target; - - var bones = this.bones.Items; - for (int i = 0, n = this.bones.Count; i < n; i++) { - Bone bone = bones[i]; - - 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); - } - } - - /// The bones that will be modified by this transform constraint. - public ExposedList Bones { get { return bones; } } - /// The target bone whose world transform will be copied to the constrained bones. - public Bone Target { get { return target; } set { target = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. - public float MixRotate { get { return mixRotate; } set { mixRotate = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. - public float MixX { get { return mixX; } set { mixX = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. - public float MixY { get { return mixY; } set { mixY = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained scale X. - public float MixScaleX { get { return mixScaleX; } set { mixScaleX = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained scale Y. - public float MixScaleY { get { return mixScaleY; } set { mixScaleY = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained shear Y. - public float MixShearY { get { return mixShearY; } set { mixShearY = value; } } - public bool Active { get { return active; } } - /// The transform constraint's setup pose data. - public TransformConstraintData Data { get { return data; } } - - override public string ToString () { - return data.name; - } - } +namespace Spine4_0_64 +{ + /// + /// + /// Stores the current pose for a transform constraint. A transform constraint adjusts the world transform of the constrained + /// bones to match that of the target bone. + /// + /// See Transform constraints in the Spine User Guide. + /// + public class TransformConstraint : IUpdatable + { + internal TransformConstraintData data; + internal ExposedList bones; + internal Bone target; + internal float mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY; + + internal bool active; + + public TransformConstraint(TransformConstraintData data, Skeleton skeleton) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + mixRotate = data.mixRotate; + mixX = data.mixX; + mixY = data.mixY; + mixScaleX = data.mixScaleX; + mixScaleY = data.mixScaleY; + mixShearY = data.mixShearY; + bones = new ExposedList(); + foreach (BoneData boneData in data.bones) + bones.Add(skeleton.FindBone(boneData.name)); + + target = skeleton.FindBone(data.target.name); + } + + /// Copy constructor. + public TransformConstraint(TransformConstraint constraint, Skeleton skeleton) + { + if (constraint == null) throw new ArgumentNullException("constraint cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); + data = constraint.data; + bones = new ExposedList(constraint.Bones.Count); + foreach (Bone bone in constraint.Bones) + bones.Add(skeleton.Bones.Items[bone.data.index]); + target = skeleton.Bones.Items[constraint.target.data.index]; + mixRotate = constraint.mixRotate; + mixX = constraint.mixX; + mixY = constraint.mixY; + mixScaleX = constraint.mixScaleX; + mixScaleY = constraint.mixScaleY; + mixShearY = constraint.mixShearY; + } + + public void Update() + { + if (mixRotate == 0 && mixX == 0 && mixY == 0 && mixScaleX == 0 && mixScaleX == 0 && mixShearY == 0) return; + if (data.local) + { + if (data.relative) + ApplyRelativeLocal(); + else + ApplyAbsoluteLocal(); + } + else + { + if (data.relative) + ApplyRelativeWorld(); + else + ApplyAbsoluteWorld(); + } + } + + void ApplyAbsoluteWorld() + { + float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX, + mixScaleY = this.mixScaleY, mixShearY = this.mixShearY; + bool translate = mixX != 0 || mixY != 0; + + Bone target = this.target; + float ta = target.a, tb = target.b, tc = target.c, td = target.d; + float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad; + float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect; + + var bones = this.bones.Items; + for (int i = 0, n = this.bones.Count; i < n; i++) + { + Bone bone = bones[i]; + + if (mixRotate != 0) + { + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + float r = MathUtils.Atan2(tc, ta) - MathUtils.Atan2(c, a) + offsetRotation; + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) // + r += MathUtils.PI2; + r *= mixRotate; + float cos = MathUtils.Cos(r), sin = MathUtils.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; //Vector2 temp = this.temp; + target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY)); + bone.worldX += (tx - bone.worldX) * mixX; + bone.worldY += (ty - bone.worldY) * mixY; + } + + if (mixScaleX != 0) + { + float s = (float)Math.Sqrt(bone.a * bone.a + bone.c * bone.c); + if (s != 0) s = (s + ((float)Math.Sqrt(ta * ta + tc * tc) - s + data.offsetScaleX) * mixScaleX) / s; + bone.a *= s; + bone.c *= s; + } + if (mixScaleY != 0) + { + float s = (float)Math.Sqrt(bone.b * bone.b + bone.d * bone.d); + if (s != 0) s = (s + ((float)Math.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 = MathUtils.Atan2(d, b); + float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta) - (by - MathUtils.Atan2(bone.c, bone.a)); + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) // + r += MathUtils.PI2; + r = by + (r + offsetShearY) * mixShearY; + float s = (float)Math.Sqrt(b * b + d * d); + bone.b = MathUtils.Cos(r) * s; + bone.d = MathUtils.Sin(r) * s; + } + + bone.UpdateAppliedTransform(); + } + } + + void ApplyRelativeWorld() + { + float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX, + mixScaleY = this.mixScaleY, mixShearY = this.mixShearY; + bool translate = mixX != 0 || mixY != 0; + + Bone target = this.target; + float ta = target.a, tb = target.b, tc = target.c, td = target.d; + float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad; + float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect; + + var bones = this.bones.Items; + for (int i = 0, n = this.bones.Count; i < n; i++) + { + Bone bone = bones[i]; + + if (mixRotate != 0) + { + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + float r = MathUtils.Atan2(tc, ta) + offsetRotation; + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) // + r += MathUtils.PI2; + r *= mixRotate; + float cos = MathUtils.Cos(r), sin = MathUtils.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; //Vector2 temp = this.temp; + target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY)); + bone.worldX += tx * mixX; + bone.worldY += ty * mixY; + } + + if (mixScaleX != 0) + { + float s = ((float)Math.Sqrt(ta * ta + tc * tc) - 1 + data.offsetScaleX) * mixScaleX + 1; + bone.a *= s; + bone.c *= s; + } + if (mixScaleY != 0) + { + float s = ((float)Math.Sqrt(tb * tb + td * td) - 1 + data.offsetScaleY) * mixScaleY + 1; + bone.b *= s; + bone.d *= s; + } + + if (mixShearY > 0) + { + float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta); + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) // + r += MathUtils.PI2; + float b = bone.b, d = bone.d; + r = MathUtils.Atan2(d, b) + (r - MathUtils.PI / 2 + offsetShearY) * mixShearY; + float s = (float)Math.Sqrt(b * b + d * d); + bone.b = MathUtils.Cos(r) * s; + bone.d = MathUtils.Sin(r) * s; + } + + bone.UpdateAppliedTransform(); + } + } + + void ApplyAbsoluteLocal() + { + float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX, + mixScaleY = this.mixScaleY, mixShearY = this.mixShearY; + + Bone target = this.target; + + var bones = this.bones.Items; + for (int i = 0, n = this.bones.Count; i < n; i++) + { + Bone bone = bones[i]; + + float rotation = bone.arotation; + if (mixRotate != 0) + { + float r = target.arotation - rotation + data.offsetRotation; + r -= (16384 - (int)(16384.499999999996 - r / 360)) * 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 -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; + shearY += r * mixShearY; + } + + bone.UpdateWorldTransform(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY); + } + } + + void ApplyRelativeLocal() + { + float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX, + mixScaleY = this.mixScaleY, mixShearY = this.mixShearY; + + Bone target = this.target; + + var bones = this.bones.Items; + for (int i = 0, n = this.bones.Count; i < n; i++) + { + Bone bone = bones[i]; + + 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); + } + } + + /// The bones that will be modified by this transform constraint. + public ExposedList Bones { get { return bones; } } + /// The target bone whose world transform will be copied to the constrained bones. + public Bone Target { get { return target; } set { target = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. + public float MixRotate { get { return mixRotate; } set { mixRotate = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. + public float MixX { get { return mixX; } set { mixX = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. + public float MixY { get { return mixY; } set { mixY = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained scale X. + public float MixScaleX { get { return mixScaleX; } set { mixScaleX = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained scale Y. + public float MixScaleY { get { return mixScaleY; } set { mixScaleY = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained shear Y. + public float MixShearY { get { return mixShearY; } set { mixShearY = value; } } + public bool Active { get { return active; } } + /// The transform constraint's setup pose data. + public TransformConstraintData Data { get { return data; } } + + override public string ToString() + { + return data.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/TransformConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/TransformConstraintData.cs index 6565f5a..d1c940e 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/TransformConstraintData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/TransformConstraintData.cs @@ -27,42 +27,43 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; +namespace Spine4_0_64 +{ + public class TransformConstraintData : ConstraintData + { + internal ExposedList bones = new ExposedList(); + internal BoneData target; + internal float mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY; + internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY; + internal bool relative, local; -namespace Spine4_0_64 { - public class TransformConstraintData : ConstraintData { - internal ExposedList bones = new ExposedList(); - internal BoneData target; - internal float mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY; - internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY; - internal bool relative, local; + public ExposedList Bones { get { return bones; } } + public BoneData Target { get { return target; } set { target = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. + public float MixRotate { get { return mixRotate; } set { mixRotate = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. + public float MixX { get { return mixX; } set { mixX = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. + public float MixY { get { return mixY; } set { mixY = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained scale X. + public float MixScaleX { get { return mixScaleX; } set { mixScaleX = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained scale Y. + public float MixScaleY { get { return mixScaleY; } set { mixScaleY = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained shear Y. + public float MixShearY { get { return mixShearY; } set { mixShearY = value; } } - public ExposedList Bones { get { return bones; } } - public BoneData Target { get { return target; } set { target = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. - public float MixRotate { get { return mixRotate; } set { mixRotate = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. - public float MixX { get { return mixX; } set { mixX = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. - public float MixY { get { return mixY; } set { mixY = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained scale X. - public float MixScaleX { get { return mixScaleX; } set { mixScaleX = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained scale Y. - public float MixScaleY { get { return mixScaleY; } set { mixScaleY = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained shear Y. - public float MixShearY { get { return mixShearY; } set { mixShearY = value; } } + public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } + public float OffsetX { get { return offsetX; } set { offsetX = value; } } + public float OffsetY { get { return offsetY; } set { offsetY = value; } } + public float OffsetScaleX { get { return offsetScaleX; } set { offsetScaleX = value; } } + public float OffsetScaleY { get { return offsetScaleY; } set { offsetScaleY = value; } } + public float OffsetShearY { get { return offsetShearY; } set { offsetShearY = value; } } - public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } - public float OffsetX { get { return offsetX; } set { offsetX = value; } } - public float OffsetY { get { return offsetY; } set { offsetY = value; } } - public float OffsetScaleX { get { return offsetScaleX; } set { offsetScaleX = value; } } - public float OffsetScaleY { get { return offsetScaleY; } set { offsetScaleY = value; } } - public float OffsetShearY { get { return offsetShearY; } set { offsetShearY = value; } } + public bool Relative { get { return relative; } set { relative = value; } } + public bool Local { get { return local; } set { local = value; } } - public bool Relative { get { return relative; } set { relative = value; } } - public bool Local { get { return local; } set { local = value; } } - - public TransformConstraintData (string name) : base(name) { - } - } + public TransformConstraintData(string name) : base(name) + { + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Triangulator.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Triangulator.cs index 2200e91..c5aba83 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Triangulator.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/Triangulator.cs @@ -29,247 +29,276 @@ using System; -namespace Spine4_0_64 { - public class Triangulator { - private readonly ExposedList> convexPolygons = new ExposedList>(); - private readonly ExposedList> convexPolygonsIndices = new ExposedList>(); - - private readonly ExposedList indicesArray = new ExposedList(); - private readonly ExposedList isConcaveArray = new ExposedList(); - private readonly ExposedList triangles = new ExposedList(); - - private readonly Pool> polygonPool = new Pool>(); - private readonly Pool> polygonIndicesPool = new Pool>(); - - public ExposedList Triangulate (ExposedList verticesArray) { - var vertices = verticesArray.Items; - int vertexCount = verticesArray.Count >> 1; - - var indicesArray = this.indicesArray; - indicesArray.Clear(); - int[] indices = indicesArray.Resize(vertexCount).Items; - for (int i = 0; i < vertexCount; i++) - indices[i] = i; - - var isConcaveArray = this.isConcaveArray; - bool[] isConcave = isConcaveArray.Resize(vertexCount).Items; - for (int i = 0, n = vertexCount; i < n; ++i) - isConcave[i] = IsConcave(i, vertexCount, vertices, indices); - - var triangles = this.triangles; - triangles.Clear(); - triangles.EnsureCapacity(Math.Max(0, vertexCount - 2) << 2); - - while (vertexCount > 3) { - // Find ear tip. - int previous = vertexCount - 1, i = 0, next = 1; - - // outer: - while (true) { - if (!isConcave[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 (int ii = (next + 1) % vertexCount; ii != previous; ii = (ii + 1) % vertexCount) { - if (!isConcave[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 (!isConcave[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]); - indicesArray.RemoveAt(i); - isConcaveArray.RemoveAt(i); - vertexCount--; - - int previousIndex = (vertexCount + i - 1) % vertexCount; - int nextIndex = i == vertexCount ? 0 : i; - isConcave[previousIndex] = IsConcave(previousIndex, vertexCount, vertices, indices); - isConcave[nextIndex] = IsConcave(nextIndex, vertexCount, vertices, indices); - } - - if (vertexCount == 3) { - triangles.Add(indices[2]); - triangles.Add(indices[0]); - triangles.Add(indices[1]); - } - - return triangles; - } - - public ExposedList> Decompose (ExposedList verticesArray, ExposedList triangles) { - var vertices = verticesArray.Items; - var convexPolygons = this.convexPolygons; - for (int i = 0, n = convexPolygons.Count; i < n; i++) - polygonPool.Free(convexPolygons.Items[i]); - convexPolygons.Clear(); - - var convexPolygonsIndices = this.convexPolygonsIndices; - for (int i = 0, n = convexPolygonsIndices.Count; i < n; i++) - polygonIndicesPool.Free(convexPolygonsIndices.Items[i]); - convexPolygonsIndices.Clear(); - - var polygonIndices = polygonIndicesPool.Obtain(); - polygonIndices.Clear(); - - var polygon = polygonPool.Obtain(); - polygon.Clear(); - - // Merge subsequent triangles if they form a triangle fan. - int fanBaseIndex = -1, lastWinding = 0; - int[] trianglesItems = triangles.Items; - for (int i = 0, n = triangles.Count; i < n; i += 3) { - int t1 = trianglesItems[i] << 1, t2 = trianglesItems[i + 1] << 1, t3 = trianglesItems[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). - var merged = false; - if (fanBaseIndex == t1) { - int o = polygon.Count - 4; - float[] p = polygon.Items; - 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.Count > 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.Count > 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 (int i = 0, n = convexPolygons.Count; i < n; i++) { - polygonIndices = convexPolygonsIndices.Items[i]; - if (polygonIndices.Count == 0) continue; - int firstIndex = polygonIndices.Items[0]; - int lastIndex = polygonIndices.Items[polygonIndices.Count - 1]; - - polygon = convexPolygons.Items[i]; - int o = polygon.Count - 4; - float[] p = polygon.Items; - 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 winding = Winding(prevPrevX, prevPrevY, prevX, prevY, firstX, firstY); - - for (int ii = 0; ii < n; ii++) { - if (ii == i) continue; - var otherIndices = convexPolygonsIndices.Items[ii]; - if (otherIndices.Count != 3) continue; - int otherFirstIndex = otherIndices.Items[0]; - int otherSecondIndex = otherIndices.Items[1]; - int otherLastIndex = otherIndices.Items[2]; - - var otherPoly = convexPolygons.Items[ii]; - float x3 = otherPoly.Items[otherPoly.Count - 2], y3 = otherPoly.Items[otherPoly.Count - 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 == winding && winding2 == winding) { - 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 = convexPolygons.Count - 1; i >= 0; i--) { - polygon = convexPolygons.Items[i]; - if (polygon.Count == 0) { - convexPolygons.RemoveAt(i); - polygonPool.Free(polygon); - polygonIndices = convexPolygonsIndices.Items[i]; - convexPolygonsIndices.RemoveAt(i); - polygonIndicesPool.Free(polygonIndices); - } - } - - return convexPolygons; - } - - static private bool IsConcave (int index, int vertexCount, float[] vertices, int[] 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]); - } - - static private bool PositiveArea (float p1x, float p1y, float p2x, float p2y, float p3x, float p3y) { - return p1x * (p3y - p2y) + p2x * (p1y - p3y) + p3x * (p2y - p1y) >= 0; - } - - static private int 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; - } - } +namespace Spine4_0_64 +{ + public class Triangulator + { + private readonly ExposedList> convexPolygons = new ExposedList>(); + private readonly ExposedList> convexPolygonsIndices = new ExposedList>(); + + private readonly ExposedList indicesArray = new ExposedList(); + private readonly ExposedList isConcaveArray = new ExposedList(); + private readonly ExposedList triangles = new ExposedList(); + + private readonly Pool> polygonPool = new Pool>(); + private readonly Pool> polygonIndicesPool = new Pool>(); + + public ExposedList Triangulate(ExposedList verticesArray) + { + var vertices = verticesArray.Items; + int vertexCount = verticesArray.Count >> 1; + + var indicesArray = this.indicesArray; + indicesArray.Clear(); + int[] indices = indicesArray.Resize(vertexCount).Items; + for (int i = 0; i < vertexCount; i++) + indices[i] = i; + + var isConcaveArray = this.isConcaveArray; + bool[] isConcave = isConcaveArray.Resize(vertexCount).Items; + for (int i = 0, n = vertexCount; i < n; ++i) + isConcave[i] = IsConcave(i, vertexCount, vertices, indices); + + var triangles = this.triangles; + triangles.Clear(); + triangles.EnsureCapacity(Math.Max(0, vertexCount - 2) << 2); + + while (vertexCount > 3) + { + // Find ear tip. + int previous = vertexCount - 1, i = 0, next = 1; + + // outer: + while (true) + { + if (!isConcave[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 (int ii = (next + 1) % vertexCount; ii != previous; ii = (ii + 1) % vertexCount) + { + if (!isConcave[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 (!isConcave[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]); + indicesArray.RemoveAt(i); + isConcaveArray.RemoveAt(i); + vertexCount--; + + int previousIndex = (vertexCount + i - 1) % vertexCount; + int nextIndex = i == vertexCount ? 0 : i; + isConcave[previousIndex] = IsConcave(previousIndex, vertexCount, vertices, indices); + isConcave[nextIndex] = IsConcave(nextIndex, vertexCount, vertices, indices); + } + + if (vertexCount == 3) + { + triangles.Add(indices[2]); + triangles.Add(indices[0]); + triangles.Add(indices[1]); + } + + return triangles; + } + + public ExposedList> Decompose(ExposedList verticesArray, ExposedList triangles) + { + var vertices = verticesArray.Items; + var convexPolygons = this.convexPolygons; + for (int i = 0, n = convexPolygons.Count; i < n; i++) + polygonPool.Free(convexPolygons.Items[i]); + convexPolygons.Clear(); + + var convexPolygonsIndices = this.convexPolygonsIndices; + for (int i = 0, n = convexPolygonsIndices.Count; i < n; i++) + polygonIndicesPool.Free(convexPolygonsIndices.Items[i]); + convexPolygonsIndices.Clear(); + + var polygonIndices = polygonIndicesPool.Obtain(); + polygonIndices.Clear(); + + var polygon = polygonPool.Obtain(); + polygon.Clear(); + + // Merge subsequent triangles if they form a triangle fan. + int fanBaseIndex = -1, lastWinding = 0; + int[] trianglesItems = triangles.Items; + for (int i = 0, n = triangles.Count; i < n; i += 3) + { + int t1 = trianglesItems[i] << 1, t2 = trianglesItems[i + 1] << 1, t3 = trianglesItems[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). + var merged = false; + if (fanBaseIndex == t1) + { + int o = polygon.Count - 4; + float[] p = polygon.Items; + 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.Count > 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.Count > 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 (int i = 0, n = convexPolygons.Count; i < n; i++) + { + polygonIndices = convexPolygonsIndices.Items[i]; + if (polygonIndices.Count == 0) continue; + int firstIndex = polygonIndices.Items[0]; + int lastIndex = polygonIndices.Items[polygonIndices.Count - 1]; + + polygon = convexPolygons.Items[i]; + int o = polygon.Count - 4; + float[] p = polygon.Items; + 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 winding = Winding(prevPrevX, prevPrevY, prevX, prevY, firstX, firstY); + + for (int ii = 0; ii < n; ii++) + { + if (ii == i) continue; + var otherIndices = convexPolygonsIndices.Items[ii]; + if (otherIndices.Count != 3) continue; + int otherFirstIndex = otherIndices.Items[0]; + int otherSecondIndex = otherIndices.Items[1]; + int otherLastIndex = otherIndices.Items[2]; + + var otherPoly = convexPolygons.Items[ii]; + float x3 = otherPoly.Items[otherPoly.Count - 2], y3 = otherPoly.Items[otherPoly.Count - 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 == winding && winding2 == winding) + { + 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 = convexPolygons.Count - 1; i >= 0; i--) + { + polygon = convexPolygons.Items[i]; + if (polygon.Count == 0) + { + convexPolygons.RemoveAt(i); + polygonPool.Free(polygon); + polygonIndices = convexPolygonsIndices.Items[i]; + convexPolygonsIndices.RemoveAt(i); + polygonIndicesPool.Free(polygonIndices); + } + } + + return convexPolygons; + } + + static private bool IsConcave(int index, int vertexCount, float[] vertices, int[] 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]); + } + + static private bool PositiveArea(float p1x, float p1y, float p2x, float p2y, float p3x, float p3y) + { + return p1x * (p3y - p2y) + p2x * (p1y - p3y) + p3x * (p2y - p1y) >= 0; + } + + static private int 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/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/XnaLoader/MeshBatcher.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/XnaLoader/MeshBatcher.cs index 398c3f3..6953caa 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/XnaLoader/MeshBatcher.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/XnaLoader/MeshBatcher.cs @@ -27,169 +27,185 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; -namespace Spine4_0_64 { - public struct VertexPositionColorTextureColor : IVertexType { - public Vector3 Position; - public Color Color; - public Vector2 TextureCoordinate; - public Color Color2; - - public readonly static VertexDeclaration VertexDeclaration = new VertexDeclaration - ( - new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0), - new VertexElement(12, VertexElementFormat.Color, VertexElementUsage.Color, 0), - new VertexElement(16, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0), - new VertexElement(24, VertexElementFormat.Color, VertexElementUsage.Color, 1) - ); - - VertexDeclaration IVertexType.VertexDeclaration { - get { return VertexDeclaration; } - } - } - - // #region License - // /* - // Microsoft Public License (Ms-PL) - // MonoGame - Copyright � 2009 The MonoGame Team - // - // All rights reserved. - // - // This license governs use of the accompanying software. If you use the software, you accept this license. If you do not - // accept the license, do not use the software. - // - // 1. Definitions - // The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under - // U.S. copyright law. - // - // A "contribution" is the original software, or any additions or changes to the software. - // A "contributor" is any person that distributes its contribution under this license. - // "Licensed patents" are a contributor's patent claims that read directly on its contribution. - // - // 2. Grant of Rights - // (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, - // each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. - // (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, - // each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. - // - // 3. Conditions and Limitations - // (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. - // (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, - // your patent license from such contributor to the software ends automatically. - // (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution - // notices that are present in the software. - // (D) If you distribute any portion of the software in source code form, you may do so only under this license by including - // a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object - // code form, you may only do so under a license that complies with this license. - // (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees - // or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent - // permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular - // purpose and non-infringement. - // */ - // #endregion License - // - - /// Draws batched meshes. - public class MeshBatcher { - private readonly List items; - private readonly Queue freeItems; - private VertexPositionColorTextureColor[] vertexArray = { }; - private short[] triangles = { }; - - public MeshBatcher () { - items = new List(256); - freeItems = new Queue(256); - EnsureCapacity(256, 512); - } - - /// Returns a pooled MeshItem. - public MeshItem NextItem (int vertexCount, int triangleCount) { - MeshItem item = freeItems.Count > 0 ? freeItems.Dequeue() : new MeshItem(); - if (item.vertices.Length < vertexCount) item.vertices = new VertexPositionColorTextureColor[vertexCount]; - if (item.triangles.Length < triangleCount) item.triangles = new int[triangleCount]; - item.vertexCount = vertexCount; - item.triangleCount = triangleCount; - items.Add(item); - return item; - } - - private void EnsureCapacity (int vertexCount, int triangleCount) { - if (vertexArray.Length < vertexCount) vertexArray = new VertexPositionColorTextureColor[vertexCount]; - if (triangles.Length < triangleCount) triangles = new short[triangleCount]; - } - - public void Draw (GraphicsDevice device) { - if (items.Count == 0) return; - - int itemCount = items.Count; - int vertexCount = 0, triangleCount = 0; - for (int i = 0; i < itemCount; i++) { - MeshItem item = items[i]; - vertexCount += item.vertexCount; - triangleCount += item.triangleCount; - } - EnsureCapacity(vertexCount, triangleCount); - - vertexCount = 0; - triangleCount = 0; - Texture2D lastTexture = null; - for (int i = 0; i < itemCount; i++) { - MeshItem item = items[i]; - int itemVertexCount = item.vertexCount; - - if (item.texture != lastTexture || vertexCount + itemVertexCount > short.MaxValue) { - FlushVertexArray(device, vertexCount, triangleCount); - vertexCount = 0; - triangleCount = 0; - lastTexture = item.texture; - device.Textures[0] = lastTexture; - if (item.textureLayers != null) { - for (int layer = 1; layer < item.textureLayers.Length; ++layer) - device.Textures[layer] = item.textureLayers[layer]; - } - } - - int[] itemTriangles = item.triangles; - int itemTriangleCount = item.triangleCount; - for (int ii = 0, t = triangleCount; ii < itemTriangleCount; ii++, t++) - triangles[t] = (short)(itemTriangles[ii] + vertexCount); - triangleCount += itemTriangleCount; - - Array.Copy(item.vertices, 0, vertexArray, vertexCount, itemVertexCount); - vertexCount += itemVertexCount; - } - FlushVertexArray(device, vertexCount, triangleCount); - } - - public void AfterLastDrawPass () { - int itemCount = items.Count; - for (int i = 0; i < itemCount; i++) { - var item = items[i]; - item.texture = null; - freeItems.Enqueue(item); - } - items.Clear(); - } - - private void FlushVertexArray (GraphicsDevice device, int vertexCount, int triangleCount) { - if (vertexCount == 0) return; - device.DrawUserIndexedPrimitives( - PrimitiveType.TriangleList, - vertexArray, 0, vertexCount, - triangles, 0, triangleCount / 3, - VertexPositionColorTextureColor.VertexDeclaration); - } - } - - public class MeshItem { - public Texture2D texture = null; - public Texture2D[] textureLayers = null; - public int vertexCount, triangleCount; - public VertexPositionColorTextureColor[] vertices = { }; - public int[] triangles = { }; - } +namespace Spine4_0_64 +{ + public struct VertexPositionColorTextureColor : IVertexType + { + public Vector3 Position; + public Color Color; + public Vector2 TextureCoordinate; + public Color Color2; + + public readonly static VertexDeclaration VertexDeclaration = new VertexDeclaration + ( + new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0), + new VertexElement(12, VertexElementFormat.Color, VertexElementUsage.Color, 0), + new VertexElement(16, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0), + new VertexElement(24, VertexElementFormat.Color, VertexElementUsage.Color, 1) + ); + + VertexDeclaration IVertexType.VertexDeclaration + { + get { return VertexDeclaration; } + } + } + + // #region License + // /* + // Microsoft Public License (Ms-PL) + // MonoGame - Copyright � 2009 The MonoGame Team + // + // All rights reserved. + // + // This license governs use of the accompanying software. If you use the software, you accept this license. If you do not + // accept the license, do not use the software. + // + // 1. Definitions + // The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under + // U.S. copyright law. + // + // A "contribution" is the original software, or any additions or changes to the software. + // A "contributor" is any person that distributes its contribution under this license. + // "Licensed patents" are a contributor's patent claims that read directly on its contribution. + // + // 2. Grant of Rights + // (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, + // each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. + // (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, + // each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. + // + // 3. Conditions and Limitations + // (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. + // (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, + // your patent license from such contributor to the software ends automatically. + // (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution + // notices that are present in the software. + // (D) If you distribute any portion of the software in source code form, you may do so only under this license by including + // a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object + // code form, you may only do so under a license that complies with this license. + // (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees + // or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent + // permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular + // purpose and non-infringement. + // */ + // #endregion License + // + + /// Draws batched meshes. + public class MeshBatcher + { + private readonly List items; + private readonly Queue freeItems; + private VertexPositionColorTextureColor[] vertexArray = { }; + private short[] triangles = { }; + + public MeshBatcher() + { + items = new List(256); + freeItems = new Queue(256); + EnsureCapacity(256, 512); + } + + /// Returns a pooled MeshItem. + public MeshItem NextItem(int vertexCount, int triangleCount) + { + MeshItem item = freeItems.Count > 0 ? freeItems.Dequeue() : new MeshItem(); + if (item.vertices.Length < vertexCount) item.vertices = new VertexPositionColorTextureColor[vertexCount]; + if (item.triangles.Length < triangleCount) item.triangles = new int[triangleCount]; + item.vertexCount = vertexCount; + item.triangleCount = triangleCount; + items.Add(item); + return item; + } + + private void EnsureCapacity(int vertexCount, int triangleCount) + { + if (vertexArray.Length < vertexCount) vertexArray = new VertexPositionColorTextureColor[vertexCount]; + if (triangles.Length < triangleCount) triangles = new short[triangleCount]; + } + + public void Draw(GraphicsDevice device) + { + if (items.Count == 0) return; + + int itemCount = items.Count; + int vertexCount = 0, triangleCount = 0; + for (int i = 0; i < itemCount; i++) + { + MeshItem item = items[i]; + vertexCount += item.vertexCount; + triangleCount += item.triangleCount; + } + EnsureCapacity(vertexCount, triangleCount); + + vertexCount = 0; + triangleCount = 0; + Texture2D lastTexture = null; + for (int i = 0; i < itemCount; i++) + { + MeshItem item = items[i]; + int itemVertexCount = item.vertexCount; + + if (item.texture != lastTexture || vertexCount + itemVertexCount > short.MaxValue) + { + FlushVertexArray(device, vertexCount, triangleCount); + vertexCount = 0; + triangleCount = 0; + lastTexture = item.texture; + device.Textures[0] = lastTexture; + if (item.textureLayers != null) + { + for (int layer = 1; layer < item.textureLayers.Length; ++layer) + device.Textures[layer] = item.textureLayers[layer]; + } + } + + int[] itemTriangles = item.triangles; + int itemTriangleCount = item.triangleCount; + for (int ii = 0, t = triangleCount; ii < itemTriangleCount; ii++, t++) + triangles[t] = (short)(itemTriangles[ii] + vertexCount); + triangleCount += itemTriangleCount; + + Array.Copy(item.vertices, 0, vertexArray, vertexCount, itemVertexCount); + vertexCount += itemVertexCount; + } + FlushVertexArray(device, vertexCount, triangleCount); + } + + public void AfterLastDrawPass() + { + int itemCount = items.Count; + for (int i = 0; i < itemCount; i++) + { + var item = items[i]; + item.texture = null; + freeItems.Enqueue(item); + } + items.Clear(); + } + + private void FlushVertexArray(GraphicsDevice device, int vertexCount, int triangleCount) + { + if (vertexCount == 0) return; + device.DrawUserIndexedPrimitives( + PrimitiveType.TriangleList, + vertexArray, 0, vertexCount, + triangles, 0, triangleCount / 3, + VertexPositionColorTextureColor.VertexDeclaration); + } + } + + public class MeshItem + { + public Texture2D texture = null; + public Texture2D[] textureLayers = null; + public int vertexCount, triangleCount; + public VertexPositionColorTextureColor[] vertices = { }; + public int[] triangles = { }; + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/XnaLoader/ShapeRenderer.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/XnaLoader/ShapeRenderer.cs index 6700687..679cde3 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/XnaLoader/ShapeRenderer.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/XnaLoader/ShapeRenderer.cs @@ -27,139 +27,156 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Spine4_0_64 { - /// - /// Batch drawing of lines and shapes that can be derived from lines. - /// - /// Call drawing methods in between Begin()/End() - /// - public class ShapeRenderer { - GraphicsDevice device; - List vertices = new List(); - Color color = Color.White; - BasicEffect effect; - public BasicEffect Effect { get { return effect; } set { effect = value; } } - - public ShapeRenderer (GraphicsDevice device) { - this.device = device; - this.effect = new BasicEffect(device); - effect.World = Matrix.Identity; - effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); - effect.TextureEnabled = false; - effect.VertexColorEnabled = true; - } - - public void SetColor (Color color) { - this.color = color; - } - - public void Begin () { - device.RasterizerState = new RasterizerState(); - device.BlendState = BlendState.AlphaBlend; - } - - public void Line (float x1, float y1, float x2, float y2) { - vertices.Add(new VertexPositionColor(new Vector3(x1, y1, 0), color)); - vertices.Add(new VertexPositionColor(new Vector3(x2, y2, 0), color)); - } - - /** Calls {@link #circle(float, float, float, int)} by estimating the number of segments needed for a smooth circle. */ - public void Circle (float x, float y, float radius) { - Circle(x, y, radius, Math.Max(1, (int)(6 * (float)Math.Pow(radius, 1.0f / 3.0f)))); - } - - /** Draws a circle using {@link ShapeType#Line} or {@link ShapeType#Filled}. */ - public void Circle (float x, float y, float radius, int segments) { - if (segments <= 0) throw new ArgumentException("segments must be > 0."); - float angle = 2 * MathUtils.PI / segments; - float cos = MathUtils.Cos(angle); - float sin = MathUtils.Sin(angle); - float cx = radius, cy = 0; - float temp = 0; - - for (int i = 0; i < segments; i++) { - vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); - temp = cx; - cx = cos * cx - sin * cy; - cy = sin * temp + cos * cy; - vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); - } - vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); - - temp = cx; - cx = radius; - cy = 0; - vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); - } - - public void Triangle (float x1, float y1, float x2, float y2, float x3, float y3) { - vertices.Add(new VertexPositionColor(new Vector3(x1, y1, 0), color)); - vertices.Add(new VertexPositionColor(new Vector3(x2, y2, 0), color)); - - vertices.Add(new VertexPositionColor(new Vector3(x2, y2, 0), color)); - vertices.Add(new VertexPositionColor(new Vector3(x3, y3, 0), color)); - - vertices.Add(new VertexPositionColor(new Vector3(x3, y3, 0), color)); - vertices.Add(new VertexPositionColor(new Vector3(x1, y1, 0), color)); - } - - public void X (float x, float y, float len) { - Line(x + len, y + len, x - len, y - len); - Line(x - len, y + len, x + len, y - len); - } - - public void Polygon (float[] polygonVertices, int offset, int count) { - if (count < 3) throw new ArgumentException("Polygon must contain at least 3 vertices"); - - offset <<= 1; - - var firstX = polygonVertices[offset]; - var firstY = polygonVertices[offset + 1]; - var last = offset + count; - - for (int i = offset, n = offset + count; i < n; i += 2) { - var x1 = polygonVertices[i]; - var y1 = polygonVertices[i + 1]; - - var x2 = 0f; - var y2 = 0f; - - if (i + 2 >= last) { - x2 = firstX; - y2 = firstY; - } else { - x2 = polygonVertices[i + 2]; - y2 = polygonVertices[i + 3]; - } - - Line(x1, y1, x2, y2); - } - } - - public void Rect (float x, float y, float width, float height) { - Line(x, y, x + width, y); - Line(x + width, y, x + width, y + height); - Line(x + width, y + height, x, y + height); - Line(x, y + height, x, y); - } - - public void End () { - if (vertices.Count == 0) return; - var verticesArray = vertices.ToArray(); - - foreach (EffectPass pass in effect.CurrentTechnique.Passes) { - pass.Apply(); - device.DrawUserPrimitives(PrimitiveType.LineList, verticesArray, 0, verticesArray.Length / 2); - } - - vertices.Clear(); - } - } +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Spine4_0_64 +{ + /// + /// Batch drawing of lines and shapes that can be derived from lines. + /// + /// Call drawing methods in between Begin()/End() + /// + public class ShapeRenderer + { + GraphicsDevice device; + List vertices = new List(); + Color color = Color.White; + BasicEffect effect; + public BasicEffect Effect { get { return effect; } set { effect = value; } } + + public ShapeRenderer(GraphicsDevice device) + { + this.device = device; + this.effect = new BasicEffect(device); + effect.World = Matrix.Identity; + effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); + effect.TextureEnabled = false; + effect.VertexColorEnabled = true; + } + + public void SetColor(Color color) + { + this.color = color; + } + + public void Begin() + { + device.RasterizerState = new RasterizerState(); + device.BlendState = BlendState.AlphaBlend; + } + + public void Line(float x1, float y1, float x2, float y2) + { + vertices.Add(new VertexPositionColor(new Vector3(x1, y1, 0), color)); + vertices.Add(new VertexPositionColor(new Vector3(x2, y2, 0), color)); + } + + /** Calls {@link #circle(float, float, float, int)} by estimating the number of segments needed for a smooth circle. */ + public void Circle(float x, float y, float radius) + { + Circle(x, y, radius, Math.Max(1, (int)(6 * (float)Math.Pow(radius, 1.0f / 3.0f)))); + } + + /** Draws a circle using {@link ShapeType#Line} or {@link ShapeType#Filled}. */ + public void Circle(float x, float y, float radius, int segments) + { + if (segments <= 0) throw new ArgumentException("segments must be > 0."); + float angle = 2 * MathUtils.PI / segments; + float cos = MathUtils.Cos(angle); + float sin = MathUtils.Sin(angle); + float cx = radius, cy = 0; + float temp = 0; + + for (int i = 0; i < segments; i++) + { + vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); + temp = cx; + cx = cos * cx - sin * cy; + cy = sin * temp + cos * cy; + vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); + } + vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); + + temp = cx; + cx = radius; + cy = 0; + vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); + } + + public void Triangle(float x1, float y1, float x2, float y2, float x3, float y3) + { + vertices.Add(new VertexPositionColor(new Vector3(x1, y1, 0), color)); + vertices.Add(new VertexPositionColor(new Vector3(x2, y2, 0), color)); + + vertices.Add(new VertexPositionColor(new Vector3(x2, y2, 0), color)); + vertices.Add(new VertexPositionColor(new Vector3(x3, y3, 0), color)); + + vertices.Add(new VertexPositionColor(new Vector3(x3, y3, 0), color)); + vertices.Add(new VertexPositionColor(new Vector3(x1, y1, 0), color)); + } + + public void X(float x, float y, float len) + { + Line(x + len, y + len, x - len, y - len); + Line(x - len, y + len, x + len, y - len); + } + + public void Polygon(float[] polygonVertices, int offset, int count) + { + if (count < 3) throw new ArgumentException("Polygon must contain at least 3 vertices"); + + offset <<= 1; + + var firstX = polygonVertices[offset]; + var firstY = polygonVertices[offset + 1]; + var last = offset + count; + + for (int i = offset, n = offset + count; i < n; i += 2) + { + var x1 = polygonVertices[i]; + var y1 = polygonVertices[i + 1]; + + var x2 = 0f; + var y2 = 0f; + + if (i + 2 >= last) + { + x2 = firstX; + y2 = firstY; + } + else + { + x2 = polygonVertices[i + 2]; + y2 = polygonVertices[i + 3]; + } + + Line(x1, y1, x2, y2); + } + } + + public void Rect(float x, float y, float width, float height) + { + Line(x, y, x + width, y); + Line(x + width, y, x + width, y + height); + Line(x + width, y + height, x, y + height); + Line(x, y + height, x, y); + } + + public void End() + { + if (vertices.Count == 0) return; + var verticesArray = vertices.ToArray(); + + foreach (EffectPass pass in effect.CurrentTechnique.Passes) + { + pass.Apply(); + device.DrawUserPrimitives(PrimitiveType.LineList, verticesArray, 0, verticesArray.Length / 2); + } + + vertices.Clear(); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/XnaLoader/SkeletonRenderer.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/XnaLoader/SkeletonRenderer.cs index 04f2d05..226ed2e 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/XnaLoader/SkeletonRenderer.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/XnaLoader/SkeletonRenderer.cs @@ -29,128 +29,141 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; -using System; -using System.Collections.Generic; -namespace Spine4_0_64 { - /// Draws region and mesh attachments. - public class SkeletonRenderer { - private const int TL = 0; - private const int TR = 1; - private const int BL = 2; - private const int BR = 3; +namespace Spine4_0_64 +{ + /// Draws region and mesh attachments. + public class SkeletonRenderer + { + private const int TL = 0; + private const int TR = 1; + private const int BL = 2; + private const int BR = 3; - SkeletonClipping clipper = new SkeletonClipping(); - GraphicsDevice device; - MeshBatcher batcher; - public MeshBatcher Batcher { get { return batcher; } } - RasterizerState rasterizerState; - float[] vertices = new float[8]; - int[] quadTriangles = { 0, 1, 2, 2, 3, 0 }; - BlendState defaultBlendState; + SkeletonClipping clipper = new SkeletonClipping(); + GraphicsDevice device; + MeshBatcher batcher; + public MeshBatcher Batcher { get { return batcher; } } + RasterizerState rasterizerState; + float[] vertices = new float[8]; + int[] quadTriangles = { 0, 1, 2, 2, 3, 0 }; + BlendState defaultBlendState; - Effect effect; - public Effect Effect { get { return effect; } set { effect = value; } } - public IVertexEffect VertexEffect { get; set; } + Effect effect; + public Effect Effect { get { return effect; } set { effect = value; } } + public IVertexEffect VertexEffect { get; set; } - private bool premultipliedAlpha; - public bool PremultipliedAlpha { get { return premultipliedAlpha; } set { premultipliedAlpha = value; } } + private bool premultipliedAlpha; + public bool PremultipliedAlpha { get { return premultipliedAlpha; } set { premultipliedAlpha = value; } } - /// Attachments are rendered back to front in the x/y plane by the SkeletonRenderer. - /// Each attachment is offset by a customizable z-spacing value on the z-axis to avoid z-fighting - /// in shaders with ZWrite enabled. Typical values lie in the range [-0.1, 0]. - private float zSpacing = 0.0f; - public float ZSpacing { get { return zSpacing; } set { zSpacing = value; } } + /// Attachments are rendered back to front in the x/y plane by the SkeletonRenderer. + /// Each attachment is offset by a customizable z-spacing value on the z-axis to avoid z-fighting + /// in shaders with ZWrite enabled. Typical values lie in the range [-0.1, 0]. + private float zSpacing = 0.0f; + public float ZSpacing { get { return zSpacing; } set { zSpacing = value; } } - /// A Z position offset added at each vertex. - private float z = 0.0f; - public float Z { get { return z; } set { z = value; } } + /// A Z position offset added at each vertex. + private float z = 0.0f; + public float Z { get { return z; } set { z = value; } } - public SkeletonRenderer (GraphicsDevice device) { - this.device = device; + public SkeletonRenderer(GraphicsDevice device) + { + this.device = device; - batcher = new MeshBatcher(); + batcher = new MeshBatcher(); - var basicEffect = new BasicEffect(device); - basicEffect.World = Matrix.Identity; - basicEffect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); - basicEffect.TextureEnabled = true; - basicEffect.VertexColorEnabled = true; - effect = basicEffect; + var basicEffect = new BasicEffect(device); + basicEffect.World = Matrix.Identity; + basicEffect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); + basicEffect.TextureEnabled = true; + basicEffect.VertexColorEnabled = true; + effect = basicEffect; - rasterizerState = new RasterizerState(); - rasterizerState.CullMode = CullMode.None; + rasterizerState = new RasterizerState(); + rasterizerState.CullMode = CullMode.None; - Bone.yDown = true; - } + Bone.yDown = true; + } - public void Begin () { - defaultBlendState = premultipliedAlpha ? BlendState.AlphaBlend : BlendState.NonPremultiplied; + public void Begin() + { + defaultBlendState = premultipliedAlpha ? BlendState.AlphaBlend : BlendState.NonPremultiplied; - device.RasterizerState = rasterizerState; - device.BlendState = defaultBlendState; - } + device.RasterizerState = rasterizerState; + device.BlendState = defaultBlendState; + } - public void End () { - foreach (EffectPass pass in effect.CurrentTechnique.Passes) { - pass.Apply(); - batcher.Draw(device); - } - batcher.AfterLastDrawPass(); - } + public void End() + { + foreach (EffectPass pass in effect.CurrentTechnique.Passes) + { + pass.Apply(); + batcher.Draw(device); + } + batcher.AfterLastDrawPass(); + } - public void Draw (Skeleton skeleton) { - var drawOrder = skeleton.DrawOrder; - var drawOrderItems = skeleton.DrawOrder.Items; - float skeletonR = skeleton.R, skeletonG = skeleton.G, skeletonB = skeleton.B, skeletonA = skeleton.A; - Color color = new Color(); + public void Draw(Skeleton skeleton) + { + var drawOrder = skeleton.DrawOrder; + var drawOrderItems = skeleton.DrawOrder.Items; + float skeletonR = skeleton.R, skeletonG = skeleton.G, skeletonB = skeleton.B, skeletonA = skeleton.A; + Color color = new Color(); - if (VertexEffect != null) VertexEffect.Begin(skeleton); + if (VertexEffect != null) VertexEffect.Begin(skeleton); - for (int i = 0, n = drawOrder.Count; i < n; i++) { - Slot slot = drawOrderItems[i]; - Attachment attachment = slot.Attachment; - float attachmentZOffset = z + zSpacing * i; + for (int i = 0, n = drawOrder.Count; i < n; i++) + { + Slot slot = drawOrderItems[i]; + Attachment attachment = slot.Attachment; + float attachmentZOffset = z + zSpacing * i; - float attachmentColorR, attachmentColorG, attachmentColorB, attachmentColorA; - object textureObject = null; - int verticesCount = 0; - float[] vertices = this.vertices; - int indicesCount = 0; - int[] indices = null; - float[] uvs = null; + float attachmentColorR, attachmentColorG, attachmentColorB, attachmentColorA; + object textureObject = null; + int verticesCount = 0; + float[] vertices = this.vertices; + int indicesCount = 0; + int[] indices = null; + float[] uvs = null; - if (attachment is RegionAttachment) { - RegionAttachment regionAttachment = (RegionAttachment)attachment; - attachmentColorR = regionAttachment.R; attachmentColorG = regionAttachment.G; attachmentColorB = regionAttachment.B; attachmentColorA = regionAttachment.A; - AtlasRegion region = (AtlasRegion)regionAttachment.RendererObject; - textureObject = region.page.rendererObject; - verticesCount = 4; - regionAttachment.ComputeWorldVertices(slot.Bone, vertices, 0, 2); - indicesCount = 6; - indices = quadTriangles; - uvs = regionAttachment.UVs; - } else if (attachment is MeshAttachment) { - MeshAttachment mesh = (MeshAttachment)attachment; - attachmentColorR = mesh.R; attachmentColorG = mesh.G; attachmentColorB = mesh.B; attachmentColorA = mesh.A; - AtlasRegion region = (AtlasRegion)mesh.RendererObject; - textureObject = region.page.rendererObject; - int vertexCount = mesh.WorldVerticesLength; - if (vertices.Length < vertexCount) vertices = new float[vertexCount]; - verticesCount = vertexCount >> 1; - mesh.ComputeWorldVertices(slot, vertices); - indicesCount = mesh.Triangles.Length; - indices = mesh.Triangles; - uvs = mesh.UVs; - } else if (attachment is ClippingAttachment) { - ClippingAttachment clip = (ClippingAttachment)attachment; - clipper.ClipStart(slot, clip); - continue; - } else { - continue; - } + if (attachment is RegionAttachment) + { + RegionAttachment regionAttachment = (RegionAttachment)attachment; + attachmentColorR = regionAttachment.R; attachmentColorG = regionAttachment.G; attachmentColorB = regionAttachment.B; attachmentColorA = regionAttachment.A; + AtlasRegion region = (AtlasRegion)regionAttachment.RendererObject; + textureObject = region.page.rendererObject; + verticesCount = 4; + regionAttachment.ComputeWorldVertices(slot.Bone, vertices, 0, 2); + indicesCount = 6; + indices = quadTriangles; + uvs = regionAttachment.UVs; + } + else if (attachment is MeshAttachment) + { + MeshAttachment mesh = (MeshAttachment)attachment; + attachmentColorR = mesh.R; attachmentColorG = mesh.G; attachmentColorB = mesh.B; attachmentColorA = mesh.A; + AtlasRegion region = (AtlasRegion)mesh.RendererObject; + textureObject = region.page.rendererObject; + int vertexCount = mesh.WorldVerticesLength; + if (vertices.Length < vertexCount) vertices = new float[vertexCount]; + verticesCount = vertexCount >> 1; + mesh.ComputeWorldVertices(slot, vertices); + indicesCount = mesh.Triangles.Length; + indices = mesh.Triangles; + uvs = mesh.UVs; + } + else if (attachment is ClippingAttachment) + { + ClippingAttachment clip = (ClippingAttachment)attachment; + clipper.ClipStart(slot, clip); + continue; + } + else + { + continue; + } - // set blend state + // set blend state BlendState blendState = new BlendState(); Blend blendSrc; Blend blendDst; @@ -202,75 +215,87 @@ public void Draw (Skeleton skeleton) { break; } - if (device.BlendState != blendState) { + if (device.BlendState != blendState) + { End(); device.BlendState = blendState; - } + } - // calculate color - float a = skeletonA * slot.A * attachmentColorA; - if (premultipliedAlpha) { - color = new Color( - skeletonR * slot.R * attachmentColorR * a, - skeletonG * slot.G * attachmentColorG * a, - skeletonB * slot.B * attachmentColorB * a, a); - } else { - color = new Color( - skeletonR * slot.R * attachmentColorR, - skeletonG * slot.G * attachmentColorG, - skeletonB * slot.B * attachmentColorB, a); - } + // calculate color + float a = skeletonA * slot.A * attachmentColorA; + if (premultipliedAlpha) + { + color = new Color( + skeletonR * slot.R * attachmentColorR * a, + skeletonG * slot.G * attachmentColorG * a, + skeletonB * slot.B * attachmentColorB * a, a); + } + else + { + color = new Color( + skeletonR * slot.R * attachmentColorR, + skeletonG * slot.G * attachmentColorG, + skeletonB * slot.B * attachmentColorB, a); + } - Color darkColor = new Color(); - if (slot.HasSecondColor) { - if (premultipliedAlpha) { - darkColor = new Color(slot.R2 * a, slot.G2 * a, slot.B2 * a); - } else { - darkColor = new Color(slot.R2 * a, slot.G2 * a, slot.B2 * a); - } - } - darkColor.A = premultipliedAlpha ? (byte)255 : (byte)0; + Color darkColor = new Color(); + if (slot.HasSecondColor) + { + if (premultipliedAlpha) + { + darkColor = new Color(slot.R2 * a, slot.G2 * a, slot.B2 * a); + } + else + { + darkColor = new Color(slot.R2 * a, slot.G2 * a, slot.B2 * a); + } + } + darkColor.A = premultipliedAlpha ? (byte)255 : (byte)0; - // clip - if (clipper.IsClipping) { - clipper.ClipTriangles(vertices, verticesCount << 1, indices, indicesCount, uvs); - vertices = clipper.ClippedVertices.Items; - verticesCount = clipper.ClippedVertices.Count >> 1; - indices = clipper.ClippedTriangles.Items; - indicesCount = clipper.ClippedTriangles.Count; - uvs = clipper.ClippedUVs.Items; - } + // clip + if (clipper.IsClipping) + { + clipper.ClipTriangles(vertices, verticesCount << 1, indices, indicesCount, uvs); + vertices = clipper.ClippedVertices.Items; + verticesCount = clipper.ClippedVertices.Count >> 1; + indices = clipper.ClippedTriangles.Items; + indicesCount = clipper.ClippedTriangles.Count; + uvs = clipper.ClippedUVs.Items; + } - if (verticesCount == 0 || indicesCount == 0) - continue; + if (verticesCount == 0 || indicesCount == 0) + continue; - // submit to batch - MeshItem item = batcher.NextItem(verticesCount, indicesCount); - if (textureObject is Texture2D) - item.texture = (Texture2D)textureObject; - else { - item.textureLayers = (Texture2D[])textureObject; - item.texture = item.textureLayers[0]; - } - for (int ii = 0, nn = indicesCount; ii < nn; ii++) { - item.triangles[ii] = indices[ii]; - } - VertexPositionColorTextureColor[] itemVertices = item.vertices; - for (int ii = 0, v = 0, nn = verticesCount << 1; v < nn; ii++, v += 2) { - itemVertices[ii].Color = color; - itemVertices[ii].Color2 = darkColor; - itemVertices[ii].Position.X = vertices[v]; - itemVertices[ii].Position.Y = vertices[v + 1]; - itemVertices[ii].Position.Z = attachmentZOffset; - itemVertices[ii].TextureCoordinate.X = uvs[v]; - itemVertices[ii].TextureCoordinate.Y = uvs[v + 1]; - if (VertexEffect != null) VertexEffect.Transform(ref itemVertices[ii]); - } + // submit to batch + MeshItem item = batcher.NextItem(verticesCount, indicesCount); + if (textureObject is Texture2D) + item.texture = (Texture2D)textureObject; + else + { + item.textureLayers = (Texture2D[])textureObject; + item.texture = item.textureLayers[0]; + } + for (int ii = 0, nn = indicesCount; ii < nn; ii++) + { + item.triangles[ii] = indices[ii]; + } + VertexPositionColorTextureColor[] itemVertices = item.vertices; + for (int ii = 0, v = 0, nn = verticesCount << 1; v < nn; ii++, v += 2) + { + itemVertices[ii].Color = color; + itemVertices[ii].Color2 = darkColor; + itemVertices[ii].Position.X = vertices[v]; + itemVertices[ii].Position.Y = vertices[v + 1]; + itemVertices[ii].Position.Z = attachmentZOffset; + itemVertices[ii].TextureCoordinate.X = uvs[v]; + itemVertices[ii].TextureCoordinate.Y = uvs[v + 1]; + if (VertexEffect != null) VertexEffect.Transform(ref itemVertices[ii]); + } - clipper.ClipEnd(slot); - } - clipper.ClipEnd(); - if (VertexEffect != null) VertexEffect.End(); - } - } + clipper.ClipEnd(slot); + } + clipper.ClipEnd(); + if (VertexEffect != null) VertexEffect.End(); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/XnaLoader/VertexEffect.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/XnaLoader/VertexEffect.cs index 1dc6a3e..f1d5ad8 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/XnaLoader/VertexEffect.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/XnaLoader/VertexEffect.cs @@ -28,70 +28,80 @@ *****************************************************************************/ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -namespace Spine4_0_64 { - public interface IVertexEffect { - void Begin (Skeleton skeleton); - void Transform (ref VertexPositionColorTextureColor vertex); - void End (); - } +namespace Spine4_0_64 +{ + public interface IVertexEffect + { + void Begin(Skeleton skeleton); + void Transform(ref VertexPositionColorTextureColor vertex); + void End(); + } - public class JitterEffect : IVertexEffect { - public float JitterX { get; set; } - public float JitterY { get; set; } + public class JitterEffect : IVertexEffect + { + public float JitterX { get; set; } + public float JitterY { get; set; } - public JitterEffect (float jitterX, float jitterY) { - JitterX = jitterX; - JitterY = jitterY; - } + public JitterEffect(float jitterX, float jitterY) + { + JitterX = jitterX; + JitterY = jitterY; + } - public void Begin (Skeleton skeleton) { - } + public void Begin(Skeleton skeleton) + { + } - public void End () { - } + public void End() + { + } - public void Transform (ref VertexPositionColorTextureColor vertex) { - vertex.Position.X += MathUtils.RandomTriangle(-JitterX, JitterY); - vertex.Position.Y += MathUtils.RandomTriangle(-JitterX, JitterY); - } - } + public void Transform(ref VertexPositionColorTextureColor vertex) + { + vertex.Position.X += MathUtils.RandomTriangle(-JitterX, JitterY); + vertex.Position.Y += MathUtils.RandomTriangle(-JitterX, JitterY); + } + } - public class SwirlEffect : IVertexEffect { - private float worldX, worldY, angle; + public class SwirlEffect : IVertexEffect + { + private float worldX, worldY, angle; - public float Radius { get; set; } - public float Angle { get { return angle; } set { angle = value * MathUtils.DegRad; } } - public float CenterX { get; set; } - public float CenterY { get; set; } - public IInterpolation Interpolation { get; set; } + public float Radius { get; set; } + public float Angle { get { return angle; } set { angle = value * MathUtils.DegRad; } } + public float CenterX { get; set; } + public float CenterY { get; set; } + public IInterpolation Interpolation { get; set; } - public SwirlEffect (float radius) { - Radius = radius; - Interpolation = IInterpolation.Pow2; - } + public SwirlEffect(float radius) + { + Radius = radius; + Interpolation = IInterpolation.Pow2; + } - public void Begin (Skeleton skeleton) { - worldX = skeleton.X + CenterX; - worldY = skeleton.Y + CenterY; - } + public void Begin(Skeleton skeleton) + { + worldX = skeleton.X + CenterX; + worldY = skeleton.Y + CenterY; + } - public void End () { - } + public void End() + { + } - public void Transform (ref VertexPositionColorTextureColor vertex) { - float x = vertex.Position.X - worldX; - float y = vertex.Position.Y - worldY; - float dist = (float)Math.Sqrt(x * x + y * y); - if (dist < Radius) { - float theta = Interpolation.Apply(0, angle, (Radius - dist) / Radius); - float cos = MathUtils.Cos(theta), sin = MathUtils.Sin(theta); - vertex.Position.X = cos * x - sin * y + worldX; - vertex.Position.Y = sin * x + cos * y + worldY; - } - } - } + public void Transform(ref VertexPositionColorTextureColor vertex) + { + float x = vertex.Position.X - worldX; + float y = vertex.Position.Y - worldY; + float dist = (float)Math.Sqrt(x * x + y * y); + if (dist < Radius) + { + float theta = Interpolation.Apply(0, angle, (Radius - dist) / Radius); + float cos = MathUtils.Cos(theta), sin = MathUtils.Sin(theta); + vertex.Position.X = cos * x - sin * y + worldX; + vertex.Position.Y = sin * x + cos * y + worldY; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/XnaLoader/XnaTextureLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/XnaLoader/XnaTextureLoader.cs index e3b713f..350cfeb 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/XnaLoader/XnaTextureLoader.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.0.64/XnaLoader/XnaTextureLoader.cs @@ -27,57 +27,63 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; using System; -using System.IO; +using Microsoft.Xna.Framework.Graphics; -namespace Spine4_0_64 { - public class XnaTextureLoader : TextureLoader { - GraphicsDevice device; - string[] textureLayerSuffixes = null; +namespace Spine4_0_64 +{ + public class XnaTextureLoader : TextureLoader + { + GraphicsDevice device; + string[] textureLayerSuffixes = null; - /// - /// Constructor. - /// - /// The graphics device to be used. - /// If true multiple textures layers - /// (e.g. a diffuse/albedo texture and a normal map) are loaded instead of a single texture. - /// Names are constructed based on suffixes added according to the textureSuffixes parameter. - /// If loadMultipleTextureLayers is true, the strings of this array - /// define the path name suffix of each layer to be loaded. Array size must be equal to the number of layers to be loaded. - /// The first array entry is the suffix to be replaced (e.g. "_albedo", or "" for a first layer without a suffix), - /// subsequent array entries contain the suffix to replace the first entry with (e.g. "_normals"). - /// - /// An example would be: - /// new string[] { "", "_normals" } for loading a base diffuse texture named "skeletonname.png" and - /// a normalmap named "skeletonname_normals.png". - public XnaTextureLoader (GraphicsDevice device, bool loadMultipleTextureLayers = false, string[] textureSuffixes = null) { - this.device = device; - if (loadMultipleTextureLayers) - this.textureLayerSuffixes = textureSuffixes; - } + /// + /// Constructor. + /// + /// The graphics device to be used. + /// If true multiple textures layers + /// (e.g. a diffuse/albedo texture and a normal map) are loaded instead of a single texture. + /// Names are constructed based on suffixes added according to the textureSuffixes parameter. + /// If loadMultipleTextureLayers is true, the strings of this array + /// define the path name suffix of each layer to be loaded. Array size must be equal to the number of layers to be loaded. + /// The first array entry is the suffix to be replaced (e.g. "_albedo", or "" for a first layer without a suffix), + /// subsequent array entries contain the suffix to replace the first entry with (e.g. "_normals"). + /// + /// An example would be: + /// new string[] { "", "_normals" } for loading a base diffuse texture named "skeletonname.png" and + /// a normalmap named "skeletonname_normals.png". + public XnaTextureLoader(GraphicsDevice device, bool loadMultipleTextureLayers = false, string[] textureSuffixes = null) + { + this.device = device; + if (loadMultipleTextureLayers) + this.textureLayerSuffixes = textureSuffixes; + } + + public void Load(AtlasPage page, String path) + { + Texture2D texture = Util.LoadTexture(device, path); - public void Load (AtlasPage page, String path) { - Texture2D texture = Util.LoadTexture(device, path); - if (page.width == 0 || page.height == 0) { page.width = texture.Width; page.height = texture.Height; } - if (textureLayerSuffixes == null) { - page.rendererObject = texture; - } else { - Texture2D[] textureLayersArray = new Texture2D[textureLayerSuffixes.Length]; - textureLayersArray[0] = texture; - for (int layer = 1; layer < textureLayersArray.Length; ++layer) { - string layerPath = GetLayerName(path, textureLayerSuffixes[0], textureLayerSuffixes[layer]); - textureLayersArray[layer] = Util.LoadTexture(device, layerPath); - } - page.rendererObject = textureLayersArray; - } - } + if (textureLayerSuffixes == null) + { + page.rendererObject = texture; + } + else + { + Texture2D[] textureLayersArray = new Texture2D[textureLayerSuffixes.Length]; + textureLayersArray[0] = texture; + for (int layer = 1; layer < textureLayersArray.Length; ++layer) + { + string layerPath = GetLayerName(path, textureLayerSuffixes[0], textureLayerSuffixes[layer]); + textureLayersArray[layer] = Util.LoadTexture(device, layerPath); + } + page.rendererObject = textureLayersArray; + } + } public void Unload(Object texture) { @@ -97,14 +103,16 @@ public void Unload(Object texture) } - private string GetLayerName (string firstLayerPath, string firstLayerSuffix, string replacementSuffix) { + private string GetLayerName(string firstLayerPath, string firstLayerSuffix, string replacementSuffix) + { - int suffixLocation = firstLayerPath.LastIndexOf(firstLayerSuffix + "."); - if (suffixLocation == -1) { - throw new Exception(string.Concat("Error composing texture layer name: first texture layer name '", firstLayerPath, - "' does not contain suffix to be replaced: '", firstLayerSuffix, "'")); - } - return firstLayerPath.Remove(suffixLocation, firstLayerSuffix.Length).Insert(suffixLocation, replacementSuffix); - } - } + int suffixLocation = firstLayerPath.LastIndexOf(firstLayerSuffix + "."); + if (suffixLocation == -1) + { + throw new Exception(string.Concat("Error composing texture layer name: first texture layer name '", firstLayerPath, + "' does not contain suffix to be replaced: '", firstLayerSuffix, "'")); + } + return firstLayerPath.Remove(suffixLocation, firstLayerSuffix.Length).Insert(suffixLocation, replacementSuffix); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Animation.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Animation.cs index b8c0830..aaafbf8 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Animation.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Animation.cs @@ -30,2664 +30,3069 @@ using System; using System.Collections.Generic; -namespace Spine4_1_00 { - - /// - /// Stores a list of timelines to animate a skeleton's pose over time. - public class Animation { - internal String name; - internal ExposedList timelines; - internal HashSet timelineIds; - internal float duration; - - public Animation (string name, ExposedList timelines, float duration) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - - this.name = name; - SetTimelines(timelines); - this.duration = duration; - } - - public ExposedList Timelines { - get { return timelines; } - set { SetTimelines(value); } - } - - public void SetTimelines (ExposedList timelines) { - if (timelines == null) throw new ArgumentNullException("timelines", "timelines cannot be null."); - this.timelines = timelines; - // Note: avoiding reallocations by adding all hash set entries at - // once (EnsureCapacity() is only available in newer .Net versions). - int idCount = 0; - int timelinesCount = timelines.Count; - Timeline[] timelinesItems = timelines.Items; - for (int t = 0; t < timelinesCount; ++t) - idCount += timelinesItems[t].PropertyIds.Length; - string[] propertyIds = new string[idCount]; - int currentId = 0; - for (int t = 0; t < timelinesCount; ++t) { - var ids = timelinesItems[t].PropertyIds; - for (int i = 0, idsLength = ids.Length; i < idsLength; ++i) - propertyIds[currentId++] = ids[i]; - } - this.timelineIds = new HashSet(propertyIds); - } - - /// The duration of the animation in seconds, which is usually the highest time of all frames in the timeline. The duration is - /// used to know when it has completed and when it should loop back to the start. - public float Duration { get { return duration; } set { duration = value; } } - - /// The animation's name, which is unique across all animations in the skeleton. - public string Name { get { return name; } } - - /// Returns true if this animation contains a timeline with any of the specified property IDs. - public bool HasTimeline (string[] propertyIds) { - foreach (string id in propertyIds) - if (timelineIds.Contains(id)) return true; - return false; - } - - /// Applies the animation's timelines to the specified skeleton. - /// - /// The skeleton the animation is being applied to. This provides access to the bones, slots, and other skeleton - /// components the timelines may change. - /// The last time in seconds this animation was applied. Some timelines trigger only at specific times rather - /// than every frame. Pass -1 the first time an animation is applied to ensure frame 0 is triggered. - /// The time in seconds the skeleton is being posed for. Most timelines find the frame before and the frame after - /// this time and interpolate between the frame values. If beyond the and loop is - /// true then the animation will repeat, else the last frame will be applied. - /// If true, the animation repeats after the . - /// If any events are fired, they are added to this list. Can be null to ignore fired events or if no timelines - /// fire events. - /// 0 applies the current or setup values (depending on blend). 1 applies the timeline values. Between - /// 0 and 1 applies values between the current or setup values and the timeline values. 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 (layering). - /// Controls how mixing is applied when alpha < 1. - /// Indicates whether the timelines are mixing in or out. Used by timelines which perform instant transitions, - /// such as or . - public void Apply (Skeleton skeleton, float lastTime, float time, bool loop, ExposedList events, float alpha, - MixBlend blend, MixDirection direction) { - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - - if (loop && duration != 0) { - time %= duration; - if (lastTime > 0) lastTime %= duration; - } - - var timelines = this.timelines.Items; - for (int i = 0, n = this.timelines.Count; i < n; i++) - timelines[i].Apply(skeleton, lastTime, time, events, alpha, blend, direction); - } - - override public string ToString () { - return name; - } - } - - /// - /// Controls how timeline values are mixed with setup pose values or current pose values when a timeline is applied with - /// alpha < 1. - /// - public enum MixBlend { - /// Transitions from the setup value to the timeline value (the current value is not used). Before the first frame, the - /// setup value is set. - Setup, - - /// - /// - /// Transitions from the current value to the timeline value. Before the first frame, transitions from the current value to - /// the setup value. Timelines which perform instant transitions, such as or - /// , use the setup value before the first frame. - /// - /// First is intended for the first animations applied, not for animations layered on top of those. - /// - First, - - /// - /// - /// Transitions from the current value to the timeline value. No change is made before the first frame (the current value is - /// kept until the first frame). - /// - /// Replace is intended for animations layered on top of others, not for the first animations applied. - /// - Replace, - - /// - /// - /// Transitions from the current value to the current value plus the timeline value. No change is made before the first frame - /// (the current value is kept until the first frame). - /// - /// Add is intended for animations layered on top of others, not for the first animations applied. Properties - /// set by additive animations must be set manually or by another animation before applying the additive animations, else the - /// property values will increase each time the additive animations are applied. - /// - /// - Add - } - - /// - /// Indicates whether a timeline's alpha is mixing out over time toward 0 (the setup or current pose value) or - /// mixing in toward 1 (the timeline's value). Some timelines use this to decide how values are applied. - /// - public enum MixDirection { - In, - Out - } - - internal enum Property { - Rotate = 0, X, Y, ScaleX, ScaleY, ShearX, ShearY, // - RGB, Alpha, RGB2, // - Attachment, Deform, // - Event, DrawOrder, // - IkConstraint, TransformConstraint, // - PathConstraintPosition, PathConstraintSpacing, PathConstraintMix, // - Sequence - } - - /// - /// The base class for all timelines. - public abstract class Timeline { - private readonly string[] propertyIds; - internal readonly float[] frames; - - /// Unique identifiers for the properties the timeline modifies. - public Timeline (int frameCount, params string[] propertyIds) { - if (propertyIds == null) throw new System.ArgumentNullException("propertyIds", "propertyIds cannot be null."); - this.propertyIds = propertyIds; - frames = new float[frameCount * FrameEntries]; - } - - /// Uniquely encodes both the type of this timeline and the skeleton properties that it affects. - public string[] PropertyIds { - get { return propertyIds; } - } - - /// The time in seconds and any other values for each frame. - public float[] Frames { - get { return frames; } - } - - /// The number of entries stored per frame. - public virtual int FrameEntries { - get { return 1; } - } - - /// The number of frames for this timeline. - public int FrameCount { - get { return frames.Length / FrameEntries; } - } - - public float Duration { - get { - return frames[frames.Length - FrameEntries]; - } - } - - /// Applies this timeline to the skeleton. - /// The skeleton the timeline is being applied to. This provides access to the bones, slots, and other - /// skeleton components the timeline may change. - /// The time this timeline was last applied. Timelines such as trigger only - /// at specific times rather than every frame. In that case, the timeline triggers everything between - /// lastTime (exclusive) and time (inclusive). Pass -1 the first time an animation is - /// applied to ensure frame 0 is triggered. - /// The time in seconds that the skeleton is being posed for. Most timelines find the frame before and the frame - /// after this time and interpolate between the frame values.If beyond the last frame, the last frame will be - /// applied. - /// If any events are fired, they are added to this list. Can be null to ignore fired events or if the timeline - /// does not fire events. - /// 0 applies the current or setup value (depending on blend). 1 applies the timeline value. - /// Between 0 and 1 applies a value between the current or setup value 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 (layering). - /// Controls how mixing is applied when alpha < 1. - /// Indicates whether the timeline is mixing in or out. Used by timelines which perform instant transitions, - /// such as or , and other such as . - public abstract void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, - MixBlend blend, MixDirection direction); - - /// Search using a stride of 1. - /// Must be >= the first value in frames. - /// The index of the first value <= time. - internal static int Search (float[] frames, float time) { - int n = frames.Length; - for (int i = 1; i < n; i++) - if (frames[i] > time) return i - 1; - return n - 1; - } - - /// Search using the specified stride. - /// Must be >= the first value in frames. - /// The index of the first value <= time. - internal static int Search (float[] frames, float time, int step) { - int n = frames.Length; - for (int i = step; i < n; i += step) - if (frames[i] > time) return i - step; - return n - step; - } - } - - /// An interface for timelines which change the property of a bone. - public interface IBoneTimeline { - /// The index of the bone in that will be changed when this timeline is applied. - int BoneIndex { get; } - } - - /// An interface for timelines which change the property of a slot. - public interface ISlotTimeline { - /// The index of the slot in that will be changed when this timeline is applied. - int SlotIndex { get; } - } - - /// The base class for timelines that interpolate between frame values using stepped, linear, or a Bezier curve. - public abstract class CurveTimeline : Timeline { - public const int LINEAR = 0, STEPPED = 1, BEZIER = 2, BEZIER_SIZE = 18; - - internal float[] curves; - /// The number of key frames for this timeline. - - /// The maximum number of Bezier curves. See . - /// Unique identifiers for the properties the timeline modifies. - public CurveTimeline (int frameCount, int bezierCount, params string[] propertyIds) - : base(frameCount, propertyIds) { - curves = new float[frameCount + bezierCount * BEZIER_SIZE]; - curves[frameCount - 1] = STEPPED; - } - - /// Sets the specified frame to linear interpolation. - /// Between 0 and frameCount - 1, inclusive. - public void SetLinear (int frame) { - curves[frame] = LINEAR; - } - - /// Sets the specified frame to stepped interpolation. - /// Between 0 and frameCount - 1, inclusive. - public void SetStepped (int frame) { - curves[frame] = STEPPED; - } - - /// Returns the interpolation type for the specified frame. - /// Between 0 and frameCount - 1, inclusive. - /// , or + the index of the Bezier segments. - public float GetCurveType (int frame) { - return (int)curves[frame]; - } - - /// Shrinks the storage for Bezier curves, for use when bezierCount (specified in the constructor) was larger - /// than the actual number of Bezier curves. - public void Shrink (int bezierCount) { - int size = FrameCount + bezierCount * BEZIER_SIZE; - if (curves.Length > size) { - float[] newCurves = new float[size]; - Array.Copy(curves, 0, newCurves, 0, size); - curves = newCurves; - } - } - - /// - /// Stores the segments for the specified Bezier curve. For timelines that modify multiple values, there may be more than - /// one curve per frame. - /// The ordinal of this Bezier curve for this timeline, between 0 and bezierCount - 1 (specified - /// in the constructor), inclusive. - /// Between 0 and frameCount - 1, inclusive. - /// The index of the value for the frame this curve is used for. - /// The time for the first key. - /// The value for the first key. - /// The time for the first Bezier handle. - /// The value for the first Bezier handle. - /// The time of the second Bezier handle. - /// The value for the second Bezier handle. - /// The time for the second key. - /// The value for the second key. - public void SetBezier (int bezier, int frame, int value, float time1, float value1, float cx1, float cy1, float cx2, - float cy2, float time2, float value2) { - - float[] curves = this.curves; - int i = FrameCount + bezier * BEZIER_SIZE; - if (value == 0) curves[frame] = BEZIER + i; - float tmpx = (time1 - cx1 * 2 + cx2) * 0.03f, tmpy = (value1 - cy1 * 2 + cy2) * 0.03f; - float dddx = ((cx1 - cx2) * 3 - time1 + time2) * 0.006f, dddy = ((cy1 - cy2) * 3 - value1 + value2) * 0.006f; - float ddx = tmpx * 2 + dddx, ddy = tmpy * 2 + dddy; - float dx = (cx1 - time1) * 0.3f + tmpx + dddx * 0.16666667f, dy = (cy1 - value1) * 0.3f + tmpy + dddy * 0.16666667f; - float x = time1 + dx, y = value1 + dy; - for (int 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; - } - } - - /// - /// Returns the Bezier interpolated value for the specified time. - /// The index into for the values of the frame before time. - /// The offset from frameIndex to the value this curve is used for. - /// The index of the Bezier segments. See . - public float GetBezierValue (float time, int frameIndex, int valueOffset, int i) { - float[] curves = this.curves; - if (curves[i] > time) { - float x = frames[frameIndex], y = frames[frameIndex + valueOffset]; - return y + (time - x) / (curves[i] - x) * (curves[i + 1] - y); - } - int 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 += FrameEntries; - { // scope added to prevent compile error "float x and y declared in enclosing scope" - float x = curves[n - 2], y = curves[n - 1]; - return y + (time - x) / (frames[frameIndex] - x) * (frames[frameIndex + valueOffset] - y); - } - } - } - - /// The base class for a that sets one property. - public abstract class CurveTimeline1 : CurveTimeline { - public const int ENTRIES = 2; - internal const int VALUE = 1; - - /// The maximum number of Bezier curves. See . - /// Unique identifiers for the properties the timeline modifies. - public CurveTimeline1 (int frameCount, int bezierCount, string propertyId) - : base(frameCount, bezierCount, propertyId) { - } - - public override int FrameEntries { - get { return ENTRIES; } - } - - /// Sets the time and value for the specified frame. - /// Between 0 and frameCount, inclusive. - /// The frame time in seconds - public void SetFrame (int frame, float time, float value) { - frame <<= 1; - frames[frame] = time; - frames[frame + VALUE] = value; - } - - /// Returns the interpolated value for the specified time. - public float GetCurveValue (float time) { - float[] frames = this.frames; - int i = frames.Length - 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 LINEAR: - float before = frames[i], value = frames[i + VALUE]; - return value + (time - before) / (frames[i + ENTRIES] - before) * (frames[i + ENTRIES + VALUE] - value); - case STEPPED: - return frames[i + VALUE]; - } - return GetBezierValue(time, i, VALUE, curveType - BEZIER); - } - } - - /// The base class for a which sets two properties. - public abstract class CurveTimeline2 : CurveTimeline { - public const int ENTRIES = 3; - internal const int VALUE1 = 1, VALUE2 = 2; - - /// The maximum number of Bezier curves. See . - /// Unique identifiers for the properties the timeline modifies. - public CurveTimeline2 (int frameCount, int bezierCount, string propertyId1, string propertyId2) - : base(frameCount, bezierCount, propertyId1, propertyId2) { - } - - public override int FrameEntries { - get { return ENTRIES; } - } - - /// Sets the time and values for the specified frame. - /// Between 0 and frameCount, inclusive. - /// The frame time in seconds. - public void SetFrame (int frame, float time, float value1, float value2) { - frame *= ENTRIES; - frames[frame] = time; - frames[frame + VALUE1] = value1; - frames[frame + VALUE2] = value2; - } - } - - /// Changes a bone's local . - public class RotateTimeline : CurveTimeline1, IBoneTimeline { - readonly int boneIndex; - - public RotateTimeline (int frameCount, int bezierCount, int boneIndex) - : base(frameCount, bezierCount, (int)Property.Rotate + "|" + boneIndex) { - this.boneIndex = boneIndex; - } - - public int BoneIndex { - get { - return boneIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - if (!bone.active) return; - - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case MixBlend.Setup: - bone.rotation = bone.data.rotation; - return; - case MixBlend.First: - bone.rotation += (bone.data.rotation - bone.rotation) * alpha; - return; - } - return; - } - - float r = GetCurveValue(time); - switch (blend) { - case MixBlend.Setup: - bone.rotation = bone.data.rotation + r * alpha; - break; - case MixBlend.First: - case MixBlend.Replace: - r += bone.data.rotation - bone.rotation; - goto case MixBlend.Add; // Fall through. - case MixBlend.Add: - bone.rotation += r * alpha; - break; - } - } - } - - /// Changes a bone's local and . - public class TranslateTimeline : CurveTimeline2, IBoneTimeline { - readonly int boneIndex; - - public TranslateTimeline (int frameCount, int bezierCount, int boneIndex) - : base(frameCount, bezierCount, // - (int)Property.X + "|" + boneIndex, // - (int)Property.Y + "|" + boneIndex) { - this.boneIndex = boneIndex; - } - - public int BoneIndex { - get { - return boneIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - if (!bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - 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; - return; - } - return; - } - - float x, y; - GetCurveValue(out x, out y, time); // note: reference implementation has code inlined - - 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; - break; - } - } - - public void GetCurveValue (out float x, out float y, float time) { - int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; - switch (curveType) { - case LINEAR: - float before = frames[i]; - x = frames[i + VALUE1]; - y = frames[i + VALUE2]; - float t = (time - before) / (frames[i + ENTRIES] - before); - x += (frames[i + ENTRIES + VALUE1] - x) * t; - y += (frames[i + ENTRIES + VALUE2] - y) * t; - break; - case STEPPED: - x = frames[i + VALUE1]; - y = frames[i + VALUE2]; - break; - default: - x = GetBezierValue(time, i, VALUE1, curveType - BEZIER); - y = GetBezierValue(time, i, VALUE2, curveType + BEZIER_SIZE - BEZIER); - break; - } - } - } - - /// Changes a bone's local . - public class TranslateXTimeline : CurveTimeline1, IBoneTimeline { - readonly int boneIndex; - - public TranslateXTimeline (int frameCount, int bezierCount, int boneIndex) - : base(frameCount, bezierCount, (int)Property.X + "|" + boneIndex) { - this.boneIndex = boneIndex; - } - - public int BoneIndex { - get { - return boneIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - if (!bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case MixBlend.Setup: - bone.x = bone.data.x; - return; - case MixBlend.First: - bone.x += (bone.data.x - bone.x) * alpha; - return; - } - return; - } - - float x = GetCurveValue(time); - switch (blend) { - case MixBlend.Setup: - bone.x = bone.data.x + x * alpha; - break; - case MixBlend.First: - case MixBlend.Replace: - bone.x += (bone.data.x + x - bone.x) * alpha; - break; - case MixBlend.Add: - bone.x += x * alpha; - break; - } - } - } - - /// Changes a bone's local . - public class TranslateYTimeline : CurveTimeline1, IBoneTimeline { - readonly int boneIndex; - - public TranslateYTimeline (int frameCount, int bezierCount, int boneIndex) - : base(frameCount, bezierCount, (int)Property.Y + "|" + boneIndex) { - this.boneIndex = boneIndex; - } - - public int BoneIndex { - get { - return boneIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - if (!bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case MixBlend.Setup: - bone.y = bone.data.y; - return; - case MixBlend.First: - bone.y += (bone.data.y - bone.y) * alpha; - return; - } - return; - } - - float y = GetCurveValue(time); - switch (blend) { - case MixBlend.Setup: - bone.y = bone.data.y + y * alpha; - break; - case MixBlend.First: - case MixBlend.Replace: - bone.y += (bone.data.y + y - bone.y) * alpha; - break; - case MixBlend.Add: - bone.y += y * alpha; - break; - } - } - } - - /// Changes a bone's local and . - public class ScaleTimeline : CurveTimeline2, IBoneTimeline { - readonly int boneIndex; - - public ScaleTimeline (int frameCount, int bezierCount, int boneIndex) - : base(frameCount, bezierCount, // - (int)Property.ScaleX + "|" + boneIndex, // - (int)Property.ScaleY + "|" + boneIndex) { - this.boneIndex = boneIndex; - } - - public int BoneIndex { - get { - return boneIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - if (!bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - 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; - return; - } - return; - } - - float x, y; - int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; - switch (curveType) { - case LINEAR: - float before = frames[i]; - x = frames[i + VALUE1]; - y = frames[i + VALUE2]; - float t = (time - before) / (frames[i + ENTRIES] - before); - x += (frames[i + ENTRIES + VALUE1] - x) * t; - y += (frames[i + ENTRIES + VALUE2] - y) * t; - break; - case STEPPED: - x = frames[i + VALUE1]; - y = frames[i + VALUE2]; - break; - default: - x = GetBezierValue(time, i, VALUE1, curveType - BEZIER); - y = GetBezierValue(time, i, VALUE2, curveType + BEZIER_SIZE - BEZIER); - break; - } - 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 { - // Mixing out uses sign of setup or current pose, else use sign of key. - float bx, by; - if (direction == MixDirection.Out) { - switch (blend) { - case MixBlend.Setup: - bx = bone.data.scaleX; - by = bone.data.scaleY; - bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha; - bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha; - break; - case MixBlend.First: - case MixBlend.Replace: - bx = bone.scaleX; - by = bone.scaleY; - bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha; - bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha; - break; - case MixBlend.Add: - bone.scaleX += (x - bone.data.scaleX) * alpha; - bone.scaleY += (y - bone.data.scaleY) * alpha; - break; - } - } else { - switch (blend) { - case MixBlend.Setup: - bx = Math.Abs(bone.data.scaleX) * Math.Sign(x); - by = Math.Abs(bone.data.scaleY) * Math.Sign(y); - bone.scaleX = bx + (x - bx) * alpha; - bone.scaleY = by + (y - by) * alpha; - break; - case MixBlend.First: - case MixBlend.Replace: - bx = Math.Abs(bone.scaleX) * Math.Sign(x); - by = Math.Abs(bone.scaleY) * Math.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; - break; - } - } - } - } - } - - /// Changes a bone's local . - public class ScaleXTimeline : CurveTimeline1, IBoneTimeline { - readonly int boneIndex; - - public ScaleXTimeline (int frameCount, int bezierCount, int boneIndex) - : base(frameCount, bezierCount, (int)Property.ScaleX + "|" + boneIndex) { - this.boneIndex = boneIndex; - } - - public int BoneIndex { - get { - return boneIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - if (!bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case MixBlend.Setup: - bone.scaleX = bone.data.scaleX; - return; - case MixBlend.First: - bone.scaleX += (bone.data.scaleX - bone.scaleX) * alpha; - return; - } - return; - } - - float x = GetCurveValue(time) * bone.data.scaleX; - if (alpha == 1) { - if (blend == MixBlend.Add) - bone.scaleX += x - bone.data.scaleX; - else - bone.scaleX = x; - } else { - // Mixing out uses sign of setup or current pose, else use sign of key. - float bx; - if (direction == MixDirection.Out) { - switch (blend) { - case MixBlend.Setup: - bx = bone.data.scaleX; - bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha; - break; - case MixBlend.First: - case MixBlend.Replace: - bx = bone.scaleX; - bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha; - break; - case MixBlend.Add: - bone.scaleX += (x - bone.data.scaleX) * alpha; - break; - } - } else { - switch (blend) { - case MixBlend.Setup: - bx = Math.Abs(bone.data.scaleX) * Math.Sign(x); - bone.scaleX = bx + (x - bx) * alpha; - break; - case MixBlend.First: - case MixBlend.Replace: - bx = Math.Abs(bone.scaleX) * Math.Sign(x); - bone.scaleX = bx + (x - bx) * alpha; - break; - case MixBlend.Add: - bone.scaleX += (x - bone.data.scaleX) * alpha; - break; - } - } - } - } - } - - /// Changes a bone's local . - public class ScaleYTimeline : CurveTimeline1, IBoneTimeline { - readonly int boneIndex; - - public ScaleYTimeline (int frameCount, int bezierCount, int boneIndex) - : base(frameCount, bezierCount, (int)Property.ScaleY + "|" + boneIndex) { - this.boneIndex = boneIndex; - } - - public int BoneIndex { - get { - return boneIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - if (!bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case MixBlend.Setup: - bone.scaleY = bone.data.scaleY; - return; - case MixBlend.First: - bone.scaleY += (bone.data.scaleY - bone.scaleY) * alpha; - return; - } - return; - } - - float y = GetCurveValue(time) * bone.data.scaleY; - if (alpha == 1) { - if (blend == MixBlend.Add) - bone.scaleY += y - bone.data.scaleY; - else - bone.scaleY = y; - } else { - // Mixing out uses sign of setup or current pose, else use sign of key. - float by; - if (direction == MixDirection.Out) { - switch (blend) { - case MixBlend.Setup: - by = bone.data.scaleY; - bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha; - break; - case MixBlend.First: - case MixBlend.Replace: - by = bone.scaleY; - bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha; - break; - case MixBlend.Add: - bone.scaleY += (y - bone.data.scaleY) * alpha; - break; - } - } else { - switch (blend) { - case MixBlend.Setup: - by = Math.Abs(bone.data.scaleY) * Math.Sign(y); - bone.scaleY = by + (y - by) * alpha; - break; - case MixBlend.First: - case MixBlend.Replace: - by = Math.Abs(bone.scaleY) * Math.Sign(y); - bone.scaleY = by + (y - by) * alpha; - break; - case MixBlend.Add: - bone.scaleY += (y - bone.data.scaleY) * alpha; - break; - } - } - } - } - } - - /// Changes a bone's local and . - public class ShearTimeline : CurveTimeline2, IBoneTimeline { - readonly int boneIndex; - - public ShearTimeline (int frameCount, int bezierCount, int boneIndex) - : base(frameCount, bezierCount, // - (int)Property.ShearX + "|" + boneIndex, // - (int)Property.ShearY + "|" + boneIndex) { - this.boneIndex = boneIndex; - } - - public int BoneIndex { - get { - return boneIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - if (!bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - 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; - return; - } - return; - } - - float x, y; - int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; - switch (curveType) { - case LINEAR: - float before = frames[i]; - x = frames[i + VALUE1]; - y = frames[i + VALUE2]; - float t = (time - before) / (frames[i + ENTRIES] - before); - x += (frames[i + ENTRIES + VALUE1] - x) * t; - y += (frames[i + ENTRIES + VALUE2] - y) * t; - break; - case STEPPED: - x = frames[i + VALUE1]; - y = frames[i + VALUE2]; - break; - default: - x = GetBezierValue(time, i, VALUE1, curveType - BEZIER); - y = GetBezierValue(time, i, VALUE2, curveType + BEZIER_SIZE - BEZIER); - break; - } - - 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; - break; - } - } - } - - /// Changes a bone's local . - public class ShearXTimeline : CurveTimeline1, IBoneTimeline { - readonly int boneIndex; - - public ShearXTimeline (int frameCount, int bezierCount, int boneIndex) - : base(frameCount, bezierCount, (int)Property.ShearX + "|" + boneIndex) { - this.boneIndex = boneIndex; - } - - public int BoneIndex { - get { - return boneIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - if (!bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case MixBlend.Setup: - bone.shearX = bone.data.shearX; - return; - case MixBlend.First: - bone.shearX += (bone.data.shearX - bone.shearX) * alpha; - return; - } - return; - } - - float x = GetCurveValue(time); - switch (blend) { - case MixBlend.Setup: - bone.shearX = bone.data.shearX + x * alpha; - break; - case MixBlend.First: - case MixBlend.Replace: - bone.shearX += (bone.data.shearX + x - bone.shearX) * alpha; - break; - case MixBlend.Add: - bone.shearX += x * alpha; - break; - } - } - } - - /// Changes a bone's local . - public class ShearYTimeline : CurveTimeline1, IBoneTimeline { - readonly int boneIndex; - - public ShearYTimeline (int frameCount, int bezierCount, int boneIndex) - : base(frameCount, bezierCount, (int)Property.ShearY + "|" + boneIndex) { - this.boneIndex = boneIndex; - } - - public int BoneIndex { - get { - return boneIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Bone bone = skeleton.bones.Items[boneIndex]; - if (!bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case MixBlend.Setup: - bone.shearY = bone.data.shearY; - return; - case MixBlend.First: - bone.shearY += (bone.data.shearY - bone.shearY) * alpha; - return; - } - return; - } - - float y = GetCurveValue(time); - switch (blend) { - case MixBlend.Setup: - bone.shearY = bone.data.shearY + y * alpha; - break; - case MixBlend.First: - case MixBlend.Replace: - bone.shearY += (bone.data.shearY + y - bone.shearY) * alpha; - break; - case MixBlend.Add: - bone.shearY += y * alpha; - break; - } - } - } - - /// Changes a slot's . - public class RGBATimeline : CurveTimeline, ISlotTimeline { - public const int ENTRIES = 5; - protected const int R = 1, G = 2, B = 3, A = 4; - - readonly int slotIndex; - - public RGBATimeline (int frameCount, int bezierCount, int slotIndex) - : base(frameCount, bezierCount, // - (int)Property.RGB + "|" + slotIndex, // - (int)Property.Alpha + "|" + slotIndex) { - this.slotIndex = slotIndex; - } - public override int FrameEntries { - get { return ENTRIES; } - } - - public int SlotIndex { - get { - return slotIndex; - } - } - - /// Sets the time and color for the specified frame. - /// Between 0 and frameCount, inclusive. - /// The frame time in seconds. - public void 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; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Slot slot = skeleton.slots.Items[slotIndex]; - if (!slot.bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - var setup = slot.data; - switch (blend) { - case MixBlend.Setup: - slot.r = setup.r; - slot.g = setup.g; - slot.b = setup.b; - slot.a = setup.a; - return; - case MixBlend.First: - slot.r += (setup.r - slot.r) * alpha; - slot.g += (setup.g - slot.g) * alpha; - slot.b += (setup.b - slot.b) * alpha; - slot.a += (setup.a - slot.a) * alpha; - slot.ClampColor(); - return; - } - return; - } - - float r, g, b, a; - int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; - switch (curveType) { - case LINEAR: - float before = frames[i]; - r = frames[i + R]; - g = frames[i + G]; - b = frames[i + B]; - a = frames[i + A]; - float t = (time - before) / (frames[i + ENTRIES] - before); - r += (frames[i + ENTRIES + R] - r) * t; - g += (frames[i + ENTRIES + G] - g) * t; - b += (frames[i + ENTRIES + B] - b) * t; - a += (frames[i + ENTRIES + A] - a) * t; - break; - case STEPPED: - r = frames[i + R]; - g = frames[i + G]; - b = frames[i + B]; - a = frames[i + A]; - break; - default: - r = GetBezierValue(time, i, R, curveType - BEZIER); - g = GetBezierValue(time, i, G, curveType + BEZIER_SIZE - BEZIER); - b = GetBezierValue(time, i, B, curveType + BEZIER_SIZE * 2 - BEZIER); - a = GetBezierValue(time, i, A, curveType + BEZIER_SIZE * 3 - BEZIER); - break; - } - - if (alpha == 1) { - slot.r = r; - slot.g = g; - slot.b = b; - slot.a = a; - } else { - float br, bg, bb, ba; - if (blend == MixBlend.Setup) { - br = slot.data.r; - bg = slot.data.g; - bb = slot.data.b; - ba = slot.data.a; - } else { - br = slot.r; - bg = slot.g; - bb = slot.b; - ba = slot.a; - } - slot.r = br + (r - br) * alpha; - slot.g = bg + (g - bg) * alpha; - slot.b = bb + (b - bb) * alpha; - slot.a = ba + (a - ba) * alpha; - } - slot.ClampColor(); - } - } - - /// Changes the RGB for a slot's . - public class RGBTimeline : CurveTimeline, ISlotTimeline { - public const int ENTRIES = 4; - protected const int R = 1, G = 2, B = 3; - - readonly int slotIndex; - - public RGBTimeline (int frameCount, int bezierCount, int slotIndex) - : base(frameCount, bezierCount, // - (int)Property.RGB + "|" + slotIndex) { - this.slotIndex = slotIndex; - } - - public override int FrameEntries { - get { return ENTRIES; } - } - - public int SlotIndex { - get { - return slotIndex; - } - } - - /// Sets the time and color for the specified frame. - /// Between 0 and frameCount, inclusive. - /// The frame time in seconds. - public void SetFrame (int frame, float time, float r, float g, float b) { - frame <<= 2; - frames[frame] = time; - frames[frame + R] = r; - frames[frame + G] = g; - frames[frame + B] = b; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Slot slot = skeleton.slots.Items[slotIndex]; - if (!slot.bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - var setup = slot.data; - switch (blend) { - case MixBlend.Setup: - slot.r = setup.r; - slot.g = setup.g; - slot.b = setup.b; - return; - case MixBlend.First: - slot.r += (setup.r - slot.r) * alpha; - slot.g += (setup.g - slot.g) * alpha; - slot.b += (setup.b - slot.b) * alpha; - slot.ClampColor(); - return; - } - return; - } - - float r, g, b; - int i = Search(frames, time, ENTRIES), curveType = (int)curves[i >> 2]; - switch (curveType) { - case LINEAR: - float before = frames[i]; - r = frames[i + R]; - g = frames[i + G]; - b = frames[i + B]; - float t = (time - before) / (frames[i + ENTRIES] - before); - r += (frames[i + ENTRIES + R] - r) * t; - g += (frames[i + ENTRIES + G] - g) * t; - b += (frames[i + ENTRIES + B] - b) * t; - break; - case STEPPED: - r = frames[i + R]; - g = frames[i + G]; - b = frames[i + B]; - break; - default: - r = GetBezierValue(time, i, R, curveType - BEZIER); - g = GetBezierValue(time, i, G, curveType + BEZIER_SIZE - BEZIER); - b = GetBezierValue(time, i, B, curveType + BEZIER_SIZE * 2 - BEZIER); - break; - } - - if (alpha == 1) { - slot.r = r; - slot.g = g; - slot.b = b; - } else { - float br, bg, bb; - if (blend == MixBlend.Setup) { - var setup = slot.data; - br = setup.r; - bg = setup.g; - bb = setup.b; - } else { - br = slot.r; - bg = slot.g; - bb = slot.b; - } - slot.r = br + (r - br) * alpha; - slot.g = bg + (g - bg) * alpha; - slot.b = bb + (b - bb) * alpha; - } - slot.ClampColor(); - } - } - - /// Changes the alpha for a slot's . - public class AlphaTimeline : CurveTimeline1, ISlotTimeline { - readonly int slotIndex; - - public AlphaTimeline (int frameCount, int bezierCount, int slotIndex) - : base(frameCount, bezierCount, (int)Property.Alpha + "|" + slotIndex) { - this.slotIndex = slotIndex; - } - - public int SlotIndex { - get { - return slotIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Slot slot = skeleton.slots.Items[slotIndex]; - if (!slot.bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - var setup = slot.data; - switch (blend) { - case MixBlend.Setup: - slot.a = setup.a; - return; - case MixBlend.First: - slot.a += (setup.a - slot.a) * alpha; - slot.ClampColor(); - return; - } - return; - } - - float a = GetCurveValue(time); - if (alpha == 1) - slot.a = a; - else { - if (blend == MixBlend.Setup) slot.a = slot.data.a; - slot.a += (a - slot.a) * alpha; - } - slot.ClampColor(); - } - } - - /// Changes a slot's and for two color tinting. - public class RGBA2Timeline : CurveTimeline, ISlotTimeline { - public const int ENTRIES = 8; - protected const int R = 1, G = 2, B = 3, A = 4, R2 = 5, G2 = 6, B2 = 7; - - readonly int slotIndex; - - public RGBA2Timeline (int frameCount, int bezierCount, int slotIndex) - : base(frameCount, bezierCount, // - (int)Property.RGB + "|" + slotIndex, // - (int)Property.Alpha + "|" + slotIndex, // - (int)Property.RGB2 + "|" + slotIndex) { - this.slotIndex = slotIndex; - } - - public override int FrameEntries { - get { - return ENTRIES; - } - } - - /// - /// The index of the slot in that will be changed when this timeline is applied. The - /// must have a dark color available. - public int SlotIndex { - get { - return slotIndex; - } - } - - /// Sets the time, light color, and dark color for the specified frame. - /// Between 0 and frameCount, inclusive. - /// The frame time in seconds. - public void SetFrame (int frame, float time, float r, float g, float b, float a, float r2, float g2, float b2) { - frame <<= 3; - 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; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Slot slot = skeleton.slots.Items[slotIndex]; - if (!slot.bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - SlotData setup = slot.data; - switch (blend) { - case MixBlend.Setup: - slot.r = setup.r; - slot.g = setup.g; - slot.b = setup.b; - slot.a = setup.a; - slot.ClampColor(); - slot.r2 = setup.r2; - slot.g2 = setup.g2; - slot.b2 = setup.b2; - slot.ClampSecondColor(); - return; - case MixBlend.First: - slot.r += (slot.r - setup.r) * alpha; - slot.g += (slot.g - setup.g) * alpha; - slot.b += (slot.b - setup.b) * alpha; - slot.a += (slot.a - setup.a) * alpha; - slot.ClampColor(); - slot.r2 += (slot.r2 - setup.r2) * alpha; - slot.g2 += (slot.g2 - setup.g2) * alpha; - slot.b2 += (slot.b2 - setup.b2) * alpha; - slot.ClampSecondColor(); - return; - } - return; - } - - float r, g, b, a, r2, g2, b2; - int i = Search(frames, time, ENTRIES), curveType = (int)curves[i >> 3]; - switch (curveType) { - case LINEAR: - float before = frames[i]; - r = frames[i + R]; - g = frames[i + G]; - b = frames[i + B]; - a = frames[i + A]; - r2 = frames[i + R2]; - g2 = frames[i + G2]; - b2 = frames[i + B2]; - float t = (time - before) / (frames[i + ENTRIES] - before); - r += (frames[i + ENTRIES + R] - r) * t; - g += (frames[i + ENTRIES + G] - g) * t; - b += (frames[i + ENTRIES + B] - b) * t; - a += (frames[i + ENTRIES + A] - a) * t; - r2 += (frames[i + ENTRIES + R2] - r2) * t; - g2 += (frames[i + ENTRIES + G2] - g2) * t; - b2 += (frames[i + ENTRIES + B2] - b2) * t; - break; - case STEPPED: - r = frames[i + R]; - g = frames[i + G]; - b = frames[i + B]; - a = frames[i + A]; - r2 = frames[i + R2]; - g2 = frames[i + G2]; - b2 = frames[i + B2]; - break; - default: - r = GetBezierValue(time, i, R, curveType - BEZIER); - g = GetBezierValue(time, i, G, curveType + BEZIER_SIZE - BEZIER); - b = GetBezierValue(time, i, B, curveType + BEZIER_SIZE * 2 - BEZIER); - a = GetBezierValue(time, i, A, curveType + BEZIER_SIZE * 3 - BEZIER); - r2 = GetBezierValue(time, i, R2, curveType + BEZIER_SIZE * 4 - BEZIER); - g2 = GetBezierValue(time, i, G2, curveType + BEZIER_SIZE * 5 - BEZIER); - b2 = GetBezierValue(time, i, B2, curveType + BEZIER_SIZE * 6 - BEZIER); - break; - } - - if (alpha == 1) { - slot.r = r; - slot.g = g; - slot.b = b; - slot.a = a; - slot.r2 = r2; - slot.g2 = g2; - slot.b2 = b2; - } else { - float br, bg, bb, ba, br2, bg2, bb2; - if (blend == MixBlend.Setup) { - br = slot.data.r; - bg = slot.data.g; - bb = slot.data.b; - ba = slot.data.a; - br2 = slot.data.r2; - bg2 = slot.data.g2; - bb2 = slot.data.b2; - } else { - br = slot.r; - bg = slot.g; - bb = slot.b; - ba = slot.a; - br2 = slot.r2; - bg2 = slot.g2; - bb2 = slot.b2; - } - slot.r = br + (r - br) * alpha; - slot.g = bg + (g - bg) * alpha; - slot.b = bb + (b - bb) * alpha; - slot.a = ba + (a - ba) * alpha; - slot.r2 = br2 + (r2 - br2) * alpha; - slot.g2 = bg2 + (g2 - bg2) * alpha; - slot.b2 = bb2 + (b2 - bb2) * alpha; - } - slot.ClampColor(); - slot.ClampSecondColor(); - } - } - - /// Changes the RGB for a slot's and for two color tinting. - public class RGB2Timeline : CurveTimeline, ISlotTimeline { - public const int ENTRIES = 7; - protected const int R = 1, G = 2, B = 3, R2 = 4, G2 = 5, B2 = 6; - - readonly int slotIndex; - - public RGB2Timeline (int frameCount, int bezierCount, int slotIndex) - : base(frameCount, bezierCount, // - (int)Property.RGB + "|" + slotIndex, // - (int)Property.RGB2 + "|" + slotIndex) { - this.slotIndex = slotIndex; - } - - public override int FrameEntries { - get { - return ENTRIES; - } - } - - /// - /// The index of the slot in that will be changed when this timeline is applied. The - /// must have a dark color available. - public int SlotIndex { - get { - return slotIndex; - } - } - - /// Sets the time, light color, and dark color for the specified frame. - /// Between 0 and frameCount, inclusive. - /// The frame time in seconds. - public void 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; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Slot slot = skeleton.slots.Items[slotIndex]; - if (!slot.bone.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - SlotData setup = slot.data; - switch (blend) { - case MixBlend.Setup: - slot.r = setup.r; - slot.g = setup.g; - slot.b = setup.b; - slot.ClampColor(); - slot.r2 = setup.r2; - slot.g2 = setup.g2; - slot.b2 = setup.b2; - slot.ClampSecondColor(); - return; - case MixBlend.First: - slot.r += (slot.r - setup.r) * alpha; - slot.g += (slot.g - setup.g) * alpha; - slot.b += (slot.b - setup.b) * alpha; - slot.ClampColor(); - slot.r2 += (slot.r2 - setup.r2) * alpha; - slot.g2 += (slot.g2 - setup.g2) * alpha; - slot.b2 += (slot.b2 - setup.b2) * alpha; - slot.ClampSecondColor(); - return; - } - return; - } - - float r, g, b, r2, g2, b2; - int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; - switch (curveType) { - case LINEAR: - float before = frames[i]; - r = frames[i + R]; - g = frames[i + G]; - b = frames[i + B]; - r2 = frames[i + R2]; - g2 = frames[i + G2]; - b2 = frames[i + B2]; - float t = (time - before) / (frames[i + ENTRIES] - before); - r += (frames[i + ENTRIES + R] - r) * t; - g += (frames[i + ENTRIES + G] - g) * t; - b += (frames[i + ENTRIES + B] - b) * t; - r2 += (frames[i + ENTRIES + R2] - r2) * t; - g2 += (frames[i + ENTRIES + G2] - g2) * t; - b2 += (frames[i + ENTRIES + B2] - b2) * t; - break; - case STEPPED: - r = frames[i + R]; - g = frames[i + G]; - b = frames[i + B]; - r2 = frames[i + R2]; - g2 = frames[i + G2]; - b2 = frames[i + B2]; - break; - default: - r = GetBezierValue(time, i, R, curveType - BEZIER); - g = GetBezierValue(time, i, G, curveType + BEZIER_SIZE - BEZIER); - b = GetBezierValue(time, i, B, curveType + BEZIER_SIZE * 2 - BEZIER); - r2 = GetBezierValue(time, i, R2, curveType + BEZIER_SIZE * 3 - BEZIER); - g2 = GetBezierValue(time, i, G2, curveType + BEZIER_SIZE * 4 - BEZIER); - b2 = GetBezierValue(time, i, B2, curveType + BEZIER_SIZE * 5 - BEZIER); - break; - } - - if (alpha == 1) { - slot.r = r; - slot.g = g; - slot.b = b; - slot.r2 = r2; - slot.g2 = g2; - slot.b2 = b2; - } else { - float br, bg, bb, br2, bg2, bb2; - if (blend == MixBlend.Setup) { - SlotData setup = slot.data; - br = setup.r; - bg = setup.g; - bb = setup.b; - br2 = setup.r2; - bg2 = setup.g2; - bb2 = setup.b2; - } else { - br = slot.r; - bg = slot.g; - bb = slot.b; - br2 = slot.r2; - bg2 = slot.g2; - bb2 = slot.b2; - } - slot.r = br + (r - br) * alpha; - slot.g = bg + (g - bg) * alpha; - slot.b = bb + (b - bb) * alpha; - slot.r2 = br2 + (r2 - br2) * alpha; - slot.g2 = bg2 + (g2 - bg2) * alpha; - slot.b2 = bb2 + (b2 - bb2) * alpha; - } - slot.ClampColor(); - slot.ClampSecondColor(); - } - } - - /// Changes a slot's . - public class AttachmentTimeline : Timeline, ISlotTimeline { - readonly int slotIndex; - readonly string[] attachmentNames; - - public AttachmentTimeline (int frameCount, int slotIndex) - : base(frameCount, (int)Property.Attachment + "|" + slotIndex) { - this.slotIndex = slotIndex; - attachmentNames = new String[frameCount]; - } - - public int SlotIndex { - get { - return slotIndex; - } - } - - /// The attachment name for each frame. May contain null values to clear the attachment. - public string[] AttachmentNames { - get { - return attachmentNames; - } - } - - /// Sets the time and attachment name for the specified frame. - /// Between 0 and frameCount, inclusive. - /// The frame time in seconds. - public void SetFrame (int frame, float time, String attachmentName) { - frames[frame] = time; - attachmentNames[frame] = attachmentName; - } - - public override void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - Slot slot = skeleton.slots.Items[slotIndex]; - if (!slot.bone.active) return; - - if (direction == MixDirection.Out) { - if (blend == MixBlend.Setup) SetAttachment(skeleton, slot, slot.data.attachmentName); - return; - } - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - if (blend == MixBlend.Setup || blend == MixBlend.First) SetAttachment(skeleton, slot, slot.data.attachmentName); - return; - } - - SetAttachment(skeleton, slot, attachmentNames[Search(frames, time)]); - } - - private void SetAttachment (Skeleton skeleton, Slot slot, string attachmentName) { - slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); - } - } - - /// Changes a slot's to deform a . - public class DeformTimeline : CurveTimeline, ISlotTimeline { - readonly int slotIndex; - readonly VertexAttachment attachment; - internal float[][] vertices; - - public DeformTimeline (int frameCount, int bezierCount, int slotIndex, VertexAttachment attachment) - : base(frameCount, bezierCount, (int)Property.Deform + "|" + slotIndex + "|" + attachment.Id) { - this.slotIndex = slotIndex; - this.attachment = attachment; - vertices = new float[frameCount][]; - } - - public int SlotIndex { - get { - return slotIndex; - } - } - /// The attachment that will be deformed. - /// - public VertexAttachment Attachment { - get { - return attachment; - } - } - - /// The vertices for each frame. - public float[][] Vertices { - get { - return vertices; - } - } - - /// Sets the time and vertices for the specified frame. - /// Between 0 and frameCount, inclusive. - /// The frame time in seconds. - /// Vertex positions for an unweighted VertexAttachment, or deform offsets if it has weights. - public void SetFrame (int frame, float time, float[] vertices) { - frames[frame] = time; - this.vertices[frame] = vertices; - } - - /// Ignored (0 is used for a deform timeline). - /// Ignored (1 is used for a deform timeline). - public void setBezier (int bezier, int frame, int value, float time1, float value1, float cx1, float cy1, float cx2, - float cy2, float time2, float value2) { - float[] curves = this.curves; - int i = FrameCount + bezier * BEZIER_SIZE; - if (value == 0) curves[frame] = BEZIER + i; - float tmpx = (time1 - cx1 * 2 + cx2) * 0.03f, tmpy = cy2 * 0.03f - cy1 * 0.06f; - float dddx = ((cx1 - cx2) * 3 - time1 + time2) * 0.006f, dddy = (cy1 - cy2 + 0.33333333f) * 0.018f; - float ddx = tmpx * 2 + dddx, ddy = tmpy * 2 + dddy; - float dx = (cx1 - time1) * 0.3f + tmpx + dddx * 0.16666667f, dy = cy1 * 0.3f + tmpy + dddy * 0.16666667f; - float x = time1 + dx, y = dy; - for (int 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; - } - } - - /// Returns the interpolated percentage for the specified time. - /// The frame before time. - private float GetCurvePercent (float time, int frame) { - float[] curves = this.curves; - int i = (int)curves[frame]; - switch (i) { - case LINEAR: - float x = frames[frame]; - return (time - x) / (frames[frame + FrameEntries] - x); - case STEPPED: - return 0; - } - i -= BEZIER; - if (curves[i] > time) { - float x = frames[frame]; - return curves[i + 1] * (time - x) / (curves[i] - x); - } - int 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); - } - } - { // scope added to prevent compile error "float x and y declared in enclosing scope" - float x = curves[n - 2], y = curves[n - 1]; - return y + (1 - y) * (time - x) / (frames[frame + FrameEntries] - x); - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - - Slot slot = skeleton.slots.Items[slotIndex]; - if (!slot.bone.active) return; - var vertexAttachment = slot.attachment as VertexAttachment; - if (vertexAttachment == null || vertexAttachment.TimelineAttachment != attachment) return; - - var deformArray = slot.deform; - if (deformArray.Count == 0) blend = MixBlend.Setup; - - float[][] vertices = this.vertices; - int vertexCount = vertices[0].Length; - - float[] deform; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case MixBlend.Setup: - deformArray.Clear(); - return; - case MixBlend.First: - if (alpha == 1) { - deformArray.Clear(); - return; - } - - // Ensure size and preemptively set count. - if (deformArray.Capacity < vertexCount) deformArray.Capacity = vertexCount; - deformArray.Count = vertexCount; - deform = deformArray.Items; - - if (vertexAttachment.bones == null) { - // Unweighted vertex positions. - float[] setupVertices = vertexAttachment.vertices; - for (int i = 0; i < vertexCount; i++) - deform[i] += (setupVertices[i] - deform[i]) * alpha; - } else { - // Weighted deform offsets. - alpha = 1 - alpha; - for (int i = 0; i < vertexCount; i++) - deform[i] *= alpha; - } - return; - } - return; - } - - // Ensure size and preemptively set count. - if (deformArray.Capacity < vertexCount) deformArray.Capacity = vertexCount; - deformArray.Count = vertexCount; - deform = deformArray.Items; - - if (time >= frames[frames.Length - 1]) { // Time is after last frame. - float[] lastVertices = vertices[frames.Length - 1]; - if (alpha == 1) { - if (blend == MixBlend.Add) { - if (vertexAttachment.bones == null) { - // Unweighted vertex positions, no alpha. - float[] setupVertices = vertexAttachment.vertices; - for (int i = 0; i < vertexCount; i++) - deform[i] += lastVertices[i] - setupVertices[i]; - } else { - // Weighted deform offsets, no alpha. - for (int i = 0; i < vertexCount; i++) - deform[i] += lastVertices[i]; - } - } else { - // Vertex positions or deform offsets, no alpha. - Array.Copy(lastVertices, 0, deform, 0, vertexCount); - } - } else { - switch (blend) { - case MixBlend.Setup: { - if (vertexAttachment.bones == null) { - // Unweighted vertex positions, with alpha. - float[] setupVertices = vertexAttachment.vertices; - for (int i = 0; i < vertexCount; i++) { - float setup = setupVertices[i]; - deform[i] = setup + (lastVertices[i] - setup) * alpha; - } - } else { - // Weighted deform offsets, with alpha. - for (int 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 (int i = 0; i < vertexCount; i++) - deform[i] += (lastVertices[i] - deform[i]) * alpha; - break; - case MixBlend.Add: - if (vertexAttachment.bones == null) { - // Unweighted vertex positions, no alpha. - float[] setupVertices = vertexAttachment.vertices; - for (int i = 0; i < vertexCount; i++) - deform[i] += (lastVertices[i] - setupVertices[i]) * alpha; - } else { - // Weighted deform offsets, alpha. - for (int i = 0; i < vertexCount; i++) - deform[i] += lastVertices[i] * alpha; - } - break; - } - } - return; - } - - int frame = Search(frames, time); - float percent = GetCurvePercent(time, frame); - float[] prevVertices = vertices[frame]; - float[] nextVertices = vertices[frame + 1]; - - if (alpha == 1) { - if (blend == MixBlend.Add) { - if (vertexAttachment.bones == null) { - // Unweighted vertex positions, no alpha. - float[] setupVertices = vertexAttachment.vertices; - for (int 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 (int 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 (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - deform[i] = prev + (nextVertices[i] - prev) * percent; - } - } - } else { - switch (blend) { - case MixBlend.Setup: { - if (vertexAttachment.bones == null) { - // Unweighted vertex positions, with alpha. - float[] setupVertices = vertexAttachment.vertices; - for (int 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 (int 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 (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - deform[i] += (prev + (nextVertices[i] - prev) * percent - deform[i]) * alpha; - } - break; - } - case MixBlend.Add: - if (vertexAttachment.bones == null) { - // Unweighted vertex positions, with alpha. - float[] setupVertices = vertexAttachment.vertices; - for (int 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 (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - deform[i] += (prev + (nextVertices[i] - prev) * percent) * alpha; - } - } - break; - } - } - } - } - - /// Fires an when specific animation times are reached. - public class EventTimeline : Timeline { - readonly static string[] propertyIds = { ((int)Property.Event).ToString() }; - readonly Event[] events; - - public EventTimeline (int frameCount) - : base(frameCount, propertyIds) { - events = new Event[frameCount]; - } - - /// The event for each frame. - public Event[] Events { - get { - return events; - } - } - - /// Sets the time and event for the specified frame. - /// Between 0 and frameCount, inclusive. - public void SetFrame (int frame, Event e) { - frames[frame] = e.time; - events[frame] = e; - } - - /// Fires events for frames > lastTime and <= time. - public override void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, - MixBlend blend, MixDirection direction) { - - if (firedEvents == null) return; - - float[] frames = this.frames; - int frameCount = frames.Length; - - if (lastTime > time) { // Fire events after last time for looped animations. - Apply(skeleton, lastTime, int.MaxValue, firedEvents, alpha, blend, direction); - lastTime = -1f; - } else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame. - return; - if (time < frames[0]) return; // Time is before first frame. - - int i; - if (lastTime < frames[0]) - i = 0; - else { - i = Search(frames, lastTime) + 1; - float frameTime = frames[i]; - while (i > 0) { // Fire multiple events with the same frame. - if (frames[i - 1] != frameTime) break; - i--; - } - } - for (; i < frameCount && time >= frames[i]; i++) - firedEvents.Add(events[i]); - } - } - - /// Changes a skeleton's . - public class DrawOrderTimeline : Timeline { - static readonly string[] propertyIds = { ((int)Property.DrawOrder).ToString() }; - - readonly int[][] drawOrders; - - public DrawOrderTimeline (int frameCount) - : base(frameCount, propertyIds) { - drawOrders = new int[frameCount][]; - } - - /// The draw order for each frame. - /// . - public int[][] DrawOrders { - get { - return drawOrders; - } - } - - /// Sets the time and draw order for the specified frame. - /// Between 0 and frameCount, inclusive. - /// The frame time in seconds. - /// For each slot in , the index of the slot in the new draw order. May be null to use - /// setup pose draw order. - public void SetFrame (int frame, float time, int[] drawOrder) { - frames[frame] = time; - drawOrders[frame] = drawOrder; - } - - public override void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - - if (direction == MixDirection.Out) { - if (blend == MixBlend.Setup) Array.Copy(skeleton.slots.Items, 0, skeleton.drawOrder.Items, 0, skeleton.slots.Count); - return; - } - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - if (blend == MixBlend.Setup || blend == MixBlend.First) Array.Copy(skeleton.slots.Items, 0, skeleton.drawOrder.Items, 0, skeleton.slots.Count); - return; - } - - int[] drawOrderToSetupIndex = drawOrders[Search(frames, time)]; - if (drawOrderToSetupIndex == null) - Array.Copy(skeleton.slots.Items, 0, skeleton.drawOrder.Items, 0, skeleton.slots.Count); - else { - Slot[] slots = skeleton.slots.Items; - Slot[] drawOrder = skeleton.drawOrder.Items; - for (int i = 0, n = drawOrderToSetupIndex.Length; i < n; i++) - drawOrder[i] = slots[drawOrderToSetupIndex[i]]; - } - } - } - - /// Changes an IK constraint's , , - /// , , and . - public class IkConstraintTimeline : CurveTimeline { - public const int ENTRIES = 6; - private const int MIX = 1, SOFTNESS = 2, BEND_DIRECTION = 3, COMPRESS = 4, STRETCH = 5; - - readonly int ikConstraintIndex; - - public IkConstraintTimeline (int frameCount, int bezierCount, int ikConstraintIndex) - : base(frameCount, bezierCount, (int)Property.IkConstraint + "|" + ikConstraintIndex) { - this.ikConstraintIndex = ikConstraintIndex; - } - - public override int FrameEntries { - get { - return ENTRIES; - } - } - - /// The index of the IK constraint slot in that will be changed when this timeline is - /// applied. - public int IkConstraintIndex { - get { - return ikConstraintIndex; - } - } - - /// Sets the time, mix, softness, bend direction, compress, and stretch for the specified frame. - /// Between 0 and frameCount, inclusive. - /// The frame time in seconds. - /// 1 or -1. - public void 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] = bendDirection; - frames[frame + COMPRESS] = compress ? 1 : 0; - frames[frame + STRETCH] = stretch ? 1 : 0; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - IkConstraint constraint = skeleton.ikConstraints.Items[ikConstraintIndex]; - if (!constraint.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - 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; - } - return; - } - - float mix, softness; - int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; - switch (curveType) { - case LINEAR: - float before = frames[i]; - mix = frames[i + MIX]; - softness = frames[i + SOFTNESS]; - float t = (time - before) / (frames[i + ENTRIES] - before); - mix += (frames[i + ENTRIES + MIX] - mix) * t; - softness += (frames[i + ENTRIES + SOFTNESS] - softness) * t; - break; - case STEPPED: - mix = frames[i + MIX]; - softness = frames[i + SOFTNESS]; - break; - default: - mix = GetBezierValue(time, i, MIX, curveType - BEZIER); - softness = GetBezierValue(time, i, SOFTNESS, curveType + BEZIER_SIZE - BEZIER); - break; - } - - 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 = (int)frames[i + BEND_DIRECTION]; - constraint.compress = frames[i + COMPRESS] != 0; - constraint.stretch = frames[i + STRETCH] != 0; - } - } else { - constraint.mix += (mix - constraint.mix) * alpha; - constraint.softness += (softness - constraint.softness) * alpha; - if (direction == MixDirection.In) { - constraint.bendDirection = (int)frames[i + BEND_DIRECTION]; - constraint.compress = frames[i + COMPRESS] != 0; - constraint.stretch = frames[i + STRETCH] != 0; - } - } - } - } - - /// Changes a transform constraint's mixes. - public class TransformConstraintTimeline : CurveTimeline { - public const int ENTRIES = 7; - private const int ROTATE = 1, X = 2, Y = 3, SCALEX = 4, SCALEY = 5, SHEARY = 6; - - readonly int transformConstraintIndex; - - public TransformConstraintTimeline (int frameCount, int bezierCount, int transformConstraintIndex) - : base(frameCount, bezierCount, (int)Property.TransformConstraint + "|" + transformConstraintIndex) { - this.transformConstraintIndex = transformConstraintIndex; - } - - public override int FrameEntries { - get { - return ENTRIES; - } - } - - /// The index of the transform constraint slot in that will be changed when this - /// timeline is applied. - public int TransformConstraintIndex { - get { - return transformConstraintIndex; - } - } - - /// Sets the time, rotate mix, translate mix, scale mix, and shear mix for the specified frame. - /// Between 0 and frameCount, inclusive. - /// The frame time in seconds. - public void SetFrame (int 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; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - TransformConstraint constraint = skeleton.transformConstraints.Items[transformConstraintIndex]; - if (!constraint.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - TransformConstraintData data = constraint.data; - 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; - } - return; - } - - float rotate, x, y, scaleX, scaleY, shearY; - GetCurveValue(out rotate, out x, out y, out scaleX, out scaleY, out shearY, time); - - if (blend == MixBlend.Setup) { - TransformConstraintData 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; - 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; - } - } - - public void GetCurveValue (out float rotate, out float x, out float y, - out float scaleX, out float scaleY, out float shearY, float time) { - - float[] frames = this.frames; - int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; - switch (curveType) { - case 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 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); - break; - } - } - } - - /// Changes a path constraint's . - public class PathConstraintPositionTimeline : CurveTimeline1 { - readonly int pathConstraintIndex; - - public PathConstraintPositionTimeline (int frameCount, int bezierCount, int pathConstraintIndex) - : base(frameCount, bezierCount, (int)Property.PathConstraintPosition + "|" + pathConstraintIndex) { - this.pathConstraintIndex = pathConstraintIndex; - } - - /// The index of the path constraint slot in that will be changed when this timeline - /// is applied. - public int PathConstraintIndex { - get { - return pathConstraintIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; - if (!constraint.active) return; - - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case MixBlend.Setup: - constraint.position = constraint.data.position; - return; - case MixBlend.First: - constraint.position += (constraint.data.position - constraint.position) * alpha; - return; - } - return; - } - - float position = GetCurveValue(time); - if (blend == MixBlend.Setup) - constraint.position = constraint.data.position + (position - constraint.data.position) * alpha; - else - constraint.position += (position - constraint.position) * alpha; - } - } - - /// Changes a path constraint's . - public class PathConstraintSpacingTimeline : CurveTimeline1 { - readonly int pathConstraintIndex; - - public PathConstraintSpacingTimeline (int frameCount, int bezierCount, int pathConstraintIndex) - : base(frameCount, bezierCount, (int)Property.PathConstraintSpacing + "|" + pathConstraintIndex) { - this.pathConstraintIndex = pathConstraintIndex; - } - - /// The index of the path constraint slot in that will be changed when this timeline - /// is applied. - public int PathConstraintIndex { - get { - return pathConstraintIndex; - } - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, MixBlend blend, - MixDirection direction) { - - PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; - if (!constraint.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case MixBlend.Setup: - constraint.spacing = constraint.data.spacing; - return; - case MixBlend.First: - constraint.spacing += (constraint.data.spacing - constraint.spacing) * alpha; - return; - } - return; - } - - float spacing = GetCurveValue(time); - if (blend == MixBlend.Setup) - constraint.spacing = constraint.data.spacing + (spacing - constraint.data.spacing) * alpha; - else - constraint.spacing += (spacing - constraint.spacing) * alpha; - } - } - - /// Changes a transform constraint's , , and - /// . - public class PathConstraintMixTimeline : CurveTimeline { - public const int ENTRIES = 4; - private const int ROTATE = 1, X = 2, Y = 3; - - readonly int pathConstraintIndex; - - public PathConstraintMixTimeline (int frameCount, int bezierCount, int pathConstraintIndex) - : base(frameCount, bezierCount, (int)Property.PathConstraintMix + "|" + pathConstraintIndex) { - this.pathConstraintIndex = pathConstraintIndex; - } - - public override int FrameEntries { - get { return ENTRIES; } - } - - /// The index of the path constraint slot in that will be changed when this timeline - /// is applied. - public int PathConstraintIndex { - get { - return pathConstraintIndex; - } - } - - /// Sets the time and color for the specified frame. - /// Between 0 and frameCount, inclusive. - /// The frame time in seconds. - public void SetFrame (int frame, float time, float mixRotate, float mixX, float mixY) { - frame <<= 2; - frames[frame] = time; - frames[frame + ROTATE] = mixRotate; - frames[frame + X] = mixX; - frames[frame + Y] = mixY; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; - if (!constraint.active) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - 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; - return; - } - return; - } - - float rotate, x, y; - int i = Search(frames, time, ENTRIES), 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); - break; - } - - 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; - } - } - } - - /// Changes a slot's for an attachment's . - public class SequenceTimeline : Timeline, ISlotTimeline { - public const int ENTRIES = 3; - private const int MODE = 1, DELAY = 2; - - readonly int slotIndex; - readonly IHasTextureRegion attachment; - - public SequenceTimeline (int frameCount, int slotIndex, Attachment attachment) - : base(frameCount, (int)Property.Sequence + "|" + slotIndex + "|" + ((IHasTextureRegion)attachment).Sequence.Id) { - this.slotIndex = slotIndex; - this.attachment = (IHasTextureRegion)attachment; - } - - public override int FrameEntries { - get { return ENTRIES; } - } - - public int SlotIndex { - get { - return slotIndex; - } - } - public Attachment Attachment { - get { - return (Attachment)attachment; - } - } - - /// Sets the time, mode, index, and frame time for the specified frame. - /// Between 0 and frameCount, inclusive. - /// Seconds between frames. - public void SetFrame (int frame, float time, SequenceMode mode, int index, float delay) { - frame *= ENTRIES; - frames[frame] = time; - frames[frame + MODE] = (int)mode | (index << 4); - frames[frame + DELAY] = delay; - } - - override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, - MixDirection direction) { - - Slot slot = skeleton.slots.Items[slotIndex]; - if (!slot.bone.active) return; - Attachment slotAttachment = slot.attachment; - if (slotAttachment != attachment) { - VertexAttachment vertexAttachment = slotAttachment as VertexAttachment; - if ((vertexAttachment == null) - || vertexAttachment.TimelineAttachment != attachment) return; - } - Sequence sequence = ((IHasTextureRegion)slotAttachment).Sequence; - if (sequence == null) return; - - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - if (blend == MixBlend.Setup || blend == MixBlend.First) slot.SequenceIndex = -1; - return; - } - - int i = Search(frames, time, ENTRIES); - float before = frames[i]; - int modeAndIndex = (int)frames[i + MODE]; - float delay = frames[i + DELAY]; - - int index = modeAndIndex >> 4, count = sequence.Regions.Length; - SequenceMode mode = (SequenceMode)(modeAndIndex & 0xf); - if (mode != SequenceMode.Hold) { - index += (int)((time - before) / delay + 0.00001f); - switch (mode) { - case SequenceMode.Once: - index = Math.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 = Math.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; - break; - } // end case - } - } - slot.SequenceIndex = index; - } - } +namespace Spine4_1_00 +{ + + /// + /// Stores a list of timelines to animate a skeleton's pose over time. + public class Animation + { + internal String name; + internal ExposedList timelines; + internal HashSet timelineIds; + internal float duration; + + public Animation(string name, ExposedList timelines, float duration) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + + this.name = name; + SetTimelines(timelines); + this.duration = duration; + } + + public ExposedList Timelines + { + get { return timelines; } + set { SetTimelines(value); } + } + + public void SetTimelines(ExposedList timelines) + { + if (timelines == null) throw new ArgumentNullException("timelines", "timelines cannot be null."); + this.timelines = timelines; + // Note: avoiding reallocations by adding all hash set entries at + // once (EnsureCapacity() is only available in newer .Net versions). + int idCount = 0; + int timelinesCount = timelines.Count; + Timeline[] timelinesItems = timelines.Items; + for (int t = 0; t < timelinesCount; ++t) + idCount += timelinesItems[t].PropertyIds.Length; + string[] propertyIds = new string[idCount]; + int currentId = 0; + for (int t = 0; t < timelinesCount; ++t) + { + var ids = timelinesItems[t].PropertyIds; + for (int i = 0, idsLength = ids.Length; i < idsLength; ++i) + propertyIds[currentId++] = ids[i]; + } + this.timelineIds = new HashSet(propertyIds); + } + + /// The duration of the animation in seconds, which is usually the highest time of all frames in the timeline. The duration is + /// used to know when it has completed and when it should loop back to the start. + public float Duration { get { return duration; } set { duration = value; } } + + /// The animation's name, which is unique across all animations in the skeleton. + public string Name { get { return name; } } + + /// Returns true if this animation contains a timeline with any of the specified property IDs. + public bool HasTimeline(string[] propertyIds) + { + foreach (string id in propertyIds) + if (timelineIds.Contains(id)) return true; + return false; + } + + /// Applies the animation's timelines to the specified skeleton. + /// + /// The skeleton the animation is being applied to. This provides access to the bones, slots, and other skeleton + /// components the timelines may change. + /// The last time in seconds this animation was applied. Some timelines trigger only at specific times rather + /// than every frame. Pass -1 the first time an animation is applied to ensure frame 0 is triggered. + /// The time in seconds the skeleton is being posed for. Most timelines find the frame before and the frame after + /// this time and interpolate between the frame values. If beyond the and loop is + /// true then the animation will repeat, else the last frame will be applied. + /// If true, the animation repeats after the . + /// If any events are fired, they are added to this list. Can be null to ignore fired events or if no timelines + /// fire events. + /// 0 applies the current or setup values (depending on blend). 1 applies the timeline values. Between + /// 0 and 1 applies values between the current or setup values and the timeline values. 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 (layering). + /// Controls how mixing is applied when alpha < 1. + /// Indicates whether the timelines are mixing in or out. Used by timelines which perform instant transitions, + /// such as or . + public void Apply(Skeleton skeleton, float lastTime, float time, bool loop, ExposedList events, float alpha, + MixBlend blend, MixDirection direction) + { + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + + if (loop && duration != 0) + { + time %= duration; + if (lastTime > 0) lastTime %= duration; + } + + var timelines = this.timelines.Items; + for (int i = 0, n = this.timelines.Count; i < n; i++) + timelines[i].Apply(skeleton, lastTime, time, events, alpha, blend, direction); + } + + override public string ToString() + { + return name; + } + } + + /// + /// Controls how timeline values are mixed with setup pose values or current pose values when a timeline is applied with + /// alpha < 1. + /// + public enum MixBlend + { + /// Transitions from the setup value to the timeline value (the current value is not used). Before the first frame, the + /// setup value is set. + Setup, + + /// + /// + /// Transitions from the current value to the timeline value. Before the first frame, transitions from the current value to + /// the setup value. Timelines which perform instant transitions, such as or + /// , use the setup value before the first frame. + /// + /// First is intended for the first animations applied, not for animations layered on top of those. + /// + First, + + /// + /// + /// Transitions from the current value to the timeline value. No change is made before the first frame (the current value is + /// kept until the first frame). + /// + /// Replace is intended for animations layered on top of others, not for the first animations applied. + /// + Replace, + + /// + /// + /// Transitions from the current value to the current value plus the timeline value. No change is made before the first frame + /// (the current value is kept until the first frame). + /// + /// Add is intended for animations layered on top of others, not for the first animations applied. Properties + /// set by additive animations must be set manually or by another animation before applying the additive animations, else the + /// property values will increase each time the additive animations are applied. + /// + /// + Add + } + + /// + /// Indicates whether a timeline's alpha is mixing out over time toward 0 (the setup or current pose value) or + /// mixing in toward 1 (the timeline's value). Some timelines use this to decide how values are applied. + /// + public enum MixDirection + { + In, + Out + } + + internal enum Property + { + Rotate = 0, X, Y, ScaleX, ScaleY, ShearX, ShearY, // + RGB, Alpha, RGB2, // + Attachment, Deform, // + Event, DrawOrder, // + IkConstraint, TransformConstraint, // + PathConstraintPosition, PathConstraintSpacing, PathConstraintMix, // + Sequence + } + + /// + /// The base class for all timelines. + public abstract class Timeline + { + private readonly string[] propertyIds; + internal readonly float[] frames; + + /// Unique identifiers for the properties the timeline modifies. + public Timeline(int frameCount, params string[] propertyIds) + { + if (propertyIds == null) throw new System.ArgumentNullException("propertyIds", "propertyIds cannot be null."); + this.propertyIds = propertyIds; + frames = new float[frameCount * FrameEntries]; + } + + /// Uniquely encodes both the type of this timeline and the skeleton properties that it affects. + public string[] PropertyIds + { + get { return propertyIds; } + } + + /// The time in seconds and any other values for each frame. + public float[] Frames + { + get { return frames; } + } + + /// The number of entries stored per frame. + public virtual int FrameEntries + { + get { return 1; } + } + + /// The number of frames for this timeline. + public int FrameCount + { + get { return frames.Length / FrameEntries; } + } + + public float Duration + { + get + { + return frames[frames.Length - FrameEntries]; + } + } + + /// Applies this timeline to the skeleton. + /// The skeleton the timeline is being applied to. This provides access to the bones, slots, and other + /// skeleton components the timeline may change. + /// The time this timeline was last applied. Timelines such as trigger only + /// at specific times rather than every frame. In that case, the timeline triggers everything between + /// lastTime (exclusive) and time (inclusive). Pass -1 the first time an animation is + /// applied to ensure frame 0 is triggered. + /// The time in seconds that the skeleton is being posed for. Most timelines find the frame before and the frame + /// after this time and interpolate between the frame values.If beyond the last frame, the last frame will be + /// applied. + /// If any events are fired, they are added to this list. Can be null to ignore fired events or if the timeline + /// does not fire events. + /// 0 applies the current or setup value (depending on blend). 1 applies the timeline value. + /// Between 0 and 1 applies a value between the current or setup value 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 (layering). + /// Controls how mixing is applied when alpha < 1. + /// Indicates whether the timeline is mixing in or out. Used by timelines which perform instant transitions, + /// such as or , and other such as . + public abstract void Apply(Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, + MixBlend blend, MixDirection direction); + + /// Search using a stride of 1. + /// Must be >= the first value in frames. + /// The index of the first value <= time. + internal static int Search(float[] frames, float time) + { + int n = frames.Length; + for (int i = 1; i < n; i++) + if (frames[i] > time) return i - 1; + return n - 1; + } + + /// Search using the specified stride. + /// Must be >= the first value in frames. + /// The index of the first value <= time. + internal static int Search(float[] frames, float time, int step) + { + int n = frames.Length; + for (int i = step; i < n; i += step) + if (frames[i] > time) return i - step; + return n - step; + } + } + + /// An interface for timelines which change the property of a bone. + public interface IBoneTimeline + { + /// The index of the bone in that will be changed when this timeline is applied. + int BoneIndex { get; } + } + + /// An interface for timelines which change the property of a slot. + public interface ISlotTimeline + { + /// The index of the slot in that will be changed when this timeline is applied. + int SlotIndex { get; } + } + + /// The base class for timelines that interpolate between frame values using stepped, linear, or a Bezier curve. + public abstract class CurveTimeline : Timeline + { + public const int LINEAR = 0, STEPPED = 1, BEZIER = 2, BEZIER_SIZE = 18; + + internal float[] curves; + /// The number of key frames for this timeline. + + /// The maximum number of Bezier curves. See . + /// Unique identifiers for the properties the timeline modifies. + public CurveTimeline(int frameCount, int bezierCount, params string[] propertyIds) + : base(frameCount, propertyIds) + { + curves = new float[frameCount + bezierCount * BEZIER_SIZE]; + curves[frameCount - 1] = STEPPED; + } + + /// Sets the specified frame to linear interpolation. + /// Between 0 and frameCount - 1, inclusive. + public void SetLinear(int frame) + { + curves[frame] = LINEAR; + } + + /// Sets the specified frame to stepped interpolation. + /// Between 0 and frameCount - 1, inclusive. + public void SetStepped(int frame) + { + curves[frame] = STEPPED; + } + + /// Returns the interpolation type for the specified frame. + /// Between 0 and frameCount - 1, inclusive. + /// , or + the index of the Bezier segments. + public float GetCurveType(int frame) + { + return (int)curves[frame]; + } + + /// Shrinks the storage for Bezier curves, for use when bezierCount (specified in the constructor) was larger + /// than the actual number of Bezier curves. + public void Shrink(int bezierCount) + { + int size = FrameCount + bezierCount * BEZIER_SIZE; + if (curves.Length > size) + { + float[] newCurves = new float[size]; + Array.Copy(curves, 0, newCurves, 0, size); + curves = newCurves; + } + } + + /// + /// Stores the segments for the specified Bezier curve. For timelines that modify multiple values, there may be more than + /// one curve per frame. + /// The ordinal of this Bezier curve for this timeline, between 0 and bezierCount - 1 (specified + /// in the constructor), inclusive. + /// Between 0 and frameCount - 1, inclusive. + /// The index of the value for the frame this curve is used for. + /// The time for the first key. + /// The value for the first key. + /// The time for the first Bezier handle. + /// The value for the first Bezier handle. + /// The time of the second Bezier handle. + /// The value for the second Bezier handle. + /// The time for the second key. + /// The value for the second key. + public void SetBezier(int bezier, int frame, int value, float time1, float value1, float cx1, float cy1, float cx2, + float cy2, float time2, float value2) + { + + float[] curves = this.curves; + int i = FrameCount + bezier * BEZIER_SIZE; + if (value == 0) curves[frame] = BEZIER + i; + float tmpx = (time1 - cx1 * 2 + cx2) * 0.03f, tmpy = (value1 - cy1 * 2 + cy2) * 0.03f; + float dddx = ((cx1 - cx2) * 3 - time1 + time2) * 0.006f, dddy = ((cy1 - cy2) * 3 - value1 + value2) * 0.006f; + float ddx = tmpx * 2 + dddx, ddy = tmpy * 2 + dddy; + float dx = (cx1 - time1) * 0.3f + tmpx + dddx * 0.16666667f, dy = (cy1 - value1) * 0.3f + tmpy + dddy * 0.16666667f; + float x = time1 + dx, y = value1 + dy; + for (int 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; + } + } + + /// + /// Returns the Bezier interpolated value for the specified time. + /// The index into for the values of the frame before time. + /// The offset from frameIndex to the value this curve is used for. + /// The index of the Bezier segments. See . + public float GetBezierValue(float time, int frameIndex, int valueOffset, int i) + { + float[] curves = this.curves; + if (curves[i] > time) + { + float x = frames[frameIndex], y = frames[frameIndex + valueOffset]; + return y + (time - x) / (curves[i] - x) * (curves[i + 1] - y); + } + int 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 += FrameEntries; + { // scope added to prevent compile error "float x and y declared in enclosing scope" + float x = curves[n - 2], y = curves[n - 1]; + return y + (time - x) / (frames[frameIndex] - x) * (frames[frameIndex + valueOffset] - y); + } + } + } + + /// The base class for a that sets one property. + public abstract class CurveTimeline1 : CurveTimeline + { + public const int ENTRIES = 2; + internal const int VALUE = 1; + + /// The maximum number of Bezier curves. See . + /// Unique identifiers for the properties the timeline modifies. + public CurveTimeline1(int frameCount, int bezierCount, string propertyId) + : base(frameCount, bezierCount, propertyId) + { + } + + public override int FrameEntries + { + get { return ENTRIES; } + } + + /// Sets the time and value for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds + public void SetFrame(int frame, float time, float value) + { + frame <<= 1; + frames[frame] = time; + frames[frame + VALUE] = value; + } + + /// Returns the interpolated value for the specified time. + public float GetCurveValue(float time) + { + float[] frames = this.frames; + int i = frames.Length - 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 LINEAR: + float before = frames[i], value = frames[i + VALUE]; + return value + (time - before) / (frames[i + ENTRIES] - before) * (frames[i + ENTRIES + VALUE] - value); + case STEPPED: + return frames[i + VALUE]; + } + return GetBezierValue(time, i, VALUE, curveType - BEZIER); + } + } + + /// The base class for a which sets two properties. + public abstract class CurveTimeline2 : CurveTimeline + { + public const int ENTRIES = 3; + internal const int VALUE1 = 1, VALUE2 = 2; + + /// The maximum number of Bezier curves. See . + /// Unique identifiers for the properties the timeline modifies. + public CurveTimeline2(int frameCount, int bezierCount, string propertyId1, string propertyId2) + : base(frameCount, bezierCount, propertyId1, propertyId2) + { + } + + public override int FrameEntries + { + get { return ENTRIES; } + } + + /// Sets the time and values for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds. + public void SetFrame(int frame, float time, float value1, float value2) + { + frame *= ENTRIES; + frames[frame] = time; + frames[frame + VALUE1] = value1; + frames[frame + VALUE2] = value2; + } + } + + /// Changes a bone's local . + public class RotateTimeline : CurveTimeline1, IBoneTimeline + { + readonly int boneIndex; + + public RotateTimeline(int frameCount, int bezierCount, int boneIndex) + : base(frameCount, bezierCount, (int)Property.Rotate + "|" + boneIndex) + { + this.boneIndex = boneIndex; + } + + public int BoneIndex + { + get + { + return boneIndex; + } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + if (!bone.active) return; + + if (time < frames[0]) + { // Time is before first frame. + switch (blend) + { + case MixBlend.Setup: + bone.rotation = bone.data.rotation; + return; + case MixBlend.First: + bone.rotation += (bone.data.rotation - bone.rotation) * alpha; + return; + } + return; + } + + float r = GetCurveValue(time); + switch (blend) + { + case MixBlend.Setup: + bone.rotation = bone.data.rotation + r * alpha; + break; + case MixBlend.First: + case MixBlend.Replace: + r += bone.data.rotation - bone.rotation; + goto case MixBlend.Add; // Fall through. + case MixBlend.Add: + bone.rotation += r * alpha; + break; + } + } + } + + /// Changes a bone's local and . + public class TranslateTimeline : CurveTimeline2, IBoneTimeline + { + readonly int boneIndex; + + public TranslateTimeline(int frameCount, int bezierCount, int boneIndex) + : base(frameCount, bezierCount, // + (int)Property.X + "|" + boneIndex, // + (int)Property.Y + "|" + boneIndex) + { + this.boneIndex = boneIndex; + } + + public int BoneIndex + { + get + { + return boneIndex; + } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + if (!bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + 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; + return; + } + return; + } + + float x, y; + GetCurveValue(out x, out y, time); // note: reference implementation has code inlined + + 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; + break; + } + } + + public void GetCurveValue(out float x, out float y, float time) + { + int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; + switch (curveType) + { + case LINEAR: + float before = frames[i]; + x = frames[i + VALUE1]; + y = frames[i + VALUE2]; + float t = (time - before) / (frames[i + ENTRIES] - before); + x += (frames[i + ENTRIES + VALUE1] - x) * t; + y += (frames[i + ENTRIES + VALUE2] - y) * t; + break; + case STEPPED: + x = frames[i + VALUE1]; + y = frames[i + VALUE2]; + break; + default: + x = GetBezierValue(time, i, VALUE1, curveType - BEZIER); + y = GetBezierValue(time, i, VALUE2, curveType + BEZIER_SIZE - BEZIER); + break; + } + } + } + + /// Changes a bone's local . + public class TranslateXTimeline : CurveTimeline1, IBoneTimeline + { + readonly int boneIndex; + + public TranslateXTimeline(int frameCount, int bezierCount, int boneIndex) + : base(frameCount, bezierCount, (int)Property.X + "|" + boneIndex) + { + this.boneIndex = boneIndex; + } + + public int BoneIndex + { + get + { + return boneIndex; + } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + if (!bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + switch (blend) + { + case MixBlend.Setup: + bone.x = bone.data.x; + return; + case MixBlend.First: + bone.x += (bone.data.x - bone.x) * alpha; + return; + } + return; + } + + float x = GetCurveValue(time); + switch (blend) + { + case MixBlend.Setup: + bone.x = bone.data.x + x * alpha; + break; + case MixBlend.First: + case MixBlend.Replace: + bone.x += (bone.data.x + x - bone.x) * alpha; + break; + case MixBlend.Add: + bone.x += x * alpha; + break; + } + } + } + + /// Changes a bone's local . + public class TranslateYTimeline : CurveTimeline1, IBoneTimeline + { + readonly int boneIndex; + + public TranslateYTimeline(int frameCount, int bezierCount, int boneIndex) + : base(frameCount, bezierCount, (int)Property.Y + "|" + boneIndex) + { + this.boneIndex = boneIndex; + } + + public int BoneIndex + { + get + { + return boneIndex; + } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + if (!bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + switch (blend) + { + case MixBlend.Setup: + bone.y = bone.data.y; + return; + case MixBlend.First: + bone.y += (bone.data.y - bone.y) * alpha; + return; + } + return; + } + + float y = GetCurveValue(time); + switch (blend) + { + case MixBlend.Setup: + bone.y = bone.data.y + y * alpha; + break; + case MixBlend.First: + case MixBlend.Replace: + bone.y += (bone.data.y + y - bone.y) * alpha; + break; + case MixBlend.Add: + bone.y += y * alpha; + break; + } + } + } + + /// Changes a bone's local and . + public class ScaleTimeline : CurveTimeline2, IBoneTimeline + { + readonly int boneIndex; + + public ScaleTimeline(int frameCount, int bezierCount, int boneIndex) + : base(frameCount, bezierCount, // + (int)Property.ScaleX + "|" + boneIndex, // + (int)Property.ScaleY + "|" + boneIndex) + { + this.boneIndex = boneIndex; + } + + public int BoneIndex + { + get + { + return boneIndex; + } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + if (!bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + 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; + return; + } + return; + } + + float x, y; + int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; + switch (curveType) + { + case LINEAR: + float before = frames[i]; + x = frames[i + VALUE1]; + y = frames[i + VALUE2]; + float t = (time - before) / (frames[i + ENTRIES] - before); + x += (frames[i + ENTRIES + VALUE1] - x) * t; + y += (frames[i + ENTRIES + VALUE2] - y) * t; + break; + case STEPPED: + x = frames[i + VALUE1]; + y = frames[i + VALUE2]; + break; + default: + x = GetBezierValue(time, i, VALUE1, curveType - BEZIER); + y = GetBezierValue(time, i, VALUE2, curveType + BEZIER_SIZE - BEZIER); + break; + } + 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 + { + // Mixing out uses sign of setup or current pose, else use sign of key. + float bx, by; + if (direction == MixDirection.Out) + { + switch (blend) + { + case MixBlend.Setup: + bx = bone.data.scaleX; + by = bone.data.scaleY; + bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha; + bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha; + break; + case MixBlend.First: + case MixBlend.Replace: + bx = bone.scaleX; + by = bone.scaleY; + bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha; + bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha; + break; + case MixBlend.Add: + bone.scaleX += (x - bone.data.scaleX) * alpha; + bone.scaleY += (y - bone.data.scaleY) * alpha; + break; + } + } + else + { + switch (blend) + { + case MixBlend.Setup: + bx = Math.Abs(bone.data.scaleX) * Math.Sign(x); + by = Math.Abs(bone.data.scaleY) * Math.Sign(y); + bone.scaleX = bx + (x - bx) * alpha; + bone.scaleY = by + (y - by) * alpha; + break; + case MixBlend.First: + case MixBlend.Replace: + bx = Math.Abs(bone.scaleX) * Math.Sign(x); + by = Math.Abs(bone.scaleY) * Math.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; + break; + } + } + } + } + } + + /// Changes a bone's local . + public class ScaleXTimeline : CurveTimeline1, IBoneTimeline + { + readonly int boneIndex; + + public ScaleXTimeline(int frameCount, int bezierCount, int boneIndex) + : base(frameCount, bezierCount, (int)Property.ScaleX + "|" + boneIndex) + { + this.boneIndex = boneIndex; + } + + public int BoneIndex + { + get + { + return boneIndex; + } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + if (!bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + switch (blend) + { + case MixBlend.Setup: + bone.scaleX = bone.data.scaleX; + return; + case MixBlend.First: + bone.scaleX += (bone.data.scaleX - bone.scaleX) * alpha; + return; + } + return; + } + + float x = GetCurveValue(time) * bone.data.scaleX; + if (alpha == 1) + { + if (blend == MixBlend.Add) + bone.scaleX += x - bone.data.scaleX; + else + bone.scaleX = x; + } + else + { + // Mixing out uses sign of setup or current pose, else use sign of key. + float bx; + if (direction == MixDirection.Out) + { + switch (blend) + { + case MixBlend.Setup: + bx = bone.data.scaleX; + bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha; + break; + case MixBlend.First: + case MixBlend.Replace: + bx = bone.scaleX; + bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha; + break; + case MixBlend.Add: + bone.scaleX += (x - bone.data.scaleX) * alpha; + break; + } + } + else + { + switch (blend) + { + case MixBlend.Setup: + bx = Math.Abs(bone.data.scaleX) * Math.Sign(x); + bone.scaleX = bx + (x - bx) * alpha; + break; + case MixBlend.First: + case MixBlend.Replace: + bx = Math.Abs(bone.scaleX) * Math.Sign(x); + bone.scaleX = bx + (x - bx) * alpha; + break; + case MixBlend.Add: + bone.scaleX += (x - bone.data.scaleX) * alpha; + break; + } + } + } + } + } + + /// Changes a bone's local . + public class ScaleYTimeline : CurveTimeline1, IBoneTimeline + { + readonly int boneIndex; + + public ScaleYTimeline(int frameCount, int bezierCount, int boneIndex) + : base(frameCount, bezierCount, (int)Property.ScaleY + "|" + boneIndex) + { + this.boneIndex = boneIndex; + } + + public int BoneIndex + { + get + { + return boneIndex; + } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + if (!bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + switch (blend) + { + case MixBlend.Setup: + bone.scaleY = bone.data.scaleY; + return; + case MixBlend.First: + bone.scaleY += (bone.data.scaleY - bone.scaleY) * alpha; + return; + } + return; + } + + float y = GetCurveValue(time) * bone.data.scaleY; + if (alpha == 1) + { + if (blend == MixBlend.Add) + bone.scaleY += y - bone.data.scaleY; + else + bone.scaleY = y; + } + else + { + // Mixing out uses sign of setup or current pose, else use sign of key. + float by; + if (direction == MixDirection.Out) + { + switch (blend) + { + case MixBlend.Setup: + by = bone.data.scaleY; + bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha; + break; + case MixBlend.First: + case MixBlend.Replace: + by = bone.scaleY; + bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha; + break; + case MixBlend.Add: + bone.scaleY += (y - bone.data.scaleY) * alpha; + break; + } + } + else + { + switch (blend) + { + case MixBlend.Setup: + by = Math.Abs(bone.data.scaleY) * Math.Sign(y); + bone.scaleY = by + (y - by) * alpha; + break; + case MixBlend.First: + case MixBlend.Replace: + by = Math.Abs(bone.scaleY) * Math.Sign(y); + bone.scaleY = by + (y - by) * alpha; + break; + case MixBlend.Add: + bone.scaleY += (y - bone.data.scaleY) * alpha; + break; + } + } + } + } + } + + /// Changes a bone's local and . + public class ShearTimeline : CurveTimeline2, IBoneTimeline + { + readonly int boneIndex; + + public ShearTimeline(int frameCount, int bezierCount, int boneIndex) + : base(frameCount, bezierCount, // + (int)Property.ShearX + "|" + boneIndex, // + (int)Property.ShearY + "|" + boneIndex) + { + this.boneIndex = boneIndex; + } + + public int BoneIndex + { + get + { + return boneIndex; + } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + if (!bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + 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; + return; + } + return; + } + + float x, y; + int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; + switch (curveType) + { + case LINEAR: + float before = frames[i]; + x = frames[i + VALUE1]; + y = frames[i + VALUE2]; + float t = (time - before) / (frames[i + ENTRIES] - before); + x += (frames[i + ENTRIES + VALUE1] - x) * t; + y += (frames[i + ENTRIES + VALUE2] - y) * t; + break; + case STEPPED: + x = frames[i + VALUE1]; + y = frames[i + VALUE2]; + break; + default: + x = GetBezierValue(time, i, VALUE1, curveType - BEZIER); + y = GetBezierValue(time, i, VALUE2, curveType + BEZIER_SIZE - BEZIER); + break; + } + + 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; + break; + } + } + } + + /// Changes a bone's local . + public class ShearXTimeline : CurveTimeline1, IBoneTimeline + { + readonly int boneIndex; + + public ShearXTimeline(int frameCount, int bezierCount, int boneIndex) + : base(frameCount, bezierCount, (int)Property.ShearX + "|" + boneIndex) + { + this.boneIndex = boneIndex; + } + + public int BoneIndex + { + get + { + return boneIndex; + } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + if (!bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + switch (blend) + { + case MixBlend.Setup: + bone.shearX = bone.data.shearX; + return; + case MixBlend.First: + bone.shearX += (bone.data.shearX - bone.shearX) * alpha; + return; + } + return; + } + + float x = GetCurveValue(time); + switch (blend) + { + case MixBlend.Setup: + bone.shearX = bone.data.shearX + x * alpha; + break; + case MixBlend.First: + case MixBlend.Replace: + bone.shearX += (bone.data.shearX + x - bone.shearX) * alpha; + break; + case MixBlend.Add: + bone.shearX += x * alpha; + break; + } + } + } + + /// Changes a bone's local . + public class ShearYTimeline : CurveTimeline1, IBoneTimeline + { + readonly int boneIndex; + + public ShearYTimeline(int frameCount, int bezierCount, int boneIndex) + : base(frameCount, bezierCount, (int)Property.ShearY + "|" + boneIndex) + { + this.boneIndex = boneIndex; + } + + public int BoneIndex + { + get + { + return boneIndex; + } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Bone bone = skeleton.bones.Items[boneIndex]; + if (!bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + switch (blend) + { + case MixBlend.Setup: + bone.shearY = bone.data.shearY; + return; + case MixBlend.First: + bone.shearY += (bone.data.shearY - bone.shearY) * alpha; + return; + } + return; + } + + float y = GetCurveValue(time); + switch (blend) + { + case MixBlend.Setup: + bone.shearY = bone.data.shearY + y * alpha; + break; + case MixBlend.First: + case MixBlend.Replace: + bone.shearY += (bone.data.shearY + y - bone.shearY) * alpha; + break; + case MixBlend.Add: + bone.shearY += y * alpha; + break; + } + } + } + + /// Changes a slot's . + public class RGBATimeline : CurveTimeline, ISlotTimeline + { + public const int ENTRIES = 5; + protected const int R = 1, G = 2, B = 3, A = 4; + + readonly int slotIndex; + + public RGBATimeline(int frameCount, int bezierCount, int slotIndex) + : base(frameCount, bezierCount, // + (int)Property.RGB + "|" + slotIndex, // + (int)Property.Alpha + "|" + slotIndex) + { + this.slotIndex = slotIndex; + } + public override int FrameEntries + { + get { return ENTRIES; } + } + + public int SlotIndex + { + get + { + return slotIndex; + } + } + + /// Sets the time and color for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds. + public void 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; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Slot slot = skeleton.slots.Items[slotIndex]; + if (!slot.bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + var setup = slot.data; + switch (blend) + { + case MixBlend.Setup: + slot.r = setup.r; + slot.g = setup.g; + slot.b = setup.b; + slot.a = setup.a; + return; + case MixBlend.First: + slot.r += (setup.r - slot.r) * alpha; + slot.g += (setup.g - slot.g) * alpha; + slot.b += (setup.b - slot.b) * alpha; + slot.a += (setup.a - slot.a) * alpha; + slot.ClampColor(); + return; + } + return; + } + + float r, g, b, a; + int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; + switch (curveType) + { + case LINEAR: + float before = frames[i]; + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + a = frames[i + A]; + float t = (time - before) / (frames[i + ENTRIES] - before); + r += (frames[i + ENTRIES + R] - r) * t; + g += (frames[i + ENTRIES + G] - g) * t; + b += (frames[i + ENTRIES + B] - b) * t; + a += (frames[i + ENTRIES + A] - a) * t; + break; + case STEPPED: + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + a = frames[i + A]; + break; + default: + r = GetBezierValue(time, i, R, curveType - BEZIER); + g = GetBezierValue(time, i, G, curveType + BEZIER_SIZE - BEZIER); + b = GetBezierValue(time, i, B, curveType + BEZIER_SIZE * 2 - BEZIER); + a = GetBezierValue(time, i, A, curveType + BEZIER_SIZE * 3 - BEZIER); + break; + } + + if (alpha == 1) + { + slot.r = r; + slot.g = g; + slot.b = b; + slot.a = a; + } + else + { + float br, bg, bb, ba; + if (blend == MixBlend.Setup) + { + br = slot.data.r; + bg = slot.data.g; + bb = slot.data.b; + ba = slot.data.a; + } + else + { + br = slot.r; + bg = slot.g; + bb = slot.b; + ba = slot.a; + } + slot.r = br + (r - br) * alpha; + slot.g = bg + (g - bg) * alpha; + slot.b = bb + (b - bb) * alpha; + slot.a = ba + (a - ba) * alpha; + } + slot.ClampColor(); + } + } + + /// Changes the RGB for a slot's . + public class RGBTimeline : CurveTimeline, ISlotTimeline + { + public const int ENTRIES = 4; + protected const int R = 1, G = 2, B = 3; + + readonly int slotIndex; + + public RGBTimeline(int frameCount, int bezierCount, int slotIndex) + : base(frameCount, bezierCount, // + (int)Property.RGB + "|" + slotIndex) + { + this.slotIndex = slotIndex; + } + + public override int FrameEntries + { + get { return ENTRIES; } + } + + public int SlotIndex + { + get + { + return slotIndex; + } + } + + /// Sets the time and color for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds. + public void SetFrame(int frame, float time, float r, float g, float b) + { + frame <<= 2; + frames[frame] = time; + frames[frame + R] = r; + frames[frame + G] = g; + frames[frame + B] = b; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Slot slot = skeleton.slots.Items[slotIndex]; + if (!slot.bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + var setup = slot.data; + switch (blend) + { + case MixBlend.Setup: + slot.r = setup.r; + slot.g = setup.g; + slot.b = setup.b; + return; + case MixBlend.First: + slot.r += (setup.r - slot.r) * alpha; + slot.g += (setup.g - slot.g) * alpha; + slot.b += (setup.b - slot.b) * alpha; + slot.ClampColor(); + return; + } + return; + } + + float r, g, b; + int i = Search(frames, time, ENTRIES), curveType = (int)curves[i >> 2]; + switch (curveType) + { + case LINEAR: + float before = frames[i]; + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + float t = (time - before) / (frames[i + ENTRIES] - before); + r += (frames[i + ENTRIES + R] - r) * t; + g += (frames[i + ENTRIES + G] - g) * t; + b += (frames[i + ENTRIES + B] - b) * t; + break; + case STEPPED: + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + break; + default: + r = GetBezierValue(time, i, R, curveType - BEZIER); + g = GetBezierValue(time, i, G, curveType + BEZIER_SIZE - BEZIER); + b = GetBezierValue(time, i, B, curveType + BEZIER_SIZE * 2 - BEZIER); + break; + } + + if (alpha == 1) + { + slot.r = r; + slot.g = g; + slot.b = b; + } + else + { + float br, bg, bb; + if (blend == MixBlend.Setup) + { + var setup = slot.data; + br = setup.r; + bg = setup.g; + bb = setup.b; + } + else + { + br = slot.r; + bg = slot.g; + bb = slot.b; + } + slot.r = br + (r - br) * alpha; + slot.g = bg + (g - bg) * alpha; + slot.b = bb + (b - bb) * alpha; + } + slot.ClampColor(); + } + } + + /// Changes the alpha for a slot's . + public class AlphaTimeline : CurveTimeline1, ISlotTimeline + { + readonly int slotIndex; + + public AlphaTimeline(int frameCount, int bezierCount, int slotIndex) + : base(frameCount, bezierCount, (int)Property.Alpha + "|" + slotIndex) + { + this.slotIndex = slotIndex; + } + + public int SlotIndex + { + get + { + return slotIndex; + } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Slot slot = skeleton.slots.Items[slotIndex]; + if (!slot.bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + var setup = slot.data; + switch (blend) + { + case MixBlend.Setup: + slot.a = setup.a; + return; + case MixBlend.First: + slot.a += (setup.a - slot.a) * alpha; + slot.ClampColor(); + return; + } + return; + } + + float a = GetCurveValue(time); + if (alpha == 1) + slot.a = a; + else + { + if (blend == MixBlend.Setup) slot.a = slot.data.a; + slot.a += (a - slot.a) * alpha; + } + slot.ClampColor(); + } + } + + /// Changes a slot's and for two color tinting. + public class RGBA2Timeline : CurveTimeline, ISlotTimeline + { + public const int ENTRIES = 8; + protected const int R = 1, G = 2, B = 3, A = 4, R2 = 5, G2 = 6, B2 = 7; + + readonly int slotIndex; + + public RGBA2Timeline(int frameCount, int bezierCount, int slotIndex) + : base(frameCount, bezierCount, // + (int)Property.RGB + "|" + slotIndex, // + (int)Property.Alpha + "|" + slotIndex, // + (int)Property.RGB2 + "|" + slotIndex) + { + this.slotIndex = slotIndex; + } + + public override int FrameEntries + { + get + { + return ENTRIES; + } + } + + /// + /// The index of the slot in that will be changed when this timeline is applied. The + /// must have a dark color available. + public int SlotIndex + { + get + { + return slotIndex; + } + } + + /// Sets the time, light color, and dark color for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds. + public void SetFrame(int frame, float time, float r, float g, float b, float a, float r2, float g2, float b2) + { + frame <<= 3; + 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; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Slot slot = skeleton.slots.Items[slotIndex]; + if (!slot.bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + SlotData setup = slot.data; + switch (blend) + { + case MixBlend.Setup: + slot.r = setup.r; + slot.g = setup.g; + slot.b = setup.b; + slot.a = setup.a; + slot.ClampColor(); + slot.r2 = setup.r2; + slot.g2 = setup.g2; + slot.b2 = setup.b2; + slot.ClampSecondColor(); + return; + case MixBlend.First: + slot.r += (slot.r - setup.r) * alpha; + slot.g += (slot.g - setup.g) * alpha; + slot.b += (slot.b - setup.b) * alpha; + slot.a += (slot.a - setup.a) * alpha; + slot.ClampColor(); + slot.r2 += (slot.r2 - setup.r2) * alpha; + slot.g2 += (slot.g2 - setup.g2) * alpha; + slot.b2 += (slot.b2 - setup.b2) * alpha; + slot.ClampSecondColor(); + return; + } + return; + } + + float r, g, b, a, r2, g2, b2; + int i = Search(frames, time, ENTRIES), curveType = (int)curves[i >> 3]; + switch (curveType) + { + case LINEAR: + float before = frames[i]; + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + a = frames[i + A]; + r2 = frames[i + R2]; + g2 = frames[i + G2]; + b2 = frames[i + B2]; + float t = (time - before) / (frames[i + ENTRIES] - before); + r += (frames[i + ENTRIES + R] - r) * t; + g += (frames[i + ENTRIES + G] - g) * t; + b += (frames[i + ENTRIES + B] - b) * t; + a += (frames[i + ENTRIES + A] - a) * t; + r2 += (frames[i + ENTRIES + R2] - r2) * t; + g2 += (frames[i + ENTRIES + G2] - g2) * t; + b2 += (frames[i + ENTRIES + B2] - b2) * t; + break; + case STEPPED: + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + a = frames[i + A]; + r2 = frames[i + R2]; + g2 = frames[i + G2]; + b2 = frames[i + B2]; + break; + default: + r = GetBezierValue(time, i, R, curveType - BEZIER); + g = GetBezierValue(time, i, G, curveType + BEZIER_SIZE - BEZIER); + b = GetBezierValue(time, i, B, curveType + BEZIER_SIZE * 2 - BEZIER); + a = GetBezierValue(time, i, A, curveType + BEZIER_SIZE * 3 - BEZIER); + r2 = GetBezierValue(time, i, R2, curveType + BEZIER_SIZE * 4 - BEZIER); + g2 = GetBezierValue(time, i, G2, curveType + BEZIER_SIZE * 5 - BEZIER); + b2 = GetBezierValue(time, i, B2, curveType + BEZIER_SIZE * 6 - BEZIER); + break; + } + + if (alpha == 1) + { + slot.r = r; + slot.g = g; + slot.b = b; + slot.a = a; + slot.r2 = r2; + slot.g2 = g2; + slot.b2 = b2; + } + else + { + float br, bg, bb, ba, br2, bg2, bb2; + if (blend == MixBlend.Setup) + { + br = slot.data.r; + bg = slot.data.g; + bb = slot.data.b; + ba = slot.data.a; + br2 = slot.data.r2; + bg2 = slot.data.g2; + bb2 = slot.data.b2; + } + else + { + br = slot.r; + bg = slot.g; + bb = slot.b; + ba = slot.a; + br2 = slot.r2; + bg2 = slot.g2; + bb2 = slot.b2; + } + slot.r = br + (r - br) * alpha; + slot.g = bg + (g - bg) * alpha; + slot.b = bb + (b - bb) * alpha; + slot.a = ba + (a - ba) * alpha; + slot.r2 = br2 + (r2 - br2) * alpha; + slot.g2 = bg2 + (g2 - bg2) * alpha; + slot.b2 = bb2 + (b2 - bb2) * alpha; + } + slot.ClampColor(); + slot.ClampSecondColor(); + } + } + + /// Changes the RGB for a slot's and for two color tinting. + public class RGB2Timeline : CurveTimeline, ISlotTimeline + { + public const int ENTRIES = 7; + protected const int R = 1, G = 2, B = 3, R2 = 4, G2 = 5, B2 = 6; + + readonly int slotIndex; + + public RGB2Timeline(int frameCount, int bezierCount, int slotIndex) + : base(frameCount, bezierCount, // + (int)Property.RGB + "|" + slotIndex, // + (int)Property.RGB2 + "|" + slotIndex) + { + this.slotIndex = slotIndex; + } + + public override int FrameEntries + { + get + { + return ENTRIES; + } + } + + /// + /// The index of the slot in that will be changed when this timeline is applied. The + /// must have a dark color available. + public int SlotIndex + { + get + { + return slotIndex; + } + } + + /// Sets the time, light color, and dark color for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds. + public void 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; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Slot slot = skeleton.slots.Items[slotIndex]; + if (!slot.bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + SlotData setup = slot.data; + switch (blend) + { + case MixBlend.Setup: + slot.r = setup.r; + slot.g = setup.g; + slot.b = setup.b; + slot.ClampColor(); + slot.r2 = setup.r2; + slot.g2 = setup.g2; + slot.b2 = setup.b2; + slot.ClampSecondColor(); + return; + case MixBlend.First: + slot.r += (slot.r - setup.r) * alpha; + slot.g += (slot.g - setup.g) * alpha; + slot.b += (slot.b - setup.b) * alpha; + slot.ClampColor(); + slot.r2 += (slot.r2 - setup.r2) * alpha; + slot.g2 += (slot.g2 - setup.g2) * alpha; + slot.b2 += (slot.b2 - setup.b2) * alpha; + slot.ClampSecondColor(); + return; + } + return; + } + + float r, g, b, r2, g2, b2; + int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; + switch (curveType) + { + case LINEAR: + float before = frames[i]; + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + r2 = frames[i + R2]; + g2 = frames[i + G2]; + b2 = frames[i + B2]; + float t = (time - before) / (frames[i + ENTRIES] - before); + r += (frames[i + ENTRIES + R] - r) * t; + g += (frames[i + ENTRIES + G] - g) * t; + b += (frames[i + ENTRIES + B] - b) * t; + r2 += (frames[i + ENTRIES + R2] - r2) * t; + g2 += (frames[i + ENTRIES + G2] - g2) * t; + b2 += (frames[i + ENTRIES + B2] - b2) * t; + break; + case STEPPED: + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + r2 = frames[i + R2]; + g2 = frames[i + G2]; + b2 = frames[i + B2]; + break; + default: + r = GetBezierValue(time, i, R, curveType - BEZIER); + g = GetBezierValue(time, i, G, curveType + BEZIER_SIZE - BEZIER); + b = GetBezierValue(time, i, B, curveType + BEZIER_SIZE * 2 - BEZIER); + r2 = GetBezierValue(time, i, R2, curveType + BEZIER_SIZE * 3 - BEZIER); + g2 = GetBezierValue(time, i, G2, curveType + BEZIER_SIZE * 4 - BEZIER); + b2 = GetBezierValue(time, i, B2, curveType + BEZIER_SIZE * 5 - BEZIER); + break; + } + + if (alpha == 1) + { + slot.r = r; + slot.g = g; + slot.b = b; + slot.r2 = r2; + slot.g2 = g2; + slot.b2 = b2; + } + else + { + float br, bg, bb, br2, bg2, bb2; + if (blend == MixBlend.Setup) + { + SlotData setup = slot.data; + br = setup.r; + bg = setup.g; + bb = setup.b; + br2 = setup.r2; + bg2 = setup.g2; + bb2 = setup.b2; + } + else + { + br = slot.r; + bg = slot.g; + bb = slot.b; + br2 = slot.r2; + bg2 = slot.g2; + bb2 = slot.b2; + } + slot.r = br + (r - br) * alpha; + slot.g = bg + (g - bg) * alpha; + slot.b = bb + (b - bb) * alpha; + slot.r2 = br2 + (r2 - br2) * alpha; + slot.g2 = bg2 + (g2 - bg2) * alpha; + slot.b2 = bb2 + (b2 - bb2) * alpha; + } + slot.ClampColor(); + slot.ClampSecondColor(); + } + } + + /// Changes a slot's . + public class AttachmentTimeline : Timeline, ISlotTimeline + { + readonly int slotIndex; + readonly string[] attachmentNames; + + public AttachmentTimeline(int frameCount, int slotIndex) + : base(frameCount, (int)Property.Attachment + "|" + slotIndex) + { + this.slotIndex = slotIndex; + attachmentNames = new String[frameCount]; + } + + public int SlotIndex + { + get + { + return slotIndex; + } + } + + /// The attachment name for each frame. May contain null values to clear the attachment. + public string[] AttachmentNames + { + get + { + return attachmentNames; + } + } + + /// Sets the time and attachment name for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds. + public void SetFrame(int frame, float time, String attachmentName) + { + frames[frame] = time; + attachmentNames[frame] = attachmentName; + } + + public override void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + Slot slot = skeleton.slots.Items[slotIndex]; + if (!slot.bone.active) return; + + if (direction == MixDirection.Out) + { + if (blend == MixBlend.Setup) SetAttachment(skeleton, slot, slot.data.attachmentName); + return; + } + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + if (blend == MixBlend.Setup || blend == MixBlend.First) SetAttachment(skeleton, slot, slot.data.attachmentName); + return; + } + + SetAttachment(skeleton, slot, attachmentNames[Search(frames, time)]); + } + + private void SetAttachment(Skeleton skeleton, Slot slot, string attachmentName) + { + slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); + } + } + + /// Changes a slot's to deform a . + public class DeformTimeline : CurveTimeline, ISlotTimeline + { + readonly int slotIndex; + readonly VertexAttachment attachment; + internal float[][] vertices; + + public DeformTimeline(int frameCount, int bezierCount, int slotIndex, VertexAttachment attachment) + : base(frameCount, bezierCount, (int)Property.Deform + "|" + slotIndex + "|" + attachment.Id) + { + this.slotIndex = slotIndex; + this.attachment = attachment; + vertices = new float[frameCount][]; + } + + public int SlotIndex + { + get + { + return slotIndex; + } + } + /// The attachment that will be deformed. + /// + public VertexAttachment Attachment + { + get + { + return attachment; + } + } + + /// The vertices for each frame. + public float[][] Vertices + { + get + { + return vertices; + } + } + + /// Sets the time and vertices for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds. + /// Vertex positions for an unweighted VertexAttachment, or deform offsets if it has weights. + public void SetFrame(int frame, float time, float[] vertices) + { + frames[frame] = time; + this.vertices[frame] = vertices; + } + + /// Ignored (0 is used for a deform timeline). + /// Ignored (1 is used for a deform timeline). + public void setBezier(int bezier, int frame, int value, float time1, float value1, float cx1, float cy1, float cx2, + float cy2, float time2, float value2) + { + float[] curves = this.curves; + int i = FrameCount + bezier * BEZIER_SIZE; + if (value == 0) curves[frame] = BEZIER + i; + float tmpx = (time1 - cx1 * 2 + cx2) * 0.03f, tmpy = cy2 * 0.03f - cy1 * 0.06f; + float dddx = ((cx1 - cx2) * 3 - time1 + time2) * 0.006f, dddy = (cy1 - cy2 + 0.33333333f) * 0.018f; + float ddx = tmpx * 2 + dddx, ddy = tmpy * 2 + dddy; + float dx = (cx1 - time1) * 0.3f + tmpx + dddx * 0.16666667f, dy = cy1 * 0.3f + tmpy + dddy * 0.16666667f; + float x = time1 + dx, y = dy; + for (int 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; + } + } + + /// Returns the interpolated percentage for the specified time. + /// The frame before time. + private float GetCurvePercent(float time, int frame) + { + float[] curves = this.curves; + int i = (int)curves[frame]; + switch (i) + { + case LINEAR: + float x = frames[frame]; + return (time - x) / (frames[frame + FrameEntries] - x); + case STEPPED: + return 0; + } + i -= BEZIER; + if (curves[i] > time) + { + float x = frames[frame]; + return curves[i + 1] * (time - x) / (curves[i] - x); + } + int 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); + } + } + { // scope added to prevent compile error "float x and y declared in enclosing scope" + float x = curves[n - 2], y = curves[n - 1]; + return y + (1 - y) * (time - x) / (frames[frame + FrameEntries] - x); + } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + + Slot slot = skeleton.slots.Items[slotIndex]; + if (!slot.bone.active) return; + var vertexAttachment = slot.attachment as VertexAttachment; + if (vertexAttachment == null || vertexAttachment.TimelineAttachment != attachment) return; + + var deformArray = slot.deform; + if (deformArray.Count == 0) blend = MixBlend.Setup; + + float[][] vertices = this.vertices; + int vertexCount = vertices[0].Length; + + float[] deform; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + switch (blend) + { + case MixBlend.Setup: + deformArray.Clear(); + return; + case MixBlend.First: + if (alpha == 1) + { + deformArray.Clear(); + return; + } + + // Ensure size and preemptively set count. + if (deformArray.Capacity < vertexCount) deformArray.Capacity = vertexCount; + deformArray.Count = vertexCount; + deform = deformArray.Items; + + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions. + float[] setupVertices = vertexAttachment.vertices; + for (int i = 0; i < vertexCount; i++) + deform[i] += (setupVertices[i] - deform[i]) * alpha; + } + else + { + // Weighted deform offsets. + alpha = 1 - alpha; + for (int i = 0; i < vertexCount; i++) + deform[i] *= alpha; + } + return; + } + return; + } + + // Ensure size and preemptively set count. + if (deformArray.Capacity < vertexCount) deformArray.Capacity = vertexCount; + deformArray.Count = vertexCount; + deform = deformArray.Items; + + if (time >= frames[frames.Length - 1]) + { // Time is after last frame. + float[] lastVertices = vertices[frames.Length - 1]; + if (alpha == 1) + { + if (blend == MixBlend.Add) + { + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions, no alpha. + float[] setupVertices = vertexAttachment.vertices; + for (int i = 0; i < vertexCount; i++) + deform[i] += lastVertices[i] - setupVertices[i]; + } + else + { + // Weighted deform offsets, no alpha. + for (int i = 0; i < vertexCount; i++) + deform[i] += lastVertices[i]; + } + } + else + { + // Vertex positions or deform offsets, no alpha. + Array.Copy(lastVertices, 0, deform, 0, vertexCount); + } + } + else + { + switch (blend) + { + case MixBlend.Setup: + { + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions, with alpha. + float[] setupVertices = vertexAttachment.vertices; + for (int i = 0; i < vertexCount; i++) + { + float setup = setupVertices[i]; + deform[i] = setup + (lastVertices[i] - setup) * alpha; + } + } + else + { + // Weighted deform offsets, with alpha. + for (int 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 (int i = 0; i < vertexCount; i++) + deform[i] += (lastVertices[i] - deform[i]) * alpha; + break; + case MixBlend.Add: + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions, no alpha. + float[] setupVertices = vertexAttachment.vertices; + for (int i = 0; i < vertexCount; i++) + deform[i] += (lastVertices[i] - setupVertices[i]) * alpha; + } + else + { + // Weighted deform offsets, alpha. + for (int i = 0; i < vertexCount; i++) + deform[i] += lastVertices[i] * alpha; + } + break; + } + } + return; + } + + int frame = Search(frames, time); + float percent = GetCurvePercent(time, frame); + float[] prevVertices = vertices[frame]; + float[] nextVertices = vertices[frame + 1]; + + if (alpha == 1) + { + if (blend == MixBlend.Add) + { + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions, no alpha. + float[] setupVertices = vertexAttachment.vertices; + for (int 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 (int 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 (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i]; + deform[i] = prev + (nextVertices[i] - prev) * percent; + } + } + } + else + { + switch (blend) + { + case MixBlend.Setup: + { + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions, with alpha. + float[] setupVertices = vertexAttachment.vertices; + for (int 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 (int 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 (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i]; + deform[i] += (prev + (nextVertices[i] - prev) * percent - deform[i]) * alpha; + } + break; + } + case MixBlend.Add: + if (vertexAttachment.bones == null) + { + // Unweighted vertex positions, with alpha. + float[] setupVertices = vertexAttachment.vertices; + for (int 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 (int i = 0; i < vertexCount; i++) + { + float prev = prevVertices[i]; + deform[i] += (prev + (nextVertices[i] - prev) * percent) * alpha; + } + } + break; + } + } + } + } + + /// Fires an when specific animation times are reached. + public class EventTimeline : Timeline + { + readonly static string[] propertyIds = { ((int)Property.Event).ToString() }; + readonly Event[] events; + + public EventTimeline(int frameCount) + : base(frameCount, propertyIds) + { + events = new Event[frameCount]; + } + + /// The event for each frame. + public Event[] Events + { + get + { + return events; + } + } + + /// Sets the time and event for the specified frame. + /// Between 0 and frameCount, inclusive. + public void SetFrame(int frame, Event e) + { + frames[frame] = e.time; + events[frame] = e; + } + + /// Fires events for frames > lastTime and <= time. + public override void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, + MixBlend blend, MixDirection direction) + { + + if (firedEvents == null) return; + + float[] frames = this.frames; + int frameCount = frames.Length; + + if (lastTime > time) + { // Fire events after last time for looped animations. + Apply(skeleton, lastTime, int.MaxValue, firedEvents, alpha, blend, direction); + lastTime = -1f; + } + else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame. + return; + if (time < frames[0]) return; // Time is before first frame. + + int i; + if (lastTime < frames[0]) + i = 0; + else + { + i = Search(frames, lastTime) + 1; + float frameTime = frames[i]; + while (i > 0) + { // Fire multiple events with the same frame. + if (frames[i - 1] != frameTime) break; + i--; + } + } + for (; i < frameCount && time >= frames[i]; i++) + firedEvents.Add(events[i]); + } + } + + /// Changes a skeleton's . + public class DrawOrderTimeline : Timeline + { + static readonly string[] propertyIds = { ((int)Property.DrawOrder).ToString() }; + + readonly int[][] drawOrders; + + public DrawOrderTimeline(int frameCount) + : base(frameCount, propertyIds) + { + drawOrders = new int[frameCount][]; + } + + /// The draw order for each frame. + /// . + public int[][] DrawOrders + { + get + { + return drawOrders; + } + } + + /// Sets the time and draw order for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds. + /// For each slot in , the index of the slot in the new draw order. May be null to use + /// setup pose draw order. + public void SetFrame(int frame, float time, int[] drawOrder) + { + frames[frame] = time; + drawOrders[frame] = drawOrder; + } + + public override void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + + if (direction == MixDirection.Out) + { + if (blend == MixBlend.Setup) Array.Copy(skeleton.slots.Items, 0, skeleton.drawOrder.Items, 0, skeleton.slots.Count); + return; + } + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + if (blend == MixBlend.Setup || blend == MixBlend.First) Array.Copy(skeleton.slots.Items, 0, skeleton.drawOrder.Items, 0, skeleton.slots.Count); + return; + } + + int[] drawOrderToSetupIndex = drawOrders[Search(frames, time)]; + if (drawOrderToSetupIndex == null) + Array.Copy(skeleton.slots.Items, 0, skeleton.drawOrder.Items, 0, skeleton.slots.Count); + else + { + Slot[] slots = skeleton.slots.Items; + Slot[] drawOrder = skeleton.drawOrder.Items; + for (int i = 0, n = drawOrderToSetupIndex.Length; i < n; i++) + drawOrder[i] = slots[drawOrderToSetupIndex[i]]; + } + } + } + + /// Changes an IK constraint's , , + /// , , and . + public class IkConstraintTimeline : CurveTimeline + { + public const int ENTRIES = 6; + private const int MIX = 1, SOFTNESS = 2, BEND_DIRECTION = 3, COMPRESS = 4, STRETCH = 5; + + readonly int ikConstraintIndex; + + public IkConstraintTimeline(int frameCount, int bezierCount, int ikConstraintIndex) + : base(frameCount, bezierCount, (int)Property.IkConstraint + "|" + ikConstraintIndex) + { + this.ikConstraintIndex = ikConstraintIndex; + } + + public override int FrameEntries + { + get + { + return ENTRIES; + } + } + + /// The index of the IK constraint slot in that will be changed when this timeline is + /// applied. + public int IkConstraintIndex + { + get + { + return ikConstraintIndex; + } + } + + /// Sets the time, mix, softness, bend direction, compress, and stretch for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds. + /// 1 or -1. + public void 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] = bendDirection; + frames[frame + COMPRESS] = compress ? 1 : 0; + frames[frame + STRETCH] = stretch ? 1 : 0; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + IkConstraint constraint = skeleton.ikConstraints.Items[ikConstraintIndex]; + if (!constraint.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + 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; + } + return; + } + + float mix, softness; + int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; + switch (curveType) + { + case LINEAR: + float before = frames[i]; + mix = frames[i + MIX]; + softness = frames[i + SOFTNESS]; + float t = (time - before) / (frames[i + ENTRIES] - before); + mix += (frames[i + ENTRIES + MIX] - mix) * t; + softness += (frames[i + ENTRIES + SOFTNESS] - softness) * t; + break; + case STEPPED: + mix = frames[i + MIX]; + softness = frames[i + SOFTNESS]; + break; + default: + mix = GetBezierValue(time, i, MIX, curveType - BEZIER); + softness = GetBezierValue(time, i, SOFTNESS, curveType + BEZIER_SIZE - BEZIER); + break; + } + + 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 = (int)frames[i + BEND_DIRECTION]; + constraint.compress = frames[i + COMPRESS] != 0; + constraint.stretch = frames[i + STRETCH] != 0; + } + } + else + { + constraint.mix += (mix - constraint.mix) * alpha; + constraint.softness += (softness - constraint.softness) * alpha; + if (direction == MixDirection.In) + { + constraint.bendDirection = (int)frames[i + BEND_DIRECTION]; + constraint.compress = frames[i + COMPRESS] != 0; + constraint.stretch = frames[i + STRETCH] != 0; + } + } + } + } + + /// Changes a transform constraint's mixes. + public class TransformConstraintTimeline : CurveTimeline + { + public const int ENTRIES = 7; + private const int ROTATE = 1, X = 2, Y = 3, SCALEX = 4, SCALEY = 5, SHEARY = 6; + + readonly int transformConstraintIndex; + + public TransformConstraintTimeline(int frameCount, int bezierCount, int transformConstraintIndex) + : base(frameCount, bezierCount, (int)Property.TransformConstraint + "|" + transformConstraintIndex) + { + this.transformConstraintIndex = transformConstraintIndex; + } + + public override int FrameEntries + { + get + { + return ENTRIES; + } + } + + /// The index of the transform constraint slot in that will be changed when this + /// timeline is applied. + public int TransformConstraintIndex + { + get + { + return transformConstraintIndex; + } + } + + /// Sets the time, rotate mix, translate mix, scale mix, and shear mix for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds. + public void SetFrame(int 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; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + TransformConstraint constraint = skeleton.transformConstraints.Items[transformConstraintIndex]; + if (!constraint.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + TransformConstraintData data = constraint.data; + 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; + } + return; + } + + float rotate, x, y, scaleX, scaleY, shearY; + GetCurveValue(out rotate, out x, out y, out scaleX, out scaleY, out shearY, time); + + if (blend == MixBlend.Setup) + { + TransformConstraintData 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; + 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; + } + } + + public void GetCurveValue(out float rotate, out float x, out float y, + out float scaleX, out float scaleY, out float shearY, float time) + { + + float[] frames = this.frames; + int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; + switch (curveType) + { + case 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 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); + break; + } + } + } + + /// Changes a path constraint's . + public class PathConstraintPositionTimeline : CurveTimeline1 + { + readonly int pathConstraintIndex; + + public PathConstraintPositionTimeline(int frameCount, int bezierCount, int pathConstraintIndex) + : base(frameCount, bezierCount, (int)Property.PathConstraintPosition + "|" + pathConstraintIndex) + { + this.pathConstraintIndex = pathConstraintIndex; + } + + /// The index of the path constraint slot in that will be changed when this timeline + /// is applied. + public int PathConstraintIndex + { + get + { + return pathConstraintIndex; + } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; + if (!constraint.active) return; + + if (time < frames[0]) + { // Time is before first frame. + switch (blend) + { + case MixBlend.Setup: + constraint.position = constraint.data.position; + return; + case MixBlend.First: + constraint.position += (constraint.data.position - constraint.position) * alpha; + return; + } + return; + } + + float position = GetCurveValue(time); + if (blend == MixBlend.Setup) + constraint.position = constraint.data.position + (position - constraint.data.position) * alpha; + else + constraint.position += (position - constraint.position) * alpha; + } + } + + /// Changes a path constraint's . + public class PathConstraintSpacingTimeline : CurveTimeline1 + { + readonly int pathConstraintIndex; + + public PathConstraintSpacingTimeline(int frameCount, int bezierCount, int pathConstraintIndex) + : base(frameCount, bezierCount, (int)Property.PathConstraintSpacing + "|" + pathConstraintIndex) + { + this.pathConstraintIndex = pathConstraintIndex; + } + + /// The index of the path constraint slot in that will be changed when this timeline + /// is applied. + public int PathConstraintIndex + { + get + { + return pathConstraintIndex; + } + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, MixBlend blend, + MixDirection direction) + { + + PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; + if (!constraint.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + switch (blend) + { + case MixBlend.Setup: + constraint.spacing = constraint.data.spacing; + return; + case MixBlend.First: + constraint.spacing += (constraint.data.spacing - constraint.spacing) * alpha; + return; + } + return; + } + + float spacing = GetCurveValue(time); + if (blend == MixBlend.Setup) + constraint.spacing = constraint.data.spacing + (spacing - constraint.data.spacing) * alpha; + else + constraint.spacing += (spacing - constraint.spacing) * alpha; + } + } + + /// Changes a transform constraint's , , and + /// . + public class PathConstraintMixTimeline : CurveTimeline + { + public const int ENTRIES = 4; + private const int ROTATE = 1, X = 2, Y = 3; + + readonly int pathConstraintIndex; + + public PathConstraintMixTimeline(int frameCount, int bezierCount, int pathConstraintIndex) + : base(frameCount, bezierCount, (int)Property.PathConstraintMix + "|" + pathConstraintIndex) + { + this.pathConstraintIndex = pathConstraintIndex; + } + + public override int FrameEntries + { + get { return ENTRIES; } + } + + /// The index of the path constraint slot in that will be changed when this timeline + /// is applied. + public int PathConstraintIndex + { + get + { + return pathConstraintIndex; + } + } + + /// Sets the time and color for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds. + public void SetFrame(int frame, float time, float mixRotate, float mixX, float mixY) + { + frame <<= 2; + frames[frame] = time; + frames[frame + ROTATE] = mixRotate; + frames[frame + X] = mixX; + frames[frame + Y] = mixY; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex]; + if (!constraint.active) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + 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; + return; + } + return; + } + + float rotate, x, y; + int i = Search(frames, time, ENTRIES), 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); + break; + } + + 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; + } + } + } + + /// Changes a slot's for an attachment's . + public class SequenceTimeline : Timeline, ISlotTimeline + { + public const int ENTRIES = 3; + private const int MODE = 1, DELAY = 2; + + readonly int slotIndex; + readonly IHasTextureRegion attachment; + + public SequenceTimeline(int frameCount, int slotIndex, Attachment attachment) + : base(frameCount, (int)Property.Sequence + "|" + slotIndex + "|" + ((IHasTextureRegion)attachment).Sequence.Id) + { + this.slotIndex = slotIndex; + this.attachment = (IHasTextureRegion)attachment; + } + + public override int FrameEntries + { + get { return ENTRIES; } + } + + public int SlotIndex + { + get + { + return slotIndex; + } + } + public Attachment Attachment + { + get + { + return (Attachment)attachment; + } + } + + /// Sets the time, mode, index, and frame time for the specified frame. + /// Between 0 and frameCount, inclusive. + /// Seconds between frames. + public void SetFrame(int frame, float time, SequenceMode mode, int index, float delay) + { + frame *= ENTRIES; + frames[frame] = time; + frames[frame + MODE] = (int)mode | (index << 4); + frames[frame + DELAY] = delay; + } + + override public void Apply(Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) + { + + Slot slot = skeleton.slots.Items[slotIndex]; + if (!slot.bone.active) return; + Attachment slotAttachment = slot.attachment; + if (slotAttachment != attachment) + { + VertexAttachment vertexAttachment = slotAttachment as VertexAttachment; + if ((vertexAttachment == null) + || vertexAttachment.TimelineAttachment != attachment) return; + } + Sequence sequence = ((IHasTextureRegion)slotAttachment).Sequence; + if (sequence == null) return; + + float[] frames = this.frames; + if (time < frames[0]) + { // Time is before first frame. + if (blend == MixBlend.Setup || blend == MixBlend.First) slot.SequenceIndex = -1; + return; + } + + int i = Search(frames, time, ENTRIES); + float before = frames[i]; + int modeAndIndex = (int)frames[i + MODE]; + float delay = frames[i + DELAY]; + + int index = modeAndIndex >> 4, count = sequence.Regions.Length; + SequenceMode mode = (SequenceMode)(modeAndIndex & 0xf); + if (mode != SequenceMode.Hold) + { + index += (int)((time - before) / delay + 0.00001f); + switch (mode) + { + case SequenceMode.Once: + index = Math.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 = Math.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; + break; + } // end case + } + } + slot.SequenceIndex = index; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/AnimationState.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/AnimationState.cs index abf7e34..69d7539 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/AnimationState.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/AnimationState.cs @@ -30,1428 +30,1584 @@ using System; using System.Collections.Generic; -namespace Spine4_1_00 { - - /// - /// - /// Applies animations over time, queues animations for later playback, mixes (crossfading) between animations, and applies - /// multiple animations on top of each other (layering). - /// - /// See Applying Animations in the Spine Runtimes Guide. - /// - public class AnimationState { - internal static readonly Animation EmptyAnimation = new Animation("", new ExposedList(), 0); - - /// 1) A previously applied timeline has set this property. - /// Result: Mix from the current pose to the timeline pose. - internal const int Subsequent = 0; - /// 1) This is the first timeline to set this property. - /// 2) The next track entry applied after this one does not have a timeline to set this property. - /// Result: Mix from the setup pose to the timeline pose. - internal const int First = 1; - /// 1) A previously applied timeline has set this property.
- /// 2) The next track entry to be applied does have a timeline to set this property.
- /// 3) The next track entry after that one does not have a timeline to set this property.
- /// Result: Mix from the current pose to the timeline pose, but do not mix out. This avoids "dipping" when crossfading - /// animations that key the same property. A subsequent timeline will set this property using a mix. - internal const int HoldSubsequent = 2; - /// 1) This is the first timeline to set this property. - /// 2) The next track entry to be applied does have a timeline to set this property. - /// 3) The next track entry after that one does not have a timeline to set this property. - /// Result: Mix from the setup pose to the timeline pose, but do not mix out. This avoids "dipping" when crossfading animations - /// that key the same property. A subsequent timeline will set this property using a mix. - internal const int HoldFirst = 3; - /// 1) This is the first timeline to set this property. - /// 2) The next track entry to be applied does have a timeline to set this property. - /// 3) The next track entry after that one does have a timeline to set this property. - /// 4) timelineHoldMix stores the first subsequent track entry that does not have a timeline to set this property. - /// Result: The same as HOLD except the mix percentage from the timelineHoldMix track entry is used. This handles when more than - /// 2 track entries in a row have a timeline that sets the same property. - /// Eg, A -> B -> C -> D where A, B, and C have a timeline setting same property, but D does not. When A is applied, to avoid - /// "dipping" A is not mixed out, however D (the first entry that doesn't set the property) mixing in is used to mix out A - /// (which affects B and C). Without using D to mix out, A would be applied fully until mixing completes, then snap to the mixed - /// out position. - internal const int HoldMix = 4; - - internal const int Setup = 1, Current = 2; - - protected AnimationStateData data; - private readonly ExposedList tracks = new ExposedList(); - private readonly ExposedList events = new ExposedList(); - // difference to libgdx reference: delegates are used for event callbacks instead of 'final SnapshotArray listeners'. - internal void OnStart (TrackEntry entry) { if (Start != null) Start(entry); } - internal void OnInterrupt (TrackEntry entry) { if (Interrupt != null) Interrupt(entry); } - internal void OnEnd (TrackEntry entry) { if (End != null) End(entry); } - internal void OnDispose (TrackEntry entry) { if (Dispose != null) Dispose(entry); } - internal void OnComplete (TrackEntry entry) { if (Complete != null) Complete(entry); } - internal void OnEvent (TrackEntry entry, Event e) { if (Event != null) Event(entry, e); } - - public delegate void TrackEntryDelegate (TrackEntry trackEntry); - /// See - /// API Reference documentation pages here for details. Usage in C# and spine-unity is explained - /// here - /// on the spine-unity documentation pages. - public event TrackEntryDelegate Start, Interrupt, End, Dispose, Complete; - - public delegate void TrackEntryEventDelegate (TrackEntry trackEntry, Event e); - public event TrackEntryEventDelegate Event; - - public void AssignEventSubscribersFrom (AnimationState src) { - Event = src.Event; - Start = src.Start; - Interrupt = src.Interrupt; - End = src.End; - Dispose = src.Dispose; - Complete = src.Complete; - } - - public void AddEventSubscribersFrom (AnimationState src) { - Event += src.Event; - Start += src.Start; - Interrupt += src.Interrupt; - End += src.End; - Dispose += src.Dispose; - Complete += src.Complete; - } - - // end of difference - private readonly EventQueue queue; // Initialized by constructor. - private readonly HashSet propertyIds = new HashSet(); - private bool animationsChanged; - private float timeScale = 1; - private int unkeyedState; - - private readonly Pool trackEntryPool = new Pool(); - - public AnimationState (AnimationStateData data) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - this.data = data; - this.queue = new EventQueue( - this, - delegate { this.animationsChanged = true; }, - trackEntryPool - ); - } - - /// - /// Increments the track entry , setting queued animations as current if needed. - /// delta time - public void Update (float delta) { - delta *= timeScale; - TrackEntry[] tracksItems = tracks.Items; - for (int i = 0, n = tracks.Count; i < n; i++) { - TrackEntry current = tracksItems[i]; - if (current == null) continue; - - 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. - tracksItems[i] = null; - queue.End(current); - ClearNext(current); - continue; - } - if (current.mixingFrom != null && UpdateMixingFrom(current, 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(); - } - - /// Returns true when all mixing from entries are complete. - private bool 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; - - // Require mixTime > 0 to ensure the mixing from entry was applied at least once. - if (to.mixTime > 0 && to.mixTime >= to.mixDuration) { - // Require totalAlpha == 0 to ensure mixing is complete, unless mixDuration == 0 (the transition is a single frame). - if (from.totalAlpha == 0 || to.mixDuration == 0) { - to.mixingFrom = from.mixingFrom; - if (from.mixingFrom != null) from.mixingFrom.mixingTo = to; - to.interruptAlpha = from.interruptAlpha; - queue.End(from); - } - return finished; - } - - from.trackTime += delta * from.timeScale; - to.mixTime += delta; - return false; - } - - /// - /// Poses the skeleton using the track entry animations. The animation state is not changed, so can be applied to multiple - /// skeletons to pose them identically. - /// True if any animations were applied. - public bool Apply (Skeleton skeleton) { - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - if (animationsChanged) AnimationsChanged(); - - ExposedList events = this.events; - bool applied = false; - TrackEntry[] tracksItems = tracks.Items; - for (int i = 0, n = tracks.Count; i < n; i++) { - TrackEntry current = tracksItems[i]; - if (current == null || current.delay > 0) continue; - applied = true; - - // Track 0 animations aren't for layering, so do not show the previously applied animations before the first key. - MixBlend blend = i == 0 ? MixBlend.First : current.mixBlend; - - // Apply mixing from entries first. - float mix = current.alpha; - if (current.mixingFrom != null) - mix *= ApplyMixingFrom(current, skeleton, blend); - else if (current.trackTime >= current.trackEnd && current.next == null) // - mix = 0; // Set to setup pose the last time the entry will be applied. - - // Apply current entry. - float animationLast = current.animationLast, animationTime = current.AnimationTime, applyTime = animationTime; - ExposedList applyEvents = events; - if (current.reverse) { - applyTime = current.animation.duration - applyTime; - applyEvents = null; - } - - int timelineCount = current.animation.timelines.Count; - Timeline[] timelines = current.animation.timelines.Items; - if ((i == 0 && mix == 1) || blend == MixBlend.Add) { - for (int ii = 0; ii < timelineCount; ii++) { - Timeline timeline = timelines[ii]; - if (timeline is AttachmentTimeline) - ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, applyTime, blend, true); - else - timeline.Apply(skeleton, animationLast, applyTime, applyEvents, mix, blend, MixDirection.In); - } - } else { - int[] timelineMode = current.timelineMode.Items; - - bool shortestRotation = current.shortestRotation; - bool firstFrame = !shortestRotation && current.timelinesRotation.Count != timelineCount << 1; - if (firstFrame) current.timelinesRotation.Resize(timelineCount << 1); - float[] timelinesRotation = current.timelinesRotation.Items; - - for (int ii = 0; ii < timelineCount; ii++) { - Timeline timeline = timelines[ii]; - MixBlend timelineBlend = timelineMode[ii] == AnimationState.Subsequent ? blend : MixBlend.Setup; - var rotateTimeline = timeline as RotateTimeline; - if (!shortestRotation && rotateTimeline != null) - ApplyRotateTimeline(rotateTimeline, skeleton, applyTime, mix, timelineBlend, timelinesRotation, - ii << 1, firstFrame); - else if (timeline is AttachmentTimeline) - ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, applyTime, blend, true); - else - timeline.Apply(skeleton, animationLast, applyTime, applyEvents, mix, timelineBlend, MixDirection.In); - } - } - QueueEvents(current, animationTime); - events.Clear(false); - current.nextAnimationLast = animationTime; - current.nextTrackLast = current.trackTime; - } - - // Set slots attachments to the setup pose, if needed. This occurs if an animation that is mixing out sets attachments so - // subsequent timelines see any deform, but the subsequent timelines don't set an attachment (eg they are also mixing out or - // the time is before the first key). - int setupState = unkeyedState + Setup; - Slot[] slots = skeleton.slots.Items; - for (int i = 0, n = skeleton.slots.Count; i < n; i++) { - Slot slot = slots[i]; - if (slot.attachmentState == setupState) { - string attachmentName = slot.data.attachmentName; - slot.Attachment = (attachmentName == null ? null : skeleton.GetAttachment(slot.data.index, attachmentName)); - } - } - unkeyedState += 2; // Increasing after each use avoids the need to reset attachmentState for every slot. - - queue.Drain(); - return applied; - } - - /// Version of only applying and updating time at - /// EventTimelines for lightweight off-screen updates. - /// When set to false, only animation times of TrackEntries are updated. - // Note: This method is not part of the libgdx reference implementation. - public bool ApplyEventTimelinesOnly (Skeleton skeleton, bool issueEvents = true) { - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - - ExposedList events = this.events; - bool applied = false; - TrackEntry[] tracksItems = tracks.Items; - for (int i = 0, n = tracks.Count; i < n; i++) { - TrackEntry current = tracksItems[i]; - if (current == null || current.delay > 0) continue; - applied = true; - - // Apply mixing from entries first. - if (current.mixingFrom != null) ApplyMixingFromEventTimelinesOnly(current, skeleton, issueEvents); - - // Apply current entry. - float animationLast = current.animationLast, animationTime = current.AnimationTime; - - if (issueEvents) { - int timelineCount = current.animation.timelines.Count; - Timeline[] timelines = current.animation.timelines.Items; - for (int ii = 0; ii < timelineCount; ii++) { - Timeline timeline = timelines[ii]; - if (timeline is EventTimeline) - timeline.Apply(skeleton, animationLast, animationTime, events, 1.0f, MixBlend.Setup, MixDirection.In); - } - QueueEvents(current, animationTime); - events.Clear(false); - } - current.nextAnimationLast = animationTime; - current.nextTrackLast = current.trackTime; - } - - if (issueEvents) - queue.Drain(); - return applied; - } - - private float 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; // Tracks > 0 are transparent and can't reset to setup pose. - } else { - mix = to.mixTime / to.mixDuration; - if (mix > 1) mix = 1; - if (blend != MixBlend.First) blend = from.mixBlend; // Track 0 ignores track mix blend. - } - - bool attachments = mix < from.attachmentThreshold, drawOrder = mix < from.drawOrderThreshold; - int timelineCount = from.animation.timelines.Count; - Timeline[] timelines = from.animation.timelines.Items; - float alphaHold = from.alpha * to.interruptAlpha, alphaMix = alphaHold * (1 - mix); - float animationLast = from.animationLast, animationTime = from.AnimationTime, applyTime = animationTime; - ExposedList events = null; - if (from.reverse) - applyTime = from.animation.duration - applyTime; - else { - if (mix < from.eventThreshold) events = this.events; - } - - if (blend == MixBlend.Add) { - for (int i = 0; i < timelineCount; i++) - timelines[i].Apply(skeleton, animationLast, applyTime, events, alphaMix, blend, MixDirection.Out); - } else { - int[] timelineMode = from.timelineMode.Items; - TrackEntry[] timelineHoldMix = from.timelineHoldMix.Items; - - bool shortestRotation = from.shortestRotation; - bool firstFrame = !shortestRotation && from.timelinesRotation.Count != timelineCount << 1; - if (firstFrame) from.timelinesRotation.Resize(timelineCount << 1); - float[] timelinesRotation = from.timelinesRotation.Items; - - from.totalAlpha = 0; - for (int i = 0; i < timelineCount; i++) { - Timeline timeline = timelines[i]; - MixDirection direction = MixDirection.Out; - MixBlend timelineBlend; - float alpha; - switch (timelineMode[i]) { - case AnimationState.Subsequent: - if (!drawOrder && timeline is DrawOrderTimeline) continue; - timelineBlend = blend; - alpha = alphaMix; - break; - case AnimationState.First: - timelineBlend = MixBlend.Setup; - alpha = alphaMix; - break; - case AnimationState.HoldSubsequent: - timelineBlend = blend; - alpha = alphaHold; - break; - case AnimationState.HoldFirst: - timelineBlend = MixBlend.Setup; - alpha = alphaHold; - break; - default: // HoldMix - timelineBlend = MixBlend.Setup; - TrackEntry holdMix = timelineHoldMix[i]; - alpha = alphaHold * Math.Max(0, 1 - holdMix.mixTime / holdMix.mixDuration); - break; - } - from.totalAlpha += alpha; - var rotateTimeline = timeline as RotateTimeline; - if (!shortestRotation && rotateTimeline != null) { - ApplyRotateTimeline(rotateTimeline, skeleton, applyTime, alpha, timelineBlend, timelinesRotation, i << 1, - firstFrame); - } else if (timeline is AttachmentTimeline) { - ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, applyTime, timelineBlend, attachments); - } else { - if (drawOrder && timeline is DrawOrderTimeline && timelineBlend == MixBlend.Setup) - direction = MixDirection.In; - timeline.Apply(skeleton, animationLast, applyTime, events, alpha, timelineBlend, direction); - } - } - } - - if (to.mixDuration > 0) QueueEvents(from, animationTime); - this.events.Clear(false); - from.nextAnimationLast = animationTime; - from.nextTrackLast = from.trackTime; - - return mix; - } - - /// Version of only applying and updating time at - /// EventTimelines for lightweight off-screen updates. - /// When set to false, only animation times of TrackEntries are updated. - // Note: This method is not part of the libgdx reference implementation. - private float ApplyMixingFromEventTimelinesOnly (TrackEntry to, Skeleton skeleton, bool issueEvents) { - TrackEntry from = to.mixingFrom; - if (from.mixingFrom != null) ApplyMixingFromEventTimelinesOnly(from, skeleton, issueEvents); - - - float mix; - if (to.mixDuration == 0) { // Single frame mix to undo mixingFrom changes. - mix = 1; - } else { - mix = to.mixTime / to.mixDuration; - if (mix > 1) mix = 1; - } - - ExposedList eventBuffer = mix < from.eventThreshold ? this.events : null; - if (eventBuffer == null) return mix; - - float animationLast = from.animationLast, animationTime = from.AnimationTime; - if (issueEvents) { - int timelineCount = from.animation.timelines.Count; - Timeline[] timelines = from.animation.timelines.Items; - for (int i = 0; i < timelineCount; i++) { - Timeline timeline = timelines[i]; - if (timeline is EventTimeline) - timeline.Apply(skeleton, animationLast, animationTime, eventBuffer, 0, MixBlend.Setup, MixDirection.Out); - } - - if (to.mixDuration > 0) QueueEvents(from, animationTime); - this.events.Clear(false); - } - from.nextAnimationLast = animationTime; - from.nextTrackLast = from.trackTime; - - return mix; - } - - /// Applies the attachment timeline and sets . - /// False when: 1) the attachment timeline is mixing out, 2) mix < attachmentThreshold, and 3) the timeline - /// is not the last timeline to set the slot's attachment. In that case the timeline is applied only so subsequent - /// timelines see any deform. - private void ApplyAttachmentTimeline (AttachmentTimeline timeline, Skeleton skeleton, float time, MixBlend blend, - bool attachments) { - - Slot slot = skeleton.slots.Items[timeline.SlotIndex]; - if (!slot.bone.active) return; - - float[] frames = timeline.frames; - if (time < frames[0]) { // Time is before first frame. - if (blend == MixBlend.Setup || blend == MixBlend.First) - SetAttachment(skeleton, slot, slot.data.attachmentName, attachments); - } else - SetAttachment(skeleton, slot, timeline.AttachmentNames[Timeline.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.attachmentState <= unkeyedState) slot.attachmentState = unkeyedState + Setup; - } - - private void SetAttachment (Skeleton skeleton, Slot slot, String attachmentName, bool attachments) { - slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slot.data.index, attachmentName); - if (attachments) slot.attachmentState = unkeyedState + Current; - } - - /// - /// Applies the rotate timeline, mixing with the current pose while keeping the same rotation direction chosen as the shortest - /// the first time the mixing was applied. - static private void ApplyRotateTimeline (RotateTimeline timeline, Skeleton skeleton, float time, float alpha, MixBlend blend, - float[] timelinesRotation, int i, bool firstFrame) { - - if (firstFrame) timelinesRotation[i] = 0; - - if (alpha == 1) { - timeline.Apply(skeleton, 0, time, null, 1, blend, MixDirection.In); - return; - } - - Bone bone = skeleton.bones.Items[timeline.BoneIndex]; - if (!bone.active) return; - - float[] frames = timeline.frames; - float r1, r2; - if (time < frames[0]) { // Time is before first frame. - switch (blend) { - case MixBlend.Setup: - bone.rotation = bone.data.rotation; - goto default; // Fall through. - default: - return; - case MixBlend.First: - r1 = bone.rotation; - r2 = bone.data.rotation; - break; - } - } else { - r1 = blend == MixBlend.Setup ? bone.data.rotation : bone.rotation; - r2 = bone.data.rotation + timeline.GetCurveValue(time); - } - - // Mix between rotations using the direction of the shortest route on the first frame. - float total, diff = r2 - r1; - diff -= (16384 - (int)(16384.499999999996 - diff / 360)) * 360; - if (diff == 0) { - total = timelinesRotation[i]; - } else { - float lastTotal, lastDiff; - if (firstFrame) { - lastTotal = 0; - lastDiff = diff; - } else { - lastTotal = timelinesRotation[i]; // Angle and direction of mix, including loops. - lastDiff = timelinesRotation[i + 1]; // Difference between bones. - } - bool current = diff > 0, dir = lastTotal >= 0; - // Detect cross at 0 (not 180). - if (Math.Sign(lastDiff) != Math.Sign(diff) && Math.Abs(lastDiff) <= 90) { - // A cross after a 360 rotation is a loop. - if (Math.Abs(lastTotal) > 180) lastTotal += 360 * Math.Sign(lastTotal); - dir = current; - } - total = diff + lastTotal - lastTotal % 360; // Store loops as part of lastTotal. - if (dir != current) total += 360 * Math.Sign(lastTotal); - timelinesRotation[i] = total; - } - timelinesRotation[i + 1] = diff; - bone.rotation = r1 + total * alpha; - } - - private void QueueEvents (TrackEntry entry, float animationTime) { - float animationStart = entry.animationStart, animationEnd = entry.animationEnd; - float duration = animationEnd - animationStart; - float trackLastWrapped = entry.trackLast % duration; - - // Queue events before complete. - Event[] eventsItems = this.events.Items; - int i = 0, n = events.Count; - for (; i < n; i++) { - Event e = eventsItems[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) - complete = duration == 0 || (trackLastWrapped > entry.trackTime % duration); - else - complete = animationTime >= animationEnd && entry.animationLast < animationEnd; - if (complete) queue.Complete(entry); - - // Queue events after complete. - for (; i < n; i++) { - Event e = eventsItems[i]; - if (e.time < animationStart) continue; // Discard events outside animation start/end. - queue.Event(entry, eventsItems[i]); - } - } - - /// - /// Removes all animations from all tracks, leaving skeletons in their current pose. - /// - /// It may be desired to use to mix the skeletons back to the setup pose, - /// rather than leaving them in their current pose. - /// - public void ClearTracks () { - bool oldDrainDisabled = queue.drainDisabled; - queue.drainDisabled = true; - for (int i = 0, n = tracks.Count; i < n; i++) { - ClearTrack(i); - } - tracks.Clear(); - queue.drainDisabled = oldDrainDisabled; - queue.Drain(); - } - - /// - /// Removes all animations from the track, leaving skeletons in their current pose. - /// - /// It may be desired to use to mix the skeletons back to the setup pose, - /// rather than leaving them in their current pose. - /// - public void ClearTrack (int trackIndex) { - if (trackIndex >= tracks.Count) return; - TrackEntry current = tracks.Items[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.Items[current.trackIndex] = null; - - queue.Drain(); - } - - /// Sets the active TrackEntry for a given track number. - private void SetCurrent (int index, TrackEntry current, bool interrupt) { - TrackEntry from = ExpandToIndex(index); - tracks.Items[index] = current; - current.previous = null; - - if (from != null) { - if (interrupt) queue.Interrupt(from); - current.mixingFrom = from; - from.mixingTo = current; - current.mixTime = 0; - - // Store the interrupted mix percentage. - if (from.mixingFrom != null && from.mixDuration > 0) - current.interruptAlpha *= Math.Min(1, from.mixTime / from.mixDuration); - - from.timelinesRotation.Clear(); // Reset rotation for mixing out, in case entry was mixed in. - } - - queue.Start(current); // triggers AnimationsChanged - } - - /// Sets an animation by name. - public TrackEntry SetAnimation (int trackIndex, string animationName, bool loop) { - Animation animation = data.skeletonData.FindAnimation(animationName); - if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName"); - return SetAnimation(trackIndex, animation, loop); - } - - /// Sets the current animation for a track, discarding any queued animations. If the formerly current track entry was never - /// applied to a skeleton, it is replaced (not mixed from). - /// 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 determines when the track is cleared. - /// A track entry to allow further customization of animation playback. References to the track entry must not be kept - /// after the event occurs. - public TrackEntry SetAnimation (int trackIndex, Animation animation, bool loop) { - if (animation == null) throw new ArgumentNullException("animation", "animation cannot be 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.Items[trackIndex] = current.mixingFrom; - queue.Interrupt(current); - queue.End(current); - ClearNext(current); - current = current.mixingFrom; - interrupt = false; // mixingFrom is current again, but don't interrupt it twice. - } else - ClearNext(current); - } - TrackEntry entry = NewTrackEntry(trackIndex, animation, loop, current); - SetCurrent(trackIndex, entry, interrupt); - queue.Drain(); - return entry; - } - - /// Queues an animation by name. - /// - public TrackEntry AddAnimation (int trackIndex, string animationName, bool loop, float delay) { - Animation animation = data.skeletonData.FindAnimation(animationName); - if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName"); - return AddAnimation(trackIndex, animation, loop, delay); - } - - /// Adds an animation to be played after the current or last queued animation for a track. If the track is empty, it is - /// equivalent to calling . - /// - /// If > 0, sets . If <= 0, the delay set is the duration of the previous track entry - /// minus any mix duration (from the plus the specified Delay (ie the mix - /// ends at (Delay = 0) or before (Delay < 0) the previous track entry duration). If the - /// previous entry is looping, its next loop completion is used instead of its duration. - /// - /// A track entry to allow further customization of animation playback. References to the track entry must not be kept - /// after the event occurs. - public TrackEntry AddAnimation (int trackIndex, Animation animation, bool loop, float delay) { - if (animation == null) throw new ArgumentNullException("animation", "animation cannot be 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(); - } else { - last.next = entry; - entry.previous = last; - if (delay <= 0) delay += last.TrackComplete - entry.mixDuration; - } - - entry.delay = delay; - return entry; - } - - /// - /// Sets an empty animation for a track, discarding any queued animations, and sets the track entry's - /// . An empty animation has no timelines and serves as a placeholder for mixing in or out. - /// - /// Mixing out is done by setting an empty animation with a mix duration using either , - /// , or . Mixing to an empty animation causes - /// the previous animation to be applied less and less over the mix duration. Properties keyed in the previous animation - /// transition to the value from lower tracks or to the setup pose value if no lower tracks key the property. A mix duration of - /// 0 still mixes out over one frame. - /// - /// Mixing in is done by first setting an empty animation, then adding an animation using - /// with the desired delay (an empty animation has a duration of 0) and on - /// the returned track entry, set the . Mixing from an empty animation causes the new - /// animation to be applied more and more over the mix duration. Properties keyed in the new animation transition from the value - /// from lower tracks or from the setup pose value if no lower tracks key the property to the value keyed in the new - /// animation. - public TrackEntry SetEmptyAnimation (int trackIndex, float mixDuration) { - TrackEntry entry = SetAnimation(trackIndex, AnimationState.EmptyAnimation, false); - entry.mixDuration = mixDuration; - entry.trackEnd = mixDuration; - return entry; - } - - /// - /// Adds an empty animation to be played after the current or last queued animation for a track, and sets the track entry's - /// . If the track is empty, it is equivalent to calling - /// . - /// - /// Track number. - /// Mix duration. - /// If > 0, sets . If <= 0, the delay set is the duration of the previous track entry - /// minus any mix duration plus the specified Delay (ie the mix ends at (Delay = 0) or - /// before (Delay < 0) the previous track entry duration). If the previous entry is looping, its next - /// loop completion is used instead of its duration. - /// A track entry to allow further customization of animation playback. References to the track entry must not be kept - /// after the event occurs. - /// - public TrackEntry AddEmptyAnimation (int trackIndex, float mixDuration, float delay) { - TrackEntry entry = AddAnimation(trackIndex, AnimationState.EmptyAnimation, false, delay); - if (delay <= 0) entry.delay += entry.mixDuration - mixDuration; - entry.mixDuration = mixDuration; - entry.trackEnd = mixDuration; - return entry; - } - - /// - /// Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix - /// duration. - public void SetEmptyAnimations (float mixDuration) { - bool oldDrainDisabled = queue.drainDisabled; - queue.drainDisabled = true; - TrackEntry[] tracksItems = tracks.Items; - for (int i = 0, n = tracks.Count; i < n; i++) { - TrackEntry current = tracksItems[i]; - if (current != null) SetEmptyAnimation(current.trackIndex, mixDuration); - } - queue.drainDisabled = oldDrainDisabled; - queue.Drain(); - } - - private TrackEntry ExpandToIndex (int index) { - if (index < tracks.Count) return tracks.Items[index]; - tracks.Resize(index + 1); - return null; - } - - /// Object-pooling version of new TrackEntry. Obtain an unused TrackEntry from the pool and clear/initialize its values. - /// May be null. - private TrackEntry NewTrackEntry (int trackIndex, Animation animation, bool loop, TrackEntry last) { - TrackEntry entry = trackEntryPool.Obtain(); - entry.trackIndex = trackIndex; - entry.animation = animation; - entry.loop = loop; - entry.holdPrevious = false; - - entry.eventThreshold = 0; - entry.attachmentThreshold = 0; - entry.drawOrderThreshold = 0; - - entry.animationStart = 0; - entry.animationEnd = animation.Duration; - entry.animationLast = -1; - entry.nextAnimationLast = -1; - - entry.delay = 0; - entry.trackTime = 0; - entry.trackLast = -1; - entry.nextTrackLast = -1; - entry.trackEnd = float.MaxValue; - entry.timeScale = 1; - - entry.alpha = 1; - entry.interruptAlpha = 1; - entry.mixTime = 0; - entry.mixDuration = last == null ? 0 : data.GetMix(last.animation, animation); - entry.mixBlend = MixBlend.Replace; - return entry; - } - - /// Removes the next entry and all entries after it for the specified entry. - public void ClearNext (TrackEntry entry) { - TrackEntry next = entry.next; - while (next != null) { - queue.Dispose(next); - next = next.next; - } - entry.next = null; - } - - private void AnimationsChanged () { - animationsChanged = false; - - // Process in the order that animations are applied. - propertyIds.Clear(); - int n = tracks.Count; - TrackEntry[] tracksItems = tracks.Items; - for (int i = 0; i < n; i++) { - TrackEntry entry = tracksItems[i]; - if (entry == null) continue; - while (entry.mixingFrom != null) // Move to last entry, then iterate in reverse. - entry = entry.mixingFrom; - do { - if (entry.mixingTo == null || entry.mixBlend != MixBlend.Add) ComputeHold(entry); - entry = entry.mixingTo; - } while (entry != null); - } - } - - private void ComputeHold (TrackEntry entry) { - TrackEntry to = entry.mixingTo; - Timeline[] timelines = entry.animation.timelines.Items; - int timelinesCount = entry.animation.timelines.Count; - int[] timelineMode = entry.timelineMode.Resize(timelinesCount).Items; - entry.timelineHoldMix.Clear(); - TrackEntry[] timelineHoldMix = entry.timelineHoldMix.Resize(timelinesCount).Items; - HashSet propertyIds = this.propertyIds; - - if (to != null && to.holdPrevious) { - for (int i = 0; i < timelinesCount; i++) - timelineMode[i] = propertyIds.AddAll(timelines[i].PropertyIds) ? AnimationState.HoldFirst : AnimationState.HoldSubsequent; - - return; - } - - // outer: - for (int i = 0; i < timelinesCount; i++) { - Timeline timeline = timelines[i]; - String[] ids = timeline.PropertyIds; - if (!propertyIds.AddAll(ids)) - timelineMode[i] = AnimationState.Subsequent; - else if (to == null || timeline is AttachmentTimeline || timeline is DrawOrderTimeline - || timeline is EventTimeline || !to.animation.HasTimeline(ids)) { - timelineMode[i] = AnimationState.First; - } else { - for (TrackEntry next = to.mixingTo; next != null; next = next.mixingTo) { - if (next.animation.HasTimeline(ids)) continue; - if (next.mixDuration > 0) { - timelineMode[i] = AnimationState.HoldMix; - timelineHoldMix[i] = next; - goto continue_outer; // continue outer; - } - break; - } - timelineMode[i] = AnimationState.HoldFirst; - } - continue_outer: { } - } - } - - /// The track entry for the animation currently playing on the track, or null if no animation is currently playing. - public TrackEntry GetCurrent (int trackIndex) { - if (trackIndex >= tracks.Count) return null; - return tracks.Items[trackIndex]; - } - - /// Discards all listener notifications that have not yet been delivered. This can be useful to call from an - /// AnimationState event subscriber when it is known that further notifications that may have been already queued for delivery - /// are not wanted because new animations are being set. - public void ClearListenerNotifications () { - queue.Clear(); - } - - /// - /// Multiplier for the delta time when the animation state is updated, causing time for all animations and mixes to play slower - /// or faster. Defaults to 1. - /// - /// See TrackEntry for affecting a single animation. - /// - public float TimeScale { get { return timeScale; } set { timeScale = value; } } - - /// The AnimationStateData to look up mix durations. - public AnimationStateData Data { - get { - return data; - } - set { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - this.data = value; - } - } - - /// A list of tracks that have animations, which may contain nulls. - public ExposedList Tracks { get { return tracks; } } - - override public string ToString () { - var buffer = new System.Text.StringBuilder(); - TrackEntry[] tracksItems = tracks.Items; - for (int i = 0, n = tracks.Count; i < n; i++) { - TrackEntry entry = tracksItems[i]; - if (entry == null) continue; - if (buffer.Length > 0) buffer.Append(", "); - buffer.Append(entry.ToString()); - } - if (buffer.Length == 0) return ""; - return buffer.ToString(); - } - } - - /// - /// - /// Stores settings and other state for the playback of an animation on an track. - /// - /// References to a track entry must not be kept after the event occurs. - /// - public class TrackEntry : Pool.IPoolable { - internal Animation animation; - - internal TrackEntry previous, next, mixingFrom, mixingTo; - // difference to libgdx reference: delegates are used for event callbacks instead of 'AnimationStateListener listener'. - /// See - /// API Reference documentation pages here for details. Usage in C# and spine-unity is explained - /// here - /// on the spine-unity documentation pages. - public event AnimationState.TrackEntryDelegate Start, Interrupt, End, Dispose, Complete; - public event AnimationState.TrackEntryEventDelegate Event; - internal void OnStart () { if (Start != null) Start(this); } - internal void OnInterrupt () { if (Interrupt != null) Interrupt(this); } - internal void OnEnd () { if (End != null) End(this); } - internal void OnDispose () { if (Dispose != null) Dispose(this); } - internal void OnComplete () { if (Complete != null) Complete(this); } - internal void OnEvent (Event e) { if (Event != null) Event(this, e); } - - internal int trackIndex; - - internal bool loop, holdPrevious, reverse, shortestRotation; - internal float eventThreshold, attachmentThreshold, drawOrderThreshold; - internal float animationStart, animationEnd, animationLast, nextAnimationLast; - internal float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale = 1f; - internal float alpha, mixTime, mixDuration, interruptAlpha, totalAlpha; - internal MixBlend mixBlend = MixBlend.Replace; - internal readonly ExposedList timelineMode = new ExposedList(); - internal readonly ExposedList timelineHoldMix = new ExposedList(); - internal readonly ExposedList timelinesRotation = new ExposedList(); - - // IPoolable.Reset() - public void Reset () { - previous = null; - next = null; - mixingFrom = null; - mixingTo = null; - animation = null; - // replaces 'listener = null;' since delegates are used for event callbacks - Start = null; - Interrupt = null; - End = null; - Dispose = null; - Complete = null; - Event = null; - timelineMode.Clear(); - timelineHoldMix.Clear(); - timelinesRotation.Clear(); - } - - /// The index of the track where this entry is either current or queued. - /// - public int TrackIndex { get { return trackIndex; } } - - /// The animation to apply for this track entry. - public Animation Animation { get { return animation; } } - - /// - /// If true, the animation will repeat. If false it will not, instead its last frame is applied if played beyond its - /// duration. - public bool Loop { get { return loop; } set { loop = value; } } - - /// - /// - /// Seconds to postpone playing the animation. When this track entry is the current track entry, Delay - /// postpones incrementing the . When this track entry is queued, Delay is the time from - /// the start of the previous animation to when this track entry will become the current track entry (ie when the previous - /// track entry >= this track entry's Delay). - /// - /// affects the delay. - /// - /// When using with a delay <= 0, the delay - /// is set using the mix duration from the . If is set afterward, the delay - /// may need to be adjusted. - public float Delay { get { return delay; } set { delay = value; } } - - /// - /// Current time in seconds this track entry has been the current track entry. The track time determines - /// . The track time can be set to start the animation at a time other than 0, without affecting - /// looping. - public float TrackTime { get { return trackTime; } set { trackTime = value; } } - - /// - /// - /// The track time in seconds when this animation will be removed from the track. Defaults to the highest possible float - /// value, meaning the animation will be applied until a new animation is set or the track is cleared. If the track end time - /// is reached, no other animations are queued for playback, and mixing from any previous animations is complete, then the - /// properties keyed by the animation are set to the setup pose and the track is cleared. - /// - /// It may be desired to use rather than have the animation - /// abruptly cease being applied. - /// - public float TrackEnd { get { return trackEnd; } set { trackEnd = value; } } - - /// - /// If this track entry is non-looping, the track time in seconds when is reached, or the current - /// if it has already been reached. If this track entry is looping, the track time when this - /// animation will reach its next (the next loop completion). - public float TrackComplete { - get { - 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. - } - } - - /// - /// - /// Seconds when this animation starts, both initially and after looping. Defaults to 0. - /// - /// When changing the AnimationStart time, it often makes sense to set to the same - /// value to prevent timeline keys before the start time from triggering. - /// - public float AnimationStart { get { return animationStart; } set { animationStart = value; } } - - /// - /// Seconds for the last frame of this animation. Non-looping animations won't play past this time. Looping animations will - /// loop back to at this time. Defaults to the animation . - /// - public float AnimationEnd { get { return animationEnd; } set { animationEnd = value; } } - - /// - /// 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 AnimationLast time (exclusive) and - /// AnimationTime (inclusive). Defaults to -1 to ensure triggers on frame 0 happen the first time this animation - /// is applied. - public float AnimationLast { - get { return animationLast; } - set { - animationLast = value; - nextAnimationLast = value; - } - } - - /// - /// Uses to compute the AnimationTime. When the TrackTime is 0, the - /// AnimationTime is equal to the AnimationStart time. - /// - /// The animationTime is between and , except if this - /// track entry is non-looping and is >= to the animation , then - /// animationTime continues to increase past . - /// - public float AnimationTime { - get { - if (loop) { - float duration = animationEnd - animationStart; - if (duration == 0) return animationStart; - return (trackTime % duration) + animationStart; - } - float animationTime = trackTime + animationStart; - return animationEnd >= animation.duration ? animationTime : Math.Min(animationTime, animationEnd); - } - } - - /// - /// - /// Multiplier for the delta time when this track entry is updated, causing time for this animation to pass slower or - /// faster. Defaults to 1. - /// - /// Values < 0 are not supported. To play an animation in reverse, use . - /// - /// is not affected by track entry time scale, so may need to be adjusted to - /// match the animation speed. - /// - /// When using with a Delay <= 0, the - /// is set using the mix duration from the , assuming time scale to be 1. If - /// the time scale is not 1, the delay may need to be adjusted. - /// - /// See AnimationState for affecting all animations. - /// - public float TimeScale { get { return timeScale; } set { timeScale = value; } } - - /// - /// - /// Values < 1 mix this animation with the skeleton's current pose (usually the pose resulting from lower tracks). Defaults - /// to 1, which overwrites the skeleton's current pose with this animation. - /// - /// Typically track 0 is used to completely pose the skeleton, then alpha is 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. - /// - public float Alpha { get { return alpha; } set { alpha = value; } } - - public float InterruptAlpha { get { return interruptAlpha; } } - - /// - /// When the mix percentage ( / ) is less than the - /// EventThreshold, event timelines are applied while this animation is being mixed out. Defaults to 0, so event - /// timelines are not applied while this animation is being mixed out. - /// - public float EventThreshold { get { return eventThreshold; } set { eventThreshold = value; } } - - /// - /// When the mix percentage ( / ) is less than the - /// AttachmentThreshold, attachment timelines are applied while this animation is being mixed out. Defaults to - /// 0, so attachment timelines are not applied while this animation is being mixed out. - /// - public float AttachmentThreshold { get { return attachmentThreshold; } set { attachmentThreshold = value; } } - - /// - /// When the mix percentage ( / ) is less than the - /// DrawOrderThreshold, draw order timelines are applied while this animation is being mixed out. Defaults to 0, - /// so draw order timelines are not applied while this animation is being mixed out. - /// - public float DrawOrderThreshold { get { return drawOrderThreshold; } set { drawOrderThreshold = value; } } - - /// - /// The animation queued to start after this animation, or null if there is none. next makes up a doubly linked - /// list. - /// - /// See to truncate the list. - public TrackEntry Next { get { return next; } } - - /// - /// The animation queued to play before this animation, or null. previous makes up a doubly linked list. - public TrackEntry Previous { get { return previous; } } - - /// - /// Returns true if at least one loop has been completed. - /// - public bool IsComplete { - get { return trackTime >= animationEnd - animationStart; } - } - - /// - /// Seconds from 0 to the when mixing from the previous animation to this animation. May be - /// slightly more than MixDuration when the mix is complete. - public float MixTime { get { return mixTime; } set { mixTime = value; } } - - /// - /// - /// 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 MixDuration can be set manually rather than use the value from - /// . In that case, the MixDuration can be set for a new - /// track entry only before is first called. - /// - /// When using with a Delay <= 0, the - /// is set using the mix duration from the . If mixDuration is set - /// afterward, the delay may need to be adjusted. For example: - /// entry.Delay = entry.previous.TrackComplete - entry.MixDuration; - /// - public float MixDuration { get { return mixDuration; } set { mixDuration = value; } } - - /// - /// - /// Controls how properties keyed in the animation are mixed with lower tracks. Defaults to . - /// - /// Track entries on track 0 ignore this setting and always use . - /// - /// The MixBlend can be set for a new track entry only before is first - /// called. - /// - public MixBlend MixBlend { get { return mixBlend; } set { mixBlend = value; } } - - /// - /// 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 linked list. - public TrackEntry MixingFrom { get { return mixingFrom; } } - - /// - /// The track entry for the next animation when mixing from this animation to the next animation, or null if no mixing is - /// currently occuring. When mixing to multiple animations, MixingTo makes up a linked list. - public TrackEntry MixingTo { get { return mixingTo; } } - - /// - /// - /// 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. - /// - public bool HoldPrevious { get { return holdPrevious; } set { holdPrevious = value; } } - - /// - /// If true, the animation will be applied in reverse. Events are not fired when an animation is applied in reverse. - public bool Reverse { get { return reverse; } set { reverse = value; } } - - /// - /// If true, mixing rotation between tracks always uses the shortest rotation direction. If the rotation is animated, the - /// shortest rotation direction may change during the mix. - /// - /// If false, the shortest rotation direction is remembered when the mix starts and the same direction is used for the rest - /// of the mix. Defaults to false. - public bool ShortestRotation { get { return shortestRotation; } set { shortestRotation = value; } } - - /// Returns true if this entry is for the empty animation. See , - /// , and . - public bool IsEmptyAnimation { get { return animation == AnimationState.EmptyAnimation; } } - - /// - /// - /// 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 and starting animations on other tracks. - /// - /// Mixing with 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. - /// - public void ResetRotationDirections () { - timelinesRotation.Clear(); - } - - override public string ToString () { - return animation == null ? "" : animation.name; - } - - // Note: This method is required by SpineAnimationStateMixerBehaviour, - // which is part of the timeline extension package. Thus the internal member variable - // nextTrackLast is not accessible. We favor providing this method - // over exposing nextTrackLast as public property, which would rather confuse users. - public void AllowImmediateQueue () { - if (nextTrackLast < 0) nextTrackLast = 0; - } - } - - class EventQueue { - private readonly List eventQueueEntries = new List(); - internal bool drainDisabled; - - private readonly AnimationState state; - private readonly Pool trackEntryPool; - internal event Action AnimationsChanged; - - internal EventQueue (AnimationState state, Action HandleAnimationsChanged, Pool trackEntryPool) { - this.state = state; - this.AnimationsChanged += HandleAnimationsChanged; - this.trackEntryPool = trackEntryPool; - } - - internal void Start (TrackEntry entry) { - eventQueueEntries.Add(new EventQueueEntry(EventType.Start, entry)); - if (AnimationsChanged != null) AnimationsChanged(); - } - - internal void Interrupt (TrackEntry entry) { - eventQueueEntries.Add(new EventQueueEntry(EventType.Interrupt, entry)); - } - - internal void End (TrackEntry entry) { - eventQueueEntries.Add(new EventQueueEntry(EventType.End, entry)); - if (AnimationsChanged != null) AnimationsChanged(); - } - - internal void Dispose (TrackEntry entry) { - eventQueueEntries.Add(new EventQueueEntry(EventType.Dispose, entry)); - } - - internal void Complete (TrackEntry entry) { - eventQueueEntries.Add(new EventQueueEntry(EventType.Complete, entry)); - } - - internal void Event (TrackEntry entry, Event e) { - eventQueueEntries.Add(new EventQueueEntry(EventType.Event, entry, e)); - } - - /// Raises all events in the queue and drains the queue. - internal void Drain () { - if (drainDisabled) return; - drainDisabled = true; - - List eventQueueEntries = this.eventQueueEntries; - AnimationState state = this.state; - - // Don't cache eventQueueEntries.Count so callbacks can queue their own events (eg, call SetAnimation in AnimationState_Complete). - for (int i = 0; i < eventQueueEntries.Count; i++) { - EventQueueEntry queueEntry = eventQueueEntries[i]; - TrackEntry trackEntry = queueEntry.entry; - - switch (queueEntry.type) { - case EventType.Start: - trackEntry.OnStart(); - state.OnStart(trackEntry); - break; - case EventType.Interrupt: - trackEntry.OnInterrupt(); - state.OnInterrupt(trackEntry); - break; - case EventType.End: - trackEntry.OnEnd(); - state.OnEnd(trackEntry); - goto case EventType.Dispose; // Fall through. (C#) - case EventType.Dispose: - trackEntry.OnDispose(); - state.OnDispose(trackEntry); - trackEntryPool.Free(trackEntry); - break; - case EventType.Complete: - trackEntry.OnComplete(); - state.OnComplete(trackEntry); - break; - case EventType.Event: - trackEntry.OnEvent(queueEntry.e); - state.OnEvent(trackEntry, queueEntry.e); - break; - } - } - eventQueueEntries.Clear(); - - drainDisabled = false; - } - - internal void Clear () { - eventQueueEntries.Clear(); - } - - struct EventQueueEntry { - public EventType type; - public TrackEntry entry; - public Event e; - - public EventQueueEntry (EventType eventType, TrackEntry trackEntry, Event e = null) { - this.type = eventType; - this.entry = trackEntry; - this.e = e; - } - } - - enum EventType { - Start, Interrupt, End, Dispose, Complete, Event - } - } - - class Pool where T : class, new() { - public readonly int max; - readonly Stack freeObjects; - - public int Count { get { return freeObjects.Count; } } - public int Peak { get; private set; } - - public Pool (int initialCapacity = 16, int max = int.MaxValue) { - freeObjects = new Stack(initialCapacity); - this.max = max; - } - - public T Obtain () { - return freeObjects.Count == 0 ? new T() : freeObjects.Pop(); - } - - public void Free (T obj) { - if (obj == null) throw new ArgumentNullException("obj", "obj cannot be null"); - if (freeObjects.Count < max) { - freeObjects.Push(obj); - Peak = Math.Max(Peak, freeObjects.Count); - } - Reset(obj); - } - - public void Clear () { - freeObjects.Clear(); - } - - protected void Reset (T obj) { - var poolable = obj as IPoolable; - if (poolable != null) poolable.Reset(); - } - - public interface IPoolable { - void Reset (); - } - } - - public static class HashSetExtensions { - public static bool AddAll (this HashSet set, T[] addSet) { - bool anyItemAdded = false; - for (int i = 0, n = addSet.Length; i < n; ++i) { - T item = addSet[i]; - anyItemAdded |= set.Add(item); - } - return anyItemAdded; - } - } +namespace Spine4_1_00 +{ + + /// + /// + /// Applies animations over time, queues animations for later playback, mixes (crossfading) between animations, and applies + /// multiple animations on top of each other (layering). + /// + /// See Applying Animations in the Spine Runtimes Guide. + /// + public class AnimationState + { + internal static readonly Animation EmptyAnimation = new Animation("", new ExposedList(), 0); + + /// 1) A previously applied timeline has set this property. + /// Result: Mix from the current pose to the timeline pose. + internal const int Subsequent = 0; + /// 1) This is the first timeline to set this property. + /// 2) The next track entry applied after this one does not have a timeline to set this property. + /// Result: Mix from the setup pose to the timeline pose. + internal const int First = 1; + /// 1) A previously applied timeline has set this property.
+ /// 2) The next track entry to be applied does have a timeline to set this property.
+ /// 3) The next track entry after that one does not have a timeline to set this property.
+ /// Result: Mix from the current pose to the timeline pose, but do not mix out. This avoids "dipping" when crossfading + /// animations that key the same property. A subsequent timeline will set this property using a mix. + internal const int HoldSubsequent = 2; + /// 1) This is the first timeline to set this property. + /// 2) The next track entry to be applied does have a timeline to set this property. + /// 3) The next track entry after that one does not have a timeline to set this property. + /// Result: Mix from the setup pose to the timeline pose, but do not mix out. This avoids "dipping" when crossfading animations + /// that key the same property. A subsequent timeline will set this property using a mix. + internal const int HoldFirst = 3; + /// 1) This is the first timeline to set this property. + /// 2) The next track entry to be applied does have a timeline to set this property. + /// 3) The next track entry after that one does have a timeline to set this property. + /// 4) timelineHoldMix stores the first subsequent track entry that does not have a timeline to set this property. + /// Result: The same as HOLD except the mix percentage from the timelineHoldMix track entry is used. This handles when more than + /// 2 track entries in a row have a timeline that sets the same property. + /// Eg, A -> B -> C -> D where A, B, and C have a timeline setting same property, but D does not. When A is applied, to avoid + /// "dipping" A is not mixed out, however D (the first entry that doesn't set the property) mixing in is used to mix out A + /// (which affects B and C). Without using D to mix out, A would be applied fully until mixing completes, then snap to the mixed + /// out position. + internal const int HoldMix = 4; + + internal const int Setup = 1, Current = 2; + + protected AnimationStateData data; + private readonly ExposedList tracks = new ExposedList(); + private readonly ExposedList events = new ExposedList(); + // difference to libgdx reference: delegates are used for event callbacks instead of 'final SnapshotArray listeners'. + internal void OnStart(TrackEntry entry) { if (Start != null) Start(entry); } + internal void OnInterrupt(TrackEntry entry) { if (Interrupt != null) Interrupt(entry); } + internal void OnEnd(TrackEntry entry) { if (End != null) End(entry); } + internal void OnDispose(TrackEntry entry) { if (Dispose != null) Dispose(entry); } + internal void OnComplete(TrackEntry entry) { if (Complete != null) Complete(entry); } + internal void OnEvent(TrackEntry entry, Event e) { if (Event != null) Event(entry, e); } + + public delegate void TrackEntryDelegate(TrackEntry trackEntry); + /// See + /// API Reference documentation pages here for details. Usage in C# and spine-unity is explained + /// here + /// on the spine-unity documentation pages. + public event TrackEntryDelegate Start, Interrupt, End, Dispose, Complete; + + public delegate void TrackEntryEventDelegate(TrackEntry trackEntry, Event e); + public event TrackEntryEventDelegate Event; + + public void AssignEventSubscribersFrom(AnimationState src) + { + Event = src.Event; + Start = src.Start; + Interrupt = src.Interrupt; + End = src.End; + Dispose = src.Dispose; + Complete = src.Complete; + } + + public void AddEventSubscribersFrom(AnimationState src) + { + Event += src.Event; + Start += src.Start; + Interrupt += src.Interrupt; + End += src.End; + Dispose += src.Dispose; + Complete += src.Complete; + } + + // end of difference + private readonly EventQueue queue; // Initialized by constructor. + private readonly HashSet propertyIds = new HashSet(); + private bool animationsChanged; + private float timeScale = 1; + private int unkeyedState; + + private readonly Pool trackEntryPool = new Pool(); + + public AnimationState(AnimationStateData data) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + this.data = data; + this.queue = new EventQueue( + this, + delegate { this.animationsChanged = true; }, + trackEntryPool + ); + } + + /// + /// Increments the track entry , setting queued animations as current if needed. + /// delta time + public void Update(float delta) + { + delta *= timeScale; + TrackEntry[] tracksItems = tracks.Items; + for (int i = 0, n = tracks.Count; i < n; i++) + { + TrackEntry current = tracksItems[i]; + if (current == null) continue; + + 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. + tracksItems[i] = null; + queue.End(current); + ClearNext(current); + continue; + } + if (current.mixingFrom != null && UpdateMixingFrom(current, 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(); + } + + /// Returns true when all mixing from entries are complete. + private bool 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; + + // Require mixTime > 0 to ensure the mixing from entry was applied at least once. + if (to.mixTime > 0 && to.mixTime >= to.mixDuration) + { + // Require totalAlpha == 0 to ensure mixing is complete, unless mixDuration == 0 (the transition is a single frame). + if (from.totalAlpha == 0 || to.mixDuration == 0) + { + to.mixingFrom = from.mixingFrom; + if (from.mixingFrom != null) from.mixingFrom.mixingTo = to; + to.interruptAlpha = from.interruptAlpha; + queue.End(from); + } + return finished; + } + + from.trackTime += delta * from.timeScale; + to.mixTime += delta; + return false; + } + + /// + /// Poses the skeleton using the track entry animations. The animation state is not changed, so can be applied to multiple + /// skeletons to pose them identically. + /// True if any animations were applied. + public bool Apply(Skeleton skeleton) + { + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + if (animationsChanged) AnimationsChanged(); + + ExposedList events = this.events; + bool applied = false; + TrackEntry[] tracksItems = tracks.Items; + for (int i = 0, n = tracks.Count; i < n; i++) + { + TrackEntry current = tracksItems[i]; + if (current == null || current.delay > 0) continue; + applied = true; + + // Track 0 animations aren't for layering, so do not show the previously applied animations before the first key. + MixBlend blend = i == 0 ? MixBlend.First : current.mixBlend; + + // Apply mixing from entries first. + float mix = current.alpha; + if (current.mixingFrom != null) + mix *= ApplyMixingFrom(current, skeleton, blend); + else if (current.trackTime >= current.trackEnd && current.next == null) // + mix = 0; // Set to setup pose the last time the entry will be applied. + + // Apply current entry. + float animationLast = current.animationLast, animationTime = current.AnimationTime, applyTime = animationTime; + ExposedList applyEvents = events; + if (current.reverse) + { + applyTime = current.animation.duration - applyTime; + applyEvents = null; + } + + int timelineCount = current.animation.timelines.Count; + Timeline[] timelines = current.animation.timelines.Items; + if ((i == 0 && mix == 1) || blend == MixBlend.Add) + { + for (int ii = 0; ii < timelineCount; ii++) + { + Timeline timeline = timelines[ii]; + if (timeline is AttachmentTimeline) + ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, applyTime, blend, true); + else + timeline.Apply(skeleton, animationLast, applyTime, applyEvents, mix, blend, MixDirection.In); + } + } + else + { + int[] timelineMode = current.timelineMode.Items; + + bool shortestRotation = current.shortestRotation; + bool firstFrame = !shortestRotation && current.timelinesRotation.Count != timelineCount << 1; + if (firstFrame) current.timelinesRotation.Resize(timelineCount << 1); + float[] timelinesRotation = current.timelinesRotation.Items; + + for (int ii = 0; ii < timelineCount; ii++) + { + Timeline timeline = timelines[ii]; + MixBlend timelineBlend = timelineMode[ii] == AnimationState.Subsequent ? blend : MixBlend.Setup; + var rotateTimeline = timeline as RotateTimeline; + if (!shortestRotation && rotateTimeline != null) + ApplyRotateTimeline(rotateTimeline, skeleton, applyTime, mix, timelineBlend, timelinesRotation, + ii << 1, firstFrame); + else if (timeline is AttachmentTimeline) + ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, applyTime, blend, true); + else + timeline.Apply(skeleton, animationLast, applyTime, applyEvents, mix, timelineBlend, MixDirection.In); + } + } + QueueEvents(current, animationTime); + events.Clear(false); + current.nextAnimationLast = animationTime; + current.nextTrackLast = current.trackTime; + } + + // Set slots attachments to the setup pose, if needed. This occurs if an animation that is mixing out sets attachments so + // subsequent timelines see any deform, but the subsequent timelines don't set an attachment (eg they are also mixing out or + // the time is before the first key). + int setupState = unkeyedState + Setup; + Slot[] slots = skeleton.slots.Items; + for (int i = 0, n = skeleton.slots.Count; i < n; i++) + { + Slot slot = slots[i]; + if (slot.attachmentState == setupState) + { + string attachmentName = slot.data.attachmentName; + slot.Attachment = (attachmentName == null ? null : skeleton.GetAttachment(slot.data.index, attachmentName)); + } + } + unkeyedState += 2; // Increasing after each use avoids the need to reset attachmentState for every slot. + + queue.Drain(); + return applied; + } + + /// Version of only applying and updating time at + /// EventTimelines for lightweight off-screen updates. + /// When set to false, only animation times of TrackEntries are updated. + // Note: This method is not part of the libgdx reference implementation. + public bool ApplyEventTimelinesOnly(Skeleton skeleton, bool issueEvents = true) + { + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + + ExposedList events = this.events; + bool applied = false; + TrackEntry[] tracksItems = tracks.Items; + for (int i = 0, n = tracks.Count; i < n; i++) + { + TrackEntry current = tracksItems[i]; + if (current == null || current.delay > 0) continue; + applied = true; + + // Apply mixing from entries first. + if (current.mixingFrom != null) ApplyMixingFromEventTimelinesOnly(current, skeleton, issueEvents); + + // Apply current entry. + float animationLast = current.animationLast, animationTime = current.AnimationTime; + + if (issueEvents) + { + int timelineCount = current.animation.timelines.Count; + Timeline[] timelines = current.animation.timelines.Items; + for (int ii = 0; ii < timelineCount; ii++) + { + Timeline timeline = timelines[ii]; + if (timeline is EventTimeline) + timeline.Apply(skeleton, animationLast, animationTime, events, 1.0f, MixBlend.Setup, MixDirection.In); + } + QueueEvents(current, animationTime); + events.Clear(false); + } + current.nextAnimationLast = animationTime; + current.nextTrackLast = current.trackTime; + } + + if (issueEvents) + queue.Drain(); + return applied; + } + + private float 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; // Tracks > 0 are transparent and can't reset to setup pose. + } + else + { + mix = to.mixTime / to.mixDuration; + if (mix > 1) mix = 1; + if (blend != MixBlend.First) blend = from.mixBlend; // Track 0 ignores track mix blend. + } + + bool attachments = mix < from.attachmentThreshold, drawOrder = mix < from.drawOrderThreshold; + int timelineCount = from.animation.timelines.Count; + Timeline[] timelines = from.animation.timelines.Items; + float alphaHold = from.alpha * to.interruptAlpha, alphaMix = alphaHold * (1 - mix); + float animationLast = from.animationLast, animationTime = from.AnimationTime, applyTime = animationTime; + ExposedList events = null; + if (from.reverse) + applyTime = from.animation.duration - applyTime; + else + { + if (mix < from.eventThreshold) events = this.events; + } + + if (blend == MixBlend.Add) + { + for (int i = 0; i < timelineCount; i++) + timelines[i].Apply(skeleton, animationLast, applyTime, events, alphaMix, blend, MixDirection.Out); + } + else + { + int[] timelineMode = from.timelineMode.Items; + TrackEntry[] timelineHoldMix = from.timelineHoldMix.Items; + + bool shortestRotation = from.shortestRotation; + bool firstFrame = !shortestRotation && from.timelinesRotation.Count != timelineCount << 1; + if (firstFrame) from.timelinesRotation.Resize(timelineCount << 1); + float[] timelinesRotation = from.timelinesRotation.Items; + + from.totalAlpha = 0; + for (int i = 0; i < timelineCount; i++) + { + Timeline timeline = timelines[i]; + MixDirection direction = MixDirection.Out; + MixBlend timelineBlend; + float alpha; + switch (timelineMode[i]) + { + case AnimationState.Subsequent: + if (!drawOrder && timeline is DrawOrderTimeline) continue; + timelineBlend = blend; + alpha = alphaMix; + break; + case AnimationState.First: + timelineBlend = MixBlend.Setup; + alpha = alphaMix; + break; + case AnimationState.HoldSubsequent: + timelineBlend = blend; + alpha = alphaHold; + break; + case AnimationState.HoldFirst: + timelineBlend = MixBlend.Setup; + alpha = alphaHold; + break; + default: // HoldMix + timelineBlend = MixBlend.Setup; + TrackEntry holdMix = timelineHoldMix[i]; + alpha = alphaHold * Math.Max(0, 1 - holdMix.mixTime / holdMix.mixDuration); + break; + } + from.totalAlpha += alpha; + var rotateTimeline = timeline as RotateTimeline; + if (!shortestRotation && rotateTimeline != null) + { + ApplyRotateTimeline(rotateTimeline, skeleton, applyTime, alpha, timelineBlend, timelinesRotation, i << 1, + firstFrame); + } + else if (timeline is AttachmentTimeline) + { + ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, applyTime, timelineBlend, attachments); + } + else + { + if (drawOrder && timeline is DrawOrderTimeline && timelineBlend == MixBlend.Setup) + direction = MixDirection.In; + timeline.Apply(skeleton, animationLast, applyTime, events, alpha, timelineBlend, direction); + } + } + } + + if (to.mixDuration > 0) QueueEvents(from, animationTime); + this.events.Clear(false); + from.nextAnimationLast = animationTime; + from.nextTrackLast = from.trackTime; + + return mix; + } + + /// Version of only applying and updating time at + /// EventTimelines for lightweight off-screen updates. + /// When set to false, only animation times of TrackEntries are updated. + // Note: This method is not part of the libgdx reference implementation. + private float ApplyMixingFromEventTimelinesOnly(TrackEntry to, Skeleton skeleton, bool issueEvents) + { + TrackEntry from = to.mixingFrom; + if (from.mixingFrom != null) ApplyMixingFromEventTimelinesOnly(from, skeleton, issueEvents); + + + float mix; + if (to.mixDuration == 0) + { // Single frame mix to undo mixingFrom changes. + mix = 1; + } + else + { + mix = to.mixTime / to.mixDuration; + if (mix > 1) mix = 1; + } + + ExposedList eventBuffer = mix < from.eventThreshold ? this.events : null; + if (eventBuffer == null) return mix; + + float animationLast = from.animationLast, animationTime = from.AnimationTime; + if (issueEvents) + { + int timelineCount = from.animation.timelines.Count; + Timeline[] timelines = from.animation.timelines.Items; + for (int i = 0; i < timelineCount; i++) + { + Timeline timeline = timelines[i]; + if (timeline is EventTimeline) + timeline.Apply(skeleton, animationLast, animationTime, eventBuffer, 0, MixBlend.Setup, MixDirection.Out); + } + + if (to.mixDuration > 0) QueueEvents(from, animationTime); + this.events.Clear(false); + } + from.nextAnimationLast = animationTime; + from.nextTrackLast = from.trackTime; + + return mix; + } + + /// Applies the attachment timeline and sets . + /// False when: 1) the attachment timeline is mixing out, 2) mix < attachmentThreshold, and 3) the timeline + /// is not the last timeline to set the slot's attachment. In that case the timeline is applied only so subsequent + /// timelines see any deform. + private void ApplyAttachmentTimeline(AttachmentTimeline timeline, Skeleton skeleton, float time, MixBlend blend, + bool attachments) + { + + Slot slot = skeleton.slots.Items[timeline.SlotIndex]; + if (!slot.bone.active) return; + + float[] frames = timeline.frames; + if (time < frames[0]) + { // Time is before first frame. + if (blend == MixBlend.Setup || blend == MixBlend.First) + SetAttachment(skeleton, slot, slot.data.attachmentName, attachments); + } + else + SetAttachment(skeleton, slot, timeline.AttachmentNames[Timeline.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.attachmentState <= unkeyedState) slot.attachmentState = unkeyedState + Setup; + } + + private void SetAttachment(Skeleton skeleton, Slot slot, String attachmentName, bool attachments) + { + slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slot.data.index, attachmentName); + if (attachments) slot.attachmentState = unkeyedState + Current; + } + + /// + /// Applies the rotate timeline, mixing with the current pose while keeping the same rotation direction chosen as the shortest + /// the first time the mixing was applied. + static private void ApplyRotateTimeline(RotateTimeline timeline, Skeleton skeleton, float time, float alpha, MixBlend blend, + float[] timelinesRotation, int i, bool firstFrame) + { + + if (firstFrame) timelinesRotation[i] = 0; + + if (alpha == 1) + { + timeline.Apply(skeleton, 0, time, null, 1, blend, MixDirection.In); + return; + } + + Bone bone = skeleton.bones.Items[timeline.BoneIndex]; + if (!bone.active) return; + + float[] frames = timeline.frames; + float r1, r2; + if (time < frames[0]) + { // Time is before first frame. + switch (blend) + { + case MixBlend.Setup: + bone.rotation = bone.data.rotation; + goto default; // Fall through. + default: + return; + case MixBlend.First: + r1 = bone.rotation; + r2 = bone.data.rotation; + break; + } + } + else + { + r1 = blend == MixBlend.Setup ? bone.data.rotation : bone.rotation; + r2 = bone.data.rotation + timeline.GetCurveValue(time); + } + + // Mix between rotations using the direction of the shortest route on the first frame. + float total, diff = r2 - r1; + diff -= (16384 - (int)(16384.499999999996 - diff / 360)) * 360; + if (diff == 0) + { + total = timelinesRotation[i]; + } + else + { + float lastTotal, lastDiff; + if (firstFrame) + { + lastTotal = 0; + lastDiff = diff; + } + else + { + lastTotal = timelinesRotation[i]; // Angle and direction of mix, including loops. + lastDiff = timelinesRotation[i + 1]; // Difference between bones. + } + bool current = diff > 0, dir = lastTotal >= 0; + // Detect cross at 0 (not 180). + if (Math.Sign(lastDiff) != Math.Sign(diff) && Math.Abs(lastDiff) <= 90) + { + // A cross after a 360 rotation is a loop. + if (Math.Abs(lastTotal) > 180) lastTotal += 360 * Math.Sign(lastTotal); + dir = current; + } + total = diff + lastTotal - lastTotal % 360; // Store loops as part of lastTotal. + if (dir != current) total += 360 * Math.Sign(lastTotal); + timelinesRotation[i] = total; + } + timelinesRotation[i + 1] = diff; + bone.rotation = r1 + total * alpha; + } + + private void QueueEvents(TrackEntry entry, float animationTime) + { + float animationStart = entry.animationStart, animationEnd = entry.animationEnd; + float duration = animationEnd - animationStart; + float trackLastWrapped = entry.trackLast % duration; + + // Queue events before complete. + Event[] eventsItems = this.events.Items; + int i = 0, n = events.Count; + for (; i < n; i++) + { + Event e = eventsItems[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) + complete = duration == 0 || (trackLastWrapped > entry.trackTime % duration); + else + complete = animationTime >= animationEnd && entry.animationLast < animationEnd; + if (complete) queue.Complete(entry); + + // Queue events after complete. + for (; i < n; i++) + { + Event e = eventsItems[i]; + if (e.time < animationStart) continue; // Discard events outside animation start/end. + queue.Event(entry, eventsItems[i]); + } + } + + /// + /// Removes all animations from all tracks, leaving skeletons in their current pose. + /// + /// It may be desired to use to mix the skeletons back to the setup pose, + /// rather than leaving them in their current pose. + /// + public void ClearTracks() + { + bool oldDrainDisabled = queue.drainDisabled; + queue.drainDisabled = true; + for (int i = 0, n = tracks.Count; i < n; i++) + { + ClearTrack(i); + } + tracks.Clear(); + queue.drainDisabled = oldDrainDisabled; + queue.Drain(); + } + + /// + /// Removes all animations from the track, leaving skeletons in their current pose. + /// + /// It may be desired to use to mix the skeletons back to the setup pose, + /// rather than leaving them in their current pose. + /// + public void ClearTrack(int trackIndex) + { + if (trackIndex >= tracks.Count) return; + TrackEntry current = tracks.Items[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.Items[current.trackIndex] = null; + + queue.Drain(); + } + + /// Sets the active TrackEntry for a given track number. + private void SetCurrent(int index, TrackEntry current, bool interrupt) + { + TrackEntry from = ExpandToIndex(index); + tracks.Items[index] = current; + current.previous = null; + + if (from != null) + { + if (interrupt) queue.Interrupt(from); + current.mixingFrom = from; + from.mixingTo = current; + current.mixTime = 0; + + // Store the interrupted mix percentage. + if (from.mixingFrom != null && from.mixDuration > 0) + current.interruptAlpha *= Math.Min(1, from.mixTime / from.mixDuration); + + from.timelinesRotation.Clear(); // Reset rotation for mixing out, in case entry was mixed in. + } + + queue.Start(current); // triggers AnimationsChanged + } + + /// Sets an animation by name. + public TrackEntry SetAnimation(int trackIndex, string animationName, bool loop) + { + Animation animation = data.skeletonData.FindAnimation(animationName); + if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName"); + return SetAnimation(trackIndex, animation, loop); + } + + /// Sets the current animation for a track, discarding any queued animations. If the formerly current track entry was never + /// applied to a skeleton, it is replaced (not mixed from). + /// 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 determines when the track is cleared. + /// A track entry to allow further customization of animation playback. References to the track entry must not be kept + /// after the event occurs. + public TrackEntry SetAnimation(int trackIndex, Animation animation, bool loop) + { + if (animation == null) throw new ArgumentNullException("animation", "animation cannot be 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.Items[trackIndex] = current.mixingFrom; + queue.Interrupt(current); + queue.End(current); + ClearNext(current); + current = current.mixingFrom; + interrupt = false; // mixingFrom is current again, but don't interrupt it twice. + } + else + ClearNext(current); + } + TrackEntry entry = NewTrackEntry(trackIndex, animation, loop, current); + SetCurrent(trackIndex, entry, interrupt); + queue.Drain(); + return entry; + } + + /// Queues an animation by name. + /// + public TrackEntry AddAnimation(int trackIndex, string animationName, bool loop, float delay) + { + Animation animation = data.skeletonData.FindAnimation(animationName); + if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName"); + return AddAnimation(trackIndex, animation, loop, delay); + } + + /// Adds an animation to be played after the current or last queued animation for a track. If the track is empty, it is + /// equivalent to calling . + /// + /// If > 0, sets . If <= 0, the delay set is the duration of the previous track entry + /// minus any mix duration (from the plus the specified Delay (ie the mix + /// ends at (Delay = 0) or before (Delay < 0) the previous track entry duration). If the + /// previous entry is looping, its next loop completion is used instead of its duration. + /// + /// A track entry to allow further customization of animation playback. References to the track entry must not be kept + /// after the event occurs. + public TrackEntry AddAnimation(int trackIndex, Animation animation, bool loop, float delay) + { + if (animation == null) throw new ArgumentNullException("animation", "animation cannot be 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(); + } + else + { + last.next = entry; + entry.previous = last; + if (delay <= 0) delay += last.TrackComplete - entry.mixDuration; + } + + entry.delay = delay; + return entry; + } + + /// + /// Sets an empty animation for a track, discarding any queued animations, and sets the track entry's + /// . An empty animation has no timelines and serves as a placeholder for mixing in or out. + /// + /// Mixing out is done by setting an empty animation with a mix duration using either , + /// , or . Mixing to an empty animation causes + /// the previous animation to be applied less and less over the mix duration. Properties keyed in the previous animation + /// transition to the value from lower tracks or to the setup pose value if no lower tracks key the property. A mix duration of + /// 0 still mixes out over one frame. + /// + /// Mixing in is done by first setting an empty animation, then adding an animation using + /// with the desired delay (an empty animation has a duration of 0) and on + /// the returned track entry, set the . Mixing from an empty animation causes the new + /// animation to be applied more and more over the mix duration. Properties keyed in the new animation transition from the value + /// from lower tracks or from the setup pose value if no lower tracks key the property to the value keyed in the new + /// animation. + public TrackEntry SetEmptyAnimation(int trackIndex, float mixDuration) + { + TrackEntry entry = SetAnimation(trackIndex, AnimationState.EmptyAnimation, false); + entry.mixDuration = mixDuration; + entry.trackEnd = mixDuration; + return entry; + } + + /// + /// Adds an empty animation to be played after the current or last queued animation for a track, and sets the track entry's + /// . If the track is empty, it is equivalent to calling + /// . + /// + /// Track number. + /// Mix duration. + /// If > 0, sets . If <= 0, the delay set is the duration of the previous track entry + /// minus any mix duration plus the specified Delay (ie the mix ends at (Delay = 0) or + /// before (Delay < 0) the previous track entry duration). If the previous entry is looping, its next + /// loop completion is used instead of its duration. + /// A track entry to allow further customization of animation playback. References to the track entry must not be kept + /// after the event occurs. + /// + public TrackEntry AddEmptyAnimation(int trackIndex, float mixDuration, float delay) + { + TrackEntry entry = AddAnimation(trackIndex, AnimationState.EmptyAnimation, false, delay); + if (delay <= 0) entry.delay += entry.mixDuration - mixDuration; + entry.mixDuration = mixDuration; + entry.trackEnd = mixDuration; + return entry; + } + + /// + /// Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix + /// duration. + public void SetEmptyAnimations(float mixDuration) + { + bool oldDrainDisabled = queue.drainDisabled; + queue.drainDisabled = true; + TrackEntry[] tracksItems = tracks.Items; + for (int i = 0, n = tracks.Count; i < n; i++) + { + TrackEntry current = tracksItems[i]; + if (current != null) SetEmptyAnimation(current.trackIndex, mixDuration); + } + queue.drainDisabled = oldDrainDisabled; + queue.Drain(); + } + + private TrackEntry ExpandToIndex(int index) + { + if (index < tracks.Count) return tracks.Items[index]; + tracks.Resize(index + 1); + return null; + } + + /// Object-pooling version of new TrackEntry. Obtain an unused TrackEntry from the pool and clear/initialize its values. + /// May be null. + private TrackEntry NewTrackEntry(int trackIndex, Animation animation, bool loop, TrackEntry last) + { + TrackEntry entry = trackEntryPool.Obtain(); + entry.trackIndex = trackIndex; + entry.animation = animation; + entry.loop = loop; + entry.holdPrevious = false; + + entry.eventThreshold = 0; + entry.attachmentThreshold = 0; + entry.drawOrderThreshold = 0; + + entry.animationStart = 0; + entry.animationEnd = animation.Duration; + entry.animationLast = -1; + entry.nextAnimationLast = -1; + + entry.delay = 0; + entry.trackTime = 0; + entry.trackLast = -1; + entry.nextTrackLast = -1; + entry.trackEnd = float.MaxValue; + entry.timeScale = 1; + + entry.alpha = 1; + entry.interruptAlpha = 1; + entry.mixTime = 0; + entry.mixDuration = last == null ? 0 : data.GetMix(last.animation, animation); + entry.mixBlend = MixBlend.Replace; + return entry; + } + + /// Removes the next entry and all entries after it for the specified entry. + public void ClearNext(TrackEntry entry) + { + TrackEntry next = entry.next; + while (next != null) + { + queue.Dispose(next); + next = next.next; + } + entry.next = null; + } + + private void AnimationsChanged() + { + animationsChanged = false; + + // Process in the order that animations are applied. + propertyIds.Clear(); + int n = tracks.Count; + TrackEntry[] tracksItems = tracks.Items; + for (int i = 0; i < n; i++) + { + TrackEntry entry = tracksItems[i]; + if (entry == null) continue; + while (entry.mixingFrom != null) // Move to last entry, then iterate in reverse. + entry = entry.mixingFrom; + do + { + if (entry.mixingTo == null || entry.mixBlend != MixBlend.Add) ComputeHold(entry); + entry = entry.mixingTo; + } while (entry != null); + } + } + + private void ComputeHold(TrackEntry entry) + { + TrackEntry to = entry.mixingTo; + Timeline[] timelines = entry.animation.timelines.Items; + int timelinesCount = entry.animation.timelines.Count; + int[] timelineMode = entry.timelineMode.Resize(timelinesCount).Items; + entry.timelineHoldMix.Clear(); + TrackEntry[] timelineHoldMix = entry.timelineHoldMix.Resize(timelinesCount).Items; + HashSet propertyIds = this.propertyIds; + + if (to != null && to.holdPrevious) + { + for (int i = 0; i < timelinesCount; i++) + timelineMode[i] = propertyIds.AddAll(timelines[i].PropertyIds) ? AnimationState.HoldFirst : AnimationState.HoldSubsequent; + + return; + } + + // outer: + for (int i = 0; i < timelinesCount; i++) + { + Timeline timeline = timelines[i]; + String[] ids = timeline.PropertyIds; + if (!propertyIds.AddAll(ids)) + timelineMode[i] = AnimationState.Subsequent; + else if (to == null || timeline is AttachmentTimeline || timeline is DrawOrderTimeline + || timeline is EventTimeline || !to.animation.HasTimeline(ids)) + { + timelineMode[i] = AnimationState.First; + } + else + { + for (TrackEntry next = to.mixingTo; next != null; next = next.mixingTo) + { + if (next.animation.HasTimeline(ids)) continue; + if (next.mixDuration > 0) + { + timelineMode[i] = AnimationState.HoldMix; + timelineHoldMix[i] = next; + goto continue_outer; // continue outer; + } + break; + } + timelineMode[i] = AnimationState.HoldFirst; + } + continue_outer: { } + } + } + + /// The track entry for the animation currently playing on the track, or null if no animation is currently playing. + public TrackEntry GetCurrent(int trackIndex) + { + if (trackIndex >= tracks.Count) return null; + return tracks.Items[trackIndex]; + } + + /// Discards all listener notifications that have not yet been delivered. This can be useful to call from an + /// AnimationState event subscriber when it is known that further notifications that may have been already queued for delivery + /// are not wanted because new animations are being set. + public void ClearListenerNotifications() + { + queue.Clear(); + } + + /// + /// Multiplier for the delta time when the animation state is updated, causing time for all animations and mixes to play slower + /// or faster. Defaults to 1. + /// + /// See TrackEntry for affecting a single animation. + /// + public float TimeScale { get { return timeScale; } set { timeScale = value; } } + + /// The AnimationStateData to look up mix durations. + public AnimationStateData Data + { + get + { + return data; + } + set + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + this.data = value; + } + } + + /// A list of tracks that have animations, which may contain nulls. + public ExposedList Tracks { get { return tracks; } } + + override public string ToString() + { + var buffer = new System.Text.StringBuilder(); + TrackEntry[] tracksItems = tracks.Items; + for (int i = 0, n = tracks.Count; i < n; i++) + { + TrackEntry entry = tracksItems[i]; + if (entry == null) continue; + if (buffer.Length > 0) buffer.Append(", "); + buffer.Append(entry.ToString()); + } + if (buffer.Length == 0) return ""; + return buffer.ToString(); + } + } + + /// + /// + /// Stores settings and other state for the playback of an animation on an track. + /// + /// References to a track entry must not be kept after the event occurs. + /// + public class TrackEntry : Pool.IPoolable + { + internal Animation animation; + + internal TrackEntry previous, next, mixingFrom, mixingTo; + // difference to libgdx reference: delegates are used for event callbacks instead of 'AnimationStateListener listener'. + /// See + /// API Reference documentation pages here for details. Usage in C# and spine-unity is explained + /// here + /// on the spine-unity documentation pages. + public event AnimationState.TrackEntryDelegate Start, Interrupt, End, Dispose, Complete; + public event AnimationState.TrackEntryEventDelegate Event; + internal void OnStart() { if (Start != null) Start(this); } + internal void OnInterrupt() { if (Interrupt != null) Interrupt(this); } + internal void OnEnd() { if (End != null) End(this); } + internal void OnDispose() { if (Dispose != null) Dispose(this); } + internal void OnComplete() { if (Complete != null) Complete(this); } + internal void OnEvent(Event e) { if (Event != null) Event(this, e); } + + internal int trackIndex; + + internal bool loop, holdPrevious, reverse, shortestRotation; + internal float eventThreshold, attachmentThreshold, drawOrderThreshold; + internal float animationStart, animationEnd, animationLast, nextAnimationLast; + internal float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale = 1f; + internal float alpha, mixTime, mixDuration, interruptAlpha, totalAlpha; + internal MixBlend mixBlend = MixBlend.Replace; + internal readonly ExposedList timelineMode = new ExposedList(); + internal readonly ExposedList timelineHoldMix = new ExposedList(); + internal readonly ExposedList timelinesRotation = new ExposedList(); + + // IPoolable.Reset() + public void Reset() + { + previous = null; + next = null; + mixingFrom = null; + mixingTo = null; + animation = null; + // replaces 'listener = null;' since delegates are used for event callbacks + Start = null; + Interrupt = null; + End = null; + Dispose = null; + Complete = null; + Event = null; + timelineMode.Clear(); + timelineHoldMix.Clear(); + timelinesRotation.Clear(); + } + + /// The index of the track where this entry is either current or queued. + /// + public int TrackIndex { get { return trackIndex; } } + + /// The animation to apply for this track entry. + public Animation Animation { get { return animation; } } + + /// + /// If true, the animation will repeat. If false it will not, instead its last frame is applied if played beyond its + /// duration. + public bool Loop { get { return loop; } set { loop = value; } } + + /// + /// + /// Seconds to postpone playing the animation. When this track entry is the current track entry, Delay + /// postpones incrementing the . When this track entry is queued, Delay is the time from + /// the start of the previous animation to when this track entry will become the current track entry (ie when the previous + /// track entry >= this track entry's Delay). + /// + /// affects the delay. + /// + /// When using with a delay <= 0, the delay + /// is set using the mix duration from the . If is set afterward, the delay + /// may need to be adjusted. + public float Delay { get { return delay; } set { delay = value; } } + + /// + /// Current time in seconds this track entry has been the current track entry. The track time determines + /// . The track time can be set to start the animation at a time other than 0, without affecting + /// looping. + public float TrackTime { get { return trackTime; } set { trackTime = value; } } + + /// + /// + /// The track time in seconds when this animation will be removed from the track. Defaults to the highest possible float + /// value, meaning the animation will be applied until a new animation is set or the track is cleared. If the track end time + /// is reached, no other animations are queued for playback, and mixing from any previous animations is complete, then the + /// properties keyed by the animation are set to the setup pose and the track is cleared. + /// + /// It may be desired to use rather than have the animation + /// abruptly cease being applied. + /// + public float TrackEnd { get { return trackEnd; } set { trackEnd = value; } } + + /// + /// If this track entry is non-looping, the track time in seconds when is reached, or the current + /// if it has already been reached. If this track entry is looping, the track time when this + /// animation will reach its next (the next loop completion). + public float TrackComplete + { + get + { + 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. + } + } + + /// + /// + /// Seconds when this animation starts, both initially and after looping. Defaults to 0. + /// + /// When changing the AnimationStart time, it often makes sense to set to the same + /// value to prevent timeline keys before the start time from triggering. + /// + public float AnimationStart { get { return animationStart; } set { animationStart = value; } } + + /// + /// Seconds for the last frame of this animation. Non-looping animations won't play past this time. Looping animations will + /// loop back to at this time. Defaults to the animation . + /// + public float AnimationEnd { get { return animationEnd; } set { animationEnd = value; } } + + /// + /// 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 AnimationLast time (exclusive) and + /// AnimationTime (inclusive). Defaults to -1 to ensure triggers on frame 0 happen the first time this animation + /// is applied. + public float AnimationLast + { + get { return animationLast; } + set + { + animationLast = value; + nextAnimationLast = value; + } + } + + /// + /// Uses to compute the AnimationTime. When the TrackTime is 0, the + /// AnimationTime is equal to the AnimationStart time. + /// + /// The animationTime is between and , except if this + /// track entry is non-looping and is >= to the animation , then + /// animationTime continues to increase past . + /// + public float AnimationTime + { + get + { + if (loop) + { + float duration = animationEnd - animationStart; + if (duration == 0) return animationStart; + return (trackTime % duration) + animationStart; + } + float animationTime = trackTime + animationStart; + return animationEnd >= animation.duration ? animationTime : Math.Min(animationTime, animationEnd); + } + } + + /// + /// + /// Multiplier for the delta time when this track entry is updated, causing time for this animation to pass slower or + /// faster. Defaults to 1. + /// + /// Values < 0 are not supported. To play an animation in reverse, use . + /// + /// is not affected by track entry time scale, so may need to be adjusted to + /// match the animation speed. + /// + /// When using with a Delay <= 0, the + /// is set using the mix duration from the , assuming time scale to be 1. If + /// the time scale is not 1, the delay may need to be adjusted. + /// + /// See AnimationState for affecting all animations. + /// + public float TimeScale { get { return timeScale; } set { timeScale = value; } } + + /// + /// + /// Values < 1 mix this animation with the skeleton's current pose (usually the pose resulting from lower tracks). Defaults + /// to 1, which overwrites the skeleton's current pose with this animation. + /// + /// Typically track 0 is used to completely pose the skeleton, then alpha is 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. + /// + public float Alpha { get { return alpha; } set { alpha = value; } } + + public float InterruptAlpha { get { return interruptAlpha; } } + + /// + /// When the mix percentage ( / ) is less than the + /// EventThreshold, event timelines are applied while this animation is being mixed out. Defaults to 0, so event + /// timelines are not applied while this animation is being mixed out. + /// + public float EventThreshold { get { return eventThreshold; } set { eventThreshold = value; } } + + /// + /// When the mix percentage ( / ) is less than the + /// AttachmentThreshold, attachment timelines are applied while this animation is being mixed out. Defaults to + /// 0, so attachment timelines are not applied while this animation is being mixed out. + /// + public float AttachmentThreshold { get { return attachmentThreshold; } set { attachmentThreshold = value; } } + + /// + /// When the mix percentage ( / ) is less than the + /// DrawOrderThreshold, draw order timelines are applied while this animation is being mixed out. Defaults to 0, + /// so draw order timelines are not applied while this animation is being mixed out. + /// + public float DrawOrderThreshold { get { return drawOrderThreshold; } set { drawOrderThreshold = value; } } + + /// + /// The animation queued to start after this animation, or null if there is none. next makes up a doubly linked + /// list. + /// + /// See to truncate the list. + public TrackEntry Next { get { return next; } } + + /// + /// The animation queued to play before this animation, or null. previous makes up a doubly linked list. + public TrackEntry Previous { get { return previous; } } + + /// + /// Returns true if at least one loop has been completed. + /// + public bool IsComplete + { + get { return trackTime >= animationEnd - animationStart; } + } + + /// + /// Seconds from 0 to the when mixing from the previous animation to this animation. May be + /// slightly more than MixDuration when the mix is complete. + public float MixTime { get { return mixTime; } set { mixTime = value; } } + + /// + /// + /// 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 MixDuration can be set manually rather than use the value from + /// . In that case, the MixDuration can be set for a new + /// track entry only before is first called. + /// + /// When using with a Delay <= 0, the + /// is set using the mix duration from the . If mixDuration is set + /// afterward, the delay may need to be adjusted. For example: + /// entry.Delay = entry.previous.TrackComplete - entry.MixDuration; + /// + public float MixDuration { get { return mixDuration; } set { mixDuration = value; } } + + /// + /// + /// Controls how properties keyed in the animation are mixed with lower tracks. Defaults to . + /// + /// Track entries on track 0 ignore this setting and always use . + /// + /// The MixBlend can be set for a new track entry only before is first + /// called. + /// + public MixBlend MixBlend { get { return mixBlend; } set { mixBlend = value; } } + + /// + /// 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 linked list. + public TrackEntry MixingFrom { get { return mixingFrom; } } + + /// + /// The track entry for the next animation when mixing from this animation to the next animation, or null if no mixing is + /// currently occuring. When mixing to multiple animations, MixingTo makes up a linked list. + public TrackEntry MixingTo { get { return mixingTo; } } + + /// + /// + /// 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. + /// + public bool HoldPrevious { get { return holdPrevious; } set { holdPrevious = value; } } + + /// + /// If true, the animation will be applied in reverse. Events are not fired when an animation is applied in reverse. + public bool Reverse { get { return reverse; } set { reverse = value; } } + + /// + /// If true, mixing rotation between tracks always uses the shortest rotation direction. If the rotation is animated, the + /// shortest rotation direction may change during the mix. + /// + /// If false, the shortest rotation direction is remembered when the mix starts and the same direction is used for the rest + /// of the mix. Defaults to false. + public bool ShortestRotation { get { return shortestRotation; } set { shortestRotation = value; } } + + /// Returns true if this entry is for the empty animation. See , + /// , and . + public bool IsEmptyAnimation { get { return animation == AnimationState.EmptyAnimation; } } + + /// + /// + /// 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 and starting animations on other tracks. + /// + /// Mixing with 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. + /// + public void ResetRotationDirections() + { + timelinesRotation.Clear(); + } + + override public string ToString() + { + return animation == null ? "" : animation.name; + } + + // Note: This method is required by SpineAnimationStateMixerBehaviour, + // which is part of the timeline extension package. Thus the internal member variable + // nextTrackLast is not accessible. We favor providing this method + // over exposing nextTrackLast as public property, which would rather confuse users. + public void AllowImmediateQueue() + { + if (nextTrackLast < 0) nextTrackLast = 0; + } + } + + class EventQueue + { + private readonly List eventQueueEntries = new List(); + internal bool drainDisabled; + + private readonly AnimationState state; + private readonly Pool trackEntryPool; + internal event Action AnimationsChanged; + + internal EventQueue(AnimationState state, Action HandleAnimationsChanged, Pool trackEntryPool) + { + this.state = state; + this.AnimationsChanged += HandleAnimationsChanged; + this.trackEntryPool = trackEntryPool; + } + + internal void Start(TrackEntry entry) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.Start, entry)); + if (AnimationsChanged != null) AnimationsChanged(); + } + + internal void Interrupt(TrackEntry entry) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.Interrupt, entry)); + } + + internal void End(TrackEntry entry) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.End, entry)); + if (AnimationsChanged != null) AnimationsChanged(); + } + + internal void Dispose(TrackEntry entry) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.Dispose, entry)); + } + + internal void Complete(TrackEntry entry) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.Complete, entry)); + } + + internal void Event(TrackEntry entry, Event e) + { + eventQueueEntries.Add(new EventQueueEntry(EventType.Event, entry, e)); + } + + /// Raises all events in the queue and drains the queue. + internal void Drain() + { + if (drainDisabled) return; + drainDisabled = true; + + List eventQueueEntries = this.eventQueueEntries; + AnimationState state = this.state; + + // Don't cache eventQueueEntries.Count so callbacks can queue their own events (eg, call SetAnimation in AnimationState_Complete). + for (int i = 0; i < eventQueueEntries.Count; i++) + { + EventQueueEntry queueEntry = eventQueueEntries[i]; + TrackEntry trackEntry = queueEntry.entry; + + switch (queueEntry.type) + { + case EventType.Start: + trackEntry.OnStart(); + state.OnStart(trackEntry); + break; + case EventType.Interrupt: + trackEntry.OnInterrupt(); + state.OnInterrupt(trackEntry); + break; + case EventType.End: + trackEntry.OnEnd(); + state.OnEnd(trackEntry); + goto case EventType.Dispose; // Fall through. (C#) + case EventType.Dispose: + trackEntry.OnDispose(); + state.OnDispose(trackEntry); + trackEntryPool.Free(trackEntry); + break; + case EventType.Complete: + trackEntry.OnComplete(); + state.OnComplete(trackEntry); + break; + case EventType.Event: + trackEntry.OnEvent(queueEntry.e); + state.OnEvent(trackEntry, queueEntry.e); + break; + } + } + eventQueueEntries.Clear(); + + drainDisabled = false; + } + + internal void Clear() + { + eventQueueEntries.Clear(); + } + + struct EventQueueEntry + { + public EventType type; + public TrackEntry entry; + public Event e; + + public EventQueueEntry(EventType eventType, TrackEntry trackEntry, Event e = null) + { + this.type = eventType; + this.entry = trackEntry; + this.e = e; + } + } + + enum EventType + { + Start, Interrupt, End, Dispose, Complete, Event + } + } + + class Pool where T : class, new() + { + public readonly int max; + readonly Stack freeObjects; + + public int Count { get { return freeObjects.Count; } } + public int Peak { get; private set; } + + public Pool(int initialCapacity = 16, int max = int.MaxValue) + { + freeObjects = new Stack(initialCapacity); + this.max = max; + } + + public T Obtain() + { + return freeObjects.Count == 0 ? new T() : freeObjects.Pop(); + } + + public void Free(T obj) + { + if (obj == null) throw new ArgumentNullException("obj", "obj cannot be null"); + if (freeObjects.Count < max) + { + freeObjects.Push(obj); + Peak = Math.Max(Peak, freeObjects.Count); + } + Reset(obj); + } + + public void Clear() + { + freeObjects.Clear(); + } + + protected void Reset(T obj) + { + var poolable = obj as IPoolable; + if (poolable != null) poolable.Reset(); + } + + public interface IPoolable + { + void Reset(); + } + } + + public static class HashSetExtensions + { + public static bool AddAll(this HashSet set, T[] addSet) + { + bool anyItemAdded = false; + for (int i = 0, n = addSet.Length; i < n; ++i) + { + T item = addSet[i]; + anyItemAdded |= set.Add(item); + } + return anyItemAdded; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/AnimationStateData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/AnimationStateData.cs index d125cae..878a09c 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/AnimationStateData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/AnimationStateData.cs @@ -30,85 +30,97 @@ using System; using System.Collections.Generic; -namespace Spine4_1_00 { +namespace Spine4_1_00 +{ - /// Stores mix (crossfade) durations to be applied when AnimationState animations are changed. - public class AnimationStateData { - internal SkeletonData skeletonData; - readonly Dictionary animationToMixTime = new Dictionary(AnimationPairComparer.Instance); - internal float defaultMix; + /// Stores mix (crossfade) durations to be applied when AnimationState animations are changed. + public class AnimationStateData + { + internal SkeletonData skeletonData; + readonly Dictionary animationToMixTime = new Dictionary(AnimationPairComparer.Instance); + internal float defaultMix; - /// The SkeletonData to look up animations when they are specified by name. - public SkeletonData SkeletonData { get { return skeletonData; } } + /// The SkeletonData to look up animations when they are specified by name. + public SkeletonData SkeletonData { get { return skeletonData; } } - /// - /// The mix duration to use when no mix duration has been specifically defined between two animations. - public float DefaultMix { get { return defaultMix; } set { defaultMix = value; } } + /// + /// The mix duration to use when no mix duration has been specifically defined between two animations. + public float DefaultMix { get { return defaultMix; } set { defaultMix = value; } } - public AnimationStateData (SkeletonData skeletonData) { - if (skeletonData == null) throw new ArgumentException("skeletonData cannot be null.", "skeletonData"); - this.skeletonData = skeletonData; - } + public AnimationStateData(SkeletonData skeletonData) + { + if (skeletonData == null) throw new ArgumentException("skeletonData cannot be null.", "skeletonData"); + this.skeletonData = skeletonData; + } - /// Sets a mix duration by animation names. - public void SetMix (string fromName, string toName, float duration) { - Animation from = skeletonData.FindAnimation(fromName); - if (from == null) throw new ArgumentException("Animation not found: " + fromName, "fromName"); - Animation to = skeletonData.FindAnimation(toName); - if (to == null) throw new ArgumentException("Animation not found: " + toName, "toName"); - SetMix(from, to, duration); - } + /// Sets a mix duration by animation names. + public void SetMix(string fromName, string toName, float duration) + { + Animation from = skeletonData.FindAnimation(fromName); + if (from == null) throw new ArgumentException("Animation not found: " + fromName, "fromName"); + Animation to = skeletonData.FindAnimation(toName); + if (to == null) throw new ArgumentException("Animation not found: " + toName, "toName"); + SetMix(from, to, duration); + } - /// Sets a mix duration when changing from the specified animation to the other. - /// See TrackEntry.MixDuration. - public void SetMix (Animation from, Animation to, float duration) { - if (from == null) throw new ArgumentNullException("from", "from cannot be null."); - if (to == null) throw new ArgumentNullException("to", "to cannot be null."); - AnimationPair key = new AnimationPair(from, to); - animationToMixTime.Remove(key); - animationToMixTime.Add(key, duration); - } + /// Sets a mix duration when changing from the specified animation to the other. + /// See TrackEntry.MixDuration. + public void SetMix(Animation from, Animation to, float duration) + { + if (from == null) throw new ArgumentNullException("from", "from cannot be null."); + if (to == null) throw new ArgumentNullException("to", "to cannot be null."); + AnimationPair key = new AnimationPair(from, to); + animationToMixTime.Remove(key); + animationToMixTime.Add(key, 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. - /// - public float GetMix (Animation from, Animation to) { - if (from == null) throw new ArgumentNullException("from", "from cannot be null."); - if (to == null) throw new ArgumentNullException("to", "to cannot be null."); - AnimationPair key = new AnimationPair(from, to); - float duration; - if (animationToMixTime.TryGetValue(key, out duration)) return duration; - return defaultMix; - } + /// + /// The mix duration to use when changing from the specified animation to the other, + /// or the DefaultMix if no mix duration has been set. + /// + public float GetMix(Animation from, Animation to) + { + if (from == null) throw new ArgumentNullException("from", "from cannot be null."); + if (to == null) throw new ArgumentNullException("to", "to cannot be null."); + AnimationPair key = new AnimationPair(from, to); + float duration; + if (animationToMixTime.TryGetValue(key, out duration)) return duration; + return defaultMix; + } - public struct AnimationPair { - public readonly Animation a1; - public readonly Animation a2; + public struct AnimationPair + { + public readonly Animation a1; + public readonly Animation a2; - public AnimationPair (Animation a1, Animation a2) { - this.a1 = a1; - this.a2 = a2; - } + public AnimationPair(Animation a1, Animation a2) + { + this.a1 = a1; + this.a2 = a2; + } - public override string ToString () { - return a1.name + "->" + a2.name; - } - } + public override string ToString() + { + return a1.name + "->" + a2.name; + } + } - // Avoids boxing in the dictionary. - public class AnimationPairComparer : IEqualityComparer { - public static readonly AnimationPairComparer Instance = new AnimationPairComparer(); + // Avoids boxing in the dictionary. + public class AnimationPairComparer : IEqualityComparer + { + public static readonly AnimationPairComparer Instance = new AnimationPairComparer(); - bool IEqualityComparer.Equals (AnimationPair x, AnimationPair y) { - return ReferenceEquals(x.a1, y.a1) && ReferenceEquals(x.a2, y.a2); - } + bool IEqualityComparer.Equals(AnimationPair x, AnimationPair y) + { + return ReferenceEquals(x.a1, y.a1) && ReferenceEquals(x.a2, y.a2); + } - int IEqualityComparer.GetHashCode (AnimationPair obj) { - // from Tuple.CombineHashCodes // return (((h1 << 5) + h1) ^ h2); - int h1 = obj.a1.GetHashCode(); - return (((h1 << 5) + h1) ^ obj.a2.GetHashCode()); - } - } - } + int IEqualityComparer.GetHashCode(AnimationPair obj) + { + // from Tuple.CombineHashCodes // return (((h1 << 5) + h1) ^ h2); + int h1 = obj.a1.GetHashCode(); + return (((h1 << 5) + h1) ^ obj.a2.GetHashCode()); + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Atlas.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Atlas.cs index 78e55ba..dfcfa50 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Atlas.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Atlas.cs @@ -35,31 +35,34 @@ using System.Collections.Generic; using System.Globalization; using System.IO; -using System.Reflection; #if WINDOWS_STOREAPP using System.Threading.Tasks; using Windows.Storage; #endif -namespace Spine4_1_00 { - public class Atlas : IEnumerable { - readonly List pages = new List(); - List regions = new List(); - TextureLoader textureLoader; +namespace Spine4_1_00 +{ + public class Atlas : IEnumerable + { + readonly List pages = new List(); + List regions = new List(); + TextureLoader textureLoader; - #region IEnumerable implementation - public IEnumerator GetEnumerator () { - return regions.GetEnumerator(); - } + #region IEnumerable implementation + public IEnumerator GetEnumerator() + { + return regions.GetEnumerator(); + } - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () { - return regions.GetEnumerator(); - } - #endregion + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return regions.GetEnumerator(); + } + #endregion - public List Regions { get { return regions; } } - public List Pages { get { return pages; } } + public List Regions { get { return regions; } } + public List Pages { get { return pages; } } #if !(IS_UNITY) #if WINDOWS_STOREAPP @@ -82,291 +85,342 @@ public Atlas(string path, TextureLoader textureLoader) { this.ReadFile(path, textureLoader).Wait(); } #else - public Atlas (string path, TextureLoader textureLoader) { + public Atlas(string path, TextureLoader textureLoader) + { #if WINDOWS_PHONE Stream stream = Microsoft.Xna.Framework.TitleContainer.OpenStream(path); using (StreamReader reader = new StreamReader(stream)) { #else - using (StreamReader reader = new StreamReader(path)) { + using (StreamReader reader = new StreamReader(path)) + { #endif // WINDOWS_PHONE - try { - Atlas atlas = new Atlas(reader, Path.GetDirectoryName(path), textureLoader); - this.pages = atlas.pages; - this.regions = atlas.regions; - this.textureLoader = atlas.textureLoader; - } catch (Exception ex) { - throw new Exception("Error reading atlas file: " + path, ex); - } - } - } + try + { + Atlas atlas = new Atlas(reader, Path.GetDirectoryName(path), textureLoader); + this.pages = atlas.pages; + this.regions = atlas.regions; + this.textureLoader = atlas.textureLoader; + } + catch (Exception ex) + { + throw new Exception("Error reading atlas file: " + path, ex); + } + } + } #endif // WINDOWS_STOREAPP #endif - public Atlas (List pages, List regions) { - if (pages == null) throw new ArgumentNullException("pages", "pages cannot be null."); - if (regions == null) throw new ArgumentNullException("regions", "regions cannot be null."); - this.pages = pages; - this.regions = regions; - this.textureLoader = null; - } + public Atlas(List pages, List regions) + { + if (pages == null) throw new ArgumentNullException("pages", "pages cannot be null."); + if (regions == null) throw new ArgumentNullException("regions", "regions cannot be null."); + this.pages = pages; + this.regions = regions; + this.textureLoader = null; + } - public Atlas (TextReader reader, string imagesDir, TextureLoader textureLoader) { - if (reader == null) throw new ArgumentNullException("reader", "reader cannot be null."); - if (imagesDir == null) throw new ArgumentNullException("imagesDir", "imagesDir cannot be null."); - if (textureLoader == null) throw new ArgumentNullException("textureLoader", "textureLoader cannot be null."); - this.textureLoader = textureLoader; + public Atlas(TextReader reader, string imagesDir, TextureLoader textureLoader) + { + if (reader == null) throw new ArgumentNullException("reader", "reader cannot be null."); + if (imagesDir == null) throw new ArgumentNullException("imagesDir", "imagesDir cannot be null."); + if (textureLoader == null) throw new ArgumentNullException("textureLoader", "textureLoader cannot be null."); + this.textureLoader = textureLoader; - string[] entry = new string[5]; - AtlasPage page = null; - AtlasRegion region = null; + string[] entry = new string[5]; + AtlasPage page = null; + AtlasRegion region = null; - var pageFields = new Dictionary(5); - pageFields.Add("size", () => { - page.width = int.Parse(entry[1], CultureInfo.InvariantCulture); - page.height = int.Parse(entry[2], CultureInfo.InvariantCulture); - }); - pageFields.Add("format", () => { - page.format = (Format)Enum.Parse(typeof(Format), entry[1], false); - }); - pageFields.Add("filter", () => { - page.minFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), entry[1], false); - page.magFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), entry[2], false); - }); - pageFields.Add("repeat", () => { - if (entry[1].IndexOf('x') != -1) page.uWrap = TextureWrap.Repeat; - if (entry[1].IndexOf('y') != -1) page.vWrap = TextureWrap.Repeat; - }); - pageFields.Add("pma", () => { - page.pma = entry[1] == "true"; - }); + var pageFields = new Dictionary(5); + pageFields.Add("size", () => + { + page.width = int.Parse(entry[1], CultureInfo.InvariantCulture); + page.height = int.Parse(entry[2], CultureInfo.InvariantCulture); + }); + pageFields.Add("format", () => + { + page.format = (Format)Enum.Parse(typeof(Format), entry[1], false); + }); + pageFields.Add("filter", () => + { + page.minFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), entry[1], false); + page.magFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), entry[2], false); + }); + pageFields.Add("repeat", () => + { + if (entry[1].IndexOf('x') != -1) page.uWrap = TextureWrap.Repeat; + if (entry[1].IndexOf('y') != -1) page.vWrap = TextureWrap.Repeat; + }); + pageFields.Add("pma", () => + { + page.pma = entry[1] == "true"; + }); - var regionFields = new Dictionary(8); - regionFields.Add("xy", () => { // Deprecated, use bounds. - region.x = int.Parse(entry[1], CultureInfo.InvariantCulture); - region.y = int.Parse(entry[2], CultureInfo.InvariantCulture); - }); - regionFields.Add("size", () => { // Deprecated, use bounds. - region.width = int.Parse(entry[1], CultureInfo.InvariantCulture); - region.height = int.Parse(entry[2], CultureInfo.InvariantCulture); - }); - regionFields.Add("bounds", () => { - region.x = int.Parse(entry[1], CultureInfo.InvariantCulture); - region.y = int.Parse(entry[2], CultureInfo.InvariantCulture); - region.width = int.Parse(entry[3], CultureInfo.InvariantCulture); - region.height = int.Parse(entry[4], CultureInfo.InvariantCulture); - }); - regionFields.Add("offset", () => { // Deprecated, use offsets. - region.offsetX = int.Parse(entry[1], CultureInfo.InvariantCulture); - region.offsetY = int.Parse(entry[2], CultureInfo.InvariantCulture); - }); - regionFields.Add("orig", () => { // Deprecated, use offsets. - region.originalWidth = int.Parse(entry[1], CultureInfo.InvariantCulture); - region.originalHeight = int.Parse(entry[2], CultureInfo.InvariantCulture); - }); - regionFields.Add("offsets", () => { - region.offsetX = int.Parse(entry[1], CultureInfo.InvariantCulture); - region.offsetY = int.Parse(entry[2], CultureInfo.InvariantCulture); - region.originalWidth = int.Parse(entry[3], CultureInfo.InvariantCulture); - region.originalHeight = int.Parse(entry[4], CultureInfo.InvariantCulture); - }); - regionFields.Add("rotate", () => { - string value = entry[1]; - if (value == "true") - region.degrees = 90; - else if (value != "false") - region.degrees = int.Parse(value, CultureInfo.InvariantCulture); - }); - regionFields.Add("index", () => { - region.index = int.Parse(entry[1], CultureInfo.InvariantCulture); - }); + var regionFields = new Dictionary(8); + regionFields.Add("xy", () => + { // Deprecated, use bounds. + region.x = int.Parse(entry[1], CultureInfo.InvariantCulture); + region.y = int.Parse(entry[2], CultureInfo.InvariantCulture); + }); + regionFields.Add("size", () => + { // Deprecated, use bounds. + region.width = int.Parse(entry[1], CultureInfo.InvariantCulture); + region.height = int.Parse(entry[2], CultureInfo.InvariantCulture); + }); + regionFields.Add("bounds", () => + { + region.x = int.Parse(entry[1], CultureInfo.InvariantCulture); + region.y = int.Parse(entry[2], CultureInfo.InvariantCulture); + region.width = int.Parse(entry[3], CultureInfo.InvariantCulture); + region.height = int.Parse(entry[4], CultureInfo.InvariantCulture); + }); + regionFields.Add("offset", () => + { // Deprecated, use offsets. + region.offsetX = int.Parse(entry[1], CultureInfo.InvariantCulture); + region.offsetY = int.Parse(entry[2], CultureInfo.InvariantCulture); + }); + regionFields.Add("orig", () => + { // Deprecated, use offsets. + region.originalWidth = int.Parse(entry[1], CultureInfo.InvariantCulture); + region.originalHeight = int.Parse(entry[2], CultureInfo.InvariantCulture); + }); + regionFields.Add("offsets", () => + { + region.offsetX = int.Parse(entry[1], CultureInfo.InvariantCulture); + region.offsetY = int.Parse(entry[2], CultureInfo.InvariantCulture); + region.originalWidth = int.Parse(entry[3], CultureInfo.InvariantCulture); + region.originalHeight = int.Parse(entry[4], CultureInfo.InvariantCulture); + }); + regionFields.Add("rotate", () => + { + string value = entry[1]; + if (value == "true") + region.degrees = 90; + else if (value != "false") + region.degrees = int.Parse(value, CultureInfo.InvariantCulture); + }); + regionFields.Add("index", () => + { + region.index = int.Parse(entry[1], CultureInfo.InvariantCulture); + }); - string line = reader.ReadLine(); - // Ignore empty lines before first entry. - while (line != null && line.Trim().Length == 0) - line = reader.ReadLine(); - // Header entries. - while (true) { - if (line == null || line.Trim().Length == 0) break; - if (ReadEntry(entry, line) == 0) break; // Silently ignore all header fields. - line = reader.ReadLine(); - } - // Page and region entries. - List names = null; - List values = null; - while (true) { - if (line == null) break; - if (line.Trim().Length == 0) { - page = null; - line = reader.ReadLine(); - } else if (page == null) { - page = new AtlasPage(); - page.name = line.Trim(); - while (true) { - if (ReadEntry(entry, line = reader.ReadLine()) == 0) break; - Action field; - if (pageFields.TryGetValue(entry[0], out field)) field(); // Silently ignore unknown page fields. - } - textureLoader.Load(page, Path.Combine(imagesDir, page.name)); - pages.Add(page); - } else { - region = new AtlasRegion(); - region.page = page; - region.name = line; - while (true) { - int count = ReadEntry(entry, line = reader.ReadLine()); - if (count == 0) break; - Action field; - if (regionFields.TryGetValue(entry[0], out field)) - field(); - else { - if (names == null) { - names = new List(8); - values = new List(8); - } - names.Add(entry[0]); - int[] entryValues = new int[count]; - for (int i = 0; i < count; i++) - int.TryParse(entry[i + 1], NumberStyles.Any, CultureInfo.InvariantCulture, out entryValues[i]); // Silently ignore non-integer values. - values.Add(entryValues); - } - } - if (region.originalWidth == 0 && region.originalHeight == 0) { - region.originalWidth = region.width; - region.originalHeight = region.height; - } - if (names != null && names.Count > 0) { - region.names = names.ToArray(); - region.values = values.ToArray(); - names.Clear(); - values.Clear(); - } - region.u = region.x / (float)page.width; - region.v = region.y / (float)page.height; - if (region.degrees == 90) { - region.u2 = (region.x + region.height) / (float)page.width; - region.v2 = (region.y + region.width) / (float)page.height; + string line = reader.ReadLine(); + // Ignore empty lines before first entry. + while (line != null && line.Trim().Length == 0) + line = reader.ReadLine(); + // Header entries. + while (true) + { + if (line == null || line.Trim().Length == 0) break; + if (ReadEntry(entry, line) == 0) break; // Silently ignore all header fields. + line = reader.ReadLine(); + } + // Page and region entries. + List names = null; + List values = null; + while (true) + { + if (line == null) break; + if (line.Trim().Length == 0) + { + page = null; + line = reader.ReadLine(); + } + else if (page == null) + { + page = new AtlasPage(); + page.name = line.Trim(); + while (true) + { + if (ReadEntry(entry, line = reader.ReadLine()) == 0) break; + Action field; + if (pageFields.TryGetValue(entry[0], out field)) field(); // Silently ignore unknown page fields. + } + textureLoader.Load(page, Path.Combine(imagesDir, page.name)); + pages.Add(page); + } + else + { + region = new AtlasRegion(); + region.page = page; + region.name = line; + while (true) + { + int count = ReadEntry(entry, line = reader.ReadLine()); + if (count == 0) break; + Action field; + if (regionFields.TryGetValue(entry[0], out field)) + field(); + else + { + if (names == null) + { + names = new List(8); + values = new List(8); + } + names.Add(entry[0]); + int[] entryValues = new int[count]; + for (int i = 0; i < count; i++) + int.TryParse(entry[i + 1], NumberStyles.Any, CultureInfo.InvariantCulture, out entryValues[i]); // Silently ignore non-integer values. + values.Add(entryValues); + } + } + if (region.originalWidth == 0 && region.originalHeight == 0) + { + region.originalWidth = region.width; + region.originalHeight = region.height; + } + if (names != null && names.Count > 0) + { + region.names = names.ToArray(); + region.values = values.ToArray(); + names.Clear(); + values.Clear(); + } + region.u = region.x / (float)page.width; + region.v = region.y / (float)page.height; + if (region.degrees == 90) + { + region.u2 = (region.x + region.height) / (float)page.width; + region.v2 = (region.y + region.width) / (float)page.height; - var tempSwap = region.packedWidth; - region.packedWidth = region.packedHeight; - region.packedHeight = tempSwap; - } else { - region.u2 = (region.x + region.width) / (float)page.width; - region.v2 = (region.y + region.height) / (float)page.height; - } - regions.Add(region); - } - } - } + var tempSwap = region.packedWidth; + region.packedWidth = region.packedHeight; + region.packedHeight = tempSwap; + } + else + { + region.u2 = (region.x + region.width) / (float)page.width; + region.v2 = (region.y + region.height) / (float)page.height; + } + regions.Add(region); + } + } + } - static private int ReadEntry (string[] entry, string line) { - if (line == null) return 0; - line = line.Trim(); - if (line.Length == 0) return 0; - int colon = line.IndexOf(':'); - if (colon == -1) return 0; - entry[0] = line.Substring(0, colon).Trim(); - for (int i = 1, lastMatch = colon + 1; ; i++) { - int comma = line.IndexOf(',', lastMatch); - if (comma == -1) { - entry[i] = line.Substring(lastMatch).Trim(); - return i; - } - entry[i] = line.Substring(lastMatch, comma - lastMatch).Trim(); - lastMatch = comma + 1; - if (i == 4) return 4; - } - } + static private int ReadEntry(string[] entry, string line) + { + if (line == null) return 0; + line = line.Trim(); + if (line.Length == 0) return 0; + int colon = line.IndexOf(':'); + if (colon == -1) return 0; + entry[0] = line.Substring(0, colon).Trim(); + for (int i = 1, lastMatch = colon + 1; ; i++) + { + int comma = line.IndexOf(',', lastMatch); + if (comma == -1) + { + entry[i] = line.Substring(lastMatch).Trim(); + return i; + } + entry[i] = line.Substring(lastMatch, comma - lastMatch).Trim(); + lastMatch = comma + 1; + if (i == 4) return 4; + } + } - public void FlipV () { - for (int i = 0, n = regions.Count; i < n; i++) { - AtlasRegion region = regions[i]; - region.v = 1 - region.v; - region.v2 = 1 - region.v2; - } - } + public void FlipV() + { + for (int i = 0, n = regions.Count; i < n; i++) + { + AtlasRegion region = regions[i]; + region.v = 1 - region.v; + region.v2 = 1 - region.v2; + } + } - /// 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. - /// The region, or null. - public AtlasRegion FindRegion (string name) { - for (int i = 0, n = regions.Count; i < n; i++) - if (regions[i].name == name) return regions[i]; - return null; - } + /// 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. + /// The region, or null. + public AtlasRegion FindRegion(string name) + { + for (int i = 0, n = regions.Count; i < n; i++) + if (regions[i].name == name) return regions[i]; + return null; + } - public void Dispose () { - if (textureLoader == null) return; - for (int i = 0, n = pages.Count; i < n; i++) - textureLoader.Unload(pages[i].rendererObject); - } - } + public void Dispose() + { + if (textureLoader == null) return; + for (int i = 0, n = pages.Count; i < n; i++) + textureLoader.Unload(pages[i].rendererObject); + } + } - public enum Format { - Alpha, - Intensity, - LuminanceAlpha, - RGB565, - RGBA4444, - RGB888, - RGBA8888 - } + public enum Format + { + Alpha, + Intensity, + LuminanceAlpha, + RGB565, + RGBA4444, + RGB888, + RGBA8888 + } - public enum TextureFilter { - Nearest, - Linear, - MipMap, - MipMapNearestNearest, - MipMapLinearNearest, - MipMapNearestLinear, - MipMapLinearLinear - } + public enum TextureFilter + { + Nearest, + Linear, + MipMap, + MipMapNearestNearest, + MipMapLinearNearest, + MipMapNearestLinear, + MipMapLinearLinear + } - public enum TextureWrap { - MirroredRepeat, - ClampToEdge, - Repeat - } + public enum TextureWrap + { + MirroredRepeat, + ClampToEdge, + Repeat + } - public class AtlasPage { - public string name; - public int width, height; - public Format format = Format.RGBA8888; - public TextureFilter minFilter = TextureFilter.Nearest; - public TextureFilter magFilter = TextureFilter.Nearest; - public TextureWrap uWrap = TextureWrap.ClampToEdge; - public TextureWrap vWrap = TextureWrap.ClampToEdge; - public bool pma; - public object rendererObject; + public class AtlasPage + { + public string name; + public int width, height; + public Format format = Format.RGBA8888; + public TextureFilter minFilter = TextureFilter.Nearest; + public TextureFilter magFilter = TextureFilter.Nearest; + public TextureWrap uWrap = TextureWrap.ClampToEdge; + public TextureWrap vWrap = TextureWrap.ClampToEdge; + public bool pma; + public object rendererObject; - public AtlasPage Clone () { - return MemberwiseClone() as AtlasPage; - } - } + public AtlasPage Clone() + { + return MemberwiseClone() as AtlasPage; + } + } - public class AtlasRegion : TextureRegion { - public AtlasPage page; - public string name; - public int x, y; - public float offsetX, offsetY; - public int originalWidth, originalHeight; - public int packedWidth { get { return width; } set { width = value; } } - public int packedHeight { get { return height; } set { height = value; } } - public int degrees; - public bool rotate; - public int index; - public string[] names; - public int[][] values; + public class AtlasRegion : TextureRegion + { + public AtlasPage page; + public string name; + public int x, y; + public float offsetX, offsetY; + public int originalWidth, originalHeight; + public int packedWidth { get { return width; } set { width = value; } } + public int packedHeight { get { return height; } set { height = value; } } + public int degrees; + public bool rotate; + public int index; + public string[] names; + public int[][] values; - override public int OriginalWidth { get { return originalWidth; } } - override public int OriginalHeight { get { return originalHeight; } } + override public int OriginalWidth { get { return originalWidth; } } + override public int OriginalHeight { get { return originalHeight; } } - public AtlasRegion Clone () { - return MemberwiseClone() as AtlasRegion; - } - } + public AtlasRegion Clone() + { + return MemberwiseClone() as AtlasRegion; + } + } - public interface TextureLoader { - void Load (AtlasPage page, string path); - void Unload (Object texture); - } + public interface TextureLoader + { + void Load(AtlasPage page, string path); + void Unload(Object texture); + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/AtlasAttachmentLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/AtlasAttachmentLoader.cs index 875234a..eb7f1c1 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/AtlasAttachmentLoader.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/AtlasAttachmentLoader.cs @@ -29,81 +29,96 @@ using System; -namespace Spine4_1_00 { +namespace Spine4_1_00 +{ - /// - /// An AttachmentLoader that configures attachments using texture regions from an Atlas. - /// See Loading Skeleton Data in the Spine Runtimes Guide. - /// - public class AtlasAttachmentLoader : AttachmentLoader { - private Atlas[] atlasArray; + /// + /// An AttachmentLoader that configures attachments using texture regions from an Atlas. + /// See Loading Skeleton Data in the Spine Runtimes Guide. + /// + public class AtlasAttachmentLoader : AttachmentLoader + { + private Atlas[] atlasArray; - public AtlasAttachmentLoader (params Atlas[] atlasArray) { - if (atlasArray == null) throw new ArgumentNullException("atlas", "atlas array cannot be null."); - this.atlasArray = atlasArray; - } + public AtlasAttachmentLoader(params Atlas[] atlasArray) + { + if (atlasArray == null) throw new ArgumentNullException("atlas", "atlas array cannot be null."); + this.atlasArray = atlasArray; + } - private void LoadSequence (string name, string basePath, Sequence sequence) { - TextureRegion[] regions = sequence.Regions; - for (int i = 0, n = regions.Length; i < n; i++) { - string path = sequence.GetPath(basePath, i); - regions[i] = FindRegion(path); - if (regions[i] == null) throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name)); - } - } + private void LoadSequence(string name, string basePath, Sequence sequence) + { + TextureRegion[] regions = sequence.Regions; + for (int i = 0, n = regions.Length; i < n; i++) + { + string path = sequence.GetPath(basePath, i); + regions[i] = FindRegion(path); + if (regions[i] == null) throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name)); + } + } - public RegionAttachment NewRegionAttachment (Skin skin, string name, string path, Sequence sequence) { - RegionAttachment attachment = new RegionAttachment(name); - if (sequence != null) - LoadSequence(name, path, sequence); - else { - AtlasRegion region = FindRegion(path); - if (region == null) - throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name)); - attachment.Region = region; - } - return attachment; - } + public RegionAttachment NewRegionAttachment(Skin skin, string name, string path, Sequence sequence) + { + RegionAttachment attachment = new RegionAttachment(name); + if (sequence != null) + LoadSequence(name, path, sequence); + else + { + AtlasRegion region = FindRegion(path); + if (region == null) + throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name)); + attachment.Region = region; + } + return attachment; + } - public MeshAttachment NewMeshAttachment (Skin skin, string name, string path, Sequence sequence) { - MeshAttachment attachment = new MeshAttachment(name); - if (sequence != null) - LoadSequence(name, path, sequence); - else { - AtlasRegion region = FindRegion(path); - if (region == null) - throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name)); - attachment.Region = region; - } - return attachment; - } + public MeshAttachment NewMeshAttachment(Skin skin, string name, string path, Sequence sequence) + { + MeshAttachment attachment = new MeshAttachment(name); + if (sequence != null) + LoadSequence(name, path, sequence); + else + { + AtlasRegion region = FindRegion(path); + if (region == null) + throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name)); + attachment.Region = region; + } + return attachment; + } - public BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, string name) { - return new BoundingBoxAttachment(name); - } + public BoundingBoxAttachment NewBoundingBoxAttachment(Skin skin, string name) + { + return new BoundingBoxAttachment(name); + } - public PathAttachment NewPathAttachment (Skin skin, string name) { - return new PathAttachment(name); - } + public PathAttachment NewPathAttachment(Skin skin, string name) + { + return new PathAttachment(name); + } - public PointAttachment NewPointAttachment (Skin skin, string name) { - return new PointAttachment(name); - } + public PointAttachment NewPointAttachment(Skin skin, string name) + { + return new PointAttachment(name); + } - public ClippingAttachment NewClippingAttachment (Skin skin, string name) { - return new ClippingAttachment(name); - } + public ClippingAttachment NewClippingAttachment(Skin skin, string name) + { + return new ClippingAttachment(name); + } - public AtlasRegion FindRegion (string name) { - AtlasRegion region; + public AtlasRegion FindRegion(string name) + { + AtlasRegion region; - for (int i = 0; i < atlasArray.Length; i++) { - region = atlasArray[i].FindRegion(name); - if (region != null) - return region; - } + for (int i = 0; i < atlasArray.Length; i++) + { + region = atlasArray[i].FindRegion(name); + if (region != null) + return region; + } - return null; - } - } + return null; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/Attachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/Attachment.cs index 242d513..ee240ad 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/Attachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/Attachment.cs @@ -29,28 +29,33 @@ using System; -namespace Spine4_1_00 { +namespace Spine4_1_00 +{ - /// The base class for all attachments. - abstract public class Attachment { - /// The attachment's name. - public string Name { get; } + /// The base class for all attachments. + abstract public class Attachment + { + /// The attachment's name. + public string Name { get; } - protected Attachment (string name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null"); - this.Name = name; - } + protected Attachment(string name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null"); + this.Name = name; + } - /// Copy constructor. - protected Attachment (Attachment other) { - Name = other.Name; - } + /// Copy constructor. + protected Attachment(Attachment other) + { + Name = other.Name; + } - override public string ToString () { - return Name; - } + override public string ToString() + { + return Name; + } - ///Returns a copy of the attachment. - public abstract Attachment Copy (); - } + ///Returns a copy of the attachment. + public abstract Attachment Copy(); + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/AttachmentLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/AttachmentLoader.cs index 078ea8a..fddc390 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/AttachmentLoader.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/AttachmentLoader.cs @@ -27,22 +27,24 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine4_1_00 { - public interface AttachmentLoader { - /// May be null to not load any attachment. - RegionAttachment NewRegionAttachment (Skin skin, string name, string path, Sequence sequence); +namespace Spine4_1_00 +{ + public interface AttachmentLoader + { + /// May be null to not load any attachment. + RegionAttachment NewRegionAttachment(Skin skin, string name, string path, Sequence sequence); - /// May be null to not load any attachment. - MeshAttachment NewMeshAttachment (Skin skin, string name, string path, Sequence sequence); + /// May be null to not load any attachment. + MeshAttachment NewMeshAttachment(Skin skin, string name, string path, Sequence sequence); - /// May be null to not load any attachment. - BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, string name); + /// May be null to not load any attachment. + BoundingBoxAttachment NewBoundingBoxAttachment(Skin skin, string name); - /// May be null to not load any attachment - PathAttachment NewPathAttachment (Skin skin, string name); + /// May be null to not load any attachment + PathAttachment NewPathAttachment(Skin skin, string name); - PointAttachment NewPointAttachment (Skin skin, string name); + PointAttachment NewPointAttachment(Skin skin, string name); - ClippingAttachment NewClippingAttachment (Skin skin, string name); - } + ClippingAttachment NewClippingAttachment(Skin skin, string name); + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/AttachmentType.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/AttachmentType.cs index 17beb29..6f736e7 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/AttachmentType.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/AttachmentType.cs @@ -27,8 +27,10 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine4_1_00 { - public enum AttachmentType { - Region, Boundingbox, Mesh, Linkedmesh, Path, Point, Clipping, Sequence - } +namespace Spine4_1_00 +{ + public enum AttachmentType + { + Region, Boundingbox, Mesh, Linkedmesh, Path, Point, Clipping, Sequence + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/BoundingBoxAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/BoundingBoxAttachment.cs index 8829d0c..f0b5e5c 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/BoundingBoxAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/BoundingBoxAttachment.cs @@ -27,22 +27,25 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; +namespace Spine4_1_00 +{ + /// Attachment that has a polygon for bounds checking. + public class BoundingBoxAttachment : VertexAttachment + { + public BoundingBoxAttachment(string name) + : base(name) + { + } -namespace Spine4_1_00 { - /// Attachment that has a polygon for bounds checking. - public class BoundingBoxAttachment : VertexAttachment { - public BoundingBoxAttachment (string name) - : base(name) { - } + /// Copy constructor. + protected BoundingBoxAttachment(BoundingBoxAttachment other) + : base(other) + { + } - /// Copy constructor. - protected BoundingBoxAttachment (BoundingBoxAttachment other) - : base(other) { - } - - public override Attachment Copy () { - return new BoundingBoxAttachment(this); - } - } + public override Attachment Copy() + { + return new BoundingBoxAttachment(this); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/ClippingAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/ClippingAttachment.cs index 65b1668..da8a01b 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/ClippingAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/ClippingAttachment.cs @@ -27,25 +27,28 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; +namespace Spine4_1_00 +{ + public class ClippingAttachment : VertexAttachment + { + internal SlotData endSlot; -namespace Spine4_1_00 { - public class ClippingAttachment : VertexAttachment { - internal SlotData endSlot; + public SlotData EndSlot { get { return endSlot; } set { endSlot = value; } } - public SlotData EndSlot { get { return endSlot; } set { endSlot = value; } } + public ClippingAttachment(string name) : base(name) + { + } - public ClippingAttachment (string name) : base(name) { - } + /// Copy constructor. + protected ClippingAttachment(ClippingAttachment other) + : base(other) + { + endSlot = other.endSlot; + } - /// Copy constructor. - protected ClippingAttachment (ClippingAttachment other) - : base(other) { - endSlot = other.endSlot; - } - - public override Attachment Copy () { - return new ClippingAttachment(this); - } - } + public override Attachment Copy() + { + return new ClippingAttachment(this); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/IHasTextureRegion.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/IHasTextureRegion.cs index 2b52372..0873e27 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/IHasTextureRegion.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/IHasTextureRegion.cs @@ -27,30 +27,29 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; -using System.Text; +namespace Spine4_1_00 +{ + public interface IHasTextureRegion + { + /// The name used to find the + string Path { get; set; } + /// + /// Sets the region used to draw the attachment. After setting the region or if the region's properties are changed, + /// must be called. + /// + TextureRegion Region { get; set; } -namespace Spine4_1_00 { - public interface IHasTextureRegion { - /// The name used to find the - string Path { get; set; } - /// - /// Sets the region used to draw the attachment. After setting the region or if the region's properties are changed, - /// must be called. - /// - TextureRegion Region { get; set; } + /// + /// Updates any values the attachment calculates using the . Must be called after setting the + /// or if the region's properties are changed. + /// + void UpdateRegion(); - /// - /// Updates any values the attachment calculates using the . Must be called after setting the - /// or if the region's properties are changed. - /// - void UpdateRegion (); + float R { get; set; } + float G { get; set; } + float B { get; set; } + float A { get; set; } - float R { get; set; } - float G { get; set; } - float B { get; set; } - float A { get; set; } - - Sequence Sequence { get; set; } - } + Sequence Sequence { get; set; } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/MeshAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/MeshAttachment.cs index 116b5f1..5ee5931 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/MeshAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/MeshAttachment.cs @@ -29,192 +29,216 @@ using System; -namespace Spine4_1_00 { - /// Attachment that displays a texture region using a mesh. - public class MeshAttachment : VertexAttachment, IHasTextureRegion { - internal TextureRegion region; - internal string path; - internal float[] regionUVs, uvs; - internal int[] triangles; - internal float r = 1, g = 1, b = 1, a = 1; - internal int hullLength; - private MeshAttachment parentMesh; - private Sequence sequence; - - public TextureRegion Region { - get { return region; } - set { - if (value == null) throw new ArgumentNullException("region", "region cannot be null."); - region = value; - } - } - public int HullLength { get { return hullLength; } set { hullLength = value; } } - public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } } - /// The UV pair for each vertex, normalized within the entire texture. - /// - public float[] UVs { get { return uvs; } set { uvs = value; } } - public int[] Triangles { get { return triangles; } set { triangles = value; } } - - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } - - public string Path { get { return path; } set { path = value; } } - public Sequence Sequence { get { return sequence; } set { sequence = value; } } - - public MeshAttachment ParentMesh { - get { return parentMesh; } - set { - parentMesh = value; - if (value != null) { - bones = value.bones; - vertices = value.vertices; - worldVerticesLength = value.worldVerticesLength; - regionUVs = value.regionUVs; - triangles = value.triangles; - HullLength = value.HullLength; - Edges = value.Edges; - Width = value.Width; - Height = value.Height; - } - } - } - - // Nonessential. - public int[] Edges { get; set; } - public float Width { get; set; } - public float Height { get; set; } - - public MeshAttachment (string name) - : base(name) { - } - - /// Copy constructor. Use if the other mesh is a linked mesh. - protected MeshAttachment (MeshAttachment other) - : base(other) { - - if (parentMesh != null) throw new ArgumentException("Use newLinkedMesh to copy a linked mesh."); - - region = other.region; - path = other.path; - r = other.r; - g = other.g; - b = other.b; - a = other.a; - - regionUVs = new float[other.regionUVs.Length]; - Array.Copy(other.regionUVs, 0, regionUVs, 0, regionUVs.Length); - - uvs = new float[other.uvs.Length]; - Array.Copy(other.uvs, 0, uvs, 0, uvs.Length); - - triangles = new int[other.triangles.Length]; - Array.Copy(other.triangles, 0, triangles, 0, triangles.Length); - - hullLength = other.hullLength; - sequence = other.sequence == null ? null : new Sequence(other.sequence); - - // Nonessential. - if (other.Edges != null) { - Edges = new int[other.Edges.Length]; - Array.Copy(other.Edges, 0, Edges, 0, Edges.Length); - } - Width = other.Width; - Height = other.Height; - } - - - public void UpdateRegion () { - float[] regionUVs = this.regionUVs; - if (this.uvs == null || this.uvs.Length != regionUVs.Length) this.uvs = new float[regionUVs.Length]; - float[] uvs = this.uvs; - int n = uvs.Length; - float u, v, width, height; - - if (region is AtlasRegion) { - u = this.region.u; - v = this.region.v; - AtlasRegion region = (AtlasRegion)this.region; - // Note: difference from reference implementation. - // Covers rotation since region.width and height are already setup accordingly. - float textureWidth = this.region.width / (region.u2 - region.u); - float textureHeight = this.region.height / (region.v2 - region.v); - switch (region.degrees) { - case 90: - u -= (region.originalHeight - region.offsetY - region.packedWidth) / textureWidth; - v -= (region.originalWidth - region.offsetX - region.packedHeight) / textureHeight; - width = region.originalHeight / textureWidth; - height = region.originalWidth / textureHeight; - for (int i = 0; i < n; i += 2) { - uvs[i] = u + regionUVs[i + 1] * width; - uvs[i + 1] = v + (1 - regionUVs[i]) * height; - } - return; - case 180: - u -= (region.originalWidth - region.offsetX - region.packedWidth) / textureWidth; - v -= region.offsetY / textureHeight; - width = region.originalWidth / textureWidth; - height = region.originalHeight / textureHeight; - for (int 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: - u -= region.offsetY / textureWidth; - v -= region.offsetX / textureHeight; - width = region.originalHeight / textureWidth; - height = region.originalWidth / textureHeight; - for (int i = 0; i < n; i += 2) { - uvs[i] = u + (1 - regionUVs[i + 1]) * width; - uvs[i + 1] = v + regionUVs[i] * height; - } - return; - } - u -= region.offsetX / textureWidth; - v -= (region.originalHeight - region.offsetY - region.packedHeight) / textureHeight; - width = region.originalWidth / textureWidth; - height = region.originalHeight / textureHeight; - } else if (region == null) { - u = v = 0; - width = height = 1; - } else { - u = region.u; - v = region.v; - width = region.u2 - u; - height = region.v2 - v; - } - for (int i = 0; i < n; i += 2) { - uvs[i] = u + regionUVs[i] * width; - uvs[i + 1] = v + regionUVs[i + 1] * height; - } - } - - /// If the attachment has a , the region may be changed. - override public void ComputeWorldVertices (Slot slot, int start, int count, float[] worldVertices, int offset, int stride = 2) { - if (sequence != null) sequence.Apply(slot, this); - base.ComputeWorldVertices(slot, start, count, worldVertices, offset, stride); - } - - ///Returns a new mesh with this mesh set as the . - public MeshAttachment NewLinkedMesh () { - MeshAttachment mesh = new MeshAttachment(Name); - - mesh.timelineAttachment = timelineAttachment; - mesh.region = region; - mesh.path = path; - mesh.r = r; - mesh.g = g; - mesh.b = b; - mesh.a = a; - mesh.ParentMesh = parentMesh != null ? parentMesh : this; - if (mesh.Region != null) mesh.UpdateRegion(); - return mesh; - } - - public override Attachment Copy () { - return parentMesh != null ? NewLinkedMesh() : new MeshAttachment(this); - } - } +namespace Spine4_1_00 +{ + /// Attachment that displays a texture region using a mesh. + public class MeshAttachment : VertexAttachment, IHasTextureRegion + { + internal TextureRegion region; + internal string path; + internal float[] regionUVs, uvs; + internal int[] triangles; + internal float r = 1, g = 1, b = 1, a = 1; + internal int hullLength; + private MeshAttachment parentMesh; + private Sequence sequence; + + public TextureRegion Region + { + get { return region; } + set + { + if (value == null) throw new ArgumentNullException("region", "region cannot be null."); + region = value; + } + } + public int HullLength { get { return hullLength; } set { hullLength = value; } } + public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } } + /// The UV pair for each vertex, normalized within the entire texture. + /// + public float[] UVs { get { return uvs; } set { uvs = value; } } + public int[] Triangles { get { return triangles; } set { triangles = value; } } + + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } + + public string Path { get { return path; } set { path = value; } } + public Sequence Sequence { get { return sequence; } set { sequence = value; } } + + public MeshAttachment ParentMesh + { + get { return parentMesh; } + set + { + parentMesh = value; + if (value != null) + { + bones = value.bones; + vertices = value.vertices; + worldVerticesLength = value.worldVerticesLength; + regionUVs = value.regionUVs; + triangles = value.triangles; + HullLength = value.HullLength; + Edges = value.Edges; + Width = value.Width; + Height = value.Height; + } + } + } + + // Nonessential. + public int[] Edges { get; set; } + public float Width { get; set; } + public float Height { get; set; } + + public MeshAttachment(string name) + : base(name) + { + } + + /// Copy constructor. Use if the other mesh is a linked mesh. + protected MeshAttachment(MeshAttachment other) + : base(other) + { + + if (parentMesh != null) throw new ArgumentException("Use newLinkedMesh to copy a linked mesh."); + + region = other.region; + path = other.path; + r = other.r; + g = other.g; + b = other.b; + a = other.a; + + regionUVs = new float[other.regionUVs.Length]; + Array.Copy(other.regionUVs, 0, regionUVs, 0, regionUVs.Length); + + uvs = new float[other.uvs.Length]; + Array.Copy(other.uvs, 0, uvs, 0, uvs.Length); + + triangles = new int[other.triangles.Length]; + Array.Copy(other.triangles, 0, triangles, 0, triangles.Length); + + hullLength = other.hullLength; + sequence = other.sequence == null ? null : new Sequence(other.sequence); + + // Nonessential. + if (other.Edges != null) + { + Edges = new int[other.Edges.Length]; + Array.Copy(other.Edges, 0, Edges, 0, Edges.Length); + } + Width = other.Width; + Height = other.Height; + } + + + public void UpdateRegion() + { + float[] regionUVs = this.regionUVs; + if (this.uvs == null || this.uvs.Length != regionUVs.Length) this.uvs = new float[regionUVs.Length]; + float[] uvs = this.uvs; + int n = uvs.Length; + float u, v, width, height; + + if (region is AtlasRegion) + { + u = this.region.u; + v = this.region.v; + AtlasRegion region = (AtlasRegion)this.region; + // Note: difference from reference implementation. + // Covers rotation since region.width and height are already setup accordingly. + float textureWidth = this.region.width / (region.u2 - region.u); + float textureHeight = this.region.height / (region.v2 - region.v); + switch (region.degrees) + { + case 90: + u -= (region.originalHeight - region.offsetY - region.packedWidth) / textureWidth; + v -= (region.originalWidth - region.offsetX - region.packedHeight) / textureHeight; + width = region.originalHeight / textureWidth; + height = region.originalWidth / textureHeight; + for (int i = 0; i < n; i += 2) + { + uvs[i] = u + regionUVs[i + 1] * width; + uvs[i + 1] = v + (1 - regionUVs[i]) * height; + } + return; + case 180: + u -= (region.originalWidth - region.offsetX - region.packedWidth) / textureWidth; + v -= region.offsetY / textureHeight; + width = region.originalWidth / textureWidth; + height = region.originalHeight / textureHeight; + for (int 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: + u -= region.offsetY / textureWidth; + v -= region.offsetX / textureHeight; + width = region.originalHeight / textureWidth; + height = region.originalWidth / textureHeight; + for (int i = 0; i < n; i += 2) + { + uvs[i] = u + (1 - regionUVs[i + 1]) * width; + uvs[i + 1] = v + regionUVs[i] * height; + } + return; + } + u -= region.offsetX / textureWidth; + v -= (region.originalHeight - region.offsetY - region.packedHeight) / textureHeight; + width = region.originalWidth / textureWidth; + height = region.originalHeight / textureHeight; + } + else if (region == null) + { + u = v = 0; + width = height = 1; + } + else + { + u = region.u; + v = region.v; + width = region.u2 - u; + height = region.v2 - v; + } + for (int i = 0; i < n; i += 2) + { + uvs[i] = u + regionUVs[i] * width; + uvs[i + 1] = v + regionUVs[i + 1] * height; + } + } + + /// If the attachment has a , the region may be changed. + override public void ComputeWorldVertices(Slot slot, int start, int count, float[] worldVertices, int offset, int stride = 2) + { + if (sequence != null) sequence.Apply(slot, this); + base.ComputeWorldVertices(slot, start, count, worldVertices, offset, stride); + } + + ///Returns a new mesh with this mesh set as the . + public MeshAttachment NewLinkedMesh() + { + MeshAttachment mesh = new MeshAttachment(Name); + + mesh.timelineAttachment = timelineAttachment; + mesh.region = region; + mesh.path = path; + mesh.r = r; + mesh.g = g; + mesh.b = b; + mesh.a = a; + mesh.ParentMesh = parentMesh != null ? parentMesh : this; + if (mesh.Region != null) mesh.UpdateRegion(); + return mesh; + } + + public override Attachment Copy() + { + return parentMesh != null ? NewLinkedMesh() : new MeshAttachment(this); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/PathAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/PathAttachment.cs index bf82146..6983ffd 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/PathAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/PathAttachment.cs @@ -28,38 +28,42 @@ *****************************************************************************/ using System; -using System.Collections.Generic; -namespace Spine4_1_00 { - public class PathAttachment : VertexAttachment { - internal float[] lengths; - internal bool closed, constantSpeed; +namespace Spine4_1_00 +{ + public class PathAttachment : VertexAttachment + { + internal float[] lengths; + internal bool closed, constantSpeed; - /// The length in the setup pose from the start of the path to the end of each curve. - public float[] Lengths { get { return lengths; } set { lengths = value; } } - /// If true, the start and end knots are connected. - public bool Closed { get { return closed; } set { closed = value; } } - /// If true, additional calculations are performed to make computing positions along the path more accurate and movement along - /// the path have a constant speed. - public bool ConstantSpeed { get { return constantSpeed; } set { constantSpeed = value; } } + /// The length in the setup pose from the start of the path to the end of each curve. + public float[] Lengths { get { return lengths; } set { lengths = value; } } + /// If true, the start and end knots are connected. + public bool Closed { get { return closed; } set { closed = value; } } + /// If true, additional calculations are performed to make computing positions along the path more accurate and movement along + /// the path have a constant speed. + public bool ConstantSpeed { get { return constantSpeed; } set { constantSpeed = value; } } - public PathAttachment (String name) - : base(name) { - } + public PathAttachment(String name) + : base(name) + { + } - /// Copy constructor. - protected PathAttachment (PathAttachment other) - : base(other) { + /// Copy constructor. + protected PathAttachment(PathAttachment other) + : base(other) + { - lengths = new float[other.lengths.Length]; - Array.Copy(other.lengths, 0, lengths, 0, lengths.Length); + lengths = new float[other.lengths.Length]; + Array.Copy(other.lengths, 0, lengths, 0, lengths.Length); - closed = other.closed; - constantSpeed = other.constantSpeed; - } + closed = other.closed; + constantSpeed = other.constantSpeed; + } - public override Attachment Copy () { - return new PathAttachment(this); - } - } + public override Attachment Copy() + { + return new PathAttachment(this); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/PointAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/PointAttachment.cs index 319720b..139cd70 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/PointAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/PointAttachment.cs @@ -27,45 +27,52 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine4_1_00 { - /// - /// 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 Point Attachments in the Spine User Guide. - ///

- public class PointAttachment : Attachment { - internal float x, y, rotation; - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public float Rotation { get { return rotation; } set { rotation = value; } } +namespace Spine4_1_00 +{ + /// + /// 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 Point Attachments in the Spine User Guide. + ///

+ public class PointAttachment : Attachment + { + internal float x, y, rotation; + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public float Rotation { get { return rotation; } set { rotation = value; } } - public PointAttachment (string name) - : base(name) { - } + public PointAttachment(string name) + : base(name) + { + } - /** Copy constructor. */ - protected PointAttachment (PointAttachment other) - : base(other) { - x = other.x; - y = other.y; - rotation = other.rotation; - } + /** Copy constructor. */ + protected PointAttachment(PointAttachment other) + : base(other) + { + x = other.x; + y = other.y; + rotation = other.rotation; + } - public void ComputeWorldPosition (Bone bone, out float ox, out float oy) { - bone.LocalToWorld(this.x, this.y, out ox, out oy); - } + public void ComputeWorldPosition(Bone bone, out float ox, out float oy) + { + bone.LocalToWorld(this.x, this.y, out ox, out oy); + } - public float ComputeWorldRotation (Bone bone) { - float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation); - float ix = cos * bone.a + sin * bone.b; - float iy = cos * bone.c + sin * bone.d; - return MathUtils.Atan2(iy, ix) * MathUtils.RadDeg; - } + public float ComputeWorldRotation(Bone bone) + { + float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation); + float ix = cos * bone.a + sin * bone.b; + float iy = cos * bone.c + sin * bone.d; + return MathUtils.Atan2(iy, ix) * MathUtils.RadDeg; + } - public override Attachment Copy () { - return new PointAttachment(this); - } - } + public override Attachment Copy() + { + return new PointAttachment(this); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/RegionAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/RegionAttachment.cs index 337beb7..e28926b 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/RegionAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/RegionAttachment.cs @@ -29,185 +29,199 @@ using System; -namespace Spine4_1_00 { - /// Attachment that displays a texture region. - public class RegionAttachment : Attachment, IHasTextureRegion { - public const int BLX = 0, BLY = 1; - public const int ULX = 2, ULY = 3; - public const int URX = 4, URY = 5; - public const int BRX = 6, BRY = 7; - - internal TextureRegion region; - internal float x, y, rotation, scaleX = 1, scaleY = 1, width, height; - internal float[] offset = new float[8], uvs = new float[8]; - internal float r = 1, g = 1, b = 1, a = 1; - internal Sequence sequence; - - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public float Rotation { get { return rotation; } set { rotation = value; } } - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - public float ScaleY { get { return scaleY; } set { scaleY = value; } } - public float Width { get { return width; } set { width = value; } } - public float Height { get { return height; } set { height = value; } } - - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } - - public string Path { get; set; } - public TextureRegion Region { get { return region; } set { region = value; } } - - /// For each of the 4 vertices, a pair of x,y values that is the local position of the vertex. - /// - public float[] Offset { get { return offset; } } - public float[] UVs { get { return uvs; } } - public Sequence Sequence { get { return sequence; } set { sequence = value; } } - - public RegionAttachment (string name) - : base(name) { - } - - /// Copy constructor. - public RegionAttachment (RegionAttachment other) - : base(other) { - region = other.region; - Path = other.Path; - x = other.x; - y = other.y; - scaleX = other.scaleX; - scaleY = other.scaleY; - rotation = other.rotation; - width = other.width; - height = other.height; - Array.Copy(other.uvs, 0, uvs, 0, 8); - Array.Copy(other.offset, 0, offset, 0, 8); - r = other.r; - g = other.g; - b = other.b; - a = other.a; - sequence = other.sequence == null ? null : new Sequence(other.sequence); - } - - /// Calculates the and using the region and the attachment's transform. Must be called if the - /// region, the region's properties, or the transform are changed. - public void UpdateRegion () { - float width = Width; - float height = Height; - float localX2 = width / 2; - float localY2 = height / 2; - float localX = -localX2; - float localY = -localY2; - bool rotated = false; - if (region is AtlasRegion) { - AtlasRegion region = (AtlasRegion)this.region; - localX += region.offsetX / region.originalWidth * width; - localY += region.offsetY / region.originalHeight * height; - if (region.degrees == 90) { - rotated = true; - localX2 -= (region.originalWidth - region.offsetX - region.packedHeight) / region.originalWidth * width; - localY2 -= (region.originalHeight - region.offsetY - region.packedWidth) / region.originalHeight * height; - } else { - localX2 -= (region.originalWidth - region.offsetX - region.packedWidth) / region.originalWidth * width; - localY2 -= (region.originalHeight - region.offsetY - region.packedHeight) / region.originalHeight * height; - } - } - float scaleX = ScaleX; - float scaleY = ScaleY; - localX *= scaleX; - localY *= scaleY; - localX2 *= scaleX; - localY2 *= scaleY; - float rotation = Rotation; - float cos = MathUtils.CosDeg(this.rotation); - float sin = MathUtils.SinDeg(this.rotation); - float x = X; - float y = Y; - 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; - float[] offset = this.offset; - offset[BLX] = localXCos - localYSin; - offset[BLY] = localYCos + localXSin; - offset[ULX] = localXCos - localY2Sin; - offset[ULY] = localY2Cos + localXSin; - offset[URX] = localX2Cos - localY2Sin; - offset[URY] = localY2Cos + localX2Sin; - offset[BRX] = localX2Cos - localYSin; - offset[BRY] = localYCos + localX2Sin; - - float[] uvs = this.uvs; - if (rotated) { - 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; - } - } - - /// - /// Transforms the attachment's four vertices to world coordinates. If the attachment has a the region may - /// be changed. - /// The parent bone. - /// The output world vertices. Must have a length greater than or equal to offset + 8. - /// The worldVertices index to begin writing values. - /// The number of worldVertices entries between the value pairs written. - public void ComputeWorldVertices (Slot slot, float[] worldVertices, int offset, int stride = 2) { - if (sequence != null) sequence.Apply(slot, this); - - float[] vertexOffset = this.offset; - Bone bone = slot.Bone; - float bwx = bone.worldX, bwy = bone.worldY; - float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - float offsetX, offsetY; - - // Vertex order is different from RegionAttachment.java - offsetX = vertexOffset[BRX]; // 0 - offsetY = vertexOffset[BRY]; // 1 - worldVertices[offset] = offsetX * a + offsetY * b + bwx; // bl - worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; - offset += stride; - - offsetX = vertexOffset[BLX]; // 2 - offsetY = vertexOffset[BLY]; // 3 - worldVertices[offset] = offsetX * a + offsetY * b + bwx; // ul - worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; - offset += stride; - - offsetX = vertexOffset[ULX]; // 4 - offsetY = vertexOffset[ULY]; // 5 - worldVertices[offset] = offsetX * a + offsetY * b + bwx; // ur - worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; - offset += stride; - - offsetX = vertexOffset[URX]; // 6 - offsetY = vertexOffset[URY]; // 7 - worldVertices[offset] = offsetX * a + offsetY * b + bwx; // br - worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; - //offset += stride; - } - - public override Attachment Copy () { - return new RegionAttachment(this); - } - } +namespace Spine4_1_00 +{ + /// Attachment that displays a texture region. + public class RegionAttachment : Attachment, IHasTextureRegion + { + public const int BLX = 0, BLY = 1; + public const int ULX = 2, ULY = 3; + public const int URX = 4, URY = 5; + public const int BRX = 6, BRY = 7; + + internal TextureRegion region; + internal float x, y, rotation, scaleX = 1, scaleY = 1, width, height; + internal float[] offset = new float[8], uvs = new float[8]; + internal float r = 1, g = 1, b = 1, a = 1; + internal Sequence sequence; + + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public float Rotation { get { return rotation; } set { rotation = value; } } + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + public float Width { get { return width; } set { width = value; } } + public float Height { get { return height; } set { height = value; } } + + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } + + public string Path { get; set; } + public TextureRegion Region { get { return region; } set { region = value; } } + + /// For each of the 4 vertices, a pair of x,y values that is the local position of the vertex. + /// + public float[] Offset { get { return offset; } } + public float[] UVs { get { return uvs; } } + public Sequence Sequence { get { return sequence; } set { sequence = value; } } + + public RegionAttachment(string name) + : base(name) + { + } + + /// Copy constructor. + public RegionAttachment(RegionAttachment other) + : base(other) + { + region = other.region; + Path = other.Path; + x = other.x; + y = other.y; + scaleX = other.scaleX; + scaleY = other.scaleY; + rotation = other.rotation; + width = other.width; + height = other.height; + Array.Copy(other.uvs, 0, uvs, 0, 8); + Array.Copy(other.offset, 0, offset, 0, 8); + r = other.r; + g = other.g; + b = other.b; + a = other.a; + sequence = other.sequence == null ? null : new Sequence(other.sequence); + } + + /// Calculates the and using the region and the attachment's transform. Must be called if the + /// region, the region's properties, or the transform are changed. + public void UpdateRegion() + { + float width = Width; + float height = Height; + float localX2 = width / 2; + float localY2 = height / 2; + float localX = -localX2; + float localY = -localY2; + bool rotated = false; + if (region is AtlasRegion) + { + AtlasRegion region = (AtlasRegion)this.region; + localX += region.offsetX / region.originalWidth * width; + localY += region.offsetY / region.originalHeight * height; + if (region.degrees == 90) + { + rotated = true; + localX2 -= (region.originalWidth - region.offsetX - region.packedHeight) / region.originalWidth * width; + localY2 -= (region.originalHeight - region.offsetY - region.packedWidth) / region.originalHeight * height; + } + else + { + localX2 -= (region.originalWidth - region.offsetX - region.packedWidth) / region.originalWidth * width; + localY2 -= (region.originalHeight - region.offsetY - region.packedHeight) / region.originalHeight * height; + } + } + float scaleX = ScaleX; + float scaleY = ScaleY; + localX *= scaleX; + localY *= scaleY; + localX2 *= scaleX; + localY2 *= scaleY; + float rotation = Rotation; + float cos = MathUtils.CosDeg(this.rotation); + float sin = MathUtils.SinDeg(this.rotation); + float x = X; + float y = Y; + 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; + float[] offset = this.offset; + offset[BLX] = localXCos - localYSin; + offset[BLY] = localYCos + localXSin; + offset[ULX] = localXCos - localY2Sin; + offset[ULY] = localY2Cos + localXSin; + offset[URX] = localX2Cos - localY2Sin; + offset[URY] = localY2Cos + localX2Sin; + offset[BRX] = localX2Cos - localYSin; + offset[BRY] = localYCos + localX2Sin; + + float[] uvs = this.uvs; + if (rotated) + { + 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; + } + } + + /// + /// Transforms the attachment's four vertices to world coordinates. If the attachment has a the region may + /// be changed. + /// The parent bone. + /// The output world vertices. Must have a length greater than or equal to offset + 8. + /// The worldVertices index to begin writing values. + /// The number of worldVertices entries between the value pairs written. + public void ComputeWorldVertices(Slot slot, float[] worldVertices, int offset, int stride = 2) + { + if (sequence != null) sequence.Apply(slot, this); + + float[] vertexOffset = this.offset; + Bone bone = slot.Bone; + float bwx = bone.worldX, bwy = bone.worldY; + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + float offsetX, offsetY; + + // Vertex order is different from RegionAttachment.java + offsetX = vertexOffset[BRX]; // 0 + offsetY = vertexOffset[BRY]; // 1 + worldVertices[offset] = offsetX * a + offsetY * b + bwx; // bl + worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; + offset += stride; + + offsetX = vertexOffset[BLX]; // 2 + offsetY = vertexOffset[BLY]; // 3 + worldVertices[offset] = offsetX * a + offsetY * b + bwx; // ul + worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; + offset += stride; + + offsetX = vertexOffset[ULX]; // 4 + offsetY = vertexOffset[ULY]; // 5 + worldVertices[offset] = offsetX * a + offsetY * b + bwx; // ur + worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; + offset += stride; + + offsetX = vertexOffset[URX]; // 6 + offsetY = vertexOffset[URY]; // 7 + worldVertices[offset] = offsetX * a + offsetY * b + bwx; // br + worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; + //offset += stride; + } + + public override Attachment Copy() + { + return new RegionAttachment(this); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/Sequence.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/Sequence.cs index 7beb9ee..96942e3 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/Sequence.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/Sequence.cs @@ -30,66 +30,76 @@ using System; using System.Text; -namespace Spine4_1_00 { - public class Sequence { - static int nextID = 0; - static readonly Object nextIdLock = new Object(); +namespace Spine4_1_00 +{ + public class Sequence + { + static int nextID = 0; + static readonly Object nextIdLock = new Object(); - internal readonly int id; - internal readonly TextureRegion[] regions; - internal int start, digits, setupIndex; + internal readonly int id; + internal readonly TextureRegion[] regions; + internal int start, digits, setupIndex; - public int Start { get { return start; } set { start = value; } } - public int Digits { get { return digits; } set { digits = value; } } - /// The index of the region to show for the setup pose. - public int SetupIndex { get { return setupIndex; } set { setupIndex = value; } } - public TextureRegion[] Regions { get { return regions; } } - /// Returns a unique ID for this attachment. - public int Id { get { return id; } } + public int Start { get { return start; } set { start = value; } } + public int Digits { get { return digits; } set { digits = value; } } + /// The index of the region to show for the setup pose. + public int SetupIndex { get { return setupIndex; } set { setupIndex = value; } } + public TextureRegion[] Regions { get { return regions; } } + /// Returns a unique ID for this attachment. + public int Id { get { return id; } } - public Sequence (int count) { - lock (Sequence.nextIdLock) { - id = Sequence.nextID++; - } - regions = new TextureRegion[count]; - } + public Sequence(int count) + { + lock (Sequence.nextIdLock) + { + id = Sequence.nextID++; + } + regions = new TextureRegion[count]; + } - /// Copy constructor. - public Sequence (Sequence other) { - lock (Sequence.nextIdLock) { - id = Sequence.nextID++; - } - regions = new TextureRegion[other.regions.Length]; - Array.Copy(other.regions, 0, regions, 0, regions.Length); + /// Copy constructor. + public Sequence(Sequence other) + { + lock (Sequence.nextIdLock) + { + id = Sequence.nextID++; + } + regions = new TextureRegion[other.regions.Length]; + Array.Copy(other.regions, 0, regions, 0, regions.Length); - start = other.start; - digits = other.digits; - setupIndex = other.setupIndex; - } + start = other.start; + digits = other.digits; + setupIndex = other.setupIndex; + } - public void Apply (Slot slot, IHasTextureRegion attachment) { - int index = slot.SequenceIndex; - if (index == -1) index = setupIndex; - if (index >= regions.Length) index = regions.Length - 1; - TextureRegion region = regions[index]; - if (attachment.Region != region) { - attachment.Region = region; - attachment.UpdateRegion(); - } - } + public void Apply(Slot slot, IHasTextureRegion attachment) + { + int index = slot.SequenceIndex; + if (index == -1) index = setupIndex; + if (index >= regions.Length) index = regions.Length - 1; + TextureRegion region = regions[index]; + if (attachment.Region != region) + { + attachment.Region = region; + attachment.UpdateRegion(); + } + } - public string GetPath (string basePath, int index) { - StringBuilder buffer = new StringBuilder(basePath.Length + digits); - buffer.Append(basePath); - string frame = (start + index).ToString(); - for (int i = digits - frame.Length; i > 0; i--) - buffer.Append('0'); - buffer.Append(frame); - return buffer.ToString(); - } - } + public string GetPath(string basePath, int index) + { + StringBuilder buffer = new StringBuilder(basePath.Length + digits); + buffer.Append(basePath); + string frame = (start + index).ToString(); + for (int i = digits - frame.Length; i > 0; i--) + buffer.Append('0'); + buffer.Append(frame); + return buffer.ToString(); + } + } - public enum SequenceMode { - Hold, Once, Loop, Pingpong, OnceReverse, LoopReverse, PingpongReverse - } + public enum SequenceMode + { + Hold, Once, Loop, Pingpong, OnceReverse, LoopReverse, PingpongReverse + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/VertexAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/VertexAttachment.cs index d108613..40f9ad5 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/VertexAttachment.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Attachments/VertexAttachment.cs @@ -29,130 +29,152 @@ using System; -namespace Spine4_1_00 { - /// >An attachment with vertices that are transformed by one or more bones and can be deformed by a slot's - /// . - public abstract class VertexAttachment : Attachment { - static int nextID = 0; - static readonly Object nextIdLock = new Object(); +namespace Spine4_1_00 +{ + /// >An attachment with vertices that are transformed by one or more bones and can be deformed by a slot's + /// . + public abstract class VertexAttachment : Attachment + { + static int nextID = 0; + static readonly Object nextIdLock = new Object(); - internal readonly int id; - internal VertexAttachment timelineAttachment; - internal int[] bones; - internal float[] vertices; - internal int worldVerticesLength; + internal readonly int id; + internal VertexAttachment timelineAttachment; + internal int[] bones; + internal float[] vertices; + internal int worldVerticesLength; - /// Gets a unique ID for this attachment. - public int Id { get { return id; } } - public int[] Bones { get { return bones; } set { bones = value; } } - public float[] Vertices { get { return vertices; } set { vertices = value; } } - public int WorldVerticesLength { get { return worldVerticesLength; } set { worldVerticesLength = value; } } - ///Timelines for the timeline attachment are also applied to this attachment. - /// May be null if no attachment-specific timelines should be applied. - public VertexAttachment TimelineAttachment { get { return timelineAttachment; } set { timelineAttachment = value; } } + /// Gets a unique ID for this attachment. + public int Id { get { return id; } } + public int[] Bones { get { return bones; } set { bones = value; } } + public float[] Vertices { get { return vertices; } set { vertices = value; } } + public int WorldVerticesLength { get { return worldVerticesLength; } set { worldVerticesLength = value; } } + ///Timelines for the timeline attachment are also applied to this attachment. + /// May be null if no attachment-specific timelines should be applied. + public VertexAttachment TimelineAttachment { get { return timelineAttachment; } set { timelineAttachment = value; } } - public VertexAttachment (string name) - : base(name) { + public VertexAttachment(string name) + : base(name) + { - lock (VertexAttachment.nextIdLock) { - id = VertexAttachment.nextID++; - } - timelineAttachment = this; - } + lock (VertexAttachment.nextIdLock) + { + id = VertexAttachment.nextID++; + } + timelineAttachment = this; + } - /// Copy constructor. - public VertexAttachment (VertexAttachment other) - : base(other) { + /// Copy constructor. + public VertexAttachment(VertexAttachment other) + : base(other) + { - lock (VertexAttachment.nextIdLock) { - id = VertexAttachment.nextID++; - } - timelineAttachment = other.timelineAttachment; - if (other.bones != null) { - bones = new int[other.bones.Length]; - Array.Copy(other.bones, 0, bones, 0, bones.Length); - } else - bones = null; + lock (VertexAttachment.nextIdLock) + { + id = VertexAttachment.nextID++; + } + timelineAttachment = other.timelineAttachment; + if (other.bones != null) + { + bones = new int[other.bones.Length]; + Array.Copy(other.bones, 0, bones, 0, bones.Length); + } + else + bones = null; - if (other.vertices != null) { - vertices = new float[other.vertices.Length]; - Array.Copy(other.vertices, 0, vertices, 0, vertices.Length); - } else - vertices = null; + if (other.vertices != null) + { + vertices = new float[other.vertices.Length]; + Array.Copy(other.vertices, 0, vertices, 0, vertices.Length); + } + else + vertices = null; - worldVerticesLength = other.worldVerticesLength; - } + worldVerticesLength = other.worldVerticesLength; + } - public void ComputeWorldVertices (Slot slot, float[] worldVertices) { - ComputeWorldVertices(slot, 0, worldVerticesLength, worldVertices, 0); - } + public void ComputeWorldVertices(Slot slot, float[] worldVertices) + { + ComputeWorldVertices(slot, 0, worldVerticesLength, worldVertices, 0); + } - /// - /// Transforms the attachment's local to world coordinates. If the slot's is - /// not empty, it is used to deform the vertices. - /// - /// See World transforms in the Spine - /// Runtimes Guide. - /// - /// The index of the first value to transform. Each vertex has 2 values, x and y. - /// The number of world vertex values to output. Must be less than or equal to - start. - /// The output world vertices. Must have a length greater than or equal to + . - /// The index to begin writing values. - /// The number of entries between the value pairs written. - public virtual void ComputeWorldVertices (Slot slot, int start, int count, float[] worldVertices, int offset, int stride = 2) { - count = offset + (count >> 1) * stride; - ExposedList deformArray = slot.deform; - float[] vertices = this.vertices; - int[] bones = this.bones; - if (bones == null) { - if (deformArray.Count > 0) vertices = deformArray.Items; - Bone bone = slot.bone; - float x = bone.worldX, y = bone.worldY; - float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - for (int vv = start, w = offset; w < count; vv += 2, w += stride) { - float vx = vertices[vv], 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 (int i = 0; i < start; i += 2) { - int n = bones[v]; - v += n + 1; - skip += n; - } - Bone[] skeletonBones = slot.bone.skeleton.bones.Items; - if (deformArray.Count == 0) { - for (int w = offset, b = skip * 3; w < count; w += stride) { - float wx = 0, wy = 0; - int n = bones[v++]; - n += v; - for (; v < n; v++, b += 3) { - Bone bone = skeletonBones[bones[v]]; - float vx = vertices[b], vy = vertices[b + 1], 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 { - float[] deform = deformArray.Items; - for (int w = offset, b = skip * 3, f = skip << 1; w < count; w += stride) { - float wx = 0, wy = 0; - int n = bones[v++]; - n += v; - for (; v < n; v++, b += 3, f += 2) { - Bone bone = skeletonBones[bones[v]]; - float vx = vertices[b] + deform[f], vy = vertices[b + 1] + deform[f + 1], 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; - } - } - } - } + /// + /// Transforms the attachment's local to world coordinates. If the slot's is + /// not empty, it is used to deform the vertices. + /// + /// See World transforms in the Spine + /// Runtimes Guide. + /// + /// The index of the first value to transform. Each vertex has 2 values, x and y. + /// The number of world vertex values to output. Must be less than or equal to - start. + /// The output world vertices. Must have a length greater than or equal to + . + /// The index to begin writing values. + /// The number of entries between the value pairs written. + public virtual void ComputeWorldVertices(Slot slot, int start, int count, float[] worldVertices, int offset, int stride = 2) + { + count = offset + (count >> 1) * stride; + ExposedList deformArray = slot.deform; + float[] vertices = this.vertices; + int[] bones = this.bones; + if (bones == null) + { + if (deformArray.Count > 0) vertices = deformArray.Items; + Bone bone = slot.bone; + float x = bone.worldX, y = bone.worldY; + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + for (int vv = start, w = offset; w < count; vv += 2, w += stride) + { + float vx = vertices[vv], 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 (int i = 0; i < start; i += 2) + { + int n = bones[v]; + v += n + 1; + skip += n; + } + Bone[] skeletonBones = slot.bone.skeleton.bones.Items; + if (deformArray.Count == 0) + { + for (int w = offset, b = skip * 3; w < count; w += stride) + { + float wx = 0, wy = 0; + int n = bones[v++]; + n += v; + for (; v < n; v++, b += 3) + { + Bone bone = skeletonBones[bones[v]]; + float vx = vertices[b], vy = vertices[b + 1], 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 + { + float[] deform = deformArray.Items; + for (int w = offset, b = skip * 3, f = skip << 1; w < count; w += stride) + { + float wx = 0, wy = 0; + int n = bones[v++]; + n += v; + for (; v < n; v++, b += 3, f += 2) + { + Bone bone = skeletonBones[bones[v]]; + float vx = vertices[b] + deform[f], vy = vertices[b + 1] + deform[f + 1], 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; + } + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/BlendMode.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/BlendMode.cs index 751bdf3..c7810c9 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/BlendMode.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/BlendMode.cs @@ -27,8 +27,10 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine4_1_00 { - public enum BlendMode { - Normal, Additive, Multiply, Screen - } +namespace Spine4_1_00 +{ + public enum BlendMode + { + Normal, Additive, Multiply, Screen + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Bone.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Bone.cs index 802a62b..9ec1102 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Bone.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Bone.cs @@ -29,365 +29,397 @@ using System; -namespace Spine4_1_00 { - /// - /// 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. - /// - /// - public class Bone : IUpdatable { - static public bool yDown; - - internal BoneData data; - internal Skeleton skeleton; - internal Bone parent; - internal ExposedList children = new ExposedList(); - internal float x, y, rotation, scaleX, scaleY, shearX, shearY; - internal float ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY; - - internal float a, b, worldX; - internal float c, d, worldY; - - internal bool sorted, active; - - public BoneData Data { get { return data; } } - public Skeleton Skeleton { get { return skeleton; } } - public Bone Parent { get { return parent; } } - public ExposedList Children { get { return children; } } - /// Returns false when the bone has not been computed because is true and the - /// active skin does not contain this bone. - public bool Active { get { return active; } } - /// The local X translation. - public float X { get { return x; } set { x = value; } } - /// The local Y translation. - public float Y { get { return y; } set { y = value; } } - /// The local rotation. - public float Rotation { get { return rotation; } set { rotation = value; } } - - /// The local scaleX. - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - - /// The local scaleY. - public float ScaleY { get { return scaleY; } set { scaleY = value; } } - - /// The local shearX. - public float ShearX { get { return shearX; } set { shearX = value; } } - - /// The local shearY. - public float ShearY { get { return shearY; } set { shearY = value; } } - - /// The rotation, as calculated by any constraints. - public float AppliedRotation { get { return arotation; } set { arotation = value; } } - - /// The applied local x translation. - public float AX { get { return ax; } set { ax = value; } } - - /// The applied local y translation. - public float AY { get { return ay; } set { ay = value; } } - - /// The applied local scaleX. - public float AScaleX { get { return ascaleX; } set { ascaleX = value; } } - - /// The applied local scaleY. - public float AScaleY { get { return ascaleY; } set { ascaleY = value; } } - - /// The applied local shearX. - public float AShearX { get { return ashearX; } set { ashearX = value; } } - - /// The applied local shearY. - public float AShearY { get { return ashearY; } set { ashearY = value; } } - - /// Part of the world transform matrix for the X axis. If changed, should be called. - public float A { get { return a; } set { a = value; } } - /// Part of the world transform matrix for the Y axis. If changed, should be called. - public float B { get { return b; } set { b = value; } } - /// Part of the world transform matrix for the X axis. If changed, should be called. - public float C { get { return c; } set { c = value; } } - /// Part of the world transform matrix for the Y axis. If changed, should be called. - public float D { get { return d; } set { d = value; } } - - /// The world X position. If changed, should be called. - public float WorldX { get { return worldX; } set { worldX = value; } } - /// The world Y position. If changed, should be called. - public float WorldY { get { return worldY; } set { worldY = value; } } - public float WorldRotationX { get { return MathUtils.Atan2(c, a) * MathUtils.RadDeg; } } - public float WorldRotationY { get { return MathUtils.Atan2(d, b) * MathUtils.RadDeg; } } - - /// Returns the magnitide (always positive) of the world scale X. - public float WorldScaleX { get { return (float)Math.Sqrt(a * a + c * c); } } - /// Returns the magnitide (always positive) of the world scale Y. - public float WorldScaleY { get { return (float)Math.Sqrt(b * b + d * d); } } - - public Bone (BoneData data, Skeleton skeleton, Bone parent) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.data = data; - this.skeleton = skeleton; - this.parent = parent; - SetToSetupPose(); - } - - /// Copy constructor. Does not copy the bones. - /// May be null. - public Bone (Bone bone, Skeleton skeleton, Bone parent) { - if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.skeleton = skeleton; - this.parent = parent; - data = bone.data; - x = bone.x; - y = bone.y; - rotation = bone.rotation; - scaleX = bone.scaleX; - scaleY = bone.scaleY; - shearX = bone.shearX; - shearY = bone.shearY; - } - - /// Computes the world transform using the parent bone and this bone's local applied transform. - public void Update () { - UpdateWorldTransform(ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY); - } - - /// Computes the world transform using the parent bone and this bone's local transform. - public void UpdateWorldTransform () { - UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); - } - - /// Computes the world transform using the parent bone and the specified local transform. The applied transform is set to the - /// specified local transform. Child bones are not updated. - /// - /// See World transforms in the Spine - /// Runtimes Guide. - public void UpdateWorldTransform (float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY) { - ax = x; - ay = y; - arotation = rotation; - ascaleX = scaleX; - ascaleY = scaleY; - ashearX = shearX; - ashearY = shearY; - - Bone parent = this.parent; - if (parent == null) { // Root bone. - float rotationY = rotation + 90 + shearY, sx = skeleton.ScaleX, sy = skeleton.ScaleY; - a = MathUtils.CosDeg(rotation + shearX) * scaleX * sx; - b = MathUtils.CosDeg(rotationY) * scaleY * sx; - c = MathUtils.SinDeg(rotation + shearX) * scaleX * sy; - d = MathUtils.SinDeg(rotationY) * scaleY * sy; - worldX = x * sx + skeleton.x; - worldY = y * sy + skeleton.y; - return; - } - - float 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 (data.transformMode) { - case TransformMode.Normal: { - float rotationY = rotation + 90 + shearY; - float la = MathUtils.CosDeg(rotation + shearX) * scaleX; - float lb = MathUtils.CosDeg(rotationY) * scaleY; - float lc = MathUtils.SinDeg(rotation + shearX) * scaleX; - float ld = MathUtils.SinDeg(rotationY) * scaleY; - a = pa * la + pb * lc; - b = pa * lb + pb * ld; - c = pc * la + pd * lc; - d = pc * lb + pd * ld; - return; - } - case TransformMode.OnlyTranslation: { - float rotationY = rotation + 90 + shearY; - a = MathUtils.CosDeg(rotation + shearX) * scaleX; - b = MathUtils.CosDeg(rotationY) * scaleY; - c = MathUtils.SinDeg(rotation + shearX) * scaleX; - d = MathUtils.SinDeg(rotationY) * scaleY; - break; - } - case TransformMode.NoRotationOrReflection: { - float s = pa * pa + pc * pc, prx; - if (s > 0.0001f) { - s = Math.Abs(pa * pd - pb * pc) / s; - pa /= skeleton.ScaleX; - pc /= skeleton.ScaleY; - pb = pc * s; - pd = pa * s; - prx = MathUtils.Atan2(pc, pa) * MathUtils.RadDeg; - } else { - pa = 0; - pc = 0; - prx = 90 - MathUtils.Atan2(pd, pb) * MathUtils.RadDeg; - } - float rx = rotation + shearX - prx; - float ry = rotation + shearY - prx + 90; - float la = MathUtils.CosDeg(rx) * scaleX; - float lb = MathUtils.CosDeg(ry) * scaleY; - float lc = MathUtils.SinDeg(rx) * scaleX; - float ld = MathUtils.SinDeg(ry) * scaleY; - a = pa * la - pb * lc; - b = pa * lb - pb * ld; - c = pc * la + pd * lc; - d = pc * lb + pd * ld; - break; - } - case TransformMode.NoScale: - case TransformMode.NoScaleOrReflection: { - float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation); - float za = (pa * cos + pb * sin) / skeleton.ScaleX; - float zc = (pc * cos + pd * sin) / skeleton.ScaleY; - float s = (float)Math.Sqrt(za * za + zc * zc); - if (s > 0.00001f) s = 1 / s; - za *= s; - zc *= s; - s = (float)Math.Sqrt(za * za + zc * zc); - if (data.transformMode == TransformMode.NoScale - && (pa * pd - pb * pc < 0) != (skeleton.ScaleX < 0 != skeleton.ScaleY < 0)) s = -s; - - float r = MathUtils.PI / 2 + MathUtils.Atan2(zc, za); - float zb = MathUtils.Cos(r) * s; - float zd = MathUtils.Sin(r) * s; - float la = MathUtils.CosDeg(shearX) * scaleX; - float lb = MathUtils.CosDeg(90 + shearY) * scaleY; - float lc = MathUtils.SinDeg(shearX) * scaleX; - float ld = MathUtils.SinDeg(90 + shearY) * scaleY; - a = za * la + zb * lc; - b = za * lb + zb * ld; - c = zc * la + zd * lc; - d = zc * lb + zd * ld; - break; - } - } - - a *= skeleton.ScaleX; - b *= skeleton.ScaleX; - c *= skeleton.ScaleY; - d *= skeleton.ScaleY; - } - - public void SetToSetupPose () { - BoneData data = this.data; - x = data.x; - y = data.y; - rotation = data.rotation; - scaleX = data.scaleX; - scaleY = data.scaleY; - shearX = data.shearX; - shearY = data.shearY; - } - - /// - /// Computes the applied transform values from the world transform. - /// - /// If the world transform is modified (by a constraint, , etc) then this method should be called so - /// the applied transform matches the world transform. The applied transform may be needed by other code (eg to apply another - /// constraint). - /// - /// Some information is ambiguous in the world transform, such as -1,-1 scale versus 180 rotation. The applied transform after - /// calling this method is equivalent to the local transform used to compute the world transform, but may not be identical. - /// - public void UpdateAppliedTransform () { - Bone parent = this.parent; - if (parent == null) { - ax = worldX - skeleton.x; - ay = worldY - skeleton.y; - arotation = MathUtils.Atan2(c, a) * MathUtils.RadDeg; - ascaleX = (float)Math.Sqrt(a * a + c * c); - ascaleY = (float)Math.Sqrt(b * b + d * d); - ashearX = 0; - ashearY = MathUtils.Atan2(a * b + c * d, a * d - b * c) * MathUtils.RadDeg; - return; - } - float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d; - float pid = 1 / (pa * pd - pb * pc); - float dx = worldX - parent.worldX, dy = worldY - parent.worldY; - ax = (dx * pd * pid - dy * pb * pid); - ay = (dy * pa * pid - dx * pc * pid); - float ia = pid * pd; - float id = pid * pa; - float ib = pid * pb; - float ic = pid * pc; - float ra = ia * a - ib * c; - float rb = ia * b - ib * d; - float rc = id * c - ic * a; - float rd = id * d - ic * b; - ashearX = 0; - ascaleX = (float)Math.Sqrt(ra * ra + rc * rc); - if (ascaleX > 0.0001f) { - float det = ra * rd - rb * rc; - ascaleY = det / ascaleX; - ashearY = MathUtils.Atan2(ra * rb + rc * rd, det) * MathUtils.RadDeg; - arotation = MathUtils.Atan2(rc, ra) * MathUtils.RadDeg; - } else { - ascaleX = 0; - ascaleY = (float)Math.Sqrt(rb * rb + rd * rd); - ashearY = 0; - arotation = 90 - MathUtils.Atan2(rd, rb) * MathUtils.RadDeg; - } - } - - public void WorldToLocal (float worldX, float worldY, out float localX, out float localY) { - float a = this.a, b = this.b, c = this.c, d = this.d; - float det = a * d - b * c; - float x = worldX - this.worldX, y = worldY - this.worldY; - localX = (x * d - y * b) / det; - localY = (y * a - x * c) / det; - } - - public void LocalToWorld (float localX, float localY, out float worldX, out float worldY) { - worldX = localX * a + localY * b + this.worldX; - worldY = localX * c + localY * d + this.worldY; - } - - public float WorldToLocalRotationX { - get { - Bone parent = this.parent; - if (parent == null) return arotation; - float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, a = this.a, c = this.c; - return MathUtils.Atan2(pa * c - pc * a, pd * a - pb * c) * MathUtils.RadDeg; - } - } - - public float WorldToLocalRotationY { - get { - Bone parent = this.parent; - if (parent == null) return arotation; - float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, b = this.b, d = this.d; - return MathUtils.Atan2(pa * d - pc * b, pd * b - pb * d) * MathUtils.RadDeg; - } - } - - public float WorldToLocalRotation (float worldRotation) { - float sin = MathUtils.SinDeg(worldRotation), cos = MathUtils.CosDeg(worldRotation); - return MathUtils.Atan2(a * sin - c * cos, d * cos - b * sin) * MathUtils.RadDeg + rotation - shearX; - } - - public float LocalToWorldRotation (float localRotation) { - localRotation -= rotation - shearX; - float sin = MathUtils.SinDeg(localRotation), cos = MathUtils.CosDeg(localRotation); - return MathUtils.Atan2(cos * c + sin * d, cos * a + sin * b) * MathUtils.RadDeg; - } - - /// - /// Rotates the world transform the specified amount. - /// - /// After changes are made to the world transform, should be called and will - /// need to be called on any child bones, recursively. - /// - public void RotateWorld (float degrees) { - float a = this.a, b = this.b, c = this.c, d = this.d; - float cos = MathUtils.CosDeg(degrees), sin = MathUtils.SinDeg(degrees); - this.a = cos * a - sin * c; - this.b = cos * b - sin * d; - this.c = sin * a + cos * c; - this.d = sin * b + cos * d; - } - - override public string ToString () { - return data.name; - } - } +namespace Spine4_1_00 +{ + /// + /// 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. + /// + /// + public class Bone : IUpdatable + { + static public bool yDown; + + internal BoneData data; + internal Skeleton skeleton; + internal Bone parent; + internal ExposedList children = new ExposedList(); + internal float x, y, rotation, scaleX, scaleY, shearX, shearY; + internal float ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY; + + internal float a, b, worldX; + internal float c, d, worldY; + + internal bool sorted, active; + + public BoneData Data { get { return data; } } + public Skeleton Skeleton { get { return skeleton; } } + public Bone Parent { get { return parent; } } + public ExposedList Children { get { return children; } } + /// Returns false when the bone has not been computed because is true and the + /// active skin does not contain this bone. + public bool Active { get { return active; } } + /// The local X translation. + public float X { get { return x; } set { x = value; } } + /// The local Y translation. + public float Y { get { return y; } set { y = value; } } + /// The local rotation. + public float Rotation { get { return rotation; } set { rotation = value; } } + + /// The local scaleX. + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + + /// The local scaleY. + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + + /// The local shearX. + public float ShearX { get { return shearX; } set { shearX = value; } } + + /// The local shearY. + public float ShearY { get { return shearY; } set { shearY = value; } } + + /// The rotation, as calculated by any constraints. + public float AppliedRotation { get { return arotation; } set { arotation = value; } } + + /// The applied local x translation. + public float AX { get { return ax; } set { ax = value; } } + + /// The applied local y translation. + public float AY { get { return ay; } set { ay = value; } } + + /// The applied local scaleX. + public float AScaleX { get { return ascaleX; } set { ascaleX = value; } } + + /// The applied local scaleY. + public float AScaleY { get { return ascaleY; } set { ascaleY = value; } } + + /// The applied local shearX. + public float AShearX { get { return ashearX; } set { ashearX = value; } } + + /// The applied local shearY. + public float AShearY { get { return ashearY; } set { ashearY = value; } } + + /// Part of the world transform matrix for the X axis. If changed, should be called. + public float A { get { return a; } set { a = value; } } + /// Part of the world transform matrix for the Y axis. If changed, should be called. + public float B { get { return b; } set { b = value; } } + /// Part of the world transform matrix for the X axis. If changed, should be called. + public float C { get { return c; } set { c = value; } } + /// Part of the world transform matrix for the Y axis. If changed, should be called. + public float D { get { return d; } set { d = value; } } + + /// The world X position. If changed, should be called. + public float WorldX { get { return worldX; } set { worldX = value; } } + /// The world Y position. If changed, should be called. + public float WorldY { get { return worldY; } set { worldY = value; } } + public float WorldRotationX { get { return MathUtils.Atan2(c, a) * MathUtils.RadDeg; } } + public float WorldRotationY { get { return MathUtils.Atan2(d, b) * MathUtils.RadDeg; } } + + /// Returns the magnitide (always positive) of the world scale X. + public float WorldScaleX { get { return (float)Math.Sqrt(a * a + c * c); } } + /// Returns the magnitide (always positive) of the world scale Y. + public float WorldScaleY { get { return (float)Math.Sqrt(b * b + d * d); } } + + public Bone(BoneData data, Skeleton skeleton, Bone parent) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + this.skeleton = skeleton; + this.parent = parent; + SetToSetupPose(); + } + + /// Copy constructor. Does not copy the bones. + /// May be null. + public Bone(Bone bone, Skeleton skeleton, Bone parent) + { + if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.skeleton = skeleton; + this.parent = parent; + data = bone.data; + x = bone.x; + y = bone.y; + rotation = bone.rotation; + scaleX = bone.scaleX; + scaleY = bone.scaleY; + shearX = bone.shearX; + shearY = bone.shearY; + } + + /// Computes the world transform using the parent bone and this bone's local applied transform. + public void Update() + { + UpdateWorldTransform(ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY); + } + + /// Computes the world transform using the parent bone and this bone's local transform. + public void UpdateWorldTransform() + { + UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); + } + + /// Computes the world transform using the parent bone and the specified local transform. The applied transform is set to the + /// specified local transform. Child bones are not updated. + /// + /// See World transforms in the Spine + /// Runtimes Guide. + public void UpdateWorldTransform(float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY) + { + ax = x; + ay = y; + arotation = rotation; + ascaleX = scaleX; + ascaleY = scaleY; + ashearX = shearX; + ashearY = shearY; + + Bone parent = this.parent; + if (parent == null) + { // Root bone. + float rotationY = rotation + 90 + shearY, sx = skeleton.ScaleX, sy = skeleton.ScaleY; + a = MathUtils.CosDeg(rotation + shearX) * scaleX * sx; + b = MathUtils.CosDeg(rotationY) * scaleY * sx; + c = MathUtils.SinDeg(rotation + shearX) * scaleX * sy; + d = MathUtils.SinDeg(rotationY) * scaleY * sy; + worldX = x * sx + skeleton.x; + worldY = y * sy + skeleton.y; + return; + } + + float 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 (data.transformMode) + { + case TransformMode.Normal: + { + float rotationY = rotation + 90 + shearY; + float la = MathUtils.CosDeg(rotation + shearX) * scaleX; + float lb = MathUtils.CosDeg(rotationY) * scaleY; + float lc = MathUtils.SinDeg(rotation + shearX) * scaleX; + float ld = MathUtils.SinDeg(rotationY) * scaleY; + a = pa * la + pb * lc; + b = pa * lb + pb * ld; + c = pc * la + pd * lc; + d = pc * lb + pd * ld; + return; + } + case TransformMode.OnlyTranslation: + { + float rotationY = rotation + 90 + shearY; + a = MathUtils.CosDeg(rotation + shearX) * scaleX; + b = MathUtils.CosDeg(rotationY) * scaleY; + c = MathUtils.SinDeg(rotation + shearX) * scaleX; + d = MathUtils.SinDeg(rotationY) * scaleY; + break; + } + case TransformMode.NoRotationOrReflection: + { + float s = pa * pa + pc * pc, prx; + if (s > 0.0001f) + { + s = Math.Abs(pa * pd - pb * pc) / s; + pa /= skeleton.ScaleX; + pc /= skeleton.ScaleY; + pb = pc * s; + pd = pa * s; + prx = MathUtils.Atan2(pc, pa) * MathUtils.RadDeg; + } + else + { + pa = 0; + pc = 0; + prx = 90 - MathUtils.Atan2(pd, pb) * MathUtils.RadDeg; + } + float rx = rotation + shearX - prx; + float ry = rotation + shearY - prx + 90; + float la = MathUtils.CosDeg(rx) * scaleX; + float lb = MathUtils.CosDeg(ry) * scaleY; + float lc = MathUtils.SinDeg(rx) * scaleX; + float ld = MathUtils.SinDeg(ry) * scaleY; + a = pa * la - pb * lc; + b = pa * lb - pb * ld; + c = pc * la + pd * lc; + d = pc * lb + pd * ld; + break; + } + case TransformMode.NoScale: + case TransformMode.NoScaleOrReflection: + { + float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation); + float za = (pa * cos + pb * sin) / skeleton.ScaleX; + float zc = (pc * cos + pd * sin) / skeleton.ScaleY; + float s = (float)Math.Sqrt(za * za + zc * zc); + if (s > 0.00001f) s = 1 / s; + za *= s; + zc *= s; + s = (float)Math.Sqrt(za * za + zc * zc); + if (data.transformMode == TransformMode.NoScale + && (pa * pd - pb * pc < 0) != (skeleton.ScaleX < 0 != skeleton.ScaleY < 0)) s = -s; + + float r = MathUtils.PI / 2 + MathUtils.Atan2(zc, za); + float zb = MathUtils.Cos(r) * s; + float zd = MathUtils.Sin(r) * s; + float la = MathUtils.CosDeg(shearX) * scaleX; + float lb = MathUtils.CosDeg(90 + shearY) * scaleY; + float lc = MathUtils.SinDeg(shearX) * scaleX; + float ld = MathUtils.SinDeg(90 + shearY) * scaleY; + a = za * la + zb * lc; + b = za * lb + zb * ld; + c = zc * la + zd * lc; + d = zc * lb + zd * ld; + break; + } + } + + a *= skeleton.ScaleX; + b *= skeleton.ScaleX; + c *= skeleton.ScaleY; + d *= skeleton.ScaleY; + } + + public void SetToSetupPose() + { + BoneData data = this.data; + x = data.x; + y = data.y; + rotation = data.rotation; + scaleX = data.scaleX; + scaleY = data.scaleY; + shearX = data.shearX; + shearY = data.shearY; + } + + /// + /// Computes the applied transform values from the world transform. + /// + /// If the world transform is modified (by a constraint, , etc) then this method should be called so + /// the applied transform matches the world transform. The applied transform may be needed by other code (eg to apply another + /// constraint). + /// + /// Some information is ambiguous in the world transform, such as -1,-1 scale versus 180 rotation. The applied transform after + /// calling this method is equivalent to the local transform used to compute the world transform, but may not be identical. + /// + public void UpdateAppliedTransform() + { + Bone parent = this.parent; + if (parent == null) + { + ax = worldX - skeleton.x; + ay = worldY - skeleton.y; + arotation = MathUtils.Atan2(c, a) * MathUtils.RadDeg; + ascaleX = (float)Math.Sqrt(a * a + c * c); + ascaleY = (float)Math.Sqrt(b * b + d * d); + ashearX = 0; + ashearY = MathUtils.Atan2(a * b + c * d, a * d - b * c) * MathUtils.RadDeg; + return; + } + float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d; + float pid = 1 / (pa * pd - pb * pc); + float dx = worldX - parent.worldX, dy = worldY - parent.worldY; + ax = (dx * pd * pid - dy * pb * pid); + ay = (dy * pa * pid - dx * pc * pid); + float ia = pid * pd; + float id = pid * pa; + float ib = pid * pb; + float ic = pid * pc; + float ra = ia * a - ib * c; + float rb = ia * b - ib * d; + float rc = id * c - ic * a; + float rd = id * d - ic * b; + ashearX = 0; + ascaleX = (float)Math.Sqrt(ra * ra + rc * rc); + if (ascaleX > 0.0001f) + { + float det = ra * rd - rb * rc; + ascaleY = det / ascaleX; + ashearY = MathUtils.Atan2(ra * rb + rc * rd, det) * MathUtils.RadDeg; + arotation = MathUtils.Atan2(rc, ra) * MathUtils.RadDeg; + } + else + { + ascaleX = 0; + ascaleY = (float)Math.Sqrt(rb * rb + rd * rd); + ashearY = 0; + arotation = 90 - MathUtils.Atan2(rd, rb) * MathUtils.RadDeg; + } + } + + public void WorldToLocal(float worldX, float worldY, out float localX, out float localY) + { + float a = this.a, b = this.b, c = this.c, d = this.d; + float det = a * d - b * c; + float x = worldX - this.worldX, y = worldY - this.worldY; + localX = (x * d - y * b) / det; + localY = (y * a - x * c) / det; + } + + public void LocalToWorld(float localX, float localY, out float worldX, out float worldY) + { + worldX = localX * a + localY * b + this.worldX; + worldY = localX * c + localY * d + this.worldY; + } + + public float WorldToLocalRotationX + { + get + { + Bone parent = this.parent; + if (parent == null) return arotation; + float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, a = this.a, c = this.c; + return MathUtils.Atan2(pa * c - pc * a, pd * a - pb * c) * MathUtils.RadDeg; + } + } + + public float WorldToLocalRotationY + { + get + { + Bone parent = this.parent; + if (parent == null) return arotation; + float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, b = this.b, d = this.d; + return MathUtils.Atan2(pa * d - pc * b, pd * b - pb * d) * MathUtils.RadDeg; + } + } + + public float WorldToLocalRotation(float worldRotation) + { + float sin = MathUtils.SinDeg(worldRotation), cos = MathUtils.CosDeg(worldRotation); + return MathUtils.Atan2(a * sin - c * cos, d * cos - b * sin) * MathUtils.RadDeg + rotation - shearX; + } + + public float LocalToWorldRotation(float localRotation) + { + localRotation -= rotation - shearX; + float sin = MathUtils.SinDeg(localRotation), cos = MathUtils.CosDeg(localRotation); + return MathUtils.Atan2(cos * c + sin * d, cos * a + sin * b) * MathUtils.RadDeg; + } + + /// + /// Rotates the world transform the specified amount. + /// + /// After changes are made to the world transform, should be called and will + /// need to be called on any child bones, recursively. + /// + public void RotateWorld(float degrees) + { + float a = this.a, b = this.b, c = this.c, d = this.d; + float cos = MathUtils.CosDeg(degrees), sin = MathUtils.SinDeg(degrees); + this.a = cos * a - sin * c; + this.b = cos * b - sin * d; + this.c = sin * a + cos * c; + this.d = sin * b + cos * d; + } + + override public string ToString() + { + return data.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/BoneData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/BoneData.cs index afd2e09..897b7b6 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/BoneData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/BoneData.cs @@ -29,77 +29,82 @@ using System; -namespace Spine4_1_00 { - public class BoneData { - internal int index; - internal string name; - internal BoneData parent; - internal float length; - internal float x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY; - internal TransformMode transformMode = TransformMode.Normal; - internal bool skinRequired; - - /// The index of the bone in Skeleton.Bones - public int Index { get { return index; } } - - /// The name of the bone, which is unique across all bones in the skeleton. - public string Name { get { return name; } } - - /// May be null. - public BoneData Parent { get { return parent; } } - - public float Length { get { return length; } set { length = value; } } - - /// Local X translation. - public float X { get { return x; } set { x = value; } } - - /// Local Y translation. - public float Y { get { return y; } set { y = value; } } - - /// Local rotation. - public float Rotation { get { return rotation; } set { rotation = value; } } - - /// Local scaleX. - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - - /// Local scaleY. - public float ScaleY { get { return scaleY; } set { scaleY = value; } } - - /// Local shearX. - public float ShearX { get { return shearX; } set { shearX = value; } } - - /// Local shearY. - public float ShearY { get { return shearY; } set { shearY = value; } } - - /// The transform mode for how parent world transforms affect this bone. - public TransformMode TransformMode { get { return transformMode; } set { transformMode = value; } } - - ///When true, only updates this bone if the contains this - /// bone. - /// - public bool SkinRequired { get { return skinRequired; } set { skinRequired = value; } } - - /// May be null. - public BoneData (int index, string name, BoneData parent) { - if (index < 0) throw new ArgumentException("index must be >= 0", "index"); - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.index = index; - this.name = name; - this.parent = parent; - } - - override public string ToString () { - return name; - } - } - - [Flags] - public enum TransformMode { - //0000 0 Flip Scale Rotation - Normal = 0, // 0000 - OnlyTranslation = 7, // 0111 - NoRotationOrReflection = 1, // 0001 - NoScale = 2, // 0010 - NoScaleOrReflection = 6, // 0110 - } +namespace Spine4_1_00 +{ + public class BoneData + { + internal int index; + internal string name; + internal BoneData parent; + internal float length; + internal float x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY; + internal TransformMode transformMode = TransformMode.Normal; + internal bool skinRequired; + + /// The index of the bone in Skeleton.Bones + public int Index { get { return index; } } + + /// The name of the bone, which is unique across all bones in the skeleton. + public string Name { get { return name; } } + + /// May be null. + public BoneData Parent { get { return parent; } } + + public float Length { get { return length; } set { length = value; } } + + /// Local X translation. + public float X { get { return x; } set { x = value; } } + + /// Local Y translation. + public float Y { get { return y; } set { y = value; } } + + /// Local rotation. + public float Rotation { get { return rotation; } set { rotation = value; } } + + /// Local scaleX. + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + + /// Local scaleY. + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + + /// Local shearX. + public float ShearX { get { return shearX; } set { shearX = value; } } + + /// Local shearY. + public float ShearY { get { return shearY; } set { shearY = value; } } + + /// The transform mode for how parent world transforms affect this bone. + public TransformMode TransformMode { get { return transformMode; } set { transformMode = value; } } + + ///When true, only updates this bone if the contains this + /// bone. + /// + public bool SkinRequired { get { return skinRequired; } set { skinRequired = value; } } + + /// May be null. + public BoneData(int index, string name, BoneData parent) + { + if (index < 0) throw new ArgumentException("index must be >= 0", "index"); + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.index = index; + this.name = name; + this.parent = parent; + } + + override public string ToString() + { + return name; + } + } + + [Flags] + public enum TransformMode + { + //0000 0 Flip Scale Rotation + Normal = 0, // 0000 + OnlyTranslation = 7, // 0111 + NoRotationOrReflection = 1, // 0001 + NoScale = 2, // 0010 + NoScaleOrReflection = 6, // 0110 + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/ConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/ConstraintData.cs index cc33dbc..0a85d6f 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/ConstraintData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/ConstraintData.cs @@ -28,34 +28,37 @@ *****************************************************************************/ using System; -using System.Collections.Generic; -namespace Spine4_1_00 { - /// The base class for all constraint datas. - public abstract class ConstraintData { - internal readonly string name; - internal int order; - internal bool skinRequired; +namespace Spine4_1_00 +{ + /// The base class for all constraint datas. + public abstract class ConstraintData + { + internal readonly string name; + internal int order; + internal bool skinRequired; - public ConstraintData (string name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; - } + public ConstraintData(string name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + } - /// The constraint's name, which is unique across all constraints in the skeleton of the same type. - public string Name { get { return name; } } + /// The constraint's name, which is unique across all constraints in the skeleton of the same type. + public string Name { get { return name; } } - ///The ordinal of this constraint for the order a skeleton's constraints will be applied by - /// . - public int Order { get { return order; } set { order = value; } } + ///The ordinal of this constraint for the order a skeleton's constraints will be applied by + /// . + public int Order { get { return order; } set { order = value; } } - ///When true, only updates this constraint if the contains - /// this constraint. - /// - public bool SkinRequired { get { return skinRequired; } set { skinRequired = value; } } + ///When true, only updates this constraint if the contains + /// this constraint. + /// + public bool SkinRequired { get { return skinRequired; } set { skinRequired = value; } } - override public string ToString () { - return name; - } - } + override public string ToString() + { + return name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Event.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Event.cs index 081735b..1861473 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Event.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Event.cs @@ -29,36 +29,40 @@ using System; -namespace Spine4_1_00 { - /// Stores the current pose values for an Event. - public class Event { - internal readonly EventData data; - internal readonly float time; - internal int intValue; - internal float floatValue; - internal string stringValue; - internal float volume; - internal float balance; +namespace Spine4_1_00 +{ + /// Stores the current pose values for an Event. + public class Event + { + internal readonly EventData data; + internal readonly float time; + internal int intValue; + internal float floatValue; + internal string stringValue; + internal float volume; + internal float balance; - public EventData Data { get { return data; } } - /// The animation time this event was keyed. - public float Time { get { return time; } } + public EventData Data { get { return data; } } + /// The animation time this event was keyed. + public float Time { get { return time; } } - public int Int { get { return intValue; } set { intValue = value; } } - public float Float { get { return floatValue; } set { floatValue = value; } } - public string String { get { return stringValue; } set { stringValue = value; } } + public int Int { get { return intValue; } set { intValue = value; } } + public float Float { get { return floatValue; } set { floatValue = value; } } + public string String { get { return stringValue; } set { stringValue = value; } } - public float Volume { get { return volume; } set { volume = value; } } - public float Balance { get { return balance; } set { balance = value; } } + public float Volume { get { return volume; } set { volume = value; } } + public float Balance { get { return balance; } set { balance = value; } } - public Event (float time, EventData data) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - this.time = time; - this.data = data; - } + public Event(float time, EventData data) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + this.time = time; + this.data = data; + } - override public string ToString () { - return this.data.Name; - } - } + override public string ToString() + { + return this.data.Name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/EventData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/EventData.cs index cad4b8c..09e39b9 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/EventData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/EventData.cs @@ -29,28 +29,32 @@ using System; -namespace Spine4_1_00 { - /// Stores the setup pose values for an Event. - public class EventData { - internal string name; +namespace Spine4_1_00 +{ + /// Stores the setup pose values for an Event. + public class EventData + { + internal string name; - /// The name of the event, which is unique across all events in the skeleton. - public string Name { get { return name; } } - public int Int { get; set; } - public float Float { get; set; } - public string @String { get; set; } + /// The name of the event, which is unique across all events in the skeleton. + public string Name { get { return name; } } + public int Int { get; set; } + public float Float { get; set; } + public string @String { get; set; } - public string AudioPath { get; set; } - public float Volume { get; set; } - public float Balance { get; set; } + public string AudioPath { get; set; } + public float Volume { get; set; } + public float Balance { get; set; } - public EventData (string name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; - } + public EventData(string name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + } - override public string ToString () { - return Name; - } - } + override public string ToString() + { + return Name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/ExposedList.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/ExposedList.cs index c750dec..c5bb174 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/ExposedList.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/ExposedList.cs @@ -35,603 +35,700 @@ using System.Collections.Generic; using System.Diagnostics; -namespace Spine4_1_00 { - [DebuggerDisplay("Count={Count}")] - public class ExposedList : IEnumerable { - public T[] Items; - public int Count; - private const int DefaultCapacity = 4; - private static readonly T[] EmptyArray = new T[0]; - private int version; - - public ExposedList () { - Items = EmptyArray; - } - - public ExposedList (IEnumerable collection) { - CheckCollection(collection); - - // initialize to needed size (if determinable) - ICollection c = collection as ICollection; - if (c == null) { - Items = EmptyArray; - AddEnumerable(collection); - } else { - Items = new T[c.Count]; - AddCollection(c); - } - } - - public ExposedList (int capacity) { - if (capacity < 0) - throw new ArgumentOutOfRangeException("capacity"); - Items = new T[capacity]; - } - - internal ExposedList (T[] data, int size) { - Items = data; - Count = size; - } - - public void Add (T item) { - // If we check to see if we need to grow before trying to grow - // we can speed things up by 25% - if (Count == Items.Length) - GrowIfNeeded(1); - Items[Count++] = item; - version++; - } - - public void GrowIfNeeded (int addedCount) { - int minimumSize = Count + addedCount; - if (minimumSize > Items.Length) - Capacity = Math.Max(Math.Max(Capacity * 2, DefaultCapacity), minimumSize); - } - - public ExposedList Resize (int newSize) { - int itemsLength = Items.Length; - var oldItems = Items; - if (newSize > itemsLength) { - Array.Resize(ref Items, newSize); - } else if (newSize < itemsLength) { - // Allow nulling of T reference type to allow GC. - for (int i = newSize; i < itemsLength; i++) - oldItems[i] = default(T); - } - Count = newSize; - return this; - } - - public void EnsureCapacity (int min) { - if (Items.Length < min) { - int newCapacity = Items.Length == 0 ? DefaultCapacity : Items.Length * 2; - //if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength; - if (newCapacity < min) newCapacity = min; - Capacity = newCapacity; - } - } - - private void CheckRange (int index, int count) { - if (index < 0) - throw new ArgumentOutOfRangeException("index"); - - if (count < 0) - throw new ArgumentOutOfRangeException("count"); - - if ((uint)index + (uint)count > (uint)Count) - throw new ArgumentException("index and count exceed length of list"); - } - - private void AddCollection (ICollection collection) { - int collectionCount = collection.Count; - if (collectionCount == 0) - return; - - GrowIfNeeded(collectionCount); - collection.CopyTo(Items, Count); - Count += collectionCount; - } - - private void AddEnumerable (IEnumerable enumerable) { - foreach (T t in enumerable) { - Add(t); - } - } - - // Additional overload provided because ExposedList only implements IEnumerable, - // leading to sub-optimal behavior: It grows multiple times as it assumes not - // to know the final size ahead of insertion. - public void AddRange (ExposedList list) { - CheckCollection(list); - - int collectionCount = list.Count; - if (collectionCount == 0) - return; - - GrowIfNeeded(collectionCount); - list.CopyTo(Items, Count); - Count += collectionCount; - - version++; - } - - public void AddRange (IEnumerable collection) { - CheckCollection(collection); - - ICollection c = collection as ICollection; - if (c != null) - AddCollection(c); - else - AddEnumerable(collection); - version++; - } - - public int BinarySearch (T item) { - return Array.BinarySearch(Items, 0, Count, item); - } - - public int BinarySearch (T item, IComparer comparer) { - return Array.BinarySearch(Items, 0, Count, item, comparer); - } - - public int BinarySearch (int index, int count, T item, IComparer comparer) { - CheckRange(index, count); - return Array.BinarySearch(Items, index, count, item, comparer); - } - - public void Clear (bool clearArray = true) { - if (clearArray) - Array.Clear(Items, 0, Items.Length); - - Count = 0; - version++; - } - - public bool Contains (T item) { - return Array.IndexOf(Items, item, 0, Count) != -1; - } - - public ExposedList ConvertAll (Converter converter) { - if (converter == null) - throw new ArgumentNullException("converter"); - ExposedList u = new ExposedList(Count); - u.Count = Count; - T[] items = Items; - TOutput[] uItems = u.Items; - for (int i = 0; i < Count; i++) - uItems[i] = converter(items[i]); - return u; - } - - public void CopyTo (T[] array) { - Array.Copy(Items, 0, array, 0, Count); - } - - public void CopyTo (T[] array, int arrayIndex) { - Array.Copy(Items, 0, array, arrayIndex, Count); - } - - public void CopyTo (int index, T[] array, int arrayIndex, int count) { - CheckRange(index, count); - Array.Copy(Items, index, array, arrayIndex, count); - } - - public bool Exists (Predicate match) { - CheckMatch(match); - return GetIndex(0, Count, match) != -1; - } - - public T Find (Predicate match) { - CheckMatch(match); - int i = GetIndex(0, Count, match); - return (i != -1) ? Items[i] : default(T); - } - - private static void CheckMatch (Predicate match) { - if (match == null) - throw new ArgumentNullException("match"); - } - - public ExposedList FindAll (Predicate match) { - CheckMatch(match); - return FindAllList(match); - } - - private ExposedList FindAllList (Predicate match) { - ExposedList results = new ExposedList(); - for (int i = 0; i < Count; i++) - if (match(Items[i])) - results.Add(Items[i]); - - return results; - } - - public int FindIndex (Predicate match) { - CheckMatch(match); - return GetIndex(0, Count, match); - } - - public int FindIndex (int startIndex, Predicate match) { - CheckMatch(match); - CheckIndex(startIndex); - return GetIndex(startIndex, Count - startIndex, match); - } - - public int FindIndex (int startIndex, int count, Predicate match) { - CheckMatch(match); - CheckRange(startIndex, count); - return GetIndex(startIndex, count, match); - } - - private int GetIndex (int startIndex, int count, Predicate match) { - int end = startIndex + count; - for (int i = startIndex; i < end; i++) - if (match(Items[i])) - return i; - - return -1; - } - - public T FindLast (Predicate match) { - CheckMatch(match); - int i = GetLastIndex(0, Count, match); - return i == -1 ? default(T) : Items[i]; - } - - public int FindLastIndex (Predicate match) { - CheckMatch(match); - return GetLastIndex(0, Count, match); - } - - public int FindLastIndex (int startIndex, Predicate match) { - CheckMatch(match); - CheckIndex(startIndex); - return GetLastIndex(0, startIndex + 1, match); - } - - public int FindLastIndex (int startIndex, int count, Predicate match) { - CheckMatch(match); - int start = startIndex - count + 1; - CheckRange(start, count); - return GetLastIndex(start, count, match); - } - - private int GetLastIndex (int startIndex, int count, Predicate match) { - // unlike FindLastIndex, takes regular params for search range - for (int i = startIndex + count; i != startIndex;) - if (match(Items[--i])) - return i; - return -1; - } - - public void ForEach (Action action) { - if (action == null) - throw new ArgumentNullException("action"); - for (int i = 0; i < Count; i++) - action(Items[i]); - } - - public Enumerator GetEnumerator () { - return new Enumerator(this); - } - - public ExposedList GetRange (int index, int count) { - CheckRange(index, count); - T[] tmpArray = new T[count]; - Array.Copy(Items, index, tmpArray, 0, count); - return new ExposedList(tmpArray, count); - } - - public int IndexOf (T item) { - return Array.IndexOf(Items, item, 0, Count); - } - - public int IndexOf (T item, int index) { - CheckIndex(index); - return Array.IndexOf(Items, item, index, Count - index); - } - - public int IndexOf (T item, int index, int count) { - if (index < 0) - throw new ArgumentOutOfRangeException("index"); - - if (count < 0) - throw new ArgumentOutOfRangeException("count"); - - if ((uint)index + (uint)count > (uint)Count) - throw new ArgumentOutOfRangeException("index and count exceed length of list"); - - return Array.IndexOf(Items, item, index, count); - } - - private void Shift (int start, int delta) { - if (delta < 0) - start -= delta; - - if (start < Count) - Array.Copy(Items, start, Items, start + delta, Count - start); - - Count += delta; - - if (delta < 0) - Array.Clear(Items, Count, -delta); - } - - private void CheckIndex (int index) { - if (index < 0 || (uint)index > (uint)Count) - throw new ArgumentOutOfRangeException("index"); - } - - public void Insert (int index, T item) { - CheckIndex(index); - if (Count == Items.Length) - GrowIfNeeded(1); - Shift(index, 1); - Items[index] = item; - version++; - } - - private void CheckCollection (IEnumerable collection) { - if (collection == null) - throw new ArgumentNullException("collection"); - } - - public void InsertRange (int index, IEnumerable collection) { - CheckCollection(collection); - CheckIndex(index); - if (collection == this) { - T[] buffer = new T[Count]; - CopyTo(buffer, 0); - GrowIfNeeded(Count); - Shift(index, buffer.Length); - Array.Copy(buffer, 0, Items, index, buffer.Length); - } else { - ICollection c = collection as ICollection; - if (c != null) - InsertCollection(index, c); - else - InsertEnumeration(index, collection); - } - version++; - } - - private void InsertCollection (int index, ICollection collection) { - int collectionCount = collection.Count; - GrowIfNeeded(collectionCount); - - Shift(index, collectionCount); - collection.CopyTo(Items, index); - } - - private void InsertEnumeration (int index, IEnumerable enumerable) { - foreach (T t in enumerable) - Insert(index++, t); - } - - public int LastIndexOf (T item) { - return Array.LastIndexOf(Items, item, Count - 1, Count); - } - - public int LastIndexOf (T item, int index) { - CheckIndex(index); - return Array.LastIndexOf(Items, item, index, index + 1); - } - - public int LastIndexOf (T item, int index, int count) { - if (index < 0) - throw new ArgumentOutOfRangeException("index", index, "index is negative"); - - if (count < 0) - throw new ArgumentOutOfRangeException("count", count, "count is negative"); - - if (index - count + 1 < 0) - throw new ArgumentOutOfRangeException("count", count, "count is too large"); - - return Array.LastIndexOf(Items, item, index, count); - } - - public bool Remove (T item) { - int loc = IndexOf(item); - if (loc != -1) - RemoveAt(loc); - - return loc != -1; - } - - public int RemoveAll (Predicate match) { - CheckMatch(match); - int i = 0; - int j = 0; - - // Find the first item to remove - for (i = 0; i < Count; i++) - if (match(Items[i])) - break; - - if (i == Count) - return 0; - - version++; - - // Remove any additional items - for (j = i + 1; j < Count; j++) { - if (!match(Items[j])) - Items[i++] = Items[j]; - } - if (j - i > 0) - Array.Clear(Items, i, j - i); - - Count = i; - return (j - i); - } - - public void RemoveAt (int index) { - if (index < 0 || (uint)index >= (uint)Count) - throw new ArgumentOutOfRangeException("index"); - Shift(index, -1); - Array.Clear(Items, Count, 1); - version++; - } - - // Spine Added Method - // Based on Stack.Pop(); https://referencesource.microsoft.com/#mscorlib/system/collections/stack.cs - /// Pops the last item of the list. If the list is empty, Pop throws an InvalidOperationException. - public T Pop () { - if (Count == 0) - throw new InvalidOperationException("List is empty. Nothing to pop."); - - int i = Count - 1; - T item = Items[i]; - Items[i] = default(T); - Count--; - version++; - return item; - } - - public void RemoveRange (int index, int count) { - CheckRange(index, count); - if (count > 0) { - Shift(index, -count); - Array.Clear(Items, Count, count); - version++; - } - } - - public void Reverse () { - Array.Reverse(Items, 0, Count); - version++; - } - - public void Reverse (int index, int count) { - CheckRange(index, count); - Array.Reverse(Items, index, count); - version++; - } - - public void Sort () { - Array.Sort(Items, 0, Count, Comparer.Default); - version++; - } - - public void Sort (IComparer comparer) { - Array.Sort(Items, 0, Count, comparer); - version++; - } - - public void Sort (Comparison comparison) { - Array.Sort(Items, comparison); - version++; - } - - public void Sort (int index, int count, IComparer comparer) { - CheckRange(index, count); - Array.Sort(Items, index, count, comparer); - version++; - } - - public T[] ToArray () { - T[] t = new T[Count]; - Array.Copy(Items, t, Count); - - return t; - } - - public void TrimExcess () { - Capacity = Count; - } - - public bool TrueForAll (Predicate match) { - CheckMatch(match); - - for (int i = 0; i < Count; i++) - if (!match(Items[i])) - return false; - - return true; - } - - public int Capacity { - get { - return Items.Length; - } - set { - if ((uint)value < (uint)Count) - throw new ArgumentOutOfRangeException(); - - Array.Resize(ref Items, value); - } - } - - #region Interface implementations. - - IEnumerator IEnumerable.GetEnumerator () { - return GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator () { - return GetEnumerator(); - } - - #endregion - - public struct Enumerator : IEnumerator, IDisposable { - private ExposedList l; - private int next; - private int ver; - private T current; - - internal Enumerator (ExposedList l) - : this() { - this.l = l; - ver = l.version; - } - - public void Dispose () { - l = null; - } - - private void VerifyState () { - if (l == null) - throw new ObjectDisposedException(GetType().FullName); - if (ver != l.version) - throw new InvalidOperationException( - "Collection was modified; enumeration operation may not execute."); - } - - public bool MoveNext () { - VerifyState(); - - if (next < 0) - return false; - - if (next < l.Count) { - current = l.Items[next++]; - return true; - } - - next = -1; - return false; - } - - public T Current { - get { - return current; - } - } - - void IEnumerator.Reset () { - VerifyState(); - next = 0; - } - - object IEnumerator.Current { - get { - VerifyState(); - if (next <= 0) - throw new InvalidOperationException(); - return current; - } - } - } - } +namespace Spine4_1_00 +{ + [DebuggerDisplay("Count={Count}")] + public class ExposedList : IEnumerable + { + public T[] Items; + public int Count; + private const int DefaultCapacity = 4; + private static readonly T[] EmptyArray = new T[0]; + private int version; + + public ExposedList() + { + Items = EmptyArray; + } + + public ExposedList(IEnumerable collection) + { + CheckCollection(collection); + + // initialize to needed size (if determinable) + ICollection c = collection as ICollection; + if (c == null) + { + Items = EmptyArray; + AddEnumerable(collection); + } + else + { + Items = new T[c.Count]; + AddCollection(c); + } + } + + public ExposedList(int capacity) + { + if (capacity < 0) + throw new ArgumentOutOfRangeException("capacity"); + Items = new T[capacity]; + } + + internal ExposedList(T[] data, int size) + { + Items = data; + Count = size; + } + + public void Add(T item) + { + // If we check to see if we need to grow before trying to grow + // we can speed things up by 25% + if (Count == Items.Length) + GrowIfNeeded(1); + Items[Count++] = item; + version++; + } + + public void GrowIfNeeded(int addedCount) + { + int minimumSize = Count + addedCount; + if (minimumSize > Items.Length) + Capacity = Math.Max(Math.Max(Capacity * 2, DefaultCapacity), minimumSize); + } + + public ExposedList Resize(int newSize) + { + int itemsLength = Items.Length; + var oldItems = Items; + if (newSize > itemsLength) + { + Array.Resize(ref Items, newSize); + } + else if (newSize < itemsLength) + { + // Allow nulling of T reference type to allow GC. + for (int i = newSize; i < itemsLength; i++) + oldItems[i] = default(T); + } + Count = newSize; + return this; + } + + public void EnsureCapacity(int min) + { + if (Items.Length < min) + { + int newCapacity = Items.Length == 0 ? DefaultCapacity : Items.Length * 2; + //if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength; + if (newCapacity < min) newCapacity = min; + Capacity = newCapacity; + } + } + + private void CheckRange(int index, int count) + { + if (index < 0) + throw new ArgumentOutOfRangeException("index"); + + if (count < 0) + throw new ArgumentOutOfRangeException("count"); + + if ((uint)index + (uint)count > (uint)Count) + throw new ArgumentException("index and count exceed length of list"); + } + + private void AddCollection(ICollection collection) + { + int collectionCount = collection.Count; + if (collectionCount == 0) + return; + + GrowIfNeeded(collectionCount); + collection.CopyTo(Items, Count); + Count += collectionCount; + } + + private void AddEnumerable(IEnumerable enumerable) + { + foreach (T t in enumerable) + { + Add(t); + } + } + + // Additional overload provided because ExposedList only implements IEnumerable, + // leading to sub-optimal behavior: It grows multiple times as it assumes not + // to know the final size ahead of insertion. + public void AddRange(ExposedList list) + { + CheckCollection(list); + + int collectionCount = list.Count; + if (collectionCount == 0) + return; + + GrowIfNeeded(collectionCount); + list.CopyTo(Items, Count); + Count += collectionCount; + + version++; + } + + public void AddRange(IEnumerable collection) + { + CheckCollection(collection); + + ICollection c = collection as ICollection; + if (c != null) + AddCollection(c); + else + AddEnumerable(collection); + version++; + } + + public int BinarySearch(T item) + { + return Array.BinarySearch(Items, 0, Count, item); + } + + public int BinarySearch(T item, IComparer comparer) + { + return Array.BinarySearch(Items, 0, Count, item, comparer); + } + + public int BinarySearch(int index, int count, T item, IComparer comparer) + { + CheckRange(index, count); + return Array.BinarySearch(Items, index, count, item, comparer); + } + + public void Clear(bool clearArray = true) + { + if (clearArray) + Array.Clear(Items, 0, Items.Length); + + Count = 0; + version++; + } + + public bool Contains(T item) + { + return Array.IndexOf(Items, item, 0, Count) != -1; + } + + public ExposedList ConvertAll(Converter converter) + { + if (converter == null) + throw new ArgumentNullException("converter"); + ExposedList u = new ExposedList(Count); + u.Count = Count; + T[] items = Items; + TOutput[] uItems = u.Items; + for (int i = 0; i < Count; i++) + uItems[i] = converter(items[i]); + return u; + } + + public void CopyTo(T[] array) + { + Array.Copy(Items, 0, array, 0, Count); + } + + public void CopyTo(T[] array, int arrayIndex) + { + Array.Copy(Items, 0, array, arrayIndex, Count); + } + + public void CopyTo(int index, T[] array, int arrayIndex, int count) + { + CheckRange(index, count); + Array.Copy(Items, index, array, arrayIndex, count); + } + + public bool Exists(Predicate match) + { + CheckMatch(match); + return GetIndex(0, Count, match) != -1; + } + + public T Find(Predicate match) + { + CheckMatch(match); + int i = GetIndex(0, Count, match); + return (i != -1) ? Items[i] : default(T); + } + + private static void CheckMatch(Predicate match) + { + if (match == null) + throw new ArgumentNullException("match"); + } + + public ExposedList FindAll(Predicate match) + { + CheckMatch(match); + return FindAllList(match); + } + + private ExposedList FindAllList(Predicate match) + { + ExposedList results = new ExposedList(); + for (int i = 0; i < Count; i++) + if (match(Items[i])) + results.Add(Items[i]); + + return results; + } + + public int FindIndex(Predicate match) + { + CheckMatch(match); + return GetIndex(0, Count, match); + } + + public int FindIndex(int startIndex, Predicate match) + { + CheckMatch(match); + CheckIndex(startIndex); + return GetIndex(startIndex, Count - startIndex, match); + } + + public int FindIndex(int startIndex, int count, Predicate match) + { + CheckMatch(match); + CheckRange(startIndex, count); + return GetIndex(startIndex, count, match); + } + + private int GetIndex(int startIndex, int count, Predicate match) + { + int end = startIndex + count; + for (int i = startIndex; i < end; i++) + if (match(Items[i])) + return i; + + return -1; + } + + public T FindLast(Predicate match) + { + CheckMatch(match); + int i = GetLastIndex(0, Count, match); + return i == -1 ? default(T) : Items[i]; + } + + public int FindLastIndex(Predicate match) + { + CheckMatch(match); + return GetLastIndex(0, Count, match); + } + + public int FindLastIndex(int startIndex, Predicate match) + { + CheckMatch(match); + CheckIndex(startIndex); + return GetLastIndex(0, startIndex + 1, match); + } + + public int FindLastIndex(int startIndex, int count, Predicate match) + { + CheckMatch(match); + int start = startIndex - count + 1; + CheckRange(start, count); + return GetLastIndex(start, count, match); + } + + private int GetLastIndex(int startIndex, int count, Predicate match) + { + // unlike FindLastIndex, takes regular params for search range + for (int i = startIndex + count; i != startIndex;) + if (match(Items[--i])) + return i; + return -1; + } + + public void ForEach(Action action) + { + if (action == null) + throw new ArgumentNullException("action"); + for (int i = 0; i < Count; i++) + action(Items[i]); + } + + public Enumerator GetEnumerator() + { + return new Enumerator(this); + } + + public ExposedList GetRange(int index, int count) + { + CheckRange(index, count); + T[] tmpArray = new T[count]; + Array.Copy(Items, index, tmpArray, 0, count); + return new ExposedList(tmpArray, count); + } + + public int IndexOf(T item) + { + return Array.IndexOf(Items, item, 0, Count); + } + + public int IndexOf(T item, int index) + { + CheckIndex(index); + return Array.IndexOf(Items, item, index, Count - index); + } + + public int IndexOf(T item, int index, int count) + { + if (index < 0) + throw new ArgumentOutOfRangeException("index"); + + if (count < 0) + throw new ArgumentOutOfRangeException("count"); + + if ((uint)index + (uint)count > (uint)Count) + throw new ArgumentOutOfRangeException("index and count exceed length of list"); + + return Array.IndexOf(Items, item, index, count); + } + + private void Shift(int start, int delta) + { + if (delta < 0) + start -= delta; + + if (start < Count) + Array.Copy(Items, start, Items, start + delta, Count - start); + + Count += delta; + + if (delta < 0) + Array.Clear(Items, Count, -delta); + } + + private void CheckIndex(int index) + { + if (index < 0 || (uint)index > (uint)Count) + throw new ArgumentOutOfRangeException("index"); + } + + public void Insert(int index, T item) + { + CheckIndex(index); + if (Count == Items.Length) + GrowIfNeeded(1); + Shift(index, 1); + Items[index] = item; + version++; + } + + private void CheckCollection(IEnumerable collection) + { + if (collection == null) + throw new ArgumentNullException("collection"); + } + + public void InsertRange(int index, IEnumerable collection) + { + CheckCollection(collection); + CheckIndex(index); + if (collection == this) + { + T[] buffer = new T[Count]; + CopyTo(buffer, 0); + GrowIfNeeded(Count); + Shift(index, buffer.Length); + Array.Copy(buffer, 0, Items, index, buffer.Length); + } + else + { + ICollection c = collection as ICollection; + if (c != null) + InsertCollection(index, c); + else + InsertEnumeration(index, collection); + } + version++; + } + + private void InsertCollection(int index, ICollection collection) + { + int collectionCount = collection.Count; + GrowIfNeeded(collectionCount); + + Shift(index, collectionCount); + collection.CopyTo(Items, index); + } + + private void InsertEnumeration(int index, IEnumerable enumerable) + { + foreach (T t in enumerable) + Insert(index++, t); + } + + public int LastIndexOf(T item) + { + return Array.LastIndexOf(Items, item, Count - 1, Count); + } + + public int LastIndexOf(T item, int index) + { + CheckIndex(index); + return Array.LastIndexOf(Items, item, index, index + 1); + } + + public int LastIndexOf(T item, int index, int count) + { + if (index < 0) + throw new ArgumentOutOfRangeException("index", index, "index is negative"); + + if (count < 0) + throw new ArgumentOutOfRangeException("count", count, "count is negative"); + + if (index - count + 1 < 0) + throw new ArgumentOutOfRangeException("count", count, "count is too large"); + + return Array.LastIndexOf(Items, item, index, count); + } + + public bool Remove(T item) + { + int loc = IndexOf(item); + if (loc != -1) + RemoveAt(loc); + + return loc != -1; + } + + public int RemoveAll(Predicate match) + { + CheckMatch(match); + int i = 0; + int j = 0; + + // Find the first item to remove + for (i = 0; i < Count; i++) + if (match(Items[i])) + break; + + if (i == Count) + return 0; + + version++; + + // Remove any additional items + for (j = i + 1; j < Count; j++) + { + if (!match(Items[j])) + Items[i++] = Items[j]; + } + if (j - i > 0) + Array.Clear(Items, i, j - i); + + Count = i; + return (j - i); + } + + public void RemoveAt(int index) + { + if (index < 0 || (uint)index >= (uint)Count) + throw new ArgumentOutOfRangeException("index"); + Shift(index, -1); + Array.Clear(Items, Count, 1); + version++; + } + + // Spine Added Method + // Based on Stack.Pop(); https://referencesource.microsoft.com/#mscorlib/system/collections/stack.cs + /// Pops the last item of the list. If the list is empty, Pop throws an InvalidOperationException. + public T Pop() + { + if (Count == 0) + throw new InvalidOperationException("List is empty. Nothing to pop."); + + int i = Count - 1; + T item = Items[i]; + Items[i] = default(T); + Count--; + version++; + return item; + } + + public void RemoveRange(int index, int count) + { + CheckRange(index, count); + if (count > 0) + { + Shift(index, -count); + Array.Clear(Items, Count, count); + version++; + } + } + + public void Reverse() + { + Array.Reverse(Items, 0, Count); + version++; + } + + public void Reverse(int index, int count) + { + CheckRange(index, count); + Array.Reverse(Items, index, count); + version++; + } + + public void Sort() + { + Array.Sort(Items, 0, Count, Comparer.Default); + version++; + } + + public void Sort(IComparer comparer) + { + Array.Sort(Items, 0, Count, comparer); + version++; + } + + public void Sort(Comparison comparison) + { + Array.Sort(Items, comparison); + version++; + } + + public void Sort(int index, int count, IComparer comparer) + { + CheckRange(index, count); + Array.Sort(Items, index, count, comparer); + version++; + } + + public T[] ToArray() + { + T[] t = new T[Count]; + Array.Copy(Items, t, Count); + + return t; + } + + public void TrimExcess() + { + Capacity = Count; + } + + public bool TrueForAll(Predicate match) + { + CheckMatch(match); + + for (int i = 0; i < Count; i++) + if (!match(Items[i])) + return false; + + return true; + } + + public int Capacity + { + get + { + return Items.Length; + } + set + { + if ((uint)value < (uint)Count) + throw new ArgumentOutOfRangeException(); + + Array.Resize(ref Items, value); + } + } + + #region Interface implementations. + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion + + public struct Enumerator : IEnumerator, IDisposable + { + private ExposedList l; + private int next; + private int ver; + private T current; + + internal Enumerator(ExposedList l) + : this() + { + this.l = l; + ver = l.version; + } + + public void Dispose() + { + l = null; + } + + private void VerifyState() + { + if (l == null) + throw new ObjectDisposedException(GetType().FullName); + if (ver != l.version) + throw new InvalidOperationException( + "Collection was modified; enumeration operation may not execute."); + } + + public bool MoveNext() + { + VerifyState(); + + if (next < 0) + return false; + + if (next < l.Count) + { + current = l.Items[next++]; + return true; + } + + next = -1; + return false; + } + + public T Current + { + get + { + return current; + } + } + + void IEnumerator.Reset() + { + VerifyState(); + next = 0; + } + + object IEnumerator.Current + { + get + { + VerifyState(); + if (next <= 0) + throw new InvalidOperationException(); + return current; + } + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/IUpdatable.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/IUpdatable.cs index 5e3a80e..4327774 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/IUpdatable.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/IUpdatable.cs @@ -27,16 +27,18 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine4_1_00 { +namespace Spine4_1_00 +{ - ///The interface for items updated by . - public interface IUpdatable { - void Update (); + ///The interface for items updated by . + public interface IUpdatable + { + void Update(); - ///Returns false when this item has not been updated because a skin is required and the active - /// skin does not contain this item. - /// - /// - bool Active { get; } - } + ///Returns false when this item has not been updated because a skin is required and the active + /// skin does not contain this item. + /// + /// + bool Active { get; } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/IkConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/IkConstraint.cs index 4fbb645..6cff128 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/IkConstraint.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/IkConstraint.cs @@ -29,340 +29,392 @@ using System; -namespace Spine4_1_00 { - /// - /// - /// Stores the current pose for an IK constraint. An IK constraint adjusts the rotation of 1 or 2 constrained bones so the tip of - /// the last bone is as close to the target bone as possible. - /// - /// See IK constraints in the Spine User Guide. - /// - public class IkConstraint : IUpdatable { - internal readonly IkConstraintData data; - internal readonly ExposedList bones = new ExposedList(); - internal Bone target; - internal int bendDirection; - internal bool compress, stretch; - internal float mix = 1, softness; +namespace Spine4_1_00 +{ + /// + /// + /// Stores the current pose for an IK constraint. An IK constraint adjusts the rotation of 1 or 2 constrained bones so the tip of + /// the last bone is as close to the target bone as possible. + /// + /// See IK constraints in the Spine User Guide. + /// + public class IkConstraint : IUpdatable + { + internal readonly IkConstraintData data; + internal readonly ExposedList bones = new ExposedList(); + internal Bone target; + internal int bendDirection; + internal bool compress, stretch; + internal float mix = 1, softness; - internal bool active; + internal bool active; - public IkConstraint (IkConstraintData data, Skeleton skeleton) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.data = data; - mix = data.mix; - softness = data.softness; - bendDirection = data.bendDirection; - compress = data.compress; - stretch = data.stretch; + public IkConstraint(IkConstraintData data, Skeleton skeleton) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + mix = data.mix; + softness = data.softness; + bendDirection = data.bendDirection; + compress = data.compress; + stretch = data.stretch; - bones = new ExposedList(data.bones.Count); - foreach (BoneData boneData in data.bones) - bones.Add(skeleton.bones.Items[boneData.index]); - target = skeleton.bones.Items[data.target.index]; - } + bones = new ExposedList(data.bones.Count); + foreach (BoneData boneData in data.bones) + bones.Add(skeleton.bones.Items[boneData.index]); + target = skeleton.bones.Items[data.target.index]; + } - /// Copy constructor. - public IkConstraint (IkConstraint constraint, Skeleton skeleton) { - if (constraint == null) throw new ArgumentNullException("constraint cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); - data = constraint.data; - bones = new ExposedList(constraint.Bones.Count); - foreach (Bone bone in constraint.Bones) - bones.Add(skeleton.Bones.Items[bone.data.index]); - target = skeleton.Bones.Items[constraint.target.data.index]; - mix = constraint.mix; - softness = constraint.softness; - bendDirection = constraint.bendDirection; - compress = constraint.compress; - stretch = constraint.stretch; - } + /// Copy constructor. + public IkConstraint(IkConstraint constraint, Skeleton skeleton) + { + if (constraint == null) throw new ArgumentNullException("constraint cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); + data = constraint.data; + bones = new ExposedList(constraint.Bones.Count); + foreach (Bone bone in constraint.Bones) + bones.Add(skeleton.Bones.Items[bone.data.index]); + target = skeleton.Bones.Items[constraint.target.data.index]; + mix = constraint.mix; + softness = constraint.softness; + bendDirection = constraint.bendDirection; + compress = constraint.compress; + stretch = constraint.stretch; + } - public void Update () { - if (mix == 0) return; - Bone target = this.target; - var bones = this.bones.Items; - switch (this.bones.Count) { - case 1: - Apply(bones[0], target.worldX, target.worldY, compress, stretch, data.uniform, mix); - break; - case 2: - Apply(bones[0], bones[1], target.worldX, target.worldY, bendDirection, stretch, data.uniform, softness, mix); - break; - } - } + public void Update() + { + if (mix == 0) return; + Bone target = this.target; + var bones = this.bones.Items; + switch (this.bones.Count) + { + case 1: + Apply(bones[0], target.worldX, target.worldY, compress, stretch, data.uniform, mix); + break; + case 2: + Apply(bones[0], bones[1], target.worldX, target.worldY, bendDirection, stretch, data.uniform, softness, mix); + break; + } + } - /// The bones that will be modified by this IK constraint. - public ExposedList Bones { - get { return bones; } - } + /// The bones that will be modified by this IK constraint. + public ExposedList Bones + { + get { return bones; } + } - /// The bone that is the IK target. - public Bone Target { - get { return target; } - set { target = value; } - } + /// The bone that is the IK target. + public Bone Target + { + get { return target; } + set { target = value; } + } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. - /// - /// For two bone IK: if the parent bone has local nonuniform scale, the child bone's local Y translation is set to 0. - /// - public float Mix { - get { return mix; } - set { mix = value; } - } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. + /// + /// For two bone IK: if the parent bone has local nonuniform scale, the child bone's local Y translation is set to 0. + /// + public float Mix + { + get { return mix; } + set { mix = value; } + } - /// For two bone IK, the target bone's distance from the maximum reach of the bones where rotation begins to slow. The bones - /// will not straighten completely until the target is this far out of range. - public float Softness { - get { return softness; } - set { softness = value; } - } + /// For two bone IK, the target bone's distance from the maximum reach of the bones where rotation begins to slow. The bones + /// will not straighten completely until the target is this far out of range. + public float Softness + { + get { return softness; } + set { softness = value; } + } - /// For two bone IK, controls the bend direction of the IK bones, either 1 or -1. - public int BendDirection { - get { return bendDirection; } - set { bendDirection = value; } - } + /// For two bone IK, controls the bend direction of the IK bones, either 1 or -1. + public int BendDirection + { + get { return bendDirection; } + set { bendDirection = value; } + } - /// For one bone IK, when true and the target is too close, the bone is scaled to reach it. - public bool Compress { - get { return compress; } - set { compress = value; } - } + /// For one bone IK, when true and the target is too close, the bone is scaled to reach it. + public bool Compress + { + get { return compress; } + set { compress = value; } + } - /// When true and the target is out of range, the parent bone is scaled to reach it. - /// - /// For two bone IK: 1) the child bone's local Y translation is set to 0, - /// 2) stretch is not applied if is > 0, - /// and 3) if the parent bone has local nonuniform scale, stretch is not applied. - /// - public bool Stretch { - get { return stretch; } - set { stretch = value; } - } + /// When true and the target is out of range, the parent bone is scaled to reach it. + /// + /// For two bone IK: 1) the child bone's local Y translation is set to 0, + /// 2) stretch is not applied if is > 0, + /// and 3) if the parent bone has local nonuniform scale, stretch is not applied. + /// + public bool Stretch + { + get { return stretch; } + set { stretch = value; } + } - public bool Active { - get { return active; } - } + public bool Active + { + get { return active; } + } - /// The IK constraint's setup pose data. - public IkConstraintData Data { - get { return data; } - } + /// The IK constraint's setup pose data. + public IkConstraintData Data + { + get { return data; } + } - override public string ToString () { - return data.name; - } + override public string ToString() + { + return data.name; + } - /// Applies 1 bone IK. The target is specified in the world coordinate system. - static public void Apply (Bone bone, float targetX, float targetY, bool compress, bool stretch, bool uniform, - float alpha) { - if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); - Bone p = bone.parent; + /// Applies 1 bone IK. The target is specified in the world coordinate system. + static public void Apply(Bone bone, float targetX, float targetY, bool compress, bool stretch, bool uniform, + float alpha) + { + if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); + Bone p = bone.parent; - float pa = p.a, pb = p.b, pc = p.c, pd = p.d; - float rotationIK = -bone.ashearX - bone.arotation; - float tx = 0, ty = 0; + 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.data.transformMode) { - case TransformMode.OnlyTranslation: - tx = targetX - bone.worldX; - ty = targetY - bone.worldY; - break; - case TransformMode.NoRotationOrReflection: { - float s = Math.Abs(pa * pd - pb * pc) / (pa * pa + pc * pc); - float sa = pa / bone.skeleton.ScaleX; - float sc = pc / bone.skeleton.ScaleY; - pb = -sc * s * bone.skeleton.ScaleX; - pd = sa * s * bone.skeleton.ScaleY; - rotationIK += (float)Math.Atan2(sc, sa) * MathUtils.RadDeg; - goto default; // Fall through. - } - default: { - float x = targetX - p.worldX, y = targetY - p.worldY; - float d = pa * pd - pb * pc; - tx = (x * pd - y * pb) / d - bone.ax; - ty = (y * pa - x * pc) / d - bone.ay; - break; - } - } + switch (bone.data.transformMode) + { + case TransformMode.OnlyTranslation: + tx = targetX - bone.worldX; + ty = targetY - bone.worldY; + break; + case TransformMode.NoRotationOrReflection: + { + float s = Math.Abs(pa * pd - pb * pc) / (pa * pa + pc * pc); + float sa = pa / bone.skeleton.ScaleX; + float sc = pc / bone.skeleton.ScaleY; + pb = -sc * s * bone.skeleton.ScaleX; + pd = sa * s * bone.skeleton.ScaleY; + rotationIK += (float)Math.Atan2(sc, sa) * MathUtils.RadDeg; + goto default; // Fall through. + } + default: + { + float x = targetX - p.worldX, y = targetY - p.worldY; + float d = pa * pd - pb * pc; + tx = (x * pd - y * pb) / d - bone.ax; + ty = (y * pa - x * pc) / d - bone.ay; + break; + } + } - rotationIK += (float)Math.Atan2(ty, tx) * MathUtils.RadDeg; - if (bone.ascaleX < 0) rotationIK += 180; - if (rotationIK > 180) - rotationIK -= 360; - else if (rotationIK < -180) // - rotationIK += 360; + rotationIK += (float)Math.Atan2(ty, tx) * MathUtils.RadDeg; + if (bone.ascaleX < 0) rotationIK += 180; + if (rotationIK > 180) + rotationIK -= 360; + else if (rotationIK < -180) // + rotationIK += 360; - float sx = bone.ascaleX, sy = bone.ascaleY; - if (compress || stretch) { - switch (bone.data.transformMode) { - case TransformMode.NoScale: - case TransformMode.NoScaleOrReflection: - tx = targetX - bone.worldX; - ty = targetY - bone.worldY; - break; - } - float b = bone.data.length * sx, dd = (float)Math.Sqrt(tx * tx + ty * ty); - if ((compress && dd < b) || (stretch && dd > b) && b > 0.0001f) { - float s = (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); - } + float sx = bone.ascaleX, sy = bone.ascaleY; + if (compress || stretch) + { + switch (bone.data.transformMode) + { + case TransformMode.NoScale: + case TransformMode.NoScaleOrReflection: + tx = targetX - bone.worldX; + ty = targetY - bone.worldY; + break; + } + float b = bone.data.length * sx, dd = (float)Math.Sqrt(tx * tx + ty * ty); + if ((compress && dd < b) || (stretch && dd > b) && b > 0.0001f) + { + float s = (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); + } - /// Applies 2 bone IK. The target is specified in the world coordinate system. - /// A direct descendant of the parent bone. - static public void Apply (Bone parent, Bone child, float targetX, float targetY, int bendDir, bool stretch, bool uniform, - float softness, float alpha) { - if (parent == null) throw new ArgumentNullException("parent", "parent cannot be null."); - if (child == null) throw new ArgumentNullException("child", "child cannot be null."); - float px = parent.ax, py = parent.ay, psx = parent.ascaleX, psy = parent.ascaleY, sx = psx, sy = psy, csx = child.ascaleX; - int os1, os2, s2; - if (psx < 0) { - psx = -psx; - os1 = 180; - s2 = -1; - } else { - os1 = 0; - s2 = 1; - } - if (psy < 0) { - psy = -psy; - s2 = -s2; - } - if (csx < 0) { - csx = -csx; - os2 = 180; - } else - os2 = 0; - float cx = child.ax, cy, cwx, cwy, a = parent.a, b = parent.b, c = parent.c, d = parent.d; - bool u = Math.Abs(psx - psy) <= 0.0001f; - if (!u || stretch) { - cy = 0; - cwx = a * cx + parent.worldX; - cwy = c * cx + parent.worldY; - } else { - cy = child.ay; - cwx = a * cx + b * cy + parent.worldX; - cwy = c * cx + d * cy + parent.worldY; - } - Bone pp = parent.parent; - a = pp.a; - b = pp.b; - c = pp.c; - d = pp.d; - float id = 1 / (a * d - b * c), x = cwx - pp.worldX, y = cwy - pp.worldY; - float dx = (x * d - y * b) * id - px, dy = (y * a - x * c) * id - py; - float l1 = (float)Math.Sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2; - if (l1 < 0.0001f) { - 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; - float tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py; - float dd = tx * tx + ty * ty; - if (softness != 0) { - softness *= psx * (csx + 1) * 0.5f; - float td = (float)Math.Sqrt(dd), sd = td - l1 - l2 * psx + softness; - if (sd > 0) { - float p = Math.Min(1, sd / (softness * 2)) - 1; - p = (sd - softness * (1 - p * p)) / td; - tx -= p * tx; - ty -= p * ty; - dd = tx * tx + ty * ty; - } - } - if (u) { - l2 *= psx; - float cos = (dd - l1 * l1 - l2 * l2) / (2 * l1 * l2); - if (cos < -1) { - cos = -1; - a2 = MathUtils.PI * bendDir; - } else if (cos > 1) { - cos = 1; - a2 = 0; - if (stretch) { - a = ((float)Math.Sqrt(dd) / (l1 + l2) - 1) * alpha + 1; - sx *= a; - if (uniform) sy *= a; - } - } else - a2 = (float)Math.Acos(cos) * bendDir; - a = l1 + l2 * cos; - b = l2 * (float)Math.Sin(a2); - a1 = (float)Math.Atan2(ty * a - tx * b, tx * a + ty * b); - } else { - a = psx * l2; - b = psy * l2; - float aa = a * a, bb = b * b, ta = (float)Math.Atan2(ty, tx); - c = bb * l1 * l1 + aa * dd - aa * bb; - float c1 = -2 * bb * l1, c2 = bb - aa; - d = c1 * c1 - 4 * c2 * c; - if (d >= 0) { - float q = (float)Math.Sqrt(d); - if (c1 < 0) q = -q; - q = -(c1 + q) * 0.5f; - float r0 = q / c2, r1 = c / q; - float r = Math.Abs(r0) < Math.Abs(r1) ? r0 : r1; - if (r * r <= dd) { - y = (float)Math.Sqrt(dd - r * r) * bendDir; - a1 = ta - (float)Math.Atan2(y, r); - a2 = (float)Math.Atan2(y / psy, (r - l1) / psx); - goto break_outer; // break outer; - } - } - float minAngle = MathUtils.PI, minX = l1 - a, minDist = minX * minX, minY = 0; - float maxAngle = 0, maxX = l1 + a, maxDist = maxX * maxX, maxY = 0; - c = -a * l1 / (aa - bb); - if (c >= -1 && c <= 1) { - c = (float)Math.Acos(c); - x = a * (float)Math.Cos(c) + l1; - y = b * (float)Math.Sin(c); - d = x * x + y * y; - if (d < minDist) { - minAngle = c; - minDist = d; - minX = x; - minY = y; - } - if (d > maxDist) { - maxAngle = c; - maxDist = d; - maxX = x; - maxY = y; - } - } - if (dd <= (minDist + maxDist) * 0.5f) { - a1 = ta - (float)Math.Atan2(minY * bendDir, minX); - a2 = minAngle * bendDir; - } else { - a1 = ta - (float)Math.Atan2(maxY * bendDir, maxX); - a2 = maxAngle * bendDir; - } - } - break_outer: - float os = (float)Math.Atan2(cy, cx) * s2; - float rotation = parent.arotation; - a1 = (a1 - os) * MathUtils.RadDeg + os1 - rotation; - if (a1 > 180) - a1 -= 360; - else if (a1 < -180) - a1 += 360; - parent.UpdateWorldTransform(px, py, rotation + a1 * alpha, sx, sy, 0, 0); - rotation = child.arotation; - a2 = ((a2 + os) * MathUtils.RadDeg - child.ashearX) * s2 + os2 - rotation; - if (a2 > 180) - a2 -= 360; - else if (a2 < -180) - a2 += 360; - child.UpdateWorldTransform(cx, cy, rotation + a2 * alpha, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY); - } - } + /// Applies 2 bone IK. The target is specified in the world coordinate system. + /// A direct descendant of the parent bone. + static public void Apply(Bone parent, Bone child, float targetX, float targetY, int bendDir, bool stretch, bool uniform, + float softness, float alpha) + { + if (parent == null) throw new ArgumentNullException("parent", "parent cannot be null."); + if (child == null) throw new ArgumentNullException("child", "child cannot be null."); + float px = parent.ax, py = parent.ay, psx = parent.ascaleX, psy = parent.ascaleY, sx = psx, sy = psy, csx = child.ascaleX; + int os1, os2, s2; + if (psx < 0) + { + psx = -psx; + os1 = 180; + s2 = -1; + } + else + { + os1 = 0; + s2 = 1; + } + if (psy < 0) + { + psy = -psy; + s2 = -s2; + } + if (csx < 0) + { + csx = -csx; + os2 = 180; + } + else + os2 = 0; + float cx = child.ax, cy, cwx, cwy, a = parent.a, b = parent.b, c = parent.c, d = parent.d; + bool u = Math.Abs(psx - psy) <= 0.0001f; + if (!u || stretch) + { + cy = 0; + cwx = a * cx + parent.worldX; + cwy = c * cx + parent.worldY; + } + else + { + cy = child.ay; + cwx = a * cx + b * cy + parent.worldX; + cwy = c * cx + d * cy + parent.worldY; + } + Bone pp = parent.parent; + a = pp.a; + b = pp.b; + c = pp.c; + d = pp.d; + float id = 1 / (a * d - b * c), x = cwx - pp.worldX, y = cwy - pp.worldY; + float dx = (x * d - y * b) * id - px, dy = (y * a - x * c) * id - py; + float l1 = (float)Math.Sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2; + if (l1 < 0.0001f) + { + 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; + float tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py; + float dd = tx * tx + ty * ty; + if (softness != 0) + { + softness *= psx * (csx + 1) * 0.5f; + float td = (float)Math.Sqrt(dd), sd = td - l1 - l2 * psx + softness; + if (sd > 0) + { + float p = Math.Min(1, sd / (softness * 2)) - 1; + p = (sd - softness * (1 - p * p)) / td; + tx -= p * tx; + ty -= p * ty; + dd = tx * tx + ty * ty; + } + } + if (u) + { + l2 *= psx; + float cos = (dd - l1 * l1 - l2 * l2) / (2 * l1 * l2); + if (cos < -1) + { + cos = -1; + a2 = MathUtils.PI * bendDir; + } + else if (cos > 1) + { + cos = 1; + a2 = 0; + if (stretch) + { + a = ((float)Math.Sqrt(dd) / (l1 + l2) - 1) * alpha + 1; + sx *= a; + if (uniform) sy *= a; + } + } + else + a2 = (float)Math.Acos(cos) * bendDir; + a = l1 + l2 * cos; + b = l2 * (float)Math.Sin(a2); + a1 = (float)Math.Atan2(ty * a - tx * b, tx * a + ty * b); + } + else + { + a = psx * l2; + b = psy * l2; + float aa = a * a, bb = b * b, ta = (float)Math.Atan2(ty, tx); + c = bb * l1 * l1 + aa * dd - aa * bb; + float c1 = -2 * bb * l1, c2 = bb - aa; + d = c1 * c1 - 4 * c2 * c; + if (d >= 0) + { + float q = (float)Math.Sqrt(d); + if (c1 < 0) q = -q; + q = -(c1 + q) * 0.5f; + float r0 = q / c2, r1 = c / q; + float r = Math.Abs(r0) < Math.Abs(r1) ? r0 : r1; + if (r * r <= dd) + { + y = (float)Math.Sqrt(dd - r * r) * bendDir; + a1 = ta - (float)Math.Atan2(y, r); + a2 = (float)Math.Atan2(y / psy, (r - l1) / psx); + goto break_outer; // break outer; + } + } + float minAngle = MathUtils.PI, minX = l1 - a, minDist = minX * minX, minY = 0; + float maxAngle = 0, maxX = l1 + a, maxDist = maxX * maxX, maxY = 0; + c = -a * l1 / (aa - bb); + if (c >= -1 && c <= 1) + { + c = (float)Math.Acos(c); + x = a * (float)Math.Cos(c) + l1; + y = b * (float)Math.Sin(c); + d = x * x + y * y; + if (d < minDist) + { + minAngle = c; + minDist = d; + minX = x; + minY = y; + } + if (d > maxDist) + { + maxAngle = c; + maxDist = d; + maxX = x; + maxY = y; + } + } + if (dd <= (minDist + maxDist) * 0.5f) + { + a1 = ta - (float)Math.Atan2(minY * bendDir, minX); + a2 = minAngle * bendDir; + } + else + { + a1 = ta - (float)Math.Atan2(maxY * bendDir, maxX); + a2 = maxAngle * bendDir; + } + } + break_outer: + float os = (float)Math.Atan2(cy, cx) * s2; + float rotation = parent.arotation; + a1 = (a1 - os) * MathUtils.RadDeg + os1 - rotation; + if (a1 > 180) + a1 -= 360; + else if (a1 < -180) + a1 += 360; + parent.UpdateWorldTransform(px, py, rotation + a1 * alpha, sx, sy, 0, 0); + rotation = child.arotation; + a2 = ((a2 + os) * MathUtils.RadDeg - child.ashearX) * s2 + os2 - rotation; + if (a2 > 180) + a2 -= 360; + else if (a2 < -180) + a2 += 360; + child.UpdateWorldTransform(cx, cy, rotation + a2 * alpha, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/IkConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/IkConstraintData.cs index 1b26077..fe45f66 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/IkConstraintData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/IkConstraintData.cs @@ -27,77 +27,85 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; -using System.Collections.Generic; +namespace Spine4_1_00 +{ + /// Stores the setup pose for an IkConstraint. + public class IkConstraintData : ConstraintData + { + internal ExposedList bones = new ExposedList(); + internal BoneData target; + internal int bendDirection = 1; + internal bool compress, stretch, uniform; + internal float mix = 1, softness; -namespace Spine4_1_00 { - /// Stores the setup pose for an IkConstraint. - public class IkConstraintData : ConstraintData { - internal ExposedList bones = new ExposedList(); - internal BoneData target; - internal int bendDirection = 1; - internal bool compress, stretch, uniform; - internal float mix = 1, softness; + public IkConstraintData(string name) : base(name) + { + } - public IkConstraintData (string name) : base(name) { - } + /// The bones that are constrained by this IK Constraint. + public ExposedList Bones + { + get { return bones; } + } - /// The bones that are constrained by this IK Constraint. - public ExposedList Bones { - get { return bones; } - } + /// The bone that is the IK target. + public BoneData Target + { + get { return target; } + set { target = value; } + } - /// The bone that is the IK target. - public BoneData Target { - get { return target; } - set { target = value; } - } + /// + /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. + /// + /// For two bone IK: if the parent bone has local nonuniform scale, the child bone's local Y translation is set to 0. + /// + public float Mix + { + get { return mix; } + set { mix = value; } + } - /// - /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. - /// - /// For two bone IK: if the parent bone has local nonuniform scale, the child bone's local Y translation is set to 0. - /// - public float Mix { - get { return mix; } - set { mix = value; } - } + /// For two bone IK, the target bone's distance from the maximum reach of the bones where rotation begins to slow. The bones + /// will not straighten completely until the target is this far out of range. + public float Softness + { + get { return softness; } + set { softness = value; } + } - /// For two bone IK, the target bone's distance from the maximum reach of the bones where rotation begins to slow. The bones - /// will not straighten completely until the target is this far out of range. - public float Softness { - get { return softness; } - set { softness = value; } - } + /// For two bone IK, controls the bend direction of the IK bones, either 1 or -1. + public int BendDirection + { + get { return bendDirection; } + set { bendDirection = value; } + } - /// For two bone IK, controls the bend direction of the IK bones, either 1 or -1. - public int BendDirection { - get { return bendDirection; } - set { bendDirection = value; } - } + /// For one bone IK, when true and the target is too close, the bone is scaled to reach it. + public bool Compress + { + get { return compress; } + set { compress = value; } + } - /// For one bone IK, when true and the target is too close, the bone is scaled to reach it. - public bool Compress { - get { return compress; } - set { compress = value; } - } + /// When true and the target is out of range, the parent bone is scaled to reach it. + /// + /// For two bone IK: 1) the child bone's local Y translation is set to 0, + /// 2) stretch is not applied if is > 0, + /// and 3) if the parent bone has local nonuniform scale, stretch is not applied. + public bool Stretch + { + get { return stretch; } + set { stretch = value; } + } - /// When true and the target is out of range, the parent bone is scaled to reach it. - /// - /// For two bone IK: 1) the child bone's local Y translation is set to 0, - /// 2) stretch is not applied if is > 0, - /// and 3) if the parent bone has local nonuniform scale, stretch is not applied. - public bool Stretch { - get { return stretch; } - set { stretch = value; } - } - - /// - /// When true and or is used, the bone is scaled on both the X and Y axes. - /// - public bool Uniform { - get { return uniform; } - set { uniform = value; } - } - } + /// + /// When true and or is used, the bone is scaled on both the X and Y axes. + /// + public bool Uniform + { + get { return uniform; } + set { uniform = value; } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Json.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Json.cs index 30786fc..de9d3a8 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Json.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Json.cs @@ -28,20 +28,22 @@ *****************************************************************************/ using System; -using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Text; -namespace Spine4_1_00 { - public static class Json { - public static object Deserialize (TextReader text) { - var parser = new SharpJson.JsonDecoder(); - parser.parseNumbersAsFloat = true; - return parser.Decode(text.ReadToEnd()); - } - } +namespace Spine4_1_00 +{ + public static class Json + { + public static object Deserialize(TextReader text) + { + var parser = new SharpJson.JsonDecoder(); + parser.parseNumbersAsFloat = true; + return parser.Decode(text.ReadToEnd()); + } + } } /** @@ -70,448 +72,504 @@ public static object Deserialize (TextReader text) { * 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. */ -namespace Spine4_1_00 { - class Lexer { - public enum Token { - None, - Null, - True, - False, - Colon, - Comma, - String, - Number, - CurlyOpen, - CurlyClose, - SquaredOpen, - SquaredClose, - }; - - public bool hasError { - get { - return !success; - } - } - - public int lineNumber { - get; - private set; - } - - public bool parseNumbersAsFloat { - get; - set; - } - - char[] json; - int index = 0; - bool success = true; - char[] stringBuffer = new char[4096]; - - public Lexer (string text) { - Reset(); - - json = text.ToCharArray(); - parseNumbersAsFloat = false; - } - - public void Reset () { - index = 0; - lineNumber = 1; - success = true; - } - - public string ParseString () { - int idx = 0; - StringBuilder builder = null; - - SkipWhiteSpaces(); - - // " - char c = json[index++]; - - bool failed = false; - bool complete = false; - - while (!complete && !failed) { - if (index == json.Length) - break; - - c = json[index++]; - if (c == '"') { - complete = true; - break; - } else if (c == '\\') { - if (index == json.Length) - break; - - c = json[index++]; - - switch (c) { - case '"': - stringBuffer[idx++] = '"'; - break; - case '\\': - stringBuffer[idx++] = '\\'; - break; - case '/': - stringBuffer[idx++] = '/'; - break; - case 'b': - stringBuffer[idx++] = '\b'; - break; - case 'f': - stringBuffer[idx++] = '\f'; - break; - case 'n': - stringBuffer[idx++] = '\n'; - break; - case 'r': - stringBuffer[idx++] = '\r'; - break; - case 't': - stringBuffer[idx++] = '\t'; - break; - case 'u': - int remainingLength = json.Length - index; - if (remainingLength >= 4) { - var hex = new string(json, index, 4); - - // XXX: handle UTF - stringBuffer[idx++] = (char)Convert.ToInt32(hex, 16); - - // skip 4 chars - index += 4; - } else { - failed = true; - } - break; - } - } else { - stringBuffer[idx++] = c; - } - - if (idx >= stringBuffer.Length) { - if (builder == null) - builder = new StringBuilder(); - - builder.Append(stringBuffer, 0, idx); - idx = 0; - } - } - - if (!complete) { - success = false; - return null; - } - - if (builder != null) - return builder.ToString(); - else - return new string(stringBuffer, 0, idx); - } - - string GetNumberString () { - SkipWhiteSpaces(); - - int lastIndex = GetLastIndexOfNumber(index); - int charLength = (lastIndex - index) + 1; - - var result = new string(json, index, charLength); - - index = lastIndex + 1; - - return result; - } - - public float ParseFloatNumber () { - float number; - var str = GetNumberString(); - - if (!float.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out number)) - return 0; - - return number; - } - - public double ParseDoubleNumber () { - double number; - var str = GetNumberString(); - - if (!double.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out number)) - return 0; - - return number; - } - - int GetLastIndexOfNumber (int index) { - int lastIndex; - - for (lastIndex = index; lastIndex < json.Length; lastIndex++) { - char ch = json[lastIndex]; - - if ((ch < '0' || ch > '9') && ch != '+' && ch != '-' - && ch != '.' && ch != 'e' && ch != 'E') - break; - } - - return lastIndex - 1; - } - - void SkipWhiteSpaces () { - for (; index < json.Length; index++) { - char ch = json[index]; - - if (ch == '\n') - lineNumber++; - - if (!char.IsWhiteSpace(json[index])) - break; - } - } - - public Token LookAhead () { - SkipWhiteSpaces(); - - int savedIndex = index; - return NextToken(json, ref savedIndex); - } - - public Token NextToken () { - SkipWhiteSpaces(); - return NextToken(json, ref index); - } - - static Token NextToken (char[] json, ref int index) { - if (index == json.Length) - return Token.None; - - char c = json[index++]; - - switch (c) { - case '{': - return Token.CurlyOpen; - case '}': - return Token.CurlyClose; - case '[': - return Token.SquaredOpen; - case ']': - return Token.SquaredClose; - case ',': - return Token.Comma; - case '"': - return Token.String; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case '-': - return Token.Number; - case ':': - return Token.Colon; - } - - index--; - - int remainingLength = json.Length - index; - - // false - if (remainingLength >= 5) { - if (json[index] == 'f' && - json[index + 1] == 'a' && - json[index + 2] == 'l' && - json[index + 3] == 's' && - json[index + 4] == 'e') { - index += 5; - return Token.False; - } - } - - // true - if (remainingLength >= 4) { - if (json[index] == 't' && - json[index + 1] == 'r' && - json[index + 2] == 'u' && - json[index + 3] == 'e') { - index += 4; - return Token.True; - } - } - - // null - if (remainingLength >= 4) { - if (json[index] == 'n' && - json[index + 1] == 'u' && - json[index + 2] == 'l' && - json[index + 3] == 'l') { - index += 4; - return Token.Null; - } - } - - return Token.None; - } - } - - public class JsonDecoder { - public string errorMessage { - get; - private set; - } - - public bool parseNumbersAsFloat { - get; - set; - } - - Lexer lexer; - - public JsonDecoder () { - errorMessage = null; - parseNumbersAsFloat = false; - } - - public object Decode (string text) { - errorMessage = null; - - lexer = new Lexer(text); - lexer.parseNumbersAsFloat = parseNumbersAsFloat; - - return ParseValue(); - } - - public static object DecodeText (string text) { - var builder = new JsonDecoder(); - return builder.Decode(text); - } - - IDictionary ParseObject () { - var table = new Dictionary(); - - // { - lexer.NextToken(); - - while (true) { - var token = lexer.LookAhead(); - - switch (token) { - case Lexer.Token.None: - TriggerError("Invalid token"); - return null; - case Lexer.Token.Comma: - lexer.NextToken(); - break; - case Lexer.Token.CurlyClose: - lexer.NextToken(); - return table; - default: - // name - string name = EvalLexer(lexer.ParseString()); - - if (errorMessage != null) - return null; - - // : - token = lexer.NextToken(); - - if (token != Lexer.Token.Colon) { - TriggerError("Invalid token; expected ':'"); - return null; - } - - // value - object value = ParseValue(); - - if (errorMessage != null) - return null; - - table[name] = value; - break; - } - } - - //return null; // Unreachable code - } - - IList ParseArray () { - var array = new List(); - - // [ - lexer.NextToken(); - - while (true) { - var token = lexer.LookAhead(); - - switch (token) { - case Lexer.Token.None: - TriggerError("Invalid token"); - return null; - case Lexer.Token.Comma: - lexer.NextToken(); - break; - case Lexer.Token.SquaredClose: - lexer.NextToken(); - return array; - default: - object value = ParseValue(); - - if (errorMessage != null) - return null; - - array.Add(value); - break; - } - } - - //return null; // Unreachable code - } - - object ParseValue () { - switch (lexer.LookAhead()) { - case Lexer.Token.String: - return EvalLexer(lexer.ParseString()); - case Lexer.Token.Number: - if (parseNumbersAsFloat) - return EvalLexer(lexer.ParseFloatNumber()); - else - return EvalLexer(lexer.ParseDoubleNumber()); - case Lexer.Token.CurlyOpen: - return ParseObject(); - case Lexer.Token.SquaredOpen: - return ParseArray(); - case Lexer.Token.True: - lexer.NextToken(); - return true; - case Lexer.Token.False: - lexer.NextToken(); - return false; - case Lexer.Token.Null: - lexer.NextToken(); - return null; - case Lexer.Token.None: - break; - } - - TriggerError("Unable to parse value"); - return null; - } - - void TriggerError (string message) { - errorMessage = string.Format("Error: '{0}' at line {1}", - message, lexer.lineNumber); - } - - T EvalLexer (T value) { - if (lexer.hasError) - TriggerError("Lexical error ocurred"); - - return value; - } - } +namespace Spine4_1_00 +{ + class Lexer + { + public enum Token + { + None, + Null, + True, + False, + Colon, + Comma, + String, + Number, + CurlyOpen, + CurlyClose, + SquaredOpen, + SquaredClose, + }; + + public bool hasError + { + get + { + return !success; + } + } + + public int lineNumber + { + get; + private set; + } + + public bool parseNumbersAsFloat + { + get; + set; + } + + char[] json; + int index = 0; + bool success = true; + char[] stringBuffer = new char[4096]; + + public Lexer(string text) + { + Reset(); + + json = text.ToCharArray(); + parseNumbersAsFloat = false; + } + + public void Reset() + { + index = 0; + lineNumber = 1; + success = true; + } + + public string ParseString() + { + int idx = 0; + StringBuilder builder = null; + + SkipWhiteSpaces(); + + // " + char c = json[index++]; + + bool failed = false; + bool complete = false; + + while (!complete && !failed) + { + if (index == json.Length) + break; + + c = json[index++]; + if (c == '"') + { + complete = true; + break; + } + else if (c == '\\') + { + if (index == json.Length) + break; + + c = json[index++]; + + switch (c) + { + case '"': + stringBuffer[idx++] = '"'; + break; + case '\\': + stringBuffer[idx++] = '\\'; + break; + case '/': + stringBuffer[idx++] = '/'; + break; + case 'b': + stringBuffer[idx++] = '\b'; + break; + case 'f': + stringBuffer[idx++] = '\f'; + break; + case 'n': + stringBuffer[idx++] = '\n'; + break; + case 'r': + stringBuffer[idx++] = '\r'; + break; + case 't': + stringBuffer[idx++] = '\t'; + break; + case 'u': + int remainingLength = json.Length - index; + if (remainingLength >= 4) + { + var hex = new string(json, index, 4); + + // XXX: handle UTF + stringBuffer[idx++] = (char)Convert.ToInt32(hex, 16); + + // skip 4 chars + index += 4; + } + else + { + failed = true; + } + break; + } + } + else + { + stringBuffer[idx++] = c; + } + + if (idx >= stringBuffer.Length) + { + if (builder == null) + builder = new StringBuilder(); + + builder.Append(stringBuffer, 0, idx); + idx = 0; + } + } + + if (!complete) + { + success = false; + return null; + } + + if (builder != null) + return builder.ToString(); + else + return new string(stringBuffer, 0, idx); + } + + string GetNumberString() + { + SkipWhiteSpaces(); + + int lastIndex = GetLastIndexOfNumber(index); + int charLength = (lastIndex - index) + 1; + + var result = new string(json, index, charLength); + + index = lastIndex + 1; + + return result; + } + + public float ParseFloatNumber() + { + float number; + var str = GetNumberString(); + + if (!float.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out number)) + return 0; + + return number; + } + + public double ParseDoubleNumber() + { + double number; + var str = GetNumberString(); + + if (!double.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out number)) + return 0; + + return number; + } + + int GetLastIndexOfNumber(int index) + { + int lastIndex; + + for (lastIndex = index; lastIndex < json.Length; lastIndex++) + { + char ch = json[lastIndex]; + + if ((ch < '0' || ch > '9') && ch != '+' && ch != '-' + && ch != '.' && ch != 'e' && ch != 'E') + break; + } + + return lastIndex - 1; + } + + void SkipWhiteSpaces() + { + for (; index < json.Length; index++) + { + char ch = json[index]; + + if (ch == '\n') + lineNumber++; + + if (!char.IsWhiteSpace(json[index])) + break; + } + } + + public Token LookAhead() + { + SkipWhiteSpaces(); + + int savedIndex = index; + return NextToken(json, ref savedIndex); + } + + public Token NextToken() + { + SkipWhiteSpaces(); + return NextToken(json, ref index); + } + + static Token NextToken(char[] json, ref int index) + { + if (index == json.Length) + return Token.None; + + char c = json[index++]; + + switch (c) + { + case '{': + return Token.CurlyOpen; + case '}': + return Token.CurlyClose; + case '[': + return Token.SquaredOpen; + case ']': + return Token.SquaredClose; + case ',': + return Token.Comma; + case '"': + return Token.String; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + return Token.Number; + case ':': + return Token.Colon; + } + + index--; + + int remainingLength = json.Length - index; + + // false + if (remainingLength >= 5) + { + if (json[index] == 'f' && + json[index + 1] == 'a' && + json[index + 2] == 'l' && + json[index + 3] == 's' && + json[index + 4] == 'e') + { + index += 5; + return Token.False; + } + } + + // true + if (remainingLength >= 4) + { + if (json[index] == 't' && + json[index + 1] == 'r' && + json[index + 2] == 'u' && + json[index + 3] == 'e') + { + index += 4; + return Token.True; + } + } + + // null + if (remainingLength >= 4) + { + if (json[index] == 'n' && + json[index + 1] == 'u' && + json[index + 2] == 'l' && + json[index + 3] == 'l') + { + index += 4; + return Token.Null; + } + } + + return Token.None; + } + } + + public class JsonDecoder + { + public string errorMessage + { + get; + private set; + } + + public bool parseNumbersAsFloat + { + get; + set; + } + + Lexer lexer; + + public JsonDecoder() + { + errorMessage = null; + parseNumbersAsFloat = false; + } + + public object Decode(string text) + { + errorMessage = null; + + lexer = new Lexer(text); + lexer.parseNumbersAsFloat = parseNumbersAsFloat; + + return ParseValue(); + } + + public static object DecodeText(string text) + { + var builder = new JsonDecoder(); + return builder.Decode(text); + } + + IDictionary ParseObject() + { + var table = new Dictionary(); + + // { + lexer.NextToken(); + + while (true) + { + var token = lexer.LookAhead(); + + switch (token) + { + case Lexer.Token.None: + TriggerError("Invalid token"); + return null; + case Lexer.Token.Comma: + lexer.NextToken(); + break; + case Lexer.Token.CurlyClose: + lexer.NextToken(); + return table; + default: + // name + string name = EvalLexer(lexer.ParseString()); + + if (errorMessage != null) + return null; + + // : + token = lexer.NextToken(); + + if (token != Lexer.Token.Colon) + { + TriggerError("Invalid token; expected ':'"); + return null; + } + + // value + object value = ParseValue(); + + if (errorMessage != null) + return null; + + table[name] = value; + break; + } + } + + //return null; // Unreachable code + } + + IList ParseArray() + { + var array = new List(); + + // [ + lexer.NextToken(); + + while (true) + { + var token = lexer.LookAhead(); + + switch (token) + { + case Lexer.Token.None: + TriggerError("Invalid token"); + return null; + case Lexer.Token.Comma: + lexer.NextToken(); + break; + case Lexer.Token.SquaredClose: + lexer.NextToken(); + return array; + default: + object value = ParseValue(); + + if (errorMessage != null) + return null; + + array.Add(value); + break; + } + } + + //return null; // Unreachable code + } + + object ParseValue() + { + switch (lexer.LookAhead()) + { + case Lexer.Token.String: + return EvalLexer(lexer.ParseString()); + case Lexer.Token.Number: + if (parseNumbersAsFloat) + return EvalLexer(lexer.ParseFloatNumber()); + else + return EvalLexer(lexer.ParseDoubleNumber()); + case Lexer.Token.CurlyOpen: + return ParseObject(); + case Lexer.Token.SquaredOpen: + return ParseArray(); + case Lexer.Token.True: + lexer.NextToken(); + return true; + case Lexer.Token.False: + lexer.NextToken(); + return false; + case Lexer.Token.Null: + lexer.NextToken(); + return null; + case Lexer.Token.None: + break; + } + + TriggerError("Unable to parse value"); + return null; + } + + void TriggerError(string message) + { + errorMessage = string.Format("Error: '{0}' at line {1}", + message, lexer.lineNumber); + } + + T EvalLexer(T value) + { + if (lexer.hasError) + TriggerError("Lexical error ocurred"); + + return value; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/MathUtils.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/MathUtils.cs index 8ff09bc..818c983 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/MathUtils.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/MathUtils.cs @@ -31,14 +31,16 @@ using System; -namespace Spine4_1_00 { - public static class MathUtils { - public const float PI = 3.1415927f; - public const float PI2 = PI * 2; - public const float RadDeg = 180f / PI; - public const float DegRad = PI / 180; +namespace Spine4_1_00 +{ + public static class MathUtils + { + public const float PI = 3.1415927f; + public const float PI2 = PI * 2; + public const float RadDeg = 180f / PI; + public const float DegRad = PI / 180; - static Random random = new Random(); + static Random random = new Random(); #if USE_FAST_SIN_COS_ATAN2_APPROXIMATIONS const int SIN_BITS = 14; // 16KB. Adjust for accuracy. @@ -95,79 +97,95 @@ static public float Atan2 (float y, float x) { return y < 0f ? atan - PI : atan; } #else - /// Returns the sine of a given angle in radians. - static public float Sin (float radians) { - return (float)Math.Sin(radians); - } - - /// Returns the cosine of a given angle in radians. - static public float Cos (float radians) { - return (float)Math.Cos(radians); - } - - /// Returns the sine of a given angle in degrees. - static public float SinDeg (float degrees) { - return (float)Math.Sin(degrees * DegRad); - } - - /// Returns the cosine of a given angle in degrees. - static public float CosDeg (float degrees) { - return (float)Math.Cos(degrees * DegRad); - } - - /// Returns the atan2 using Math.Atan2. - static public float Atan2 (float y, float x) { - return (float)Math.Atan2(y, x); - } + /// Returns the sine of a given angle in radians. + static public float Sin(float radians) + { + return (float)Math.Sin(radians); + } + + /// Returns the cosine of a given angle in radians. + static public float Cos(float radians) + { + return (float)Math.Cos(radians); + } + + /// Returns the sine of a given angle in degrees. + static public float SinDeg(float degrees) + { + return (float)Math.Sin(degrees * DegRad); + } + + /// Returns the cosine of a given angle in degrees. + static public float CosDeg(float degrees) + { + return (float)Math.Cos(degrees * DegRad); + } + + /// Returns the atan2 using Math.Atan2. + static public float Atan2(float y, float x) + { + return (float)Math.Atan2(y, x); + } #endif - static public float Clamp (float value, float min, float max) { - if (value < min) return min; - if (value > max) return max; - return value; - } - - static public float RandomTriangle (float min, float max) { - return RandomTriangle(min, max, (min + max) * 0.5f); - } - - static public float RandomTriangle (float min, float max, float mode) { - float u = (float)random.NextDouble(); - float d = max - min; - if (u <= (mode - min) / d) return min + (float)Math.Sqrt(u * d * (mode - min)); - return max - (float)Math.Sqrt((1 - u) * d * (max - mode)); - } - } - - public abstract class IInterpolation { - public static IInterpolation Pow2 = new Pow(2); - public static IInterpolation Pow2Out = new PowOut(2); - - protected abstract float Apply (float a); - - public float Apply (float start, float end, float a) { - return start + (end - start) * Apply(a); - } - } - - public class Pow : IInterpolation { - public float Power { get; set; } - - public Pow (float power) { - Power = power; - } - - protected override float Apply (float a) { - if (a <= 0.5f) return (float)Math.Pow(a * 2, Power) / 2; - return (float)Math.Pow((a - 1) * 2, Power) / (Power % 2 == 0 ? -2 : 2) + 1; - } - } - - public class PowOut : Pow { - public PowOut (float power) : base(power) { - } - - protected override float Apply (float a) { - return (float)Math.Pow(a - 1, Power) * (Power % 2 == 0 ? -1 : 1) + 1; - } - } + static public float Clamp(float value, float min, float max) + { + if (value < min) return min; + if (value > max) return max; + return value; + } + + static public float RandomTriangle(float min, float max) + { + return RandomTriangle(min, max, (min + max) * 0.5f); + } + + static public float RandomTriangle(float min, float max, float mode) + { + float u = (float)random.NextDouble(); + float d = max - min; + if (u <= (mode - min) / d) return min + (float)Math.Sqrt(u * d * (mode - min)); + return max - (float)Math.Sqrt((1 - u) * d * (max - mode)); + } + } + + public abstract class IInterpolation + { + public static IInterpolation Pow2 = new Pow(2); + public static IInterpolation Pow2Out = new PowOut(2); + + protected abstract float Apply(float a); + + public float Apply(float start, float end, float a) + { + return start + (end - start) * Apply(a); + } + } + + public class Pow : IInterpolation + { + public float Power { get; set; } + + public Pow(float power) + { + Power = power; + } + + protected override float Apply(float a) + { + if (a <= 0.5f) return (float)Math.Pow(a * 2, Power) / 2; + return (float)Math.Pow((a - 1) * 2, Power) / (Power % 2 == 0 ? -2 : 2) + 1; + } + } + + public class PowOut : Pow + { + public PowOut(float power) : base(power) + { + } + + protected override float Apply(float a) + { + return (float)Math.Pow(a - 1, Power) * (Power % 2 == 0 ? -1 : 1) + 1; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/PathConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/PathConstraint.cs index cd1c56a..75d3baf 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/PathConstraint.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/PathConstraint.cs @@ -29,486 +29,555 @@ using System; -namespace Spine4_1_00 { - - /// - /// - /// Stores the current pose for a path constraint. A path constraint adjusts the rotation, translation, and scale of the - /// constrained bones so they follow a . - /// - /// See Path constraints in the Spine User Guide. - /// - public class PathConstraint : IUpdatable { - const int NONE = -1, BEFORE = -2, AFTER = -3; - const float Epsilon = 0.00001f; - - internal readonly PathConstraintData data; - internal readonly ExposedList bones; - internal Slot target; - internal float position, spacing, mixRotate, mixX, mixY; - - internal bool active; - - internal readonly ExposedList spaces = new ExposedList(), positions = new ExposedList(); - internal readonly ExposedList world = new ExposedList(), curves = new ExposedList(), lengths = new ExposedList(); - internal readonly float[] segments = new float[10]; - - public PathConstraint (PathConstraintData data, Skeleton skeleton) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.data = data; - bones = new ExposedList(data.Bones.Count); - foreach (BoneData boneData in data.bones) - bones.Add(skeleton.bones.Items[boneData.index]); - target = skeleton.slots.Items[data.target.index]; - position = data.position; - spacing = data.spacing; - mixRotate = data.mixRotate; - mixX = data.mixX; - mixY = data.mixY; - } - - /// Copy constructor. - public PathConstraint (PathConstraint constraint, Skeleton skeleton) { - if (constraint == null) throw new ArgumentNullException("constraint cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); - data = constraint.data; - bones = new ExposedList(constraint.bones.Count); - foreach (Bone bone in constraint.bones) - bones.Add(skeleton.bones.Items[bone.data.index]); - target = skeleton.slots.Items[constraint.target.data.index]; - position = constraint.position; - spacing = constraint.spacing; - mixRotate = constraint.mixRotate; - mixX = constraint.mixX; - mixY = constraint.mixY; - } - - public static void ArraysFill (float[] a, int fromIndex, int toIndex, float val) { - for (int i = fromIndex; i < toIndex; i++) - a[i] = val; - } - - public void Update () { - PathAttachment attachment = target.Attachment as PathAttachment; - if (attachment == null) return; - - float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY; - if (mixRotate == 0 && mixX == 0 && mixY == 0) return; - - PathConstraintData data = this.data; - bool tangents = data.rotateMode == RotateMode.Tangent, scale = data.rotateMode == RotateMode.ChainScale; - int boneCount = this.bones.Count, spacesCount = tangents ? boneCount : boneCount + 1; - Bone[] bonesItems = this.bones.Items; - float[] spaces = this.spaces.Resize(spacesCount).Items, lengths = scale ? this.lengths.Resize(boneCount).Items : null; - float spacing = this.spacing; - switch (data.spacingMode) { - case SpacingMode.Percent: - if (scale) { - for (int i = 0, n = spacesCount - 1; i < n; i++) { - Bone bone = bonesItems[i]; - float setupLength = bone.data.length; - if (setupLength < PathConstraint.Epsilon) - lengths[i] = 0; - else { - float x = setupLength * bone.a, y = setupLength * bone.c; - lengths[i] = (float)Math.Sqrt(x * x + y * y); - } - } - } - ArraysFill(spaces, 1, spacesCount, spacing); - break; - case SpacingMode.Proportional: { - float sum = 0; - for (int i = 0, n = spacesCount - 1; i < n;) { - Bone bone = bonesItems[i]; - float setupLength = bone.data.length; - if (setupLength < PathConstraint.Epsilon) { - if (scale) lengths[i] = 0; - spaces[++i] = spacing; - } else { - float x = setupLength * bone.a, y = setupLength * bone.c; - float length = (float)Math.Sqrt(x * x + y * y); - if (scale) lengths[i] = length; - spaces[++i] = length; - sum += length; - } - } - if (sum > 0) { - sum = spacesCount / sum * spacing; - for (int i = 1; i < spacesCount; i++) - spaces[i] *= sum; - } - break; - } - default: { - bool lengthSpacing = data.spacingMode == SpacingMode.Length; - for (int i = 0, n = spacesCount - 1; i < n;) { - Bone bone = bonesItems[i]; - float setupLength = bone.data.length; - if (setupLength < PathConstraint.Epsilon) { - if (scale) lengths[i] = 0; - spaces[++i] = spacing; - } else { - float x = setupLength * bone.a, y = setupLength * bone.c; - float length = (float)Math.Sqrt(x * x + y * y); - if (scale) lengths[i] = length; - spaces[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length / setupLength; - } - } - break; - } - } - - float[] positions = ComputeWorldPositions(attachment, spacesCount, tangents); - float boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation; - bool tip; - if (offsetRotation == 0) { - tip = data.rotateMode == RotateMode.Chain; - } else { - tip = false; - Bone p = target.bone; - offsetRotation *= p.a * p.d - p.b * p.c > 0 ? MathUtils.DegRad : -MathUtils.DegRad; - } - for (int i = 0, p = 3; i < boneCount; i++, p += 3) { - Bone bone = bonesItems[i]; - bone.worldX += (boneX - bone.worldX) * mixX; - bone.worldY += (boneY - bone.worldY) * mixY; - float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY; - if (scale) { - float length = lengths[i]; - if (length >= PathConstraint.Epsilon) { - float s = ((float)Math.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 = MathUtils.Atan2(dy, dx); - r -= MathUtils.Atan2(c, a); - if (tip) { - cos = MathUtils.Cos(r); - sin = MathUtils.Sin(r); - float length = bone.data.length; - boneX += (length * (cos * a - sin * c) - dx) * mixRotate; - boneY += (length * (sin * a + cos * c) - dy) * mixRotate; - } else - r += offsetRotation; - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) // - r += MathUtils.PI2; - r *= mixRotate; - cos = MathUtils.Cos(r); - sin = MathUtils.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(); - } - } - - float[] ComputeWorldPositions (PathAttachment path, int spacesCount, bool tangents) { - Slot target = this.target; - float position = this.position; - float[] spaces = this.spaces.Items, output = this.positions.Resize(spacesCount * 3 + 2).Items, world; - bool closed = path.Closed; - int verticesLength = path.WorldVerticesLength, curveCount = verticesLength / 6, prevCurve = NONE; - - float pathLength, multiplier; - if (!path.ConstantSpeed) { - float[] lengths = path.Lengths; - curveCount -= closed ? 1 : 2; - pathLength = lengths[curveCount]; - - if (data.positionMode == PositionMode.Percent) position *= pathLength; - - switch (data.spacingMode) { - case SpacingMode.Percent: - multiplier = pathLength; - break; - case SpacingMode.Proportional: - multiplier = pathLength / spacesCount; - break; - default: - multiplier = 1; - break; - } - - world = this.world.Resize(8).Items; - 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 %= pathLength; - if (p < 0) p += pathLength; - curve = 0; - } else if (p < 0) { - if (prevCurve != BEFORE) { - prevCurve = BEFORE; - path.ComputeWorldVertices(target, 2, 4, world, 0, 2); - } - AddBeforePosition(p, world, 0, output, o); - continue; - } else if (p > pathLength) { - if (prevCurve != AFTER) { - prevCurve = AFTER; - path.ComputeWorldVertices(target, verticesLength - 6, 4, world, 0, 2); - } - AddAfterPosition(p - pathLength, world, 0, output, 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, 2); - path.ComputeWorldVertices(target, 0, 4, world, 4, 2); - } else - path.ComputeWorldVertices(target, curve * 6 + 2, 8, world, 0, 2); - } - AddCurvePosition(p, world[0], world[1], world[2], world[3], world[4], world[5], world[6], world[7], output, o, - tangents || (i > 0 && space < PathConstraint.Epsilon)); - } - return output; - } - - // World vertices. - if (closed) { - verticesLength += 2; - world = this.world.Resize(verticesLength).Items; - path.ComputeWorldVertices(target, 2, verticesLength - 4, world, 0, 2); - path.ComputeWorldVertices(target, 0, 2, world, verticesLength - 4, 2); - world[verticesLength - 2] = world[0]; - world[verticesLength - 1] = world[1]; - } else { - curveCount--; - verticesLength -= 4; - world = this.world.Resize(verticesLength).Items; - path.ComputeWorldVertices(target, 2, verticesLength, world, 0, 2); - } - - // Curve lengths. - float[] curves = this.curves.Resize(curveCount).Items; - 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 += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - dfx += ddfx; - dfy += ddfy; - ddfx += dddfx; - ddfy += dddfy; - pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - dfx += ddfx; - dfy += ddfy; - pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - dfx += ddfx + dddfx; - dfy += ddfy + dddfy; - pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - curves[i] = pathLength; - x1 = x2; - y1 = y2; - } - - if (data.positionMode == PositionMode.Percent) position *= pathLength; - - switch (data.spacingMode) { - case SpacingMode.Percent: - multiplier = pathLength; - break; - case SpacingMode.Proportional: - multiplier = pathLength / spacesCount; - break; - default: - multiplier = 1; - break; - } - - float[] segments = this.segments; - 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 %= pathLength; - if (p < 0) p += pathLength; - curve = 0; - } else if (p < 0) { - AddBeforePosition(p, world, 0, output, o); - continue; - } else if (p > pathLength) { - AddAfterPosition(p - pathLength, world, verticesLength - 4, output, 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 = (float)Math.Sqrt(dfx * dfx + dfy * dfy); - segments[0] = curveLength; - for (ii = 1; ii < 8; ii++) { - dfx += ddfx; - dfy += ddfy; - ddfx += dddfx; - ddfy += dddfy; - curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - segments[ii] = curveLength; - } - dfx += ddfx; - dfy += ddfy; - curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); - segments[8] = curveLength; - dfx += ddfx + dddfx; - dfy += ddfy + dddfy; - curveLength += (float)Math.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, output, o, tangents || (i > 0 && space < PathConstraint.Epsilon)); - } - return output; - } - - static void AddBeforePosition (float p, float[] temp, int i, float[] output, int o) { - float x1 = temp[i], y1 = temp[i + 1], dx = temp[i + 2] - x1, dy = temp[i + 3] - y1, r = MathUtils.Atan2(dy, dx); - output[o] = x1 + p * MathUtils.Cos(r); - output[o + 1] = y1 + p * MathUtils.Sin(r); - output[o + 2] = r; - } - - static void AddAfterPosition (float p, float[] temp, int i, float[] output, int o) { - float x1 = temp[i + 2], y1 = temp[i + 3], dx = x1 - temp[i], dy = y1 - temp[i + 1], r = MathUtils.Atan2(dy, dx); - output[o] = x1 + p * MathUtils.Cos(r); - output[o + 1] = y1 + p * MathUtils.Sin(r); - output[o + 2] = r; - } - - static void AddCurvePosition (float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2, - float[] output, int o, bool tangents) { - if (p < PathConstraint.Epsilon || float.IsNaN(p)) { - output[o] = x1; - output[o + 1] = y1; - output[o + 2] = (float)Math.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.001f) - output[o + 2] = (float)Math.Atan2(cy1 - y1, cx1 - x1); - else - output[o + 2] = (float)Math.Atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt)); - } - } - - /// The position along the path. - public float Position { get { return position; } set { position = value; } } - /// The spacing between bones. - public float Spacing { get { return spacing; } set { spacing = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotations. - public float MixRotate { get { return mixRotate; } set { mixRotate = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. - public float MixX { get { return mixX; } set { mixX = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. - public float MixY { get { return mixY; } set { mixY = value; } } - /// The bones that will be modified by this path constraint. - public ExposedList Bones { get { return bones; } } - /// The slot whose path attachment will be used to constrained the bones. - public Slot Target { get { return target; } set { target = value; } } - public bool Active { get { return active; } } - /// The path constraint's setup pose data. - public PathConstraintData Data { get { return data; } } - - override public string ToString () { - return data.name; - } - } +namespace Spine4_1_00 +{ + + /// + /// + /// Stores the current pose for a path constraint. A path constraint adjusts the rotation, translation, and scale of the + /// constrained bones so they follow a . + /// + /// See Path constraints in the Spine User Guide. + /// + public class PathConstraint : IUpdatable + { + const int NONE = -1, BEFORE = -2, AFTER = -3; + const float Epsilon = 0.00001f; + + internal readonly PathConstraintData data; + internal readonly ExposedList bones; + internal Slot target; + internal float position, spacing, mixRotate, mixX, mixY; + + internal bool active; + + internal readonly ExposedList spaces = new ExposedList(), positions = new ExposedList(); + internal readonly ExposedList world = new ExposedList(), curves = new ExposedList(), lengths = new ExposedList(); + internal readonly float[] segments = new float[10]; + + public PathConstraint(PathConstraintData data, Skeleton skeleton) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + bones = new ExposedList(data.Bones.Count); + foreach (BoneData boneData in data.bones) + bones.Add(skeleton.bones.Items[boneData.index]); + target = skeleton.slots.Items[data.target.index]; + position = data.position; + spacing = data.spacing; + mixRotate = data.mixRotate; + mixX = data.mixX; + mixY = data.mixY; + } + + /// Copy constructor. + public PathConstraint(PathConstraint constraint, Skeleton skeleton) + { + if (constraint == null) throw new ArgumentNullException("constraint cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); + data = constraint.data; + bones = new ExposedList(constraint.bones.Count); + foreach (Bone bone in constraint.bones) + bones.Add(skeleton.bones.Items[bone.data.index]); + target = skeleton.slots.Items[constraint.target.data.index]; + position = constraint.position; + spacing = constraint.spacing; + mixRotate = constraint.mixRotate; + mixX = constraint.mixX; + mixY = constraint.mixY; + } + + public static void ArraysFill(float[] a, int fromIndex, int toIndex, float val) + { + for (int i = fromIndex; i < toIndex; i++) + a[i] = val; + } + + public void Update() + { + PathAttachment attachment = target.Attachment as PathAttachment; + if (attachment == null) return; + + float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY; + if (mixRotate == 0 && mixX == 0 && mixY == 0) return; + + PathConstraintData data = this.data; + bool tangents = data.rotateMode == RotateMode.Tangent, scale = data.rotateMode == RotateMode.ChainScale; + int boneCount = this.bones.Count, spacesCount = tangents ? boneCount : boneCount + 1; + Bone[] bonesItems = this.bones.Items; + float[] spaces = this.spaces.Resize(spacesCount).Items, lengths = scale ? this.lengths.Resize(boneCount).Items : null; + float spacing = this.spacing; + switch (data.spacingMode) + { + case SpacingMode.Percent: + if (scale) + { + for (int i = 0, n = spacesCount - 1; i < n; i++) + { + Bone bone = bonesItems[i]; + float setupLength = bone.data.length; + if (setupLength < PathConstraint.Epsilon) + lengths[i] = 0; + else + { + float x = setupLength * bone.a, y = setupLength * bone.c; + lengths[i] = (float)Math.Sqrt(x * x + y * y); + } + } + } + ArraysFill(spaces, 1, spacesCount, spacing); + break; + case SpacingMode.Proportional: + { + float sum = 0; + for (int i = 0, n = spacesCount - 1; i < n;) + { + Bone bone = bonesItems[i]; + float setupLength = bone.data.length; + if (setupLength < PathConstraint.Epsilon) + { + if (scale) lengths[i] = 0; + spaces[++i] = spacing; + } + else + { + float x = setupLength * bone.a, y = setupLength * bone.c; + float length = (float)Math.Sqrt(x * x + y * y); + if (scale) lengths[i] = length; + spaces[++i] = length; + sum += length; + } + } + if (sum > 0) + { + sum = spacesCount / sum * spacing; + for (int i = 1; i < spacesCount; i++) + spaces[i] *= sum; + } + break; + } + default: + { + bool lengthSpacing = data.spacingMode == SpacingMode.Length; + for (int i = 0, n = spacesCount - 1; i < n;) + { + Bone bone = bonesItems[i]; + float setupLength = bone.data.length; + if (setupLength < PathConstraint.Epsilon) + { + if (scale) lengths[i] = 0; + spaces[++i] = spacing; + } + else + { + float x = setupLength * bone.a, y = setupLength * bone.c; + float length = (float)Math.Sqrt(x * x + y * y); + if (scale) lengths[i] = length; + spaces[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length / setupLength; + } + } + break; + } + } + + float[] positions = ComputeWorldPositions(attachment, spacesCount, tangents); + float boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation; + bool tip; + if (offsetRotation == 0) + { + tip = data.rotateMode == RotateMode.Chain; + } + else + { + tip = false; + Bone p = target.bone; + offsetRotation *= p.a * p.d - p.b * p.c > 0 ? MathUtils.DegRad : -MathUtils.DegRad; + } + for (int i = 0, p = 3; i < boneCount; i++, p += 3) + { + Bone bone = bonesItems[i]; + bone.worldX += (boneX - bone.worldX) * mixX; + bone.worldY += (boneY - bone.worldY) * mixY; + float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY; + if (scale) + { + float length = lengths[i]; + if (length >= PathConstraint.Epsilon) + { + float s = ((float)Math.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 = MathUtils.Atan2(dy, dx); + r -= MathUtils.Atan2(c, a); + if (tip) + { + cos = MathUtils.Cos(r); + sin = MathUtils.Sin(r); + float length = bone.data.length; + boneX += (length * (cos * a - sin * c) - dx) * mixRotate; + boneY += (length * (sin * a + cos * c) - dy) * mixRotate; + } + else + r += offsetRotation; + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) // + r += MathUtils.PI2; + r *= mixRotate; + cos = MathUtils.Cos(r); + sin = MathUtils.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(); + } + } + + float[] ComputeWorldPositions(PathAttachment path, int spacesCount, bool tangents) + { + Slot target = this.target; + float position = this.position; + float[] spaces = this.spaces.Items, output = this.positions.Resize(spacesCount * 3 + 2).Items, world; + bool closed = path.Closed; + int verticesLength = path.WorldVerticesLength, curveCount = verticesLength / 6, prevCurve = NONE; + + float pathLength, multiplier; + if (!path.ConstantSpeed) + { + float[] lengths = path.Lengths; + curveCount -= closed ? 1 : 2; + pathLength = lengths[curveCount]; + + if (data.positionMode == PositionMode.Percent) position *= pathLength; + + switch (data.spacingMode) + { + case SpacingMode.Percent: + multiplier = pathLength; + break; + case SpacingMode.Proportional: + multiplier = pathLength / spacesCount; + break; + default: + multiplier = 1; + break; + } + + world = this.world.Resize(8).Items; + 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 %= pathLength; + if (p < 0) p += pathLength; + curve = 0; + } + else if (p < 0) + { + if (prevCurve != BEFORE) + { + prevCurve = BEFORE; + path.ComputeWorldVertices(target, 2, 4, world, 0, 2); + } + AddBeforePosition(p, world, 0, output, o); + continue; + } + else if (p > pathLength) + { + if (prevCurve != AFTER) + { + prevCurve = AFTER; + path.ComputeWorldVertices(target, verticesLength - 6, 4, world, 0, 2); + } + AddAfterPosition(p - pathLength, world, 0, output, 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, 2); + path.ComputeWorldVertices(target, 0, 4, world, 4, 2); + } + else + path.ComputeWorldVertices(target, curve * 6 + 2, 8, world, 0, 2); + } + AddCurvePosition(p, world[0], world[1], world[2], world[3], world[4], world[5], world[6], world[7], output, o, + tangents || (i > 0 && space < PathConstraint.Epsilon)); + } + return output; + } + + // World vertices. + if (closed) + { + verticesLength += 2; + world = this.world.Resize(verticesLength).Items; + path.ComputeWorldVertices(target, 2, verticesLength - 4, world, 0, 2); + path.ComputeWorldVertices(target, 0, 2, world, verticesLength - 4, 2); + world[verticesLength - 2] = world[0]; + world[verticesLength - 1] = world[1]; + } + else + { + curveCount--; + verticesLength -= 4; + world = this.world.Resize(verticesLength).Items; + path.ComputeWorldVertices(target, 2, verticesLength, world, 0, 2); + } + + // Curve lengths. + float[] curves = this.curves.Resize(curveCount).Items; + 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 += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx; + dfy += ddfy; + pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx + dddfx; + dfy += ddfy + dddfy; + pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + curves[i] = pathLength; + x1 = x2; + y1 = y2; + } + + if (data.positionMode == PositionMode.Percent) position *= pathLength; + + switch (data.spacingMode) + { + case SpacingMode.Percent: + multiplier = pathLength; + break; + case SpacingMode.Proportional: + multiplier = pathLength / spacesCount; + break; + default: + multiplier = 1; + break; + } + + float[] segments = this.segments; + 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 %= pathLength; + if (p < 0) p += pathLength; + curve = 0; + } + else if (p < 0) + { + AddBeforePosition(p, world, 0, output, o); + continue; + } + else if (p > pathLength) + { + AddAfterPosition(p - pathLength, world, verticesLength - 4, output, 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 = (float)Math.Sqrt(dfx * dfx + dfy * dfy); + segments[0] = curveLength; + for (ii = 1; ii < 8; ii++) + { + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + segments[ii] = curveLength; + } + dfx += ddfx; + dfy += ddfy; + curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + segments[8] = curveLength; + dfx += ddfx + dddfx; + dfy += ddfy + dddfy; + curveLength += (float)Math.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, output, o, tangents || (i > 0 && space < PathConstraint.Epsilon)); + } + return output; + } + + static void AddBeforePosition(float p, float[] temp, int i, float[] output, int o) + { + float x1 = temp[i], y1 = temp[i + 1], dx = temp[i + 2] - x1, dy = temp[i + 3] - y1, r = MathUtils.Atan2(dy, dx); + output[o] = x1 + p * MathUtils.Cos(r); + output[o + 1] = y1 + p * MathUtils.Sin(r); + output[o + 2] = r; + } + + static void AddAfterPosition(float p, float[] temp, int i, float[] output, int o) + { + float x1 = temp[i + 2], y1 = temp[i + 3], dx = x1 - temp[i], dy = y1 - temp[i + 1], r = MathUtils.Atan2(dy, dx); + output[o] = x1 + p * MathUtils.Cos(r); + output[o + 1] = y1 + p * MathUtils.Sin(r); + output[o + 2] = r; + } + + static void AddCurvePosition(float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2, + float[] output, int o, bool tangents) + { + if (p < PathConstraint.Epsilon || float.IsNaN(p)) + { + output[o] = x1; + output[o + 1] = y1; + output[o + 2] = (float)Math.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.001f) + output[o + 2] = (float)Math.Atan2(cy1 - y1, cx1 - x1); + else + output[o + 2] = (float)Math.Atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt)); + } + } + + /// The position along the path. + public float Position { get { return position; } set { position = value; } } + /// The spacing between bones. + public float Spacing { get { return spacing; } set { spacing = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotations. + public float MixRotate { get { return mixRotate; } set { mixRotate = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. + public float MixX { get { return mixX; } set { mixX = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. + public float MixY { get { return mixY; } set { mixY = value; } } + /// The bones that will be modified by this path constraint. + public ExposedList Bones { get { return bones; } } + /// The slot whose path attachment will be used to constrained the bones. + public Slot Target { get { return target; } set { target = value; } } + public bool Active { get { return active; } } + /// The path constraint's setup pose data. + public PathConstraintData Data { get { return data; } } + + override public string ToString() + { + return data.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/PathConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/PathConstraintData.cs index a36c64a..71117c7 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/PathConstraintData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/PathConstraintData.cs @@ -27,46 +27,50 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; +namespace Spine4_1_00 +{ + public class PathConstraintData : ConstraintData + { + internal ExposedList bones = new ExposedList(); + internal SlotData target; + internal PositionMode positionMode; + internal SpacingMode spacingMode; + internal RotateMode rotateMode; + internal float offsetRotation; + internal float position, spacing, mixRotate, mixX, mixY; -namespace Spine4_1_00 { - public class PathConstraintData : ConstraintData { - internal ExposedList bones = new ExposedList(); - internal SlotData target; - internal PositionMode positionMode; - internal SpacingMode spacingMode; - internal RotateMode rotateMode; - internal float offsetRotation; - internal float position, spacing, mixRotate, mixX, mixY; + public PathConstraintData(string name) : base(name) + { + } - public PathConstraintData (string name) : base(name) { - } + public ExposedList Bones { get { return bones; } } + public SlotData Target { get { return target; } set { target = value; } } + public PositionMode PositionMode { get { return positionMode; } set { positionMode = value; } } + public SpacingMode SpacingMode { get { return spacingMode; } set { spacingMode = value; } } + public RotateMode RotateMode { get { return rotateMode; } set { rotateMode = value; } } + public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } + public float Position { get { return position; } set { position = value; } } + public float Spacing { get { return spacing; } set { spacing = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. + public float RotateMix { get { return mixRotate; } set { mixRotate = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. + public float MixX { get { return mixX; } set { mixX = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. + public float MixY { get { return mixY; } set { mixY = value; } } + } - public ExposedList Bones { get { return bones; } } - public SlotData Target { get { return target; } set { target = value; } } - public PositionMode PositionMode { get { return positionMode; } set { positionMode = value; } } - public SpacingMode SpacingMode { get { return spacingMode; } set { spacingMode = value; } } - public RotateMode RotateMode { get { return rotateMode; } set { rotateMode = value; } } - public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } - public float Position { get { return position; } set { position = value; } } - public float Spacing { get { return spacing; } set { spacing = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. - public float RotateMix { get { return mixRotate; } set { mixRotate = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. - public float MixX { get { return mixX; } set { mixX = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. - public float MixY { get { return mixY; } set { mixY = value; } } - } + public enum PositionMode + { + Fixed, Percent + } - public enum PositionMode { - Fixed, Percent - } + public enum SpacingMode + { + Length, Fixed, Percent, Proportional + } - public enum SpacingMode { - Length, Fixed, Percent, Proportional - } - - public enum RotateMode { - Tangent, Chain, ChainScale - } + public enum RotateMode + { + Tangent, Chain, ChainScale + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Skeleton.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Skeleton.cs index 7af941b..c564d4b 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Skeleton.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Skeleton.cs @@ -29,696 +29,786 @@ using System; -namespace Spine4_1_00 { - public class Skeleton { - internal SkeletonData data; - internal ExposedList bones; - internal ExposedList slots; - internal ExposedList drawOrder; - internal ExposedList ikConstraints; - internal ExposedList transformConstraints; - internal ExposedList pathConstraints; - internal ExposedList springConstraints; - internal ExposedList updateCache = new ExposedList(); - internal Skin skin; - internal float r = 1, g = 1, b = 1, a = 1; - private float scaleX = 1, scaleY = 1; - internal float x, y; - - public SkeletonData Data { get { return data; } } - public ExposedList Bones { get { return bones; } } - public ExposedList UpdateCacheList { get { return updateCache; } } - public ExposedList Slots { get { return slots; } } - public ExposedList DrawOrder { get { return drawOrder; } } - public ExposedList IkConstraints { get { return ikConstraints; } } - public ExposedList PathConstraints { get { return pathConstraints; } } - public ExposedList SpringConstraints { get { return SpringConstraints; } } - public ExposedList TransformConstraints { get { return transformConstraints; } } - - public Skin Skin { - /// The skeleton's current skin. May be null. - get { return skin; } - /// Sets a skin, . - set { SetSkin(value); } - } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public float ScaleX { get { return scaleX; } set { scaleX = value; } } - public float ScaleY { get { return scaleY * (Bone.yDown ? -1 : 1); } set { scaleY = value; } } - - [Obsolete("Use ScaleX instead. FlipX is when ScaleX is negative.")] - public bool FlipX { get { return scaleX < 0; } set { scaleX = value ? -1f : 1f; } } - - [Obsolete("Use ScaleY instead. FlipY is when ScaleY is negative.")] - public bool FlipY { get { return scaleY < 0; } set { scaleY = value ? -1f : 1f; } } - - /// Returns the root bone, or null if the skeleton has no bones. - public Bone RootBone { - get { return bones.Count == 0 ? null : bones.Items[0]; } - } - - public Skeleton (SkeletonData data) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - this.data = data; - - bones = new ExposedList(data.bones.Count); - Bone[] bonesItems = this.bones.Items; - foreach (BoneData boneData in data.bones) { - Bone bone; - if (boneData.parent == null) { - bone = new Bone(boneData, this, null); - } else { - Bone parent = bonesItems[boneData.parent.index]; - bone = new Bone(boneData, this, parent); - parent.children.Add(bone); - } - this.bones.Add(bone); - } - - slots = new ExposedList(data.slots.Count); - drawOrder = new ExposedList(data.slots.Count); - foreach (SlotData slotData in data.slots) { - Bone bone = bonesItems[slotData.boneData.index]; - Slot slot = new Slot(slotData, bone); - slots.Add(slot); - drawOrder.Add(slot); - } - - ikConstraints = new ExposedList(data.ikConstraints.Count); - foreach (IkConstraintData ikConstraintData in data.ikConstraints) - ikConstraints.Add(new IkConstraint(ikConstraintData, this)); - - transformConstraints = new ExposedList(data.transformConstraints.Count); - foreach (TransformConstraintData transformConstraintData in data.transformConstraints) - transformConstraints.Add(new TransformConstraint(transformConstraintData, this)); - - pathConstraints = new ExposedList(data.pathConstraints.Count); - foreach (PathConstraintData pathConstraintData in data.pathConstraints) - pathConstraints.Add(new PathConstraint(pathConstraintData, this)); - - springConstraints = new ExposedList(data.springConstraints.Count); - foreach (SpringConstraintData springConstraintData in data.springConstraints) - springConstraints.Add(new SpringConstraint(springConstraintData, this)); - - UpdateCache(); - } - - /// Copy constructor. - public Skeleton (Skeleton skeleton) { - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - data = skeleton.data; - - bones = new ExposedList(skeleton.bones.Count); - foreach (Bone bone in skeleton.bones) { - Bone newBone; - if (bone.parent == null) - newBone = new Bone(bone, this, null); - else { - Bone parent = bones.Items[bone.parent.data.index]; - newBone = new Bone(bone, this, parent); - parent.children.Add(newBone); - } - bones.Add(newBone); - } - - slots = new ExposedList(skeleton.slots.Count); - Bone[] bonesItems = bones.Items; - foreach (Slot slot in skeleton.slots) { - Bone bone = bonesItems[slot.bone.data.index]; - slots.Add(new Slot(slot, bone)); - } - - drawOrder = new ExposedList(slots.Count); - Slot[] slotsItems = slots.Items; - foreach (Slot slot in skeleton.drawOrder) - drawOrder.Add(slotsItems[slot.data.index]); - - ikConstraints = new ExposedList(skeleton.ikConstraints.Count); - foreach (IkConstraint ikConstraint in skeleton.ikConstraints) - ikConstraints.Add(new IkConstraint(ikConstraint, this)); - - transformConstraints = new ExposedList(skeleton.transformConstraints.Count); - foreach (TransformConstraint transformConstraint in skeleton.transformConstraints) - transformConstraints.Add(new TransformConstraint(transformConstraint, this)); - - pathConstraints = new ExposedList(skeleton.pathConstraints.Count); - foreach (PathConstraint pathConstraint in skeleton.pathConstraints) - pathConstraints.Add(new PathConstraint(pathConstraint, this)); - - springConstraints = new ExposedList(skeleton.springConstraints.Count); - foreach (SpringConstraint springConstraint in skeleton.springConstraints) - springConstraints.Add(new SpringConstraint(springConstraint, this)); - - skin = skeleton.skin; - r = skeleton.r; - g = skeleton.g; - b = skeleton.b; - a = skeleton.a; - scaleX = skeleton.scaleX; - scaleY = skeleton.scaleY; - - UpdateCache(); - } - - /// Caches information about bones and constraints. Must be called if the is modified or if bones, constraints, or - /// constraints, or weighted path attachments are added or removed. - public void UpdateCache () { - var updateCache = this.updateCache; - updateCache.Clear(); - - int boneCount = this.bones.Count; - Bone[] bones = this.bones.Items; - for (int i = 0; i < boneCount; i++) { - Bone bone = bones[i]; - bone.sorted = bone.data.skinRequired; - bone.active = !bone.sorted; - } - if (skin != null) { - BoneData[] skinBones = skin.bones.Items; - for (int i = 0, n = skin.bones.Count; i < n; i++) { - var bone = bones[skinBones[i].index]; - do { - bone.sorted = false; - bone.active = true; - bone = bone.parent; - } while (bone != null); - } - } - - int ikCount = this.ikConstraints.Count, transformCount = this.transformConstraints.Count, pathCount = this.pathConstraints.Count, - springCount = this.springConstraints.Count; - IkConstraint[] ikConstraints = this.ikConstraints.Items; - TransformConstraint[] transformConstraints = this.transformConstraints.Items; - PathConstraint[] pathConstraints = this.pathConstraints.Items; - SpringConstraint[] springConstraints = this.springConstraints.Items; - int constraintCount = ikCount + transformCount + pathCount + springCount; - for (int i = 0; i < constraintCount; i++) { - for (int ii = 0; ii < ikCount; ii++) { - IkConstraint constraint = ikConstraints[ii]; - if (constraint.data.order == i) { - SortIkConstraint(constraint); - goto continue_outer; - } - } - for (int ii = 0; ii < transformCount; ii++) { - TransformConstraint constraint = transformConstraints[ii]; - if (constraint.data.order == i) { - SortTransformConstraint(constraint); - goto continue_outer; - } - } - for (int ii = 0; ii < pathCount; ii++) { - PathConstraint constraint = pathConstraints[ii]; - if (constraint.data.order == i) { - SortPathConstraint(constraint); - goto continue_outer; - } - } - for (int ii = 0; ii < springCount; ii++) { - SpringConstraint constraint = springConstraints[ii]; - if (constraint.data.order == i) { - SortSpringConstraint(constraint); - goto continue_outer; - } - } - continue_outer: { } - } - - for (int i = 0; i < boneCount; i++) - SortBone(bones[i]); - } - - private void SortIkConstraint (IkConstraint constraint) { - constraint.active = constraint.target.active - && (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data))); - if (!constraint.active) return; - - Bone target = constraint.target; - SortBone(target); - - var constrained = constraint.bones; - Bone parent = constrained.Items[0]; - SortBone(parent); - - if (constrained.Count == 1) { - updateCache.Add(constraint); - SortReset(parent.children); - } else { - Bone child = constrained.Items[constrained.Count - 1]; - SortBone(child); - - updateCache.Add(constraint); - - SortReset(parent.children); - child.sorted = true; - } - } - - private void SortTransformConstraint (TransformConstraint constraint) { - constraint.active = constraint.target.active - && (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data))); - if (!constraint.active) return; - - SortBone(constraint.target); - - var constrained = constraint.bones.Items; - int boneCount = constraint.bones.Count; - if (constraint.data.local) { - for (int i = 0; i < boneCount; i++) { - Bone child = constrained[i]; - SortBone(child.parent); - SortBone(child); - } - } else { - for (int i = 0; i < boneCount; i++) - SortBone(constrained[i]); - } - - updateCache.Add(constraint); - - for (int i = 0; i < boneCount; i++) - SortReset(constrained[i].children); - for (int i = 0; i < boneCount; i++) - constrained[i].sorted = true; - } - - private void SortPathConstraint (PathConstraint constraint) { - constraint.active = constraint.target.bone.active - && (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data))); - if (!constraint.active) return; - - Slot slot = constraint.target; - int slotIndex = slot.data.index; - Bone slotBone = slot.bone; - if (skin != null) SortPathConstraintAttachment(skin, slotIndex, slotBone); - if (data.defaultSkin != null && data.defaultSkin != skin) - SortPathConstraintAttachment(data.defaultSkin, slotIndex, slotBone); - - Attachment attachment = slot.attachment; - if (attachment is PathAttachment) SortPathConstraintAttachment(attachment, slotBone); - - var constrained = constraint.bones.Items; - int boneCount = constraint.bones.Count; - for (int i = 0; i < boneCount; i++) - SortBone(constrained[i]); - - updateCache.Add(constraint); - - for (int i = 0; i < boneCount; i++) - SortReset(constrained[i].children); - for (int i = 0; i < boneCount; i++) - constrained[i].sorted = true; - } - - private void SortPathConstraintAttachment (Skin skin, int slotIndex, Bone slotBone) { - foreach (var entry in skin.Attachments) - if (entry.SlotIndex == slotIndex) SortPathConstraintAttachment(entry.Attachment, slotBone); - } - - private void SortPathConstraintAttachment (Attachment attachment, Bone slotBone) { - if (!(attachment is PathAttachment)) return; - int[] pathBones = ((PathAttachment)attachment).bones; - if (pathBones == null) - SortBone(slotBone); - else { - var bones = this.bones.Items; - for (int i = 0, n = pathBones.Length; i < n;) { - int nn = pathBones[i++]; - nn += i; - while (i < nn) - SortBone(bones[pathBones[i++]]); - } - } - } - - private void SortSpringConstraint (SpringConstraint constraint) { - constraint.active = !constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data)); - if (!constraint.active) return; - - Object[] constrained = constraint.bones.Items; - int boneCount = constraint.bones.Count; - for (int i = 0; i < boneCount; i++) - SortBone((Bone)constrained[i]); - - updateCache.Add(constraint); - - for (int i = 0; i < boneCount; i++) - SortReset(((Bone)constrained[i]).children); - for (int i = 0; i < boneCount; i++) - ((Bone)constrained[i]).sorted = true; - } - - private void SortBone (Bone bone) { - if (bone.sorted) return; - Bone parent = bone.parent; - if (parent != null) SortBone(parent); - bone.sorted = true; - updateCache.Add(bone); - } - - private static void SortReset (ExposedList bones) { - Bone[] bonesItems = bones.Items; - for (int i = 0, n = bones.Count; i < n; i++) { - Bone bone = bonesItems[i]; - if (!bone.active) continue; - if (bone.sorted) SortReset(bone.children); - bone.sorted = false; - } - } - - /// - /// Updates the world transform for each bone and applies all constraints. - /// - /// See World transforms in the Spine - /// Runtimes Guide. - /// - public void UpdateWorldTransform () { - Bone[] bones = this.bones.Items; - for (int i = 0, n = this.bones.Count; 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; - } - - var updateCache = this.updateCache.Items; - for (int i = 0, n = this.updateCache.Count; i < n; i++) - updateCache[i].Update(); - } - - /// - /// Temporarily sets the root bone as a child of the specified bone, then updates the world transform for each bone and applies - /// all constraints. - /// - public void UpdateWorldTransform (Bone parent) { - if (parent == null) throw new ArgumentNullException("parent", "parent cannot be null."); - - // Apply the parent bone transform to the root bone. The root bone always inherits scale, rotation and reflection. - Bone rootBone = this.RootBone; - 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 rotationY = rootBone.rotation + 90 + rootBone.shearY; - float la = MathUtils.CosDeg(rootBone.rotation + rootBone.shearX) * rootBone.scaleX; - float lb = MathUtils.CosDeg(rotationY) * rootBone.scaleY; - float lc = MathUtils.SinDeg(rootBone.rotation + rootBone.shearX) * rootBone.scaleX; - float ld = MathUtils.SinDeg(rotationY) * 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. - var updateCache = this.updateCache.Items; - for (int i = 0, n = this.updateCache.Count; i < n; i++) { - var updatable = updateCache[i]; - if (updatable != rootBone) updatable.Update(); - } - } - - /// Sets the bones, constraints, and slots to their setup pose values. - public void SetToSetupPose () { - SetBonesToSetupPose(); - SetSlotsToSetupPose(); - } - - /// Sets the bones and constraints to their setup pose values. - public void SetBonesToSetupPose () { - var bones = this.bones.Items; - for (int i = 0, n = this.bones.Count; i < n; i++) - bones[i].SetToSetupPose(); - - IkConstraint[] ikConstraints = this.ikConstraints.Items; - for (int i = 0, n = this.ikConstraints.Count; i < n; i++) { - IkConstraint constraint = ikConstraints[i]; - IkConstraintData data = constraint.data; - constraint.mix = data.mix; - constraint.softness = data.softness; - constraint.bendDirection = data.bendDirection; - constraint.compress = data.compress; - constraint.stretch = data.stretch; - } - - TransformConstraint[] transformConstraints = this.transformConstraints.Items; - for (int i = 0, n = this.transformConstraints.Count; i < n; i++) { - TransformConstraint constraint = transformConstraints[i]; - TransformConstraintData data = constraint.data; - constraint.mixRotate = data.mixRotate; - constraint.mixX = data.mixX; - constraint.mixY = data.mixY; - constraint.mixScaleX = data.mixScaleX; - constraint.mixScaleY = data.mixScaleY; - constraint.mixShearY = data.mixShearY; - } - - PathConstraint[] pathConstraints = this.pathConstraints.Items; - for (int i = 0, n = this.pathConstraints.Count; i < n; i++) { - PathConstraint constraint = pathConstraints[i]; - PathConstraintData data = constraint.data; - constraint.position = data.position; - constraint.spacing = data.spacing; - constraint.mixRotate = data.mixRotate; - constraint.mixX = data.mixX; - constraint.mixY = data.mixY; - } - - SpringConstraint[] springConstraints = this.springConstraints.Items; - for (int i = 0, n = this.springConstraints.Count; i < n; i++) { - SpringConstraint constraint = springConstraints[i]; - SpringConstraintData data = constraint.data; - constraint.mix = data.mix; - constraint.friction = data.friction; - constraint.gravity = data.gravity; - constraint.wind = data.wind; - constraint.stiffness = data.stiffness; - constraint.damping = data.damping; - constraint.rope = data.rope; - constraint.stretch = data.stretch; - } - } - - public void SetSlotsToSetupPose () { - var slots = this.slots.Items; - int n = this.slots.Count; - Array.Copy(slots, 0, drawOrder.Items, 0, n); - for (int i = 0; i < n; i++) - slots[i].SetToSetupPose(); - } - - /// Finds a bone by comparing each bone's name. It is more efficient to cache the results of this method than to call it - /// repeatedly. - /// May be null. - public Bone FindBone (string boneName) { - if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); - var bones = this.bones.Items; - for (int i = 0, n = this.bones.Count; i < n; i++) { - Bone bone = bones[i]; - if (bone.data.name == boneName) return bone; - } - return null; - } - - /// Finds a slot by comparing each slot's name. It is more efficient to cache the results of this method than to call it - /// repeatedly. - /// May be null. - public Slot FindSlot (string slotName) { - if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); - var slots = this.slots.Items; - for (int i = 0, n = this.slots.Count; i < n; i++) { - Slot slot = slots[i]; - if (slot.data.name == slotName) return slot; - } - return null; - } - - /// Sets a skin by name (). - public void SetSkin (string skinName) { - Skin foundSkin = data.FindSkin(skinName); - if (foundSkin == null) throw new ArgumentException("Skin not found: " + skinName, "skinName"); - SetSkin(foundSkin); - } - - /// - /// Sets the skin used to look up attachments before looking in the . If the - /// skin is changed, is called. - /// - /// 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 - /// . - /// Also, often 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. - /// - /// May be null. - public void SetSkin (Skin newSkin) { - if (newSkin == skin) return; - if (newSkin != null) { - if (skin != null) - newSkin.AttachAll(this, skin); - else { - Slot[] slots = this.slots.Items; - for (int i = 0, n = this.slots.Count; i < n; i++) { - Slot slot = slots[i]; - string name = slot.data.attachmentName; - if (name != null) { - Attachment attachment = newSkin.GetAttachment(i, name); - if (attachment != null) slot.Attachment = attachment; - } - } - } - } - skin = newSkin; - UpdateCache(); - } - - /// Finds an attachment by looking in the and using the slot name and attachment name. - /// May be null. - public Attachment GetAttachment (string slotName, string attachmentName) { - return GetAttachment(data.FindSlot(slotName).index, attachmentName); - } - - /// Finds an attachment by looking in the skin and skeletonData.defaultSkin using the slot index and attachment name.First the skin is checked and if the attachment was not found, the default skin is checked. - /// May be null. - public Attachment GetAttachment (int slotIndex, string attachmentName) { - if (attachmentName == null) throw new ArgumentNullException("attachmentName", "attachmentName cannot be null."); - if (skin != null) { - Attachment attachment = skin.GetAttachment(slotIndex, attachmentName); - if (attachment != null) return attachment; - } - return data.defaultSkin != null ? data.defaultSkin.GetAttachment(slotIndex, attachmentName) : null; - } - - /// A convenience method to set an attachment by finding the slot with FindSlot, finding the attachment with GetAttachment, then setting the slot's slot.Attachment. - /// May be null to clear the slot's attachment. - public void SetAttachment (string slotName, string attachmentName) { - if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); - Slot[] slots = this.slots.Items; - for (int i = 0, n = this.slots.Count; i < n; i++) { - Slot slot = slots[i]; - if (slot.data.name == slotName) { - Attachment attachment = null; - if (attachmentName != null) { - attachment = GetAttachment(i, attachmentName); - if (attachment == null) throw new Exception("Attachment not found: " + attachmentName + ", for slot: " + slotName); - } - slot.Attachment = attachment; - return; - } - } - throw new Exception("Slot not found: " + slotName); - } - - /// Finds an IK constraint by comparing each IK constraint's name. It is more efficient to cache the results of this method - /// than to call it repeatedly. - /// May be null. - public IkConstraint FindIkConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - var ikConstraints = this.ikConstraints.Items; - for (int i = 0, n = this.ikConstraints.Count; i < n; i++) { - IkConstraint ikConstraint = ikConstraints[i]; - if (ikConstraint.data.name == constraintName) return ikConstraint; - } - return null; - } - - /// Finds a transform constraint by comparing each transform constraint's name. It is more efficient to cache the results of - /// this method than to call it repeatedly. - /// May be null. - public TransformConstraint FindTransformConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - var transformConstraints = this.transformConstraints.Items; - for (int i = 0, n = this.transformConstraints.Count; i < n; i++) { - TransformConstraint transformConstraint = transformConstraints[i]; - if (transformConstraint.data.Name == constraintName) return transformConstraint; - } - return null; - } - - /// Finds a path constraint by comparing each path constraint's name. It is more efficient to cache the results of this method - /// than to call it repeatedly. - /// May be null. - public PathConstraint FindPathConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - var pathConstraints = this.pathConstraints.Items; - for (int i = 0, n = this.pathConstraints.Count; i < n; i++) { - PathConstraint constraint = pathConstraints[i]; - if (constraint.data.Name.Equals(constraintName)) return constraint; - } - return null; - } - - /// Finds a spring constraint by comparing each spring constraint's name. It is more efficient to cache the results of this - /// method than to call it repeatedly. - /// May be null. - public SpringConstraint FindSpringConstraint (String constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - SpringConstraint[] springConstraints = this.springConstraints.Items; - for (int i = 0, n = this.springConstraints.Count; i < n; i++) { - SpringConstraint constraint = springConstraints[i]; - if (constraint.data.name.Equals(constraintName)) return constraint; - } - return null; - } - - /// Returns the axis aligned bounding box (AABB) of the region and mesh attachments for the current pose. - /// The horizontal distance between the skeleton origin and the left side of the AABB. - /// The vertical distance between the skeleton origin and the bottom side of the AABB. - /// The width of the AABB - /// The height of the AABB. - /// Reference to hold a float[]. May be a null reference. This method will assign it a new float[] with the appropriate size as needed. - public void GetBounds (out float x, out float y, out float width, out float height, ref float[] vertexBuffer) { - float[] temp = vertexBuffer; - temp = temp ?? new float[8]; - var drawOrder = this.drawOrder.Items; - float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; - for (int i = 0, n = this.drawOrder.Count; i < n; i++) { - Slot slot = drawOrder[i]; - if (!slot.bone.active) continue; - int verticesLength = 0; - float[] vertices = null; - Attachment attachment = slot.attachment; - RegionAttachment region = attachment as RegionAttachment; - if (region != null) { - verticesLength = 8; - vertices = temp; - if (vertices.Length < 8) vertices = temp = new float[8]; - region.ComputeWorldVertices(slot, temp, 0, 2); - } else { - var meshAttachment = attachment as MeshAttachment; - if (meshAttachment != null) { - MeshAttachment mesh = meshAttachment; - verticesLength = mesh.WorldVerticesLength; - vertices = temp; - if (vertices.Length < verticesLength) vertices = temp = new float[verticesLength]; - mesh.ComputeWorldVertices(slot, 0, verticesLength, temp, 0, 2); - } - } - - if (vertices != null) { - for (int ii = 0; ii < verticesLength; ii += 2) { - float vx = vertices[ii], vy = vertices[ii + 1]; - minX = Math.Min(minX, vx); - minY = Math.Min(minY, vy); - maxX = Math.Max(maxX, vx); - maxY = Math.Max(maxY, vy); - } - } - } - x = minX; - y = minY; - width = maxX - minX; - height = maxY - minY; - vertexBuffer = temp; - } - } +namespace Spine4_1_00 +{ + public class Skeleton + { + internal SkeletonData data; + internal ExposedList bones; + internal ExposedList slots; + internal ExposedList drawOrder; + internal ExposedList ikConstraints; + internal ExposedList transformConstraints; + internal ExposedList pathConstraints; + internal ExposedList springConstraints; + internal ExposedList updateCache = new ExposedList(); + internal Skin skin; + internal float r = 1, g = 1, b = 1, a = 1; + private float scaleX = 1, scaleY = 1; + internal float x, y; + + public SkeletonData Data { get { return data; } } + public ExposedList Bones { get { return bones; } } + public ExposedList UpdateCacheList { get { return updateCache; } } + public ExposedList Slots { get { return slots; } } + public ExposedList DrawOrder { get { return drawOrder; } } + public ExposedList IkConstraints { get { return ikConstraints; } } + public ExposedList PathConstraints { get { return pathConstraints; } } + public ExposedList SpringConstraints { get { return SpringConstraints; } } + public ExposedList TransformConstraints { get { return transformConstraints; } } + + public Skin Skin + { + /// The skeleton's current skin. May be null. + get { return skin; } + /// Sets a skin, . + set { SetSkin(value); } + } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + public float ScaleY { get { return scaleY * (Bone.yDown ? -1 : 1); } set { scaleY = value; } } + + [Obsolete("Use ScaleX instead. FlipX is when ScaleX is negative.")] + public bool FlipX { get { return scaleX < 0; } set { scaleX = value ? -1f : 1f; } } + + [Obsolete("Use ScaleY instead. FlipY is when ScaleY is negative.")] + public bool FlipY { get { return scaleY < 0; } set { scaleY = value ? -1f : 1f; } } + + /// Returns the root bone, or null if the skeleton has no bones. + public Bone RootBone + { + get { return bones.Count == 0 ? null : bones.Items[0]; } + } + + public Skeleton(SkeletonData data) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + this.data = data; + + bones = new ExposedList(data.bones.Count); + Bone[] bonesItems = this.bones.Items; + foreach (BoneData boneData in data.bones) + { + Bone bone; + if (boneData.parent == null) + { + bone = new Bone(boneData, this, null); + } + else + { + Bone parent = bonesItems[boneData.parent.index]; + bone = new Bone(boneData, this, parent); + parent.children.Add(bone); + } + this.bones.Add(bone); + } + + slots = new ExposedList(data.slots.Count); + drawOrder = new ExposedList(data.slots.Count); + foreach (SlotData slotData in data.slots) + { + Bone bone = bonesItems[slotData.boneData.index]; + Slot slot = new Slot(slotData, bone); + slots.Add(slot); + drawOrder.Add(slot); + } + + ikConstraints = new ExposedList(data.ikConstraints.Count); + foreach (IkConstraintData ikConstraintData in data.ikConstraints) + ikConstraints.Add(new IkConstraint(ikConstraintData, this)); + + transformConstraints = new ExposedList(data.transformConstraints.Count); + foreach (TransformConstraintData transformConstraintData in data.transformConstraints) + transformConstraints.Add(new TransformConstraint(transformConstraintData, this)); + + pathConstraints = new ExposedList(data.pathConstraints.Count); + foreach (PathConstraintData pathConstraintData in data.pathConstraints) + pathConstraints.Add(new PathConstraint(pathConstraintData, this)); + + springConstraints = new ExposedList(data.springConstraints.Count); + foreach (SpringConstraintData springConstraintData in data.springConstraints) + springConstraints.Add(new SpringConstraint(springConstraintData, this)); + + UpdateCache(); + } + + /// Copy constructor. + public Skeleton(Skeleton skeleton) + { + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + data = skeleton.data; + + bones = new ExposedList(skeleton.bones.Count); + foreach (Bone bone in skeleton.bones) + { + Bone newBone; + if (bone.parent == null) + newBone = new Bone(bone, this, null); + else + { + Bone parent = bones.Items[bone.parent.data.index]; + newBone = new Bone(bone, this, parent); + parent.children.Add(newBone); + } + bones.Add(newBone); + } + + slots = new ExposedList(skeleton.slots.Count); + Bone[] bonesItems = bones.Items; + foreach (Slot slot in skeleton.slots) + { + Bone bone = bonesItems[slot.bone.data.index]; + slots.Add(new Slot(slot, bone)); + } + + drawOrder = new ExposedList(slots.Count); + Slot[] slotsItems = slots.Items; + foreach (Slot slot in skeleton.drawOrder) + drawOrder.Add(slotsItems[slot.data.index]); + + ikConstraints = new ExposedList(skeleton.ikConstraints.Count); + foreach (IkConstraint ikConstraint in skeleton.ikConstraints) + ikConstraints.Add(new IkConstraint(ikConstraint, this)); + + transformConstraints = new ExposedList(skeleton.transformConstraints.Count); + foreach (TransformConstraint transformConstraint in skeleton.transformConstraints) + transformConstraints.Add(new TransformConstraint(transformConstraint, this)); + + pathConstraints = new ExposedList(skeleton.pathConstraints.Count); + foreach (PathConstraint pathConstraint in skeleton.pathConstraints) + pathConstraints.Add(new PathConstraint(pathConstraint, this)); + + springConstraints = new ExposedList(skeleton.springConstraints.Count); + foreach (SpringConstraint springConstraint in skeleton.springConstraints) + springConstraints.Add(new SpringConstraint(springConstraint, this)); + + skin = skeleton.skin; + r = skeleton.r; + g = skeleton.g; + b = skeleton.b; + a = skeleton.a; + scaleX = skeleton.scaleX; + scaleY = skeleton.scaleY; + + UpdateCache(); + } + + /// Caches information about bones and constraints. Must be called if the is modified or if bones, constraints, or + /// constraints, or weighted path attachments are added or removed. + public void UpdateCache() + { + var updateCache = this.updateCache; + updateCache.Clear(); + + int boneCount = this.bones.Count; + Bone[] bones = this.bones.Items; + for (int i = 0; i < boneCount; i++) + { + Bone bone = bones[i]; + bone.sorted = bone.data.skinRequired; + bone.active = !bone.sorted; + } + if (skin != null) + { + BoneData[] skinBones = skin.bones.Items; + for (int i = 0, n = skin.bones.Count; i < n; i++) + { + var bone = bones[skinBones[i].index]; + do + { + bone.sorted = false; + bone.active = true; + bone = bone.parent; + } while (bone != null); + } + } + + int ikCount = this.ikConstraints.Count, transformCount = this.transformConstraints.Count, pathCount = this.pathConstraints.Count, + springCount = this.springConstraints.Count; + IkConstraint[] ikConstraints = this.ikConstraints.Items; + TransformConstraint[] transformConstraints = this.transformConstraints.Items; + PathConstraint[] pathConstraints = this.pathConstraints.Items; + SpringConstraint[] springConstraints = this.springConstraints.Items; + int constraintCount = ikCount + transformCount + pathCount + springCount; + for (int i = 0; i < constraintCount; i++) + { + for (int ii = 0; ii < ikCount; ii++) + { + IkConstraint constraint = ikConstraints[ii]; + if (constraint.data.order == i) + { + SortIkConstraint(constraint); + goto continue_outer; + } + } + for (int ii = 0; ii < transformCount; ii++) + { + TransformConstraint constraint = transformConstraints[ii]; + if (constraint.data.order == i) + { + SortTransformConstraint(constraint); + goto continue_outer; + } + } + for (int ii = 0; ii < pathCount; ii++) + { + PathConstraint constraint = pathConstraints[ii]; + if (constraint.data.order == i) + { + SortPathConstraint(constraint); + goto continue_outer; + } + } + for (int ii = 0; ii < springCount; ii++) + { + SpringConstraint constraint = springConstraints[ii]; + if (constraint.data.order == i) + { + SortSpringConstraint(constraint); + goto continue_outer; + } + } + continue_outer: { } + } + + for (int i = 0; i < boneCount; i++) + SortBone(bones[i]); + } + + private void SortIkConstraint(IkConstraint constraint) + { + constraint.active = constraint.target.active + && (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data))); + if (!constraint.active) return; + + Bone target = constraint.target; + SortBone(target); + + var constrained = constraint.bones; + Bone parent = constrained.Items[0]; + SortBone(parent); + + if (constrained.Count == 1) + { + updateCache.Add(constraint); + SortReset(parent.children); + } + else + { + Bone child = constrained.Items[constrained.Count - 1]; + SortBone(child); + + updateCache.Add(constraint); + + SortReset(parent.children); + child.sorted = true; + } + } + + private void SortTransformConstraint(TransformConstraint constraint) + { + constraint.active = constraint.target.active + && (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data))); + if (!constraint.active) return; + + SortBone(constraint.target); + + var constrained = constraint.bones.Items; + int boneCount = constraint.bones.Count; + if (constraint.data.local) + { + for (int i = 0; i < boneCount; i++) + { + Bone child = constrained[i]; + SortBone(child.parent); + SortBone(child); + } + } + else + { + for (int i = 0; i < boneCount; i++) + SortBone(constrained[i]); + } + + updateCache.Add(constraint); + + for (int i = 0; i < boneCount; i++) + SortReset(constrained[i].children); + for (int i = 0; i < boneCount; i++) + constrained[i].sorted = true; + } + + private void SortPathConstraint(PathConstraint constraint) + { + constraint.active = constraint.target.bone.active + && (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data))); + if (!constraint.active) return; + + Slot slot = constraint.target; + int slotIndex = slot.data.index; + Bone slotBone = slot.bone; + if (skin != null) SortPathConstraintAttachment(skin, slotIndex, slotBone); + if (data.defaultSkin != null && data.defaultSkin != skin) + SortPathConstraintAttachment(data.defaultSkin, slotIndex, slotBone); + + Attachment attachment = slot.attachment; + if (attachment is PathAttachment) SortPathConstraintAttachment(attachment, slotBone); + + var constrained = constraint.bones.Items; + int boneCount = constraint.bones.Count; + for (int i = 0; i < boneCount; i++) + SortBone(constrained[i]); + + updateCache.Add(constraint); + + for (int i = 0; i < boneCount; i++) + SortReset(constrained[i].children); + for (int i = 0; i < boneCount; i++) + constrained[i].sorted = true; + } + + private void SortPathConstraintAttachment(Skin skin, int slotIndex, Bone slotBone) + { + foreach (var entry in skin.Attachments) + if (entry.SlotIndex == slotIndex) SortPathConstraintAttachment(entry.Attachment, slotBone); + } + + private void SortPathConstraintAttachment(Attachment attachment, Bone slotBone) + { + if (!(attachment is PathAttachment)) return; + int[] pathBones = ((PathAttachment)attachment).bones; + if (pathBones == null) + SortBone(slotBone); + else + { + var bones = this.bones.Items; + for (int i = 0, n = pathBones.Length; i < n;) + { + int nn = pathBones[i++]; + nn += i; + while (i < nn) + SortBone(bones[pathBones[i++]]); + } + } + } + + private void SortSpringConstraint(SpringConstraint constraint) + { + constraint.active = !constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data)); + if (!constraint.active) return; + + Object[] constrained = constraint.bones.Items; + int boneCount = constraint.bones.Count; + for (int i = 0; i < boneCount; i++) + SortBone((Bone)constrained[i]); + + updateCache.Add(constraint); + + for (int i = 0; i < boneCount; i++) + SortReset(((Bone)constrained[i]).children); + for (int i = 0; i < boneCount; i++) + ((Bone)constrained[i]).sorted = true; + } + + private void SortBone(Bone bone) + { + if (bone.sorted) return; + Bone parent = bone.parent; + if (parent != null) SortBone(parent); + bone.sorted = true; + updateCache.Add(bone); + } + + private static void SortReset(ExposedList bones) + { + Bone[] bonesItems = bones.Items; + for (int i = 0, n = bones.Count; i < n; i++) + { + Bone bone = bonesItems[i]; + if (!bone.active) continue; + if (bone.sorted) SortReset(bone.children); + bone.sorted = false; + } + } + + /// + /// Updates the world transform for each bone and applies all constraints. + /// + /// See World transforms in the Spine + /// Runtimes Guide. + /// + public void UpdateWorldTransform() + { + Bone[] bones = this.bones.Items; + for (int i = 0, n = this.bones.Count; 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; + } + + var updateCache = this.updateCache.Items; + for (int i = 0, n = this.updateCache.Count; i < n; i++) + updateCache[i].Update(); + } + + /// + /// Temporarily sets the root bone as a child of the specified bone, then updates the world transform for each bone and applies + /// all constraints. + /// + public void UpdateWorldTransform(Bone parent) + { + if (parent == null) throw new ArgumentNullException("parent", "parent cannot be null."); + + // Apply the parent bone transform to the root bone. The root bone always inherits scale, rotation and reflection. + Bone rootBone = this.RootBone; + 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 rotationY = rootBone.rotation + 90 + rootBone.shearY; + float la = MathUtils.CosDeg(rootBone.rotation + rootBone.shearX) * rootBone.scaleX; + float lb = MathUtils.CosDeg(rotationY) * rootBone.scaleY; + float lc = MathUtils.SinDeg(rootBone.rotation + rootBone.shearX) * rootBone.scaleX; + float ld = MathUtils.SinDeg(rotationY) * 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. + var updateCache = this.updateCache.Items; + for (int i = 0, n = this.updateCache.Count; i < n; i++) + { + var updatable = updateCache[i]; + if (updatable != rootBone) updatable.Update(); + } + } + + /// Sets the bones, constraints, and slots to their setup pose values. + public void SetToSetupPose() + { + SetBonesToSetupPose(); + SetSlotsToSetupPose(); + } + + /// Sets the bones and constraints to their setup pose values. + public void SetBonesToSetupPose() + { + var bones = this.bones.Items; + for (int i = 0, n = this.bones.Count; i < n; i++) + bones[i].SetToSetupPose(); + + IkConstraint[] ikConstraints = this.ikConstraints.Items; + for (int i = 0, n = this.ikConstraints.Count; i < n; i++) + { + IkConstraint constraint = ikConstraints[i]; + IkConstraintData data = constraint.data; + constraint.mix = data.mix; + constraint.softness = data.softness; + constraint.bendDirection = data.bendDirection; + constraint.compress = data.compress; + constraint.stretch = data.stretch; + } + + TransformConstraint[] transformConstraints = this.transformConstraints.Items; + for (int i = 0, n = this.transformConstraints.Count; i < n; i++) + { + TransformConstraint constraint = transformConstraints[i]; + TransformConstraintData data = constraint.data; + constraint.mixRotate = data.mixRotate; + constraint.mixX = data.mixX; + constraint.mixY = data.mixY; + constraint.mixScaleX = data.mixScaleX; + constraint.mixScaleY = data.mixScaleY; + constraint.mixShearY = data.mixShearY; + } + + PathConstraint[] pathConstraints = this.pathConstraints.Items; + for (int i = 0, n = this.pathConstraints.Count; i < n; i++) + { + PathConstraint constraint = pathConstraints[i]; + PathConstraintData data = constraint.data; + constraint.position = data.position; + constraint.spacing = data.spacing; + constraint.mixRotate = data.mixRotate; + constraint.mixX = data.mixX; + constraint.mixY = data.mixY; + } + + SpringConstraint[] springConstraints = this.springConstraints.Items; + for (int i = 0, n = this.springConstraints.Count; i < n; i++) + { + SpringConstraint constraint = springConstraints[i]; + SpringConstraintData data = constraint.data; + constraint.mix = data.mix; + constraint.friction = data.friction; + constraint.gravity = data.gravity; + constraint.wind = data.wind; + constraint.stiffness = data.stiffness; + constraint.damping = data.damping; + constraint.rope = data.rope; + constraint.stretch = data.stretch; + } + } + + public void SetSlotsToSetupPose() + { + var slots = this.slots.Items; + int n = this.slots.Count; + Array.Copy(slots, 0, drawOrder.Items, 0, n); + for (int i = 0; i < n; i++) + slots[i].SetToSetupPose(); + } + + /// Finds a bone by comparing each bone's name. It is more efficient to cache the results of this method than to call it + /// repeatedly. + /// May be null. + public Bone FindBone(string boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); + var bones = this.bones.Items; + for (int i = 0, n = this.bones.Count; i < n; i++) + { + Bone bone = bones[i]; + if (bone.data.name == boneName) return bone; + } + return null; + } + + /// Finds a slot by comparing each slot's name. It is more efficient to cache the results of this method than to call it + /// repeatedly. + /// May be null. + public Slot FindSlot(string slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + var slots = this.slots.Items; + for (int i = 0, n = this.slots.Count; i < n; i++) + { + Slot slot = slots[i]; + if (slot.data.name == slotName) return slot; + } + return null; + } + + /// Sets a skin by name (). + public void SetSkin(string skinName) + { + Skin foundSkin = data.FindSkin(skinName); + if (foundSkin == null) throw new ArgumentException("Skin not found: " + skinName, "skinName"); + SetSkin(foundSkin); + } + + /// + /// Sets the skin used to look up attachments before looking in the . If the + /// skin is changed, is called. + /// + /// 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 + /// . + /// Also, often 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. + /// + /// May be null. + public void SetSkin(Skin newSkin) + { + if (newSkin == skin) return; + if (newSkin != null) + { + if (skin != null) + newSkin.AttachAll(this, skin); + else + { + Slot[] slots = this.slots.Items; + for (int i = 0, n = this.slots.Count; i < n; i++) + { + Slot slot = slots[i]; + string name = slot.data.attachmentName; + if (name != null) + { + Attachment attachment = newSkin.GetAttachment(i, name); + if (attachment != null) slot.Attachment = attachment; + } + } + } + } + skin = newSkin; + UpdateCache(); + } + + /// Finds an attachment by looking in the and using the slot name and attachment name. + /// May be null. + public Attachment GetAttachment(string slotName, string attachmentName) + { + return GetAttachment(data.FindSlot(slotName).index, attachmentName); + } + + /// Finds an attachment by looking in the skin and skeletonData.defaultSkin using the slot index and attachment name.First the skin is checked and if the attachment was not found, the default skin is checked. + /// May be null. + public Attachment GetAttachment(int slotIndex, string attachmentName) + { + if (attachmentName == null) throw new ArgumentNullException("attachmentName", "attachmentName cannot be null."); + if (skin != null) + { + Attachment attachment = skin.GetAttachment(slotIndex, attachmentName); + if (attachment != null) return attachment; + } + return data.defaultSkin != null ? data.defaultSkin.GetAttachment(slotIndex, attachmentName) : null; + } + + /// A convenience method to set an attachment by finding the slot with FindSlot, finding the attachment with GetAttachment, then setting the slot's slot.Attachment. + /// May be null to clear the slot's attachment. + public void SetAttachment(string slotName, string attachmentName) + { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + Slot[] slots = this.slots.Items; + for (int i = 0, n = this.slots.Count; i < n; i++) + { + Slot slot = slots[i]; + if (slot.data.name == slotName) + { + Attachment attachment = null; + if (attachmentName != null) + { + attachment = GetAttachment(i, attachmentName); + if (attachment == null) throw new Exception("Attachment not found: " + attachmentName + ", for slot: " + slotName); + } + slot.Attachment = attachment; + return; + } + } + throw new Exception("Slot not found: " + slotName); + } + + /// Finds an IK constraint by comparing each IK constraint's name. It is more efficient to cache the results of this method + /// than to call it repeatedly. + /// May be null. + public IkConstraint FindIkConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + var ikConstraints = this.ikConstraints.Items; + for (int i = 0, n = this.ikConstraints.Count; i < n; i++) + { + IkConstraint ikConstraint = ikConstraints[i]; + if (ikConstraint.data.name == constraintName) return ikConstraint; + } + return null; + } + + /// Finds a transform constraint by comparing each transform constraint's name. It is more efficient to cache the results of + /// this method than to call it repeatedly. + /// May be null. + public TransformConstraint FindTransformConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + var transformConstraints = this.transformConstraints.Items; + for (int i = 0, n = this.transformConstraints.Count; i < n; i++) + { + TransformConstraint transformConstraint = transformConstraints[i]; + if (transformConstraint.data.Name == constraintName) return transformConstraint; + } + return null; + } + + /// Finds a path constraint by comparing each path constraint's name. It is more efficient to cache the results of this method + /// than to call it repeatedly. + /// May be null. + public PathConstraint FindPathConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + var pathConstraints = this.pathConstraints.Items; + for (int i = 0, n = this.pathConstraints.Count; i < n; i++) + { + PathConstraint constraint = pathConstraints[i]; + if (constraint.data.Name.Equals(constraintName)) return constraint; + } + return null; + } + + /// Finds a spring constraint by comparing each spring constraint's name. It is more efficient to cache the results of this + /// method than to call it repeatedly. + /// May be null. + public SpringConstraint FindSpringConstraint(String constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + SpringConstraint[] springConstraints = this.springConstraints.Items; + for (int i = 0, n = this.springConstraints.Count; i < n; i++) + { + SpringConstraint constraint = springConstraints[i]; + if (constraint.data.name.Equals(constraintName)) return constraint; + } + return null; + } + + /// Returns the axis aligned bounding box (AABB) of the region and mesh attachments for the current pose. + /// The horizontal distance between the skeleton origin and the left side of the AABB. + /// The vertical distance between the skeleton origin and the bottom side of the AABB. + /// The width of the AABB + /// The height of the AABB. + /// Reference to hold a float[]. May be a null reference. This method will assign it a new float[] with the appropriate size as needed. + public void GetBounds(out float x, out float y, out float width, out float height, ref float[] vertexBuffer) + { + float[] temp = vertexBuffer; + temp = temp ?? new float[8]; + var drawOrder = this.drawOrder.Items; + float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; + for (int i = 0, n = this.drawOrder.Count; i < n; i++) + { + Slot slot = drawOrder[i]; + if (!slot.bone.active) continue; + int verticesLength = 0; + float[] vertices = null; + Attachment attachment = slot.attachment; + RegionAttachment region = attachment as RegionAttachment; + if (region != null) + { + verticesLength = 8; + vertices = temp; + if (vertices.Length < 8) vertices = temp = new float[8]; + region.ComputeWorldVertices(slot, temp, 0, 2); + } + else + { + var meshAttachment = attachment as MeshAttachment; + if (meshAttachment != null) + { + MeshAttachment mesh = meshAttachment; + verticesLength = mesh.WorldVerticesLength; + vertices = temp; + if (vertices.Length < verticesLength) vertices = temp = new float[verticesLength]; + mesh.ComputeWorldVertices(slot, 0, verticesLength, temp, 0, 2); + } + } + + if (vertices != null) + { + for (int ii = 0; ii < verticesLength; ii += 2) + { + float vx = vertices[ii], vy = vertices[ii + 1]; + minX = Math.Min(minX, vx); + minY = Math.Min(minY, vy); + maxX = Math.Max(maxX, vx); + maxY = Math.Max(maxY, vy); + } + } + } + x = minX; + y = minY; + width = maxX - minX; + height = maxY - minY; + vertexBuffer = temp; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/SkeletonBinary.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/SkeletonBinary.cs index a2e9c68..4df465a 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/SkeletonBinary.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/SkeletonBinary.cs @@ -32,7 +32,6 @@ #endif using System; -using System.Collections.Generic; using System.IO; using System.Runtime.Serialization; @@ -41,44 +40,48 @@ using Windows.Storage; #endif -namespace Spine4_1_00 { - public class SkeletonBinary : SkeletonLoader { - public const int BONE_ROTATE = 0; - public const int BONE_TRANSLATE = 1; - public const int BONE_TRANSLATEX = 2; - public const int BONE_TRANSLATEY = 3; - public const int BONE_SCALE = 4; - public const int BONE_SCALEX = 5; - public const int BONE_SCALEY = 6; - public const int BONE_SHEAR = 7; - public const int BONE_SHEARX = 8; - public const int BONE_SHEARY = 9; - - public const int SLOT_ATTACHMENT = 0; - public const int SLOT_RGBA = 1; - public const int SLOT_RGB = 2; - public const int SLOT_RGBA2 = 3; - public const int SLOT_RGB2 = 4; - public const int SLOT_ALPHA = 5; - - public const int ATTACHMENT_DEFORM = 0; - public const int ATTACHMENT_SEQUENCE = 1; - - public const int PATH_POSITION = 0; - public const int PATH_SPACING = 1; - public const int PATH_MIX = 2; - - public const int CURVE_LINEAR = 0; - public const int CURVE_STEPPED = 1; - public const int CURVE_BEZIER = 2; - - public SkeletonBinary (AttachmentLoader attachmentLoader) - : base(attachmentLoader) { - } - - public SkeletonBinary (params Atlas[] atlasArray) - : base(atlasArray) { - } +namespace Spine4_1_00 +{ + public class SkeletonBinary : SkeletonLoader + { + public const int BONE_ROTATE = 0; + public const int BONE_TRANSLATE = 1; + public const int BONE_TRANSLATEX = 2; + public const int BONE_TRANSLATEY = 3; + public const int BONE_SCALE = 4; + public const int BONE_SCALEX = 5; + public const int BONE_SCALEY = 6; + public const int BONE_SHEAR = 7; + public const int BONE_SHEARX = 8; + public const int BONE_SHEARY = 9; + + public const int SLOT_ATTACHMENT = 0; + public const int SLOT_RGBA = 1; + public const int SLOT_RGB = 2; + public const int SLOT_RGBA2 = 3; + public const int SLOT_RGB2 = 4; + public const int SLOT_ALPHA = 5; + + public const int ATTACHMENT_DEFORM = 0; + public const int ATTACHMENT_SEQUENCE = 1; + + public const int PATH_POSITION = 0; + public const int PATH_SPACING = 1; + public const int PATH_MIX = 2; + + public const int CURVE_LINEAR = 0; + public const int CURVE_STEPPED = 1; + public const int CURVE_BEZIER = 2; + + public SkeletonBinary(AttachmentLoader attachmentLoader) + : base(attachmentLoader) + { + } + + public SkeletonBinary(params Atlas[] atlasArray) + : base(atlasArray) + { + } #if !ISUNITY && WINDOWS_STOREAPP private async Task ReadFile(string path) { @@ -94,1168 +97,1299 @@ public override SkeletonData ReadSkeletonData (string path) { return this.ReadFile(path).Result; } #else - public override SkeletonData ReadSkeletonData (string path) { + public override SkeletonData ReadSkeletonData(string path) + { #if WINDOWS_PHONE using (var input = new BufferedStream(Microsoft.Xna.Framework.TitleContainer.OpenStream(path))) { #else - using (var input = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) { + using (var input = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) + { #endif - SkeletonData skeletonData = ReadSkeletonData(input); - skeletonData.name = Path.GetFileNameWithoutExtension(path); - return skeletonData; - } - } + SkeletonData skeletonData = ReadSkeletonData(input); + skeletonData.name = Path.GetFileNameWithoutExtension(path); + return skeletonData; + } + } #endif // WINDOWS_STOREAPP - public static readonly TransformMode[] TransformModeValues = { - TransformMode.Normal, - TransformMode.OnlyTranslation, - TransformMode.NoRotationOrReflection, - TransformMode.NoScale, - TransformMode.NoScaleOrReflection - }; - - /// Returns the version string of binary skeleton data. - public static string GetVersionString (Stream file) { - if (file == null) throw new ArgumentNullException("file"); - - SkeletonInput input = new SkeletonInput(file); - return input.GetVersionString(); - } - - public SkeletonData ReadSkeletonData (Stream file) { - if (file == null) throw new ArgumentNullException("file"); - float scale = this.scale; - - var skeletonData = new SkeletonData(); - SkeletonInput input = new SkeletonInput(file); - - long hash = input.ReadLong(); - skeletonData.hash = hash == 0 ? null : hash.ToString(); - skeletonData.version = input.ReadString(); - if (skeletonData.version.Length == 0) skeletonData.version = null; - // early return for old 3.8 format instead of reading past the end - if (skeletonData.version.Length > 13) return null; - skeletonData.x = input.ReadFloat(); - skeletonData.y = input.ReadFloat(); - skeletonData.width = input.ReadFloat(); - skeletonData.height = input.ReadFloat(); - - bool nonessential = input.ReadBoolean(); - - if (nonessential) { - skeletonData.fps = input.ReadFloat(); - - skeletonData.imagesPath = input.ReadString(); - if (string.IsNullOrEmpty(skeletonData.imagesPath)) skeletonData.imagesPath = null; - - skeletonData.audioPath = input.ReadString(); - if (string.IsNullOrEmpty(skeletonData.audioPath)) skeletonData.audioPath = null; - } - - int n; - Object[] o; - - // Strings. - o = input.strings = new String[n = input.ReadInt(true)]; - for (int i = 0; i < n; i++) - o[i] = input.ReadString(); - - // Bones. - var bones = skeletonData.bones.Resize(n = input.ReadInt(true)).Items; - for (int i = 0; i < n; i++) { - String name = input.ReadString(); - BoneData parent = i == 0 ? null : bones[input.ReadInt(true)]; - BoneData data = new BoneData(i, name, parent); - data.rotation = input.ReadFloat(); - data.x = input.ReadFloat() * scale; - data.y = input.ReadFloat() * scale; - data.scaleX = input.ReadFloat(); - data.scaleY = input.ReadFloat(); - data.shearX = input.ReadFloat(); - data.shearY = input.ReadFloat(); - data.Length = input.ReadFloat() * scale; - data.transformMode = TransformModeValues[input.ReadInt(true)]; - data.skinRequired = input.ReadBoolean(); - if (nonessential) input.ReadInt(); // Skip bone color. - bones[i] = data; - } - - // Slots. - var slots = skeletonData.slots.Resize(n = input.ReadInt(true)).Items; - for (int i = 0; i < n; i++) { - String slotName = input.ReadString(); - BoneData boneData = bones[input.ReadInt(true)]; - SlotData slotData = new SlotData(i, slotName, boneData); - int color = input.ReadInt(); - slotData.r = ((color & 0xff000000) >> 24) / 255f; - slotData.g = ((color & 0x00ff0000) >> 16) / 255f; - slotData.b = ((color & 0x0000ff00) >> 8) / 255f; - slotData.a = ((color & 0x000000ff)) / 255f; - - int darkColor = input.ReadInt(); // 0x00rrggbb - if (darkColor != -1) { - slotData.hasSecondColor = true; - slotData.r2 = ((darkColor & 0x00ff0000) >> 16) / 255f; - slotData.g2 = ((darkColor & 0x0000ff00) >> 8) / 255f; - slotData.b2 = ((darkColor & 0x000000ff)) / 255f; - } - - slotData.attachmentName = input.ReadStringRef(); - slotData.blendMode = (BlendMode)input.ReadInt(true); - slots[i] = slotData; - } - - // IK constraints. - o = skeletonData.ikConstraints.Resize(n = input.ReadInt(true)).Items; - for (int i = 0, nn; i < n; i++) { - IkConstraintData data = new IkConstraintData(input.ReadString()); - data.order = input.ReadInt(true); - data.skinRequired = input.ReadBoolean(); - var constraintBones = data.bones.Resize(nn = input.ReadInt(true)).Items; - for (int ii = 0; ii < nn; ii++) - constraintBones[ii] = bones[input.ReadInt(true)]; - data.target = bones[input.ReadInt(true)]; - data.mix = input.ReadFloat(); - data.softness = input.ReadFloat() * scale; - data.bendDirection = input.ReadSByte(); - data.compress = input.ReadBoolean(); - data.stretch = input.ReadBoolean(); - data.uniform = input.ReadBoolean(); - o[i] = data; - } - - // Transform constraints. - o = skeletonData.transformConstraints.Resize(n = input.ReadInt(true)).Items; - for (int i = 0, nn; i < n; i++) { - TransformConstraintData data = new TransformConstraintData(input.ReadString()); - data.order = input.ReadInt(true); - data.skinRequired = input.ReadBoolean(); - var constraintBones = data.bones.Resize(nn = input.ReadInt(true)).Items; - for (int ii = 0; ii < nn; ii++) - constraintBones[ii] = bones[input.ReadInt(true)]; - data.target = bones[input.ReadInt(true)]; - data.local = input.ReadBoolean(); - data.relative = input.ReadBoolean(); - data.offsetRotation = input.ReadFloat(); - data.offsetX = input.ReadFloat() * scale; - data.offsetY = input.ReadFloat() * scale; - data.offsetScaleX = input.ReadFloat(); - data.offsetScaleY = input.ReadFloat(); - data.offsetShearY = input.ReadFloat(); - data.mixRotate = input.ReadFloat(); - data.mixX = input.ReadFloat(); - data.mixY = input.ReadFloat(); - data.mixScaleX = input.ReadFloat(); - data.mixScaleY = input.ReadFloat(); - data.mixShearY = input.ReadFloat(); - o[i] = data; - } - - // Path constraints - o = skeletonData.pathConstraints.Resize(n = input.ReadInt(true)).Items; - for (int i = 0, nn; i < n; i++) { - PathConstraintData data = new PathConstraintData(input.ReadString()); - data.order = input.ReadInt(true); - data.skinRequired = input.ReadBoolean(); - Object[] constraintBones = data.bones.Resize(nn = input.ReadInt(true)).Items; - for (int ii = 0; ii < nn; ii++) - constraintBones[ii] = bones[input.ReadInt(true)]; - data.target = slots[input.ReadInt(true)]; - data.positionMode = (PositionMode)Enum.GetValues(typeof(PositionMode)).GetValue(input.ReadInt(true)); - data.spacingMode = (SpacingMode)Enum.GetValues(typeof(SpacingMode)).GetValue(input.ReadInt(true)); - data.rotateMode = (RotateMode)Enum.GetValues(typeof(RotateMode)).GetValue(input.ReadInt(true)); - data.offsetRotation = input.ReadFloat(); - data.position = input.ReadFloat(); - if (data.positionMode == PositionMode.Fixed) data.position *= scale; - data.spacing = input.ReadFloat(); - if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale; - data.mixRotate = input.ReadFloat(); - data.mixX = input.ReadFloat(); - data.mixY = input.ReadFloat(); - o[i] = data; - } - - // Default skin. - Skin defaultSkin = ReadSkin(input, skeletonData, true, nonessential); - if (defaultSkin != null) { - skeletonData.defaultSkin = defaultSkin; - skeletonData.skins.Add(defaultSkin); - } - - // Skins. - { - int i = skeletonData.skins.Count; - o = skeletonData.skins.Resize(n = i + input.ReadInt(true)).Items; - for (; i < n; i++) - o[i] = ReadSkin(input, skeletonData, false, nonessential); - } - - // Linked meshes. - n = linkedMeshes.Count; - for (int i = 0; i < n; i++) { - LinkedMesh linkedMesh = linkedMeshes[i]; - Skin skin = linkedMesh.skin == null ? skeletonData.DefaultSkin : skeletonData.FindSkin(linkedMesh.skin); - if (skin == null) throw new Exception("Skin not found: " + linkedMesh.skin); - Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); - if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); - linkedMesh.mesh.TimelineAttachment = linkedMesh.inheritTimelines ? (VertexAttachment)parent : linkedMesh.mesh; - linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; - if (linkedMesh.mesh.Sequence == null) linkedMesh.mesh.UpdateRegion(); - } - linkedMeshes.Clear(); - - // Events. - o = skeletonData.events.Resize(n = input.ReadInt(true)).Items; - for (int i = 0; i < n; i++) { - EventData data = new EventData(input.ReadStringRef()); - data.Int = input.ReadInt(false); - data.Float = input.ReadFloat(); - data.String = input.ReadString(); - data.AudioPath = input.ReadString(); - if (data.AudioPath != null) { - data.Volume = input.ReadFloat(); - data.Balance = input.ReadFloat(); - } - o[i] = data; - } - - // Animations. - o = skeletonData.animations.Resize(n = input.ReadInt(true)).Items; - for (int i = 0; i < n; i++) - o[i] = ReadAnimation(input.ReadString(), input, skeletonData); - - return skeletonData; - } - - /// May be null. - private Skin ReadSkin (SkeletonInput input, SkeletonData skeletonData, bool defaultSkin, bool nonessential) { - - Skin skin; - int slotCount; - - if (defaultSkin) { - slotCount = input.ReadInt(true); - if (slotCount == 0) return null; - skin = new Skin("default"); - } else { - skin = new Skin(input.ReadStringRef()); - Object[] bones = skin.bones.Resize(input.ReadInt(true)).Items; - var bonesItems = skeletonData.bones.Items; - for (int i = 0, n = skin.bones.Count; i < n; i++) - bones[i] = bonesItems[input.ReadInt(true)]; - - var ikConstraintsItems = skeletonData.ikConstraints.Items; - for (int i = 0, n = input.ReadInt(true); i < n; i++) - skin.constraints.Add(ikConstraintsItems[input.ReadInt(true)]); - var transformConstraintsItems = skeletonData.transformConstraints.Items; - for (int i = 0, n = input.ReadInt(true); i < n; i++) - skin.constraints.Add(transformConstraintsItems[input.ReadInt(true)]); - var pathConstraintsItems = skeletonData.pathConstraints.Items; - for (int i = 0, n = input.ReadInt(true); i < n; i++) - skin.constraints.Add(pathConstraintsItems[input.ReadInt(true)]); - skin.constraints.TrimExcess(); - - slotCount = input.ReadInt(true); - } - for (int i = 0; i < slotCount; i++) { - int slotIndex = input.ReadInt(true); - for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) { - String name = input.ReadStringRef(); - Attachment attachment = ReadAttachment(input, skeletonData, skin, slotIndex, name, nonessential); - if (attachment != null) skin.SetAttachment(slotIndex, name, attachment); - } - } - return skin; - } - - private Attachment ReadAttachment (SkeletonInput input, SkeletonData skeletonData, Skin skin, int slotIndex, - String attachmentName, bool nonessential) { - float scale = this.scale; - - String name = input.ReadStringRef(); - if (name == null) name = attachmentName; - - switch ((AttachmentType)input.ReadByte()) { - case AttachmentType.Region: { - String path = input.ReadStringRef(); - float rotation = input.ReadFloat(); - float x = input.ReadFloat(); - float y = input.ReadFloat(); - float scaleX = input.ReadFloat(); - float scaleY = input.ReadFloat(); - float width = input.ReadFloat(); - float height = input.ReadFloat(); - int color = input.ReadInt(); - Sequence sequence = ReadSequence(input); - - if (path == null) path = name; - RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path, sequence); - if (region == null) return null; - region.Path = path; - region.x = x * scale; - region.y = y * scale; - region.scaleX = scaleX; - region.scaleY = scaleY; - region.rotation = rotation; - region.width = width * scale; - region.height = height * scale; - region.r = ((color & 0xff000000) >> 24) / 255f; - region.g = ((color & 0x00ff0000) >> 16) / 255f; - region.b = ((color & 0x0000ff00) >> 8) / 255f; - region.a = ((color & 0x000000ff)) / 255f; - region.sequence = sequence; - if (sequence == null) region.UpdateRegion(); - return region; - } - case AttachmentType.Boundingbox: { - int vertexCount = input.ReadInt(true); - Vertices vertices = ReadVertices(input, vertexCount); - if (nonessential) input.ReadInt(); //int color = nonessential ? input.ReadInt() : 0; // Avoid unused local warning. - - BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); - if (box == null) return null; - box.worldVerticesLength = vertexCount << 1; - box.vertices = vertices.vertices; - box.bones = vertices.bones; - // skipped porting: if (nonessential) Color.rgba8888ToColor(box.getColor(), color); - return box; - } - case AttachmentType.Mesh: { - String path = input.ReadStringRef(); - int color = input.ReadInt(); - int vertexCount = input.ReadInt(true); - float[] uvs = ReadFloatArray(input, vertexCount << 1, 1); - int[] triangles = ReadShortArray(input); - Vertices vertices = ReadVertices(input, vertexCount); - int hullLength = input.ReadInt(true); - Sequence sequence = ReadSequence(input); - int[] edges = null; - float width = 0, height = 0; - if (nonessential) { - edges = ReadShortArray(input); - width = input.ReadFloat(); - height = input.ReadFloat(); - } - - if (path == null) path = name; - MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path, sequence); - if (mesh == null) return null; - mesh.Path = path; - mesh.r = ((color & 0xff000000) >> 24) / 255f; - mesh.g = ((color & 0x00ff0000) >> 16) / 255f; - mesh.b = ((color & 0x0000ff00) >> 8) / 255f; - mesh.a = ((color & 0x000000ff)) / 255f; - mesh.bones = vertices.bones; - mesh.vertices = vertices.vertices; - mesh.WorldVerticesLength = vertexCount << 1; - mesh.triangles = triangles; - mesh.regionUVs = uvs; - if (sequence == null) mesh.UpdateRegion(); - mesh.HullLength = hullLength << 1; - mesh.Sequence = sequence; - if (nonessential) { - mesh.Edges = edges; - mesh.Width = width * scale; - mesh.Height = height * scale; - } - return mesh; - } - case AttachmentType.Linkedmesh: { - String path = input.ReadStringRef(); - int color = input.ReadInt(); - String skinName = input.ReadStringRef(); - String parent = input.ReadStringRef(); - bool inheritTimelines = input.ReadBoolean(); - Sequence sequence = ReadSequence(input); - float width = 0, height = 0; - if (nonessential) { - width = input.ReadFloat(); - height = input.ReadFloat(); - } - - if (path == null) path = name; - MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path, sequence); - if (mesh == null) return null; - mesh.Path = path; - mesh.r = ((color & 0xff000000) >> 24) / 255f; - mesh.g = ((color & 0x00ff0000) >> 16) / 255f; - mesh.b = ((color & 0x0000ff00) >> 8) / 255f; - mesh.a = ((color & 0x000000ff)) / 255f; - mesh.Sequence = sequence; - if (nonessential) { - mesh.Width = width * scale; - mesh.Height = height * scale; - } - linkedMeshes.Add(new SkeletonJson.LinkedMesh(mesh, skinName, slotIndex, parent, inheritTimelines)); - return mesh; - } - case AttachmentType.Path: { - bool closed = input.ReadBoolean(); - bool constantSpeed = input.ReadBoolean(); - int vertexCount = input.ReadInt(true); - Vertices vertices = ReadVertices(input, vertexCount); - float[] lengths = new float[vertexCount / 3]; - for (int i = 0, n = lengths.Length; i < n; i++) - lengths[i] = input.ReadFloat() * scale; - if (nonessential) input.ReadInt(); //int color = nonessential ? input.ReadInt() : 0; - - PathAttachment path = attachmentLoader.NewPathAttachment(skin, name); - if (path == null) return null; - path.closed = closed; - path.constantSpeed = constantSpeed; - path.worldVerticesLength = vertexCount << 1; - path.vertices = vertices.vertices; - path.bones = vertices.bones; - path.lengths = lengths; - // skipped porting: if (nonessential) Color.rgba8888ToColor(path.getColor(), color); - return path; - } - case AttachmentType.Point: { - float rotation = input.ReadFloat(); - float x = input.ReadFloat(); - float y = input.ReadFloat(); - if (nonessential) input.ReadInt(); //int color = nonessential ? input.ReadInt() : 0; - - PointAttachment point = attachmentLoader.NewPointAttachment(skin, name); - if (point == null) return null; - point.x = x * scale; - point.y = y * scale; - point.rotation = rotation; - // skipped porting: if (nonessential) point.color = color; - return point; - } - case AttachmentType.Clipping: { - int endSlotIndex = input.ReadInt(true); - int vertexCount = input.ReadInt(true); - Vertices vertices = ReadVertices(input, vertexCount); - if (nonessential) input.ReadInt(); - - ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name); - if (clip == null) return null; - clip.EndSlot = skeletonData.slots.Items[endSlotIndex]; - clip.worldVerticesLength = vertexCount << 1; - clip.vertices = vertices.vertices; - clip.bones = vertices.bones; - // skipped porting: if (nonessential) Color.rgba8888ToColor(clip.getColor(), color); - return clip; - } - } - return null; - } - - private Sequence ReadSequence (SkeletonInput input) { - if (!input.ReadBoolean()) return null; - Sequence sequence = new Sequence(input.ReadInt(true)); - sequence.Start = input.ReadInt(true); - sequence.Digits = input.ReadInt(true); - sequence.SetupIndex = input.ReadInt(true); - return sequence; - } - - private Vertices ReadVertices (SkeletonInput input, int vertexCount) { - float scale = this.scale; - int verticesLength = vertexCount << 1; - Vertices vertices = new Vertices(); - if (!input.ReadBoolean()) { - vertices.vertices = ReadFloatArray(input, verticesLength, scale); - return vertices; - } - var weights = new ExposedList(verticesLength * 3 * 3); - var bonesArray = new ExposedList(verticesLength * 3); - for (int i = 0; i < vertexCount; i++) { - int boneCount = input.ReadInt(true); - bonesArray.Add(boneCount); - for (int ii = 0; ii < boneCount; ii++) { - bonesArray.Add(input.ReadInt(true)); - weights.Add(input.ReadFloat() * scale); - weights.Add(input.ReadFloat() * scale); - weights.Add(input.ReadFloat()); - } - } - - vertices.vertices = weights.ToArray(); - vertices.bones = bonesArray.ToArray(); - return vertices; - } - - private float[] ReadFloatArray (SkeletonInput input, int n, float scale) { - float[] array = new float[n]; - if (scale == 1) { - for (int i = 0; i < n; i++) - array[i] = input.ReadFloat(); - } else { - for (int i = 0; i < n; i++) - array[i] = input.ReadFloat() * scale; - } - return array; - } - - private int[] ReadShortArray (SkeletonInput input) { - int n = input.ReadInt(true); - int[] array = new int[n]; - for (int i = 0; i < n; i++) - array[i] = (input.ReadByte() << 8) | input.ReadByte(); - return array; - } - - /// SerializationException will be thrown when a Vertex attachment is not found. - /// Throws IOException when a read operation fails. - private Animation ReadAnimation (String name, SkeletonInput input, SkeletonData skeletonData) { - var timelines = new ExposedList(input.ReadInt(true)); - float scale = this.scale; - - // Slot timelines. - for (int i = 0, n = input.ReadInt(true); i < n; i++) { - int slotIndex = input.ReadInt(true); - for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) { - int timelineType = input.ReadByte(), frameCount = input.ReadInt(true), frameLast = frameCount - 1; - switch (timelineType) { - case SLOT_ATTACHMENT: { - AttachmentTimeline timeline = new AttachmentTimeline(frameCount, slotIndex); - for (int frame = 0; frame < frameCount; frame++) - timeline.SetFrame(frame, input.ReadFloat(), input.ReadStringRef()); - timelines.Add(timeline); - break; - } - case SLOT_RGBA: { - RGBATimeline timeline = new RGBATimeline(frameCount, input.ReadInt(true), slotIndex); - float time = input.ReadFloat(); - float r = input.Read() / 255f, g = input.Read() / 255f; - float b = input.Read() / 255f, a = input.Read() / 255f; - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, r, g, b, a); - if (frame == frameLast) break; - float time2 = input.ReadFloat(); - float r2 = input.Read() / 255f, g2 = input.Read() / 255f; - float b2 = input.Read() / 255f, a2 = input.Read() / 255f; - switch (input.ReadByte()) { - 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); - break; - } - time = time2; - r = r2; - g = g2; - b = b2; - a = a2; - } - timelines.Add(timeline); - break; - } - case SLOT_RGB: { - RGBTimeline timeline = new RGBTimeline(frameCount, input.ReadInt(true), slotIndex); - float time = input.ReadFloat(); - float r = input.Read() / 255f, g = input.Read() / 255f, b = input.Read() / 255f; - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, r, g, b); - if (frame == frameLast) break; - float time2 = input.ReadFloat(); - float r2 = input.Read() / 255f, g2 = input.Read() / 255f, b2 = input.Read() / 255f; - switch (input.ReadByte()) { - 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); - break; - } - time = time2; - r = r2; - g = g2; - b = b2; - } - timelines.Add(timeline); - break; - } - case SLOT_RGBA2: { - RGBA2Timeline timeline = new RGBA2Timeline(frameCount, input.ReadInt(true), slotIndex); - float time = input.ReadFloat(); - float r = input.Read() / 255f, g = input.Read() / 255f; - float b = input.Read() / 255f, a = input.Read() / 255f; - float r2 = input.Read() / 255f, g2 = input.Read() / 255f, b2 = input.Read() / 255f; - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, r, g, b, a, r2, g2, b2); - if (frame == frameLast) break; - float time2 = input.ReadFloat(); - float nr = input.Read() / 255f, ng = input.Read() / 255f; - float nb = input.Read() / 255f, na = input.Read() / 255f; - float nr2 = input.Read() / 255f, ng2 = input.Read() / 255f, nb2 = input.Read() / 255f; - switch (input.ReadByte()) { - 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); - break; - } - time = time2; - r = nr; - g = ng; - b = nb; - a = na; - r2 = nr2; - g2 = ng2; - b2 = nb2; - } - timelines.Add(timeline); - break; - } - case SLOT_RGB2: { - RGB2Timeline timeline = new RGB2Timeline(frameCount, input.ReadInt(true), slotIndex); - float time = input.ReadFloat(); - float r = input.Read() / 255f, g = input.Read() / 255f, b = input.Read() / 255f; - float r2 = input.Read() / 255f, g2 = input.Read() / 255f, b2 = input.Read() / 255f; - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, r, g, b, r2, g2, b2); - if (frame == frameLast) break; - float time2 = input.ReadFloat(); - float nr = input.Read() / 255f, ng = input.Read() / 255f, nb = input.Read() / 255f; - float nr2 = input.Read() / 255f, ng2 = input.Read() / 255f, nb2 = input.Read() / 255f; - switch (input.ReadByte()) { - 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); - break; - } - time = time2; - r = nr; - g = ng; - b = nb; - r2 = nr2; - g2 = ng2; - b2 = nb2; - } - timelines.Add(timeline); - break; - } - case SLOT_ALPHA: { - AlphaTimeline timeline = new AlphaTimeline(frameCount, input.ReadInt(true), slotIndex); - float time = input.ReadFloat(), a = input.Read() / 255f; - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, a); - if (frame == frameLast) break; - float time2 = input.ReadFloat(); - float a2 = input.Read() / 255f; - switch (input.ReadByte()) { - case CURVE_STEPPED: - timeline.SetStepped(frame); - break; - case CURVE_BEZIER: - SetBezier(input, timeline, bezier++, frame, 0, time, time2, a, a2, 1); - break; - } - time = time2; - a = a2; - } - timelines.Add(timeline); - break; - } - } - } - } - - // Bone timelines. - for (int i = 0, n = input.ReadInt(true); i < n; i++) { - int boneIndex = input.ReadInt(true); - for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) { - int type = input.ReadByte(), frameCount = input.ReadInt(true), bezierCount = input.ReadInt(true); - switch (type) { - case BONE_ROTATE: - timelines.Add(ReadTimeline(input, new RotateTimeline(frameCount, bezierCount, boneIndex), 1)); - break; - case BONE_TRANSLATE: - timelines.Add(ReadTimeline(input, new TranslateTimeline(frameCount, bezierCount, boneIndex), scale)); - break; - case BONE_TRANSLATEX: - timelines.Add(ReadTimeline(input, new TranslateXTimeline(frameCount, bezierCount, boneIndex), scale)); - break; - case BONE_TRANSLATEY: - timelines.Add(ReadTimeline(input, new TranslateYTimeline(frameCount, bezierCount, boneIndex), scale)); - break; - case BONE_SCALE: - timelines.Add(ReadTimeline(input, new ScaleTimeline(frameCount, bezierCount, boneIndex), 1)); - break; - case BONE_SCALEX: - timelines.Add(ReadTimeline(input, new ScaleXTimeline(frameCount, bezierCount, boneIndex), 1)); - break; - case BONE_SCALEY: - timelines.Add(ReadTimeline(input, new ScaleYTimeline(frameCount, bezierCount, boneIndex), 1)); - break; - case BONE_SHEAR: - timelines.Add(ReadTimeline(input, new ShearTimeline(frameCount, bezierCount, boneIndex), 1)); - break; - case BONE_SHEARX: - timelines.Add(ReadTimeline(input, new ShearXTimeline(frameCount, bezierCount, boneIndex), 1)); - break; - case BONE_SHEARY: - timelines.Add(ReadTimeline(input, new ShearYTimeline(frameCount, bezierCount, boneIndex), 1)); - break; - } - } - } - - // IK constraint timelines. - for (int i = 0, n = input.ReadInt(true); i < n; i++) { - int index = input.ReadInt(true), frameCount = input.ReadInt(true), frameLast = frameCount - 1; - IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount, input.ReadInt(true), index); - float time = input.ReadFloat(), mix = input.ReadFloat(), softness = input.ReadFloat() * scale; - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, mix, softness, input.ReadSByte(), input.ReadBoolean(), input.ReadBoolean()); - if (frame == frameLast) break; - float time2 = input.ReadFloat(), mix2 = input.ReadFloat(), softness2 = input.ReadFloat() * scale; - switch (input.ReadByte()) { - case CURVE_STEPPED: - timeline.SetStepped(frame); - break; - case CURVE_BEZIER: - SetBezier(input, timeline, bezier++, frame, 0, time, time2, mix, mix2, 1); - SetBezier(input, timeline, bezier++, frame, 1, time, time2, softness, softness2, scale); - break; - } - time = time2; - mix = mix2; - softness = softness2; - } - timelines.Add(timeline); - } - - // Transform constraint timelines. - for (int i = 0, n = input.ReadInt(true); i < n; i++) { - int index = input.ReadInt(true), frameCount = input.ReadInt(true), frameLast = frameCount - 1; - TransformConstraintTimeline timeline = new TransformConstraintTimeline(frameCount, input.ReadInt(true), index); - float time = input.ReadFloat(), mixRotate = input.ReadFloat(), mixX = input.ReadFloat(), mixY = input.ReadFloat(), - mixScaleX = input.ReadFloat(), mixScaleY = input.ReadFloat(), mixShearY = input.ReadFloat(); - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY); - if (frame == frameLast) break; - float time2 = input.ReadFloat(), mixRotate2 = input.ReadFloat(), mixX2 = input.ReadFloat(), mixY2 = input.ReadFloat(), - mixScaleX2 = input.ReadFloat(), mixScaleY2 = input.ReadFloat(), mixShearY2 = input.ReadFloat(); - switch (input.ReadByte()) { - 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); - break; - } - 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 = input.ReadInt(true); i < n; i++) { - int index = input.ReadInt(true); - PathConstraintData data = skeletonData.pathConstraints.Items[index]; - for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) { - switch (input.ReadByte()) { - case PATH_POSITION: - timelines - .Add(ReadTimeline(input, new PathConstraintPositionTimeline(input.ReadInt(true), input.ReadInt(true), index), - data.positionMode == PositionMode.Fixed ? scale : 1)); - break; - case PATH_SPACING: - timelines - .Add(ReadTimeline(input, new PathConstraintSpacingTimeline(input.ReadInt(true), input.ReadInt(true), index), - data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed ? scale : 1)); - break; - case PATH_MIX: - PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(input.ReadInt(true), input.ReadInt(true), - index); - float time = input.ReadFloat(), mixRotate = input.ReadFloat(), mixX = input.ReadFloat(), mixY = input.ReadFloat(); - for (int frame = 0, bezier = 0, frameLast = timeline.FrameCount - 1; ; frame++) { - timeline.SetFrame(frame, time, mixRotate, mixX, mixY); - if (frame == frameLast) break; - float time2 = input.ReadFloat(), mixRotate2 = input.ReadFloat(), mixX2 = input.ReadFloat(), - mixY2 = input.ReadFloat(); - switch (input.ReadByte()) { - 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); - break; - } - time = time2; - mixRotate = mixRotate2; - mixX = mixX2; - mixY = mixY2; - } - timelines.Add(timeline); - break; - } - } - } - - // Attachment timelines. - for (int i = 0, n = input.ReadInt(true); i < n; i++) { - Skin skin = skeletonData.skins.Items[input.ReadInt(true)]; - for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) { - int slotIndex = input.ReadInt(true); - for (int iii = 0, nnn = input.ReadInt(true); iii < nnn; iii++) { - String attachmentName = input.ReadStringRef(); - Attachment attachment = skin.GetAttachment(slotIndex, attachmentName); - if (attachment == null) throw new SerializationException("Timeline attachment not found: " + attachmentName); - - int timelineType = input.ReadByte(), frameCount = input.ReadInt(true), frameLast = frameCount - 1; - switch (timelineType) { - case ATTACHMENT_DEFORM: { - VertexAttachment vertexAttachment = (VertexAttachment)attachment; - bool weighted = vertexAttachment.Bones != null; - float[] vertices = vertexAttachment.Vertices; - int deformLength = weighted ? (vertices.Length / 3) << 1 : vertices.Length; - - DeformTimeline timeline = new DeformTimeline(frameCount, input.ReadInt(true), slotIndex, vertexAttachment); - - float time = input.ReadFloat(); - for (int frame = 0, bezier = 0; ; frame++) { - float[] deform; - int end = input.ReadInt(true); - if (end == 0) - deform = weighted ? new float[deformLength] : vertices; - else { - deform = new float[deformLength]; - int start = input.ReadInt(true); - end += start; - if (scale == 1) { - for (int v = start; v < end; v++) - deform[v] = input.ReadFloat(); - } else { - for (int v = start; v < end; v++) - deform[v] = input.ReadFloat() * scale; - } - if (!weighted) { - for (int v = 0, vn = deform.Length; v < vn; v++) - deform[v] += vertices[v]; - } - } - timeline.SetFrame(frame, time, deform); - if (frame == frameLast) break; - float time2 = input.ReadFloat(); - switch (input.ReadByte()) { - case CURVE_STEPPED: - timeline.SetStepped(frame); - break; - case CURVE_BEZIER: - SetBezier(input, timeline, bezier++, frame, 0, time, time2, 0, 1, 1); - break; - } - time = time2; - } - timelines.Add(timeline); - break; - } - case ATTACHMENT_SEQUENCE: { - SequenceTimeline timeline = new SequenceTimeline(frameCount, slotIndex, attachment); - for (int frame = 0; frame < frameCount; frame++) { - float time = input.ReadFloat(); - int modeAndIndex = input.ReadInt(); - timeline.SetFrame(frame, time, (SequenceMode)(modeAndIndex & 0xf), modeAndIndex >> 4, - input.ReadFloat()); - } - timelines.Add(timeline); - break; - } // end case - } // end switch - } - } - } - - // Draw order timeline. - int drawOrderCount = input.ReadInt(true); - if (drawOrderCount > 0) { - DrawOrderTimeline timeline = new DrawOrderTimeline(drawOrderCount); - int slotCount = skeletonData.slots.Count; - for (int i = 0; i < drawOrderCount; i++) { - float time = input.ReadFloat(); - int offsetCount = input.ReadInt(true); - int[] drawOrder = new int[slotCount]; - for (int ii = slotCount - 1; ii >= 0; ii--) - drawOrder[ii] = -1; - int[] unchanged = new int[slotCount - offsetCount]; - int originalIndex = 0, unchangedIndex = 0; - for (int ii = 0; ii < offsetCount; ii++) { - int slotIndex = input.ReadInt(true); - // Collect unchanged items. - while (originalIndex != slotIndex) - unchanged[unchangedIndex++] = originalIndex++; - // Set changed items. - drawOrder[originalIndex + input.ReadInt(true)] = originalIndex++; - } - // Collect remaining unchanged items. - while (originalIndex < slotCount) - unchanged[unchangedIndex++] = originalIndex++; - // Fill in unchanged items. - for (int ii = 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 = input.ReadInt(true); - if (eventCount > 0) { - EventTimeline timeline = new EventTimeline(eventCount); - for (int i = 0; i < eventCount; i++) { - float time = input.ReadFloat(); - EventData eventData = skeletonData.events.Items[input.ReadInt(true)]; - Event e = new Event(time, eventData); - e.intValue = input.ReadInt(false); - e.floatValue = input.ReadFloat(); - e.stringValue = input.ReadBoolean() ? input.ReadString() : eventData.String; - if (e.Data.AudioPath != null) { - e.volume = input.ReadFloat(); - e.balance = input.ReadFloat(); - } - timeline.SetFrame(i, e); - } - timelines.Add(timeline); - } - - float duration = 0; - var items = timelines.Items; - for (int i = 0, n = timelines.Count; i < n; i++) - duration = Math.Max(duration, items[i].Duration); - return new Animation(name, timelines, duration); - } - - /// Throws IOException when a read operation fails. - private Timeline ReadTimeline (SkeletonInput input, CurveTimeline1 timeline, float scale) { - float time = input.ReadFloat(), value = input.ReadFloat() * scale; - for (int frame = 0, bezier = 0, frameLast = timeline.FrameCount - 1; ; frame++) { - timeline.SetFrame(frame, time, value); - if (frame == frameLast) break; - float time2 = input.ReadFloat(), value2 = input.ReadFloat() * scale; - switch (input.ReadByte()) { - case CURVE_STEPPED: - timeline.SetStepped(frame); - break; - case CURVE_BEZIER: - SetBezier(input, timeline, bezier++, frame, 0, time, time2, value, value2, scale); - break; - } - time = time2; - value = value2; - } - return timeline; - } - - /// Throws IOException when a read operation fails. - private Timeline ReadTimeline (SkeletonInput input, CurveTimeline2 timeline, float scale) { - float time = input.ReadFloat(), value1 = input.ReadFloat() * scale, value2 = input.ReadFloat() * scale; - for (int frame = 0, bezier = 0, frameLast = timeline.FrameCount - 1; ; frame++) { - timeline.SetFrame(frame, time, value1, value2); - if (frame == frameLast) break; - float time2 = input.ReadFloat(), nvalue1 = input.ReadFloat() * scale, nvalue2 = input.ReadFloat() * scale; - switch (input.ReadByte()) { - 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); - break; - } - time = time2; - value1 = nvalue1; - value2 = nvalue2; - } - return timeline; - } - - /// Throws IOException when a read operation fails. - void SetBezier (SkeletonInput input, CurveTimeline timeline, int bezier, int frame, int value, float time1, float time2, - float value1, float value2, float scale) { - timeline.SetBezier(bezier, frame, value, time1, value1, input.ReadFloat(), input.ReadFloat() * scale, input.ReadFloat(), - input.ReadFloat() * scale, time2, value2); - } - - internal class Vertices { - public int[] bones; - public float[] vertices; - } - - internal class SkeletonInput { - private byte[] chars = new byte[32]; - private byte[] bytesBigEndian = new byte[8]; - internal string[] strings; - Stream input; - - public SkeletonInput (Stream input) { - this.input = input; - } - - public int Read () { - return input.ReadByte(); - } - - public byte ReadByte () { - return (byte)input.ReadByte(); - } - - public sbyte ReadSByte () { - int value = input.ReadByte(); - if (value == -1) throw new EndOfStreamException(); - return (sbyte)value; - } - - public bool ReadBoolean () { - return input.ReadByte() != 0; - } - - public float ReadFloat () { - input.Read(bytesBigEndian, 0, 4); - chars[3] = bytesBigEndian[0]; - chars[2] = bytesBigEndian[1]; - chars[1] = bytesBigEndian[2]; - chars[0] = bytesBigEndian[3]; - return BitConverter.ToSingle(chars, 0); - } - - public int ReadInt () { - input.Read(bytesBigEndian, 0, 4); - return (bytesBigEndian[0] << 24) - + (bytesBigEndian[1] << 16) - + (bytesBigEndian[2] << 8) - + bytesBigEndian[3]; - } - - public long ReadLong () { - input.Read(bytesBigEndian, 0, 8); - return ((long)(bytesBigEndian[0]) << 56) - + ((long)(bytesBigEndian[1]) << 48) - + ((long)(bytesBigEndian[2]) << 40) - + ((long)(bytesBigEndian[3]) << 32) - + ((long)(bytesBigEndian[4]) << 24) - + ((long)(bytesBigEndian[5]) << 16) - + ((long)(bytesBigEndian[6]) << 8) - + (long)(bytesBigEndian[7]); - } - - public int ReadInt (bool optimizePositive) { - int b = input.ReadByte(); - int result = b & 0x7F; - if ((b & 0x80) != 0) { - b = input.ReadByte(); - result |= (b & 0x7F) << 7; - if ((b & 0x80) != 0) { - b = input.ReadByte(); - result |= (b & 0x7F) << 14; - if ((b & 0x80) != 0) { - b = input.ReadByte(); - result |= (b & 0x7F) << 21; - if ((b & 0x80) != 0) result |= (input.ReadByte() & 0x7F) << 28; - } - } - } - return optimizePositive ? result : ((result >> 1) ^ -(result & 1)); - } - - public string ReadString () { - int byteCount = ReadInt(true); - switch (byteCount) { - case 0: - return null; - case 1: - return ""; - } - byteCount--; - byte[] buffer = this.chars; - if (buffer.Length < byteCount) buffer = new byte[byteCount]; - ReadFully(buffer, 0, byteCount); - return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount); - } - - ///May be null. - public String ReadStringRef () { - int index = ReadInt(true); - return index == 0 ? null : strings[index - 1]; - } - - public void ReadFully (byte[] buffer, int offset, int length) { - while (length > 0) { - int count = input.Read(buffer, offset, length); - if (count <= 0) throw new EndOfStreamException(); - offset += count; - length -= count; - } - } - - /// Returns the version string of binary skeleton data. - public string GetVersionString () { - try { - // try reading 4.0+ format - var initialPosition = input.Position; - ReadLong(); // long hash - - var stringPosition = input.Position; - int stringByteCount = ReadInt(true); - input.Position = stringPosition; - if (stringByteCount <= 13) { - string version = ReadString(); - if (char.IsDigit(version[0])) - return version; - } - // fallback to old version format - input.Position = initialPosition; - return GetVersionStringOld3X(); - } catch (Exception e) { - throw new ArgumentException("Stream does not contain valid binary Skeleton Data.\n" + e, "input"); - } - } - - /// Returns old 3.8 and earlier format version string of binary skeleton data. - public string GetVersionStringOld3X () { - // Hash. - int byteCount = ReadInt(true); - if (byteCount > 1) input.Position += byteCount - 1; - - // Version. - byteCount = ReadInt(true); - if (byteCount > 1 && byteCount <= 13) { - byteCount--; - var buffer = new byte[byteCount]; - ReadFully(buffer, 0, byteCount); - return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount); - } - throw new ArgumentException("Stream does not contain valid binary Skeleton Data."); - } - } - } + public static readonly TransformMode[] TransformModeValues = { + TransformMode.Normal, + TransformMode.OnlyTranslation, + TransformMode.NoRotationOrReflection, + TransformMode.NoScale, + TransformMode.NoScaleOrReflection + }; + + /// Returns the version string of binary skeleton data. + public static string GetVersionString(Stream file) + { + if (file == null) throw new ArgumentNullException("file"); + + SkeletonInput input = new SkeletonInput(file); + return input.GetVersionString(); + } + + public SkeletonData ReadSkeletonData(Stream file) + { + if (file == null) throw new ArgumentNullException("file"); + float scale = this.scale; + + var skeletonData = new SkeletonData(); + SkeletonInput input = new SkeletonInput(file); + + long hash = input.ReadLong(); + skeletonData.hash = hash == 0 ? null : hash.ToString(); + skeletonData.version = input.ReadString(); + if (skeletonData.version.Length == 0) skeletonData.version = null; + // early return for old 3.8 format instead of reading past the end + if (skeletonData.version.Length > 13) return null; + skeletonData.x = input.ReadFloat(); + skeletonData.y = input.ReadFloat(); + skeletonData.width = input.ReadFloat(); + skeletonData.height = input.ReadFloat(); + + bool nonessential = input.ReadBoolean(); + + if (nonessential) + { + skeletonData.fps = input.ReadFloat(); + + skeletonData.imagesPath = input.ReadString(); + if (string.IsNullOrEmpty(skeletonData.imagesPath)) skeletonData.imagesPath = null; + + skeletonData.audioPath = input.ReadString(); + if (string.IsNullOrEmpty(skeletonData.audioPath)) skeletonData.audioPath = null; + } + + int n; + Object[] o; + + // Strings. + o = input.strings = new String[n = input.ReadInt(true)]; + for (int i = 0; i < n; i++) + o[i] = input.ReadString(); + + // Bones. + var bones = skeletonData.bones.Resize(n = input.ReadInt(true)).Items; + for (int i = 0; i < n; i++) + { + String name = input.ReadString(); + BoneData parent = i == 0 ? null : bones[input.ReadInt(true)]; + BoneData data = new BoneData(i, name, parent); + data.rotation = input.ReadFloat(); + data.x = input.ReadFloat() * scale; + data.y = input.ReadFloat() * scale; + data.scaleX = input.ReadFloat(); + data.scaleY = input.ReadFloat(); + data.shearX = input.ReadFloat(); + data.shearY = input.ReadFloat(); + data.Length = input.ReadFloat() * scale; + data.transformMode = TransformModeValues[input.ReadInt(true)]; + data.skinRequired = input.ReadBoolean(); + if (nonessential) input.ReadInt(); // Skip bone color. + bones[i] = data; + } + + // Slots. + var slots = skeletonData.slots.Resize(n = input.ReadInt(true)).Items; + for (int i = 0; i < n; i++) + { + String slotName = input.ReadString(); + BoneData boneData = bones[input.ReadInt(true)]; + SlotData slotData = new SlotData(i, slotName, boneData); + int color = input.ReadInt(); + slotData.r = ((color & 0xff000000) >> 24) / 255f; + slotData.g = ((color & 0x00ff0000) >> 16) / 255f; + slotData.b = ((color & 0x0000ff00) >> 8) / 255f; + slotData.a = ((color & 0x000000ff)) / 255f; + + int darkColor = input.ReadInt(); // 0x00rrggbb + if (darkColor != -1) + { + slotData.hasSecondColor = true; + slotData.r2 = ((darkColor & 0x00ff0000) >> 16) / 255f; + slotData.g2 = ((darkColor & 0x0000ff00) >> 8) / 255f; + slotData.b2 = ((darkColor & 0x000000ff)) / 255f; + } + + slotData.attachmentName = input.ReadStringRef(); + slotData.blendMode = (BlendMode)input.ReadInt(true); + slots[i] = slotData; + } + + // IK constraints. + o = skeletonData.ikConstraints.Resize(n = input.ReadInt(true)).Items; + for (int i = 0, nn; i < n; i++) + { + IkConstraintData data = new IkConstraintData(input.ReadString()); + data.order = input.ReadInt(true); + data.skinRequired = input.ReadBoolean(); + var constraintBones = data.bones.Resize(nn = input.ReadInt(true)).Items; + for (int ii = 0; ii < nn; ii++) + constraintBones[ii] = bones[input.ReadInt(true)]; + data.target = bones[input.ReadInt(true)]; + data.mix = input.ReadFloat(); + data.softness = input.ReadFloat() * scale; + data.bendDirection = input.ReadSByte(); + data.compress = input.ReadBoolean(); + data.stretch = input.ReadBoolean(); + data.uniform = input.ReadBoolean(); + o[i] = data; + } + + // Transform constraints. + o = skeletonData.transformConstraints.Resize(n = input.ReadInt(true)).Items; + for (int i = 0, nn; i < n; i++) + { + TransformConstraintData data = new TransformConstraintData(input.ReadString()); + data.order = input.ReadInt(true); + data.skinRequired = input.ReadBoolean(); + var constraintBones = data.bones.Resize(nn = input.ReadInt(true)).Items; + for (int ii = 0; ii < nn; ii++) + constraintBones[ii] = bones[input.ReadInt(true)]; + data.target = bones[input.ReadInt(true)]; + data.local = input.ReadBoolean(); + data.relative = input.ReadBoolean(); + data.offsetRotation = input.ReadFloat(); + data.offsetX = input.ReadFloat() * scale; + data.offsetY = input.ReadFloat() * scale; + data.offsetScaleX = input.ReadFloat(); + data.offsetScaleY = input.ReadFloat(); + data.offsetShearY = input.ReadFloat(); + data.mixRotate = input.ReadFloat(); + data.mixX = input.ReadFloat(); + data.mixY = input.ReadFloat(); + data.mixScaleX = input.ReadFloat(); + data.mixScaleY = input.ReadFloat(); + data.mixShearY = input.ReadFloat(); + o[i] = data; + } + + // Path constraints + o = skeletonData.pathConstraints.Resize(n = input.ReadInt(true)).Items; + for (int i = 0, nn; i < n; i++) + { + PathConstraintData data = new PathConstraintData(input.ReadString()); + data.order = input.ReadInt(true); + data.skinRequired = input.ReadBoolean(); + Object[] constraintBones = data.bones.Resize(nn = input.ReadInt(true)).Items; + for (int ii = 0; ii < nn; ii++) + constraintBones[ii] = bones[input.ReadInt(true)]; + data.target = slots[input.ReadInt(true)]; + data.positionMode = (PositionMode)Enum.GetValues(typeof(PositionMode)).GetValue(input.ReadInt(true)); + data.spacingMode = (SpacingMode)Enum.GetValues(typeof(SpacingMode)).GetValue(input.ReadInt(true)); + data.rotateMode = (RotateMode)Enum.GetValues(typeof(RotateMode)).GetValue(input.ReadInt(true)); + data.offsetRotation = input.ReadFloat(); + data.position = input.ReadFloat(); + if (data.positionMode == PositionMode.Fixed) data.position *= scale; + data.spacing = input.ReadFloat(); + if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale; + data.mixRotate = input.ReadFloat(); + data.mixX = input.ReadFloat(); + data.mixY = input.ReadFloat(); + o[i] = data; + } + + // Default skin. + Skin defaultSkin = ReadSkin(input, skeletonData, true, nonessential); + if (defaultSkin != null) + { + skeletonData.defaultSkin = defaultSkin; + skeletonData.skins.Add(defaultSkin); + } + + // Skins. + { + int i = skeletonData.skins.Count; + o = skeletonData.skins.Resize(n = i + input.ReadInt(true)).Items; + for (; i < n; i++) + o[i] = ReadSkin(input, skeletonData, false, nonessential); + } + + // Linked meshes. + n = linkedMeshes.Count; + for (int i = 0; i < n; i++) + { + LinkedMesh linkedMesh = linkedMeshes[i]; + Skin skin = linkedMesh.skin == null ? skeletonData.DefaultSkin : skeletonData.FindSkin(linkedMesh.skin); + if (skin == null) throw new Exception("Skin not found: " + linkedMesh.skin); + Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); + if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); + linkedMesh.mesh.TimelineAttachment = linkedMesh.inheritTimelines ? (VertexAttachment)parent : linkedMesh.mesh; + linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; + if (linkedMesh.mesh.Sequence == null) linkedMesh.mesh.UpdateRegion(); + } + linkedMeshes.Clear(); + + // Events. + o = skeletonData.events.Resize(n = input.ReadInt(true)).Items; + for (int i = 0; i < n; i++) + { + EventData data = new EventData(input.ReadStringRef()); + data.Int = input.ReadInt(false); + data.Float = input.ReadFloat(); + data.String = input.ReadString(); + data.AudioPath = input.ReadString(); + if (data.AudioPath != null) + { + data.Volume = input.ReadFloat(); + data.Balance = input.ReadFloat(); + } + o[i] = data; + } + + // Animations. + o = skeletonData.animations.Resize(n = input.ReadInt(true)).Items; + for (int i = 0; i < n; i++) + o[i] = ReadAnimation(input.ReadString(), input, skeletonData); + + return skeletonData; + } + + /// May be null. + private Skin ReadSkin(SkeletonInput input, SkeletonData skeletonData, bool defaultSkin, bool nonessential) + { + + Skin skin; + int slotCount; + + if (defaultSkin) + { + slotCount = input.ReadInt(true); + if (slotCount == 0) return null; + skin = new Skin("default"); + } + else + { + skin = new Skin(input.ReadStringRef()); + Object[] bones = skin.bones.Resize(input.ReadInt(true)).Items; + var bonesItems = skeletonData.bones.Items; + for (int i = 0, n = skin.bones.Count; i < n; i++) + bones[i] = bonesItems[input.ReadInt(true)]; + + var ikConstraintsItems = skeletonData.ikConstraints.Items; + for (int i = 0, n = input.ReadInt(true); i < n; i++) + skin.constraints.Add(ikConstraintsItems[input.ReadInt(true)]); + var transformConstraintsItems = skeletonData.transformConstraints.Items; + for (int i = 0, n = input.ReadInt(true); i < n; i++) + skin.constraints.Add(transformConstraintsItems[input.ReadInt(true)]); + var pathConstraintsItems = skeletonData.pathConstraints.Items; + for (int i = 0, n = input.ReadInt(true); i < n; i++) + skin.constraints.Add(pathConstraintsItems[input.ReadInt(true)]); + skin.constraints.TrimExcess(); + + slotCount = input.ReadInt(true); + } + for (int i = 0; i < slotCount; i++) + { + int slotIndex = input.ReadInt(true); + for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) + { + String name = input.ReadStringRef(); + Attachment attachment = ReadAttachment(input, skeletonData, skin, slotIndex, name, nonessential); + if (attachment != null) skin.SetAttachment(slotIndex, name, attachment); + } + } + return skin; + } + + private Attachment ReadAttachment(SkeletonInput input, SkeletonData skeletonData, Skin skin, int slotIndex, + String attachmentName, bool nonessential) + { + float scale = this.scale; + + String name = input.ReadStringRef(); + if (name == null) name = attachmentName; + + switch ((AttachmentType)input.ReadByte()) + { + case AttachmentType.Region: + { + String path = input.ReadStringRef(); + float rotation = input.ReadFloat(); + float x = input.ReadFloat(); + float y = input.ReadFloat(); + float scaleX = input.ReadFloat(); + float scaleY = input.ReadFloat(); + float width = input.ReadFloat(); + float height = input.ReadFloat(); + int color = input.ReadInt(); + Sequence sequence = ReadSequence(input); + + if (path == null) path = name; + RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path, sequence); + if (region == null) return null; + region.Path = path; + region.x = x * scale; + region.y = y * scale; + region.scaleX = scaleX; + region.scaleY = scaleY; + region.rotation = rotation; + region.width = width * scale; + region.height = height * scale; + region.r = ((color & 0xff000000) >> 24) / 255f; + region.g = ((color & 0x00ff0000) >> 16) / 255f; + region.b = ((color & 0x0000ff00) >> 8) / 255f; + region.a = ((color & 0x000000ff)) / 255f; + region.sequence = sequence; + if (sequence == null) region.UpdateRegion(); + return region; + } + case AttachmentType.Boundingbox: + { + int vertexCount = input.ReadInt(true); + Vertices vertices = ReadVertices(input, vertexCount); + if (nonessential) input.ReadInt(); //int color = nonessential ? input.ReadInt() : 0; // Avoid unused local warning. + + BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); + if (box == null) return null; + box.worldVerticesLength = vertexCount << 1; + box.vertices = vertices.vertices; + box.bones = vertices.bones; + // skipped porting: if (nonessential) Color.rgba8888ToColor(box.getColor(), color); + return box; + } + case AttachmentType.Mesh: + { + String path = input.ReadStringRef(); + int color = input.ReadInt(); + int vertexCount = input.ReadInt(true); + float[] uvs = ReadFloatArray(input, vertexCount << 1, 1); + int[] triangles = ReadShortArray(input); + Vertices vertices = ReadVertices(input, vertexCount); + int hullLength = input.ReadInt(true); + Sequence sequence = ReadSequence(input); + int[] edges = null; + float width = 0, height = 0; + if (nonessential) + { + edges = ReadShortArray(input); + width = input.ReadFloat(); + height = input.ReadFloat(); + } + + if (path == null) path = name; + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path, sequence); + if (mesh == null) return null; + mesh.Path = path; + mesh.r = ((color & 0xff000000) >> 24) / 255f; + mesh.g = ((color & 0x00ff0000) >> 16) / 255f; + mesh.b = ((color & 0x0000ff00) >> 8) / 255f; + mesh.a = ((color & 0x000000ff)) / 255f; + mesh.bones = vertices.bones; + mesh.vertices = vertices.vertices; + mesh.WorldVerticesLength = vertexCount << 1; + mesh.triangles = triangles; + mesh.regionUVs = uvs; + if (sequence == null) mesh.UpdateRegion(); + mesh.HullLength = hullLength << 1; + mesh.Sequence = sequence; + if (nonessential) + { + mesh.Edges = edges; + mesh.Width = width * scale; + mesh.Height = height * scale; + } + return mesh; + } + case AttachmentType.Linkedmesh: + { + String path = input.ReadStringRef(); + int color = input.ReadInt(); + String skinName = input.ReadStringRef(); + String parent = input.ReadStringRef(); + bool inheritTimelines = input.ReadBoolean(); + Sequence sequence = ReadSequence(input); + float width = 0, height = 0; + if (nonessential) + { + width = input.ReadFloat(); + height = input.ReadFloat(); + } + + if (path == null) path = name; + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path, sequence); + if (mesh == null) return null; + mesh.Path = path; + mesh.r = ((color & 0xff000000) >> 24) / 255f; + mesh.g = ((color & 0x00ff0000) >> 16) / 255f; + mesh.b = ((color & 0x0000ff00) >> 8) / 255f; + mesh.a = ((color & 0x000000ff)) / 255f; + mesh.Sequence = sequence; + if (nonessential) + { + mesh.Width = width * scale; + mesh.Height = height * scale; + } + linkedMeshes.Add(new SkeletonJson.LinkedMesh(mesh, skinName, slotIndex, parent, inheritTimelines)); + return mesh; + } + case AttachmentType.Path: + { + bool closed = input.ReadBoolean(); + bool constantSpeed = input.ReadBoolean(); + int vertexCount = input.ReadInt(true); + Vertices vertices = ReadVertices(input, vertexCount); + float[] lengths = new float[vertexCount / 3]; + for (int i = 0, n = lengths.Length; i < n; i++) + lengths[i] = input.ReadFloat() * scale; + if (nonessential) input.ReadInt(); //int color = nonessential ? input.ReadInt() : 0; + + PathAttachment path = attachmentLoader.NewPathAttachment(skin, name); + if (path == null) return null; + path.closed = closed; + path.constantSpeed = constantSpeed; + path.worldVerticesLength = vertexCount << 1; + path.vertices = vertices.vertices; + path.bones = vertices.bones; + path.lengths = lengths; + // skipped porting: if (nonessential) Color.rgba8888ToColor(path.getColor(), color); + return path; + } + case AttachmentType.Point: + { + float rotation = input.ReadFloat(); + float x = input.ReadFloat(); + float y = input.ReadFloat(); + if (nonessential) input.ReadInt(); //int color = nonessential ? input.ReadInt() : 0; + + PointAttachment point = attachmentLoader.NewPointAttachment(skin, name); + if (point == null) return null; + point.x = x * scale; + point.y = y * scale; + point.rotation = rotation; + // skipped porting: if (nonessential) point.color = color; + return point; + } + case AttachmentType.Clipping: + { + int endSlotIndex = input.ReadInt(true); + int vertexCount = input.ReadInt(true); + Vertices vertices = ReadVertices(input, vertexCount); + if (nonessential) input.ReadInt(); + + ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name); + if (clip == null) return null; + clip.EndSlot = skeletonData.slots.Items[endSlotIndex]; + clip.worldVerticesLength = vertexCount << 1; + clip.vertices = vertices.vertices; + clip.bones = vertices.bones; + // skipped porting: if (nonessential) Color.rgba8888ToColor(clip.getColor(), color); + return clip; + } + } + return null; + } + + private Sequence ReadSequence(SkeletonInput input) + { + if (!input.ReadBoolean()) return null; + Sequence sequence = new Sequence(input.ReadInt(true)); + sequence.Start = input.ReadInt(true); + sequence.Digits = input.ReadInt(true); + sequence.SetupIndex = input.ReadInt(true); + return sequence; + } + + private Vertices ReadVertices(SkeletonInput input, int vertexCount) + { + float scale = this.scale; + int verticesLength = vertexCount << 1; + Vertices vertices = new Vertices(); + if (!input.ReadBoolean()) + { + vertices.vertices = ReadFloatArray(input, verticesLength, scale); + return vertices; + } + var weights = new ExposedList(verticesLength * 3 * 3); + var bonesArray = new ExposedList(verticesLength * 3); + for (int i = 0; i < vertexCount; i++) + { + int boneCount = input.ReadInt(true); + bonesArray.Add(boneCount); + for (int ii = 0; ii < boneCount; ii++) + { + bonesArray.Add(input.ReadInt(true)); + weights.Add(input.ReadFloat() * scale); + weights.Add(input.ReadFloat() * scale); + weights.Add(input.ReadFloat()); + } + } + + vertices.vertices = weights.ToArray(); + vertices.bones = bonesArray.ToArray(); + return vertices; + } + + private float[] ReadFloatArray(SkeletonInput input, int n, float scale) + { + float[] array = new float[n]; + if (scale == 1) + { + for (int i = 0; i < n; i++) + array[i] = input.ReadFloat(); + } + else + { + for (int i = 0; i < n; i++) + array[i] = input.ReadFloat() * scale; + } + return array; + } + + private int[] ReadShortArray(SkeletonInput input) + { + int n = input.ReadInt(true); + int[] array = new int[n]; + for (int i = 0; i < n; i++) + array[i] = (input.ReadByte() << 8) | input.ReadByte(); + return array; + } + + /// SerializationException will be thrown when a Vertex attachment is not found. + /// Throws IOException when a read operation fails. + private Animation ReadAnimation(String name, SkeletonInput input, SkeletonData skeletonData) + { + var timelines = new ExposedList(input.ReadInt(true)); + float scale = this.scale; + + // Slot timelines. + for (int i = 0, n = input.ReadInt(true); i < n; i++) + { + int slotIndex = input.ReadInt(true); + for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) + { + int timelineType = input.ReadByte(), frameCount = input.ReadInt(true), frameLast = frameCount - 1; + switch (timelineType) + { + case SLOT_ATTACHMENT: + { + AttachmentTimeline timeline = new AttachmentTimeline(frameCount, slotIndex); + for (int frame = 0; frame < frameCount; frame++) + timeline.SetFrame(frame, input.ReadFloat(), input.ReadStringRef()); + timelines.Add(timeline); + break; + } + case SLOT_RGBA: + { + RGBATimeline timeline = new RGBATimeline(frameCount, input.ReadInt(true), slotIndex); + float time = input.ReadFloat(); + float r = input.Read() / 255f, g = input.Read() / 255f; + float b = input.Read() / 255f, a = input.Read() / 255f; + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, r, g, b, a); + if (frame == frameLast) break; + float time2 = input.ReadFloat(); + float r2 = input.Read() / 255f, g2 = input.Read() / 255f; + float b2 = input.Read() / 255f, a2 = input.Read() / 255f; + switch (input.ReadByte()) + { + 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); + break; + } + time = time2; + r = r2; + g = g2; + b = b2; + a = a2; + } + timelines.Add(timeline); + break; + } + case SLOT_RGB: + { + RGBTimeline timeline = new RGBTimeline(frameCount, input.ReadInt(true), slotIndex); + float time = input.ReadFloat(); + float r = input.Read() / 255f, g = input.Read() / 255f, b = input.Read() / 255f; + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, r, g, b); + if (frame == frameLast) break; + float time2 = input.ReadFloat(); + float r2 = input.Read() / 255f, g2 = input.Read() / 255f, b2 = input.Read() / 255f; + switch (input.ReadByte()) + { + 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); + break; + } + time = time2; + r = r2; + g = g2; + b = b2; + } + timelines.Add(timeline); + break; + } + case SLOT_RGBA2: + { + RGBA2Timeline timeline = new RGBA2Timeline(frameCount, input.ReadInt(true), slotIndex); + float time = input.ReadFloat(); + float r = input.Read() / 255f, g = input.Read() / 255f; + float b = input.Read() / 255f, a = input.Read() / 255f; + float r2 = input.Read() / 255f, g2 = input.Read() / 255f, b2 = input.Read() / 255f; + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, r, g, b, a, r2, g2, b2); + if (frame == frameLast) break; + float time2 = input.ReadFloat(); + float nr = input.Read() / 255f, ng = input.Read() / 255f; + float nb = input.Read() / 255f, na = input.Read() / 255f; + float nr2 = input.Read() / 255f, ng2 = input.Read() / 255f, nb2 = input.Read() / 255f; + switch (input.ReadByte()) + { + 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); + break; + } + time = time2; + r = nr; + g = ng; + b = nb; + a = na; + r2 = nr2; + g2 = ng2; + b2 = nb2; + } + timelines.Add(timeline); + break; + } + case SLOT_RGB2: + { + RGB2Timeline timeline = new RGB2Timeline(frameCount, input.ReadInt(true), slotIndex); + float time = input.ReadFloat(); + float r = input.Read() / 255f, g = input.Read() / 255f, b = input.Read() / 255f; + float r2 = input.Read() / 255f, g2 = input.Read() / 255f, b2 = input.Read() / 255f; + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, r, g, b, r2, g2, b2); + if (frame == frameLast) break; + float time2 = input.ReadFloat(); + float nr = input.Read() / 255f, ng = input.Read() / 255f, nb = input.Read() / 255f; + float nr2 = input.Read() / 255f, ng2 = input.Read() / 255f, nb2 = input.Read() / 255f; + switch (input.ReadByte()) + { + 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); + break; + } + time = time2; + r = nr; + g = ng; + b = nb; + r2 = nr2; + g2 = ng2; + b2 = nb2; + } + timelines.Add(timeline); + break; + } + case SLOT_ALPHA: + { + AlphaTimeline timeline = new AlphaTimeline(frameCount, input.ReadInt(true), slotIndex); + float time = input.ReadFloat(), a = input.Read() / 255f; + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, a); + if (frame == frameLast) break; + float time2 = input.ReadFloat(); + float a2 = input.Read() / 255f; + switch (input.ReadByte()) + { + case CURVE_STEPPED: + timeline.SetStepped(frame); + break; + case CURVE_BEZIER: + SetBezier(input, timeline, bezier++, frame, 0, time, time2, a, a2, 1); + break; + } + time = time2; + a = a2; + } + timelines.Add(timeline); + break; + } + } + } + } + + // Bone timelines. + for (int i = 0, n = input.ReadInt(true); i < n; i++) + { + int boneIndex = input.ReadInt(true); + for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) + { + int type = input.ReadByte(), frameCount = input.ReadInt(true), bezierCount = input.ReadInt(true); + switch (type) + { + case BONE_ROTATE: + timelines.Add(ReadTimeline(input, new RotateTimeline(frameCount, bezierCount, boneIndex), 1)); + break; + case BONE_TRANSLATE: + timelines.Add(ReadTimeline(input, new TranslateTimeline(frameCount, bezierCount, boneIndex), scale)); + break; + case BONE_TRANSLATEX: + timelines.Add(ReadTimeline(input, new TranslateXTimeline(frameCount, bezierCount, boneIndex), scale)); + break; + case BONE_TRANSLATEY: + timelines.Add(ReadTimeline(input, new TranslateYTimeline(frameCount, bezierCount, boneIndex), scale)); + break; + case BONE_SCALE: + timelines.Add(ReadTimeline(input, new ScaleTimeline(frameCount, bezierCount, boneIndex), 1)); + break; + case BONE_SCALEX: + timelines.Add(ReadTimeline(input, new ScaleXTimeline(frameCount, bezierCount, boneIndex), 1)); + break; + case BONE_SCALEY: + timelines.Add(ReadTimeline(input, new ScaleYTimeline(frameCount, bezierCount, boneIndex), 1)); + break; + case BONE_SHEAR: + timelines.Add(ReadTimeline(input, new ShearTimeline(frameCount, bezierCount, boneIndex), 1)); + break; + case BONE_SHEARX: + timelines.Add(ReadTimeline(input, new ShearXTimeline(frameCount, bezierCount, boneIndex), 1)); + break; + case BONE_SHEARY: + timelines.Add(ReadTimeline(input, new ShearYTimeline(frameCount, bezierCount, boneIndex), 1)); + break; + } + } + } + + // IK constraint timelines. + for (int i = 0, n = input.ReadInt(true); i < n; i++) + { + int index = input.ReadInt(true), frameCount = input.ReadInt(true), frameLast = frameCount - 1; + IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount, input.ReadInt(true), index); + float time = input.ReadFloat(), mix = input.ReadFloat(), softness = input.ReadFloat() * scale; + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, mix, softness, input.ReadSByte(), input.ReadBoolean(), input.ReadBoolean()); + if (frame == frameLast) break; + float time2 = input.ReadFloat(), mix2 = input.ReadFloat(), softness2 = input.ReadFloat() * scale; + switch (input.ReadByte()) + { + case CURVE_STEPPED: + timeline.SetStepped(frame); + break; + case CURVE_BEZIER: + SetBezier(input, timeline, bezier++, frame, 0, time, time2, mix, mix2, 1); + SetBezier(input, timeline, bezier++, frame, 1, time, time2, softness, softness2, scale); + break; + } + time = time2; + mix = mix2; + softness = softness2; + } + timelines.Add(timeline); + } + + // Transform constraint timelines. + for (int i = 0, n = input.ReadInt(true); i < n; i++) + { + int index = input.ReadInt(true), frameCount = input.ReadInt(true), frameLast = frameCount - 1; + TransformConstraintTimeline timeline = new TransformConstraintTimeline(frameCount, input.ReadInt(true), index); + float time = input.ReadFloat(), mixRotate = input.ReadFloat(), mixX = input.ReadFloat(), mixY = input.ReadFloat(), + mixScaleX = input.ReadFloat(), mixScaleY = input.ReadFloat(), mixShearY = input.ReadFloat(); + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY); + if (frame == frameLast) break; + float time2 = input.ReadFloat(), mixRotate2 = input.ReadFloat(), mixX2 = input.ReadFloat(), mixY2 = input.ReadFloat(), + mixScaleX2 = input.ReadFloat(), mixScaleY2 = input.ReadFloat(), mixShearY2 = input.ReadFloat(); + switch (input.ReadByte()) + { + 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); + break; + } + 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 = input.ReadInt(true); i < n; i++) + { + int index = input.ReadInt(true); + PathConstraintData data = skeletonData.pathConstraints.Items[index]; + for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) + { + switch (input.ReadByte()) + { + case PATH_POSITION: + timelines + .Add(ReadTimeline(input, new PathConstraintPositionTimeline(input.ReadInt(true), input.ReadInt(true), index), + data.positionMode == PositionMode.Fixed ? scale : 1)); + break; + case PATH_SPACING: + timelines + .Add(ReadTimeline(input, new PathConstraintSpacingTimeline(input.ReadInt(true), input.ReadInt(true), index), + data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed ? scale : 1)); + break; + case PATH_MIX: + PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(input.ReadInt(true), input.ReadInt(true), + index); + float time = input.ReadFloat(), mixRotate = input.ReadFloat(), mixX = input.ReadFloat(), mixY = input.ReadFloat(); + for (int frame = 0, bezier = 0, frameLast = timeline.FrameCount - 1; ; frame++) + { + timeline.SetFrame(frame, time, mixRotate, mixX, mixY); + if (frame == frameLast) break; + float time2 = input.ReadFloat(), mixRotate2 = input.ReadFloat(), mixX2 = input.ReadFloat(), + mixY2 = input.ReadFloat(); + switch (input.ReadByte()) + { + 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); + break; + } + time = time2; + mixRotate = mixRotate2; + mixX = mixX2; + mixY = mixY2; + } + timelines.Add(timeline); + break; + } + } + } + + // Attachment timelines. + for (int i = 0, n = input.ReadInt(true); i < n; i++) + { + Skin skin = skeletonData.skins.Items[input.ReadInt(true)]; + for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) + { + int slotIndex = input.ReadInt(true); + for (int iii = 0, nnn = input.ReadInt(true); iii < nnn; iii++) + { + String attachmentName = input.ReadStringRef(); + Attachment attachment = skin.GetAttachment(slotIndex, attachmentName); + if (attachment == null) throw new SerializationException("Timeline attachment not found: " + attachmentName); + + int timelineType = input.ReadByte(), frameCount = input.ReadInt(true), frameLast = frameCount - 1; + switch (timelineType) + { + case ATTACHMENT_DEFORM: + { + VertexAttachment vertexAttachment = (VertexAttachment)attachment; + bool weighted = vertexAttachment.Bones != null; + float[] vertices = vertexAttachment.Vertices; + int deformLength = weighted ? (vertices.Length / 3) << 1 : vertices.Length; + + DeformTimeline timeline = new DeformTimeline(frameCount, input.ReadInt(true), slotIndex, vertexAttachment); + + float time = input.ReadFloat(); + for (int frame = 0, bezier = 0; ; frame++) + { + float[] deform; + int end = input.ReadInt(true); + if (end == 0) + deform = weighted ? new float[deformLength] : vertices; + else + { + deform = new float[deformLength]; + int start = input.ReadInt(true); + end += start; + if (scale == 1) + { + for (int v = start; v < end; v++) + deform[v] = input.ReadFloat(); + } + else + { + for (int v = start; v < end; v++) + deform[v] = input.ReadFloat() * scale; + } + if (!weighted) + { + for (int v = 0, vn = deform.Length; v < vn; v++) + deform[v] += vertices[v]; + } + } + timeline.SetFrame(frame, time, deform); + if (frame == frameLast) break; + float time2 = input.ReadFloat(); + switch (input.ReadByte()) + { + case CURVE_STEPPED: + timeline.SetStepped(frame); + break; + case CURVE_BEZIER: + SetBezier(input, timeline, bezier++, frame, 0, time, time2, 0, 1, 1); + break; + } + time = time2; + } + timelines.Add(timeline); + break; + } + case ATTACHMENT_SEQUENCE: + { + SequenceTimeline timeline = new SequenceTimeline(frameCount, slotIndex, attachment); + for (int frame = 0; frame < frameCount; frame++) + { + float time = input.ReadFloat(); + int modeAndIndex = input.ReadInt(); + timeline.SetFrame(frame, time, (SequenceMode)(modeAndIndex & 0xf), modeAndIndex >> 4, + input.ReadFloat()); + } + timelines.Add(timeline); + break; + } // end case + } // end switch + } + } + } + + // Draw order timeline. + int drawOrderCount = input.ReadInt(true); + if (drawOrderCount > 0) + { + DrawOrderTimeline timeline = new DrawOrderTimeline(drawOrderCount); + int slotCount = skeletonData.slots.Count; + for (int i = 0; i < drawOrderCount; i++) + { + float time = input.ReadFloat(); + int offsetCount = input.ReadInt(true); + int[] drawOrder = new int[slotCount]; + for (int ii = slotCount - 1; ii >= 0; ii--) + drawOrder[ii] = -1; + int[] unchanged = new int[slotCount - offsetCount]; + int originalIndex = 0, unchangedIndex = 0; + for (int ii = 0; ii < offsetCount; ii++) + { + int slotIndex = input.ReadInt(true); + // Collect unchanged items. + while (originalIndex != slotIndex) + unchanged[unchangedIndex++] = originalIndex++; + // Set changed items. + drawOrder[originalIndex + input.ReadInt(true)] = originalIndex++; + } + // Collect remaining unchanged items. + while (originalIndex < slotCount) + unchanged[unchangedIndex++] = originalIndex++; + // Fill in unchanged items. + for (int ii = 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 = input.ReadInt(true); + if (eventCount > 0) + { + EventTimeline timeline = new EventTimeline(eventCount); + for (int i = 0; i < eventCount; i++) + { + float time = input.ReadFloat(); + EventData eventData = skeletonData.events.Items[input.ReadInt(true)]; + Event e = new Event(time, eventData); + e.intValue = input.ReadInt(false); + e.floatValue = input.ReadFloat(); + e.stringValue = input.ReadBoolean() ? input.ReadString() : eventData.String; + if (e.Data.AudioPath != null) + { + e.volume = input.ReadFloat(); + e.balance = input.ReadFloat(); + } + timeline.SetFrame(i, e); + } + timelines.Add(timeline); + } + + float duration = 0; + var items = timelines.Items; + for (int i = 0, n = timelines.Count; i < n; i++) + duration = Math.Max(duration, items[i].Duration); + return new Animation(name, timelines, duration); + } + + /// Throws IOException when a read operation fails. + private Timeline ReadTimeline(SkeletonInput input, CurveTimeline1 timeline, float scale) + { + float time = input.ReadFloat(), value = input.ReadFloat() * scale; + for (int frame = 0, bezier = 0, frameLast = timeline.FrameCount - 1; ; frame++) + { + timeline.SetFrame(frame, time, value); + if (frame == frameLast) break; + float time2 = input.ReadFloat(), value2 = input.ReadFloat() * scale; + switch (input.ReadByte()) + { + case CURVE_STEPPED: + timeline.SetStepped(frame); + break; + case CURVE_BEZIER: + SetBezier(input, timeline, bezier++, frame, 0, time, time2, value, value2, scale); + break; + } + time = time2; + value = value2; + } + return timeline; + } + + /// Throws IOException when a read operation fails. + private Timeline ReadTimeline(SkeletonInput input, CurveTimeline2 timeline, float scale) + { + float time = input.ReadFloat(), value1 = input.ReadFloat() * scale, value2 = input.ReadFloat() * scale; + for (int frame = 0, bezier = 0, frameLast = timeline.FrameCount - 1; ; frame++) + { + timeline.SetFrame(frame, time, value1, value2); + if (frame == frameLast) break; + float time2 = input.ReadFloat(), nvalue1 = input.ReadFloat() * scale, nvalue2 = input.ReadFloat() * scale; + switch (input.ReadByte()) + { + 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); + break; + } + time = time2; + value1 = nvalue1; + value2 = nvalue2; + } + return timeline; + } + + /// Throws IOException when a read operation fails. + void SetBezier(SkeletonInput input, CurveTimeline timeline, int bezier, int frame, int value, float time1, float time2, + float value1, float value2, float scale) + { + timeline.SetBezier(bezier, frame, value, time1, value1, input.ReadFloat(), input.ReadFloat() * scale, input.ReadFloat(), + input.ReadFloat() * scale, time2, value2); + } + + internal class Vertices + { + public int[] bones; + public float[] vertices; + } + + internal class SkeletonInput + { + private byte[] chars = new byte[32]; + private byte[] bytesBigEndian = new byte[8]; + internal string[] strings; + Stream input; + + public SkeletonInput(Stream input) + { + this.input = input; + } + + public int Read() + { + return input.ReadByte(); + } + + public byte ReadByte() + { + return (byte)input.ReadByte(); + } + + public sbyte ReadSByte() + { + int value = input.ReadByte(); + if (value == -1) throw new EndOfStreamException(); + return (sbyte)value; + } + + public bool ReadBoolean() + { + return input.ReadByte() != 0; + } + + public float ReadFloat() + { + input.Read(bytesBigEndian, 0, 4); + chars[3] = bytesBigEndian[0]; + chars[2] = bytesBigEndian[1]; + chars[1] = bytesBigEndian[2]; + chars[0] = bytesBigEndian[3]; + return BitConverter.ToSingle(chars, 0); + } + + public int ReadInt() + { + input.Read(bytesBigEndian, 0, 4); + return (bytesBigEndian[0] << 24) + + (bytesBigEndian[1] << 16) + + (bytesBigEndian[2] << 8) + + bytesBigEndian[3]; + } + + public long ReadLong() + { + input.Read(bytesBigEndian, 0, 8); + return ((long)(bytesBigEndian[0]) << 56) + + ((long)(bytesBigEndian[1]) << 48) + + ((long)(bytesBigEndian[2]) << 40) + + ((long)(bytesBigEndian[3]) << 32) + + ((long)(bytesBigEndian[4]) << 24) + + ((long)(bytesBigEndian[5]) << 16) + + ((long)(bytesBigEndian[6]) << 8) + + bytesBigEndian[7]; + } + + public int ReadInt(bool optimizePositive) + { + int b = input.ReadByte(); + int result = b & 0x7F; + if ((b & 0x80) != 0) + { + b = input.ReadByte(); + result |= (b & 0x7F) << 7; + if ((b & 0x80) != 0) + { + b = input.ReadByte(); + result |= (b & 0x7F) << 14; + if ((b & 0x80) != 0) + { + b = input.ReadByte(); + result |= (b & 0x7F) << 21; + if ((b & 0x80) != 0) result |= (input.ReadByte() & 0x7F) << 28; + } + } + } + return optimizePositive ? result : ((result >> 1) ^ -(result & 1)); + } + + public string ReadString() + { + int byteCount = ReadInt(true); + switch (byteCount) + { + case 0: + return null; + case 1: + return ""; + } + byteCount--; + byte[] buffer = this.chars; + if (buffer.Length < byteCount) buffer = new byte[byteCount]; + ReadFully(buffer, 0, byteCount); + return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount); + } + + ///May be null. + public String ReadStringRef() + { + int index = ReadInt(true); + return index == 0 ? null : strings[index - 1]; + } + + public void ReadFully(byte[] buffer, int offset, int length) + { + while (length > 0) + { + int count = input.Read(buffer, offset, length); + if (count <= 0) throw new EndOfStreamException(); + offset += count; + length -= count; + } + } + + /// Returns the version string of binary skeleton data. + public string GetVersionString() + { + try + { + // try reading 4.0+ format + var initialPosition = input.Position; + ReadLong(); // long hash + + var stringPosition = input.Position; + int stringByteCount = ReadInt(true); + input.Position = stringPosition; + if (stringByteCount <= 13) + { + string version = ReadString(); + if (char.IsDigit(version[0])) + return version; + } + // fallback to old version format + input.Position = initialPosition; + return GetVersionStringOld3X(); + } + catch (Exception e) + { + throw new ArgumentException("Stream does not contain valid binary Skeleton Data.\n" + e, "input"); + } + } + + /// Returns old 3.8 and earlier format version string of binary skeleton data. + public string GetVersionStringOld3X() + { + // Hash. + int byteCount = ReadInt(true); + if (byteCount > 1) input.Position += byteCount - 1; + + // Version. + byteCount = ReadInt(true); + if (byteCount > 1 && byteCount <= 13) + { + byteCount--; + var buffer = new byte[byteCount]; + ReadFully(buffer, 0, byteCount); + return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount); + } + throw new ArgumentException("Stream does not contain valid binary Skeleton Data."); + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/SkeletonBounds.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/SkeletonBounds.cs index 8f36486..ff9fa7a 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/SkeletonBounds.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/SkeletonBounds.cs @@ -29,205 +29,232 @@ using System; -namespace Spine4_1_00 { - - /// - /// 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. - /// - public class SkeletonBounds { - private ExposedList polygonPool = new ExposedList(); - private float minX, minY, maxX, maxY; - - public ExposedList BoundingBoxes { get; private set; } - public ExposedList Polygons { get; private set; } - public float MinX { get { return minX; } set { minX = value; } } - public float MinY { get { return minY; } set { minY = value; } } - public float MaxX { get { return maxX; } set { maxX = value; } } - public float MaxY { get { return maxY; } set { maxY = value; } } - public float Width { get { return maxX - minX; } } - public float Height { get { return maxY - minY; } } - - public SkeletonBounds () { - BoundingBoxes = new ExposedList(); - Polygons = new ExposedList(); - } - - /// - /// Clears any previous polygons, finds all visible bounding box attachments, - /// and computes the world vertices for each bounding box's polygon. - /// The skeleton. - /// - /// If true, the axis aligned bounding box containing all the polygons is computed. - /// If false, the SkeletonBounds AABB methods will always return true. - /// - public void Update (Skeleton skeleton, bool updateAabb) { - ExposedList boundingBoxes = BoundingBoxes; - ExposedList polygons = Polygons; - Slot[] slots = skeleton.slots.Items; - int slotCount = skeleton.slots.Count; - - boundingBoxes.Clear(); - for (int i = 0, n = polygons.Count; i < n; i++) - polygonPool.Add(polygons.Items[i]); - polygons.Clear(); - - for (int i = 0; i < slotCount; i++) { - Slot slot = slots[i]; - if (!slot.bone.active) continue; - BoundingBoxAttachment boundingBox = slot.attachment as BoundingBoxAttachment; - if (boundingBox == null) continue; - boundingBoxes.Add(boundingBox); - - Polygon polygon = null; - int poolCount = polygonPool.Count; - if (poolCount > 0) { - polygon = polygonPool.Items[poolCount - 1]; - polygonPool.RemoveAt(poolCount - 1); - } else - polygon = new Polygon(); - polygons.Add(polygon); - - int count = boundingBox.worldVerticesLength; - polygon.Count = count; - if (polygon.Vertices.Length < count) polygon.Vertices = new float[count]; - boundingBox.ComputeWorldVertices(slot, polygon.Vertices); - } - - if (updateAabb) { - AabbCompute(); - } else { - minX = int.MinValue; - minY = int.MinValue; - maxX = int.MaxValue; - maxY = int.MaxValue; - } - } - - private void AabbCompute () { - float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; - Polygon[] polygons = Polygons.Items; - for (int i = 0, n = Polygons.Count; i < n; i++) { - Polygon polygon = polygons[i]; - float[] vertices = polygon.Vertices; - for (int ii = 0, nn = polygon.Count; ii < nn; ii += 2) { - float x = vertices[ii]; - float y = vertices[ii + 1]; - minX = Math.Min(minX, x); - minY = Math.Min(minY, y); - maxX = Math.Max(maxX, x); - maxY = Math.Max(maxY, y); - } - } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; - } - - /// Returns true if the axis aligned bounding box contains the point. - public bool AabbContainsPoint (float x, float y) { - return x >= minX && x <= maxX && y >= minY && y <= maxY; - } - - /// Returns true if the axis aligned bounding box intersects the line segment. - public bool AabbIntersectsSegment (float x1, float y1, float x2, float y2) { - float minX = this.minX; - float minY = this.minY; - float maxX = this.maxX; - float maxY = this.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; - } - - /// Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds. - public bool AabbIntersectsSkeleton (SkeletonBounds bounds) { - return minX < bounds.maxX && maxX > bounds.minX && minY < bounds.maxY && maxY > bounds.minY; - } - - /// Returns true if the polygon contains the point. - public bool ContainsPoint (Polygon polygon, float x, float y) { - float[] 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; - } - - /// 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 returns true. - public BoundingBoxAttachment ContainsPoint (float x, float y) { - Polygon[] polygons = Polygons.Items; - for (int i = 0, n = Polygons.Count; i < n; i++) - if (ContainsPoint(polygons[i], x, y)) return BoundingBoxes.Items[i]; - return null; - } - - /// 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 returns true. - public BoundingBoxAttachment IntersectsSegment (float x1, float y1, float x2, float y2) { - Polygon[] polygons = Polygons.Items; - for (int i = 0, n = Polygons.Count; i < n; i++) - if (IntersectsSegment(polygons[i], x1, y1, x2, y2)) return BoundingBoxes.Items[i]; - return null; - } - - /// Returns true if the polygon contains the line segment. - public bool IntersectsSegment (Polygon polygon, float x1, float y1, float x2, float y2) { - float[] vertices = polygon.Vertices; - int 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 (int 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; - } - - public Polygon GetPolygon (BoundingBoxAttachment attachment) { - int index = BoundingBoxes.IndexOf(attachment); - return index == -1 ? null : Polygons.Items[index]; - } - } - - public class Polygon { - public float[] Vertices { get; set; } - public int Count { get; set; } - - public Polygon () { - Vertices = new float[16]; - } - } +namespace Spine4_1_00 +{ + + /// + /// 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. + /// + public class SkeletonBounds + { + private ExposedList polygonPool = new ExposedList(); + private float minX, minY, maxX, maxY; + + public ExposedList BoundingBoxes { get; private set; } + public ExposedList Polygons { get; private set; } + public float MinX { get { return minX; } set { minX = value; } } + public float MinY { get { return minY; } set { minY = value; } } + public float MaxX { get { return maxX; } set { maxX = value; } } + public float MaxY { get { return maxY; } set { maxY = value; } } + public float Width { get { return maxX - minX; } } + public float Height { get { return maxY - minY; } } + + public SkeletonBounds() + { + BoundingBoxes = new ExposedList(); + Polygons = new ExposedList(); + } + + /// + /// Clears any previous polygons, finds all visible bounding box attachments, + /// and computes the world vertices for each bounding box's polygon. + /// The skeleton. + /// + /// If true, the axis aligned bounding box containing all the polygons is computed. + /// If false, the SkeletonBounds AABB methods will always return true. + /// + public void Update(Skeleton skeleton, bool updateAabb) + { + ExposedList boundingBoxes = BoundingBoxes; + ExposedList polygons = Polygons; + Slot[] slots = skeleton.slots.Items; + int slotCount = skeleton.slots.Count; + + boundingBoxes.Clear(); + for (int i = 0, n = polygons.Count; i < n; i++) + polygonPool.Add(polygons.Items[i]); + polygons.Clear(); + + for (int i = 0; i < slotCount; i++) + { + Slot slot = slots[i]; + if (!slot.bone.active) continue; + BoundingBoxAttachment boundingBox = slot.attachment as BoundingBoxAttachment; + if (boundingBox == null) continue; + boundingBoxes.Add(boundingBox); + + Polygon polygon = null; + int poolCount = polygonPool.Count; + if (poolCount > 0) + { + polygon = polygonPool.Items[poolCount - 1]; + polygonPool.RemoveAt(poolCount - 1); + } + else + polygon = new Polygon(); + polygons.Add(polygon); + + int count = boundingBox.worldVerticesLength; + polygon.Count = count; + if (polygon.Vertices.Length < count) polygon.Vertices = new float[count]; + boundingBox.ComputeWorldVertices(slot, polygon.Vertices); + } + + if (updateAabb) + { + AabbCompute(); + } + else + { + minX = int.MinValue; + minY = int.MinValue; + maxX = int.MaxValue; + maxY = int.MaxValue; + } + } + + private void AabbCompute() + { + float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; + Polygon[] polygons = Polygons.Items; + for (int i = 0, n = Polygons.Count; i < n; i++) + { + Polygon polygon = polygons[i]; + float[] vertices = polygon.Vertices; + for (int ii = 0, nn = polygon.Count; ii < nn; ii += 2) + { + float x = vertices[ii]; + float y = vertices[ii + 1]; + minX = Math.Min(minX, x); + minY = Math.Min(minY, y); + maxX = Math.Max(maxX, x); + maxY = Math.Max(maxY, y); + } + } + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /// Returns true if the axis aligned bounding box contains the point. + public bool AabbContainsPoint(float x, float y) + { + return x >= minX && x <= maxX && y >= minY && y <= maxY; + } + + /// Returns true if the axis aligned bounding box intersects the line segment. + public bool AabbIntersectsSegment(float x1, float y1, float x2, float y2) + { + float minX = this.minX; + float minY = this.minY; + float maxX = this.maxX; + float maxY = this.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; + } + + /// Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds. + public bool AabbIntersectsSkeleton(SkeletonBounds bounds) + { + return minX < bounds.maxX && maxX > bounds.minX && minY < bounds.maxY && maxY > bounds.minY; + } + + /// Returns true if the polygon contains the point. + public bool ContainsPoint(Polygon polygon, float x, float y) + { + float[] 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; + } + + /// 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 returns true. + public BoundingBoxAttachment ContainsPoint(float x, float y) + { + Polygon[] polygons = Polygons.Items; + for (int i = 0, n = Polygons.Count; i < n; i++) + if (ContainsPoint(polygons[i], x, y)) return BoundingBoxes.Items[i]; + return null; + } + + /// 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 returns true. + public BoundingBoxAttachment IntersectsSegment(float x1, float y1, float x2, float y2) + { + Polygon[] polygons = Polygons.Items; + for (int i = 0, n = Polygons.Count; i < n; i++) + if (IntersectsSegment(polygons[i], x1, y1, x2, y2)) return BoundingBoxes.Items[i]; + return null; + } + + /// Returns true if the polygon contains the line segment. + public bool IntersectsSegment(Polygon polygon, float x1, float y1, float x2, float y2) + { + float[] vertices = polygon.Vertices; + int 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 (int 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; + } + + public Polygon GetPolygon(BoundingBoxAttachment attachment) + { + int index = BoundingBoxes.IndexOf(attachment); + return index == -1 ? null : Polygons.Items[index]; + } + } + + public class Polygon + { + public float[] Vertices { get; set; } + public int Count { get; set; } + + public Polygon() + { + Vertices = new float[16]; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/SkeletonClipping.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/SkeletonClipping.cs index df478e7..84de166 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/SkeletonClipping.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/SkeletonClipping.cs @@ -29,264 +29,300 @@ using System; -namespace Spine4_1_00 { - public class SkeletonClipping { - internal readonly Triangulator triangulator = new Triangulator(); - internal readonly ExposedList clippingPolygon = new ExposedList(); - internal readonly ExposedList clipOutput = new ExposedList(128); - internal readonly ExposedList clippedVertices = new ExposedList(128); - internal readonly ExposedList clippedTriangles = new ExposedList(128); - internal readonly ExposedList clippedUVs = new ExposedList(128); - internal readonly ExposedList scratch = new ExposedList(); - - internal ClippingAttachment clipAttachment; - internal ExposedList> clippingPolygons; - - public ExposedList ClippedVertices { get { return clippedVertices; } } - public ExposedList ClippedTriangles { get { return clippedTriangles; } } - public ExposedList ClippedUVs { get { return clippedUVs; } } - - public bool IsClipping { get { return clipAttachment != null; } } - - public int ClipStart (Slot slot, ClippingAttachment clip) { - if (clipAttachment != null) return 0; - clipAttachment = clip; - - int n = clip.worldVerticesLength; - float[] vertices = clippingPolygon.Resize(n).Items; - clip.ComputeWorldVertices(slot, 0, n, vertices, 0, 2); - MakeClockwise(clippingPolygon); - clippingPolygons = triangulator.Decompose(clippingPolygon, triangulator.Triangulate(clippingPolygon)); - foreach (var polygon in clippingPolygons) { - MakeClockwise(polygon); - polygon.Add(polygon.Items[0]); - polygon.Add(polygon.Items[1]); - } - return clippingPolygons.Count; - } - - public void ClipEnd (Slot slot) { - if (clipAttachment != null && clipAttachment.endSlot == slot.data) ClipEnd(); - } - - public void ClipEnd () { - if (clipAttachment == null) return; - clipAttachment = null; - clippingPolygons = null; - clippedVertices.Clear(); - clippedTriangles.Clear(); - clippingPolygon.Clear(); - } - - public void ClipTriangles (float[] vertices, int verticesLength, int[] triangles, int trianglesLength, float[] uvs) { - ExposedList clipOutput = this.clipOutput, clippedVertices = this.clippedVertices; - var clippedTriangles = this.clippedTriangles; - var polygons = clippingPolygons.Items; - int polygonsCount = clippingPolygons.Count; - - int index = 0; - clippedVertices.Clear(); - clippedUVs.Clear(); - clippedTriangles.Clear(); - //outer: - for (int i = 0; i < trianglesLength; i += 3) { - int vertexOffset = triangles[i] << 1; - float x1 = vertices[vertexOffset], y1 = vertices[vertexOffset + 1]; - float u1 = uvs[vertexOffset], v1 = uvs[vertexOffset + 1]; - - vertexOffset = triangles[i + 1] << 1; - float x2 = vertices[vertexOffset], y2 = vertices[vertexOffset + 1]; - float u2 = uvs[vertexOffset], v2 = uvs[vertexOffset + 1]; - - vertexOffset = triangles[i + 2] << 1; - float x3 = vertices[vertexOffset], y3 = vertices[vertexOffset + 1]; - float u3 = uvs[vertexOffset], v3 = uvs[vertexOffset + 1]; - - for (int p = 0; p < polygonsCount; p++) { - int s = clippedVertices.Count; - if (Clip(x1, y1, x2, y2, x3, y3, polygons[p], clipOutput)) { - int clipOutputLength = clipOutput.Count; - 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)); - - int clipOutputCount = clipOutputLength >> 1; - float[] clipOutputItems = clipOutput.Items; - float[] clippedVerticesItems = clippedVertices.Resize(s + clipOutputCount * 2).Items; - float[] clippedUVsItems = clippedUVs.Resize(s + clipOutputCount * 2).Items; - for (int ii = 0; ii < clipOutputLength; ii += 2) { - float x = clipOutputItems[ii], y = clipOutputItems[ii + 1]; - clippedVerticesItems[s] = x; - clippedVerticesItems[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; - clippedUVsItems[s] = u1 * a + u2 * b + u3 * c; - clippedUVsItems[s + 1] = v1 * a + v2 * b + v3 * c; - s += 2; - } - - s = clippedTriangles.Count; - int[] clippedTrianglesItems = clippedTriangles.Resize(s + 3 * (clipOutputCount - 2)).Items; - clipOutputCount--; - for (int ii = 1; ii < clipOutputCount; ii++) { - clippedTrianglesItems[s] = index; - clippedTrianglesItems[s + 1] = index + ii; - clippedTrianglesItems[s + 2] = index + ii + 1; - s += 3; - } - index += clipOutputCount + 1; - } else { - float[] clippedVerticesItems = clippedVertices.Resize(s + 3 * 2).Items; - float[] clippedUVsItems = clippedUVs.Resize(s + 3 * 2).Items; - clippedVerticesItems[s] = x1; - clippedVerticesItems[s + 1] = y1; - clippedVerticesItems[s + 2] = x2; - clippedVerticesItems[s + 3] = y2; - clippedVerticesItems[s + 4] = x3; - clippedVerticesItems[s + 5] = y3; - - clippedUVsItems[s] = u1; - clippedUVsItems[s + 1] = v1; - clippedUVsItems[s + 2] = u2; - clippedUVsItems[s + 3] = v2; - clippedUVsItems[s + 4] = u3; - clippedUVsItems[s + 5] = v3; - - s = clippedTriangles.Count; - int[] clippedTrianglesItems = clippedTriangles.Resize(s + 3).Items; - clippedTrianglesItems[s] = index; - clippedTrianglesItems[s + 1] = index + 1; - clippedTrianglesItems[s + 2] = index + 2; - index += 3; - break; //continue outer; - } - } - } - - } - - /** Clips the input triangle against the convex, clockwise clipping area. If the triangle lies entirely within the clipping +namespace Spine4_1_00 +{ + public class SkeletonClipping + { + internal readonly Triangulator triangulator = new Triangulator(); + internal readonly ExposedList clippingPolygon = new ExposedList(); + internal readonly ExposedList clipOutput = new ExposedList(128); + internal readonly ExposedList clippedVertices = new ExposedList(128); + internal readonly ExposedList clippedTriangles = new ExposedList(128); + internal readonly ExposedList clippedUVs = new ExposedList(128); + internal readonly ExposedList scratch = new ExposedList(); + + internal ClippingAttachment clipAttachment; + internal ExposedList> clippingPolygons; + + public ExposedList ClippedVertices { get { return clippedVertices; } } + public ExposedList ClippedTriangles { get { return clippedTriangles; } } + public ExposedList ClippedUVs { get { return clippedUVs; } } + + public bool IsClipping { get { return clipAttachment != null; } } + + public int ClipStart(Slot slot, ClippingAttachment clip) + { + if (clipAttachment != null) return 0; + clipAttachment = clip; + + int n = clip.worldVerticesLength; + float[] vertices = clippingPolygon.Resize(n).Items; + clip.ComputeWorldVertices(slot, 0, n, vertices, 0, 2); + MakeClockwise(clippingPolygon); + clippingPolygons = triangulator.Decompose(clippingPolygon, triangulator.Triangulate(clippingPolygon)); + foreach (var polygon in clippingPolygons) + { + MakeClockwise(polygon); + polygon.Add(polygon.Items[0]); + polygon.Add(polygon.Items[1]); + } + return clippingPolygons.Count; + } + + public void ClipEnd(Slot slot) + { + if (clipAttachment != null && clipAttachment.endSlot == slot.data) ClipEnd(); + } + + public void ClipEnd() + { + if (clipAttachment == null) return; + clipAttachment = null; + clippingPolygons = null; + clippedVertices.Clear(); + clippedTriangles.Clear(); + clippingPolygon.Clear(); + } + + public void ClipTriangles(float[] vertices, int verticesLength, int[] triangles, int trianglesLength, float[] uvs) + { + ExposedList clipOutput = this.clipOutput, clippedVertices = this.clippedVertices; + var clippedTriangles = this.clippedTriangles; + var polygons = clippingPolygons.Items; + int polygonsCount = clippingPolygons.Count; + + int index = 0; + clippedVertices.Clear(); + clippedUVs.Clear(); + clippedTriangles.Clear(); + //outer: + for (int i = 0; i < trianglesLength; i += 3) + { + int vertexOffset = triangles[i] << 1; + float x1 = vertices[vertexOffset], y1 = vertices[vertexOffset + 1]; + float u1 = uvs[vertexOffset], v1 = uvs[vertexOffset + 1]; + + vertexOffset = triangles[i + 1] << 1; + float x2 = vertices[vertexOffset], y2 = vertices[vertexOffset + 1]; + float u2 = uvs[vertexOffset], v2 = uvs[vertexOffset + 1]; + + vertexOffset = triangles[i + 2] << 1; + float x3 = vertices[vertexOffset], y3 = vertices[vertexOffset + 1]; + float u3 = uvs[vertexOffset], v3 = uvs[vertexOffset + 1]; + + for (int p = 0; p < polygonsCount; p++) + { + int s = clippedVertices.Count; + if (Clip(x1, y1, x2, y2, x3, y3, polygons[p], clipOutput)) + { + int clipOutputLength = clipOutput.Count; + 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)); + + int clipOutputCount = clipOutputLength >> 1; + float[] clipOutputItems = clipOutput.Items; + float[] clippedVerticesItems = clippedVertices.Resize(s + clipOutputCount * 2).Items; + float[] clippedUVsItems = clippedUVs.Resize(s + clipOutputCount * 2).Items; + for (int ii = 0; ii < clipOutputLength; ii += 2) + { + float x = clipOutputItems[ii], y = clipOutputItems[ii + 1]; + clippedVerticesItems[s] = x; + clippedVerticesItems[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; + clippedUVsItems[s] = u1 * a + u2 * b + u3 * c; + clippedUVsItems[s + 1] = v1 * a + v2 * b + v3 * c; + s += 2; + } + + s = clippedTriangles.Count; + int[] clippedTrianglesItems = clippedTriangles.Resize(s + 3 * (clipOutputCount - 2)).Items; + clipOutputCount--; + for (int ii = 1; ii < clipOutputCount; ii++) + { + clippedTrianglesItems[s] = index; + clippedTrianglesItems[s + 1] = index + ii; + clippedTrianglesItems[s + 2] = index + ii + 1; + s += 3; + } + index += clipOutputCount + 1; + } + else + { + float[] clippedVerticesItems = clippedVertices.Resize(s + 3 * 2).Items; + float[] clippedUVsItems = clippedUVs.Resize(s + 3 * 2).Items; + clippedVerticesItems[s] = x1; + clippedVerticesItems[s + 1] = y1; + clippedVerticesItems[s + 2] = x2; + clippedVerticesItems[s + 3] = y2; + clippedVerticesItems[s + 4] = x3; + clippedVerticesItems[s + 5] = y3; + + clippedUVsItems[s] = u1; + clippedUVsItems[s + 1] = v1; + clippedUVsItems[s + 2] = u2; + clippedUVsItems[s + 3] = v2; + clippedUVsItems[s + 4] = u3; + clippedUVsItems[s + 5] = v3; + + s = clippedTriangles.Count; + int[] clippedTrianglesItems = clippedTriangles.Resize(s + 3).Items; + clippedTrianglesItems[s] = index; + clippedTrianglesItems[s + 1] = index + 1; + clippedTrianglesItems[s + 2] = index + 2; + index += 3; + break; //continue outer; + } + } + } + + } + + /** 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. */ - internal bool Clip (float x1, float y1, float x2, float y2, float x3, float y3, ExposedList clippingArea, ExposedList output) { - var originalOutput = output; - var clipped = false; - - // Avoid copy at the end. - ExposedList input = null; - if (clippingArea.Count % 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(); - - float[] clippingVertices = clippingArea.Items; - int clippingVerticesLast = clippingArea.Count - 4; - for (int i = 0; ; i += 2) { - float edgeX = clippingVertices[i], edgeY = clippingVertices[i + 1]; - float edgeX2 = clippingVertices[i + 2], edgeY2 = clippingVertices[i + 3]; - float deltaX = edgeX - edgeX2, deltaY = edgeY - edgeY2; - - float[] inputVertices = input.Items; - int inputVerticesLength = input.Count - 2, outputStart = output.Count; - for (int ii = 0; ii < inputVerticesLength; ii += 2) { - float inputX = inputVertices[ii], inputY = inputVertices[ii + 1]; - float inputX2 = inputVertices[ii + 2], inputY2 = inputVertices[ii + 3]; - bool side2 = deltaX * (inputY2 - edgeY2) - deltaY * (inputX2 - edgeX2) > 0; - if (deltaX * (inputY - edgeY2) - deltaY * (inputX - edgeX2) > 0) { - if (side2) { // v1 inside, v2 inside - output.Add(inputX2); - output.Add(inputY2); - continue; - } - // v1 inside, v2 outside - float c0 = inputY2 - inputY, c2 = inputX2 - inputX; - float s = c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY); - if (Math.Abs(s) > 0.000001f) { - float ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / s; - output.Add(edgeX + (edgeX2 - edgeX) * ua); - output.Add(edgeY + (edgeY2 - edgeY) * ua); - } else { - output.Add(edgeX); - output.Add(edgeY); - } - } else if (side2) { // v1 outside, v2 inside - float c0 = inputY2 - inputY, c2 = inputX2 - inputX; - float s = c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY); - if (Math.Abs(s) > 0.000001f) { - float ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / s; - output.Add(edgeX + (edgeX2 - edgeX) * ua); - output.Add(edgeY + (edgeY2 - edgeY) * ua); - } else { - output.Add(edgeX); - output.Add(edgeY); - } - output.Add(inputX2); - output.Add(inputY2); - } - clipped = true; - } - - if (outputStart == output.Count) { // All edges outside. - originalOutput.Clear(); - return true; - } - - output.Add(output.Items[0]); - output.Add(output.Items[1]); - - if (i == clippingVerticesLast) break; - var temp = output; - output = input; - output.Clear(); - input = temp; - } - - if (originalOutput != output) { - originalOutput.Clear(); - for (int i = 0, n = output.Count - 2; i < n; i++) - originalOutput.Add(output.Items[i]); - } else - originalOutput.Resize(originalOutput.Count - 2); - - return clipped; - } - - public static void MakeClockwise (ExposedList polygon) { - float[] vertices = polygon.Items; - int verticeslength = polygon.Count; - - float area = vertices[verticeslength - 2] * vertices[1] - vertices[0] * vertices[verticeslength - 1], p1x, p1y, p2x, p2y; - for (int i = 0, n = verticeslength - 3; i < n; i += 2) { - p1x = vertices[i]; - p1y = vertices[i + 1]; - p2x = vertices[i + 2]; - p2y = vertices[i + 3]; - area += p1x * p2y - p2x * p1y; - } - if (area < 0) return; - - for (int i = 0, lastX = verticeslength - 2, n = verticeslength >> 1; i < n; i += 2) { - float x = vertices[i], y = vertices[i + 1]; - int other = lastX - i; - vertices[i] = vertices[other]; - vertices[i + 1] = vertices[other + 1]; - vertices[other] = x; - vertices[other + 1] = y; - } - } - } + internal bool Clip(float x1, float y1, float x2, float y2, float x3, float y3, ExposedList clippingArea, ExposedList output) + { + var originalOutput = output; + var clipped = false; + + // Avoid copy at the end. + ExposedList input = null; + if (clippingArea.Count % 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(); + + float[] clippingVertices = clippingArea.Items; + int clippingVerticesLast = clippingArea.Count - 4; + for (int i = 0; ; i += 2) + { + float edgeX = clippingVertices[i], edgeY = clippingVertices[i + 1]; + float edgeX2 = clippingVertices[i + 2], edgeY2 = clippingVertices[i + 3]; + float deltaX = edgeX - edgeX2, deltaY = edgeY - edgeY2; + + float[] inputVertices = input.Items; + int inputVerticesLength = input.Count - 2, outputStart = output.Count; + for (int ii = 0; ii < inputVerticesLength; ii += 2) + { + float inputX = inputVertices[ii], inputY = inputVertices[ii + 1]; + float inputX2 = inputVertices[ii + 2], inputY2 = inputVertices[ii + 3]; + bool side2 = deltaX * (inputY2 - edgeY2) - deltaY * (inputX2 - edgeX2) > 0; + if (deltaX * (inputY - edgeY2) - deltaY * (inputX - edgeX2) > 0) + { + if (side2) + { // v1 inside, v2 inside + output.Add(inputX2); + output.Add(inputY2); + continue; + } + // v1 inside, v2 outside + float c0 = inputY2 - inputY, c2 = inputX2 - inputX; + float s = c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY); + if (Math.Abs(s) > 0.000001f) + { + float ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / s; + output.Add(edgeX + (edgeX2 - edgeX) * ua); + output.Add(edgeY + (edgeY2 - edgeY) * ua); + } + else + { + output.Add(edgeX); + output.Add(edgeY); + } + } + else if (side2) + { // v1 outside, v2 inside + float c0 = inputY2 - inputY, c2 = inputX2 - inputX; + float s = c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY); + if (Math.Abs(s) > 0.000001f) + { + float ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / s; + output.Add(edgeX + (edgeX2 - edgeX) * ua); + output.Add(edgeY + (edgeY2 - edgeY) * ua); + } + else + { + output.Add(edgeX); + output.Add(edgeY); + } + output.Add(inputX2); + output.Add(inputY2); + } + clipped = true; + } + + if (outputStart == output.Count) + { // All edges outside. + originalOutput.Clear(); + return true; + } + + output.Add(output.Items[0]); + output.Add(output.Items[1]); + + if (i == clippingVerticesLast) break; + var temp = output; + output = input; + output.Clear(); + input = temp; + } + + if (originalOutput != output) + { + originalOutput.Clear(); + for (int i = 0, n = output.Count - 2; i < n; i++) + originalOutput.Add(output.Items[i]); + } + else + originalOutput.Resize(originalOutput.Count - 2); + + return clipped; + } + + public static void MakeClockwise(ExposedList polygon) + { + float[] vertices = polygon.Items; + int verticeslength = polygon.Count; + + float area = vertices[verticeslength - 2] * vertices[1] - vertices[0] * vertices[verticeslength - 1], p1x, p1y, p2x, p2y; + for (int i = 0, n = verticeslength - 3; i < n; i += 2) + { + p1x = vertices[i]; + p1y = vertices[i + 1]; + p2x = vertices[i + 2]; + p2y = vertices[i + 3]; + area += p1x * p2y - p2x * p1y; + } + if (area < 0) return; + + for (int i = 0, lastX = verticeslength - 2, n = verticeslength >> 1; i < n; i += 2) + { + float x = vertices[i], y = vertices[i + 1]; + int other = lastX - i; + vertices[i] = vertices[other]; + vertices[i + 1] = vertices[other + 1]; + vertices[other] = x; + vertices[other + 1] = y; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/SkeletonData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/SkeletonData.cs index d6776ad..90601b9 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/SkeletonData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/SkeletonData.cs @@ -29,199 +29,218 @@ using System; -namespace Spine4_1_00 { - - /// Stores the setup pose and all of the stateless data for a skeleton. - public class SkeletonData { - internal string name; - internal ExposedList bones = new ExposedList(); // Ordered parents first - internal ExposedList slots = new ExposedList(); // Setup pose draw order. - internal ExposedList skins = new ExposedList(); - internal Skin defaultSkin; - internal ExposedList events = new ExposedList(); - internal ExposedList animations = new ExposedList(); - internal ExposedList ikConstraints = new ExposedList(); - internal ExposedList transformConstraints = new ExposedList(); - internal ExposedList pathConstraints = new ExposedList(); - internal ExposedList springConstraints = new ExposedList(); - internal float x, y, width, height; - internal string version, hash; - - // Nonessential. - internal float fps; - internal string imagesPath, audioPath; - - ///The skeleton's name, which by default is the name of the skeleton data file when possible, or null when a name hasn't been - ///set. - public string Name { get { return name; } set { name = value; } } - - /// The skeleton's bones, sorted parent first. The root bone is always the first bone. - public ExposedList Bones { get { return bones; } } - - public ExposedList Slots { get { return slots; } } - - /// All skins, including the default skin. - public ExposedList Skins { get { return skins; } set { skins = value; } } - - /// - /// The skeleton's default skin. - /// By default this skin contains all attachments that were not in a skin in Spine. - /// - /// May be null. - public Skin DefaultSkin { get { return defaultSkin; } set { defaultSkin = value; } } - - public ExposedList Events { get { return events; } set { events = value; } } - public ExposedList Animations { get { return animations; } set { animations = value; } } - public ExposedList IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } } - public ExposedList TransformConstraints { get { return transformConstraints; } set { transformConstraints = value; } } - public ExposedList PathConstraints { get { return pathConstraints; } set { pathConstraints = value; } } - - public float X { get { return x; } set { x = value; } } - public float Y { get { return y; } set { y = value; } } - public float Width { get { return width; } set { width = value; } } - public float Height { get { return height; } set { height = value; } } - /// The Spine version used to export this data, or null. - public string Version { get { return version; } set { version = value; } } - - ///The skeleton data hash. This value will change if any of the skeleton data has changed. - ///May be null. - public string Hash { get { return hash; } set { hash = value; } } - - public string ImagesPath { get { return imagesPath; } set { imagesPath = value; } } - - /// The path to the audio directory as defined in Spine. Available only when nonessential data was exported. - /// May be null. - public string AudioPath { get { return audioPath; } set { audioPath = value; } } - - /// The dopesheet FPS in Spine, or zero if nonessential data was not exported. - public float Fps { get { return fps; } set { fps = value; } } - - // --- Bones - - /// - /// 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. - /// May be null. - public BoneData FindBone (string boneName) { - if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); - var bones = this.bones.Items; - for (int i = 0, n = this.bones.Count; i < n; i++) { - BoneData bone = bones[i]; - if (bone.name == boneName) return bone; - } - return null; - } - - // --- Slots - - /// May be null. - public SlotData FindSlot (string slotName) { - if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); - var slots = this.slots.Items; - for (int i = 0, n = this.slots.Count; i < n; i++) { - SlotData slot = slots[i]; - if (slot.name == slotName) return slot; - } - return null; - } - - // --- Skins - - /// May be null. - public Skin FindSkin (string skinName) { - if (skinName == null) throw new ArgumentNullException("skinName", "skinName cannot be null."); - foreach (Skin skin in skins) - if (skin.name == skinName) return skin; - return null; - } - - // --- Events - - /// May be null. - public EventData FindEvent (string eventDataName) { - if (eventDataName == null) throw new ArgumentNullException("eventDataName", "eventDataName cannot be null."); - foreach (EventData eventData in events) - if (eventData.name == eventDataName) return eventData; - return null; - } - - // --- Animations - - /// May be null. - public Animation FindAnimation (string animationName) { - if (animationName == null) throw new ArgumentNullException("animationName", "animationName cannot be null."); - var animations = this.animations.Items; - for (int i = 0, n = this.animations.Count; i < n; i++) { - Animation animation = animations[i]; - if (animation.name == animationName) return animation; - } - return null; - } - - // --- IK constraints - - /// May be null. - public IkConstraintData FindIkConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - var ikConstraints = this.ikConstraints.Items; - for (int i = 0, n = this.ikConstraints.Count; i < n; i++) { - IkConstraintData ikConstraint = ikConstraints[i]; - if (ikConstraint.name == constraintName) return ikConstraint; - } - return null; - } - - // --- Transform constraints - - /// May be null. - public TransformConstraintData FindTransformConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - var transformConstraints = this.transformConstraints.Items; - for (int i = 0, n = this.transformConstraints.Count; i < n; i++) { - TransformConstraintData transformConstraint = transformConstraints[i]; - if (transformConstraint.name == constraintName) return transformConstraint; - } - return null; - } - - // --- Path constraints - - /// - /// Finds a path constraint by comparing each path constraint's name. It is more efficient to cache the results of this method - /// than to call it multiple times. - /// - /// May be null. - public PathConstraintData FindPathConstraint (string constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - var pathConstraints = this.pathConstraints.Items; - for (int i = 0, n = this.pathConstraints.Count; i < n; i++) { - PathConstraintData constraint = pathConstraints[i]; - if (constraint.name.Equals(constraintName)) return constraint; - } - return null; - } - - // --- Spring constraints - - /// - /// Finds a spring constraint by comparing each spring constraint's name. It is more efficient to cache the results of this - /// method than to call it multiple times. - /// - /// May be null. - public SpringConstraintData FindSpringConstraint (String constraintName) { - if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); - Object[] springConstraints = this.springConstraints.Items; - for (int i = 0, n = this.springConstraints.Count; i < n; i++) { - SpringConstraintData constraint = (SpringConstraintData)springConstraints[i]; - if (constraint.name.Equals(constraintName)) return constraint; - } - return null; - } - - // --- - - override public string ToString () { - return name ?? base.ToString(); - } - } +namespace Spine4_1_00 +{ + + /// Stores the setup pose and all of the stateless data for a skeleton. + public class SkeletonData + { + internal string name; + internal ExposedList bones = new ExposedList(); // Ordered parents first + internal ExposedList slots = new ExposedList(); // Setup pose draw order. + internal ExposedList skins = new ExposedList(); + internal Skin defaultSkin; + internal ExposedList events = new ExposedList(); + internal ExposedList animations = new ExposedList(); + internal ExposedList ikConstraints = new ExposedList(); + internal ExposedList transformConstraints = new ExposedList(); + internal ExposedList pathConstraints = new ExposedList(); + internal ExposedList springConstraints = new ExposedList(); + internal float x, y, width, height; + internal string version, hash; + + // Nonessential. + internal float fps; + internal string imagesPath, audioPath; + + ///The skeleton's name, which by default is the name of the skeleton data file when possible, or null when a name hasn't been + ///set. + public string Name { get { return name; } set { name = value; } } + + /// The skeleton's bones, sorted parent first. The root bone is always the first bone. + public ExposedList Bones { get { return bones; } } + + public ExposedList Slots { get { return slots; } } + + /// All skins, including the default skin. + public ExposedList Skins { get { return skins; } set { skins = value; } } + + /// + /// The skeleton's default skin. + /// By default this skin contains all attachments that were not in a skin in Spine. + /// + /// May be null. + public Skin DefaultSkin { get { return defaultSkin; } set { defaultSkin = value; } } + + public ExposedList Events { get { return events; } set { events = value; } } + public ExposedList Animations { get { return animations; } set { animations = value; } } + public ExposedList IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } } + public ExposedList TransformConstraints { get { return transformConstraints; } set { transformConstraints = value; } } + public ExposedList PathConstraints { get { return pathConstraints; } set { pathConstraints = value; } } + + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public float Width { get { return width; } set { width = value; } } + public float Height { get { return height; } set { height = value; } } + /// The Spine version used to export this data, or null. + public string Version { get { return version; } set { version = value; } } + + ///The skeleton data hash. This value will change if any of the skeleton data has changed. + ///May be null. + public string Hash { get { return hash; } set { hash = value; } } + + public string ImagesPath { get { return imagesPath; } set { imagesPath = value; } } + + /// The path to the audio directory as defined in Spine. Available only when nonessential data was exported. + /// May be null. + public string AudioPath { get { return audioPath; } set { audioPath = value; } } + + /// The dopesheet FPS in Spine, or zero if nonessential data was not exported. + public float Fps { get { return fps; } set { fps = value; } } + + // --- Bones + + /// + /// 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. + /// May be null. + public BoneData FindBone(string boneName) + { + if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); + var bones = this.bones.Items; + for (int i = 0, n = this.bones.Count; i < n; i++) + { + BoneData bone = bones[i]; + if (bone.name == boneName) return bone; + } + return null; + } + + // --- Slots + + /// May be null. + public SlotData FindSlot(string slotName) + { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + var slots = this.slots.Items; + for (int i = 0, n = this.slots.Count; i < n; i++) + { + SlotData slot = slots[i]; + if (slot.name == slotName) return slot; + } + return null; + } + + // --- Skins + + /// May be null. + public Skin FindSkin(string skinName) + { + if (skinName == null) throw new ArgumentNullException("skinName", "skinName cannot be null."); + foreach (Skin skin in skins) + if (skin.name == skinName) return skin; + return null; + } + + // --- Events + + /// May be null. + public EventData FindEvent(string eventDataName) + { + if (eventDataName == null) throw new ArgumentNullException("eventDataName", "eventDataName cannot be null."); + foreach (EventData eventData in events) + if (eventData.name == eventDataName) return eventData; + return null; + } + + // --- Animations + + /// May be null. + public Animation FindAnimation(string animationName) + { + if (animationName == null) throw new ArgumentNullException("animationName", "animationName cannot be null."); + var animations = this.animations.Items; + for (int i = 0, n = this.animations.Count; i < n; i++) + { + Animation animation = animations[i]; + if (animation.name == animationName) return animation; + } + return null; + } + + // --- IK constraints + + /// May be null. + public IkConstraintData FindIkConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + var ikConstraints = this.ikConstraints.Items; + for (int i = 0, n = this.ikConstraints.Count; i < n; i++) + { + IkConstraintData ikConstraint = ikConstraints[i]; + if (ikConstraint.name == constraintName) return ikConstraint; + } + return null; + } + + // --- Transform constraints + + /// May be null. + public TransformConstraintData FindTransformConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + var transformConstraints = this.transformConstraints.Items; + for (int i = 0, n = this.transformConstraints.Count; i < n; i++) + { + TransformConstraintData transformConstraint = transformConstraints[i]; + if (transformConstraint.name == constraintName) return transformConstraint; + } + return null; + } + + // --- Path constraints + + /// + /// Finds a path constraint by comparing each path constraint's name. It is more efficient to cache the results of this method + /// than to call it multiple times. + /// + /// May be null. + public PathConstraintData FindPathConstraint(string constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + var pathConstraints = this.pathConstraints.Items; + for (int i = 0, n = this.pathConstraints.Count; i < n; i++) + { + PathConstraintData constraint = pathConstraints[i]; + if (constraint.name.Equals(constraintName)) return constraint; + } + return null; + } + + // --- Spring constraints + + /// + /// Finds a spring constraint by comparing each spring constraint's name. It is more efficient to cache the results of this + /// method than to call it multiple times. + /// + /// May be null. + public SpringConstraintData FindSpringConstraint(String constraintName) + { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + Object[] springConstraints = this.springConstraints.Items; + for (int i = 0, n = this.springConstraints.Count; i < n; i++) + { + SpringConstraintData constraint = (SpringConstraintData)springConstraints[i]; + if (constraint.name.Equals(constraintName)) return constraint; + } + return null; + } + + // --- + + override public string ToString() + { + return name ?? base.ToString(); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/SkeletonJson.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/SkeletonJson.cs index f29f489..9901263 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/SkeletonJson.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/SkeletonJson.cs @@ -40,26 +40,30 @@ using Windows.Storage; #endif -namespace Spine4_1_00 { - - /// - /// Loads skeleton data in the Spine JSON format. - /// - /// JSON is human readable but the binary format is much smaller on disk and faster to load. See . - /// - /// See Spine JSON format and - /// JSON and binary data in the Spine - /// Runtimes Guide. - /// - public class SkeletonJson : SkeletonLoader { - - public SkeletonJson (AttachmentLoader attachmentLoader) - : base(attachmentLoader) { - } - - public SkeletonJson (params Atlas[] atlasArray) - : base(atlasArray) { - } +namespace Spine4_1_00 +{ + + /// + /// Loads skeleton data in the Spine JSON format. + /// + /// JSON is human readable but the binary format is much smaller on disk and faster to load. See . + /// + /// See Spine JSON format and + /// JSON and binary data in the Spine + /// Runtimes Guide. + /// + public class SkeletonJson : SkeletonLoader + { + + public SkeletonJson(AttachmentLoader attachmentLoader) + : base(attachmentLoader) + { + } + + public SkeletonJson(params Atlas[] atlasArray) + : base(atlasArray) + { + } #if !IS_UNITY && WINDOWS_STOREAPP private async Task ReadFile(string path) { @@ -76,1165 +80,1344 @@ public override SkeletonData ReadSkeletonData (string path) { return this.ReadFile(path).Result; } #else - public override SkeletonData ReadSkeletonData (string path) { + public override SkeletonData ReadSkeletonData(string path) + { #if WINDOWS_PHONE using (var reader = new StreamReader(Microsoft.Xna.Framework.TitleContainer.OpenStream(path))) { #else - using (var reader = new StreamReader(new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))) { + using (var reader = new StreamReader(new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))) + { #endif - SkeletonData skeletonData = ReadSkeletonData(reader); - skeletonData.name = Path.GetFileNameWithoutExtension(path); - return skeletonData; - } - } + SkeletonData skeletonData = ReadSkeletonData(reader); + skeletonData.name = Path.GetFileNameWithoutExtension(path); + return skeletonData; + } + } #endif - public SkeletonData ReadSkeletonData (TextReader reader) { - if (reader == null) throw new ArgumentNullException("reader", "reader cannot be null."); - - float scale = this.scale; - var skeletonData = new SkeletonData(); - - var root = Json.Deserialize(reader) as Dictionary; - if (root == null) throw new Exception("Invalid JSON."); - - // Skeleton. - if (root.ContainsKey("skeleton")) { - var skeletonMap = (Dictionary)root["skeleton"]; - skeletonData.hash = (string)skeletonMap["hash"]; - skeletonData.version = (string)skeletonMap["spine"]; - skeletonData.x = GetFloat(skeletonMap, "x", 0); - skeletonData.y = GetFloat(skeletonMap, "y", 0); - skeletonData.width = GetFloat(skeletonMap, "width", 0); - skeletonData.height = GetFloat(skeletonMap, "height", 0); - skeletonData.fps = GetFloat(skeletonMap, "fps", 30); - skeletonData.imagesPath = GetString(skeletonMap, "images", null); - skeletonData.audioPath = GetString(skeletonMap, "audio", null); - } - - // Bones. - if (root.ContainsKey("bones")) { - foreach (Dictionary boneMap in (List)root["bones"]) { - BoneData parent = null; - if (boneMap.ContainsKey("parent")) { - parent = skeletonData.FindBone((string)boneMap["parent"]); - if (parent == null) - throw new Exception("Parent bone not found: " + boneMap["parent"]); - } - var data = new BoneData(skeletonData.Bones.Count, (string)boneMap["name"], parent); - data.length = GetFloat(boneMap, "length", 0) * scale; - data.x = GetFloat(boneMap, "x", 0) * scale; - data.y = GetFloat(boneMap, "y", 0) * scale; - data.rotation = GetFloat(boneMap, "rotation", 0); - data.scaleX = GetFloat(boneMap, "scaleX", 1); - data.scaleY = GetFloat(boneMap, "scaleY", 1); - data.shearX = GetFloat(boneMap, "shearX", 0); - data.shearY = GetFloat(boneMap, "shearY", 0); - - string tm = GetString(boneMap, "transform", TransformMode.Normal.ToString()); - data.transformMode = (TransformMode)Enum.Parse(typeof(TransformMode), tm, true); - data.skinRequired = GetBoolean(boneMap, "skin", false); - - skeletonData.bones.Add(data); - } - } - - // Slots. - if (root.ContainsKey("slots")) { - foreach (Dictionary slotMap in (List)root["slots"]) { - var slotName = (string)slotMap["name"]; - var boneName = (string)slotMap["bone"]; - BoneData boneData = skeletonData.FindBone(boneName); - if (boneData == null) throw new Exception("Slot bone not found: " + boneName); - var data = new SlotData(skeletonData.Slots.Count, slotName, boneData); - - if (slotMap.ContainsKey("color")) { - string color = (string)slotMap["color"]; - data.r = ToColor(color, 0); - data.g = ToColor(color, 1); - data.b = ToColor(color, 2); - data.a = ToColor(color, 3); - } - - if (slotMap.ContainsKey("dark")) { - var color2 = (string)slotMap["dark"]; - data.r2 = ToColor(color2, 0, 6); // expectedLength = 6. ie. "RRGGBB" - data.g2 = ToColor(color2, 1, 6); - data.b2 = ToColor(color2, 2, 6); - data.hasSecondColor = true; - } - - data.attachmentName = GetString(slotMap, "attachment", null); - if (slotMap.ContainsKey("blend")) - data.blendMode = (BlendMode)Enum.Parse(typeof(BlendMode), (string)slotMap["blend"], true); - else - data.blendMode = BlendMode.Normal; - skeletonData.slots.Add(data); - } - } - - // IK constraints. - if (root.ContainsKey("ik")) { - foreach (Dictionary constraintMap in (List)root["ik"]) { - IkConstraintData data = new IkConstraintData((string)constraintMap["name"]); - data.order = GetInt(constraintMap, "order", 0); - data.skinRequired = GetBoolean(constraintMap, "skin", false); - - if (constraintMap.ContainsKey("bones")) { - foreach (string boneName in (List)constraintMap["bones"]) { - BoneData bone = skeletonData.FindBone(boneName); - if (bone == null) throw new Exception("IK bone not found: " + boneName); - data.bones.Add(bone); - } - } - - string targetName = (string)constraintMap["target"]; - data.target = skeletonData.FindBone(targetName); - if (data.target == null) throw new Exception("IK target bone not found: " + targetName); - data.mix = GetFloat(constraintMap, "mix", 1); - data.softness = GetFloat(constraintMap, "softness", 0) * scale; - data.bendDirection = GetBoolean(constraintMap, "bendPositive", true) ? 1 : -1; - data.compress = GetBoolean(constraintMap, "compress", false); - data.stretch = GetBoolean(constraintMap, "stretch", false); - data.uniform = GetBoolean(constraintMap, "uniform", false); - - skeletonData.ikConstraints.Add(data); - } - } - - // Transform constraints. - if (root.ContainsKey("transform")) { - foreach (Dictionary constraintMap in (List)root["transform"]) { - TransformConstraintData data = new TransformConstraintData((string)constraintMap["name"]); - data.order = GetInt(constraintMap, "order", 0); - data.skinRequired = GetBoolean(constraintMap, "skin", false); - - if (constraintMap.ContainsKey("bones")) { - foreach (string boneName in (List)constraintMap["bones"]) { - BoneData bone = skeletonData.FindBone(boneName); - if (bone == null) throw new Exception("Transform constraint bone not found: " + boneName); - data.bones.Add(bone); - } - } - - string targetName = (string)constraintMap["target"]; - data.target = skeletonData.FindBone(targetName); - if (data.target == null) throw new Exception("Transform constraint target bone not found: " + targetName); - - data.local = GetBoolean(constraintMap, "local", false); - data.relative = GetBoolean(constraintMap, "relative", false); - - data.offsetRotation = GetFloat(constraintMap, "rotation", 0); - data.offsetX = GetFloat(constraintMap, "x", 0) * scale; - data.offsetY = GetFloat(constraintMap, "y", 0) * scale; - data.offsetScaleX = GetFloat(constraintMap, "scaleX", 0); - data.offsetScaleY = GetFloat(constraintMap, "scaleY", 0); - data.offsetShearY = GetFloat(constraintMap, "shearY", 0); - - data.mixRotate = GetFloat(constraintMap, "mixRotate", 1); - data.mixX = GetFloat(constraintMap, "mixX", 1); - data.mixY = GetFloat(constraintMap, "mixY", data.mixX); - data.mixScaleX = GetFloat(constraintMap, "mixScaleX", 1); - data.mixScaleY = GetFloat(constraintMap, "mixScaleY", data.mixScaleX); - data.mixShearY = GetFloat(constraintMap, "mixShearY", 1); - - skeletonData.transformConstraints.Add(data); - } - } - - // Path constraints. - if (root.ContainsKey("path")) { - foreach (Dictionary constraintMap in (List)root["path"]) { - PathConstraintData data = new PathConstraintData((string)constraintMap["name"]); - data.order = GetInt(constraintMap, "order", 0); - data.skinRequired = GetBoolean(constraintMap, "skin", false); - - if (constraintMap.ContainsKey("bones")) { - foreach (string boneName in (List)constraintMap["bones"]) { - BoneData bone = skeletonData.FindBone(boneName); - if (bone == null) throw new Exception("Path bone not found: " + boneName); - data.bones.Add(bone); - } - } - - string targetName = (string)constraintMap["target"]; - data.target = skeletonData.FindSlot(targetName); - if (data.target == null) throw new Exception("Path target slot not found: " + targetName); - - data.positionMode = (PositionMode)Enum.Parse(typeof(PositionMode), GetString(constraintMap, "positionMode", "percent"), true); - data.spacingMode = (SpacingMode)Enum.Parse(typeof(SpacingMode), GetString(constraintMap, "spacingMode", "length"), true); - data.rotateMode = (RotateMode)Enum.Parse(typeof(RotateMode), GetString(constraintMap, "rotateMode", "tangent"), true); - data.offsetRotation = GetFloat(constraintMap, "rotation", 0); - data.position = GetFloat(constraintMap, "position", 0); - if (data.positionMode == PositionMode.Fixed) data.position *= scale; - data.spacing = GetFloat(constraintMap, "spacing", 0); - if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale; - data.mixRotate = GetFloat(constraintMap, "mixRotate", 1); - data.mixX = GetFloat(constraintMap, "mixX", 1); - data.mixY = GetFloat(constraintMap, "mixY", data.mixX); - - skeletonData.pathConstraints.Add(data); - } - } - - // Skins. - if (root.ContainsKey("skins")) { - foreach (Dictionary skinMap in (List)root["skins"]) { - Skin skin = new Skin((string)skinMap["name"]); - if (skinMap.ContainsKey("bones")) { - foreach (string entryName in (List)skinMap["bones"]) { - BoneData bone = skeletonData.FindBone(entryName); - if (bone == null) throw new Exception("Skin bone not found: " + entryName); - skin.bones.Add(bone); - } - } - skin.bones.TrimExcess(); - if (skinMap.ContainsKey("ik")) { - foreach (string entryName in (List)skinMap["ik"]) { - IkConstraintData constraint = skeletonData.FindIkConstraint(entryName); - if (constraint == null) throw new Exception("Skin IK constraint not found: " + entryName); - skin.constraints.Add(constraint); - } - } - if (skinMap.ContainsKey("transform")) { - foreach (string entryName in (List)skinMap["transform"]) { - TransformConstraintData constraint = skeletonData.FindTransformConstraint(entryName); - if (constraint == null) throw new Exception("Skin transform constraint not found: " + entryName); - skin.constraints.Add(constraint); - } - } - if (skinMap.ContainsKey("path")) { - foreach (string entryName in (List)skinMap["path"]) { - PathConstraintData constraint = skeletonData.FindPathConstraint(entryName); - if (constraint == null) throw new Exception("Skin path constraint not found: " + entryName); - skin.constraints.Add(constraint); - } - } - skin.constraints.TrimExcess(); - if (skinMap.ContainsKey("attachments")) { - foreach (KeyValuePair slotEntry in (Dictionary)skinMap["attachments"]) { - int slotIndex = FindSlotIndex(skeletonData, slotEntry.Key); - foreach (KeyValuePair entry in ((Dictionary)slotEntry.Value)) { - try { - Attachment attachment = ReadAttachment((Dictionary)entry.Value, skin, slotIndex, entry.Key, skeletonData); - if (attachment != null) skin.SetAttachment(slotIndex, entry.Key, attachment); - } catch (Exception e) { - throw new Exception("Error reading attachment: " + entry.Key + ", skin: " + skin, e); - } - } - } - } - skeletonData.skins.Add(skin); - if (skin.name == "default") skeletonData.defaultSkin = skin; - } - } - - // Linked meshes. - for (int i = 0, n = linkedMeshes.Count; i < n; i++) { - LinkedMesh linkedMesh = linkedMeshes[i]; - Skin skin = linkedMesh.skin == null ? skeletonData.defaultSkin : skeletonData.FindSkin(linkedMesh.skin); - if (skin == null) throw new Exception("Slot not found: " + linkedMesh.skin); - Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); - if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); - linkedMesh.mesh.TimelineAttachment = linkedMesh.inheritTimelines ? (VertexAttachment)parent : linkedMesh.mesh; - linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; - if (linkedMesh.mesh.Region != null) linkedMesh.mesh.UpdateRegion(); - } - linkedMeshes.Clear(); - - // Events. - if (root.ContainsKey("events")) { - foreach (KeyValuePair entry in (Dictionary)root["events"]) { - var entryMap = (Dictionary)entry.Value; - var data = new EventData(entry.Key); - data.Int = GetInt(entryMap, "int", 0); - data.Float = GetFloat(entryMap, "float", 0); - data.String = GetString(entryMap, "string", string.Empty); - data.AudioPath = GetString(entryMap, "audio", null); - if (data.AudioPath != null) { - data.Volume = GetFloat(entryMap, "volume", 1); - data.Balance = GetFloat(entryMap, "balance", 0); - } - skeletonData.events.Add(data); - } - } - - // Animations. - if (root.ContainsKey("animations")) { - foreach (KeyValuePair entry in (Dictionary)root["animations"]) { - try { - ReadAnimation((Dictionary)entry.Value, entry.Key, skeletonData); - } catch (Exception e) { - throw new Exception("Error reading animation: " + entry.Key + "\n" + e.Message, e); - } - } - } - - skeletonData.bones.TrimExcess(); - skeletonData.slots.TrimExcess(); - skeletonData.skins.TrimExcess(); - skeletonData.events.TrimExcess(); - skeletonData.animations.TrimExcess(); - skeletonData.ikConstraints.TrimExcess(); - return skeletonData; - } - - private Attachment ReadAttachment (Dictionary map, Skin skin, int slotIndex, string name, SkeletonData skeletonData) { - float scale = this.scale; - name = GetString(map, "name", name); - - var typeName = GetString(map, "type", "region"); - var type = (AttachmentType)Enum.Parse(typeof(AttachmentType), typeName, true); - - switch (type) { - case AttachmentType.Region: { - string path = GetString(map, "path", name); - object sequenceJson; - map.TryGetValue("sequence", out sequenceJson); - Sequence sequence = ReadSequence(sequenceJson); - RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path, sequence); - if (region == null) return null; - region.Path = path; - region.x = GetFloat(map, "x", 0) * scale; - region.y = GetFloat(map, "y", 0) * scale; - region.scaleX = GetFloat(map, "scaleX", 1); - region.scaleY = GetFloat(map, "scaleY", 1); - region.rotation = GetFloat(map, "rotation", 0); - region.width = GetFloat(map, "width", 32) * scale; - region.height = GetFloat(map, "height", 32) * scale; - region.sequence = sequence; - - if (map.ContainsKey("color")) { - var color = (string)map["color"]; - region.r = ToColor(color, 0); - region.g = ToColor(color, 1); - region.b = ToColor(color, 2); - region.a = ToColor(color, 3); - } - - if (region.Region != null) region.UpdateRegion(); - return region; - } - case AttachmentType.Boundingbox: - BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); - if (box == null) return null; - ReadVertices(map, box, GetInt(map, "vertexCount", 0) << 1); - return box; - case AttachmentType.Mesh: - case AttachmentType.Linkedmesh: { - string path = GetString(map, "path", name); - object sequenceJson; - map.TryGetValue("sequence", out sequenceJson); - Sequence sequence = ReadSequence(sequenceJson); - MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path, sequence); - if (mesh == null) return null; - mesh.Path = path; - - if (map.ContainsKey("color")) { - var color = (string)map["color"]; - mesh.r = ToColor(color, 0); - mesh.g = ToColor(color, 1); - mesh.b = ToColor(color, 2); - mesh.a = ToColor(color, 3); - } - - mesh.Width = GetFloat(map, "width", 0) * scale; - mesh.Height = GetFloat(map, "height", 0) * scale; - mesh.Sequence = sequence; - - string parent = GetString(map, "parent", null); - if (parent != null) { - linkedMeshes.Add(new LinkedMesh(mesh, GetString(map, "skin", null), slotIndex, parent, GetBoolean(map, "timelines", true))); - return mesh; - } - - float[] uvs = GetFloatArray(map, "uvs", 1); - ReadVertices(map, mesh, uvs.Length); - mesh.triangles = GetIntArray(map, "triangles"); - mesh.regionUVs = uvs; - if (mesh.Region != null) mesh.UpdateRegion(); - - if (map.ContainsKey("hull")) mesh.HullLength = GetInt(map, "hull", 0) << 1; - if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges"); - return mesh; - } - case AttachmentType.Path: { - PathAttachment pathAttachment = attachmentLoader.NewPathAttachment(skin, name); - if (pathAttachment == null) return null; - pathAttachment.closed = GetBoolean(map, "closed", false); - pathAttachment.constantSpeed = GetBoolean(map, "constantSpeed", true); - - int vertexCount = GetInt(map, "vertexCount", 0); - ReadVertices(map, pathAttachment, vertexCount << 1); - - // potential BOZO see Java impl - pathAttachment.lengths = GetFloatArray(map, "lengths", scale); - return pathAttachment; - } - case AttachmentType.Point: { - PointAttachment point = attachmentLoader.NewPointAttachment(skin, name); - if (point == null) return null; - point.x = GetFloat(map, "x", 0) * scale; - point.y = GetFloat(map, "y", 0) * scale; - point.rotation = GetFloat(map, "rotation", 0); - - //string color = GetString(map, "color", null); - //if (color != null) point.color = color; - return point; - } - case AttachmentType.Clipping: { - ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name); - if (clip == null) return null; - - string end = GetString(map, "end", null); - if (end != null) { - SlotData slot = skeletonData.FindSlot(end); - if (slot == null) throw new Exception("Clipping end slot not found: " + end); - clip.EndSlot = slot; - } - - ReadVertices(map, clip, GetInt(map, "vertexCount", 0) << 1); - - //string color = GetString(map, "color", null); - // if (color != null) clip.color = color; - return clip; - } - } - return null; - } - - public static Sequence ReadSequence (object sequenceJson) { - var map = sequenceJson as Dictionary; - if (map == null) return null; - Sequence sequence = new Sequence(GetInt(map, "count")); - sequence.start = GetInt(map, "start", 1); - sequence.digits = GetInt(map, "digits", 0); - sequence.setupIndex = GetInt(map, "setup", 0); - return sequence; - } - - private void ReadVertices (Dictionary map, VertexAttachment attachment, int verticesLength) { - attachment.WorldVerticesLength = verticesLength; - float[] vertices = GetFloatArray(map, "vertices", 1); - float scale = Scale; - if (verticesLength == vertices.Length) { - if (scale != 1) { - for (int i = 0; i < vertices.Length; i++) { - vertices[i] *= scale; - } - } - attachment.vertices = vertices; - return; - } - ExposedList weights = new ExposedList(verticesLength * 3 * 3); - ExposedList bones = new ExposedList(verticesLength * 3); - for (int i = 0, n = vertices.Length; i < n;) { - int boneCount = (int)vertices[i++]; - bones.Add(boneCount); - for (int nn = i + (boneCount << 2); i < nn; i += 4) { - bones.Add((int)vertices[i]); - weights.Add(vertices[i + 1] * this.Scale); - weights.Add(vertices[i + 2] * this.Scale); - weights.Add(vertices[i + 3]); - } - } - attachment.bones = bones.ToArray(); - attachment.vertices = weights.ToArray(); - } - - private int FindSlotIndex (SkeletonData skeletonData, string slotName) { - SlotData[] slots = skeletonData.slots.Items; - for (int i = 0, n = skeletonData.slots.Count; i < n; i++) - if (slots[i].name == slotName) return i; - throw new Exception("Slot not found: " + slotName); - } - - private void ReadAnimation (Dictionary map, string name, SkeletonData skeletonData) { - var scale = this.scale; - var timelines = new ExposedList(); - - // Slot timelines. - if (map.ContainsKey("slots")) { - foreach (KeyValuePair entry in (Dictionary)map["slots"]) { - string slotName = entry.Key; - int slotIndex = FindSlotIndex(skeletonData, slotName); - var timelineMap = (Dictionary)entry.Value; - foreach (KeyValuePair timelineEntry in timelineMap) { - var values = (List)timelineEntry.Value; - int frames = values.Count; - if (frames == 0) continue; - var timelineName = (string)timelineEntry.Key; - if (timelineName == "attachment") { - var timeline = new AttachmentTimeline(frames, slotIndex); - int frame = 0; - foreach (Dictionary keyMap in values) { - timeline.SetFrame(frame++, GetFloat(keyMap, "time", 0), GetString(keyMap, "name", null)); - } - timelines.Add(timeline); - - } else if (timelineName == "rgba") { - var timeline = new RGBATimeline(frames, frames << 2, slotIndex); - - var keyMapEnumerator = values.GetEnumerator(); - keyMapEnumerator.MoveNext(); - var keyMap = (Dictionary)keyMapEnumerator.Current; - float time = GetFloat(keyMap, "time", 0); - string color = (string)keyMap["color"]; - float r = ToColor(color, 0); - float g = ToColor(color, 1); - float b = ToColor(color, 2); - float a = ToColor(color, 3); - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, r, g, b, a); - if (!keyMapEnumerator.MoveNext()) { - timeline.Shrink(bezier); - break; - } - var nextMap = (Dictionary)keyMapEnumerator.Current; - - float time2 = GetFloat(nextMap, "time", 0); - color = (string)nextMap["color"]; - float nr = ToColor(color, 0); - float ng = ToColor(color, 1); - float nb = ToColor(color, 2); - float na = ToColor(color, 3); - - if (keyMap.ContainsKey("curve")) { - object curve = keyMap["curve"]; - bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1); - bezier = ReadCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1); - bezier = ReadCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1); - bezier = ReadCurve(curve, timeline, bezier, frame, 3, time, time2, a, na, 1); - } - time = time2; - r = nr; - g = ng; - b = nb; - a = na; - keyMap = nextMap; - } - timelines.Add(timeline); - - } else if (timelineName == "rgb") { - var timeline = new RGBTimeline(frames, frames * 3, slotIndex); - - var keyMapEnumerator = values.GetEnumerator(); - keyMapEnumerator.MoveNext(); - var keyMap = (Dictionary)keyMapEnumerator.Current; - float time = GetFloat(keyMap, "time", 0); - string color = (string)keyMap["color"]; - float r = ToColor(color, 0, 6); - float g = ToColor(color, 1, 6); - float b = ToColor(color, 2, 6); - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, r, g, b); - if (!keyMapEnumerator.MoveNext()) { - timeline.Shrink(bezier); - break; - } - var nextMap = (Dictionary)keyMapEnumerator.Current; - - float time2 = GetFloat(nextMap, "time", 0); - color = (string)nextMap["color"]; - float nr = ToColor(color, 0, 6); - float ng = ToColor(color, 1, 6); - float nb = ToColor(color, 2, 6); - - if (keyMap.ContainsKey("curve")) { - object curve = keyMap["curve"]; - bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1); - bezier = ReadCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1); - bezier = ReadCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1); - } - time = time2; - r = nr; - g = ng; - b = nb; - keyMap = nextMap; - } - timelines.Add(timeline); - - } else if (timelineName == "alpha") { - var keyMapEnumerator = values.GetEnumerator(); - keyMapEnumerator.MoveNext(); - timelines.Add(ReadTimeline(ref keyMapEnumerator, new AlphaTimeline(frames, frames, slotIndex), 0, 1)); - - } else if (timelineName == "rgba2") { - var timeline = new RGBA2Timeline(frames, frames * 7, slotIndex); - - var keyMapEnumerator = values.GetEnumerator(); - keyMapEnumerator.MoveNext(); - var keyMap = (Dictionary)keyMapEnumerator.Current; - float time = GetFloat(keyMap, "time", 0); - string color = (string)keyMap["light"]; - float r = ToColor(color, 0); - float g = ToColor(color, 1); - float b = ToColor(color, 2); - float a = ToColor(color, 3); - color = (string)keyMap["dark"]; - float r2 = ToColor(color, 0, 6); - float g2 = ToColor(color, 1, 6); - float b2 = ToColor(color, 2, 6); - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, r, g, b, a, r2, g2, b2); - if (!keyMapEnumerator.MoveNext()) { - timeline.Shrink(bezier); - break; - } - var nextMap = (Dictionary)keyMapEnumerator.Current; - - float time2 = GetFloat(nextMap, "time", 0); - color = (string)nextMap["light"]; - float nr = ToColor(color, 0); - float ng = ToColor(color, 1); - float nb = ToColor(color, 2); - float na = ToColor(color, 3); - color = (string)nextMap["dark"]; - float nr2 = ToColor(color, 0, 6); - float ng2 = ToColor(color, 1, 6); - float nb2 = ToColor(color, 2, 6); - - if (keyMap.ContainsKey("curve")) { - object curve = keyMap["curve"]; - bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1); - bezier = ReadCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1); - bezier = ReadCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1); - bezier = ReadCurve(curve, timeline, bezier, frame, 3, time, time2, a, na, 1); - bezier = ReadCurve(curve, timeline, bezier, frame, 4, time, time2, r2, nr2, 1); - bezier = ReadCurve(curve, timeline, bezier, frame, 5, time, time2, g2, ng2, 1); - bezier = ReadCurve(curve, 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; - keyMap = nextMap; - } - timelines.Add(timeline); - - } else if (timelineName == "rgb2") { - var timeline = new RGB2Timeline(frames, frames * 6, slotIndex); - - var keyMapEnumerator = values.GetEnumerator(); - keyMapEnumerator.MoveNext(); - var keyMap = (Dictionary)keyMapEnumerator.Current; - float time = GetFloat(keyMap, "time", 0); - string color = (string)keyMap["light"]; - float r = ToColor(color, 0, 6); - float g = ToColor(color, 1, 6); - float b = ToColor(color, 2, 6); - color = (string)keyMap["dark"]; - float r2 = ToColor(color, 0, 6); - float g2 = ToColor(color, 1, 6); - float b2 = ToColor(color, 2, 6); - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, r, g, b, r2, g2, b2); - if (!keyMapEnumerator.MoveNext()) { - timeline.Shrink(bezier); - break; - } - var nextMap = (Dictionary)keyMapEnumerator.Current; - - float time2 = GetFloat(nextMap, "time", 0); - color = (string)nextMap["light"]; - float nr = ToColor(color, 0, 6); - float ng = ToColor(color, 1, 6); - float nb = ToColor(color, 2, 6); - color = (string)nextMap["dark"]; - float nr2 = ToColor(color, 0, 6); - float ng2 = ToColor(color, 1, 6); - float nb2 = ToColor(color, 2, 6); - - if (keyMap.ContainsKey("curve")) { - object curve = keyMap["curve"]; - bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1); - bezier = ReadCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1); - bezier = ReadCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1); - bezier = ReadCurve(curve, timeline, bezier, frame, 3, time, time2, r2, nr2, 1); - bezier = ReadCurve(curve, timeline, bezier, frame, 4, time, time2, g2, ng2, 1); - bezier = ReadCurve(curve, timeline, bezier, frame, 5, time, time2, b2, nb2, 1); - } - time = time2; - r = nr; - g = ng; - b = nb; - r2 = nr2; - g2 = ng2; - b2 = nb2; - keyMap = nextMap; - } - timelines.Add(timeline); - - } else - throw new Exception("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")"); - } - } - } - - // Bone timelines. - if (map.ContainsKey("bones")) { - foreach (KeyValuePair entry in (Dictionary)map["bones"]) { - string boneName = entry.Key; - int boneIndex = -1; - var bones = skeletonData.bones.Items; - for (int i = 0, n = skeletonData.bones.Count; i < n; i++) { - if (bones[i].name == boneName) { - boneIndex = i; - break; - } - } - if (boneIndex == -1) throw new Exception("Bone not found: " + boneName); - var timelineMap = (Dictionary)entry.Value; - foreach (KeyValuePair timelineEntry in timelineMap) { - var values = (List)timelineEntry.Value; - var keyMapEnumerator = values.GetEnumerator(); - if (!keyMapEnumerator.MoveNext()) continue; - int frames = values.Count; - var timelineName = (string)timelineEntry.Key; - if (timelineName == "rotate") - timelines.Add(ReadTimeline(ref keyMapEnumerator, new RotateTimeline(frames, frames, boneIndex), 0, 1)); - else if (timelineName == "translate") { - TranslateTimeline timeline = new TranslateTimeline(frames, frames << 1, boneIndex); - timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, "x", "y", 0, scale)); - } else if (timelineName == "translatex") { - timelines - .Add(ReadTimeline(ref keyMapEnumerator, new TranslateXTimeline(frames, frames, boneIndex), 0, scale)); - } else if (timelineName == "translatey") { - timelines - .Add(ReadTimeline(ref keyMapEnumerator, new TranslateYTimeline(frames, frames, boneIndex), 0, scale)); - } else if (timelineName == "scale") { - ScaleTimeline timeline = new ScaleTimeline(frames, frames << 1, boneIndex); - timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, "x", "y", 1, 1)); - } else if (timelineName == "scalex") - timelines.Add(ReadTimeline(ref keyMapEnumerator, new ScaleXTimeline(frames, frames, boneIndex), 1, 1)); - else if (timelineName == "scaley") - timelines.Add(ReadTimeline(ref keyMapEnumerator, new ScaleYTimeline(frames, frames, boneIndex), 1, 1)); - else if (timelineName == "shear") { - ShearTimeline timeline = new ShearTimeline(frames, frames << 1, boneIndex); - timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, "x", "y", 0, 1)); - } else if (timelineName == "shearx") - timelines.Add(ReadTimeline(ref keyMapEnumerator, new ShearXTimeline(frames, frames, boneIndex), 0, 1)); - else if (timelineName == "sheary") - timelines.Add(ReadTimeline(ref keyMapEnumerator, new ShearYTimeline(frames, frames, boneIndex), 0, 1)); - else - throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")"); - } - } - } - - // IK constraint timelines. - if (map.ContainsKey("ik")) { - foreach (KeyValuePair timelineMap in (Dictionary)map["ik"]) { - var values = (List)timelineMap.Value; - var keyMapEnumerator = values.GetEnumerator(); - if (!keyMapEnumerator.MoveNext()) continue; - var keyMap = (Dictionary)keyMapEnumerator.Current; - IkConstraintData constraint = skeletonData.FindIkConstraint(timelineMap.Key); - IkConstraintTimeline timeline = new IkConstraintTimeline(values.Count, values.Count << 1, - skeletonData.IkConstraints.IndexOf(constraint)); - float time = GetFloat(keyMap, "time", 0); - float mix = GetFloat(keyMap, "mix", 1), softness = GetFloat(keyMap, "softness", 0) * scale; - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, mix, softness, GetBoolean(keyMap, "bendPositive", true) ? 1 : -1, - GetBoolean(keyMap, "compress", false), GetBoolean(keyMap, "stretch", false)); - if (!keyMapEnumerator.MoveNext()) { - timeline.Shrink(bezier); - break; - } - var nextMap = (Dictionary)keyMapEnumerator.Current; - float time2 = GetFloat(nextMap, "time", 0); - float mix2 = GetFloat(nextMap, "mix", 1), softness2 = GetFloat(nextMap, "softness", 0) * scale; - if (keyMap.ContainsKey("curve")) { - object curve = keyMap["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. - if (map.ContainsKey("transform")) { - foreach (KeyValuePair timelineMap in (Dictionary)map["transform"]) { - var values = (List)timelineMap.Value; - var keyMapEnumerator = values.GetEnumerator(); - if (!keyMapEnumerator.MoveNext()) continue; - var keyMap = (Dictionary)keyMapEnumerator.Current; - TransformConstraintData constraint = skeletonData.FindTransformConstraint(timelineMap.Key); - TransformConstraintTimeline timeline = new TransformConstraintTimeline(values.Count, values.Count * 6, - skeletonData.TransformConstraints.IndexOf(constraint)); - float time = GetFloat(keyMap, "time", 0); - float mixRotate = GetFloat(keyMap, "mixRotate", 1), mixShearY = GetFloat(keyMap, "mixShearY", 1); - float mixX = GetFloat(keyMap, "mixX", 1), mixY = GetFloat(keyMap, "mixY", mixX); - float mixScaleX = GetFloat(keyMap, "mixScaleX", 1), mixScaleY = GetFloat(keyMap, "mixScaleY", mixScaleX); - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY); - if (!keyMapEnumerator.MoveNext()) { - timeline.Shrink(bezier); - break; - } - var nextMap = (Dictionary)keyMapEnumerator.Current; - float time2 = GetFloat(nextMap, "time", 0); - float mixRotate2 = GetFloat(nextMap, "mixRotate", 1), mixShearY2 = GetFloat(nextMap, "mixShearY", 1); - float mixX2 = GetFloat(nextMap, "mixX", 1), mixY2 = GetFloat(nextMap, "mixY", mixX2); - float mixScaleX2 = GetFloat(nextMap, "mixScaleX", 1), mixScaleY2 = GetFloat(nextMap, "mixScaleY", mixScaleX2); - if (keyMap.ContainsKey("curve")) { - object curve = keyMap["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; - mixScaleX = mixScaleX2; - keyMap = nextMap; - } - timelines.Add(timeline); - } - } - - // Path constraint timelines. - if (map.ContainsKey("path")) { - foreach (KeyValuePair constraintMap in (Dictionary)map["path"]) { - PathConstraintData constraint = skeletonData.FindPathConstraint(constraintMap.Key); - if (constraint == null) throw new Exception("Path constraint not found: " + constraintMap.Key); - int constraintIndex = skeletonData.pathConstraints.IndexOf(constraint); - var timelineMap = (Dictionary)constraintMap.Value; - foreach (KeyValuePair timelineEntry in timelineMap) { - var values = (List)timelineEntry.Value; - var keyMapEnumerator = values.GetEnumerator(); - if (!keyMapEnumerator.MoveNext()) continue; - - int frames = values.Count; - var timelineName = (string)timelineEntry.Key; - if (timelineName == "position") { - CurveTimeline1 timeline = new PathConstraintPositionTimeline(frames, frames, constraintIndex); - timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, 0, constraint.positionMode == PositionMode.Fixed ? scale : 1)); - } else if (timelineName == "spacing") { - CurveTimeline1 timeline = new PathConstraintSpacingTimeline(frames, frames, constraintIndex); - timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, 0, - constraint.spacingMode == SpacingMode.Length || constraint.spacingMode == SpacingMode.Fixed ? scale : 1)); - } else if (timelineName == "mix") { - PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(frames, frames * 3, constraintIndex); - var keyMap = (Dictionary)keyMapEnumerator.Current; - float time = GetFloat(keyMap, "time", 0); - float mixRotate = GetFloat(keyMap, "mixRotate", 1); - float mixX = GetFloat(keyMap, "mixX", 1), mixY = GetFloat(keyMap, "mixY", mixX); - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, mixRotate, mixX, mixY); - if (!keyMapEnumerator.MoveNext()) { - timeline.Shrink(bezier); - break; - } - var nextMap = (Dictionary)keyMapEnumerator.Current; - float time2 = GetFloat(nextMap, "time", 0); - float mixRotate2 = GetFloat(nextMap, "mixRotate", 1); - float mixX2 = GetFloat(nextMap, "mixX", 1), mixY2 = GetFloat(nextMap, "mixY", mixX2); - if (keyMap.ContainsKey("curve")) { - object curve = keyMap["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); - } - time = time2; - mixRotate = mixRotate2; - mixX = mixX2; - mixY = mixY2; - keyMap = nextMap; - } - timelines.Add(timeline); - } - } - } - } - - // Attachment timelines. - if (map.ContainsKey("attachments")) { - foreach (KeyValuePair attachmentsMap in (Dictionary)map["attachments"]) { - Skin skin = skeletonData.FindSkin(attachmentsMap.Key); - foreach (KeyValuePair slotMap in (Dictionary)attachmentsMap.Value) { - SlotData slot = skeletonData.FindSlot(slotMap.Key); - if (slot == null) throw new Exception("Slot not found: " + slotMap.Key); - foreach (KeyValuePair attachmentMap in (Dictionary)slotMap.Value) { - Attachment attachment = skin.GetAttachment(slot.index, attachmentMap.Key); - if (attachment == null) throw new Exception("Timeline attachment not found: " + attachmentMap.Key); - foreach (KeyValuePair timelineMap in (Dictionary)attachmentMap.Value) { - var values = (List)timelineMap.Value; - var keyMapEnumerator = values.GetEnumerator(); - if (!keyMapEnumerator.MoveNext()) continue; - var keyMap = (Dictionary)keyMapEnumerator.Current; - int frames = values.Count; - string timelineName = timelineMap.Key; - if (timelineName == "deform") { - VertexAttachment vertexAttachment = (VertexAttachment)attachment; - bool weighted = vertexAttachment.bones != null; - float[] vertices = vertexAttachment.vertices; - int deformLength = weighted ? (vertices.Length / 3) << 1 : vertices.Length; - - DeformTimeline timeline = new DeformTimeline(frames, frames, slot.Index, vertexAttachment); - float time = GetFloat(keyMap, "time", 0); - for (int frame = 0, bezier = 0; ; frame++) { - float[] deform; - if (!keyMap.ContainsKey("vertices")) { - deform = weighted ? new float[deformLength] : vertices; - } else { - deform = new float[deformLength]; - int start = GetInt(keyMap, "offset", 0); - float[] verticesValue = GetFloatArray(keyMap, "vertices", 1); - Array.Copy(verticesValue, 0, deform, start, verticesValue.Length); - if (scale != 1) { - for (int i = start, n = i + verticesValue.Length; i < n; i++) - deform[i] *= scale; - } - - if (!weighted) { - for (int i = 0; i < deformLength; i++) - deform[i] += vertices[i]; - } - } - - timeline.SetFrame(frame, time, deform); - if (!keyMapEnumerator.MoveNext()) { - timeline.Shrink(bezier); - break; - } - var nextMap = (Dictionary)keyMapEnumerator.Current; - float time2 = GetFloat(nextMap, "time", 0); - if (keyMap.ContainsKey("curve")) { - object curve = keyMap["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, slot.index, attachment); - float lastDelay = 0; - for (int frame = 0; keyMap != null; - keyMapEnumerator.MoveNext(), keyMap = (Dictionary)keyMapEnumerator.Current, frame++) { - - float delay = GetFloat(keyMap, "delay", lastDelay); - SequenceMode sequenceMode = (SequenceMode)Enum.Parse(typeof(SequenceMode), - GetString(keyMap, "mode", "hold"), true); - timeline.SetFrame(frame, GetFloat(keyMap, "time", 0), - sequenceMode, GetInt(keyMap, "index", 0), delay); - lastDelay = delay; - } - timelines.Add(timeline); - } - } - } - } - } - } - - // Draw order timeline. - if (map.ContainsKey("drawOrder")) { - var values = (List)map["drawOrder"]; - var timeline = new DrawOrderTimeline(values.Count); - int slotCount = skeletonData.slots.Count; - int frame = 0; - foreach (Dictionary drawOrderMap in values) { - int[] drawOrder = null; - if (drawOrderMap.ContainsKey("offsets")) { - drawOrder = new int[slotCount]; - for (int i = slotCount - 1; i >= 0; i--) - drawOrder[i] = -1; - var offsets = (List)drawOrderMap["offsets"]; - int[] unchanged = new int[slotCount - offsets.Count]; - int originalIndex = 0, unchangedIndex = 0; - foreach (Dictionary offsetMap in offsets) { - int slotIndex = FindSlotIndex(skeletonData, (string)offsetMap["slot"]); - // Collect unchanged items. - while (originalIndex != slotIndex) - unchanged[unchangedIndex++] = originalIndex++; - // Set changed items. - int index = originalIndex + (int)(float)offsetMap["offset"]; - drawOrder[index] = originalIndex++; - } - // Collect remaining unchanged items. - while (originalIndex < slotCount) - unchanged[unchangedIndex++] = originalIndex++; - // Fill in unchanged items. - for (int i = slotCount - 1; i >= 0; i--) - if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex]; - } - timeline.SetFrame(frame, GetFloat(drawOrderMap, "time", 0), drawOrder); - ++frame; - } - timelines.Add(timeline); - } - - // Event timeline. - if (map.ContainsKey("events")) { - var eventsMap = (List)map["events"]; - var timeline = new EventTimeline(eventsMap.Count); - int frame = 0; - foreach (Dictionary eventMap in eventsMap) { - EventData eventData = skeletonData.FindEvent((string)eventMap["name"]); - if (eventData == null) throw new Exception("Event not found: " + eventMap["name"]); - var e = new Event(GetFloat(eventMap, "time", 0), eventData) { - intValue = GetInt(eventMap, "int", eventData.Int), - floatValue = GetFloat(eventMap, "float", eventData.Float), - stringValue = GetString(eventMap, "string", eventData.String) - }; - if (e.data.AudioPath != null) { - e.volume = GetFloat(eventMap, "volume", eventData.Volume); - e.balance = GetFloat(eventMap, "balance", eventData.Balance); - } - timeline.SetFrame(frame, e); - ++frame; - } - timelines.Add(timeline); - } - timelines.TrimExcess(); - float duration = 0; - var items = timelines.Items; - for (int i = 0, n = timelines.Count; i < n; i++) - duration = Math.Max(duration, items[i].Duration); - skeletonData.animations.Add(new Animation(name, timelines, duration)); - } - - static Timeline ReadTimeline (ref List.Enumerator keyMapEnumerator, CurveTimeline1 timeline, float defaultValue, float scale) { - var keyMap = (Dictionary)keyMapEnumerator.Current; - float time = GetFloat(keyMap, "time", 0); - float value = GetFloat(keyMap, "value", defaultValue) * scale; - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, value); - if (!keyMapEnumerator.MoveNext()) { - timeline.Shrink(bezier); - return timeline; - } - var nextMap = (Dictionary)keyMapEnumerator.Current; - float time2 = GetFloat(nextMap, "time", 0); - float value2 = GetFloat(nextMap, "value", defaultValue) * scale; - if (keyMap.ContainsKey("curve")) { - object curve = keyMap["curve"]; - bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, value, value2, scale); - } - time = time2; - value = value2; - keyMap = nextMap; - } - } - - static Timeline ReadTimeline (ref List.Enumerator keyMapEnumerator, CurveTimeline2 timeline, String name1, String name2, float defaultValue, - float scale) { - - var keyMap = (Dictionary)keyMapEnumerator.Current; - float time = GetFloat(keyMap, "time", 0); - float value1 = GetFloat(keyMap, name1, defaultValue) * scale, value2 = GetFloat(keyMap, name2, defaultValue) * scale; - for (int frame = 0, bezier = 0; ; frame++) { - timeline.SetFrame(frame, time, value1, value2); - if (!keyMapEnumerator.MoveNext()) { - timeline.Shrink(bezier); - return timeline; - } - var nextMap = (Dictionary)keyMapEnumerator.Current; - float time2 = GetFloat(nextMap, "time", 0); - float nvalue1 = GetFloat(nextMap, name1, defaultValue) * scale, nvalue2 = GetFloat(nextMap, name2, defaultValue) * scale; - if (keyMap.ContainsKey("curve")) { - object curve = keyMap["curve"]; - 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; - } - } - - static int ReadCurve (object curve, CurveTimeline timeline, int bezier, int frame, int value, float time1, float time2, - float value1, float value2, float scale) { - - string curveString = curve as string; - if (curveString != null) { - if (curveString == "stepped") timeline.SetStepped(frame); - return bezier; - } - var curveValues = (List)curve; - int i = value << 2; - float cx1 = (float)curveValues[i]; - float cy1 = (float)curveValues[i + 1] * scale; - float cx2 = (float)curveValues[i + 2]; - float cy2 = (float)curveValues[i + 3] * scale; - SetBezier(timeline, frame, value, bezier, time1, value1, cx1, cy1, cx2, cy2, time2, value2); - return bezier + 1; - } - - 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) { - timeline.SetBezier(bezier, frame, value, time1, value1, cx1, cy1, cx2, cy2, time2, value2); - } - - static float[] GetFloatArray (Dictionary map, string name, float scale) { - var list = (List)map[name]; - var values = new float[list.Count]; - if (scale == 1) { - for (int i = 0, n = list.Count; i < n; i++) - values[i] = (float)list[i]; - } else { - for (int i = 0, n = list.Count; i < n; i++) - values[i] = (float)list[i] * scale; - } - return values; - } - - static int[] GetIntArray (Dictionary map, string name) { - var list = (List)map[name]; - var values = new int[list.Count]; - for (int i = 0, n = list.Count; i < n; i++) - values[i] = (int)(float)list[i]; - return values; - } - - static float GetFloat (Dictionary map, string name, float defaultValue) { - if (!map.ContainsKey(name)) return defaultValue; - return (float)map[name]; - } - - static int GetInt (Dictionary map, string name, int defaultValue) { - if (!map.ContainsKey(name)) return defaultValue; - return (int)(float)map[name]; - } - - static int GetInt (Dictionary map, string name) { - if (!map.ContainsKey(name)) throw new ArgumentException("Named value not found: " + name); - return (int)(float)map[name]; - } - - static bool GetBoolean (Dictionary map, string name, bool defaultValue) { - if (!map.ContainsKey(name)) return defaultValue; - return (bool)map[name]; - } - - static string GetString (Dictionary map, string name, string defaultValue) { - if (!map.ContainsKey(name)) return defaultValue; - return (string)map[name]; - } - - static float ToColor (string hexString, int colorIndex, int expectedLength = 8) { - if (hexString.Length != expectedLength) - throw new ArgumentException("Color hexidecimal length must be " + expectedLength + ", recieved: " + hexString, "hexString"); - return Convert.ToInt32(hexString.Substring(colorIndex * 2, 2), 16) / (float)255; - } - } + public SkeletonData ReadSkeletonData(TextReader reader) + { + if (reader == null) throw new ArgumentNullException("reader", "reader cannot be null."); + + float scale = this.scale; + var skeletonData = new SkeletonData(); + + var root = Json.Deserialize(reader) as Dictionary; + if (root == null) throw new Exception("Invalid JSON."); + + // Skeleton. + if (root.ContainsKey("skeleton")) + { + var skeletonMap = (Dictionary)root["skeleton"]; + skeletonData.hash = (string)skeletonMap["hash"]; + skeletonData.version = (string)skeletonMap["spine"]; + skeletonData.x = GetFloat(skeletonMap, "x", 0); + skeletonData.y = GetFloat(skeletonMap, "y", 0); + skeletonData.width = GetFloat(skeletonMap, "width", 0); + skeletonData.height = GetFloat(skeletonMap, "height", 0); + skeletonData.fps = GetFloat(skeletonMap, "fps", 30); + skeletonData.imagesPath = GetString(skeletonMap, "images", null); + skeletonData.audioPath = GetString(skeletonMap, "audio", null); + } + + // Bones. + if (root.ContainsKey("bones")) + { + foreach (Dictionary boneMap in (List)root["bones"]) + { + BoneData parent = null; + if (boneMap.ContainsKey("parent")) + { + parent = skeletonData.FindBone((string)boneMap["parent"]); + if (parent == null) + throw new Exception("Parent bone not found: " + boneMap["parent"]); + } + var data = new BoneData(skeletonData.Bones.Count, (string)boneMap["name"], parent); + data.length = GetFloat(boneMap, "length", 0) * scale; + data.x = GetFloat(boneMap, "x", 0) * scale; + data.y = GetFloat(boneMap, "y", 0) * scale; + data.rotation = GetFloat(boneMap, "rotation", 0); + data.scaleX = GetFloat(boneMap, "scaleX", 1); + data.scaleY = GetFloat(boneMap, "scaleY", 1); + data.shearX = GetFloat(boneMap, "shearX", 0); + data.shearY = GetFloat(boneMap, "shearY", 0); + + string tm = GetString(boneMap, "transform", TransformMode.Normal.ToString()); + data.transformMode = (TransformMode)Enum.Parse(typeof(TransformMode), tm, true); + data.skinRequired = GetBoolean(boneMap, "skin", false); + + skeletonData.bones.Add(data); + } + } + + // Slots. + if (root.ContainsKey("slots")) + { + foreach (Dictionary slotMap in (List)root["slots"]) + { + var slotName = (string)slotMap["name"]; + var boneName = (string)slotMap["bone"]; + BoneData boneData = skeletonData.FindBone(boneName); + if (boneData == null) throw new Exception("Slot bone not found: " + boneName); + var data = new SlotData(skeletonData.Slots.Count, slotName, boneData); + + if (slotMap.ContainsKey("color")) + { + string color = (string)slotMap["color"]; + data.r = ToColor(color, 0); + data.g = ToColor(color, 1); + data.b = ToColor(color, 2); + data.a = ToColor(color, 3); + } + + if (slotMap.ContainsKey("dark")) + { + var color2 = (string)slotMap["dark"]; + data.r2 = ToColor(color2, 0, 6); // expectedLength = 6. ie. "RRGGBB" + data.g2 = ToColor(color2, 1, 6); + data.b2 = ToColor(color2, 2, 6); + data.hasSecondColor = true; + } + + data.attachmentName = GetString(slotMap, "attachment", null); + if (slotMap.ContainsKey("blend")) + data.blendMode = (BlendMode)Enum.Parse(typeof(BlendMode), (string)slotMap["blend"], true); + else + data.blendMode = BlendMode.Normal; + skeletonData.slots.Add(data); + } + } + + // IK constraints. + if (root.ContainsKey("ik")) + { + foreach (Dictionary constraintMap in (List)root["ik"]) + { + IkConstraintData data = new IkConstraintData((string)constraintMap["name"]); + data.order = GetInt(constraintMap, "order", 0); + data.skinRequired = GetBoolean(constraintMap, "skin", false); + + if (constraintMap.ContainsKey("bones")) + { + foreach (string boneName in (List)constraintMap["bones"]) + { + BoneData bone = skeletonData.FindBone(boneName); + if (bone == null) throw new Exception("IK bone not found: " + boneName); + data.bones.Add(bone); + } + } + + string targetName = (string)constraintMap["target"]; + data.target = skeletonData.FindBone(targetName); + if (data.target == null) throw new Exception("IK target bone not found: " + targetName); + data.mix = GetFloat(constraintMap, "mix", 1); + data.softness = GetFloat(constraintMap, "softness", 0) * scale; + data.bendDirection = GetBoolean(constraintMap, "bendPositive", true) ? 1 : -1; + data.compress = GetBoolean(constraintMap, "compress", false); + data.stretch = GetBoolean(constraintMap, "stretch", false); + data.uniform = GetBoolean(constraintMap, "uniform", false); + + skeletonData.ikConstraints.Add(data); + } + } + + // Transform constraints. + if (root.ContainsKey("transform")) + { + foreach (Dictionary constraintMap in (List)root["transform"]) + { + TransformConstraintData data = new TransformConstraintData((string)constraintMap["name"]); + data.order = GetInt(constraintMap, "order", 0); + data.skinRequired = GetBoolean(constraintMap, "skin", false); + + if (constraintMap.ContainsKey("bones")) + { + foreach (string boneName in (List)constraintMap["bones"]) + { + BoneData bone = skeletonData.FindBone(boneName); + if (bone == null) throw new Exception("Transform constraint bone not found: " + boneName); + data.bones.Add(bone); + } + } + + string targetName = (string)constraintMap["target"]; + data.target = skeletonData.FindBone(targetName); + if (data.target == null) throw new Exception("Transform constraint target bone not found: " + targetName); + + data.local = GetBoolean(constraintMap, "local", false); + data.relative = GetBoolean(constraintMap, "relative", false); + + data.offsetRotation = GetFloat(constraintMap, "rotation", 0); + data.offsetX = GetFloat(constraintMap, "x", 0) * scale; + data.offsetY = GetFloat(constraintMap, "y", 0) * scale; + data.offsetScaleX = GetFloat(constraintMap, "scaleX", 0); + data.offsetScaleY = GetFloat(constraintMap, "scaleY", 0); + data.offsetShearY = GetFloat(constraintMap, "shearY", 0); + + data.mixRotate = GetFloat(constraintMap, "mixRotate", 1); + data.mixX = GetFloat(constraintMap, "mixX", 1); + data.mixY = GetFloat(constraintMap, "mixY", data.mixX); + data.mixScaleX = GetFloat(constraintMap, "mixScaleX", 1); + data.mixScaleY = GetFloat(constraintMap, "mixScaleY", data.mixScaleX); + data.mixShearY = GetFloat(constraintMap, "mixShearY", 1); + + skeletonData.transformConstraints.Add(data); + } + } + + // Path constraints. + if (root.ContainsKey("path")) + { + foreach (Dictionary constraintMap in (List)root["path"]) + { + PathConstraintData data = new PathConstraintData((string)constraintMap["name"]); + data.order = GetInt(constraintMap, "order", 0); + data.skinRequired = GetBoolean(constraintMap, "skin", false); + + if (constraintMap.ContainsKey("bones")) + { + foreach (string boneName in (List)constraintMap["bones"]) + { + BoneData bone = skeletonData.FindBone(boneName); + if (bone == null) throw new Exception("Path bone not found: " + boneName); + data.bones.Add(bone); + } + } + + string targetName = (string)constraintMap["target"]; + data.target = skeletonData.FindSlot(targetName); + if (data.target == null) throw new Exception("Path target slot not found: " + targetName); + + data.positionMode = (PositionMode)Enum.Parse(typeof(PositionMode), GetString(constraintMap, "positionMode", "percent"), true); + data.spacingMode = (SpacingMode)Enum.Parse(typeof(SpacingMode), GetString(constraintMap, "spacingMode", "length"), true); + data.rotateMode = (RotateMode)Enum.Parse(typeof(RotateMode), GetString(constraintMap, "rotateMode", "tangent"), true); + data.offsetRotation = GetFloat(constraintMap, "rotation", 0); + data.position = GetFloat(constraintMap, "position", 0); + if (data.positionMode == PositionMode.Fixed) data.position *= scale; + data.spacing = GetFloat(constraintMap, "spacing", 0); + if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale; + data.mixRotate = GetFloat(constraintMap, "mixRotate", 1); + data.mixX = GetFloat(constraintMap, "mixX", 1); + data.mixY = GetFloat(constraintMap, "mixY", data.mixX); + + skeletonData.pathConstraints.Add(data); + } + } + + // Skins. + if (root.ContainsKey("skins")) + { + foreach (Dictionary skinMap in (List)root["skins"]) + { + Skin skin = new Skin((string)skinMap["name"]); + if (skinMap.ContainsKey("bones")) + { + foreach (string entryName in (List)skinMap["bones"]) + { + BoneData bone = skeletonData.FindBone(entryName); + if (bone == null) throw new Exception("Skin bone not found: " + entryName); + skin.bones.Add(bone); + } + } + skin.bones.TrimExcess(); + if (skinMap.ContainsKey("ik")) + { + foreach (string entryName in (List)skinMap["ik"]) + { + IkConstraintData constraint = skeletonData.FindIkConstraint(entryName); + if (constraint == null) throw new Exception("Skin IK constraint not found: " + entryName); + skin.constraints.Add(constraint); + } + } + if (skinMap.ContainsKey("transform")) + { + foreach (string entryName in (List)skinMap["transform"]) + { + TransformConstraintData constraint = skeletonData.FindTransformConstraint(entryName); + if (constraint == null) throw new Exception("Skin transform constraint not found: " + entryName); + skin.constraints.Add(constraint); + } + } + if (skinMap.ContainsKey("path")) + { + foreach (string entryName in (List)skinMap["path"]) + { + PathConstraintData constraint = skeletonData.FindPathConstraint(entryName); + if (constraint == null) throw new Exception("Skin path constraint not found: " + entryName); + skin.constraints.Add(constraint); + } + } + skin.constraints.TrimExcess(); + if (skinMap.ContainsKey("attachments")) + { + foreach (KeyValuePair slotEntry in (Dictionary)skinMap["attachments"]) + { + int slotIndex = FindSlotIndex(skeletonData, slotEntry.Key); + foreach (KeyValuePair entry in ((Dictionary)slotEntry.Value)) + { + try + { + Attachment attachment = ReadAttachment((Dictionary)entry.Value, skin, slotIndex, entry.Key, skeletonData); + if (attachment != null) skin.SetAttachment(slotIndex, entry.Key, attachment); + } + catch (Exception e) + { + throw new Exception("Error reading attachment: " + entry.Key + ", skin: " + skin, e); + } + } + } + } + skeletonData.skins.Add(skin); + if (skin.name == "default") skeletonData.defaultSkin = skin; + } + } + + // Linked meshes. + for (int i = 0, n = linkedMeshes.Count; i < n; i++) + { + LinkedMesh linkedMesh = linkedMeshes[i]; + Skin skin = linkedMesh.skin == null ? skeletonData.defaultSkin : skeletonData.FindSkin(linkedMesh.skin); + if (skin == null) throw new Exception("Slot not found: " + linkedMesh.skin); + Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); + if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); + linkedMesh.mesh.TimelineAttachment = linkedMesh.inheritTimelines ? (VertexAttachment)parent : linkedMesh.mesh; + linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; + if (linkedMesh.mesh.Region != null) linkedMesh.mesh.UpdateRegion(); + } + linkedMeshes.Clear(); + + // Events. + if (root.ContainsKey("events")) + { + foreach (KeyValuePair entry in (Dictionary)root["events"]) + { + var entryMap = (Dictionary)entry.Value; + var data = new EventData(entry.Key); + data.Int = GetInt(entryMap, "int", 0); + data.Float = GetFloat(entryMap, "float", 0); + data.String = GetString(entryMap, "string", string.Empty); + data.AudioPath = GetString(entryMap, "audio", null); + if (data.AudioPath != null) + { + data.Volume = GetFloat(entryMap, "volume", 1); + data.Balance = GetFloat(entryMap, "balance", 0); + } + skeletonData.events.Add(data); + } + } + + // Animations. + if (root.ContainsKey("animations")) + { + foreach (KeyValuePair entry in (Dictionary)root["animations"]) + { + try + { + ReadAnimation((Dictionary)entry.Value, entry.Key, skeletonData); + } + catch (Exception e) + { + throw new Exception("Error reading animation: " + entry.Key + "\n" + e.Message, e); + } + } + } + + skeletonData.bones.TrimExcess(); + skeletonData.slots.TrimExcess(); + skeletonData.skins.TrimExcess(); + skeletonData.events.TrimExcess(); + skeletonData.animations.TrimExcess(); + skeletonData.ikConstraints.TrimExcess(); + return skeletonData; + } + + private Attachment ReadAttachment(Dictionary map, Skin skin, int slotIndex, string name, SkeletonData skeletonData) + { + float scale = this.scale; + name = GetString(map, "name", name); + + var typeName = GetString(map, "type", "region"); + var type = (AttachmentType)Enum.Parse(typeof(AttachmentType), typeName, true); + + switch (type) + { + case AttachmentType.Region: + { + string path = GetString(map, "path", name); + object sequenceJson; + map.TryGetValue("sequence", out sequenceJson); + Sequence sequence = ReadSequence(sequenceJson); + RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path, sequence); + if (region == null) return null; + region.Path = path; + region.x = GetFloat(map, "x", 0) * scale; + region.y = GetFloat(map, "y", 0) * scale; + region.scaleX = GetFloat(map, "scaleX", 1); + region.scaleY = GetFloat(map, "scaleY", 1); + region.rotation = GetFloat(map, "rotation", 0); + region.width = GetFloat(map, "width", 32) * scale; + region.height = GetFloat(map, "height", 32) * scale; + region.sequence = sequence; + + if (map.ContainsKey("color")) + { + var color = (string)map["color"]; + region.r = ToColor(color, 0); + region.g = ToColor(color, 1); + region.b = ToColor(color, 2); + region.a = ToColor(color, 3); + } + + if (region.Region != null) region.UpdateRegion(); + return region; + } + case AttachmentType.Boundingbox: + BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); + if (box == null) return null; + ReadVertices(map, box, GetInt(map, "vertexCount", 0) << 1); + return box; + case AttachmentType.Mesh: + case AttachmentType.Linkedmesh: + { + string path = GetString(map, "path", name); + object sequenceJson; + map.TryGetValue("sequence", out sequenceJson); + Sequence sequence = ReadSequence(sequenceJson); + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path, sequence); + if (mesh == null) return null; + mesh.Path = path; + + if (map.ContainsKey("color")) + { + var color = (string)map["color"]; + mesh.r = ToColor(color, 0); + mesh.g = ToColor(color, 1); + mesh.b = ToColor(color, 2); + mesh.a = ToColor(color, 3); + } + + mesh.Width = GetFloat(map, "width", 0) * scale; + mesh.Height = GetFloat(map, "height", 0) * scale; + mesh.Sequence = sequence; + + string parent = GetString(map, "parent", null); + if (parent != null) + { + linkedMeshes.Add(new LinkedMesh(mesh, GetString(map, "skin", null), slotIndex, parent, GetBoolean(map, "timelines", true))); + return mesh; + } + + float[] uvs = GetFloatArray(map, "uvs", 1); + ReadVertices(map, mesh, uvs.Length); + mesh.triangles = GetIntArray(map, "triangles"); + mesh.regionUVs = uvs; + if (mesh.Region != null) mesh.UpdateRegion(); + + if (map.ContainsKey("hull")) mesh.HullLength = GetInt(map, "hull", 0) << 1; + if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges"); + return mesh; + } + case AttachmentType.Path: + { + PathAttachment pathAttachment = attachmentLoader.NewPathAttachment(skin, name); + if (pathAttachment == null) return null; + pathAttachment.closed = GetBoolean(map, "closed", false); + pathAttachment.constantSpeed = GetBoolean(map, "constantSpeed", true); + + int vertexCount = GetInt(map, "vertexCount", 0); + ReadVertices(map, pathAttachment, vertexCount << 1); + + // potential BOZO see Java impl + pathAttachment.lengths = GetFloatArray(map, "lengths", scale); + return pathAttachment; + } + case AttachmentType.Point: + { + PointAttachment point = attachmentLoader.NewPointAttachment(skin, name); + if (point == null) return null; + point.x = GetFloat(map, "x", 0) * scale; + point.y = GetFloat(map, "y", 0) * scale; + point.rotation = GetFloat(map, "rotation", 0); + + //string color = GetString(map, "color", null); + //if (color != null) point.color = color; + return point; + } + case AttachmentType.Clipping: + { + ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name); + if (clip == null) return null; + + string end = GetString(map, "end", null); + if (end != null) + { + SlotData slot = skeletonData.FindSlot(end); + if (slot == null) throw new Exception("Clipping end slot not found: " + end); + clip.EndSlot = slot; + } + + ReadVertices(map, clip, GetInt(map, "vertexCount", 0) << 1); + + //string color = GetString(map, "color", null); + // if (color != null) clip.color = color; + return clip; + } + } + return null; + } + + public static Sequence ReadSequence(object sequenceJson) + { + var map = sequenceJson as Dictionary; + if (map == null) return null; + Sequence sequence = new Sequence(GetInt(map, "count")); + sequence.start = GetInt(map, "start", 1); + sequence.digits = GetInt(map, "digits", 0); + sequence.setupIndex = GetInt(map, "setup", 0); + return sequence; + } + + private void ReadVertices(Dictionary map, VertexAttachment attachment, int verticesLength) + { + attachment.WorldVerticesLength = verticesLength; + float[] vertices = GetFloatArray(map, "vertices", 1); + float scale = Scale; + if (verticesLength == vertices.Length) + { + if (scale != 1) + { + for (int i = 0; i < vertices.Length; i++) + { + vertices[i] *= scale; + } + } + attachment.vertices = vertices; + return; + } + ExposedList weights = new ExposedList(verticesLength * 3 * 3); + ExposedList bones = new ExposedList(verticesLength * 3); + for (int i = 0, n = vertices.Length; i < n;) + { + int boneCount = (int)vertices[i++]; + bones.Add(boneCount); + for (int nn = i + (boneCount << 2); i < nn; i += 4) + { + bones.Add((int)vertices[i]); + weights.Add(vertices[i + 1] * this.Scale); + weights.Add(vertices[i + 2] * this.Scale); + weights.Add(vertices[i + 3]); + } + } + attachment.bones = bones.ToArray(); + attachment.vertices = weights.ToArray(); + } + + private int FindSlotIndex(SkeletonData skeletonData, string slotName) + { + SlotData[] slots = skeletonData.slots.Items; + for (int i = 0, n = skeletonData.slots.Count; i < n; i++) + if (slots[i].name == slotName) return i; + throw new Exception("Slot not found: " + slotName); + } + + private void ReadAnimation(Dictionary map, string name, SkeletonData skeletonData) + { + var scale = this.scale; + var timelines = new ExposedList(); + + // Slot timelines. + if (map.ContainsKey("slots")) + { + foreach (KeyValuePair entry in (Dictionary)map["slots"]) + { + string slotName = entry.Key; + int slotIndex = FindSlotIndex(skeletonData, slotName); + var timelineMap = (Dictionary)entry.Value; + foreach (KeyValuePair timelineEntry in timelineMap) + { + var values = (List)timelineEntry.Value; + int frames = values.Count; + if (frames == 0) continue; + var timelineName = timelineEntry.Key; + if (timelineName == "attachment") + { + var timeline = new AttachmentTimeline(frames, slotIndex); + int frame = 0; + foreach (Dictionary keyMap in values) + { + timeline.SetFrame(frame++, GetFloat(keyMap, "time", 0), GetString(keyMap, "name", null)); + } + timelines.Add(timeline); + + } + else if (timelineName == "rgba") + { + var timeline = new RGBATimeline(frames, frames << 2, slotIndex); + + var keyMapEnumerator = values.GetEnumerator(); + keyMapEnumerator.MoveNext(); + var keyMap = (Dictionary)keyMapEnumerator.Current; + float time = GetFloat(keyMap, "time", 0); + string color = (string)keyMap["color"]; + float r = ToColor(color, 0); + float g = ToColor(color, 1); + float b = ToColor(color, 2); + float a = ToColor(color, 3); + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, r, g, b, a); + if (!keyMapEnumerator.MoveNext()) + { + timeline.Shrink(bezier); + break; + } + var nextMap = (Dictionary)keyMapEnumerator.Current; + + float time2 = GetFloat(nextMap, "time", 0); + color = (string)nextMap["color"]; + float nr = ToColor(color, 0); + float ng = ToColor(color, 1); + float nb = ToColor(color, 2); + float na = ToColor(color, 3); + + if (keyMap.ContainsKey("curve")) + { + object curve = keyMap["curve"]; + bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 3, time, time2, a, na, 1); + } + time = time2; + r = nr; + g = ng; + b = nb; + a = na; + keyMap = nextMap; + } + timelines.Add(timeline); + + } + else if (timelineName == "rgb") + { + var timeline = new RGBTimeline(frames, frames * 3, slotIndex); + + var keyMapEnumerator = values.GetEnumerator(); + keyMapEnumerator.MoveNext(); + var keyMap = (Dictionary)keyMapEnumerator.Current; + float time = GetFloat(keyMap, "time", 0); + string color = (string)keyMap["color"]; + float r = ToColor(color, 0, 6); + float g = ToColor(color, 1, 6); + float b = ToColor(color, 2, 6); + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, r, g, b); + if (!keyMapEnumerator.MoveNext()) + { + timeline.Shrink(bezier); + break; + } + var nextMap = (Dictionary)keyMapEnumerator.Current; + + float time2 = GetFloat(nextMap, "time", 0); + color = (string)nextMap["color"]; + float nr = ToColor(color, 0, 6); + float ng = ToColor(color, 1, 6); + float nb = ToColor(color, 2, 6); + + if (keyMap.ContainsKey("curve")) + { + object curve = keyMap["curve"]; + bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1); + } + time = time2; + r = nr; + g = ng; + b = nb; + keyMap = nextMap; + } + timelines.Add(timeline); + + } + else if (timelineName == "alpha") + { + var keyMapEnumerator = values.GetEnumerator(); + keyMapEnumerator.MoveNext(); + timelines.Add(ReadTimeline(ref keyMapEnumerator, new AlphaTimeline(frames, frames, slotIndex), 0, 1)); + + } + else if (timelineName == "rgba2") + { + var timeline = new RGBA2Timeline(frames, frames * 7, slotIndex); + + var keyMapEnumerator = values.GetEnumerator(); + keyMapEnumerator.MoveNext(); + var keyMap = (Dictionary)keyMapEnumerator.Current; + float time = GetFloat(keyMap, "time", 0); + string color = (string)keyMap["light"]; + float r = ToColor(color, 0); + float g = ToColor(color, 1); + float b = ToColor(color, 2); + float a = ToColor(color, 3); + color = (string)keyMap["dark"]; + float r2 = ToColor(color, 0, 6); + float g2 = ToColor(color, 1, 6); + float b2 = ToColor(color, 2, 6); + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, r, g, b, a, r2, g2, b2); + if (!keyMapEnumerator.MoveNext()) + { + timeline.Shrink(bezier); + break; + } + var nextMap = (Dictionary)keyMapEnumerator.Current; + + float time2 = GetFloat(nextMap, "time", 0); + color = (string)nextMap["light"]; + float nr = ToColor(color, 0); + float ng = ToColor(color, 1); + float nb = ToColor(color, 2); + float na = ToColor(color, 3); + color = (string)nextMap["dark"]; + float nr2 = ToColor(color, 0, 6); + float ng2 = ToColor(color, 1, 6); + float nb2 = ToColor(color, 2, 6); + + if (keyMap.ContainsKey("curve")) + { + object curve = keyMap["curve"]; + bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 3, time, time2, a, na, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 4, time, time2, r2, nr2, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 5, time, time2, g2, ng2, 1); + bezier = ReadCurve(curve, 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; + keyMap = nextMap; + } + timelines.Add(timeline); + + } + else if (timelineName == "rgb2") + { + var timeline = new RGB2Timeline(frames, frames * 6, slotIndex); + + var keyMapEnumerator = values.GetEnumerator(); + keyMapEnumerator.MoveNext(); + var keyMap = (Dictionary)keyMapEnumerator.Current; + float time = GetFloat(keyMap, "time", 0); + string color = (string)keyMap["light"]; + float r = ToColor(color, 0, 6); + float g = ToColor(color, 1, 6); + float b = ToColor(color, 2, 6); + color = (string)keyMap["dark"]; + float r2 = ToColor(color, 0, 6); + float g2 = ToColor(color, 1, 6); + float b2 = ToColor(color, 2, 6); + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, r, g, b, r2, g2, b2); + if (!keyMapEnumerator.MoveNext()) + { + timeline.Shrink(bezier); + break; + } + var nextMap = (Dictionary)keyMapEnumerator.Current; + + float time2 = GetFloat(nextMap, "time", 0); + color = (string)nextMap["light"]; + float nr = ToColor(color, 0, 6); + float ng = ToColor(color, 1, 6); + float nb = ToColor(color, 2, 6); + color = (string)nextMap["dark"]; + float nr2 = ToColor(color, 0, 6); + float ng2 = ToColor(color, 1, 6); + float nb2 = ToColor(color, 2, 6); + + if (keyMap.ContainsKey("curve")) + { + object curve = keyMap["curve"]; + bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 3, time, time2, r2, nr2, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 4, time, time2, g2, ng2, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 5, time, time2, b2, nb2, 1); + } + time = time2; + r = nr; + g = ng; + b = nb; + r2 = nr2; + g2 = ng2; + b2 = nb2; + keyMap = nextMap; + } + timelines.Add(timeline); + + } + else + throw new Exception("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")"); + } + } + } + + // Bone timelines. + if (map.ContainsKey("bones")) + { + foreach (KeyValuePair entry in (Dictionary)map["bones"]) + { + string boneName = entry.Key; + int boneIndex = -1; + var bones = skeletonData.bones.Items; + for (int i = 0, n = skeletonData.bones.Count; i < n; i++) + { + if (bones[i].name == boneName) + { + boneIndex = i; + break; + } + } + if (boneIndex == -1) throw new Exception("Bone not found: " + boneName); + var timelineMap = (Dictionary)entry.Value; + foreach (KeyValuePair timelineEntry in timelineMap) + { + var values = (List)timelineEntry.Value; + var keyMapEnumerator = values.GetEnumerator(); + if (!keyMapEnumerator.MoveNext()) continue; + int frames = values.Count; + var timelineName = timelineEntry.Key; + if (timelineName == "rotate") + timelines.Add(ReadTimeline(ref keyMapEnumerator, new RotateTimeline(frames, frames, boneIndex), 0, 1)); + else if (timelineName == "translate") + { + TranslateTimeline timeline = new TranslateTimeline(frames, frames << 1, boneIndex); + timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, "x", "y", 0, scale)); + } + else if (timelineName == "translatex") + { + timelines + .Add(ReadTimeline(ref keyMapEnumerator, new TranslateXTimeline(frames, frames, boneIndex), 0, scale)); + } + else if (timelineName == "translatey") + { + timelines + .Add(ReadTimeline(ref keyMapEnumerator, new TranslateYTimeline(frames, frames, boneIndex), 0, scale)); + } + else if (timelineName == "scale") + { + ScaleTimeline timeline = new ScaleTimeline(frames, frames << 1, boneIndex); + timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, "x", "y", 1, 1)); + } + else if (timelineName == "scalex") + timelines.Add(ReadTimeline(ref keyMapEnumerator, new ScaleXTimeline(frames, frames, boneIndex), 1, 1)); + else if (timelineName == "scaley") + timelines.Add(ReadTimeline(ref keyMapEnumerator, new ScaleYTimeline(frames, frames, boneIndex), 1, 1)); + else if (timelineName == "shear") + { + ShearTimeline timeline = new ShearTimeline(frames, frames << 1, boneIndex); + timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, "x", "y", 0, 1)); + } + else if (timelineName == "shearx") + timelines.Add(ReadTimeline(ref keyMapEnumerator, new ShearXTimeline(frames, frames, boneIndex), 0, 1)); + else if (timelineName == "sheary") + timelines.Add(ReadTimeline(ref keyMapEnumerator, new ShearYTimeline(frames, frames, boneIndex), 0, 1)); + else + throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")"); + } + } + } + + // IK constraint timelines. + if (map.ContainsKey("ik")) + { + foreach (KeyValuePair timelineMap in (Dictionary)map["ik"]) + { + var values = (List)timelineMap.Value; + var keyMapEnumerator = values.GetEnumerator(); + if (!keyMapEnumerator.MoveNext()) continue; + var keyMap = (Dictionary)keyMapEnumerator.Current; + IkConstraintData constraint = skeletonData.FindIkConstraint(timelineMap.Key); + IkConstraintTimeline timeline = new IkConstraintTimeline(values.Count, values.Count << 1, + skeletonData.IkConstraints.IndexOf(constraint)); + float time = GetFloat(keyMap, "time", 0); + float mix = GetFloat(keyMap, "mix", 1), softness = GetFloat(keyMap, "softness", 0) * scale; + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, mix, softness, GetBoolean(keyMap, "bendPositive", true) ? 1 : -1, + GetBoolean(keyMap, "compress", false), GetBoolean(keyMap, "stretch", false)); + if (!keyMapEnumerator.MoveNext()) + { + timeline.Shrink(bezier); + break; + } + var nextMap = (Dictionary)keyMapEnumerator.Current; + float time2 = GetFloat(nextMap, "time", 0); + float mix2 = GetFloat(nextMap, "mix", 1), softness2 = GetFloat(nextMap, "softness", 0) * scale; + if (keyMap.ContainsKey("curve")) + { + object curve = keyMap["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. + if (map.ContainsKey("transform")) + { + foreach (KeyValuePair timelineMap in (Dictionary)map["transform"]) + { + var values = (List)timelineMap.Value; + var keyMapEnumerator = values.GetEnumerator(); + if (!keyMapEnumerator.MoveNext()) continue; + var keyMap = (Dictionary)keyMapEnumerator.Current; + TransformConstraintData constraint = skeletonData.FindTransformConstraint(timelineMap.Key); + TransformConstraintTimeline timeline = new TransformConstraintTimeline(values.Count, values.Count * 6, + skeletonData.TransformConstraints.IndexOf(constraint)); + float time = GetFloat(keyMap, "time", 0); + float mixRotate = GetFloat(keyMap, "mixRotate", 1), mixShearY = GetFloat(keyMap, "mixShearY", 1); + float mixX = GetFloat(keyMap, "mixX", 1), mixY = GetFloat(keyMap, "mixY", mixX); + float mixScaleX = GetFloat(keyMap, "mixScaleX", 1), mixScaleY = GetFloat(keyMap, "mixScaleY", mixScaleX); + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY); + if (!keyMapEnumerator.MoveNext()) + { + timeline.Shrink(bezier); + break; + } + var nextMap = (Dictionary)keyMapEnumerator.Current; + float time2 = GetFloat(nextMap, "time", 0); + float mixRotate2 = GetFloat(nextMap, "mixRotate", 1), mixShearY2 = GetFloat(nextMap, "mixShearY", 1); + float mixX2 = GetFloat(nextMap, "mixX", 1), mixY2 = GetFloat(nextMap, "mixY", mixX2); + float mixScaleX2 = GetFloat(nextMap, "mixScaleX", 1), mixScaleY2 = GetFloat(nextMap, "mixScaleY", mixScaleX2); + if (keyMap.ContainsKey("curve")) + { + object curve = keyMap["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; + mixScaleX = mixScaleX2; + keyMap = nextMap; + } + timelines.Add(timeline); + } + } + + // Path constraint timelines. + if (map.ContainsKey("path")) + { + foreach (KeyValuePair constraintMap in (Dictionary)map["path"]) + { + PathConstraintData constraint = skeletonData.FindPathConstraint(constraintMap.Key); + if (constraint == null) throw new Exception("Path constraint not found: " + constraintMap.Key); + int constraintIndex = skeletonData.pathConstraints.IndexOf(constraint); + var timelineMap = (Dictionary)constraintMap.Value; + foreach (KeyValuePair timelineEntry in timelineMap) + { + var values = (List)timelineEntry.Value; + var keyMapEnumerator = values.GetEnumerator(); + if (!keyMapEnumerator.MoveNext()) continue; + + int frames = values.Count; + var timelineName = timelineEntry.Key; + if (timelineName == "position") + { + CurveTimeline1 timeline = new PathConstraintPositionTimeline(frames, frames, constraintIndex); + timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, 0, constraint.positionMode == PositionMode.Fixed ? scale : 1)); + } + else if (timelineName == "spacing") + { + CurveTimeline1 timeline = new PathConstraintSpacingTimeline(frames, frames, constraintIndex); + timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, 0, + constraint.spacingMode == SpacingMode.Length || constraint.spacingMode == SpacingMode.Fixed ? scale : 1)); + } + else if (timelineName == "mix") + { + PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(frames, frames * 3, constraintIndex); + var keyMap = (Dictionary)keyMapEnumerator.Current; + float time = GetFloat(keyMap, "time", 0); + float mixRotate = GetFloat(keyMap, "mixRotate", 1); + float mixX = GetFloat(keyMap, "mixX", 1), mixY = GetFloat(keyMap, "mixY", mixX); + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, mixRotate, mixX, mixY); + if (!keyMapEnumerator.MoveNext()) + { + timeline.Shrink(bezier); + break; + } + var nextMap = (Dictionary)keyMapEnumerator.Current; + float time2 = GetFloat(nextMap, "time", 0); + float mixRotate2 = GetFloat(nextMap, "mixRotate", 1); + float mixX2 = GetFloat(nextMap, "mixX", 1), mixY2 = GetFloat(nextMap, "mixY", mixX2); + if (keyMap.ContainsKey("curve")) + { + object curve = keyMap["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); + } + time = time2; + mixRotate = mixRotate2; + mixX = mixX2; + mixY = mixY2; + keyMap = nextMap; + } + timelines.Add(timeline); + } + } + } + } + + // Attachment timelines. + if (map.ContainsKey("attachments")) + { + foreach (KeyValuePair attachmentsMap in (Dictionary)map["attachments"]) + { + Skin skin = skeletonData.FindSkin(attachmentsMap.Key); + foreach (KeyValuePair slotMap in (Dictionary)attachmentsMap.Value) + { + SlotData slot = skeletonData.FindSlot(slotMap.Key); + if (slot == null) throw new Exception("Slot not found: " + slotMap.Key); + foreach (KeyValuePair attachmentMap in (Dictionary)slotMap.Value) + { + Attachment attachment = skin.GetAttachment(slot.index, attachmentMap.Key); + if (attachment == null) throw new Exception("Timeline attachment not found: " + attachmentMap.Key); + foreach (KeyValuePair timelineMap in (Dictionary)attachmentMap.Value) + { + var values = (List)timelineMap.Value; + var keyMapEnumerator = values.GetEnumerator(); + if (!keyMapEnumerator.MoveNext()) continue; + var keyMap = (Dictionary)keyMapEnumerator.Current; + int frames = values.Count; + string timelineName = timelineMap.Key; + if (timelineName == "deform") + { + VertexAttachment vertexAttachment = (VertexAttachment)attachment; + bool weighted = vertexAttachment.bones != null; + float[] vertices = vertexAttachment.vertices; + int deformLength = weighted ? (vertices.Length / 3) << 1 : vertices.Length; + + DeformTimeline timeline = new DeformTimeline(frames, frames, slot.Index, vertexAttachment); + float time = GetFloat(keyMap, "time", 0); + for (int frame = 0, bezier = 0; ; frame++) + { + float[] deform; + if (!keyMap.ContainsKey("vertices")) + { + deform = weighted ? new float[deformLength] : vertices; + } + else + { + deform = new float[deformLength]; + int start = GetInt(keyMap, "offset", 0); + float[] verticesValue = GetFloatArray(keyMap, "vertices", 1); + Array.Copy(verticesValue, 0, deform, start, verticesValue.Length); + if (scale != 1) + { + for (int i = start, n = i + verticesValue.Length; i < n; i++) + deform[i] *= scale; + } + + if (!weighted) + { + for (int i = 0; i < deformLength; i++) + deform[i] += vertices[i]; + } + } + + timeline.SetFrame(frame, time, deform); + if (!keyMapEnumerator.MoveNext()) + { + timeline.Shrink(bezier); + break; + } + var nextMap = (Dictionary)keyMapEnumerator.Current; + float time2 = GetFloat(nextMap, "time", 0); + if (keyMap.ContainsKey("curve")) + { + object curve = keyMap["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, slot.index, attachment); + float lastDelay = 0; + for (int frame = 0; keyMap != null; + keyMapEnumerator.MoveNext(), keyMap = (Dictionary)keyMapEnumerator.Current, frame++) + { + + float delay = GetFloat(keyMap, "delay", lastDelay); + SequenceMode sequenceMode = (SequenceMode)Enum.Parse(typeof(SequenceMode), + GetString(keyMap, "mode", "hold"), true); + timeline.SetFrame(frame, GetFloat(keyMap, "time", 0), + sequenceMode, GetInt(keyMap, "index", 0), delay); + lastDelay = delay; + } + timelines.Add(timeline); + } + } + } + } + } + } + + // Draw order timeline. + if (map.ContainsKey("drawOrder")) + { + var values = (List)map["drawOrder"]; + var timeline = new DrawOrderTimeline(values.Count); + int slotCount = skeletonData.slots.Count; + int frame = 0; + foreach (Dictionary drawOrderMap in values) + { + int[] drawOrder = null; + if (drawOrderMap.ContainsKey("offsets")) + { + drawOrder = new int[slotCount]; + for (int i = slotCount - 1; i >= 0; i--) + drawOrder[i] = -1; + var offsets = (List)drawOrderMap["offsets"]; + int[] unchanged = new int[slotCount - offsets.Count]; + int originalIndex = 0, unchangedIndex = 0; + foreach (Dictionary offsetMap in offsets) + { + int slotIndex = FindSlotIndex(skeletonData, (string)offsetMap["slot"]); + // Collect unchanged items. + while (originalIndex != slotIndex) + unchanged[unchangedIndex++] = originalIndex++; + // Set changed items. + int index = originalIndex + (int)(float)offsetMap["offset"]; + drawOrder[index] = originalIndex++; + } + // Collect remaining unchanged items. + while (originalIndex < slotCount) + unchanged[unchangedIndex++] = originalIndex++; + // Fill in unchanged items. + for (int i = slotCount - 1; i >= 0; i--) + if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex]; + } + timeline.SetFrame(frame, GetFloat(drawOrderMap, "time", 0), drawOrder); + ++frame; + } + timelines.Add(timeline); + } + + // Event timeline. + if (map.ContainsKey("events")) + { + var eventsMap = (List)map["events"]; + var timeline = new EventTimeline(eventsMap.Count); + int frame = 0; + foreach (Dictionary eventMap in eventsMap) + { + EventData eventData = skeletonData.FindEvent((string)eventMap["name"]); + if (eventData == null) throw new Exception("Event not found: " + eventMap["name"]); + var e = new Event(GetFloat(eventMap, "time", 0), eventData) + { + intValue = GetInt(eventMap, "int", eventData.Int), + floatValue = GetFloat(eventMap, "float", eventData.Float), + stringValue = GetString(eventMap, "string", eventData.String) + }; + if (e.data.AudioPath != null) + { + e.volume = GetFloat(eventMap, "volume", eventData.Volume); + e.balance = GetFloat(eventMap, "balance", eventData.Balance); + } + timeline.SetFrame(frame, e); + ++frame; + } + timelines.Add(timeline); + } + timelines.TrimExcess(); + float duration = 0; + var items = timelines.Items; + for (int i = 0, n = timelines.Count; i < n; i++) + duration = Math.Max(duration, items[i].Duration); + skeletonData.animations.Add(new Animation(name, timelines, duration)); + } + + static Timeline ReadTimeline(ref List.Enumerator keyMapEnumerator, CurveTimeline1 timeline, float defaultValue, float scale) + { + var keyMap = (Dictionary)keyMapEnumerator.Current; + float time = GetFloat(keyMap, "time", 0); + float value = GetFloat(keyMap, "value", defaultValue) * scale; + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, value); + if (!keyMapEnumerator.MoveNext()) + { + timeline.Shrink(bezier); + return timeline; + } + var nextMap = (Dictionary)keyMapEnumerator.Current; + float time2 = GetFloat(nextMap, "time", 0); + float value2 = GetFloat(nextMap, "value", defaultValue) * scale; + if (keyMap.ContainsKey("curve")) + { + object curve = keyMap["curve"]; + bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, value, value2, scale); + } + time = time2; + value = value2; + keyMap = nextMap; + } + } + + static Timeline ReadTimeline(ref List.Enumerator keyMapEnumerator, CurveTimeline2 timeline, String name1, String name2, float defaultValue, + float scale) + { + + var keyMap = (Dictionary)keyMapEnumerator.Current; + float time = GetFloat(keyMap, "time", 0); + float value1 = GetFloat(keyMap, name1, defaultValue) * scale, value2 = GetFloat(keyMap, name2, defaultValue) * scale; + for (int frame = 0, bezier = 0; ; frame++) + { + timeline.SetFrame(frame, time, value1, value2); + if (!keyMapEnumerator.MoveNext()) + { + timeline.Shrink(bezier); + return timeline; + } + var nextMap = (Dictionary)keyMapEnumerator.Current; + float time2 = GetFloat(nextMap, "time", 0); + float nvalue1 = GetFloat(nextMap, name1, defaultValue) * scale, nvalue2 = GetFloat(nextMap, name2, defaultValue) * scale; + if (keyMap.ContainsKey("curve")) + { + object curve = keyMap["curve"]; + 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; + } + } + + static int ReadCurve(object curve, CurveTimeline timeline, int bezier, int frame, int value, float time1, float time2, + float value1, float value2, float scale) + { + + string curveString = curve as string; + if (curveString != null) + { + if (curveString == "stepped") timeline.SetStepped(frame); + return bezier; + } + var curveValues = (List)curve; + int i = value << 2; + float cx1 = (float)curveValues[i]; + float cy1 = (float)curveValues[i + 1] * scale; + float cx2 = (float)curveValues[i + 2]; + float cy2 = (float)curveValues[i + 3] * scale; + SetBezier(timeline, frame, value, bezier, time1, value1, cx1, cy1, cx2, cy2, time2, value2); + return bezier + 1; + } + + 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) + { + timeline.SetBezier(bezier, frame, value, time1, value1, cx1, cy1, cx2, cy2, time2, value2); + } + + static float[] GetFloatArray(Dictionary map, string name, float scale) + { + var list = (List)map[name]; + var values = new float[list.Count]; + if (scale == 1) + { + for (int i = 0, n = list.Count; i < n; i++) + values[i] = (float)list[i]; + } + else + { + for (int i = 0, n = list.Count; i < n; i++) + values[i] = (float)list[i] * scale; + } + return values; + } + + static int[] GetIntArray(Dictionary map, string name) + { + var list = (List)map[name]; + var values = new int[list.Count]; + for (int i = 0, n = list.Count; i < n; i++) + values[i] = (int)(float)list[i]; + return values; + } + + static float GetFloat(Dictionary map, string name, float defaultValue) + { + if (!map.ContainsKey(name)) return defaultValue; + return (float)map[name]; + } + + static int GetInt(Dictionary map, string name, int defaultValue) + { + if (!map.ContainsKey(name)) return defaultValue; + return (int)(float)map[name]; + } + + static int GetInt(Dictionary map, string name) + { + if (!map.ContainsKey(name)) throw new ArgumentException("Named value not found: " + name); + return (int)(float)map[name]; + } + + static bool GetBoolean(Dictionary map, string name, bool defaultValue) + { + if (!map.ContainsKey(name)) return defaultValue; + return (bool)map[name]; + } + + static string GetString(Dictionary map, string name, string defaultValue) + { + if (!map.ContainsKey(name)) return defaultValue; + return (string)map[name]; + } + + static float ToColor(string hexString, int colorIndex, int expectedLength = 8) + { + if (hexString.Length != expectedLength) + throw new ArgumentException("Color hexidecimal length must be " + expectedLength + ", recieved: " + hexString, "hexString"); + return Convert.ToInt32(hexString.Substring(colorIndex * 2, 2), 16) / (float)255; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/SkeletonLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/SkeletonLoader.cs index d21be16..1e3e8f7 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/SkeletonLoader.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/SkeletonLoader.cs @@ -29,64 +29,71 @@ using System; using System.Collections.Generic; -using System.IO; -namespace Spine4_1_00 { +namespace Spine4_1_00 +{ - /// - /// Base class for loading skeleton data from a file. - /// - /// SeeJSON and binary data in the - /// Spine Runtimes Guide. - /// - public abstract class SkeletonLoader { - protected readonly AttachmentLoader attachmentLoader; - protected float scale = 1; - protected readonly List linkedMeshes = new List(); + /// + /// Base class for loading skeleton data from a file. + /// + /// SeeJSON and binary data in the + /// Spine Runtimes Guide. + /// + public abstract class SkeletonLoader + { + protected readonly AttachmentLoader attachmentLoader; + protected float scale = 1; + protected readonly List linkedMeshes = new List(); - /// Creates a skeleton loader that loads attachments using an with the specified atlas. - /// - public SkeletonLoader (params Atlas[] atlasArray) { - attachmentLoader = new AtlasAttachmentLoader(atlasArray); - } + /// Creates a skeleton loader that loads attachments using an with the specified atlas. + /// + public SkeletonLoader(params Atlas[] atlasArray) + { + attachmentLoader = new AtlasAttachmentLoader(atlasArray); + } - /// Creates a skeleton loader that loads attachments using the specified attachment loader. - /// See Loading skeleton data in the - /// Spine Runtimes Guide. - public SkeletonLoader (AttachmentLoader attachmentLoader) { - if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader", "attachmentLoader cannot be null."); - this.attachmentLoader = attachmentLoader; - } + /// Creates a skeleton loader that loads attachments using the specified attachment loader. + /// See Loading skeleton data in the + /// Spine Runtimes Guide. + public SkeletonLoader(AttachmentLoader attachmentLoader) + { + if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader", "attachmentLoader cannot be null."); + this.attachmentLoader = attachmentLoader; + } - /// Scales bone positions, image sizes, and translations as they are loaded. This allows different size images to be used at - /// runtime than were used in Spine. - /// - /// See Scaling in the Spine Runtimes Guide. - /// - public float Scale { - get { return scale; } - set { - if (scale == 0) throw new ArgumentNullException("scale", "scale cannot be 0."); - this.scale = value; - } - } + /// Scales bone positions, image sizes, and translations as they are loaded. This allows different size images to be used at + /// runtime than were used in Spine. + /// + /// See Scaling in the Spine Runtimes Guide. + /// + public float Scale + { + get { return scale; } + set + { + if (scale == 0) throw new ArgumentNullException("scale", "scale cannot be 0."); + this.scale = value; + } + } - public abstract SkeletonData ReadSkeletonData (string path); + public abstract SkeletonData ReadSkeletonData(string path); - protected class LinkedMesh { - internal string parent, skin; - internal int slotIndex; - internal MeshAttachment mesh; - internal bool inheritTimelines; + protected class LinkedMesh + { + internal string parent, skin; + internal int slotIndex; + internal MeshAttachment mesh; + internal bool inheritTimelines; - public LinkedMesh (MeshAttachment mesh, string skin, int slotIndex, string parent, bool inheritTimelines) { - this.mesh = mesh; - this.skin = skin; - this.slotIndex = slotIndex; - this.parent = parent; - this.inheritTimelines = inheritTimelines; - } - } + public LinkedMesh(MeshAttachment mesh, string skin, int slotIndex, string parent, bool inheritTimelines) + { + this.mesh = mesh; + this.skin = skin; + this.slotIndex = slotIndex; + this.parent = parent; + this.inheritTimelines = inheritTimelines; + } + } - } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Skin.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Skin.cs index 98cf9d1..6ef577b 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Skin.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Skin.cs @@ -28,177 +28,208 @@ *****************************************************************************/ using System; -using System.Collections; using System.Collections.Generic; -namespace Spine4_1_00 { - /// Stores attachments by slot index and attachment name. - /// See SkeletonData , Skeleton , and - /// Runtime skins in the Spine Runtimes Guide. - /// - public class Skin { - internal string name; - // Difference to reference implementation: using Dictionary instead of HashSet. - // Reason is that there is no efficient way to replace or access an already added element, losing any benefits. - private Dictionary attachments = new Dictionary(SkinKeyComparer.Instance); - internal readonly ExposedList bones = new ExposedList(); - internal readonly ExposedList constraints = new ExposedList(); - - public string Name { get { return name; } } - ///Returns all attachments contained in this skin. - public ICollection Attachments { get { return attachments.Values; } } - public ExposedList Bones { get { return bones; } } - public ExposedList Constraints { get { return constraints; } } - - public Skin (string name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; - } - - /// 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. - public void SetAttachment (int slotIndex, string name, Attachment attachment) { - if (attachment == null) throw new ArgumentNullException("attachment", "attachment cannot be null."); - attachments[new SkinKey(slotIndex, name)] = new SkinEntry(slotIndex, name, attachment); - } - - ///Adds all attachments, bones, and constraints from the specified skin to this skin. - public void AddSkin (Skin skin) { - foreach (BoneData data in skin.bones) - if (!bones.Contains(data)) bones.Add(data); - - foreach (ConstraintData data in skin.constraints) - if (!constraints.Contains(data)) constraints.Add(data); - - foreach (var item in skin.attachments) { - SkinEntry entry = item.Value; - SetAttachment(entry.slotIndex, entry.name, entry.attachment); - } - } - - ///Adds all attachments from the specified skin to this skin. Attachments are deep copied. - public void CopySkin (Skin skin) { - foreach (BoneData data in skin.bones) - if (!bones.Contains(data)) bones.Add(data); - - foreach (ConstraintData data in skin.constraints) - if (!constraints.Contains(data)) constraints.Add(data); - - foreach (var item in skin.attachments) { - SkinEntry entry = item.Value; - if (entry.attachment is MeshAttachment) { - SetAttachment(entry.slotIndex, entry.name, - entry.attachment != null ? ((MeshAttachment)entry.attachment).NewLinkedMesh() : null); - } else - SetAttachment(entry.slotIndex, entry.name, entry.attachment != null ? entry.attachment.Copy() : null); - } - } - - /// Returns the attachment for the specified slot index and name, or null. - /// May be null. - public Attachment GetAttachment (int slotIndex, string name) { - SkinEntry entry; - bool containsKey = attachments.TryGetValue(new SkinKey(slotIndex, name), out entry); - return containsKey ? entry.attachment : null; - } - - /// Removes the attachment in the skin for the specified slot index and name, if any. - public void RemoveAttachment (int slotIndex, string name) { - attachments.Remove(new SkinKey(slotIndex, name)); - } - - /// Returns all attachments in this skin for the specified slot index. - /// The target slotIndex. To find the slot index, use and . - public void GetAttachments (int slotIndex, List attachments) { - if (slotIndex < 0) throw new ArgumentException("slotIndex must be >= 0."); - if (attachments == null) throw new ArgumentNullException("attachments", "attachments cannot be null."); - foreach (var item in this.attachments) { - SkinEntry entry = item.Value; - if (entry.slotIndex == slotIndex) attachments.Add(entry); - } - } - - ///Clears all attachments, bones, and constraints. - public void Clear () { - attachments.Clear(); - bones.Clear(); - constraints.Clear(); - } - - override public string ToString () { - return name; - } - - /// Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached. - internal void AttachAll (Skeleton skeleton, Skin oldSkin) { - Slot[] slots = skeleton.slots.Items; - foreach (var item in oldSkin.attachments) { - SkinEntry entry = item.Value; - int slotIndex = entry.slotIndex; - Slot slot = slots[slotIndex]; - if (slot.Attachment == entry.attachment) { - Attachment attachment = GetAttachment(slotIndex, entry.name); - if (attachment != null) slot.Attachment = attachment; - } - } - } - - /// Stores an entry in the skin consisting of the slot index, name, and attachment. - public struct SkinEntry { - internal readonly int slotIndex; - internal readonly string name; - internal readonly Attachment attachment; - - public SkinEntry (int slotIndex, string name, Attachment attachment) { - this.slotIndex = slotIndex; - this.name = name; - this.attachment = attachment; - } - - public int SlotIndex { - get { - return slotIndex; - } - } - - /// The name the attachment is associated with, equivalent to the skin placeholder name in the Spine editor. - public String Name { - get { - return name; - } - } - - public Attachment Attachment { - get { - return attachment; - } - } - } - - private struct SkinKey { - internal readonly int slotIndex; - internal readonly string name; - internal readonly int hashCode; - - public SkinKey (int slotIndex, string name) { - if (slotIndex < 0) throw new ArgumentException("slotIndex must be >= 0."); - if (name == null) throw new ArgumentNullException("name", "name cannot be null"); - this.slotIndex = slotIndex; - this.name = name; - this.hashCode = name.GetHashCode() + slotIndex * 37; - } - } - - class SkinKeyComparer : IEqualityComparer { - internal static readonly SkinKeyComparer Instance = new SkinKeyComparer(); - - bool IEqualityComparer.Equals (SkinKey e1, SkinKey e2) { - return e1.slotIndex == e2.slotIndex && string.Equals(e1.name, e2.name, StringComparison.Ordinal); - } - - int IEqualityComparer.GetHashCode (SkinKey e) { - return e.hashCode; - } - } - } +namespace Spine4_1_00 +{ + /// Stores attachments by slot index and attachment name. + /// See SkeletonData , Skeleton , and + /// Runtime skins in the Spine Runtimes Guide. + /// + public class Skin + { + internal string name; + // Difference to reference implementation: using Dictionary instead of HashSet. + // Reason is that there is no efficient way to replace or access an already added element, losing any benefits. + private Dictionary attachments = new Dictionary(SkinKeyComparer.Instance); + internal readonly ExposedList bones = new ExposedList(); + internal readonly ExposedList constraints = new ExposedList(); + + public string Name { get { return name; } } + ///Returns all attachments contained in this skin. + public ICollection Attachments { get { return attachments.Values; } } + public ExposedList Bones { get { return bones; } } + public ExposedList Constraints { get { return constraints; } } + + public Skin(string name) + { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + } + + /// 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. + public void SetAttachment(int slotIndex, string name, Attachment attachment) + { + if (attachment == null) throw new ArgumentNullException("attachment", "attachment cannot be null."); + attachments[new SkinKey(slotIndex, name)] = new SkinEntry(slotIndex, name, attachment); + } + + ///Adds all attachments, bones, and constraints from the specified skin to this skin. + public void AddSkin(Skin skin) + { + foreach (BoneData data in skin.bones) + if (!bones.Contains(data)) bones.Add(data); + + foreach (ConstraintData data in skin.constraints) + if (!constraints.Contains(data)) constraints.Add(data); + + foreach (var item in skin.attachments) + { + SkinEntry entry = item.Value; + SetAttachment(entry.slotIndex, entry.name, entry.attachment); + } + } + + ///Adds all attachments from the specified skin to this skin. Attachments are deep copied. + public void CopySkin(Skin skin) + { + foreach (BoneData data in skin.bones) + if (!bones.Contains(data)) bones.Add(data); + + foreach (ConstraintData data in skin.constraints) + if (!constraints.Contains(data)) constraints.Add(data); + + foreach (var item in skin.attachments) + { + SkinEntry entry = item.Value; + if (entry.attachment is MeshAttachment) + { + SetAttachment(entry.slotIndex, entry.name, + entry.attachment != null ? ((MeshAttachment)entry.attachment).NewLinkedMesh() : null); + } + else + SetAttachment(entry.slotIndex, entry.name, entry.attachment != null ? entry.attachment.Copy() : null); + } + } + + /// Returns the attachment for the specified slot index and name, or null. + /// May be null. + public Attachment GetAttachment(int slotIndex, string name) + { + SkinEntry entry; + bool containsKey = attachments.TryGetValue(new SkinKey(slotIndex, name), out entry); + return containsKey ? entry.attachment : null; + } + + /// Removes the attachment in the skin for the specified slot index and name, if any. + public void RemoveAttachment(int slotIndex, string name) + { + attachments.Remove(new SkinKey(slotIndex, name)); + } + + /// Returns all attachments in this skin for the specified slot index. + /// The target slotIndex. To find the slot index, use and . + public void GetAttachments(int slotIndex, List attachments) + { + if (slotIndex < 0) throw new ArgumentException("slotIndex must be >= 0."); + if (attachments == null) throw new ArgumentNullException("attachments", "attachments cannot be null."); + foreach (var item in this.attachments) + { + SkinEntry entry = item.Value; + if (entry.slotIndex == slotIndex) attachments.Add(entry); + } + } + + ///Clears all attachments, bones, and constraints. + public void Clear() + { + attachments.Clear(); + bones.Clear(); + constraints.Clear(); + } + + override public string ToString() + { + return name; + } + + /// Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached. + internal void AttachAll(Skeleton skeleton, Skin oldSkin) + { + Slot[] slots = skeleton.slots.Items; + foreach (var item in oldSkin.attachments) + { + SkinEntry entry = item.Value; + int slotIndex = entry.slotIndex; + Slot slot = slots[slotIndex]; + if (slot.Attachment == entry.attachment) + { + Attachment attachment = GetAttachment(slotIndex, entry.name); + if (attachment != null) slot.Attachment = attachment; + } + } + } + + /// Stores an entry in the skin consisting of the slot index, name, and attachment. + public struct SkinEntry + { + internal readonly int slotIndex; + internal readonly string name; + internal readonly Attachment attachment; + + public SkinEntry(int slotIndex, string name, Attachment attachment) + { + this.slotIndex = slotIndex; + this.name = name; + this.attachment = attachment; + } + + public int SlotIndex + { + get + { + return slotIndex; + } + } + + /// The name the attachment is associated with, equivalent to the skin placeholder name in the Spine editor. + public String Name + { + get + { + return name; + } + } + + public Attachment Attachment + { + get + { + return attachment; + } + } + } + + private struct SkinKey + { + internal readonly int slotIndex; + internal readonly string name; + internal readonly int hashCode; + + public SkinKey(int slotIndex, string name) + { + if (slotIndex < 0) throw new ArgumentException("slotIndex must be >= 0."); + if (name == null) throw new ArgumentNullException("name", "name cannot be null"); + this.slotIndex = slotIndex; + this.name = name; + this.hashCode = name.GetHashCode() + slotIndex * 37; + } + } + + class SkinKeyComparer : IEqualityComparer + { + internal static readonly SkinKeyComparer Instance = new SkinKeyComparer(); + + bool IEqualityComparer.Equals(SkinKey e1, SkinKey e2) + { + return e1.slotIndex == e2.slotIndex && string.Equals(e1.name, e2.name, StringComparison.Ordinal); + } + + int IEqualityComparer.GetHashCode(SkinKey e) + { + return e.hashCode; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Slot.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Slot.cs index 2e57206..b811f87 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Slot.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Slot.cs @@ -29,171 +29,191 @@ using System; -namespace Spine4_1_00 { - - /// - /// Stores a slot's current pose. Slots organize attachments for purposes and provide a place to store - /// state for an attachment.State cannot be stored in an attachment itself because attachments are stateless and may be shared - /// across multiple skeletons. - /// - public class Slot { - internal SlotData data; - internal Bone bone; - internal float r, g, b, a; - internal float r2, g2, b2; - internal bool hasSecondColor; - internal Attachment attachment; - internal int sequenceIndex; - internal ExposedList deform = new ExposedList(); - internal int attachmentState; - - public Slot (SlotData data, Bone bone) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); - this.data = data; - this.bone = bone; - - // darkColor = data.darkColor == null ? null : new Color(); - if (data.hasSecondColor) { - r2 = g2 = b2 = 0; - } - - SetToSetupPose(); - } - - /// Copy constructor. - public Slot (Slot slot, Bone bone) { - if (slot == null) throw new ArgumentNullException("slot", "slot cannot be null."); - if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); - data = slot.data; - this.bone = bone; - r = slot.r; - g = slot.g; - b = slot.b; - a = slot.a; - - // darkColor = slot.darkColor == null ? null : new Color(slot.darkColor); - if (slot.hasSecondColor) { - r2 = slot.r2; - g2 = slot.g2; - b2 = slot.b2; - } else { - r2 = g2 = b2 = 0; - } - hasSecondColor = slot.hasSecondColor; - - attachment = slot.attachment; - sequenceIndex = slot.sequenceIndex; - deform.AddRange(slot.deform); - } - - /// The slot's setup pose data. - public SlotData Data { get { return data; } } - /// The bone this slot belongs to. - public Bone Bone { get { return bone; } } - /// The skeleton this slot belongs to. - public Skeleton Skeleton { get { return bone.skeleton; } } - /// The color used to tint the slot's attachment. If is set, this is used as the light color for two - /// color tinting. - public float R { get { return r; } set { r = value; } } - /// The color used to tint the slot's attachment. If is set, this is used as the light color for two - /// color tinting. - public float G { get { return g; } set { g = value; } } - /// The color used to tint the slot's attachment. If is set, this is used as the light color for two - /// color tinting. - public float B { get { return b; } set { b = value; } } - /// The color used to tint the slot's attachment. If is set, this is used as the light color for two - /// color tinting. - public float A { get { return a; } set { a = value; } } - - public void ClampColor () { - r = MathUtils.Clamp(r, 0, 1); - g = MathUtils.Clamp(g, 0, 1); - b = MathUtils.Clamp(b, 0, 1); - a = MathUtils.Clamp(a, 0, 1); - } - - /// The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used. - /// - public float R2 { get { return r2; } set { r2 = value; } } - /// The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used. - /// - public float G2 { get { return g2; } set { g2 = value; } } - /// The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used. - /// - public float B2 { get { return b2; } set { b2 = value; } } - /// Whether R2 G2 B2 are used to tint the slot's attachment for two color tinting. False if two color tinting is not used. - public bool HasSecondColor { get { return data.hasSecondColor; } set { data.hasSecondColor = value; } } - - public void ClampSecondColor () { - r2 = MathUtils.Clamp(r2, 0, 1); - g2 = MathUtils.Clamp(g2, 0, 1); - b2 = MathUtils.Clamp(b2, 0, 1); - } - - public Attachment Attachment { - /// The current attachment for the slot, or null if the slot has no attachment. - get { return attachment; } - /// - /// Sets the slot's attachment and, if the attachment changed, resets and clears the . - /// The deform is not cleared if the old attachment has the same as the - /// specified attachment. - /// May be null. - set { - if (attachment == value) return; - if (!(value is VertexAttachment) || !(this.attachment is VertexAttachment) - || ((VertexAttachment)value).TimelineAttachment != ((VertexAttachment)this.attachment).TimelineAttachment) { - deform.Clear(); - } - this.attachment = value; - sequenceIndex = -1; - } - } - - /// - /// The index of the texture region to display when the slot's attachment has a . -1 represents the - /// . - /// - public int SequenceIndex { get { return sequenceIndex; } set { sequenceIndex = value; } } - - /// Vertices to deform the slot's attachment. For an unweighted mesh, the entries are local positions for each vertex. For a - /// weighted mesh, the entries are an offset for each vertex which will be added to the mesh's local vertex positions. - /// - /// See and . - public ExposedList Deform { - get { - return deform; - } - set { - if (deform == null) throw new ArgumentNullException("deform", "deform cannot be null."); - deform = value; - } - } - - /// Sets this slot to the setup pose. - public void SetToSetupPose () { - r = data.r; - g = data.g; - b = data.b; - a = data.a; - - // if (darkColor != null) darkColor.set(data.darkColor); - if (HasSecondColor) { - r2 = data.r2; - g2 = data.g2; - b2 = data.b2; - } - - if (data.attachmentName == null) - Attachment = null; - else { - attachment = null; - Attachment = bone.skeleton.GetAttachment(data.index, data.attachmentName); - } - } - - override public string ToString () { - return data.name; - } - } +namespace Spine4_1_00 +{ + + /// + /// Stores a slot's current pose. Slots organize attachments for purposes and provide a place to store + /// state for an attachment.State cannot be stored in an attachment itself because attachments are stateless and may be shared + /// across multiple skeletons. + /// + public class Slot + { + internal SlotData data; + internal Bone bone; + internal float r, g, b, a; + internal float r2, g2, b2; + internal bool hasSecondColor; + internal Attachment attachment; + internal int sequenceIndex; + internal ExposedList deform = new ExposedList(); + internal int attachmentState; + + public Slot(SlotData data, Bone bone) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); + this.data = data; + this.bone = bone; + + // darkColor = data.darkColor == null ? null : new Color(); + if (data.hasSecondColor) + { + r2 = g2 = b2 = 0; + } + + SetToSetupPose(); + } + + /// Copy constructor. + public Slot(Slot slot, Bone bone) + { + if (slot == null) throw new ArgumentNullException("slot", "slot cannot be null."); + if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); + data = slot.data; + this.bone = bone; + r = slot.r; + g = slot.g; + b = slot.b; + a = slot.a; + + // darkColor = slot.darkColor == null ? null : new Color(slot.darkColor); + if (slot.hasSecondColor) + { + r2 = slot.r2; + g2 = slot.g2; + b2 = slot.b2; + } + else + { + r2 = g2 = b2 = 0; + } + hasSecondColor = slot.hasSecondColor; + + attachment = slot.attachment; + sequenceIndex = slot.sequenceIndex; + deform.AddRange(slot.deform); + } + + /// The slot's setup pose data. + public SlotData Data { get { return data; } } + /// The bone this slot belongs to. + public Bone Bone { get { return bone; } } + /// The skeleton this slot belongs to. + public Skeleton Skeleton { get { return bone.skeleton; } } + /// The color used to tint the slot's attachment. If is set, this is used as the light color for two + /// color tinting. + public float R { get { return r; } set { r = value; } } + /// The color used to tint the slot's attachment. If is set, this is used as the light color for two + /// color tinting. + public float G { get { return g; } set { g = value; } } + /// The color used to tint the slot's attachment. If is set, this is used as the light color for two + /// color tinting. + public float B { get { return b; } set { b = value; } } + /// The color used to tint the slot's attachment. If is set, this is used as the light color for two + /// color tinting. + public float A { get { return a; } set { a = value; } } + + public void ClampColor() + { + r = MathUtils.Clamp(r, 0, 1); + g = MathUtils.Clamp(g, 0, 1); + b = MathUtils.Clamp(b, 0, 1); + a = MathUtils.Clamp(a, 0, 1); + } + + /// The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used. + /// + public float R2 { get { return r2; } set { r2 = value; } } + /// The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used. + /// + public float G2 { get { return g2; } set { g2 = value; } } + /// The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used. + /// + public float B2 { get { return b2; } set { b2 = value; } } + /// Whether R2 G2 B2 are used to tint the slot's attachment for two color tinting. False if two color tinting is not used. + public bool HasSecondColor { get { return data.hasSecondColor; } set { data.hasSecondColor = value; } } + + public void ClampSecondColor() + { + r2 = MathUtils.Clamp(r2, 0, 1); + g2 = MathUtils.Clamp(g2, 0, 1); + b2 = MathUtils.Clamp(b2, 0, 1); + } + + public Attachment Attachment + { + /// The current attachment for the slot, or null if the slot has no attachment. + get { return attachment; } + /// + /// Sets the slot's attachment and, if the attachment changed, resets and clears the . + /// The deform is not cleared if the old attachment has the same as the + /// specified attachment. + /// May be null. + set + { + if (attachment == value) return; + if (!(value is VertexAttachment) || !(this.attachment is VertexAttachment) + || ((VertexAttachment)value).TimelineAttachment != ((VertexAttachment)this.attachment).TimelineAttachment) + { + deform.Clear(); + } + this.attachment = value; + sequenceIndex = -1; + } + } + + /// + /// The index of the texture region to display when the slot's attachment has a . -1 represents the + /// . + /// + public int SequenceIndex { get { return sequenceIndex; } set { sequenceIndex = value; } } + + /// Vertices to deform the slot's attachment. For an unweighted mesh, the entries are local positions for each vertex. For a + /// weighted mesh, the entries are an offset for each vertex which will be added to the mesh's local vertex positions. + /// + /// See and . + public ExposedList Deform + { + get + { + return deform; + } + set + { + if (deform == null) throw new ArgumentNullException("deform", "deform cannot be null."); + deform = value; + } + } + + /// Sets this slot to the setup pose. + public void SetToSetupPose() + { + r = data.r; + g = data.g; + b = data.b; + a = data.a; + + // if (darkColor != null) darkColor.set(data.darkColor); + if (HasSecondColor) + { + r2 = data.r2; + g2 = data.g2; + b2 = data.b2; + } + + if (data.attachmentName == null) + Attachment = null; + else + { + attachment = null; + Attachment = bone.skeleton.GetAttachment(data.index, data.attachmentName); + } + } + + override public string ToString() + { + return data.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/SlotData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/SlotData.cs index d23a1d2..85b9119 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/SlotData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/SlotData.cs @@ -29,49 +29,53 @@ using System; -namespace Spine4_1_00 { - public class SlotData { - internal int index; - internal string name; - internal BoneData boneData; - internal float r = 1, g = 1, b = 1, a = 1; - internal float r2 = 0, g2 = 0, b2 = 0; - internal bool hasSecondColor = false; - internal string attachmentName; - internal BlendMode blendMode; +namespace Spine4_1_00 +{ + public class SlotData + { + internal int index; + internal string name; + internal BoneData boneData; + internal float r = 1, g = 1, b = 1, a = 1; + internal float r2 = 0, g2 = 0, b2 = 0; + internal bool hasSecondColor = false; + internal string attachmentName; + internal BlendMode blendMode; - /// The index of the slot in . - public int Index { get { return index; } } - /// The name of the slot, which is unique across all slots in the skeleton. - public string Name { get { return name; } } - /// The bone this slot belongs to. - public BoneData BoneData { get { return boneData; } } - public float R { get { return r; } set { r = value; } } - public float G { get { return g; } set { g = value; } } - public float B { get { return b; } set { b = value; } } - public float A { get { return a; } set { a = value; } } + /// The index of the slot in . + public int Index { get { return index; } } + /// The name of the slot, which is unique across all slots in the skeleton. + public string Name { get { return name; } } + /// The bone this slot belongs to. + public BoneData BoneData { get { return boneData; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } - public float R2 { get { return r2; } set { r2 = value; } } - public float G2 { get { return g2; } set { g2 = value; } } - public float B2 { get { return b2; } set { b2 = value; } } - public bool HasSecondColor { get { return hasSecondColor; } set { hasSecondColor = value; } } + public float R2 { get { return r2; } set { r2 = value; } } + public float G2 { get { return g2; } set { g2 = value; } } + public float B2 { get { return b2; } set { b2 = value; } } + public bool HasSecondColor { get { return hasSecondColor; } set { hasSecondColor = value; } } - /// The name of the attachment that is visible for this slot in the setup pose, or null if no attachment is visible. - public String AttachmentName { get { return attachmentName; } set { attachmentName = value; } } - /// The blend mode for drawing the slot's attachment. - public BlendMode BlendMode { get { return blendMode; } set { blendMode = value; } } + /// The name of the attachment that is visible for this slot in the setup pose, or null if no attachment is visible. + public String AttachmentName { get { return attachmentName; } set { attachmentName = value; } } + /// The blend mode for drawing the slot's attachment. + public BlendMode BlendMode { get { return blendMode; } set { blendMode = value; } } - public SlotData (int index, String name, BoneData boneData) { - if (index < 0) throw new ArgumentException("index must be >= 0.", "index"); - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - if (boneData == null) throw new ArgumentNullException("boneData", "boneData cannot be null."); - this.index = index; - this.name = name; - this.boneData = boneData; - } + public SlotData(int index, String name, BoneData boneData) + { + if (index < 0) throw new ArgumentException("index must be >= 0.", "index"); + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + if (boneData == null) throw new ArgumentNullException("boneData", "boneData cannot be null."); + this.index = index; + this.name = name; + this.boneData = boneData; + } - override public string ToString () { - return name; - } - } + override public string ToString() + { + return name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/SpringConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/SpringConstraint.cs index bf2e5cf..17b17e9 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/SpringConstraint.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/SpringConstraint.cs @@ -29,77 +29,83 @@ using System; -namespace Spine4_1_00 { - /// - /// Stores the current pose for a spring constraint. A spring constraint applies physics to bones. - /// - /// See Spring constraints in the Spine User Guide. - /// - public class SpringConstraint : IUpdatable { - internal readonly SpringConstraintData data; - internal readonly ExposedList bones; - // BOZO! - stiffness -> strength. stiffness, damping, rope, stretch -> move to spring. - internal float mix, friction, gravity, wind, stiffness, damping; - internal bool rope, stretch; +namespace Spine4_1_00 +{ + /// + /// Stores the current pose for a spring constraint. A spring constraint applies physics to bones. + /// + /// See Spring constraints in the Spine User Guide. + /// + public class SpringConstraint : IUpdatable + { + internal readonly SpringConstraintData data; + internal readonly ExposedList bones; + // BOZO! - stiffness -> strength. stiffness, damping, rope, stretch -> move to spring. + internal float mix, friction, gravity, wind, stiffness, damping; + internal bool rope, stretch; - internal bool active; + internal bool active; - public SpringConstraint (SpringConstraintData data, Skeleton skeleton) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.data = data; - mix = data.mix; - friction = data.friction; - gravity = data.gravity; - wind = data.wind; - stiffness = data.stiffness; - damping = data.damping; - rope = data.rope; - stretch = data.stretch; + public SpringConstraint(SpringConstraintData data, Skeleton skeleton) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + mix = data.mix; + friction = data.friction; + gravity = data.gravity; + wind = data.wind; + stiffness = data.stiffness; + damping = data.damping; + rope = data.rope; + stretch = data.stretch; - bones = new ExposedList(data.Bones.Count); - foreach (BoneData boneData in data.bones) - bones.Add(skeleton.bones.Items[boneData.index]); - } + bones = new ExposedList(data.Bones.Count); + foreach (BoneData boneData in data.bones) + bones.Add(skeleton.bones.Items[boneData.index]); + } - /// Copy constructor. - public SpringConstraint (SpringConstraint constraint, Skeleton skeleton) { - if (constraint == null) throw new ArgumentNullException("constraint", "constraint cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - data = constraint.data; - bones = new ExposedList(constraint.bones.Count); - foreach (Bone bone in constraint.bones) - bones.Add(skeleton.bones.Items[bone.data.index]); - mix = constraint.mix; - friction = constraint.friction; - gravity = constraint.gravity; - wind = constraint.wind; - stiffness = constraint.stiffness; - damping = constraint.damping; - rope = constraint.rope; - stretch = constraint.stretch; - } + /// Copy constructor. + public SpringConstraint(SpringConstraint constraint, Skeleton skeleton) + { + if (constraint == null) throw new ArgumentNullException("constraint", "constraint cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + data = constraint.data; + bones = new ExposedList(constraint.bones.Count); + foreach (Bone bone in constraint.bones) + bones.Add(skeleton.bones.Items[bone.data.index]); + mix = constraint.mix; + friction = constraint.friction; + gravity = constraint.gravity; + wind = constraint.wind; + stiffness = constraint.stiffness; + damping = constraint.damping; + rope = constraint.rope; + stretch = constraint.stretch; + } - /// Applies the constraint to the constrained bones. - public void Update () { + /// Applies the constraint to the constrained bones. + public void Update() + { - } + } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained poses. - public float Mix { get { return mix; } set { mix = value; } } - public float Friction { get { return friction; } set { friction = value; } } - public float Gravity { get { return gravity; } set { gravity = value; } } - public float Wind { get { return wind; } set { wind = value; } } - public float Stiffness { get { return stiffness; } set { stiffness = value; } } - public float Damping { get { return damping; } set { damping = value; } } - public bool Rope { get { return rope; } set { rope = value; } } - public bool Stretch { get { return stretch; } set { stretch = value; } } - public bool Active { get { return active; } } - /// The spring constraint's setup pose data. - public SpringConstraintData Data { get { return data; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained poses. + public float Mix { get { return mix; } set { mix = value; } } + public float Friction { get { return friction; } set { friction = value; } } + public float Gravity { get { return gravity; } set { gravity = value; } } + public float Wind { get { return wind; } set { wind = value; } } + public float Stiffness { get { return stiffness; } set { stiffness = value; } } + public float Damping { get { return damping; } set { damping = value; } } + public bool Rope { get { return rope; } set { rope = value; } } + public bool Stretch { get { return stretch; } set { stretch = value; } } + public bool Active { get { return active; } } + /// The spring constraint's setup pose data. + public SpringConstraintData Data { get { return data; } } - override public string ToString () { - return data.name; - } - } + override public string ToString() + { + return data.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/SpringConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/SpringConstraintData.cs index f818db6..8777661 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/SpringConstraintData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/SpringConstraintData.cs @@ -27,33 +27,34 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; +namespace Spine4_1_00 +{ + /// + /// Stores the setup pose for a . + /// + /// See Spring constraints in the Spine User Guide. + /// + public class SpringConstraintData : ConstraintData + { + internal ExposedList bones = new ExposedList(); + internal float mix, friction, gravity, wind, stiffness, damping; + internal bool rope, stretch; -namespace Spine4_1_00 { - /// - /// Stores the setup pose for a . - /// - /// See Spring constraints in the Spine User Guide. - /// - public class SpringConstraintData : ConstraintData { - internal ExposedList bones = new ExposedList(); - internal float mix, friction, gravity, wind, stiffness, damping; - internal bool rope, stretch; + public SpringConstraintData(string name) : base(name) + { + } - public SpringConstraintData (string name) : base(name) { - } + /// The bones that are constrained by this spring constraint. + public ExposedList Bones { get { return bones; } } - /// The bones that are constrained by this spring constraint. - public ExposedList Bones { get { return bones; } } - - /// A percentage (0-1) that controls the mix between the constrained and unconstrained poses. - public float Mix { get { return mix; } set { mix = value; } } - public float Friction { get { return friction; } set { friction = value; } } - public float Gravity { get { return gravity; } set { gravity = value; } } - public float Wind { get { return wind; } set { wind = value; } } - public float Stiffness { get { return stiffness; } set { stiffness = value; } } - public float Damping { get { return damping; } set { damping = value; } } - public bool Rope { get { return rope; } set { rope = value; } } - public bool Stretch { get { return stretch; } set { stretch = value; } } - } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained poses. + public float Mix { get { return mix; } set { mix = value; } } + public float Friction { get { return friction; } set { friction = value; } } + public float Gravity { get { return gravity; } set { gravity = value; } } + public float Wind { get { return wind; } set { wind = value; } } + public float Stiffness { get { return stiffness; } set { stiffness = value; } } + public float Damping { get { return damping; } set { damping = value; } } + public bool Rope { get { return rope; } set { rope = value; } } + public bool Stretch { get { return stretch; } set { stretch = value; } } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/TextureRegion.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/TextureRegion.cs index 5fcb65d..4920784 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/TextureRegion.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/TextureRegion.cs @@ -31,19 +31,15 @@ #define IS_UNITY #endif -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Reflection; +namespace Spine4_1_00 +{ + public class TextureRegion + { + public int width, height; + public float u, v, u2, v2; -namespace Spine4_1_00 { - public class TextureRegion { - public int width, height; - public float u, v, u2, v2; - - virtual public int OriginalWidth { get { return width; } } - virtual public int OriginalHeight { get { return height; } } - } + virtual public int OriginalWidth { get { return width; } } + virtual public int OriginalHeight { get { return height; } } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/TransformConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/TransformConstraint.cs index 48d2be5..1bccdd8 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/TransformConstraint.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/TransformConstraint.cs @@ -29,283 +29,312 @@ using System; -namespace Spine4_1_00 { - /// - /// - /// Stores the current pose for a transform constraint. A transform constraint adjusts the world transform of the constrained - /// bones to match that of the target bone. - /// - /// See Transform constraints in the Spine User Guide. - /// - public class TransformConstraint : IUpdatable { - internal readonly TransformConstraintData data; - internal readonly ExposedList bones; - internal Bone target; - internal float mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY; - - internal bool active; - - public TransformConstraint (TransformConstraintData data, Skeleton skeleton) { - if (data == null) throw new ArgumentNullException("data", "data cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); - this.data = data; - mixRotate = data.mixRotate; - mixX = data.mixX; - mixY = data.mixY; - mixScaleX = data.mixScaleX; - mixScaleY = data.mixScaleY; - mixShearY = data.mixShearY; - bones = new ExposedList(); - foreach (BoneData boneData in data.bones) - bones.Add(skeleton.bones.Items[boneData.index]); - - target = skeleton.bones.Items[data.target.index]; - } - - /// Copy constructor. - public TransformConstraint (TransformConstraint constraint, Skeleton skeleton) { - if (constraint == null) throw new ArgumentNullException("constraint cannot be null."); - if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); - data = constraint.data; - bones = new ExposedList(constraint.Bones.Count); - foreach (Bone bone in constraint.Bones) - bones.Add(skeleton.Bones.Items[bone.data.index]); - target = skeleton.Bones.Items[constraint.target.data.index]; - mixRotate = constraint.mixRotate; - mixX = constraint.mixX; - mixY = constraint.mixY; - mixScaleX = constraint.mixScaleX; - mixScaleY = constraint.mixScaleY; - mixShearY = constraint.mixShearY; - } - - public void Update () { - if (mixRotate == 0 && mixX == 0 && mixY == 0 && mixScaleX == 0 && mixScaleX == 0 && mixShearY == 0) return; - if (data.local) { - if (data.relative) - ApplyRelativeLocal(); - else - ApplyAbsoluteLocal(); - } else { - if (data.relative) - ApplyRelativeWorld(); - else - ApplyAbsoluteWorld(); - } - } - - void ApplyAbsoluteWorld () { - float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX, - mixScaleY = this.mixScaleY, mixShearY = this.mixShearY; - bool translate = mixX != 0 || mixY != 0; - - Bone target = this.target; - float ta = target.a, tb = target.b, tc = target.c, td = target.d; - float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad; - float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect; - - var bones = this.bones.Items; - for (int i = 0, n = this.bones.Count; i < n; i++) { - Bone bone = bones[i]; - - if (mixRotate != 0) { - float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - float r = MathUtils.Atan2(tc, ta) - MathUtils.Atan2(c, a) + offsetRotation; - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) // - r += MathUtils.PI2; - r *= mixRotate; - float cos = MathUtils.Cos(r), sin = MathUtils.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; //Vector2 temp = this.temp; - target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY)); - bone.worldX += (tx - bone.worldX) * mixX; - bone.worldY += (ty - bone.worldY) * mixY; - } - - if (mixScaleX != 0) { - float s = (float)Math.Sqrt(bone.a * bone.a + bone.c * bone.c); - if (s != 0) s = (s + ((float)Math.Sqrt(ta * ta + tc * tc) - s + data.offsetScaleX) * mixScaleX) / s; - bone.a *= s; - bone.c *= s; - } - if (mixScaleY != 0) { - float s = (float)Math.Sqrt(bone.b * bone.b + bone.d * bone.d); - if (s != 0) s = (s + ((float)Math.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 = MathUtils.Atan2(d, b); - float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta) - (by - MathUtils.Atan2(bone.c, bone.a)); - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) // - r += MathUtils.PI2; - r = by + (r + offsetShearY) * mixShearY; - float s = (float)Math.Sqrt(b * b + d * d); - bone.b = MathUtils.Cos(r) * s; - bone.d = MathUtils.Sin(r) * s; - } - - bone.UpdateAppliedTransform(); - } - } - - void ApplyRelativeWorld () { - float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX, - mixScaleY = this.mixScaleY, mixShearY = this.mixShearY; - bool translate = mixX != 0 || mixY != 0; - - Bone target = this.target; - float ta = target.a, tb = target.b, tc = target.c, td = target.d; - float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad; - float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect; - - var bones = this.bones.Items; - for (int i = 0, n = this.bones.Count; i < n; i++) { - Bone bone = bones[i]; - - if (mixRotate != 0) { - float a = bone.a, b = bone.b, c = bone.c, d = bone.d; - float r = MathUtils.Atan2(tc, ta) + offsetRotation; - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) // - r += MathUtils.PI2; - r *= mixRotate; - float cos = MathUtils.Cos(r), sin = MathUtils.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; //Vector2 temp = this.temp; - target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY)); - bone.worldX += tx * mixX; - bone.worldY += ty * mixY; - } - - if (mixScaleX != 0) { - float s = ((float)Math.Sqrt(ta * ta + tc * tc) - 1 + data.offsetScaleX) * mixScaleX + 1; - bone.a *= s; - bone.c *= s; - } - if (mixScaleY != 0) { - float s = ((float)Math.Sqrt(tb * tb + td * td) - 1 + data.offsetScaleY) * mixScaleY + 1; - bone.b *= s; - bone.d *= s; - } - - if (mixShearY > 0) { - float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta); - if (r > MathUtils.PI) - r -= MathUtils.PI2; - else if (r < -MathUtils.PI) // - r += MathUtils.PI2; - float b = bone.b, d = bone.d; - r = MathUtils.Atan2(d, b) + (r - MathUtils.PI / 2 + offsetShearY) * mixShearY; - float s = (float)Math.Sqrt(b * b + d * d); - bone.b = MathUtils.Cos(r) * s; - bone.d = MathUtils.Sin(r) * s; - } - - bone.UpdateAppliedTransform(); - } - } - - void ApplyAbsoluteLocal () { - float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX, - mixScaleY = this.mixScaleY, mixShearY = this.mixShearY; - - Bone target = this.target; - - var bones = this.bones.Items; - for (int i = 0, n = this.bones.Count; i < n; i++) { - Bone bone = bones[i]; - - float rotation = bone.arotation; - if (mixRotate != 0) { - float r = target.arotation - rotation + data.offsetRotation; - r -= (16384 - (int)(16384.499999999996 - r / 360)) * 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 -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; - shearY += r * mixShearY; - } - - bone.UpdateWorldTransform(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY); - } - } - - void ApplyRelativeLocal () { - float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX, - mixScaleY = this.mixScaleY, mixShearY = this.mixShearY; - - Bone target = this.target; - - var bones = this.bones.Items; - for (int i = 0, n = this.bones.Count; i < n; i++) { - Bone bone = bones[i]; - - 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); - } - } - - /// The bones that will be modified by this transform constraint. - public ExposedList Bones { get { return bones; } } - /// The target bone whose world transform will be copied to the constrained bones. - public Bone Target { get { return target; } set { target = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. - public float MixRotate { get { return mixRotate; } set { mixRotate = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. - public float MixX { get { return mixX; } set { mixX = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. - public float MixY { get { return mixY; } set { mixY = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained scale X. - public float MixScaleX { get { return mixScaleX; } set { mixScaleX = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained scale Y. - public float MixScaleY { get { return mixScaleY; } set { mixScaleY = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained shear Y. - public float MixShearY { get { return mixShearY; } set { mixShearY = value; } } - public bool Active { get { return active; } } - /// The transform constraint's setup pose data. - public TransformConstraintData Data { get { return data; } } - - override public string ToString () { - return data.name; - } - } +namespace Spine4_1_00 +{ + /// + /// + /// Stores the current pose for a transform constraint. A transform constraint adjusts the world transform of the constrained + /// bones to match that of the target bone. + /// + /// See Transform constraints in the Spine User Guide. + /// + public class TransformConstraint : IUpdatable + { + internal readonly TransformConstraintData data; + internal readonly ExposedList bones; + internal Bone target; + internal float mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY; + + internal bool active; + + public TransformConstraint(TransformConstraintData data, Skeleton skeleton) + { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + mixRotate = data.mixRotate; + mixX = data.mixX; + mixY = data.mixY; + mixScaleX = data.mixScaleX; + mixScaleY = data.mixScaleY; + mixShearY = data.mixShearY; + bones = new ExposedList(); + foreach (BoneData boneData in data.bones) + bones.Add(skeleton.bones.Items[boneData.index]); + + target = skeleton.bones.Items[data.target.index]; + } + + /// Copy constructor. + public TransformConstraint(TransformConstraint constraint, Skeleton skeleton) + { + if (constraint == null) throw new ArgumentNullException("constraint cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null."); + data = constraint.data; + bones = new ExposedList(constraint.Bones.Count); + foreach (Bone bone in constraint.Bones) + bones.Add(skeleton.Bones.Items[bone.data.index]); + target = skeleton.Bones.Items[constraint.target.data.index]; + mixRotate = constraint.mixRotate; + mixX = constraint.mixX; + mixY = constraint.mixY; + mixScaleX = constraint.mixScaleX; + mixScaleY = constraint.mixScaleY; + mixShearY = constraint.mixShearY; + } + + public void Update() + { + if (mixRotate == 0 && mixX == 0 && mixY == 0 && mixScaleX == 0 && mixScaleX == 0 && mixShearY == 0) return; + if (data.local) + { + if (data.relative) + ApplyRelativeLocal(); + else + ApplyAbsoluteLocal(); + } + else + { + if (data.relative) + ApplyRelativeWorld(); + else + ApplyAbsoluteWorld(); + } + } + + void ApplyAbsoluteWorld() + { + float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX, + mixScaleY = this.mixScaleY, mixShearY = this.mixShearY; + bool translate = mixX != 0 || mixY != 0; + + Bone target = this.target; + float ta = target.a, tb = target.b, tc = target.c, td = target.d; + float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad; + float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect; + + var bones = this.bones.Items; + for (int i = 0, n = this.bones.Count; i < n; i++) + { + Bone bone = bones[i]; + + if (mixRotate != 0) + { + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + float r = MathUtils.Atan2(tc, ta) - MathUtils.Atan2(c, a) + offsetRotation; + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) // + r += MathUtils.PI2; + r *= mixRotate; + float cos = MathUtils.Cos(r), sin = MathUtils.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; //Vector2 temp = this.temp; + target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY)); + bone.worldX += (tx - bone.worldX) * mixX; + bone.worldY += (ty - bone.worldY) * mixY; + } + + if (mixScaleX != 0) + { + float s = (float)Math.Sqrt(bone.a * bone.a + bone.c * bone.c); + if (s != 0) s = (s + ((float)Math.Sqrt(ta * ta + tc * tc) - s + data.offsetScaleX) * mixScaleX) / s; + bone.a *= s; + bone.c *= s; + } + if (mixScaleY != 0) + { + float s = (float)Math.Sqrt(bone.b * bone.b + bone.d * bone.d); + if (s != 0) s = (s + ((float)Math.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 = MathUtils.Atan2(d, b); + float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta) - (by - MathUtils.Atan2(bone.c, bone.a)); + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) // + r += MathUtils.PI2; + r = by + (r + offsetShearY) * mixShearY; + float s = (float)Math.Sqrt(b * b + d * d); + bone.b = MathUtils.Cos(r) * s; + bone.d = MathUtils.Sin(r) * s; + } + + bone.UpdateAppliedTransform(); + } + } + + void ApplyRelativeWorld() + { + float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX, + mixScaleY = this.mixScaleY, mixShearY = this.mixShearY; + bool translate = mixX != 0 || mixY != 0; + + Bone target = this.target; + float ta = target.a, tb = target.b, tc = target.c, td = target.d; + float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad; + float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect; + + var bones = this.bones.Items; + for (int i = 0, n = this.bones.Count; i < n; i++) + { + Bone bone = bones[i]; + + if (mixRotate != 0) + { + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + float r = MathUtils.Atan2(tc, ta) + offsetRotation; + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) // + r += MathUtils.PI2; + r *= mixRotate; + float cos = MathUtils.Cos(r), sin = MathUtils.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; //Vector2 temp = this.temp; + target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY)); + bone.worldX += tx * mixX; + bone.worldY += ty * mixY; + } + + if (mixScaleX != 0) + { + float s = ((float)Math.Sqrt(ta * ta + tc * tc) - 1 + data.offsetScaleX) * mixScaleX + 1; + bone.a *= s; + bone.c *= s; + } + if (mixScaleY != 0) + { + float s = ((float)Math.Sqrt(tb * tb + td * td) - 1 + data.offsetScaleY) * mixScaleY + 1; + bone.b *= s; + bone.d *= s; + } + + if (mixShearY > 0) + { + float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta); + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) // + r += MathUtils.PI2; + float b = bone.b, d = bone.d; + r = MathUtils.Atan2(d, b) + (r - MathUtils.PI / 2 + offsetShearY) * mixShearY; + float s = (float)Math.Sqrt(b * b + d * d); + bone.b = MathUtils.Cos(r) * s; + bone.d = MathUtils.Sin(r) * s; + } + + bone.UpdateAppliedTransform(); + } + } + + void ApplyAbsoluteLocal() + { + float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX, + mixScaleY = this.mixScaleY, mixShearY = this.mixShearY; + + Bone target = this.target; + + var bones = this.bones.Items; + for (int i = 0, n = this.bones.Count; i < n; i++) + { + Bone bone = bones[i]; + + float rotation = bone.arotation; + if (mixRotate != 0) + { + float r = target.arotation - rotation + data.offsetRotation; + r -= (16384 - (int)(16384.499999999996 - r / 360)) * 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 -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; + shearY += r * mixShearY; + } + + bone.UpdateWorldTransform(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY); + } + } + + void ApplyRelativeLocal() + { + float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX, + mixScaleY = this.mixScaleY, mixShearY = this.mixShearY; + + Bone target = this.target; + + var bones = this.bones.Items; + for (int i = 0, n = this.bones.Count; i < n; i++) + { + Bone bone = bones[i]; + + 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); + } + } + + /// The bones that will be modified by this transform constraint. + public ExposedList Bones { get { return bones; } } + /// The target bone whose world transform will be copied to the constrained bones. + public Bone Target { get { return target; } set { target = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. + public float MixRotate { get { return mixRotate; } set { mixRotate = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. + public float MixX { get { return mixX; } set { mixX = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. + public float MixY { get { return mixY; } set { mixY = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained scale X. + public float MixScaleX { get { return mixScaleX; } set { mixScaleX = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained scale Y. + public float MixScaleY { get { return mixScaleY; } set { mixScaleY = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained shear Y. + public float MixShearY { get { return mixShearY; } set { mixShearY = value; } } + public bool Active { get { return active; } } + /// The transform constraint's setup pose data. + public TransformConstraintData Data { get { return data; } } + + override public string ToString() + { + return data.name; + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/TransformConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/TransformConstraintData.cs index 7f697d5..092c707 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/TransformConstraintData.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/TransformConstraintData.cs @@ -27,42 +27,43 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using System; +namespace Spine4_1_00 +{ + public class TransformConstraintData : ConstraintData + { + internal ExposedList bones = new ExposedList(); + internal BoneData target; + internal float mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY; + internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY; + internal bool relative, local; -namespace Spine4_1_00 { - public class TransformConstraintData : ConstraintData { - internal ExposedList bones = new ExposedList(); - internal BoneData target; - internal float mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY; - internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY; - internal bool relative, local; + public ExposedList Bones { get { return bones; } } + public BoneData Target { get { return target; } set { target = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. + public float MixRotate { get { return mixRotate; } set { mixRotate = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. + public float MixX { get { return mixX; } set { mixX = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. + public float MixY { get { return mixY; } set { mixY = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained scale X. + public float MixScaleX { get { return mixScaleX; } set { mixScaleX = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained scale Y. + public float MixScaleY { get { return mixScaleY; } set { mixScaleY = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained shear Y. + public float MixShearY { get { return mixShearY; } set { mixShearY = value; } } - public ExposedList Bones { get { return bones; } } - public BoneData Target { get { return target; } set { target = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. - public float MixRotate { get { return mixRotate; } set { mixRotate = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. - public float MixX { get { return mixX; } set { mixX = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. - public float MixY { get { return mixY; } set { mixY = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained scale X. - public float MixScaleX { get { return mixScaleX; } set { mixScaleX = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained scale Y. - public float MixScaleY { get { return mixScaleY; } set { mixScaleY = value; } } - /// A percentage (0-1) that controls the mix between the constrained and unconstrained shear Y. - public float MixShearY { get { return mixShearY; } set { mixShearY = value; } } + public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } + public float OffsetX { get { return offsetX; } set { offsetX = value; } } + public float OffsetY { get { return offsetY; } set { offsetY = value; } } + public float OffsetScaleX { get { return offsetScaleX; } set { offsetScaleX = value; } } + public float OffsetScaleY { get { return offsetScaleY; } set { offsetScaleY = value; } } + public float OffsetShearY { get { return offsetShearY; } set { offsetShearY = value; } } - public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } - public float OffsetX { get { return offsetX; } set { offsetX = value; } } - public float OffsetY { get { return offsetY; } set { offsetY = value; } } - public float OffsetScaleX { get { return offsetScaleX; } set { offsetScaleX = value; } } - public float OffsetScaleY { get { return offsetScaleY; } set { offsetScaleY = value; } } - public float OffsetShearY { get { return offsetShearY; } set { offsetShearY = value; } } + public bool Relative { get { return relative; } set { relative = value; } } + public bool Local { get { return local; } set { local = value; } } - public bool Relative { get { return relative; } set { relative = value; } } - public bool Local { get { return local; } set { local = value; } } - - public TransformConstraintData (string name) : base(name) { - } - } + public TransformConstraintData(string name) : base(name) + { + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Triangulator.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Triangulator.cs index 3917bd5..c47b422 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Triangulator.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/Triangulator.cs @@ -29,247 +29,276 @@ using System; -namespace Spine4_1_00 { - public class Triangulator { - private readonly ExposedList> convexPolygons = new ExposedList>(); - private readonly ExposedList> convexPolygonsIndices = new ExposedList>(); - - private readonly ExposedList indicesArray = new ExposedList(); - private readonly ExposedList isConcaveArray = new ExposedList(); - private readonly ExposedList triangles = new ExposedList(); - - private readonly Pool> polygonPool = new Pool>(); - private readonly Pool> polygonIndicesPool = new Pool>(); - - public ExposedList Triangulate (ExposedList verticesArray) { - var vertices = verticesArray.Items; - int vertexCount = verticesArray.Count >> 1; - - var indicesArray = this.indicesArray; - indicesArray.Clear(); - int[] indices = indicesArray.Resize(vertexCount).Items; - for (int i = 0; i < vertexCount; i++) - indices[i] = i; - - var isConcaveArray = this.isConcaveArray; - bool[] isConcave = isConcaveArray.Resize(vertexCount).Items; - for (int i = 0, n = vertexCount; i < n; ++i) - isConcave[i] = IsConcave(i, vertexCount, vertices, indices); - - var triangles = this.triangles; - triangles.Clear(); - triangles.EnsureCapacity(Math.Max(0, vertexCount - 2) << 2); - - while (vertexCount > 3) { - // Find ear tip. - int previous = vertexCount - 1, i = 0, next = 1; - - // outer: - while (true) { - if (!isConcave[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 (int ii = (next + 1) % vertexCount; ii != previous; ii = (ii + 1) % vertexCount) { - if (!isConcave[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 (!isConcave[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]); - indicesArray.RemoveAt(i); - isConcaveArray.RemoveAt(i); - vertexCount--; - - int previousIndex = (vertexCount + i - 1) % vertexCount; - int nextIndex = i == vertexCount ? 0 : i; - isConcave[previousIndex] = IsConcave(previousIndex, vertexCount, vertices, indices); - isConcave[nextIndex] = IsConcave(nextIndex, vertexCount, vertices, indices); - } - - if (vertexCount == 3) { - triangles.Add(indices[2]); - triangles.Add(indices[0]); - triangles.Add(indices[1]); - } - - return triangles; - } - - public ExposedList> Decompose (ExposedList verticesArray, ExposedList triangles) { - var vertices = verticesArray.Items; - var convexPolygons = this.convexPolygons; - for (int i = 0, n = convexPolygons.Count; i < n; i++) - polygonPool.Free(convexPolygons.Items[i]); - convexPolygons.Clear(); - - var convexPolygonsIndices = this.convexPolygonsIndices; - for (int i = 0, n = convexPolygonsIndices.Count; i < n; i++) - polygonIndicesPool.Free(convexPolygonsIndices.Items[i]); - convexPolygonsIndices.Clear(); - - var polygonIndices = polygonIndicesPool.Obtain(); - polygonIndices.Clear(); - - var polygon = polygonPool.Obtain(); - polygon.Clear(); - - // Merge subsequent triangles if they form a triangle fan. - int fanBaseIndex = -1, lastWinding = 0; - int[] trianglesItems = triangles.Items; - for (int i = 0, n = triangles.Count; i < n; i += 3) { - int t1 = trianglesItems[i] << 1, t2 = trianglesItems[i + 1] << 1, t3 = trianglesItems[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). - var merged = false; - if (fanBaseIndex == t1) { - int o = polygon.Count - 4; - float[] p = polygon.Items; - 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.Count > 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.Count > 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 (int i = 0, n = convexPolygons.Count; i < n; i++) { - polygonIndices = convexPolygonsIndices.Items[i]; - if (polygonIndices.Count == 0) continue; - int firstIndex = polygonIndices.Items[0]; - int lastIndex = polygonIndices.Items[polygonIndices.Count - 1]; - - polygon = convexPolygons.Items[i]; - int o = polygon.Count - 4; - float[] p = polygon.Items; - 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 winding = Winding(prevPrevX, prevPrevY, prevX, prevY, firstX, firstY); - - for (int ii = 0; ii < n; ii++) { - if (ii == i) continue; - var otherIndices = convexPolygonsIndices.Items[ii]; - if (otherIndices.Count != 3) continue; - int otherFirstIndex = otherIndices.Items[0]; - int otherSecondIndex = otherIndices.Items[1]; - int otherLastIndex = otherIndices.Items[2]; - - var otherPoly = convexPolygons.Items[ii]; - float x3 = otherPoly.Items[otherPoly.Count - 2], y3 = otherPoly.Items[otherPoly.Count - 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 == winding && winding2 == winding) { - 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 = convexPolygons.Count - 1; i >= 0; i--) { - polygon = convexPolygons.Items[i]; - if (polygon.Count == 0) { - convexPolygons.RemoveAt(i); - polygonPool.Free(polygon); - polygonIndices = convexPolygonsIndices.Items[i]; - convexPolygonsIndices.RemoveAt(i); - polygonIndicesPool.Free(polygonIndices); - } - } - - return convexPolygons; - } - - static private bool IsConcave (int index, int vertexCount, float[] vertices, int[] 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]); - } - - static private bool PositiveArea (float p1x, float p1y, float p2x, float p2y, float p3x, float p3y) { - return p1x * (p3y - p2y) + p2x * (p1y - p3y) + p3x * (p2y - p1y) >= 0; - } - - static private int 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; - } - } +namespace Spine4_1_00 +{ + public class Triangulator + { + private readonly ExposedList> convexPolygons = new ExposedList>(); + private readonly ExposedList> convexPolygonsIndices = new ExposedList>(); + + private readonly ExposedList indicesArray = new ExposedList(); + private readonly ExposedList isConcaveArray = new ExposedList(); + private readonly ExposedList triangles = new ExposedList(); + + private readonly Pool> polygonPool = new Pool>(); + private readonly Pool> polygonIndicesPool = new Pool>(); + + public ExposedList Triangulate(ExposedList verticesArray) + { + var vertices = verticesArray.Items; + int vertexCount = verticesArray.Count >> 1; + + var indicesArray = this.indicesArray; + indicesArray.Clear(); + int[] indices = indicesArray.Resize(vertexCount).Items; + for (int i = 0; i < vertexCount; i++) + indices[i] = i; + + var isConcaveArray = this.isConcaveArray; + bool[] isConcave = isConcaveArray.Resize(vertexCount).Items; + for (int i = 0, n = vertexCount; i < n; ++i) + isConcave[i] = IsConcave(i, vertexCount, vertices, indices); + + var triangles = this.triangles; + triangles.Clear(); + triangles.EnsureCapacity(Math.Max(0, vertexCount - 2) << 2); + + while (vertexCount > 3) + { + // Find ear tip. + int previous = vertexCount - 1, i = 0, next = 1; + + // outer: + while (true) + { + if (!isConcave[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 (int ii = (next + 1) % vertexCount; ii != previous; ii = (ii + 1) % vertexCount) + { + if (!isConcave[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 (!isConcave[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]); + indicesArray.RemoveAt(i); + isConcaveArray.RemoveAt(i); + vertexCount--; + + int previousIndex = (vertexCount + i - 1) % vertexCount; + int nextIndex = i == vertexCount ? 0 : i; + isConcave[previousIndex] = IsConcave(previousIndex, vertexCount, vertices, indices); + isConcave[nextIndex] = IsConcave(nextIndex, vertexCount, vertices, indices); + } + + if (vertexCount == 3) + { + triangles.Add(indices[2]); + triangles.Add(indices[0]); + triangles.Add(indices[1]); + } + + return triangles; + } + + public ExposedList> Decompose(ExposedList verticesArray, ExposedList triangles) + { + var vertices = verticesArray.Items; + var convexPolygons = this.convexPolygons; + for (int i = 0, n = convexPolygons.Count; i < n; i++) + polygonPool.Free(convexPolygons.Items[i]); + convexPolygons.Clear(); + + var convexPolygonsIndices = this.convexPolygonsIndices; + for (int i = 0, n = convexPolygonsIndices.Count; i < n; i++) + polygonIndicesPool.Free(convexPolygonsIndices.Items[i]); + convexPolygonsIndices.Clear(); + + var polygonIndices = polygonIndicesPool.Obtain(); + polygonIndices.Clear(); + + var polygon = polygonPool.Obtain(); + polygon.Clear(); + + // Merge subsequent triangles if they form a triangle fan. + int fanBaseIndex = -1, lastWinding = 0; + int[] trianglesItems = triangles.Items; + for (int i = 0, n = triangles.Count; i < n; i += 3) + { + int t1 = trianglesItems[i] << 1, t2 = trianglesItems[i + 1] << 1, t3 = trianglesItems[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). + var merged = false; + if (fanBaseIndex == t1) + { + int o = polygon.Count - 4; + float[] p = polygon.Items; + 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.Count > 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.Count > 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 (int i = 0, n = convexPolygons.Count; i < n; i++) + { + polygonIndices = convexPolygonsIndices.Items[i]; + if (polygonIndices.Count == 0) continue; + int firstIndex = polygonIndices.Items[0]; + int lastIndex = polygonIndices.Items[polygonIndices.Count - 1]; + + polygon = convexPolygons.Items[i]; + int o = polygon.Count - 4; + float[] p = polygon.Items; + 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 winding = Winding(prevPrevX, prevPrevY, prevX, prevY, firstX, firstY); + + for (int ii = 0; ii < n; ii++) + { + if (ii == i) continue; + var otherIndices = convexPolygonsIndices.Items[ii]; + if (otherIndices.Count != 3) continue; + int otherFirstIndex = otherIndices.Items[0]; + int otherSecondIndex = otherIndices.Items[1]; + int otherLastIndex = otherIndices.Items[2]; + + var otherPoly = convexPolygons.Items[ii]; + float x3 = otherPoly.Items[otherPoly.Count - 2], y3 = otherPoly.Items[otherPoly.Count - 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 == winding && winding2 == winding) + { + 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 = convexPolygons.Count - 1; i >= 0; i--) + { + polygon = convexPolygons.Items[i]; + if (polygon.Count == 0) + { + convexPolygons.RemoveAt(i); + polygonPool.Free(polygon); + polygonIndices = convexPolygonsIndices.Items[i]; + convexPolygonsIndices.RemoveAt(i); + polygonIndicesPool.Free(polygonIndices); + } + } + + return convexPolygons; + } + + static private bool IsConcave(int index, int vertexCount, float[] vertices, int[] 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]); + } + + static private bool PositiveArea(float p1x, float p1y, float p2x, float p2y, float p3x, float p3y) + { + return p1x * (p3y - p2y) + p2x * (p1y - p3y) + p3x * (p2y - p1y) >= 0; + } + + static private int 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/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/XnaLoader/MeshBatcher.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/XnaLoader/MeshBatcher.cs index 9a6c7f3..687062d 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/XnaLoader/MeshBatcher.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/XnaLoader/MeshBatcher.cs @@ -27,169 +27,185 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; -namespace Spine4_1_00 { - public struct VertexPositionColorTextureColor : IVertexType { - public Vector3 Position; - public Color Color; - public Vector2 TextureCoordinate; - public Color Color2; - - public readonly static VertexDeclaration VertexDeclaration = new VertexDeclaration - ( - new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0), - new VertexElement(12, VertexElementFormat.Color, VertexElementUsage.Color, 0), - new VertexElement(16, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0), - new VertexElement(24, VertexElementFormat.Color, VertexElementUsage.Color, 1) - ); - - VertexDeclaration IVertexType.VertexDeclaration { - get { return VertexDeclaration; } - } - } - - // #region License - // /* - // Microsoft Public License (Ms-PL) - // MonoGame - Copyright � 2009 The MonoGame Team - // - // All rights reserved. - // - // This license governs use of the accompanying software. If you use the software, you accept this license. If you do not - // accept the license, do not use the software. - // - // 1. Definitions - // The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under - // U.S. copyright law. - // - // A "contribution" is the original software, or any additions or changes to the software. - // A "contributor" is any person that distributes its contribution under this license. - // "Licensed patents" are a contributor's patent claims that read directly on its contribution. - // - // 2. Grant of Rights - // (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, - // each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. - // (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, - // each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. - // - // 3. Conditions and Limitations - // (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. - // (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, - // your patent license from such contributor to the software ends automatically. - // (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution - // notices that are present in the software. - // (D) If you distribute any portion of the software in source code form, you may do so only under this license by including - // a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object - // code form, you may only do so under a license that complies with this license. - // (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees - // or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent - // permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular - // purpose and non-infringement. - // */ - // #endregion License - // - - /// Draws batched meshes. - public class MeshBatcher { - private readonly List items; - private readonly Queue freeItems; - private VertexPositionColorTextureColor[] vertexArray = { }; - private short[] triangles = { }; - - public MeshBatcher () { - items = new List(256); - freeItems = new Queue(256); - EnsureCapacity(256, 512); - } - - /// Returns a pooled MeshItem. - public MeshItem NextItem (int vertexCount, int triangleCount) { - MeshItem item = freeItems.Count > 0 ? freeItems.Dequeue() : new MeshItem(); - if (item.vertices.Length < vertexCount) item.vertices = new VertexPositionColorTextureColor[vertexCount]; - if (item.triangles.Length < triangleCount) item.triangles = new int[triangleCount]; - item.vertexCount = vertexCount; - item.triangleCount = triangleCount; - items.Add(item); - return item; - } - - private void EnsureCapacity (int vertexCount, int triangleCount) { - if (vertexArray.Length < vertexCount) vertexArray = new VertexPositionColorTextureColor[vertexCount]; - if (triangles.Length < triangleCount) triangles = new short[triangleCount]; - } - - public void Draw (GraphicsDevice device) { - if (items.Count == 0) return; - - int itemCount = items.Count; - int vertexCount = 0, triangleCount = 0; - for (int i = 0; i < itemCount; i++) { - MeshItem item = items[i]; - vertexCount += item.vertexCount; - triangleCount += item.triangleCount; - } - EnsureCapacity(vertexCount, triangleCount); - - vertexCount = 0; - triangleCount = 0; - Texture2D lastTexture = null; - for (int i = 0; i < itemCount; i++) { - MeshItem item = items[i]; - int itemVertexCount = item.vertexCount; - - if (item.texture != lastTexture || vertexCount + itemVertexCount > short.MaxValue) { - FlushVertexArray(device, vertexCount, triangleCount); - vertexCount = 0; - triangleCount = 0; - lastTexture = item.texture; - device.Textures[0] = lastTexture; - if (item.textureLayers != null) { - for (int layer = 1; layer < item.textureLayers.Length; ++layer) - device.Textures[layer] = item.textureLayers[layer]; - } - } - - int[] itemTriangles = item.triangles; - int itemTriangleCount = item.triangleCount; - for (int ii = 0, t = triangleCount; ii < itemTriangleCount; ii++, t++) - triangles[t] = (short)(itemTriangles[ii] + vertexCount); - triangleCount += itemTriangleCount; - - Array.Copy(item.vertices, 0, vertexArray, vertexCount, itemVertexCount); - vertexCount += itemVertexCount; - } - FlushVertexArray(device, vertexCount, triangleCount); - } - - public void AfterLastDrawPass () { - int itemCount = items.Count; - for (int i = 0; i < itemCount; i++) { - var item = items[i]; - item.texture = null; - freeItems.Enqueue(item); - } - items.Clear(); - } - - private void FlushVertexArray (GraphicsDevice device, int vertexCount, int triangleCount) { - if (vertexCount == 0) return; - device.DrawUserIndexedPrimitives( - PrimitiveType.TriangleList, - vertexArray, 0, vertexCount, - triangles, 0, triangleCount / 3, - VertexPositionColorTextureColor.VertexDeclaration); - } - } - - public class MeshItem { - public Texture2D texture = null; - public Texture2D[] textureLayers = null; - public int vertexCount, triangleCount; - public VertexPositionColorTextureColor[] vertices = { }; - public int[] triangles = { }; - } +namespace Spine4_1_00 +{ + public struct VertexPositionColorTextureColor : IVertexType + { + public Vector3 Position; + public Color Color; + public Vector2 TextureCoordinate; + public Color Color2; + + public readonly static VertexDeclaration VertexDeclaration = new VertexDeclaration + ( + new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0), + new VertexElement(12, VertexElementFormat.Color, VertexElementUsage.Color, 0), + new VertexElement(16, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0), + new VertexElement(24, VertexElementFormat.Color, VertexElementUsage.Color, 1) + ); + + VertexDeclaration IVertexType.VertexDeclaration + { + get { return VertexDeclaration; } + } + } + + // #region License + // /* + // Microsoft Public License (Ms-PL) + // MonoGame - Copyright � 2009 The MonoGame Team + // + // All rights reserved. + // + // This license governs use of the accompanying software. If you use the software, you accept this license. If you do not + // accept the license, do not use the software. + // + // 1. Definitions + // The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under + // U.S. copyright law. + // + // A "contribution" is the original software, or any additions or changes to the software. + // A "contributor" is any person that distributes its contribution under this license. + // "Licensed patents" are a contributor's patent claims that read directly on its contribution. + // + // 2. Grant of Rights + // (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, + // each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. + // (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, + // each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. + // + // 3. Conditions and Limitations + // (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. + // (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, + // your patent license from such contributor to the software ends automatically. + // (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution + // notices that are present in the software. + // (D) If you distribute any portion of the software in source code form, you may do so only under this license by including + // a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object + // code form, you may only do so under a license that complies with this license. + // (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees + // or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent + // permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular + // purpose and non-infringement. + // */ + // #endregion License + // + + /// Draws batched meshes. + public class MeshBatcher + { + private readonly List items; + private readonly Queue freeItems; + private VertexPositionColorTextureColor[] vertexArray = { }; + private short[] triangles = { }; + + public MeshBatcher() + { + items = new List(256); + freeItems = new Queue(256); + EnsureCapacity(256, 512); + } + + /// Returns a pooled MeshItem. + public MeshItem NextItem(int vertexCount, int triangleCount) + { + MeshItem item = freeItems.Count > 0 ? freeItems.Dequeue() : new MeshItem(); + if (item.vertices.Length < vertexCount) item.vertices = new VertexPositionColorTextureColor[vertexCount]; + if (item.triangles.Length < triangleCount) item.triangles = new int[triangleCount]; + item.vertexCount = vertexCount; + item.triangleCount = triangleCount; + items.Add(item); + return item; + } + + private void EnsureCapacity(int vertexCount, int triangleCount) + { + if (vertexArray.Length < vertexCount) vertexArray = new VertexPositionColorTextureColor[vertexCount]; + if (triangles.Length < triangleCount) triangles = new short[triangleCount]; + } + + public void Draw(GraphicsDevice device) + { + if (items.Count == 0) return; + + int itemCount = items.Count; + int vertexCount = 0, triangleCount = 0; + for (int i = 0; i < itemCount; i++) + { + MeshItem item = items[i]; + vertexCount += item.vertexCount; + triangleCount += item.triangleCount; + } + EnsureCapacity(vertexCount, triangleCount); + + vertexCount = 0; + triangleCount = 0; + Texture2D lastTexture = null; + for (int i = 0; i < itemCount; i++) + { + MeshItem item = items[i]; + int itemVertexCount = item.vertexCount; + + if (item.texture != lastTexture || vertexCount + itemVertexCount > short.MaxValue) + { + FlushVertexArray(device, vertexCount, triangleCount); + vertexCount = 0; + triangleCount = 0; + lastTexture = item.texture; + device.Textures[0] = lastTexture; + if (item.textureLayers != null) + { + for (int layer = 1; layer < item.textureLayers.Length; ++layer) + device.Textures[layer] = item.textureLayers[layer]; + } + } + + int[] itemTriangles = item.triangles; + int itemTriangleCount = item.triangleCount; + for (int ii = 0, t = triangleCount; ii < itemTriangleCount; ii++, t++) + triangles[t] = (short)(itemTriangles[ii] + vertexCount); + triangleCount += itemTriangleCount; + + Array.Copy(item.vertices, 0, vertexArray, vertexCount, itemVertexCount); + vertexCount += itemVertexCount; + } + FlushVertexArray(device, vertexCount, triangleCount); + } + + public void AfterLastDrawPass() + { + int itemCount = items.Count; + for (int i = 0; i < itemCount; i++) + { + var item = items[i]; + item.texture = null; + freeItems.Enqueue(item); + } + items.Clear(); + } + + private void FlushVertexArray(GraphicsDevice device, int vertexCount, int triangleCount) + { + if (vertexCount == 0) return; + device.DrawUserIndexedPrimitives( + PrimitiveType.TriangleList, + vertexArray, 0, vertexCount, + triangles, 0, triangleCount / 3, + VertexPositionColorTextureColor.VertexDeclaration); + } + } + + public class MeshItem + { + public Texture2D texture = null; + public Texture2D[] textureLayers = null; + public int vertexCount, triangleCount; + public VertexPositionColorTextureColor[] vertices = { }; + public int[] triangles = { }; + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/XnaLoader/ShapeRenderer.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/XnaLoader/ShapeRenderer.cs index c0aa52c..2821d87 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/XnaLoader/ShapeRenderer.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/XnaLoader/ShapeRenderer.cs @@ -27,139 +27,156 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Spine4_1_00 { - /// - /// Batch drawing of lines and shapes that can be derived from lines. - /// - /// Call drawing methods in between Begin()/End() - /// - public class ShapeRenderer { - GraphicsDevice device; - List vertices = new List(); - Color color = Color.White; - BasicEffect effect; - public BasicEffect Effect { get { return effect; } set { effect = value; } } - - public ShapeRenderer (GraphicsDevice device) { - this.device = device; - this.effect = new BasicEffect(device); - effect.World = Matrix.Identity; - effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); - effect.TextureEnabled = false; - effect.VertexColorEnabled = true; - } - - public void SetColor (Color color) { - this.color = color; - } - - public void Begin () { - device.RasterizerState = new RasterizerState(); - device.BlendState = BlendState.AlphaBlend; - } - - public void Line (float x1, float y1, float x2, float y2) { - vertices.Add(new VertexPositionColor(new Vector3(x1, y1, 0), color)); - vertices.Add(new VertexPositionColor(new Vector3(x2, y2, 0), color)); - } - - /** Calls {@link #circle(float, float, float, int)} by estimating the number of segments needed for a smooth circle. */ - public void Circle (float x, float y, float radius) { - Circle(x, y, radius, Math.Max(1, (int)(6 * (float)Math.Pow(radius, 1.0f / 3.0f)))); - } - - /** Draws a circle using {@link ShapeType#Line} or {@link ShapeType#Filled}. */ - public void Circle (float x, float y, float radius, int segments) { - if (segments <= 0) throw new ArgumentException("segments must be > 0."); - float angle = 2 * MathUtils.PI / segments; - float cos = MathUtils.Cos(angle); - float sin = MathUtils.Sin(angle); - float cx = radius, cy = 0; - float temp = 0; - - for (int i = 0; i < segments; i++) { - vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); - temp = cx; - cx = cos * cx - sin * cy; - cy = sin * temp + cos * cy; - vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); - } - vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); - - temp = cx; - cx = radius; - cy = 0; - vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); - } - - public void Triangle (float x1, float y1, float x2, float y2, float x3, float y3) { - vertices.Add(new VertexPositionColor(new Vector3(x1, y1, 0), color)); - vertices.Add(new VertexPositionColor(new Vector3(x2, y2, 0), color)); - - vertices.Add(new VertexPositionColor(new Vector3(x2, y2, 0), color)); - vertices.Add(new VertexPositionColor(new Vector3(x3, y3, 0), color)); - - vertices.Add(new VertexPositionColor(new Vector3(x3, y3, 0), color)); - vertices.Add(new VertexPositionColor(new Vector3(x1, y1, 0), color)); - } - - public void X (float x, float y, float len) { - Line(x + len, y + len, x - len, y - len); - Line(x - len, y + len, x + len, y - len); - } - - public void Polygon (float[] polygonVertices, int offset, int count) { - if (count < 3) throw new ArgumentException("Polygon must contain at least 3 vertices"); - - offset <<= 1; - - var firstX = polygonVertices[offset]; - var firstY = polygonVertices[offset + 1]; - var last = offset + count; - - for (int i = offset, n = offset + count; i < n; i += 2) { - var x1 = polygonVertices[i]; - var y1 = polygonVertices[i + 1]; - - var x2 = 0f; - var y2 = 0f; - - if (i + 2 >= last) { - x2 = firstX; - y2 = firstY; - } else { - x2 = polygonVertices[i + 2]; - y2 = polygonVertices[i + 3]; - } - - Line(x1, y1, x2, y2); - } - } - - public void Rect (float x, float y, float width, float height) { - Line(x, y, x + width, y); - Line(x + width, y, x + width, y + height); - Line(x + width, y + height, x, y + height); - Line(x, y + height, x, y); - } - - public void End () { - if (vertices.Count == 0) return; - var verticesArray = vertices.ToArray(); - - foreach (EffectPass pass in effect.CurrentTechnique.Passes) { - pass.Apply(); - device.DrawUserPrimitives(PrimitiveType.LineList, verticesArray, 0, verticesArray.Length / 2); - } - - vertices.Clear(); - } - } +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Spine4_1_00 +{ + /// + /// Batch drawing of lines and shapes that can be derived from lines. + /// + /// Call drawing methods in between Begin()/End() + /// + public class ShapeRenderer + { + GraphicsDevice device; + List vertices = new List(); + Color color = Color.White; + BasicEffect effect; + public BasicEffect Effect { get { return effect; } set { effect = value; } } + + public ShapeRenderer(GraphicsDevice device) + { + this.device = device; + this.effect = new BasicEffect(device); + effect.World = Matrix.Identity; + effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); + effect.TextureEnabled = false; + effect.VertexColorEnabled = true; + } + + public void SetColor(Color color) + { + this.color = color; + } + + public void Begin() + { + device.RasterizerState = new RasterizerState(); + device.BlendState = BlendState.AlphaBlend; + } + + public void Line(float x1, float y1, float x2, float y2) + { + vertices.Add(new VertexPositionColor(new Vector3(x1, y1, 0), color)); + vertices.Add(new VertexPositionColor(new Vector3(x2, y2, 0), color)); + } + + /** Calls {@link #circle(float, float, float, int)} by estimating the number of segments needed for a smooth circle. */ + public void Circle(float x, float y, float radius) + { + Circle(x, y, radius, Math.Max(1, (int)(6 * (float)Math.Pow(radius, 1.0f / 3.0f)))); + } + + /** Draws a circle using {@link ShapeType#Line} or {@link ShapeType#Filled}. */ + public void Circle(float x, float y, float radius, int segments) + { + if (segments <= 0) throw new ArgumentException("segments must be > 0."); + float angle = 2 * MathUtils.PI / segments; + float cos = MathUtils.Cos(angle); + float sin = MathUtils.Sin(angle); + float cx = radius, cy = 0; + float temp = 0; + + for (int i = 0; i < segments; i++) + { + vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); + temp = cx; + cx = cos * cx - sin * cy; + cy = sin * temp + cos * cy; + vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); + } + vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); + + temp = cx; + cx = radius; + cy = 0; + vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, 0), color)); + } + + public void Triangle(float x1, float y1, float x2, float y2, float x3, float y3) + { + vertices.Add(new VertexPositionColor(new Vector3(x1, y1, 0), color)); + vertices.Add(new VertexPositionColor(new Vector3(x2, y2, 0), color)); + + vertices.Add(new VertexPositionColor(new Vector3(x2, y2, 0), color)); + vertices.Add(new VertexPositionColor(new Vector3(x3, y3, 0), color)); + + vertices.Add(new VertexPositionColor(new Vector3(x3, y3, 0), color)); + vertices.Add(new VertexPositionColor(new Vector3(x1, y1, 0), color)); + } + + public void X(float x, float y, float len) + { + Line(x + len, y + len, x - len, y - len); + Line(x - len, y + len, x + len, y - len); + } + + public void Polygon(float[] polygonVertices, int offset, int count) + { + if (count < 3) throw new ArgumentException("Polygon must contain at least 3 vertices"); + + offset <<= 1; + + var firstX = polygonVertices[offset]; + var firstY = polygonVertices[offset + 1]; + var last = offset + count; + + for (int i = offset, n = offset + count; i < n; i += 2) + { + var x1 = polygonVertices[i]; + var y1 = polygonVertices[i + 1]; + + var x2 = 0f; + var y2 = 0f; + + if (i + 2 >= last) + { + x2 = firstX; + y2 = firstY; + } + else + { + x2 = polygonVertices[i + 2]; + y2 = polygonVertices[i + 3]; + } + + Line(x1, y1, x2, y2); + } + } + + public void Rect(float x, float y, float width, float height) + { + Line(x, y, x + width, y); + Line(x + width, y, x + width, y + height); + Line(x + width, y + height, x, y + height); + Line(x, y + height, x, y); + } + + public void End() + { + if (vertices.Count == 0) return; + var verticesArray = vertices.ToArray(); + + foreach (EffectPass pass in effect.CurrentTechnique.Passes) + { + pass.Apply(); + device.DrawUserPrimitives(PrimitiveType.LineList, verticesArray, 0, verticesArray.Length / 2); + } + + vertices.Clear(); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/XnaLoader/SkeletonRenderer.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/XnaLoader/SkeletonRenderer.cs index 789a677..3bdcda7 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/XnaLoader/SkeletonRenderer.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/XnaLoader/SkeletonRenderer.cs @@ -29,128 +29,141 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; -using System; -using System.Collections.Generic; -namespace Spine4_1_00 { - /// Draws region and mesh attachments. - public class SkeletonRenderer { - private const int TL = 0; - private const int TR = 1; - private const int BL = 2; - private const int BR = 3; +namespace Spine4_1_00 +{ + /// Draws region and mesh attachments. + public class SkeletonRenderer + { + private const int TL = 0; + private const int TR = 1; + private const int BL = 2; + private const int BR = 3; - SkeletonClipping clipper = new SkeletonClipping(); - GraphicsDevice device; - MeshBatcher batcher; - public MeshBatcher Batcher { get { return batcher; } } - RasterizerState rasterizerState; - float[] vertices = new float[8]; - int[] quadTriangles = { 0, 1, 2, 2, 3, 0 }; - BlendState defaultBlendState; + SkeletonClipping clipper = new SkeletonClipping(); + GraphicsDevice device; + MeshBatcher batcher; + public MeshBatcher Batcher { get { return batcher; } } + RasterizerState rasterizerState; + float[] vertices = new float[8]; + int[] quadTriangles = { 0, 1, 2, 2, 3, 0 }; + BlendState defaultBlendState; - Effect effect; - public Effect Effect { get { return effect; } set { effect = value; } } - public IVertexEffect VertexEffect { get; set; } + Effect effect; + public Effect Effect { get { return effect; } set { effect = value; } } + public IVertexEffect VertexEffect { get; set; } - private bool premultipliedAlpha; - public bool PremultipliedAlpha { get { return premultipliedAlpha; } set { premultipliedAlpha = value; } } + private bool premultipliedAlpha; + public bool PremultipliedAlpha { get { return premultipliedAlpha; } set { premultipliedAlpha = value; } } - /// Attachments are rendered back to front in the x/y plane by the SkeletonRenderer. - /// Each attachment is offset by a customizable z-spacing value on the z-axis to avoid z-fighting - /// in shaders with ZWrite enabled. Typical values lie in the range [-0.1, 0]. - private float zSpacing = 0.0f; - public float ZSpacing { get { return zSpacing; } set { zSpacing = value; } } + /// Attachments are rendered back to front in the x/y plane by the SkeletonRenderer. + /// Each attachment is offset by a customizable z-spacing value on the z-axis to avoid z-fighting + /// in shaders with ZWrite enabled. Typical values lie in the range [-0.1, 0]. + private float zSpacing = 0.0f; + public float ZSpacing { get { return zSpacing; } set { zSpacing = value; } } - /// A Z position offset added at each vertex. - private float z = 0.0f; - public float Z { get { return z; } set { z = value; } } + /// A Z position offset added at each vertex. + private float z = 0.0f; + public float Z { get { return z; } set { z = value; } } - public SkeletonRenderer (GraphicsDevice device) { - this.device = device; + public SkeletonRenderer(GraphicsDevice device) + { + this.device = device; - batcher = new MeshBatcher(); + batcher = new MeshBatcher(); - var basicEffect = new BasicEffect(device); - basicEffect.World = Matrix.Identity; - basicEffect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); - basicEffect.TextureEnabled = true; - basicEffect.VertexColorEnabled = true; - effect = basicEffect; + var basicEffect = new BasicEffect(device); + basicEffect.World = Matrix.Identity; + basicEffect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); + basicEffect.TextureEnabled = true; + basicEffect.VertexColorEnabled = true; + effect = basicEffect; - rasterizerState = new RasterizerState(); - rasterizerState.CullMode = CullMode.None; + rasterizerState = new RasterizerState(); + rasterizerState.CullMode = CullMode.None; - Bone.yDown = true; - } + Bone.yDown = true; + } - public void Begin () { - defaultBlendState = premultipliedAlpha ? BlendState.AlphaBlend : BlendState.NonPremultiplied; + public void Begin() + { + defaultBlendState = premultipliedAlpha ? BlendState.AlphaBlend : BlendState.NonPremultiplied; - device.RasterizerState = rasterizerState; - device.BlendState = defaultBlendState; - } + device.RasterizerState = rasterizerState; + device.BlendState = defaultBlendState; + } - public void End () { - foreach (EffectPass pass in effect.CurrentTechnique.Passes) { - pass.Apply(); - batcher.Draw(device); - } - batcher.AfterLastDrawPass(); - } + public void End() + { + foreach (EffectPass pass in effect.CurrentTechnique.Passes) + { + pass.Apply(); + batcher.Draw(device); + } + batcher.AfterLastDrawPass(); + } - public void Draw (Skeleton skeleton) { - var drawOrder = skeleton.DrawOrder; - var drawOrderItems = skeleton.DrawOrder.Items; - float skeletonR = skeleton.R, skeletonG = skeleton.G, skeletonB = skeleton.B, skeletonA = skeleton.A; - Color color = new Color(); + public void Draw(Skeleton skeleton) + { + var drawOrder = skeleton.DrawOrder; + var drawOrderItems = skeleton.DrawOrder.Items; + float skeletonR = skeleton.R, skeletonG = skeleton.G, skeletonB = skeleton.B, skeletonA = skeleton.A; + Color color = new Color(); - if (VertexEffect != null) VertexEffect.Begin(skeleton); + if (VertexEffect != null) VertexEffect.Begin(skeleton); - for (int i = 0, n = drawOrder.Count; i < n; i++) { - Slot slot = drawOrderItems[i]; - Attachment attachment = slot.Attachment; - float attachmentZOffset = z + zSpacing * i; + for (int i = 0, n = drawOrder.Count; i < n; i++) + { + Slot slot = drawOrderItems[i]; + Attachment attachment = slot.Attachment; + float attachmentZOffset = z + zSpacing * i; - float attachmentColorR, attachmentColorG, attachmentColorB, attachmentColorA; - object textureObject = null; - int verticesCount = 0; - float[] vertices = this.vertices; - int indicesCount = 0; - int[] indices = null; - float[] uvs = null; + float attachmentColorR, attachmentColorG, attachmentColorB, attachmentColorA; + object textureObject = null; + int verticesCount = 0; + float[] vertices = this.vertices; + int indicesCount = 0; + int[] indices = null; + float[] uvs = null; - if (attachment is RegionAttachment) { - RegionAttachment regionAttachment = (RegionAttachment)attachment; - attachmentColorR = regionAttachment.R; attachmentColorG = regionAttachment.G; attachmentColorB = regionAttachment.B; attachmentColorA = regionAttachment.A; - AtlasRegion region = (AtlasRegion)regionAttachment.Region; - textureObject = region.page.rendererObject; - verticesCount = 4; - regionAttachment.ComputeWorldVertices(slot, vertices, 0, 2); - indicesCount = 6; - indices = quadTriangles; - uvs = regionAttachment.UVs; - } else if (attachment is MeshAttachment) { - MeshAttachment mesh = (MeshAttachment)attachment; - attachmentColorR = mesh.R; attachmentColorG = mesh.G; attachmentColorB = mesh.B; attachmentColorA = mesh.A; - AtlasRegion region = (AtlasRegion)mesh.Region; - textureObject = region.page.rendererObject; - int vertexCount = mesh.WorldVerticesLength; - if (vertices.Length < vertexCount) vertices = new float[vertexCount]; - verticesCount = vertexCount >> 1; - mesh.ComputeWorldVertices(slot, vertices); - indicesCount = mesh.Triangles.Length; - indices = mesh.Triangles; - uvs = mesh.UVs; - } else if (attachment is ClippingAttachment) { - ClippingAttachment clip = (ClippingAttachment)attachment; - clipper.ClipStart(slot, clip); - continue; - } else { - continue; - } + if (attachment is RegionAttachment) + { + RegionAttachment regionAttachment = (RegionAttachment)attachment; + attachmentColorR = regionAttachment.R; attachmentColorG = regionAttachment.G; attachmentColorB = regionAttachment.B; attachmentColorA = regionAttachment.A; + AtlasRegion region = (AtlasRegion)regionAttachment.Region; + textureObject = region.page.rendererObject; + verticesCount = 4; + regionAttachment.ComputeWorldVertices(slot, vertices, 0, 2); + indicesCount = 6; + indices = quadTriangles; + uvs = regionAttachment.UVs; + } + else if (attachment is MeshAttachment) + { + MeshAttachment mesh = (MeshAttachment)attachment; + attachmentColorR = mesh.R; attachmentColorG = mesh.G; attachmentColorB = mesh.B; attachmentColorA = mesh.A; + AtlasRegion region = (AtlasRegion)mesh.Region; + textureObject = region.page.rendererObject; + int vertexCount = mesh.WorldVerticesLength; + if (vertices.Length < vertexCount) vertices = new float[vertexCount]; + verticesCount = vertexCount >> 1; + mesh.ComputeWorldVertices(slot, vertices); + indicesCount = mesh.Triangles.Length; + indices = mesh.Triangles; + uvs = mesh.UVs; + } + else if (attachment is ClippingAttachment) + { + ClippingAttachment clip = (ClippingAttachment)attachment; + clipper.ClipStart(slot, clip); + continue; + } + else + { + continue; + } - // set blend state + // set blend state BlendState blendState = new BlendState(); Blend blendSrc; Blend blendDst; @@ -202,75 +215,87 @@ public void Draw (Skeleton skeleton) { break; } - if (device.BlendState != blendState) { + if (device.BlendState != blendState) + { End(); device.BlendState = blendState; - } + } - // calculate color - float a = skeletonA * slot.A * attachmentColorA; - if (premultipliedAlpha) { - color = new Color( - skeletonR * slot.R * attachmentColorR * a, - skeletonG * slot.G * attachmentColorG * a, - skeletonB * slot.B * attachmentColorB * a, a); - } else { - color = new Color( - skeletonR * slot.R * attachmentColorR, - skeletonG * slot.G * attachmentColorG, - skeletonB * slot.B * attachmentColorB, a); - } + // calculate color + float a = skeletonA * slot.A * attachmentColorA; + if (premultipliedAlpha) + { + color = new Color( + skeletonR * slot.R * attachmentColorR * a, + skeletonG * slot.G * attachmentColorG * a, + skeletonB * slot.B * attachmentColorB * a, a); + } + else + { + color = new Color( + skeletonR * slot.R * attachmentColorR, + skeletonG * slot.G * attachmentColorG, + skeletonB * slot.B * attachmentColorB, a); + } - Color darkColor = new Color(); - if (slot.HasSecondColor) { - if (premultipliedAlpha) { - darkColor = new Color(slot.R2 * a, slot.G2 * a, slot.B2 * a); - } else { - darkColor = new Color(slot.R2 * a, slot.G2 * a, slot.B2 * a); - } - } - darkColor.A = premultipliedAlpha ? (byte)255 : (byte)0; + Color darkColor = new Color(); + if (slot.HasSecondColor) + { + if (premultipliedAlpha) + { + darkColor = new Color(slot.R2 * a, slot.G2 * a, slot.B2 * a); + } + else + { + darkColor = new Color(slot.R2 * a, slot.G2 * a, slot.B2 * a); + } + } + darkColor.A = premultipliedAlpha ? (byte)255 : (byte)0; - // clip - if (clipper.IsClipping) { - clipper.ClipTriangles(vertices, verticesCount << 1, indices, indicesCount, uvs); - vertices = clipper.ClippedVertices.Items; - verticesCount = clipper.ClippedVertices.Count >> 1; - indices = clipper.ClippedTriangles.Items; - indicesCount = clipper.ClippedTriangles.Count; - uvs = clipper.ClippedUVs.Items; - } + // clip + if (clipper.IsClipping) + { + clipper.ClipTriangles(vertices, verticesCount << 1, indices, indicesCount, uvs); + vertices = clipper.ClippedVertices.Items; + verticesCount = clipper.ClippedVertices.Count >> 1; + indices = clipper.ClippedTriangles.Items; + indicesCount = clipper.ClippedTriangles.Count; + uvs = clipper.ClippedUVs.Items; + } - if (verticesCount == 0 || indicesCount == 0) - continue; + if (verticesCount == 0 || indicesCount == 0) + continue; - // submit to batch - MeshItem item = batcher.NextItem(verticesCount, indicesCount); - if (textureObject is Texture2D) - item.texture = (Texture2D)textureObject; - else { - item.textureLayers = (Texture2D[])textureObject; - item.texture = item.textureLayers[0]; - } - for (int ii = 0, nn = indicesCount; ii < nn; ii++) { - item.triangles[ii] = indices[ii]; - } - VertexPositionColorTextureColor[] itemVertices = item.vertices; - for (int ii = 0, v = 0, nn = verticesCount << 1; v < nn; ii++, v += 2) { - itemVertices[ii].Color = color; - itemVertices[ii].Color2 = darkColor; - itemVertices[ii].Position.X = vertices[v]; - itemVertices[ii].Position.Y = vertices[v + 1]; - itemVertices[ii].Position.Z = attachmentZOffset; - itemVertices[ii].TextureCoordinate.X = uvs[v]; - itemVertices[ii].TextureCoordinate.Y = uvs[v + 1]; - if (VertexEffect != null) VertexEffect.Transform(ref itemVertices[ii]); - } + // submit to batch + MeshItem item = batcher.NextItem(verticesCount, indicesCount); + if (textureObject is Texture2D) + item.texture = (Texture2D)textureObject; + else + { + item.textureLayers = (Texture2D[])textureObject; + item.texture = item.textureLayers[0]; + } + for (int ii = 0, nn = indicesCount; ii < nn; ii++) + { + item.triangles[ii] = indices[ii]; + } + VertexPositionColorTextureColor[] itemVertices = item.vertices; + for (int ii = 0, v = 0, nn = verticesCount << 1; v < nn; ii++, v += 2) + { + itemVertices[ii].Color = color; + itemVertices[ii].Color2 = darkColor; + itemVertices[ii].Position.X = vertices[v]; + itemVertices[ii].Position.Y = vertices[v + 1]; + itemVertices[ii].Position.Z = attachmentZOffset; + itemVertices[ii].TextureCoordinate.X = uvs[v]; + itemVertices[ii].TextureCoordinate.Y = uvs[v + 1]; + if (VertexEffect != null) VertexEffect.Transform(ref itemVertices[ii]); + } - clipper.ClipEnd(slot); - } - clipper.ClipEnd(); - if (VertexEffect != null) VertexEffect.End(); - } - } + clipper.ClipEnd(slot); + } + clipper.ClipEnd(); + if (VertexEffect != null) VertexEffect.End(); + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/XnaLoader/VertexEffect.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/XnaLoader/VertexEffect.cs index edc7f58..3e4801a 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/XnaLoader/VertexEffect.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/XnaLoader/VertexEffect.cs @@ -28,70 +28,80 @@ *****************************************************************************/ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -namespace Spine4_1_00 { - public interface IVertexEffect { - void Begin (Skeleton skeleton); - void Transform (ref VertexPositionColorTextureColor vertex); - void End (); - } +namespace Spine4_1_00 +{ + public interface IVertexEffect + { + void Begin(Skeleton skeleton); + void Transform(ref VertexPositionColorTextureColor vertex); + void End(); + } - public class JitterEffect : IVertexEffect { - public float JitterX { get; set; } - public float JitterY { get; set; } + public class JitterEffect : IVertexEffect + { + public float JitterX { get; set; } + public float JitterY { get; set; } - public JitterEffect (float jitterX, float jitterY) { - JitterX = jitterX; - JitterY = jitterY; - } + public JitterEffect(float jitterX, float jitterY) + { + JitterX = jitterX; + JitterY = jitterY; + } - public void Begin (Skeleton skeleton) { - } + public void Begin(Skeleton skeleton) + { + } - public void End () { - } + public void End() + { + } - public void Transform (ref VertexPositionColorTextureColor vertex) { - vertex.Position.X += MathUtils.RandomTriangle(-JitterX, JitterY); - vertex.Position.Y += MathUtils.RandomTriangle(-JitterX, JitterY); - } - } + public void Transform(ref VertexPositionColorTextureColor vertex) + { + vertex.Position.X += MathUtils.RandomTriangle(-JitterX, JitterY); + vertex.Position.Y += MathUtils.RandomTriangle(-JitterX, JitterY); + } + } - public class SwirlEffect : IVertexEffect { - private float worldX, worldY, angle; + public class SwirlEffect : IVertexEffect + { + private float worldX, worldY, angle; - public float Radius { get; set; } - public float Angle { get { return angle; } set { angle = value * MathUtils.DegRad; } } - public float CenterX { get; set; } - public float CenterY { get; set; } - public IInterpolation Interpolation { get; set; } + public float Radius { get; set; } + public float Angle { get { return angle; } set { angle = value * MathUtils.DegRad; } } + public float CenterX { get; set; } + public float CenterY { get; set; } + public IInterpolation Interpolation { get; set; } - public SwirlEffect (float radius) { - Radius = radius; - Interpolation = IInterpolation.Pow2; - } + public SwirlEffect(float radius) + { + Radius = radius; + Interpolation = IInterpolation.Pow2; + } - public void Begin (Skeleton skeleton) { - worldX = skeleton.X + CenterX; - worldY = skeleton.Y + CenterY; - } + public void Begin(Skeleton skeleton) + { + worldX = skeleton.X + CenterX; + worldY = skeleton.Y + CenterY; + } - public void End () { - } + public void End() + { + } - public void Transform (ref VertexPositionColorTextureColor vertex) { - float x = vertex.Position.X - worldX; - float y = vertex.Position.Y - worldY; - float dist = (float)Math.Sqrt(x * x + y * y); - if (dist < Radius) { - float theta = Interpolation.Apply(0, angle, (Radius - dist) / Radius); - float cos = MathUtils.Cos(theta), sin = MathUtils.Sin(theta); - vertex.Position.X = cos * x - sin * y + worldX; - vertex.Position.Y = sin * x + cos * y + worldY; - } - } - } + public void Transform(ref VertexPositionColorTextureColor vertex) + { + float x = vertex.Position.X - worldX; + float y = vertex.Position.Y - worldY; + float dist = (float)Math.Sqrt(x * x + y * y); + if (dist < Radius) + { + float theta = Interpolation.Apply(0, angle, (Radius - dist) / Radius); + float cos = MathUtils.Cos(theta), sin = MathUtils.Sin(theta); + vertex.Position.X = cos * x - sin * y + worldX; + vertex.Position.Y = sin * x + cos * y + worldY; + } + } + } } diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/XnaLoader/XnaTextureLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/XnaLoader/XnaTextureLoader.cs index a06878e..e3f79a5 100644 --- a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/XnaLoader/XnaTextureLoader.cs +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.1.00/XnaLoader/XnaTextureLoader.cs @@ -27,57 +27,63 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; using System; -using System.IO; +using Microsoft.Xna.Framework.Graphics; -namespace Spine4_1_00 { - public class XnaTextureLoader : TextureLoader { - GraphicsDevice device; - string[] textureLayerSuffixes = null; +namespace Spine4_1_00 +{ + public class XnaTextureLoader : TextureLoader + { + GraphicsDevice device; + string[] textureLayerSuffixes = null; - /// - /// Constructor. - /// - /// The graphics device to be used. - /// If true multiple textures layers - /// (e.g. a diffuse/albedo texture and a normal map) are loaded instead of a single texture. - /// Names are constructed based on suffixes added according to the textureSuffixes parameter. - /// If loadMultipleTextureLayers is true, the strings of this array - /// define the path name suffix of each layer to be loaded. Array size must be equal to the number of layers to be loaded. - /// The first array entry is the suffix to be replaced (e.g. "_albedo", or "" for a first layer without a suffix), - /// subsequent array entries contain the suffix to replace the first entry with (e.g. "_normals"). - /// - /// An example would be: - /// new string[] { "", "_normals" } for loading a base diffuse texture named "skeletonname.png" and - /// a normalmap named "skeletonname_normals.png". - public XnaTextureLoader (GraphicsDevice device, bool loadMultipleTextureLayers = false, string[] textureSuffixes = null) { - this.device = device; - if (loadMultipleTextureLayers) - this.textureLayerSuffixes = textureSuffixes; - } + /// + /// Constructor. + /// + /// The graphics device to be used. + /// If true multiple textures layers + /// (e.g. a diffuse/albedo texture and a normal map) are loaded instead of a single texture. + /// Names are constructed based on suffixes added according to the textureSuffixes parameter. + /// If loadMultipleTextureLayers is true, the strings of this array + /// define the path name suffix of each layer to be loaded. Array size must be equal to the number of layers to be loaded. + /// The first array entry is the suffix to be replaced (e.g. "_albedo", or "" for a first layer without a suffix), + /// subsequent array entries contain the suffix to replace the first entry with (e.g. "_normals"). + /// + /// An example would be: + /// new string[] { "", "_normals" } for loading a base diffuse texture named "skeletonname.png" and + /// a normalmap named "skeletonname_normals.png". + public XnaTextureLoader(GraphicsDevice device, bool loadMultipleTextureLayers = false, string[] textureSuffixes = null) + { + this.device = device; + if (loadMultipleTextureLayers) + this.textureLayerSuffixes = textureSuffixes; + } + + public void Load(AtlasPage page, String path) + { + Texture2D texture = Util.LoadTexture(device, path); - public void Load (AtlasPage page, String path) { - Texture2D texture = Util.LoadTexture(device, path); - if (page.width == 0 || page.height == 0) { page.width = texture.Width; page.height = texture.Height; } - if (textureLayerSuffixes == null) { - page.rendererObject = texture; - } else { - Texture2D[] textureLayersArray = new Texture2D[textureLayerSuffixes.Length]; - textureLayersArray[0] = texture; - for (int layer = 1; layer < textureLayersArray.Length; ++layer) { - string layerPath = GetLayerName(path, textureLayerSuffixes[0], textureLayerSuffixes[layer]); - textureLayersArray[layer] = Util.LoadTexture(device, layerPath); - } - page.rendererObject = textureLayersArray; - } - } + if (textureLayerSuffixes == null) + { + page.rendererObject = texture; + } + else + { + Texture2D[] textureLayersArray = new Texture2D[textureLayerSuffixes.Length]; + textureLayersArray[0] = texture; + for (int layer = 1; layer < textureLayersArray.Length; ++layer) + { + string layerPath = GetLayerName(path, textureLayerSuffixes[0], textureLayerSuffixes[layer]); + textureLayersArray[layer] = Util.LoadTexture(device, layerPath); + } + page.rendererObject = textureLayersArray; + } + } public void Unload(Object texture) { @@ -97,14 +103,16 @@ public void Unload(Object texture) } - private string GetLayerName (string firstLayerPath, string firstLayerSuffix, string replacementSuffix) { + private string GetLayerName(string firstLayerPath, string firstLayerSuffix, string replacementSuffix) + { - int suffixLocation = firstLayerPath.LastIndexOf(firstLayerSuffix + "."); - if (suffixLocation == -1) { - throw new Exception(string.Concat("Error composing texture layer name: first texture layer name '", firstLayerPath, - "' does not contain suffix to be replaced: '", firstLayerSuffix, "'")); - } - return firstLayerPath.Remove(suffixLocation, firstLayerSuffix.Length).Insert(suffixLocation, replacementSuffix); - } - } + int suffixLocation = firstLayerPath.LastIndexOf(firstLayerSuffix + "."); + if (suffixLocation == -1) + { + throw new Exception(string.Concat("Error composing texture layer name: first texture layer name '", firstLayerPath, + "' does not contain suffix to be replaced: '", firstLayerSuffix, "'")); + } + return firstLayerPath.Remove(suffixLocation, firstLayerSuffix.Length).Insert(suffixLocation, replacementSuffix); + } + } } diff --git a/SpineViewerWPF/SpineViewerWPF.csproj b/SpineViewerWPF/SpineViewerWPF.csproj index ad3317b..8981a4f 100644 --- a/SpineViewerWPF/SpineViewerWPF.csproj +++ b/SpineViewerWPF/SpineViewerWPF.csproj @@ -1,19 +1,7 @@ - - - + - Debug - AnyCPU - {28600FC6-6C22-4BEF-8865-AD159B5E8C5F} + net8.0-windows WinExe - SpineViewerWPF - SpineViewerWPF - v4.7.2 - 512 - {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 4 - true - false publish\ true @@ -29,774 +17,63 @@ 2.0.0.0 false true + false + true + true + true - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 true - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - icon.ico - - ..\packages\ControlzEx.3.0.2.4\lib\net462\ControlzEx.dll - - - - - - ..\packages\SixLabors.ImageSharp.2.1.3\lib\net472\SixLabors.ImageSharp.dll - - - - ..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll - - - - - ..\packages\System.Drawing.Common.4.7.0\lib\net461\System.Drawing.Common.dll - - - ..\packages\System.Memory.4.5.4\lib\net461\System.Memory.dll - - - - ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll - - - ..\packages\System.Runtime.CompilerServices.Unsafe.5.0.0\lib\net45\System.Runtime.CompilerServices.Unsafe.dll - - - ..\packages\System.Text.Encoding.CodePages.5.0.0\lib\net461\System.Text.Encoding.CodePages.dll - - - - ..\packages\ControlzEx.3.0.2.4\lib\net462\System.Windows.Interactivity.dll - - - - - - - - - 4.0 - - - - - - ..\packages\WpfXnaControl.1.0.0\lib\NET4.0\WpfXnaControl.dll - - - - - MSBuild:Compile - Designer - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - UCPlayer.xaml - - - Open.xaml - - - MSBuild:Compile - Designer - - - App.xaml - Code - - - MainWindow.xaml - Code - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - + - - Code - - - True - True - Resources.resx - - - True - Settings.settings - True - - - ResXFileCodeGenerator - Resources.Designer.cs - - - - SettingsSingleFileGenerator - Settings.Designer.cs - + - + - + - + - + + 6.0.0 + + + + 1.1.135 + + + + 3.1.6 + + + - + + False + Microsoft .NET Framework 4.7.2 %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 + false + - + + ..\..\WpfXnaControl\WpfXnaControl\bin\Release\net8.0-windows\WpfXnaControl.dll + True + - - \ No newline at end of file diff --git a/SpineViewerWPF/Views/UCPlayer.xaml.cs b/SpineViewerWPF/Views/UCPlayer.xaml.cs index be2cba4..f5c8f93 100644 --- a/SpineViewerWPF/Views/UCPlayer.xaml.cs +++ b/SpineViewerWPF/Views/UCPlayer.xaml.cs @@ -1,17 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Linq; using System.Windows; using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Navigation; -using System.Windows.Shapes; namespace SpineViewerWPF.Views { @@ -27,14 +18,14 @@ public UCPlayer() { InitializeComponent(); - if(App.appXC == null) + if (App.appXC == null) { App.appXC = new WpfXnaControl.XnaControl(); } - - if(player != null) + + if (player != null) { player.Dispose(); } @@ -94,8 +85,8 @@ public UCPlayer() var transformGroup = (TransformGroup)Frame.RenderTransform; var tt = (TranslateTransform)transformGroup.Children.Where(x => x.GetType() == typeof(TranslateTransform)).FirstOrDefault(); - tt.X = (float)((App.mainWidth ) / 2 - (App.canvasWidth / 2) -10); - tt.Y = (float)((App.mainHeight ) / 2 - (App.canvasHeight / 2)-40); + tt.X = (float)((App.mainWidth) / 2 - (App.canvasWidth / 2) - 10); + tt.Y = (float)((App.mainHeight) / 2 - (App.canvasHeight / 2) - 40); Frame.Children.Add(App.appXC); @@ -123,13 +114,13 @@ private void Frame_MouseMove(object sender, MouseEventArgs e) } else if (Keyboard.IsKeyDown(Key.LeftCtrl)) { - + var transformGroup = (TransformGroup)Frame.RenderTransform; var tt = (TranslateTransform)transformGroup.Children.Where(x => x.GetType() == typeof(TranslateTransform)).FirstOrDefault(); tt.X = (float)(position.X + tt.X - App.mouseLocation.X); tt.Y = (float)(position.Y + tt.Y - App.mouseLocation.Y); } - App.mouseLocation = Mouse.GetPosition(this.Frame); + App.mouseLocation = Mouse.GetPosition(this.Frame); } } diff --git a/SpineViewerWPF/Windows/Open.xaml.cs b/SpineViewerWPF/Windows/Open.xaml.cs index 8e1ba0a..b79aaa5 100644 --- a/SpineViewerWPF/Windows/Open.xaml.cs +++ b/SpineViewerWPF/Windows/Open.xaml.cs @@ -2,17 +2,9 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; -using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Documents; using Microsoft.Win32; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Shapes; namespace SpineViewerWPF.Windows { @@ -38,10 +30,11 @@ public Open(MainWindow main) { tb_Atlas_File.Text = App.globalValues.SelectAtlasFile; } - if (App.globalValues.SelectSpineFile != "") { + if (App.globalValues.SelectSpineFile != "") + { tb_JS_file.Text = App.globalValues.SelectSpineFile; } - + tb_Canvas_X.Text = App.canvasWidth.ToString(); @@ -50,7 +43,7 @@ public Open(MainWindow main) private void btn_Altas_Open_Click(object sender, RoutedEventArgs e) { - bool isSelect = SelectFile("Spine Altas File (*.atlas)|*.atlas;", tb_Atlas_File); + bool isSelect = SelectFile("Spine Altas File (*.atlas)|*.atlas;", tb_Atlas_File); if (isSelect) { @@ -70,21 +63,21 @@ private void btn_Altas_Open_Click(object sender, RoutedEventArgs e) tb_JS_file.Text = App.globalValues.SelectSpineFile; } } - + } private void btn_JS_Open_Click(object sender, RoutedEventArgs e) { - bool isSelect = SelectFile("Spine Json File (*.json)|*.json|Spine Binary File (*.skel)|*.skel", tb_JS_file); + bool isSelect = SelectFile("Spine Json File (*.json)|*.json|Spine Binary File (*.skel)|*.skel", tb_JS_file); if (isSelect) { tb_JS_file.Text = App.globalValues.SelectSpineFile; } } - private bool SelectFile(string filter,TextBox textBox) + private bool SelectFile(string filter, TextBox textBox) { OpenFileDialog openFileDialog = new OpenFileDialog(); if (Directory.Exists(App.lastDir)) @@ -113,7 +106,7 @@ private void btn_Open_Click(object sender, RoutedEventArgs e) System.Windows.MessageBox.Show("Please Select Spine Version!"); return; } - if(tb_Atlas_File.Text.Trim() == "") + if (tb_Atlas_File.Text.Trim() == "") { System.Windows.MessageBox.Show("Please Select Atlas File!"); return; @@ -126,7 +119,7 @@ private void btn_Open_Click(object sender, RoutedEventArgs e) double setWidth; double setHeight; - if (!double.TryParse(tb_Canvas_X.Text,out setWidth) || !double.TryParse(tb_Canvas_Y.Text, out setHeight)) + if (!double.TryParse(tb_Canvas_X.Text, out setWidth) || !double.TryParse(tb_Canvas_Y.Text, out setHeight)) { System.Windows.MessageBox.Show("Please Set Currect Canvas Value!"); return; @@ -143,7 +136,8 @@ private void btn_Open_Click(object sender, RoutedEventArgs e) muiltTextureList.Insert(0, ""); App.mulitTexture = muiltTextureList.ToArray(); } - else { + else + { App.mulitTexture = null; } @@ -165,9 +159,9 @@ private void TextBox_PreviewDrop(object sender, DragEventArgs e) TextBox tb = sender as TextBox; if (tb != null) { - if(tb.Name == "tb_Atlas_File") + if (tb.Name == "tb_Atlas_File") { - if(((string[])text)[0].IndexOf(".atlas") != -1) + if (((string[])text)[0].IndexOf(".atlas") != -1) { tb_Atlas_File.Text = ((string[])text)[0]; App.globalValues.SelectAtlasFile = tb_Atlas_File.Text; @@ -197,7 +191,7 @@ private void TextBox_PreviewDrop(object sender, DragEventArgs e) } - + } } diff --git a/SpineViewerWPF/packages.config b/SpineViewerWPF/packages.config deleted file mode 100644 index 0830c18..0000000 --- a/SpineViewerWPF/packages.config +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file From e58e2e60ac0b8f4daa9cc3a3b82025b8f8e8c724 Mon Sep 17 00:00:00 2001 From: Eleiyas Date: Fri, 31 Jan 2025 15:58:45 +0000 Subject: [PATCH 2/5] Update README.md --- README.md | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index df0897b..bd0a60f 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,12 @@ # SpineViewerWPF -a tool can view spine files with different spine-runtimes version and export gif or png file. +A tool that can view spine files with different spine-runtime versions and export to gif or png. 中文說明請看[這裡](README_zhTW.md) +## Usage +Start by loading an Atlas file for the specific spine you want to view, and the program will then try to import the skeleton data in JSON format and the sprite-atlas in PNG format. All 3 files must be named according to the atlas data (eg: Spine1.atlas, Spine1.json, Spine1.png). Make sure to select the correct spine version according to your skeleton data, or else there may be visual bugs, or the spine will not load/animate correctly. + + ## Hot Key * Ctrl+Mousewheel Canvas Scaling * Alt+Mousewheel Spine Scaling @@ -10,7 +14,7 @@ a tool can view spine files with different spine-runtimes version and export gif * Alt+Mousedown+Mousemove Spine Moving ## Features -* Suppot Spine Runtimes Version +* Support Spine Runtime Versions * **2.1.08** * **2.1.25** * **3.1.07** @@ -28,20 +32,24 @@ a tool can view spine files with different spine-runtimes version and export gif * Export animation to gif or png file. * Can view Animation with different options. - - - -## Usage - +## Uses Library: - [ImageSharp](https://github.com/SixLabors/ImageSharp) - [WpfXnaControl](https://github.com/erickeek/WpfXnaControl) - [spine-runtimes](https://github.com/EsotericSoftware/spine-runtimes) - Requirements: -- [.NET Framework 4.7.2](http://go.microsoft.com/fwlink/?linkid=863265) +- [.NET 8.0](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) - [Microsoft XNA Framework Redistributable 4.0](https://www.microsoft.com/en-us/download/details.aspx?id=20914) +## Changelog +* 31/01/2025 (Eleiyas) + * Upgraded program to .NET 8.0 + * Updated packages to latest versions + * Imported MonoGame packages to retain usage of Microsoft.Xna.Framework + * Custom import of WpfXnaControl.dll file as original NuGet package is massively outdated + * Cleaned code + * Updated ReadMe + ## Issue: * \_(:3」∠)\_ From 0c62294ae1515fd957b5b2c9b411eb2f9024cf82 Mon Sep 17 00:00:00 2001 From: Eleiyas Date: Fri, 31 Jan 2025 16:48:22 +0000 Subject: [PATCH 3/5] Implemented Spine 4.2.33 --- README.md | 2 +- .../PublicFunction/Player/Player_4_2_33.cs | 243 ++ .../spine-runtimes-4.2.33/Animation.cs | 2835 +++++++++++++++++ .../spine-runtimes-4.2.33/AnimationState.cs | 1507 +++++++++ .../AnimationStateData.cs | 114 + .../spine-runtimes-4.2.33/Atlas.cs | 372 +++ .../Attachments/AtlasAttachmentLoader.cs | 109 + .../Attachments/Attachment.cs | 56 + .../Attachments/AttachmentLoader.cs | 48 + .../Attachments/AttachmentType.cs | 34 + .../Attachments/BoundingBoxAttachment.cs | 48 + .../Attachments/ClippingAttachment.cs | 51 + .../Attachments/IHasTextureRegion.cs | 56 + .../Attachments/MeshAttachment.cs | 220 ++ .../Attachments/PathAttachment.cs | 65 + .../Attachments/PointAttachment.cs | 73 + .../Attachments/RegionAttachment.cs | 220 ++ .../Attachments/Sequence.cs | 95 + .../Attachments/VertexAttachment.cs | 158 + .../spine-runtimes-4.2.33/BlendMode.cs | 34 + .../spine-runtimes-4.2.33/Bone.cs | 458 +++ .../spine-runtimes-4.2.33/BoneData.cs | 113 + .../spine-runtimes-4.2.33/ConstraintData.cs | 61 + .../spine-runtimes-4.2.33/Event.cs | 64 + .../spine-runtimes-4.2.33/EventData.cs | 56 + .../spine-runtimes-4.2.33/ExposedList.cs | 637 ++++ .../spine-runtimes-4.2.33/IUpdatable.cs | 47 + .../spine-runtimes-4.2.33/IkConstraint.cs | 386 +++ .../spine-runtimes-4.2.33/IkConstraintData.cs | 103 + .../spine-runtimes-4.2.33/Json.cs | 519 +++ .../spine-runtimes-4.2.33/MathUtils.cs | 184 ++ .../MonoGameLoader/MeshBatcher.cs | 195 ++ .../MonoGameLoader/ShapeRenderer.cs | 165 + .../MonoGameLoader/SkeletonDebugRenderer.cs | 234 ++ .../MonoGameLoader/SkeletonRenderer.cs | 249 ++ .../MonoGameLoader/Util.cs | 79 + .../MonoGameLoader/VertexEffect.cs | 97 + .../MonoGameLoader/XnaTextureLoader.cs | 93 + .../spine-runtimes-4.2.33/PathConstraint.cs | 518 +++ .../PathConstraintData.cs | 72 + .../PhysicsConstraint.cs | 326 ++ .../PhysicsConstraintData.cs | 71 + .../spine-runtimes-4.2.33/Skeleton.cs | 782 +++++ .../spine-runtimes-4.2.33/SkeletonBinary.cs | 1379 ++++++++ .../spine-runtimes-4.2.33/SkeletonBounds.cs | 233 ++ .../spine-runtimes-4.2.33/SkeletonClipping.cs | 353 ++ .../spine-runtimes-4.2.33/SkeletonData.cs | 240 ++ .../spine-runtimes-4.2.33/SkeletonJson.cs | 1363 ++++++++ .../spine-runtimes-4.2.33/SkeletonLoader.cs | 75 + .../spine-runtimes-4.2.33/Skin.cs | 204 ++ .../spine-runtimes-4.2.33/Slot.cs | 204 ++ .../spine-runtimes-4.2.33/SlotData.cs | 80 + .../spine-runtimes-4.2.33/TextureRegion.cs | 49 + .../TransformConstraint.cs | 312 ++ .../TransformConstraintData.cs | 68 + .../spine-runtimes-4.2.33/Triangulator.cs | 275 ++ SpineViewerWPF/SpineViewerWPF.csproj | 3 + SpineViewerWPF/Views/UCPlayer.xaml.cs | 3 + SpineViewerWPF/Windows/Open.xaml | 1 + 59 files changed, 16690 insertions(+), 1 deletion(-) create mode 100644 SpineViewerWPF/PublicFunction/Player/Player_4_2_33.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Animation.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/AnimationState.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/AnimationStateData.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Atlas.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/AtlasAttachmentLoader.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/Attachment.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/AttachmentLoader.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/AttachmentType.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/BoundingBoxAttachment.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/ClippingAttachment.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/IHasTextureRegion.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/MeshAttachment.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/PathAttachment.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/PointAttachment.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/RegionAttachment.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/Sequence.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/VertexAttachment.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/BlendMode.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Bone.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/BoneData.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/ConstraintData.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Event.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/EventData.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/ExposedList.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/IUpdatable.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/IkConstraint.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/IkConstraintData.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Json.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/MathUtils.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/MonoGameLoader/MeshBatcher.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/MonoGameLoader/ShapeRenderer.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/MonoGameLoader/SkeletonDebugRenderer.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/MonoGameLoader/SkeletonRenderer.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/MonoGameLoader/Util.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/MonoGameLoader/VertexEffect.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/MonoGameLoader/XnaTextureLoader.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/PathConstraint.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/PathConstraintData.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/PhysicsConstraint.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/PhysicsConstraintData.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Skeleton.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/SkeletonBinary.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/SkeletonBounds.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/SkeletonClipping.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/SkeletonData.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/SkeletonJson.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/SkeletonLoader.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Skin.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Slot.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/SlotData.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/TextureRegion.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/TransformConstraint.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/TransformConstraintData.cs create mode 100644 SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Triangulator.cs diff --git a/README.md b/README.md index bd0a60f..080e3eb 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,6 @@ A tool that can view spine files with different spine-runtime versions and expor ## Usage Start by loading an Atlas file for the specific spine you want to view, and the program will then try to import the skeleton data in JSON format and the sprite-atlas in PNG format. All 3 files must be named according to the atlas data (eg: Spine1.atlas, Spine1.json, Spine1.png). Make sure to select the correct spine version according to your skeleton data, or else there may be visual bugs, or the spine will not load/animate correctly. - ## Hot Key * Ctrl+Mousewheel Canvas Scaling * Alt+Mousewheel Spine Scaling @@ -29,6 +28,7 @@ Start by loading an Atlas file for the specific spine you want to view, and the * **4.0.31** * **4.0.64** * **4.1.00** + * **4.2.33** * Export animation to gif or png file. * Can view Animation with different options. diff --git a/SpineViewerWPF/PublicFunction/Player/Player_4_2_33.cs b/SpineViewerWPF/PublicFunction/Player/Player_4_2_33.cs new file mode 100644 index 0000000..ce1e2ba --- /dev/null +++ b/SpineViewerWPF/PublicFunction/Player/Player_4_2_33.cs @@ -0,0 +1,243 @@ +using System; +using System.Collections.Generic; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; +using Spine4_2_33; +using SpineViewerWPF; + +public class Player_4_2_33 : IPlayer +{ + private Skeleton skeleton; + private AnimationState state; + private SkeletonRenderer skeletonRenderer; + private ExposedList listAnimation; + private ExposedList listSkin; + private Atlas atlas; + private SkeletonData skeletonData; + private AnimationStateData stateData; + private SkeletonBinary binary; + private SkeletonJson json; + + public void Initialize() + { + Player.Initialize(ref App.graphicsDevice, ref App.spriteBatch); + } + + public void LoadContent(ContentManager contentManager) + { + skeletonRenderer = new SkeletonRenderer(App.graphicsDevice); + skeletonRenderer.PremultipliedAlpha = App.globalValues.Alpha; + + if (App.mulitTexture != null && App.mulitTexture.Length == 0) + { + atlas = new Atlas(App.globalValues.SelectAtlasFile, new XnaTextureLoader(App.graphicsDevice)); + } + else + { + atlas = new Atlas(App.globalValues.SelectAtlasFile, new XnaTextureLoader(App.graphicsDevice, true, App.mulitTexture)); + } + + if (Common.IsBinaryData(App.globalValues.SelectSpineFile)) + { + binary = new SkeletonBinary(atlas); + binary.Scale = App.globalValues.Scale; + skeletonData = binary.ReadSkeletonData(App.globalValues.SelectSpineFile); + } + else + { + json = new SkeletonJson(atlas); + json.Scale = App.globalValues.Scale; + skeletonData = json.ReadSkeletonData(App.globalValues.SelectSpineFile); + } + App.globalValues.SpineVersion = skeletonData.Version; + skeleton = new Skeleton(skeletonData); + + + + + Common.SetInitLocation(skeleton.Data.Height); + App.globalValues.FileHash = skeleton.Data.Hash; + + stateData = new AnimationStateData(skeleton.Data); + + state = new AnimationState(stateData); + + List AnimationNames = new List(); + listAnimation = state.Data.skeletonData.Animations; + foreach (Animation An in listAnimation) + { + AnimationNames.Add(An.name); + } + App.globalValues.AnimeList = AnimationNames; + + List SkinNames = new List(); + listSkin = state.Data.skeletonData.Skins; + foreach (Skin Sk in listSkin) + { + SkinNames.Add(Sk.name); + } + App.globalValues.SkinList = SkinNames; + + if (App.globalValues.SelectAnimeName != "") + { + state.SetAnimation(0, App.globalValues.SelectAnimeName, App.globalValues.IsLoop); + } + else + { + state.SetAnimation(0, state.Data.skeletonData.animations.Items[0].name, App.globalValues.IsLoop); + } + + if (App.isNew) + { + App.globalValues.PosX = (float)App.canvasWidth / 2; + App.globalValues.PosY = (float)App.canvasHeight / 2; + MainWindow.SetCBAnimeName(); + } + App.isNew = false; + + } + + + + public void Update(GameTime gameTime) + { + if (App.globalValues.SelectAnimeName != "" && App.globalValues.SetAnime) + { + state.ClearTracks(); + skeleton.SetToSetupPose(); + state.SetAnimation(0, App.globalValues.SelectAnimeName, App.globalValues.IsLoop); + App.globalValues.SetAnime = false; + } + + if (App.globalValues.SelectSkin != "" && App.globalValues.SetSkin) + { + skeleton.SetSkin(App.globalValues.SelectSkin); + skeleton.SetSlotsToSetupPose(); + App.globalValues.SetSkin = false; + } + + + } + + public void Draw() + { + if (App.globalValues.SelectSpineVersion != "4.2.33" || App.globalValues.FileHash != skeleton.Data.Hash) + { + state = null; + skeletonRenderer = null; + return; + } + App.graphicsDevice.Clear(Color.Transparent); + + Player.DrawBG(ref App.spriteBatch); + + + state.Update(App.globalValues.Speed / 1000f); + state.Apply(skeleton); + state.TimeScale = App.globalValues.TimeScale; + if (binary != null) + { + if (App.globalValues.Scale != binary.Scale) + { + binary.Scale = App.globalValues.Scale; + skeletonData = binary.ReadSkeletonData(App.globalValues.SelectSpineFile); + skeleton = new Skeleton(skeletonData); + } + } + else if (json != null) + { + if (App.globalValues.Scale != json.Scale) + { + json.Scale = App.globalValues.Scale; + skeletonData = json.ReadSkeletonData(App.globalValues.SelectSpineFile); + skeleton = new Skeleton(skeletonData); + } + } + + skeleton.X = App.globalValues.PosX; + skeleton.Y = App.globalValues.PosY; + skeleton.ScaleX = (App.globalValues.FilpX ? -1 : 1); + skeleton.ScaleY = (App.globalValues.FilpY ? -1 : 1); + + + skeleton.RootBone.Rotation = App.globalValues.Rotation; + skeleton.UpdateWorldTransform(Skeleton.Physics.Update); + skeletonRenderer.PremultipliedAlpha = App.globalValues.Alpha; + if (skeletonRenderer.Effect is BasicEffect) + { + ((BasicEffect)skeletonRenderer.Effect).Projection = Matrix.CreateOrthographicOffCenter(0, App.graphicsDevice.Viewport.Width, App.graphicsDevice.Viewport.Height, 0, 1, 0); + } + else + { + skeletonRenderer.Effect.Parameters["Projection"].SetValue(Matrix.CreateOrthographicOffCenter(0, App.graphicsDevice.Viewport.Width, App.graphicsDevice.Viewport.Height, 0, 1, 0)); + } + skeletonRenderer.Begin(); + skeletonRenderer.Draw(skeleton); + skeletonRenderer.End(); + + if (state != null) + { + TrackEntry entry = state.GetCurrent(0); + if (entry != null) + { + if (App.globalValues.IsRecoding && (App.globalValues.GifList != null || App.recordImageCount > 0) && !entry.IsComplete) + { + if (App.recordImageCount == 1) + { + TrackEntry te = state.GetCurrent(0); + te.trackTime = 0; + App.globalValues.TimeScale = 1; + App.globalValues.Lock = 0; + } + + Common.TakeRecodeScreenshot(App.graphicsDevice); + } + + if (App.globalValues.IsRecoding && entry.IsComplete) + { + state.TimeScale = 0; + App.globalValues.IsRecoding = false; + Common.RecodingEnd(entry.AnimationEnd); + + state.TimeScale = 1; + App.globalValues.TimeScale = 1; + } + + if (App.globalValues.TimeScale == 0) + { + entry.TrackTime = entry.AnimationEnd * App.globalValues.Lock; + entry.TimeScale = 0; + } + else + { + App.globalValues.Lock = entry.AnimationTime / entry.AnimationEnd; + entry.TimeScale = 1; + } + App.globalValues.LoadingProcess = $"{Math.Round(entry.AnimationTime / entry.AnimationEnd * 100, 2)}%"; + } + } + + + } + + public void ChangeSet() + { + App.appXC.ContentManager.Dispose(); + atlas.Dispose(); + atlas = null; + App.appXC.LoadContent.Invoke(App.appXC.ContentManager); + } + + public void SizeChange() + { + if (App.graphicsDevice != null) + Player.UserControl_SizeChanged(ref App.graphicsDevice); + } + + public void Dispose() + { + ChangeSet(); + } +} + diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Animation.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Animation.cs new file mode 100644 index 0000000..5070a12 --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Animation.cs @@ -0,0 +1,2835 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +using System; +using System.Collections.Generic; + +namespace Spine4_2_33 { + + /// + /// Stores a list of timelines to animate a skeleton's pose over time. + public class Animation { + internal String name; + internal ExposedList timelines; + internal HashSet timelineIds; + internal float duration; + + public Animation (string name, ExposedList timelines, float duration) { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + + this.name = name; + SetTimelines(timelines); + this.duration = duration; + } + + public ExposedList Timelines { + get { return timelines; } + set { SetTimelines(value); } + } + + public void SetTimelines (ExposedList timelines) { + if (timelines == null) throw new ArgumentNullException("timelines", "timelines cannot be null."); + this.timelines = timelines; + // Note: avoiding reallocations by adding all hash set entries at + // once (EnsureCapacity() is only available in newer .Net versions). + int idCount = 0; + int timelinesCount = timelines.Count; + Timeline[] timelinesItems = timelines.Items; + for (int t = 0; t < timelinesCount; ++t) + idCount += timelinesItems[t].PropertyIds.Length; + string[] propertyIds = new string[idCount]; + int currentId = 0; + for (int t = 0; t < timelinesCount; ++t) { + string[] ids = timelinesItems[t].PropertyIds; + for (int i = 0, idsLength = ids.Length; i < idsLength; ++i) + propertyIds[currentId++] = ids[i]; + } + this.timelineIds = new HashSet(propertyIds); + } + + /// The duration of the animation in seconds, which is usually the highest time of all frames in the timeline. The duration is + /// used to know when it has completed and when it should loop back to the start. + public float Duration { get { return duration; } set { duration = value; } } + + /// The animation's name, which is unique across all animations in the skeleton. + public string Name { get { return name; } } + + /// Returns true if this animation contains a timeline with any of the specified property IDs. + public bool HasTimeline (string[] propertyIds) { + foreach (string id in propertyIds) + if (timelineIds.Contains(id)) return true; + return false; + } + + /// Applies the animation's timelines to the specified skeleton. + /// + /// The skeleton the animation is being applied to. This provides access to the bones, slots, and other skeleton + /// components the timelines may change. + /// The last time in seconds this animation was applied. Some timelines trigger only at specific times rather + /// than every frame. Pass -1 the first time an animation is applied to ensure frame 0 is triggered. + /// The time in seconds the skeleton is being posed for. Most timelines find the frame before and the frame after + /// this time and interpolate between the frame values. If beyond the and loop is + /// true then the animation will repeat, else the last frame will be applied. + /// If true, the animation repeats after the . + /// If any events are fired, they are added to this list. Can be null to ignore fired events or if no timelines + /// fire events. + /// 0 applies the current or setup values (depending on blend). 1 applies the timeline values. Between + /// 0 and 1 applies values between the current or setup values and the timeline values. 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 (layering). + /// Controls how mixing is applied when alpha < 1. + /// Indicates whether the timelines are mixing in or out. Used by timelines which perform instant transitions, + /// such as or . + public void Apply (Skeleton skeleton, float lastTime, float time, bool loop, ExposedList events, float alpha, + MixBlend blend, MixDirection direction) { + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + + if (loop && duration != 0) { + time %= duration; + if (lastTime > 0) lastTime %= duration; + } + + Timeline[] timelines = this.timelines.Items; + for (int i = 0, n = this.timelines.Count; i < n; i++) + timelines[i].Apply(skeleton, lastTime, time, events, alpha, blend, direction); + } + + override public string ToString () { + return name; + } + } + + /// + /// Controls how timeline values are mixed with setup pose values or current pose values when a timeline is applied with + /// alpha < 1. + /// + public enum MixBlend { + /// Transitions from the setup value to the timeline value (the current value is not used). Before the first frame, the + /// setup value is set. + Setup, + + /// + /// + /// Transitions from the current value to the timeline value. Before the first frame, transitions from the current value to + /// the setup value. Timelines which perform instant transitions, such as or + /// , use the setup value before the first frame. + /// + /// First is intended for the first animations applied, not for animations layered on top of those. + /// + First, + + /// + /// + /// Transitions from the current value to the timeline value. No change is made before the first frame (the current value is + /// kept until the first frame). + /// + /// Replace is intended for animations layered on top of others, not for the first animations applied. + /// + Replace, + + /// + /// + /// Transitions from the current value to the current value plus the timeline value. No change is made before the first frame + /// (the current value is kept until the first frame). + /// + /// Add is intended for animations layered on top of others, not for the first animations applied. Properties + /// set by additive animations must be set manually or by another animation before applying the additive animations, else the + /// property values will increase each time the additive animations are applied. + /// + /// + Add + } + + /// + /// Indicates whether a timeline's alpha is mixing out over time toward 0 (the setup or current pose value) or + /// mixing in toward 1 (the timeline's value). Some timelines use this to decide how values are applied. + /// + public enum MixDirection { + In, + Out + } + + public enum Property { + Rotate = 0, X, Y, ScaleX, ScaleY, ShearX, ShearY, Inherit, // + RGB, Alpha, RGB2, // + Attachment, Deform, // + Event, DrawOrder, // + IkConstraint, TransformConstraint, // + PathConstraintPosition, PathConstraintSpacing, PathConstraintMix, // + PhysicsConstraintInertia, PhysicsConstraintStrength, PhysicsConstraintDamping, PhysicsConstraintMass, // + PhysicsConstraintWind, PhysicsConstraintGravity, PhysicsConstraintMix, PhysicsConstraintReset, // + Sequence + } + + /// + /// The base class for all timelines. + public abstract class Timeline { + private readonly string[] propertyIds; + internal readonly float[] frames; + + /// Unique identifiers for the properties the timeline modifies. + public Timeline (int frameCount, params string[] propertyIds) { + if (propertyIds == null) throw new System.ArgumentNullException("propertyIds", "propertyIds cannot be null."); + this.propertyIds = propertyIds; + frames = new float[frameCount * FrameEntries]; + } + + /// Uniquely encodes both the type of this timeline and the skeleton properties that it affects. + public string[] PropertyIds { + get { return propertyIds; } + } + + /// The time in seconds and any other values for each frame. + public float[] Frames { + get { return frames; } + } + + /// The number of entries stored per frame. + public virtual int FrameEntries { + get { return 1; } + } + + /// The number of frames for this timeline. + public virtual int FrameCount { + get { return frames.Length / FrameEntries; } + } + + public float Duration { + get { + return frames[frames.Length - FrameEntries]; + } + } + + /// Applies this timeline to the skeleton. + /// The skeleton the timeline is being applied to. This provides access to the bones, slots, and other + /// skeleton components the timeline may change. + /// The time this timeline was last applied. Timelines such as trigger only + /// at specific times rather than every frame. In that case, the timeline triggers everything between + /// lastTime (exclusive) and time (inclusive). Pass -1 the first time an animation is + /// applied to ensure frame 0 is triggered. + /// The time in seconds that the skeleton is being posed for. Most timelines find the frame before and the frame + /// after this time and interpolate between the frame values.If beyond the last frame, the last frame will be + /// applied. + /// If any events are fired, they are added to this list. Can be null to ignore fired events or if the timeline + /// does not fire events. + /// 0 applies the current or setup value (depending on blend). 1 applies the timeline value. + /// Between 0 and 1 applies a value between the current or setup value 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 (layering). + /// Controls how mixing is applied when alpha < 1. + /// Indicates whether the timeline is mixing in or out. Used by timelines which perform instant transitions, + /// such as or , and other such as . + public abstract void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, + MixBlend blend, MixDirection direction); + + /// Search using a stride of 1. + /// Must be >= the first value in frames. + /// The index of the first value <= time. + internal static int Search (float[] frames, float time) { + int n = frames.Length; + for (int i = 1; i < n; i++) + if (frames[i] > time) return i - 1; + return n - 1; + } + + /// Search using the specified stride. + /// Must be >= the first value in frames. + /// The index of the first value <= time. + internal static int Search (float[] frames, float time, int step) { + int n = frames.Length; + for (int i = step; i < n; i += step) + if (frames[i] > time) return i - step; + return n - step; + } + } + + /// An interface for timelines which change the property of a bone. + public interface IBoneTimeline { + /// The index of the bone in that will be changed when this timeline is applied. + int BoneIndex { get; } + } + + /// An interface for timelines which change the property of a slot. + public interface ISlotTimeline { + /// The index of the slot in that will be changed when this timeline is applied. + int SlotIndex { get; } + } + + /// The base class for timelines that interpolate between frame values using stepped, linear, or a Bezier curve. + public abstract class CurveTimeline : Timeline { + public const int LINEAR = 0, STEPPED = 1, BEZIER = 2, BEZIER_SIZE = 18; + + internal float[] curves; + /// The number of key frames for this timeline. + + /// The maximum number of Bezier curves. See . + /// Unique identifiers for the properties the timeline modifies. + public CurveTimeline (int frameCount, int bezierCount, params string[] propertyIds) + : base(frameCount, propertyIds) { + curves = new float[frameCount + bezierCount * BEZIER_SIZE]; + curves[frameCount - 1] = STEPPED; + } + + /// Sets the specified frame to linear interpolation. + /// Between 0 and frameCount - 1, inclusive. + public void SetLinear (int frame) { + curves[frame] = LINEAR; + } + + /// Sets the specified frame to stepped interpolation. + /// Between 0 and frameCount - 1, inclusive. + public void SetStepped (int frame) { + curves[frame] = STEPPED; + } + + /// Returns the interpolation type for the specified frame. + /// Between 0 and frameCount - 1, inclusive. + /// , or + the index of the Bezier segments. + public float GetCurveType (int frame) { + return (int)curves[frame]; + } + + /// Shrinks the storage for Bezier curves, for use when bezierCount (specified in the constructor) was larger + /// than the actual number of Bezier curves. + public void Shrink (int bezierCount) { + int size = FrameCount + bezierCount * BEZIER_SIZE; + if (curves.Length > size) { + float[] newCurves = new float[size]; + Array.Copy(curves, 0, newCurves, 0, size); + curves = newCurves; + } + } + + /// + /// Stores the segments for the specified Bezier curve. For timelines that modify multiple values, there may be more than + /// one curve per frame. + /// The ordinal of this Bezier curve for this timeline, between 0 and bezierCount - 1 (specified + /// in the constructor), inclusive. + /// Between 0 and frameCount - 1, inclusive. + /// The index of the value for the frame this curve is used for. + /// The time for the first key. + /// The value for the first key. + /// The time for the first Bezier handle. + /// The value for the first Bezier handle. + /// The time of the second Bezier handle. + /// The value for the second Bezier handle. + /// The time for the second key. + /// The value for the second key. + public void SetBezier (int bezier, int frame, int value, float time1, float value1, float cx1, float cy1, float cx2, + float cy2, float time2, float value2) { + + float[] curves = this.curves; + int i = FrameCount + bezier * BEZIER_SIZE; + if (value == 0) curves[frame] = BEZIER + i; + float tmpx = (time1 - cx1 * 2 + cx2) * 0.03f, tmpy = (value1 - cy1 * 2 + cy2) * 0.03f; + float dddx = ((cx1 - cx2) * 3 - time1 + time2) * 0.006f, dddy = ((cy1 - cy2) * 3 - value1 + value2) * 0.006f; + float ddx = tmpx * 2 + dddx, ddy = tmpy * 2 + dddy; + float dx = (cx1 - time1) * 0.3f + tmpx + dddx * 0.16666667f, dy = (cy1 - value1) * 0.3f + tmpy + dddy * 0.16666667f; + float x = time1 + dx, y = value1 + dy; + for (int 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; + } + } + + /// + /// Returns the Bezier interpolated value for the specified time. + /// The index into for the values of the frame before time. + /// The offset from frameIndex to the value this curve is used for. + /// The index of the Bezier segments. See . + public float GetBezierValue (float time, int frameIndex, int valueOffset, int i) { + float[] curves = this.curves; + if (curves[i] > time) { + float x = frames[frameIndex], y = frames[frameIndex + valueOffset]; + return y + (time - x) / (curves[i] - x) * (curves[i + 1] - y); + } + int 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 += FrameEntries; + { // scope added to prevent compile error "float x and y declared in enclosing scope" + float x = curves[n - 2], y = curves[n - 1]; + return y + (time - x) / (frames[frameIndex] - x) * (frames[frameIndex + valueOffset] - y); + } + } + } + + /// The base class for a that sets one property. + public abstract class CurveTimeline1 : CurveTimeline { + public const int ENTRIES = 2; + internal const int VALUE = 1; + + /// The maximum number of Bezier curves. See . + /// Unique identifiers for the properties the timeline modifies. + public CurveTimeline1 (int frameCount, int bezierCount, string propertyId) + : base(frameCount, bezierCount, propertyId) { + } + + public override int FrameEntries { + get { return ENTRIES; } + } + + /// Sets the time and value for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds + public void SetFrame (int frame, float time, float value) { + frame <<= 1; + frames[frame] = time; + frames[frame + VALUE] = value; + } + + /// Returns the interpolated value for the specified time. + public float GetCurveValue (float time) { + float[] frames = this.frames; + int i = frames.Length - 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 LINEAR: + float before = frames[i], value = frames[i + VALUE]; + return value + (time - before) / (frames[i + ENTRIES] - before) * (frames[i + ENTRIES + VALUE] - value); + case STEPPED: + return frames[i + VALUE]; + } + return GetBezierValue(time, i, VALUE, curveType - BEZIER); + } + + public float 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; + } + 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; + } + return current + value * alpha; + } + + public float 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; + } + return current; + } + float value = GetCurveValue(time); + if (blend == MixBlend.Setup) return setup + (value - setup) * alpha; + return current + (value - current) * alpha; + } + + public float 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; + } + return current; + } + if (blend == MixBlend.Setup) return setup + (value - setup) * alpha; + return current + (value - current) * alpha; + } + + public float GetScaleValue (float time, float alpha, MixBlend blend, MixDirection direction, float current, float setup) { + float[] frames = this.frames; + if (time < frames[0]) { + switch (blend) { + case MixBlend.Setup: + return setup; + case MixBlend.First: + return current + (setup - current) * alpha; + } + 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 + (Math.Abs(value) * Math.Sign(setup) - setup) * alpha; + case MixBlend.First: + case MixBlend.Replace: + return current + (Math.Abs(value) * Math.Sign(current) - current) * alpha; + } + } else { + float s; + switch (blend) { + case MixBlend.Setup: + s = Math.Abs(setup) * Math.Sign(value); + return s + (value - s) * alpha; + case MixBlend.First: + case MixBlend.Replace: + s = Math.Abs(current) * Math.Sign(value); + return s + (value - s) * alpha; + } + } + return current + (value - setup) * alpha; + } + } + + /// The base class for a which sets two properties. + public abstract class CurveTimeline2 : CurveTimeline { + public const int ENTRIES = 3; + internal const int VALUE1 = 1, VALUE2 = 2; + + /// The maximum number of Bezier curves. See . + /// Unique identifiers for the properties the timeline modifies. + public CurveTimeline2 (int frameCount, int bezierCount, string propertyId1, string propertyId2) + : base(frameCount, bezierCount, propertyId1, propertyId2) { + } + + public override int FrameEntries { + get { return ENTRIES; } + } + + /// Sets the time and values for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds. + public void SetFrame (int frame, float time, float value1, float value2) { + frame *= ENTRIES; + frames[frame] = time; + frames[frame + VALUE1] = value1; + frames[frame + VALUE2] = value2; + } + } + + /// Changes a bone's local . + public class RotateTimeline : CurveTimeline1, IBoneTimeline { + readonly int boneIndex; + + public RotateTimeline (int frameCount, int bezierCount, int boneIndex) + : base(frameCount, bezierCount, (int)Property.Rotate + "|" + boneIndex) { + this.boneIndex = boneIndex; + } + + public int BoneIndex { + get { + return boneIndex; + } + } + + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) { + Bone bone = skeleton.bones.Items[boneIndex]; + if (bone.active) bone.rotation = GetRelativeValue(time, alpha, blend, bone.rotation, bone.data.rotation); + } + } + + /// Changes a bone's local and . + public class TranslateTimeline : CurveTimeline2, IBoneTimeline { + readonly int boneIndex; + + public TranslateTimeline (int frameCount, int bezierCount, int boneIndex) + : base(frameCount, bezierCount, // + (int)Property.X + "|" + boneIndex, // + (int)Property.Y + "|" + boneIndex) { + this.boneIndex = boneIndex; + } + + public int BoneIndex { + get { + return boneIndex; + } + } + + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) { + Bone bone = skeleton.bones.Items[boneIndex]; + if (!bone.active) return; + + float[] frames = this.frames; + 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; + return; + } + return; + } + + float x, y; + GetCurveValue(out x, out y, time); // note: reference implementation has code inlined + + 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; + break; + } + } + + public void GetCurveValue (out float x, out float y, float time) { + int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; + switch (curveType) { + case LINEAR: + float before = frames[i]; + x = frames[i + VALUE1]; + y = frames[i + VALUE2]; + float t = (time - before) / (frames[i + ENTRIES] - before); + x += (frames[i + ENTRIES + VALUE1] - x) * t; + y += (frames[i + ENTRIES + VALUE2] - y) * t; + break; + case STEPPED: + x = frames[i + VALUE1]; + y = frames[i + VALUE2]; + break; + default: + x = GetBezierValue(time, i, VALUE1, curveType - BEZIER); + y = GetBezierValue(time, i, VALUE2, curveType + BEZIER_SIZE - BEZIER); + break; + } + } + } + + /// Changes a bone's local . + public class TranslateXTimeline : CurveTimeline1, IBoneTimeline { + readonly int boneIndex; + + public TranslateXTimeline (int frameCount, int bezierCount, int boneIndex) + : base(frameCount, bezierCount, (int)Property.X + "|" + boneIndex) { + this.boneIndex = boneIndex; + } + + public int BoneIndex { + get { + return boneIndex; + } + } + + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) { + Bone bone = skeleton.bones.Items[boneIndex]; + if (bone.active) bone.x = GetRelativeValue(time, alpha, blend, bone.x, bone.data.x); + } + } + + /// Changes a bone's local . + public class TranslateYTimeline : CurveTimeline1, IBoneTimeline { + readonly int boneIndex; + + public TranslateYTimeline (int frameCount, int bezierCount, int boneIndex) + : base(frameCount, bezierCount, (int)Property.Y + "|" + boneIndex) { + this.boneIndex = boneIndex; + } + + public int BoneIndex { + get { + return boneIndex; + } + } + + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) { + Bone bone = skeleton.bones.Items[boneIndex]; + if (bone.active) bone.y = GetRelativeValue(time, alpha, blend, bone.y, bone.data.y); + } + } + + /// Changes a bone's local and . + public class ScaleTimeline : CurveTimeline2, IBoneTimeline { + readonly int boneIndex; + + public ScaleTimeline (int frameCount, int bezierCount, int boneIndex) + : base(frameCount, bezierCount, // + (int)Property.ScaleX + "|" + boneIndex, // + (int)Property.ScaleY + "|" + boneIndex) { + this.boneIndex = boneIndex; + } + + public int BoneIndex { + get { + return boneIndex; + } + } + + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) { + Bone bone = skeleton.bones.Items[boneIndex]; + if (!bone.active) return; + + float[] frames = this.frames; + 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; + return; + } + return; + } + + float x, y; + int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; + switch (curveType) { + case LINEAR: + float before = frames[i]; + x = frames[i + VALUE1]; + y = frames[i + VALUE2]; + float t = (time - before) / (frames[i + ENTRIES] - before); + x += (frames[i + ENTRIES + VALUE1] - x) * t; + y += (frames[i + ENTRIES + VALUE2] - y) * t; + break; + case STEPPED: + x = frames[i + VALUE1]; + y = frames[i + VALUE2]; + break; + default: + x = GetBezierValue(time, i, VALUE1, curveType - BEZIER); + y = GetBezierValue(time, i, VALUE2, curveType + BEZIER_SIZE - BEZIER); + break; + } + 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 { + // Mixing out uses sign of setup or current pose, else use sign of key. + float bx, by; + if (direction == MixDirection.Out) { + switch (blend) { + case MixBlend.Setup: + bx = bone.data.scaleX; + by = bone.data.scaleY; + bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha; + bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha; + break; + case MixBlend.First: + case MixBlend.Replace: + bx = bone.scaleX; + by = bone.scaleY; + bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha; + bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha; + break; + case MixBlend.Add: + bone.scaleX += (x - bone.data.scaleX) * alpha; + bone.scaleY += (y - bone.data.scaleY) * alpha; + break; + } + } else { + switch (blend) { + case MixBlend.Setup: + bx = Math.Abs(bone.data.scaleX) * Math.Sign(x); + by = Math.Abs(bone.data.scaleY) * Math.Sign(y); + bone.scaleX = bx + (x - bx) * alpha; + bone.scaleY = by + (y - by) * alpha; + break; + case MixBlend.First: + case MixBlend.Replace: + bx = Math.Abs(bone.scaleX) * Math.Sign(x); + by = Math.Abs(bone.scaleY) * Math.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; + break; + } + } + } + } + } + + /// Changes a bone's local . + public class ScaleXTimeline : CurveTimeline1, IBoneTimeline { + readonly int boneIndex; + + public ScaleXTimeline (int frameCount, int bezierCount, int boneIndex) + : base(frameCount, bezierCount, (int)Property.ScaleX + "|" + boneIndex) { + this.boneIndex = boneIndex; + } + + public int BoneIndex { + get { + return boneIndex; + } + } + + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) { + Bone bone = skeleton.bones.Items[boneIndex]; + if (bone.active) bone.scaleX = GetScaleValue(time, alpha, blend, direction, bone.scaleX, bone.data.scaleX); + } + } + + /// Changes a bone's local . + public class ScaleYTimeline : CurveTimeline1, IBoneTimeline { + readonly int boneIndex; + + public ScaleYTimeline (int frameCount, int bezierCount, int boneIndex) + : base(frameCount, bezierCount, (int)Property.ScaleY + "|" + boneIndex) { + this.boneIndex = boneIndex; + } + + public int BoneIndex { + get { + return boneIndex; + } + } + + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) { + Bone bone = skeleton.bones.Items[boneIndex]; + if (bone.active) bone.scaleY = GetScaleValue(time, alpha, blend, direction, bone.scaleY, bone.data.scaleY); + } + } + + /// Changes a bone's local and . + public class ShearTimeline : CurveTimeline2, IBoneTimeline { + readonly int boneIndex; + + public ShearTimeline (int frameCount, int bezierCount, int boneIndex) + : base(frameCount, bezierCount, // + (int)Property.ShearX + "|" + boneIndex, // + (int)Property.ShearY + "|" + boneIndex) { + this.boneIndex = boneIndex; + } + + public int BoneIndex { + get { + return boneIndex; + } + } + + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) { + Bone bone = skeleton.bones.Items[boneIndex]; + if (!bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) { // Time is before first frame. + 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; + return; + } + return; + } + + float x, y; + int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; + switch (curveType) { + case LINEAR: + float before = frames[i]; + x = frames[i + VALUE1]; + y = frames[i + VALUE2]; + float t = (time - before) / (frames[i + ENTRIES] - before); + x += (frames[i + ENTRIES + VALUE1] - x) * t; + y += (frames[i + ENTRIES + VALUE2] - y) * t; + break; + case STEPPED: + x = frames[i + VALUE1]; + y = frames[i + VALUE2]; + break; + default: + x = GetBezierValue(time, i, VALUE1, curveType - BEZIER); + y = GetBezierValue(time, i, VALUE2, curveType + BEZIER_SIZE - BEZIER); + break; + } + + 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; + break; + } + } + } + + /// Changes a bone's local . + public class ShearXTimeline : CurveTimeline1, IBoneTimeline { + readonly int boneIndex; + + public ShearXTimeline (int frameCount, int bezierCount, int boneIndex) + : base(frameCount, bezierCount, (int)Property.ShearX + "|" + boneIndex) { + this.boneIndex = boneIndex; + } + + public int BoneIndex { + get { + return boneIndex; + } + } + + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) { + Bone bone = skeleton.bones.Items[boneIndex]; + if (bone.active) bone.shearX = GetRelativeValue(time, alpha, blend, bone.shearX, bone.data.shearX); + } + } + + /// Changes a bone's local . + public class ShearYTimeline : CurveTimeline1, IBoneTimeline { + readonly int boneIndex; + + public ShearYTimeline (int frameCount, int bezierCount, int boneIndex) + : base(frameCount, bezierCount, (int)Property.ShearY + "|" + boneIndex) { + this.boneIndex = boneIndex; + } + + public int BoneIndex { + get { + return boneIndex; + } + } + + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) { + Bone bone = skeleton.bones.Items[boneIndex]; + if (bone.active) bone.shearY = GetRelativeValue(time, alpha, blend, bone.shearY, bone.data.shearY); + } + } + + /// Changes a bone's . + + public class InheritTimeline : Timeline, IBoneTimeline { + public const int ENTRIES = 2; + public const int INHERIT = 1; + + readonly int boneIndex; + + public InheritTimeline (int frameCount, int boneIndex) + : base(frameCount, (int)Property.Inherit + "|" + boneIndex) { + this.boneIndex = boneIndex; + } + + public int BoneIndex { + get { + return boneIndex; + } + } + + public override int FrameEntries { + get { return ENTRIES; } + } + + /// Sets the transform mode for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds. + public void SetFrame (int frame, float time, Inherit inherit) { + frame *= ENTRIES; + frames[frame] = time; + frames[frame + INHERIT] = (int)inherit; + } + + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) { + + Bone bone = skeleton.bones.Items[boneIndex]; + if (!bone.active) return; + + if (direction == MixDirection.Out) { + if (blend == MixBlend.Setup) bone.inherit = bone.data.inherit; + return; + } + + float[] frames = this.frames; + if (time < frames[0]) { + if (blend == MixBlend.Setup || blend == MixBlend.First) bone.inherit = bone.data.inherit; + return; + } + bone.inherit = InheritEnum.Values[(int)frames[Search(frames, time, ENTRIES) + INHERIT]]; + } + } + + /// Changes a slot's . + public class RGBATimeline : CurveTimeline, ISlotTimeline { + public const int ENTRIES = 5; + protected const int R = 1, G = 2, B = 3, A = 4; + + readonly int slotIndex; + + public RGBATimeline (int frameCount, int bezierCount, int slotIndex) + : base(frameCount, bezierCount, // + (int)Property.RGB + "|" + slotIndex, // + (int)Property.Alpha + "|" + slotIndex) { + this.slotIndex = slotIndex; + } + public override int FrameEntries { + get { return ENTRIES; } + } + + public int SlotIndex { + get { + return slotIndex; + } + } + + /// Sets the time and color for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds. + public void 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; + } + + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) { + Slot slot = skeleton.slots.Items[slotIndex]; + if (!slot.bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) { + SlotData setup = slot.data; + switch (blend) { + case MixBlend.Setup: + slot.r = setup.r; + slot.g = setup.g; + slot.b = setup.b; + slot.a = setup.a; + return; + case MixBlend.First: + slot.r += (setup.r - slot.r) * alpha; + slot.g += (setup.g - slot.g) * alpha; + slot.b += (setup.b - slot.b) * alpha; + slot.a += (setup.a - slot.a) * alpha; + slot.ClampColor(); + return; + } + return; + } + + float r, g, b, a; + int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; + switch (curveType) { + case LINEAR: + float before = frames[i]; + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + a = frames[i + A]; + float t = (time - before) / (frames[i + ENTRIES] - before); + r += (frames[i + ENTRIES + R] - r) * t; + g += (frames[i + ENTRIES + G] - g) * t; + b += (frames[i + ENTRIES + B] - b) * t; + a += (frames[i + ENTRIES + A] - a) * t; + break; + case STEPPED: + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + a = frames[i + A]; + break; + default: + r = GetBezierValue(time, i, R, curveType - BEZIER); + g = GetBezierValue(time, i, G, curveType + BEZIER_SIZE - BEZIER); + b = GetBezierValue(time, i, B, curveType + BEZIER_SIZE * 2 - BEZIER); + a = GetBezierValue(time, i, A, curveType + BEZIER_SIZE * 3 - BEZIER); + break; + } + + if (alpha == 1) { + slot.r = r; + slot.g = g; + slot.b = b; + slot.a = a; + } else { + float br, bg, bb, ba; + if (blend == MixBlend.Setup) { + br = slot.data.r; + bg = slot.data.g; + bb = slot.data.b; + ba = slot.data.a; + } else { + br = slot.r; + bg = slot.g; + bb = slot.b; + ba = slot.a; + } + slot.r = br + (r - br) * alpha; + slot.g = bg + (g - bg) * alpha; + slot.b = bb + (b - bb) * alpha; + slot.a = ba + (a - ba) * alpha; + } + slot.ClampColor(); + } + } + + /// Changes the RGB for a slot's . + public class RGBTimeline : CurveTimeline, ISlotTimeline { + public const int ENTRIES = 4; + protected const int R = 1, G = 2, B = 3; + + readonly int slotIndex; + + public RGBTimeline (int frameCount, int bezierCount, int slotIndex) + : base(frameCount, bezierCount, // + (int)Property.RGB + "|" + slotIndex) { + this.slotIndex = slotIndex; + } + + public override int FrameEntries { + get { return ENTRIES; } + } + + public int SlotIndex { + get { + return slotIndex; + } + } + + /// Sets the time and color for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds. + public void SetFrame (int frame, float time, float r, float g, float b) { + frame <<= 2; + frames[frame] = time; + frames[frame + R] = r; + frames[frame + G] = g; + frames[frame + B] = b; + } + + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) { + Slot slot = skeleton.slots.Items[slotIndex]; + if (!slot.bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) { + SlotData setup = slot.data; + switch (blend) { + case MixBlend.Setup: + slot.r = setup.r; + slot.g = setup.g; + slot.b = setup.b; + return; + case MixBlend.First: + slot.r += (setup.r - slot.r) * alpha; + slot.g += (setup.g - slot.g) * alpha; + slot.b += (setup.b - slot.b) * alpha; + slot.ClampColor(); + return; + } + return; + } + + float r, g, b; + int i = Search(frames, time, ENTRIES), curveType = (int)curves[i >> 2]; + switch (curveType) { + case LINEAR: + float before = frames[i]; + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + float t = (time - before) / (frames[i + ENTRIES] - before); + r += (frames[i + ENTRIES + R] - r) * t; + g += (frames[i + ENTRIES + G] - g) * t; + b += (frames[i + ENTRIES + B] - b) * t; + break; + case STEPPED: + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + break; + default: + r = GetBezierValue(time, i, R, curveType - BEZIER); + g = GetBezierValue(time, i, G, curveType + BEZIER_SIZE - BEZIER); + b = GetBezierValue(time, i, B, curveType + BEZIER_SIZE * 2 - BEZIER); + break; + } + + if (alpha == 1) { + slot.r = r; + slot.g = g; + slot.b = b; + } else { + float br, bg, bb; + if (blend == MixBlend.Setup) { + SlotData setup = slot.data; + br = setup.r; + bg = setup.g; + bb = setup.b; + } else { + br = slot.r; + bg = slot.g; + bb = slot.b; + } + slot.r = br + (r - br) * alpha; + slot.g = bg + (g - bg) * alpha; + slot.b = bb + (b - bb) * alpha; + } + slot.ClampColor(); + } + } + + /// Changes the alpha for a slot's . + public class AlphaTimeline : CurveTimeline1, ISlotTimeline { + readonly int slotIndex; + + public AlphaTimeline (int frameCount, int bezierCount, int slotIndex) + : base(frameCount, bezierCount, (int)Property.Alpha + "|" + slotIndex) { + this.slotIndex = slotIndex; + } + + public int SlotIndex { + get { + return slotIndex; + } + } + + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) { + Slot slot = skeleton.slots.Items[slotIndex]; + if (!slot.bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) { + SlotData setup = slot.data; + switch (blend) { + case MixBlend.Setup: + slot.a = setup.a; + return; + case MixBlend.First: + slot.a += (setup.a - slot.a) * alpha; + slot.ClampColor(); + return; + } + return; + } + + float a = GetCurveValue(time); + if (alpha == 1) + slot.a = a; + else { + if (blend == MixBlend.Setup) slot.a = slot.data.a; + slot.a += (a - slot.a) * alpha; + } + slot.ClampColor(); + } + } + + /// Changes a slot's and for two color tinting. + public class RGBA2Timeline : CurveTimeline, ISlotTimeline { + public const int ENTRIES = 8; + protected const int R = 1, G = 2, B = 3, A = 4, R2 = 5, G2 = 6, B2 = 7; + + readonly int slotIndex; + + public RGBA2Timeline (int frameCount, int bezierCount, int slotIndex) + : base(frameCount, bezierCount, // + (int)Property.RGB + "|" + slotIndex, // + (int)Property.Alpha + "|" + slotIndex, // + (int)Property.RGB2 + "|" + slotIndex) { + this.slotIndex = slotIndex; + } + + public override int FrameEntries { + get { + return ENTRIES; + } + } + + /// + /// The index of the slot in that will be changed when this timeline is applied. The + /// must have a dark color available. + public int SlotIndex { + get { + return slotIndex; + } + } + + /// Sets the time, light color, and dark color for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds. + public void SetFrame (int frame, float time, float r, float g, float b, float a, float r2, float g2, float b2) { + frame <<= 3; + 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; + } + + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) { + Slot slot = skeleton.slots.Items[slotIndex]; + if (!slot.bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) { + SlotData setup = slot.data; + switch (blend) { + case MixBlend.Setup: + slot.r = setup.r; + slot.g = setup.g; + slot.b = setup.b; + slot.a = setup.a; + slot.ClampColor(); + slot.r2 = setup.r2; + slot.g2 = setup.g2; + slot.b2 = setup.b2; + slot.ClampSecondColor(); + return; + case MixBlend.First: + slot.r += (slot.r - setup.r) * alpha; + slot.g += (slot.g - setup.g) * alpha; + slot.b += (slot.b - setup.b) * alpha; + slot.a += (slot.a - setup.a) * alpha; + slot.ClampColor(); + slot.r2 += (slot.r2 - setup.r2) * alpha; + slot.g2 += (slot.g2 - setup.g2) * alpha; + slot.b2 += (slot.b2 - setup.b2) * alpha; + slot.ClampSecondColor(); + return; + } + return; + } + + float r, g, b, a, r2, g2, b2; + int i = Search(frames, time, ENTRIES), curveType = (int)curves[i >> 3]; + switch (curveType) { + case LINEAR: + float before = frames[i]; + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + a = frames[i + A]; + r2 = frames[i + R2]; + g2 = frames[i + G2]; + b2 = frames[i + B2]; + float t = (time - before) / (frames[i + ENTRIES] - before); + r += (frames[i + ENTRIES + R] - r) * t; + g += (frames[i + ENTRIES + G] - g) * t; + b += (frames[i + ENTRIES + B] - b) * t; + a += (frames[i + ENTRIES + A] - a) * t; + r2 += (frames[i + ENTRIES + R2] - r2) * t; + g2 += (frames[i + ENTRIES + G2] - g2) * t; + b2 += (frames[i + ENTRIES + B2] - b2) * t; + break; + case STEPPED: + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + a = frames[i + A]; + r2 = frames[i + R2]; + g2 = frames[i + G2]; + b2 = frames[i + B2]; + break; + default: + r = GetBezierValue(time, i, R, curveType - BEZIER); + g = GetBezierValue(time, i, G, curveType + BEZIER_SIZE - BEZIER); + b = GetBezierValue(time, i, B, curveType + BEZIER_SIZE * 2 - BEZIER); + a = GetBezierValue(time, i, A, curveType + BEZIER_SIZE * 3 - BEZIER); + r2 = GetBezierValue(time, i, R2, curveType + BEZIER_SIZE * 4 - BEZIER); + g2 = GetBezierValue(time, i, G2, curveType + BEZIER_SIZE * 5 - BEZIER); + b2 = GetBezierValue(time, i, B2, curveType + BEZIER_SIZE * 6 - BEZIER); + break; + } + + if (alpha == 1) { + slot.r = r; + slot.g = g; + slot.b = b; + slot.a = a; + slot.r2 = r2; + slot.g2 = g2; + slot.b2 = b2; + } else { + float br, bg, bb, ba, br2, bg2, bb2; + if (blend == MixBlend.Setup) { + br = slot.data.r; + bg = slot.data.g; + bb = slot.data.b; + ba = slot.data.a; + br2 = slot.data.r2; + bg2 = slot.data.g2; + bb2 = slot.data.b2; + } else { + br = slot.r; + bg = slot.g; + bb = slot.b; + ba = slot.a; + br2 = slot.r2; + bg2 = slot.g2; + bb2 = slot.b2; + } + slot.r = br + (r - br) * alpha; + slot.g = bg + (g - bg) * alpha; + slot.b = bb + (b - bb) * alpha; + slot.a = ba + (a - ba) * alpha; + slot.r2 = br2 + (r2 - br2) * alpha; + slot.g2 = bg2 + (g2 - bg2) * alpha; + slot.b2 = bb2 + (b2 - bb2) * alpha; + } + slot.ClampColor(); + slot.ClampSecondColor(); + } + } + + /// Changes the RGB for a slot's and for two color tinting. + public class RGB2Timeline : CurveTimeline, ISlotTimeline { + public const int ENTRIES = 7; + protected const int R = 1, G = 2, B = 3, R2 = 4, G2 = 5, B2 = 6; + + readonly int slotIndex; + + public RGB2Timeline (int frameCount, int bezierCount, int slotIndex) + : base(frameCount, bezierCount, // + (int)Property.RGB + "|" + slotIndex, // + (int)Property.RGB2 + "|" + slotIndex) { + this.slotIndex = slotIndex; + } + + public override int FrameEntries { + get { + return ENTRIES; + } + } + + /// + /// The index of the slot in that will be changed when this timeline is applied. The + /// must have a dark color available. + public int SlotIndex { + get { + return slotIndex; + } + } + + /// Sets the time, light color, and dark color for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds. + public void 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; + } + + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) { + Slot slot = skeleton.slots.Items[slotIndex]; + if (!slot.bone.active) return; + + float[] frames = this.frames; + if (time < frames[0]) { + SlotData setup = slot.data; + switch (blend) { + case MixBlend.Setup: + slot.r = setup.r; + slot.g = setup.g; + slot.b = setup.b; + slot.ClampColor(); + slot.r2 = setup.r2; + slot.g2 = setup.g2; + slot.b2 = setup.b2; + slot.ClampSecondColor(); + return; + case MixBlend.First: + slot.r += (slot.r - setup.r) * alpha; + slot.g += (slot.g - setup.g) * alpha; + slot.b += (slot.b - setup.b) * alpha; + slot.ClampColor(); + slot.r2 += (slot.r2 - setup.r2) * alpha; + slot.g2 += (slot.g2 - setup.g2) * alpha; + slot.b2 += (slot.b2 - setup.b2) * alpha; + slot.ClampSecondColor(); + return; + } + return; + } + + float r, g, b, r2, g2, b2; + int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; + switch (curveType) { + case LINEAR: + float before = frames[i]; + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + r2 = frames[i + R2]; + g2 = frames[i + G2]; + b2 = frames[i + B2]; + float t = (time - before) / (frames[i + ENTRIES] - before); + r += (frames[i + ENTRIES + R] - r) * t; + g += (frames[i + ENTRIES + G] - g) * t; + b += (frames[i + ENTRIES + B] - b) * t; + r2 += (frames[i + ENTRIES + R2] - r2) * t; + g2 += (frames[i + ENTRIES + G2] - g2) * t; + b2 += (frames[i + ENTRIES + B2] - b2) * t; + break; + case STEPPED: + r = frames[i + R]; + g = frames[i + G]; + b = frames[i + B]; + r2 = frames[i + R2]; + g2 = frames[i + G2]; + b2 = frames[i + B2]; + break; + default: + r = GetBezierValue(time, i, R, curveType - BEZIER); + g = GetBezierValue(time, i, G, curveType + BEZIER_SIZE - BEZIER); + b = GetBezierValue(time, i, B, curveType + BEZIER_SIZE * 2 - BEZIER); + r2 = GetBezierValue(time, i, R2, curveType + BEZIER_SIZE * 3 - BEZIER); + g2 = GetBezierValue(time, i, G2, curveType + BEZIER_SIZE * 4 - BEZIER); + b2 = GetBezierValue(time, i, B2, curveType + BEZIER_SIZE * 5 - BEZIER); + break; + } + + if (alpha == 1) { + slot.r = r; + slot.g = g; + slot.b = b; + slot.r2 = r2; + slot.g2 = g2; + slot.b2 = b2; + } else { + float br, bg, bb, br2, bg2, bb2; + if (blend == MixBlend.Setup) { + SlotData setup = slot.data; + br = setup.r; + bg = setup.g; + bb = setup.b; + br2 = setup.r2; + bg2 = setup.g2; + bb2 = setup.b2; + } else { + br = slot.r; + bg = slot.g; + bb = slot.b; + br2 = slot.r2; + bg2 = slot.g2; + bb2 = slot.b2; + } + slot.r = br + (r - br) * alpha; + slot.g = bg + (g - bg) * alpha; + slot.b = bb + (b - bb) * alpha; + slot.r2 = br2 + (r2 - br2) * alpha; + slot.g2 = bg2 + (g2 - bg2) * alpha; + slot.b2 = bb2 + (b2 - bb2) * alpha; + } + slot.ClampColor(); + slot.ClampSecondColor(); + } + } + + /// Changes a slot's . + public class AttachmentTimeline : Timeline, ISlotTimeline { + readonly int slotIndex; + readonly string[] attachmentNames; + + public AttachmentTimeline (int frameCount, int slotIndex) + : base(frameCount, (int)Property.Attachment + "|" + slotIndex) { + this.slotIndex = slotIndex; + attachmentNames = new String[frameCount]; + } + + public int SlotIndex { + get { + return slotIndex; + } + } + + /// The attachment name for each frame. May contain null values to clear the attachment. + public string[] AttachmentNames { + get { + return attachmentNames; + } + } + + /// Sets the time and attachment name for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds. + public void SetFrame (int frame, float time, String attachmentName) { + frames[frame] = time; + attachmentNames[frame] = attachmentName; + } + + public override void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) { + Slot slot = skeleton.slots.Items[slotIndex]; + if (!slot.bone.active) return; + + if (direction == MixDirection.Out) { + if (blend == MixBlend.Setup) SetAttachment(skeleton, slot, slot.data.attachmentName); + return; + } + + float[] frames = this.frames; + if (time < frames[0]) { + if (blend == MixBlend.Setup || blend == MixBlend.First) SetAttachment(skeleton, slot, slot.data.attachmentName); + return; + } + + SetAttachment(skeleton, slot, attachmentNames[Search(frames, time)]); + } + + private void SetAttachment (Skeleton skeleton, Slot slot, string attachmentName) { + slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName); + } + } + + /// Changes a slot's to deform a . + public class DeformTimeline : CurveTimeline, ISlotTimeline { + readonly int slotIndex; + readonly VertexAttachment attachment; + internal float[][] vertices; + + public DeformTimeline (int frameCount, int bezierCount, int slotIndex, VertexAttachment attachment) + : base(frameCount, bezierCount, (int)Property.Deform + "|" + slotIndex + "|" + attachment.Id) { + this.slotIndex = slotIndex; + this.attachment = attachment; + vertices = new float[frameCount][]; + } + + public int SlotIndex { + get { + return slotIndex; + } + } + /// The attachment that will be deformed. + /// + public VertexAttachment Attachment { + get { + return attachment; + } + } + + /// The vertices for each frame. + public float[][] Vertices { + get { + return vertices; + } + } + + /// Sets the time and vertices for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds. + /// Vertex positions for an unweighted VertexAttachment, or deform offsets if it has weights. + public void SetFrame (int frame, float time, float[] vertices) { + frames[frame] = time; + this.vertices[frame] = vertices; + } + + /// Ignored (0 is used for a deform timeline). + /// Ignored (1 is used for a deform timeline). + public void setBezier (int bezier, int frame, int value, float time1, float value1, float cx1, float cy1, float cx2, + float cy2, float time2, float value2) { + float[] curves = this.curves; + int i = FrameCount + bezier * BEZIER_SIZE; + if (value == 0) curves[frame] = BEZIER + i; + float tmpx = (time1 - cx1 * 2 + cx2) * 0.03f, tmpy = cy2 * 0.03f - cy1 * 0.06f; + float dddx = ((cx1 - cx2) * 3 - time1 + time2) * 0.006f, dddy = (cy1 - cy2 + 0.33333333f) * 0.018f; + float ddx = tmpx * 2 + dddx, ddy = tmpy * 2 + dddy; + float dx = (cx1 - time1) * 0.3f + tmpx + dddx * 0.16666667f, dy = cy1 * 0.3f + tmpy + dddy * 0.16666667f; + float x = time1 + dx, y = dy; + for (int 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; + } + } + + /// Returns the interpolated percentage for the specified time. + /// The frame before time. + private float GetCurvePercent (float time, int frame) { + float[] curves = this.curves; + int i = (int)curves[frame]; + switch (i) { + case LINEAR: + float x = frames[frame]; + return (time - x) / (frames[frame + FrameEntries] - x); + case STEPPED: + return 0; + } + i -= BEZIER; + if (curves[i] > time) { + float x = frames[frame]; + return curves[i + 1] * (time - x) / (curves[i] - x); + } + int 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); + } + } + { // scope added to prevent compile error "float x and y declared in enclosing scope" + float x = curves[n - 2], y = curves[n - 1]; + return y + (1 - y) * (time - x) / (frames[frame + FrameEntries] - x); + } + } + + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) { + + Slot slot = skeleton.slots.Items[slotIndex]; + if (!slot.bone.active) return; + VertexAttachment vertexAttachment = slot.attachment as VertexAttachment; + if (vertexAttachment == null || vertexAttachment.TimelineAttachment != attachment) return; + + ExposedList deformArray = slot.deform; + if (deformArray.Count == 0) blend = MixBlend.Setup; + + float[][] vertices = this.vertices; + int vertexCount = vertices[0].Length; + + float[] deform; + + float[] frames = this.frames; + if (time < frames[0]) { + switch (blend) { + case MixBlend.Setup: + deformArray.Clear(); + return; + case MixBlend.First: + if (alpha == 1) { + deformArray.Clear(); + return; + } + + // Ensure size and preemptively set count. + if (deformArray.Capacity < vertexCount) deformArray.Capacity = vertexCount; + deformArray.Count = vertexCount; + deform = deformArray.Items; + + if (vertexAttachment.bones == null) { + // Unweighted vertex positions. + float[] setupVertices = vertexAttachment.vertices; + for (int i = 0; i < vertexCount; i++) + deform[i] += (setupVertices[i] - deform[i]) * alpha; + } else { + // Weighted deform offsets. + alpha = 1 - alpha; + for (int i = 0; i < vertexCount; i++) + deform[i] *= alpha; + } + return; + } + return; + } + + // Ensure size and preemptively set count. + if (deformArray.Capacity < vertexCount) deformArray.Capacity = vertexCount; + deformArray.Count = vertexCount; + deform = deformArray.Items; + + if (time >= frames[frames.Length - 1]) { // Time is after last frame. + float[] lastVertices = vertices[frames.Length - 1]; + if (alpha == 1) { + if (blend == MixBlend.Add) { + if (vertexAttachment.bones == null) { + // Unweighted vertex positions, no alpha. + float[] setupVertices = vertexAttachment.vertices; + for (int i = 0; i < vertexCount; i++) + deform[i] += lastVertices[i] - setupVertices[i]; + } else { + // Weighted deform offsets, no alpha. + for (int i = 0; i < vertexCount; i++) + deform[i] += lastVertices[i]; + } + } else { + // Vertex positions or deform offsets, no alpha. + Array.Copy(lastVertices, 0, deform, 0, vertexCount); + } + } else { + switch (blend) { + case MixBlend.Setup: { + if (vertexAttachment.bones == null) { + // Unweighted vertex positions, with alpha. + float[] setupVertices = vertexAttachment.vertices; + for (int i = 0; i < vertexCount; i++) { + float setup = setupVertices[i]; + deform[i] = setup + (lastVertices[i] - setup) * alpha; + } + } else { + // Weighted deform offsets, with alpha. + for (int 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 (int i = 0; i < vertexCount; i++) + deform[i] += (lastVertices[i] - deform[i]) * alpha; + break; + case MixBlend.Add: + if (vertexAttachment.bones == null) { + // Unweighted vertex positions, no alpha. + float[] setupVertices = vertexAttachment.vertices; + for (int i = 0; i < vertexCount; i++) + deform[i] += (lastVertices[i] - setupVertices[i]) * alpha; + } else { + // Weighted deform offsets, alpha. + for (int i = 0; i < vertexCount; i++) + deform[i] += lastVertices[i] * alpha; + } + break; + } + } + return; + } + + int frame = Search(frames, time); + float percent = GetCurvePercent(time, frame); + float[] prevVertices = vertices[frame]; + float[] nextVertices = vertices[frame + 1]; + + if (alpha == 1) { + if (blend == MixBlend.Add) { + if (vertexAttachment.bones == null) { + // Unweighted vertex positions, no alpha. + float[] setupVertices = vertexAttachment.vertices; + for (int 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 (int 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 (int i = 0; i < vertexCount; i++) { + float prev = prevVertices[i]; + deform[i] = prev + (nextVertices[i] - prev) * percent; + } + } + } else { + switch (blend) { + case MixBlend.Setup: { + if (vertexAttachment.bones == null) { + // Unweighted vertex positions, with alpha. + float[] setupVertices = vertexAttachment.vertices; + for (int 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 (int 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 (int i = 0; i < vertexCount; i++) { + float prev = prevVertices[i]; + deform[i] += (prev + (nextVertices[i] - prev) * percent - deform[i]) * alpha; + } + break; + } + case MixBlend.Add: + if (vertexAttachment.bones == null) { + // Unweighted vertex positions, with alpha. + float[] setupVertices = vertexAttachment.vertices; + for (int 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 (int i = 0; i < vertexCount; i++) { + float prev = prevVertices[i]; + deform[i] += (prev + (nextVertices[i] - prev) * percent) * alpha; + } + } + break; + } + } + } + } + + /// Fires an when specific animation times are reached. + public class EventTimeline : Timeline { + readonly static string[] propertyIds = { ((int)Property.Event).ToString() }; + readonly Event[] events; + + public EventTimeline (int frameCount) + : base(frameCount, propertyIds) { + events = new Event[frameCount]; + } + + /// The event for each frame. + public Event[] Events { + get { + return events; + } + } + + /// Sets the time and event for the specified frame. + /// Between 0 and frameCount, inclusive. + public void SetFrame (int frame, Event e) { + frames[frame] = e.time; + events[frame] = e; + } + + /// Fires events for frames > lastTime and <= time. + public override void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, + MixBlend blend, MixDirection direction) { + + if (firedEvents == null) return; + + float[] frames = this.frames; + int frameCount = frames.Length; + + if (lastTime > time) { // Apply after lastTime for looped animations. + Apply(skeleton, lastTime, int.MaxValue, firedEvents, alpha, blend, direction); + lastTime = -1f; + } else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame. + return; + if (time < frames[0]) return; + + int i; + if (lastTime < frames[0]) + i = 0; + else { + i = Search(frames, lastTime) + 1; + float frameTime = frames[i]; + while (i > 0) { // Fire multiple events with the same frame. + if (frames[i - 1] != frameTime) break; + i--; + } + } + for (; i < frameCount && time >= frames[i]; i++) + firedEvents.Add(events[i]); + } + } + + /// Changes a skeleton's . + public class DrawOrderTimeline : Timeline { + static readonly string[] propertyIds = { ((int)Property.DrawOrder).ToString() }; + + readonly int[][] drawOrders; + + public DrawOrderTimeline (int frameCount) + : base(frameCount, propertyIds) { + drawOrders = new int[frameCount][]; + } + + /// The draw order for each frame. + /// . + public int[][] DrawOrders { + get { + return drawOrders; + } + } + + /// Sets the time and draw order for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds. + /// For each slot in , the index of the slot in the new draw order. May be null to use + /// setup pose draw order. + public void SetFrame (int frame, float time, int[] drawOrder) { + frames[frame] = time; + drawOrders[frame] = drawOrder; + } + + public override void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) { + + if (direction == MixDirection.Out) { + if (blend == MixBlend.Setup) Array.Copy(skeleton.slots.Items, 0, skeleton.drawOrder.Items, 0, skeleton.slots.Count); + return; + } + + float[] frames = this.frames; + if (time < frames[0]) { + if (blend == MixBlend.Setup || blend == MixBlend.First) Array.Copy(skeleton.slots.Items, 0, skeleton.drawOrder.Items, 0, skeleton.slots.Count); + return; + } + + int[] drawOrderToSetupIndex = drawOrders[Search(frames, time)]; + if (drawOrderToSetupIndex == null) + Array.Copy(skeleton.slots.Items, 0, skeleton.drawOrder.Items, 0, skeleton.slots.Count); + else { + Slot[] slots = skeleton.slots.Items; + Slot[] drawOrder = skeleton.drawOrder.Items; + for (int i = 0, n = drawOrderToSetupIndex.Length; i < n; i++) + drawOrder[i] = slots[drawOrderToSetupIndex[i]]; + } + } + } + + /// Changes an IK constraint's , , + /// , , and . + public class IkConstraintTimeline : CurveTimeline { + public const int ENTRIES = 6; + private const int MIX = 1, SOFTNESS = 2, BEND_DIRECTION = 3, COMPRESS = 4, STRETCH = 5; + + readonly int constraintIndex; + + public IkConstraintTimeline (int frameCount, int bezierCount, int ikConstraintIndex) + : base(frameCount, bezierCount, (int)Property.IkConstraint + "|" + ikConstraintIndex) { + this.constraintIndex = ikConstraintIndex; + } + + public override int FrameEntries { + get { + return ENTRIES; + } + } + + /// The index of the IK constraint in that will be changed when this timeline is + /// applied. + public int IkConstraintIndex { + get { + return constraintIndex; + } + } + + /// Sets the time, mix, softness, bend direction, compress, and stretch for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds. + /// 1 or -1. + public void 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] = bendDirection; + frames[frame + COMPRESS] = compress ? 1 : 0; + frames[frame + STRETCH] = stretch ? 1 : 0; + } + + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) { + IkConstraint constraint = skeleton.ikConstraints.Items[constraintIndex]; + if (!constraint.active) return; + + float[] frames = this.frames; + 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; + } + return; + } + + float mix, softness; + int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; + switch (curveType) { + case LINEAR: + float before = frames[i]; + mix = frames[i + MIX]; + softness = frames[i + SOFTNESS]; + float t = (time - before) / (frames[i + ENTRIES] - before); + mix += (frames[i + ENTRIES + MIX] - mix) * t; + softness += (frames[i + ENTRIES + SOFTNESS] - softness) * t; + break; + case STEPPED: + mix = frames[i + MIX]; + softness = frames[i + SOFTNESS]; + break; + default: + mix = GetBezierValue(time, i, MIX, curveType - BEZIER); + softness = GetBezierValue(time, i, SOFTNESS, curveType + BEZIER_SIZE - BEZIER); + break; + } + + 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 = (int)frames[i + BEND_DIRECTION]; + constraint.compress = frames[i + COMPRESS] != 0; + constraint.stretch = frames[i + STRETCH] != 0; + } + } else { + constraint.mix += (mix - constraint.mix) * alpha; + constraint.softness += (softness - constraint.softness) * alpha; + if (direction == MixDirection.In) { + constraint.bendDirection = (int)frames[i + BEND_DIRECTION]; + constraint.compress = frames[i + COMPRESS] != 0; + constraint.stretch = frames[i + STRETCH] != 0; + } + } + } + } + + /// Changes a transform constraint's mixes. + public class TransformConstraintTimeline : CurveTimeline { + public const int ENTRIES = 7; + private const int ROTATE = 1, X = 2, Y = 3, SCALEX = 4, SCALEY = 5, SHEARY = 6; + + readonly int constraintIndex; + + public TransformConstraintTimeline (int frameCount, int bezierCount, int transformConstraintIndex) + : base(frameCount, bezierCount, (int)Property.TransformConstraint + "|" + transformConstraintIndex) { + constraintIndex = transformConstraintIndex; + } + + public override int FrameEntries { + get { + return ENTRIES; + } + } + + /// The index of the transform constraint in that will be changed when this + /// timeline is applied. + public int TransformConstraintIndex { + get { + return constraintIndex; + } + } + + /// Sets the time, rotate mix, translate mix, scale mix, and shear mix for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds. + public void SetFrame (int 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; + } + + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) { + TransformConstraint constraint = skeleton.transformConstraints.Items[constraintIndex]; + if (!constraint.active) return; + + float[] frames = this.frames; + if (time < frames[0]) { + TransformConstraintData data = constraint.data; + 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; + } + return; + } + + float rotate, x, y, scaleX, scaleY, shearY; + GetCurveValue(out rotate, out x, out y, out scaleX, out scaleY, out shearY, time); + + if (blend == MixBlend.Setup) { + TransformConstraintData 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; + 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; + } + } + + public void GetCurveValue (out float rotate, out float x, out float y, + out float scaleX, out float scaleY, out float shearY, float time) { + + float[] frames = this.frames; + int i = Search(frames, time, ENTRIES), curveType = (int)curves[i / ENTRIES]; + switch (curveType) { + case 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 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); + break; + } + } + } + + /// Changes a path constraint's . + public class PathConstraintPositionTimeline : CurveTimeline1 { + readonly int constraintIndex; + + public PathConstraintPositionTimeline (int frameCount, int bezierCount, int pathConstraintIndex) + : base(frameCount, bezierCount, (int)Property.PathConstraintPosition + "|" + pathConstraintIndex) { + this.constraintIndex = pathConstraintIndex; + } + + /// The index of the path constraint slot in that will be changed when this timeline + /// is applied. + public int PathConstraintIndex { + get { + return constraintIndex; + } + } + + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) { + PathConstraint constraint = skeleton.pathConstraints.Items[constraintIndex]; + if (constraint.active) + constraint.position = GetAbsoluteValue(time, alpha, blend, constraint.position, constraint.data.position); + } + } + + /// Changes a path constraint's . + public class PathConstraintSpacingTimeline : CurveTimeline1 { + readonly int constraintIndex; + + public PathConstraintSpacingTimeline (int frameCount, int bezierCount, int pathConstraintIndex) + : base(frameCount, bezierCount, (int)Property.PathConstraintSpacing + "|" + pathConstraintIndex) { + constraintIndex = pathConstraintIndex; + } + + /// The index of the path constraint in that will be changed when this timeline + /// is applied. + public int PathConstraintIndex { + get { + return constraintIndex; + } + } + + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, MixBlend blend, + MixDirection direction) { + + PathConstraint constraint = skeleton.pathConstraints.Items[constraintIndex]; + if (constraint.active) + constraint.spacing = GetAbsoluteValue(time, alpha, blend, constraint.spacing, constraint.data.spacing); + } + } + + /// Changes a path constraint's , , and + /// . + public class PathConstraintMixTimeline : CurveTimeline { + public const int ENTRIES = 4; + private const int ROTATE = 1, X = 2, Y = 3; + + readonly int constraintIndex; + + public PathConstraintMixTimeline (int frameCount, int bezierCount, int pathConstraintIndex) + : base(frameCount, bezierCount, (int)Property.PathConstraintMix + "|" + pathConstraintIndex) { + constraintIndex = pathConstraintIndex; + } + + public override int FrameEntries { + get { return ENTRIES; } + } + + /// The index of the path constraint slot in that will be changed when this timeline + /// is applied. + public int PathConstraintIndex { + get { + return constraintIndex; + } + } + + /// Sets the time and color for the specified frame. + /// Between 0 and frameCount, inclusive. + /// The frame time in seconds. + public void SetFrame (int frame, float time, float mixRotate, float mixX, float mixY) { + frame <<= 2; + frames[frame] = time; + frames[frame + ROTATE] = mixRotate; + frames[frame + X] = mixX; + frames[frame + Y] = mixY; + } + + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) { + PathConstraint constraint = skeleton.pathConstraints.Items[constraintIndex]; + if (!constraint.active) return; + + float[] frames = this.frames; + 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; + return; + } + return; + } + + float rotate, x, y; + int i = Search(frames, time, ENTRIES), 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); + break; + } + + 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; + } + } + } + + /// The base class for most timelines. + public abstract class PhysicsConstraintTimeline : CurveTimeline1 { + readonly int constraintIndex; + + /// -1 for all physics constraints in the skeleton. + public PhysicsConstraintTimeline (int frameCount, int bezierCount, int physicsConstraintIndex, Property property) + : base(frameCount, bezierCount, (int)property + "|" + physicsConstraintIndex) { + + constraintIndex = physicsConstraintIndex; + } + + /// The index of the physics constraint in that will be changed when this timeline + /// is applied, or -1 if all physics constraints in the skeleton will be changed. + public int PhysicsConstraintIndex { + get { + return constraintIndex; + } + } + + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) { + PhysicsConstraint constraint; + if (constraintIndex == -1) { + float value = time >= frames[0] ? GetCurveValue(time) : 0; + + PhysicsConstraint[] constraints = skeleton.physicsConstraints.Items; + for (int i = 0, n = skeleton.physicsConstraints.Count; i < n; i++) { + constraint = (PhysicsConstraint)constraints[i]; + if (constraint.active && Global(constraint.data)) + Set(constraint, GetAbsoluteValue(time, alpha, blend, Get(constraint), Setup(constraint), value)); + } + } else { + constraint = skeleton.physicsConstraints.Items[constraintIndex]; + if (constraint.active) Set(constraint, GetAbsoluteValue(time, alpha, blend, Get(constraint), Setup(constraint))); + } + } + + abstract protected float Setup (PhysicsConstraint constraint); + + abstract protected float Get (PhysicsConstraint constraint); + + abstract protected void Set (PhysicsConstraint constraint, float value); + + abstract protected bool Global (PhysicsConstraintData constraint); + } + + /// Changes a physics constraint's . + public class PhysicsConstraintInertiaTimeline : PhysicsConstraintTimeline { + public PhysicsConstraintInertiaTimeline (int frameCount, int bezierCount, int physicsConstraintIndex) + : base(frameCount, bezierCount, physicsConstraintIndex, Property.PhysicsConstraintInertia) { + } + + override protected float Setup (PhysicsConstraint constraint) { + return constraint.data.inertia; + } + + override protected float Get (PhysicsConstraint constraint) { + return constraint.inertia; + } + + override protected void Set (PhysicsConstraint constraint, float value) { + constraint.inertia = value; + } + + override protected bool Global (PhysicsConstraintData constraint) { + return constraint.inertiaGlobal; + } + } + + /// Changes a physics constraint's . + public class PhysicsConstraintStrengthTimeline : PhysicsConstraintTimeline { + public PhysicsConstraintStrengthTimeline (int frameCount, int bezierCount, int physicsConstraintIndex) + : base(frameCount, bezierCount, physicsConstraintIndex, Property.PhysicsConstraintStrength) { + } + + override protected float Setup (PhysicsConstraint constraint) { + return constraint.data.strength; + } + + override protected float Get (PhysicsConstraint constraint) { + return constraint.strength; + } + + override protected void Set (PhysicsConstraint constraint, float value) { + constraint.strength = value; + } + + override protected bool Global (PhysicsConstraintData constraint) { + return constraint.strengthGlobal; + } + } + + /// Changes a physics constraint's . + public class PhysicsConstraintDampingTimeline : PhysicsConstraintTimeline { + public PhysicsConstraintDampingTimeline (int frameCount, int bezierCount, int physicsConstraintIndex) + : base(frameCount, bezierCount, physicsConstraintIndex, Property.PhysicsConstraintDamping) { + } + + override protected float Setup (PhysicsConstraint constraint) { + return constraint.data.damping; + } + + override protected float Get (PhysicsConstraint constraint) { + return constraint.damping; + } + + override protected void Set (PhysicsConstraint constraint, float value) { + constraint.damping = value; + } + + override protected bool Global (PhysicsConstraintData constraint) { + return constraint.dampingGlobal; + } + } + + /// Changes a physics constraint's . The timeline values are not inverted. + public class PhysicsConstraintMassTimeline : PhysicsConstraintTimeline { + public PhysicsConstraintMassTimeline (int frameCount, int bezierCount, int physicsConstraintIndex) + : base(frameCount, bezierCount, physicsConstraintIndex, Property.PhysicsConstraintMass) { + } + + override protected float Setup (PhysicsConstraint constraint) { + return 1 / constraint.data.massInverse; + } + + override protected float Get (PhysicsConstraint constraint) { + return 1 / constraint.massInverse; + } + + override protected void Set (PhysicsConstraint constraint, float value) { + constraint.massInverse = 1 / value; + } + + override protected bool Global (PhysicsConstraintData constraint) { + return constraint.massGlobal; + } + } + + /// Changes a physics constraint's . + public class PhysicsConstraintWindTimeline : PhysicsConstraintTimeline { + public PhysicsConstraintWindTimeline (int frameCount, int bezierCount, int physicsConstraintIndex) + : base(frameCount, bezierCount, physicsConstraintIndex, Property.PhysicsConstraintWind) { + } + + override protected float Setup (PhysicsConstraint constraint) { + return constraint.data.wind; + } + + override protected float Get (PhysicsConstraint constraint) { + return constraint.wind; + } + + override protected void Set (PhysicsConstraint constraint, float value) { + constraint.wind = value; + } + + override protected bool Global (PhysicsConstraintData constraint) { + return constraint.windGlobal; + } + } + + /// Changes a physics constraint's . + public class PhysicsConstraintGravityTimeline : PhysicsConstraintTimeline { + public PhysicsConstraintGravityTimeline (int frameCount, int bezierCount, int physicsConstraintIndex) + : base(frameCount, bezierCount, physicsConstraintIndex, Property.PhysicsConstraintGravity) { + } + + override protected float Setup (PhysicsConstraint constraint) { + return constraint.data.gravity; + } + + override protected float Get (PhysicsConstraint constraint) { + return constraint.gravity; + } + + override protected void Set (PhysicsConstraint constraint, float value) { + constraint.gravity = value; + } + + override protected bool Global (PhysicsConstraintData constraint) { + return constraint.gravityGlobal; + } + } + + /// Changes a physics constraint's . + public class PhysicsConstraintMixTimeline : PhysicsConstraintTimeline { + public PhysicsConstraintMixTimeline (int frameCount, int bezierCount, int physicsConstraintIndex) + : base(frameCount, bezierCount, physicsConstraintIndex, Property.PhysicsConstraintMix) { + } + + override protected float Setup (PhysicsConstraint constraint) { + return constraint.data.mix; + } + + override protected float Get (PhysicsConstraint constraint) { + return constraint.mix; + } + + override protected void Set (PhysicsConstraint constraint, float value) { + constraint.mix = value; + } + + override protected bool Global (PhysicsConstraintData constraint) { + return constraint.mixGlobal; + } + } + + /// Resets a physics constraint when specific animation times are reached. + public class PhysicsConstraintResetTimeline : Timeline { + static readonly string[] propertyIds = { ((int)Property.PhysicsConstraintReset).ToString() }; + + readonly int constraintIndex; + + /// -1 for all physics constraints in the skeleton. + public PhysicsConstraintResetTimeline (int frameCount, int physicsConstraintIndex) + : base(frameCount, propertyIds) { + constraintIndex = physicsConstraintIndex; + } + + /// The index of the physics constraint in that will be reset when this timeline is + /// applied, or -1 if all physics constraints in the skeleton will be reset. + public int PhysicsConstraintIndex { + get { + return constraintIndex; + } + } + + override public int FrameCount { + get { return frames.Length; } + } + + /// Sets the time for the specified frame. + /// Between 0 and frameCount, inclusive. + public void SetFrame (int frame, float time) { + frames[frame] = time; + } + + /// Resets the physics constraint when frames > lastTime and <= time. + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) { + + PhysicsConstraint constraint = null; + if (constraintIndex != -1) { + constraint = skeleton.physicsConstraints.Items[constraintIndex]; + if (!constraint.active) return; + } + + float[] frames = this.frames; + + if (lastTime > time) { // Apply after lastTime for looped animations. + Apply(skeleton, lastTime, int.MaxValue, null, alpha, blend, direction); + lastTime = -1f; + } else if (lastTime >= frames[frames.Length - 1]) // Last time is after last frame. + return; + if (time < frames[0]) return; + + if (lastTime < frames[0] || time >= frames[Search(frames, lastTime) + 1]) { + if (constraint != null) + constraint.Reset(); + else { + PhysicsConstraint[] constraints = skeleton.physicsConstraints.Items; + for (int i = 0, n = skeleton.physicsConstraints.Count; i < n; i++) { + constraint = (PhysicsConstraint)constraints[i]; + if (constraint.active) constraint.Reset(); + } + } + } + } + } + + + /// Changes a slot's for an attachment's . + public class SequenceTimeline : Timeline, ISlotTimeline { + public const int ENTRIES = 3; + private const int MODE = 1, DELAY = 2; + + readonly int slotIndex; + readonly IHasTextureRegion attachment; + + public SequenceTimeline (int frameCount, int slotIndex, Attachment attachment) + : base(frameCount, (int)Property.Sequence + "|" + slotIndex + "|" + ((IHasTextureRegion)attachment).Sequence.Id) { + this.slotIndex = slotIndex; + this.attachment = (IHasTextureRegion)attachment; + } + + public override int FrameEntries { + get { return ENTRIES; } + } + + public int SlotIndex { + get { + return slotIndex; + } + } + public Attachment Attachment { + get { + return (Attachment)attachment; + } + } + + /// Sets the time, mode, index, and frame time for the specified frame. + /// Between 0 and frameCount, inclusive. + /// Seconds between frames. + public void SetFrame (int frame, float time, SequenceMode mode, int index, float delay) { + frame *= ENTRIES; + frames[frame] = time; + frames[frame + MODE] = (int)mode | (index << 4); + frames[frame + DELAY] = delay; + } + + override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList firedEvents, float alpha, MixBlend blend, + MixDirection direction) { + + Slot slot = skeleton.slots.Items[slotIndex]; + if (!slot.bone.active) return; + Attachment slotAttachment = slot.attachment; + if (slotAttachment != attachment) { + VertexAttachment vertexAttachment = slotAttachment as VertexAttachment; + if ((vertexAttachment == null) + || vertexAttachment.TimelineAttachment != attachment) return; + } + Sequence sequence = ((IHasTextureRegion)slotAttachment).Sequence; + if (sequence == null) return; + + if (direction == MixDirection.Out) { + if (blend == MixBlend.Setup) slot.SequenceIndex = -1; + return; + } + + float[] frames = this.frames; + if (time < frames[0]) { + if (blend == MixBlend.Setup || blend == MixBlend.First) slot.SequenceIndex = -1; + return; + } + + int i = Search(frames, time, ENTRIES); + float before = frames[i]; + int modeAndIndex = (int)frames[i + MODE]; + float delay = frames[i + DELAY]; + + int index = modeAndIndex >> 4, count = sequence.Regions.Length; + SequenceMode mode = (SequenceMode)(modeAndIndex & 0xf); + if (mode != SequenceMode.Hold) { + index += (int)((time - before) / delay + 0.0001f); + switch (mode) { + case SequenceMode.Once: + index = Math.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 = Math.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; + break; + } // end case + } + } + slot.SequenceIndex = index; + } + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/AnimationState.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/AnimationState.cs new file mode 100644 index 0000000..b47624f --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/AnimationState.cs @@ -0,0 +1,1507 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +using System; +using System.Collections.Generic; + +namespace Spine4_2_33 { + + /// + /// + /// Applies animations over time, queues animations for later playback, mixes (crossfading) between animations, and applies + /// multiple animations on top of each other (layering). + /// + /// See Applying Animations in the Spine Runtimes Guide. + /// + public class AnimationState { + internal static readonly Animation EmptyAnimation = new Animation("", new ExposedList(), 0); + + /// 1) A previously applied timeline has set this property. + /// Result: Mix from the current pose to the timeline pose. + internal const int Subsequent = 0; + /// 1) This is the first timeline to set this property. + /// 2) The next track entry applied after this one does not have a timeline to set this property. + /// Result: Mix from the setup pose to the timeline pose. + internal const int First = 1; + /// 1) A previously applied timeline has set this property. + /// 2) The next track entry to be applied does have a timeline to set this property. + /// 3) The next track entry after that one does not have a timeline to set this property. + /// Result: Mix from the current pose to the timeline pose, but do not mix out. This avoids "dipping" when crossfading + /// animations that key the same property. A subsequent timeline will set this property using a mix. + internal const int HoldSubsequent = 2; + /// 1) This is the first timeline to set this property. + /// 2) The next track entry to be applied does have a timeline to set this property. + /// 3) The next track entry after that one does not have a timeline to set this property. + /// Result: Mix from the setup pose to the timeline pose, but do not mix out. This avoids "dipping" when crossfading animations + /// that key the same property. A subsequent timeline will set this property using a mix. + internal const int HoldFirst = 3; + /// 1) This is the first timeline to set this property. + /// 2) The next track entry to be applied does have a timeline to set this property. + /// 3) The next track entry after that one does have a timeline to set this property. + /// 4) timelineHoldMix stores the first subsequent track entry that does not have a timeline to set this property. + /// Result: The same as HOLD except the mix percentage from the timelineHoldMix track entry is used. This handles when more than + /// 2 track entries in a row have a timeline that sets the same property. + /// Eg, A -> B -> C -> D where A, B, and C have a timeline setting same property, but D does not. When A is applied, to avoid + /// "dipping" A is not mixed out, however D (the first entry that doesn't set the property) mixing in is used to mix out A + /// (which affects B and C). Without using D to mix out, A would be applied fully until mixing completes, then snap to the mixed + /// out position. + internal const int HoldMix = 4; + + internal const int Setup = 1, Current = 2; + + protected AnimationStateData data; + private readonly ExposedList tracks = new ExposedList(); + private readonly ExposedList events = new ExposedList(); + // difference to libgdx reference: delegates are used for event callbacks instead of 'final SnapshotArray listeners'. + internal void OnStart (TrackEntry entry) { if (Start != null) Start(entry); } + internal void OnInterrupt (TrackEntry entry) { if (Interrupt != null) Interrupt(entry); } + internal void OnEnd (TrackEntry entry) { if (End != null) End(entry); } + internal void OnDispose (TrackEntry entry) { if (Dispose != null) Dispose(entry); } + internal void OnComplete (TrackEntry entry) { if (Complete != null) Complete(entry); } + internal void OnEvent (TrackEntry entry, Event e) { if (Event != null) Event(entry, e); } + + public delegate void TrackEntryDelegate (TrackEntry trackEntry); + /// See + /// API Reference documentation pages here for details. Usage in C# and spine-unity is explained + /// here + /// on the spine-unity documentation pages. + public event TrackEntryDelegate Start, Interrupt, End, Dispose, Complete; + + public delegate void TrackEntryEventDelegate (TrackEntry trackEntry, Event e); + public event TrackEntryEventDelegate Event; + + public void AssignEventSubscribersFrom (AnimationState src) { + Event = src.Event; + Start = src.Start; + Interrupt = src.Interrupt; + End = src.End; + Dispose = src.Dispose; + Complete = src.Complete; + } + + public void AddEventSubscribersFrom (AnimationState src) { + Event += src.Event; + Start += src.Start; + Interrupt += src.Interrupt; + End += src.End; + Dispose += src.Dispose; + Complete += src.Complete; + } + + // end of difference + private readonly EventQueue queue; // Initialized by constructor. + private readonly HashSet propertyIds = new HashSet(); + private bool animationsChanged; + private float timeScale = 1; + private int unkeyedState; + + private readonly Pool trackEntryPool = new Pool(); + + public AnimationState (AnimationStateData data) { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + this.data = data; + this.queue = new EventQueue( + this, + delegate { this.animationsChanged = true; }, + trackEntryPool + ); + } + + /// + /// Increments the track entry , setting queued animations as current if needed. + /// delta time + public void Update (float delta) { + delta *= timeScale; + TrackEntry[] tracksItems = tracks.Items; + for (int i = 0, n = tracks.Count; i < n; i++) { + TrackEntry current = tracksItems[i]; + if (current == null) continue; + + 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. + tracksItems[i] = null; + queue.End(current); + ClearNext(current); + continue; + } + if (current.mixingFrom != null && UpdateMixingFrom(current, 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(); + } + + /// Returns true when all mixing from entries are complete. + private bool 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; + + if (to.nextTrackLast != -1) { // The from entry was applied at least once. + bool discard = to.mixTime == 0 && from.mixTime == 0; // Discard the from entry when neither have advanced yet. + if (to.mixTime >= to.mixDuration || discard) { + // Require totalAlpha == 0 to ensure mixing is complete or the transition is a single frame or discarded. + if (from.totalAlpha == 0 || to.mixDuration == 0 || discard) { + to.mixingFrom = from.mixingFrom; + if (from.mixingFrom != null) from.mixingFrom.mixingTo = to; + to.interruptAlpha = from.interruptAlpha; + queue.End(from); + } + return finished; + } + } + + from.trackTime += delta * from.timeScale; + to.mixTime += delta; + return false; + } + + /// + /// Poses the skeleton using the track entry animations. The animation state is not changed, so can be applied to multiple + /// skeletons to pose them identically. + /// True if any animations were applied. + public bool Apply (Skeleton skeleton) { + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + if (animationsChanged) AnimationsChanged(); + + ExposedList events = this.events; + bool applied = false; + TrackEntry[] tracksItems = tracks.Items; + for (int i = 0, n = tracks.Count; i < n; i++) { + TrackEntry current = tracksItems[i]; + if (current == null || current.delay > 0) continue; + applied = true; + + // Track 0 animations aren't for layering, so do not show the previously applied animations before the first key. + MixBlend blend = i == 0 ? MixBlend.First : current.mixBlend; + + // Apply mixing from entries first. + float alpha = current.alpha; + if (current.mixingFrom != null) + alpha *= ApplyMixingFrom(current, 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.AnimationTime, applyTime = animationTime; + ExposedList applyEvents = events; + if (current.reverse) { + applyTime = current.animation.duration - applyTime; + applyEvents = null; + } + + int timelineCount = current.animation.timelines.Count; + Timeline[] timelines = current.animation.timelines.Items; + if ((i == 0 && alpha == 1) || blend == MixBlend.Add) { + if (i == 0) attachments = true; + for (int ii = 0; ii < timelineCount; ii++) { + Timeline timeline = timelines[ii]; + if (timeline is AttachmentTimeline) + ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, applyTime, blend, attachments); + else + timeline.Apply(skeleton, animationLast, applyTime, applyEvents, alpha, blend, MixDirection.In); + } + } else { + int[] timelineMode = current.timelineMode.Items; + + bool shortestRotation = current.shortestRotation; + bool firstFrame = !shortestRotation && current.timelinesRotation.Count != timelineCount << 1; + if (firstFrame) current.timelinesRotation.Resize(timelineCount << 1); + float[] timelinesRotation = current.timelinesRotation.Items; + + for (int ii = 0; ii < timelineCount; ii++) { + Timeline timeline = timelines[ii]; + MixBlend timelineBlend = timelineMode[ii] == AnimationState.Subsequent ? blend : MixBlend.Setup; + RotateTimeline rotateTimeline = timeline as RotateTimeline; + if (!shortestRotation && rotateTimeline != null) + ApplyRotateTimeline(rotateTimeline, skeleton, applyTime, alpha, timelineBlend, timelinesRotation, + ii << 1, firstFrame); + else if (timeline is AttachmentTimeline) + ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, applyTime, blend, attachments); + else + timeline.Apply(skeleton, animationLast, applyTime, applyEvents, alpha, timelineBlend, MixDirection.In); + } + } + QueueEvents(current, animationTime); + events.Clear(false); + current.nextAnimationLast = animationTime; + current.nextTrackLast = current.trackTime; + } + + // Set slots attachments to the setup pose, if needed. This occurs if an animation that is mixing out sets attachments so + // subsequent timelines see any deform, but the subsequent timelines don't set an attachment (eg they are also mixing out or + // the time is before the first key). + int setupState = unkeyedState + Setup; + Slot[] slots = skeleton.slots.Items; + for (int i = 0, n = skeleton.slots.Count; i < n; i++) { + Slot slot = slots[i]; + if (slot.attachmentState == setupState) { + string attachmentName = slot.data.attachmentName; + slot.Attachment = (attachmentName == null ? null : skeleton.GetAttachment(slot.data.index, attachmentName)); + } + } + unkeyedState += 2; // Increasing after each use avoids the need to reset attachmentState for every slot. + + queue.Drain(); + return applied; + } + + /// Version of only applying and updating time at + /// EventTimelines for lightweight off-screen updates. + /// When set to false, only animation times of TrackEntries are updated. + // Note: This method is not part of the libgdx reference implementation. + public bool ApplyEventTimelinesOnly (Skeleton skeleton, bool issueEvents = true) { + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + + ExposedList events = this.events; + bool applied = false; + TrackEntry[] tracksItems = tracks.Items; + for (int i = 0, n = tracks.Count; i < n; i++) { + TrackEntry current = tracksItems[i]; + if (current == null || current.delay > 0) continue; + applied = true; + + // Apply mixing from entries first. + if (current.mixingFrom != null) ApplyMixingFromEventTimelinesOnly(current, skeleton, issueEvents); + + // Apply current entry. + float animationLast = current.animationLast, animationTime = current.AnimationTime; + + if (issueEvents) { + int timelineCount = current.animation.timelines.Count; + Timeline[] timelines = current.animation.timelines.Items; + for (int ii = 0; ii < timelineCount; ii++) { + Timeline timeline = timelines[ii]; + if (timeline is EventTimeline) + timeline.Apply(skeleton, animationLast, animationTime, events, 1.0f, MixBlend.Setup, MixDirection.In); + } + QueueEvents(current, animationTime); + events.Clear(false); + } + current.nextAnimationLast = animationTime; + current.nextTrackLast = current.trackTime; + } + + if (issueEvents) + queue.Drain(); + return applied; + } + + private float 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; // Tracks > 0 are transparent and can't reset to setup pose. + } else { + mix = to.mixTime / to.mixDuration; + if (mix > 1) mix = 1; + if (blend != MixBlend.First) blend = from.mixBlend; // Track 0 ignores track mix blend. + } + + bool attachments = mix < from.mixAttachmentThreshold, drawOrder = mix < from.mixDrawOrderThreshold; + int timelineCount = from.animation.timelines.Count; + Timeline[] timelines = from.animation.timelines.Items; + float alphaHold = from.alpha * to.interruptAlpha, alphaMix = alphaHold * (1 - mix); + float animationLast = from.animationLast, animationTime = from.AnimationTime, applyTime = animationTime; + ExposedList events = null; + if (from.reverse) + applyTime = from.animation.duration - applyTime; + else { + if (mix < from.eventThreshold) events = this.events; + } + + if (blend == MixBlend.Add) { + for (int i = 0; i < timelineCount; i++) + timelines[i].Apply(skeleton, animationLast, applyTime, events, alphaMix, blend, MixDirection.Out); + } else { + int[] timelineMode = from.timelineMode.Items; + TrackEntry[] timelineHoldMix = from.timelineHoldMix.Items; + + bool shortestRotation = from.shortestRotation; + bool firstFrame = !shortestRotation && from.timelinesRotation.Count != timelineCount << 1; + if (firstFrame) from.timelinesRotation.Resize(timelineCount << 1); + float[] timelinesRotation = from.timelinesRotation.Items; + + from.totalAlpha = 0; + for (int i = 0; i < timelineCount; i++) { + Timeline timeline = timelines[i]; + MixDirection direction = MixDirection.Out; + MixBlend timelineBlend; + float alpha; + switch (timelineMode[i]) { + case AnimationState.Subsequent: + if (!drawOrder && timeline is DrawOrderTimeline) continue; + timelineBlend = blend; + alpha = alphaMix; + break; + case AnimationState.First: + timelineBlend = MixBlend.Setup; + alpha = alphaMix; + break; + case AnimationState.HoldSubsequent: + timelineBlend = blend; + alpha = alphaHold; + break; + case AnimationState.HoldFirst: + timelineBlend = MixBlend.Setup; + alpha = alphaHold; + break; + default: // HoldMix + timelineBlend = MixBlend.Setup; + TrackEntry holdMix = timelineHoldMix[i]; + alpha = alphaHold * Math.Max(0, 1 - holdMix.mixTime / holdMix.mixDuration); + break; + } + from.totalAlpha += alpha; + RotateTimeline rotateTimeline = timeline as RotateTimeline; + if (!shortestRotation && rotateTimeline != null) { + ApplyRotateTimeline(rotateTimeline, skeleton, applyTime, alpha, timelineBlend, timelinesRotation, i << 1, + firstFrame); + } else if (timeline is AttachmentTimeline) { + ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, applyTime, timelineBlend, + attachments && alpha >= from.alphaAttachmentThreshold); + } else { + if (drawOrder && timeline is DrawOrderTimeline && timelineBlend == MixBlend.Setup) + direction = MixDirection.In; + timeline.Apply(skeleton, animationLast, applyTime, events, alpha, timelineBlend, direction); + } + } + } + + if (to.mixDuration > 0) QueueEvents(from, animationTime); + this.events.Clear(false); + from.nextAnimationLast = animationTime; + from.nextTrackLast = from.trackTime; + + return mix; + } + + /// Version of only applying and updating time at + /// EventTimelines for lightweight off-screen updates. + /// When set to false, only animation times of TrackEntries are updated. + // Note: This method is not part of the libgdx reference implementation. + private float ApplyMixingFromEventTimelinesOnly (TrackEntry to, Skeleton skeleton, bool issueEvents) { + TrackEntry from = to.mixingFrom; + if (from.mixingFrom != null) ApplyMixingFromEventTimelinesOnly(from, skeleton, issueEvents); + + + float mix; + if (to.mixDuration == 0) { // Single frame mix to undo mixingFrom changes. + mix = 1; + } else { + mix = to.mixTime / to.mixDuration; + if (mix > 1) mix = 1; + } + + ExposedList eventBuffer = mix < from.eventThreshold ? this.events : null; + if (eventBuffer == null) return mix; + + float animationLast = from.animationLast, animationTime = from.AnimationTime; + if (issueEvents) { + int timelineCount = from.animation.timelines.Count; + Timeline[] timelines = from.animation.timelines.Items; + for (int i = 0; i < timelineCount; i++) { + Timeline timeline = timelines[i]; + if (timeline is EventTimeline) + timeline.Apply(skeleton, animationLast, animationTime, eventBuffer, 0, MixBlend.Setup, MixDirection.Out); + } + + if (to.mixDuration > 0) QueueEvents(from, animationTime); + this.events.Clear(false); + } + from.nextAnimationLast = animationTime; + from.nextTrackLast = from.trackTime; + + return mix; + } + + /// Applies the attachment timeline and sets . + /// False when: 1) the attachment timeline is mixing out, 2) mix < attachmentThreshold, and 3) the timeline + /// is not the last timeline to set the slot's attachment. In that case the timeline is applied only so subsequent + /// timelines see any deform. + private void ApplyAttachmentTimeline (AttachmentTimeline timeline, Skeleton skeleton, float time, MixBlend blend, + bool attachments) { + + Slot slot = skeleton.slots.Items[timeline.SlotIndex]; + if (!slot.bone.active) return; + + float[] frames = timeline.frames; + if (time < frames[0]) { // Time is before first frame. + if (blend == MixBlend.Setup || blend == MixBlend.First) + SetAttachment(skeleton, slot, slot.data.attachmentName, attachments); + } else + SetAttachment(skeleton, slot, timeline.AttachmentNames[Timeline.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.attachmentState <= unkeyedState) slot.attachmentState = unkeyedState + Setup; + } + + private void SetAttachment (Skeleton skeleton, Slot slot, String attachmentName, bool attachments) { + slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slot.data.index, attachmentName); + if (attachments) slot.attachmentState = unkeyedState + Current; + } + + /// + /// Applies the rotate timeline, mixing with the current pose while keeping the same rotation direction chosen as the shortest + /// the first time the mixing was applied. + static private void ApplyRotateTimeline (RotateTimeline timeline, Skeleton skeleton, float time, float alpha, MixBlend blend, + float[] timelinesRotation, int i, bool firstFrame) { + + if (firstFrame) timelinesRotation[i] = 0; + + if (alpha == 1) { + timeline.Apply(skeleton, 0, time, null, 1, blend, MixDirection.In); + return; + } + + Bone bone = skeleton.bones.Items[timeline.BoneIndex]; + if (!bone.active) return; + + float[] frames = timeline.frames; + float r1, r2; + if (time < frames[0]) { // Time is before first frame. + switch (blend) { + case MixBlend.Setup: + bone.rotation = bone.data.rotation; + goto default; // Fall through. + default: + return; + case MixBlend.First: + r1 = bone.rotation; + r2 = bone.data.rotation; + break; + } + } else { + r1 = blend == MixBlend.Setup ? bone.data.rotation : bone.rotation; + r2 = bone.data.rotation + timeline.GetCurveValue(time); + } + + // Mix between rotations using the direction of the shortest route on the first frame. + float total, diff = r2 - r1; + diff -= (float)Math.Ceiling(diff / 360 - 0.5f) * 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 - lastTotal % 360; + total = diff + loops; + bool current = diff >= 0, dir = lastTotal >= 0; + if (Math.Abs(lastDiff) <= 90 && Math.Sign(lastDiff) != Math.Sign(diff)) { + if (Math.Abs(lastTotal - loops) > 180) { + total += 360 * Math.Sign(lastTotal); + dir = current; + } else if (loops != 0) + total -= 360 * Math.Sign(lastTotal); + else + dir = current; + } + if (dir != current) total += 360 * Math.Sign(lastTotal); + timelinesRotation[i] = total; + } + timelinesRotation[i + 1] = diff; + bone.rotation = r1 + total * alpha; + } + + private void QueueEvents (TrackEntry entry, float animationTime) { + float animationStart = entry.animationStart, animationEnd = entry.animationEnd; + float duration = animationEnd - animationStart; + float trackLastWrapped = entry.trackLast % duration; + + // Queue events before complete. + Event[] eventsItems = this.events.Items; + int i = 0, n = events.Count; + for (; i < n; i++) { + Event e = eventsItems[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 = eventsItems[i]; + if (e.time < animationStart) continue; // Discard events outside animation start/end. + queue.Event(entry, eventsItems[i]); + } + } + + /// + /// Removes all animations from all tracks, leaving skeletons in their current pose. + /// + /// It may be desired to use to mix the skeletons back to the setup pose, + /// rather than leaving them in their current pose. + /// + public void ClearTracks () { + bool oldDrainDisabled = queue.drainDisabled; + queue.drainDisabled = true; + for (int i = 0, n = tracks.Count; i < n; i++) { + ClearTrack(i); + } + tracks.Clear(); + queue.drainDisabled = oldDrainDisabled; + queue.Drain(); + } + + /// + /// Removes all animations from the track, leaving skeletons in their current pose. + /// + /// It may be desired to use to mix the skeletons back to the setup pose, + /// rather than leaving them in their current pose. + /// + public void ClearTrack (int trackIndex) { + if (trackIndex >= tracks.Count) return; + TrackEntry current = tracks.Items[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.Items[current.trackIndex] = null; + + queue.Drain(); + } + + /// Sets the active TrackEntry for a given track number. + private void SetCurrent (int index, TrackEntry current, bool interrupt) { + TrackEntry from = ExpandToIndex(index); + tracks.Items[index] = current; + current.previous = null; + + if (from != null) { + if (interrupt) queue.Interrupt(from); + current.mixingFrom = from; + from.mixingTo = current; + current.mixTime = 0; + + // Store the interrupted mix percentage. + if (from.mixingFrom != null && from.mixDuration > 0) + current.interruptAlpha *= Math.Min(1, from.mixTime / from.mixDuration); + + from.timelinesRotation.Clear(); // Reset rotation for mixing out, in case entry was mixed in. + } + + queue.Start(current); // triggers AnimationsChanged + } + + /// Sets an animation by name. + public TrackEntry SetAnimation (int trackIndex, string animationName, bool loop) { + Animation animation = data.skeletonData.FindAnimation(animationName); + if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName"); + return SetAnimation(trackIndex, animation, loop); + } + + /// Sets the current animation for a track, discarding any queued animations. If the formerly current track entry was never + /// applied to a skeleton, it is replaced (not mixed from). + /// 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 determines when the track is cleared. + /// A track entry to allow further customization of animation playback. References to the track entry must not be kept + /// after the event occurs. + public TrackEntry SetAnimation (int trackIndex, Animation animation, bool loop) { + if (animation == null) throw new ArgumentNullException("animation", "animation cannot be 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.Items[trackIndex] = current.mixingFrom; + queue.Interrupt(current); + queue.End(current); + ClearNext(current); + current = current.mixingFrom; + interrupt = false; // mixingFrom is current again, but don't interrupt it twice. + } else + ClearNext(current); + } + TrackEntry entry = NewTrackEntry(trackIndex, animation, loop, current); + SetCurrent(trackIndex, entry, interrupt); + queue.Drain(); + return entry; + } + + /// Queues an animation by name. + /// + public TrackEntry AddAnimation (int trackIndex, string animationName, bool loop, float delay) { + Animation animation = data.skeletonData.FindAnimation(animationName); + if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName"); + return AddAnimation(trackIndex, animation, loop, delay); + } + + /// Adds an animation to be played after the current or last queued animation for a track. If the track is empty, it is + /// equivalent to calling . + /// + /// If > 0, sets . If <= 0, the delay set is the duration of the previous track entry + /// minus any mix duration (from the plus the specified Delay (ie the mix + /// ends at (Delay = 0) or before (Delay < 0) the previous track entry duration). If the + /// previous entry is looping, its next loop completion is used instead of its duration. + /// + /// A track entry to allow further customization of animation playback. References to the track entry must not be kept + /// after the event occurs. + public TrackEntry AddAnimation (int trackIndex, Animation animation, bool loop, float delay) { + if (animation == null) throw new ArgumentNullException("animation", "animation cannot be 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(); + } else { + last.next = entry; + entry.previous = last; + if (delay <= 0) delay += last.TrackComplete - entry.mixDuration; + } + + entry.delay = delay; + return entry; + } + + /// + /// Sets an empty animation for a track, discarding any queued animations, and sets the track entry's + /// . An empty animation has no timelines and serves as a placeholder for mixing in or out. + /// + /// Mixing out is done by setting an empty animation with a mix duration using either , + /// , or . Mixing to an empty animation causes + /// the previous animation to be applied less and less over the mix duration. Properties keyed in the previous animation + /// transition to the value from lower tracks or to the setup pose value if no lower tracks key the property. A mix duration of + /// 0 still mixes out over one frame. + /// + /// Mixing in is done by first setting an empty animation, then adding an animation using + /// with the desired delay (an empty animation has a duration of 0) and on + /// the returned track entry, set the . Mixing from an empty animation causes the new + /// animation to be applied more and more over the mix duration. Properties keyed in the new animation transition from the value + /// from lower tracks or from the setup pose value if no lower tracks key the property to the value keyed in the new + /// animation. + public TrackEntry SetEmptyAnimation (int trackIndex, float mixDuration) { + TrackEntry entry = SetAnimation(trackIndex, AnimationState.EmptyAnimation, false); + entry.mixDuration = mixDuration; + entry.trackEnd = mixDuration; + return entry; + } + + /// + /// Adds an empty animation to be played after the current or last queued animation for a track, and sets the track entry's + /// . If the track is empty, it is equivalent to calling + /// . + /// + /// Track number. + /// Mix duration. + /// If > 0, sets . If <= 0, the delay set is the duration of the previous track entry + /// minus any mix duration plus the specified Delay (ie the mix ends at (Delay = 0) or + /// before (Delay < 0) the previous track entry duration). If the previous entry is looping, its next + /// loop completion is used instead of its duration. + /// A track entry to allow further customization of animation playback. References to the track entry must not be kept + /// after the event occurs. + /// + public TrackEntry AddEmptyAnimation (int trackIndex, float mixDuration, float delay) { + TrackEntry entry = AddAnimation(trackIndex, AnimationState.EmptyAnimation, false, delay); + if (delay <= 0) entry.delay += entry.mixDuration - mixDuration; + entry.mixDuration = mixDuration; + entry.trackEnd = mixDuration; + return entry; + } + + /// + /// Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix + /// duration. + public void SetEmptyAnimations (float mixDuration) { + bool oldDrainDisabled = queue.drainDisabled; + queue.drainDisabled = true; + TrackEntry[] tracksItems = tracks.Items; + for (int i = 0, n = tracks.Count; i < n; i++) { + TrackEntry current = tracksItems[i]; + if (current != null) SetEmptyAnimation(current.trackIndex, mixDuration); + } + queue.drainDisabled = oldDrainDisabled; + queue.Drain(); + } + + private TrackEntry ExpandToIndex (int index) { + if (index < tracks.Count) return tracks.Items[index]; + tracks.Resize(index + 1); + return null; + } + + /// Object-pooling version of new TrackEntry. Obtain an unused TrackEntry from the pool and clear/initialize its values. + /// May be null. + private TrackEntry NewTrackEntry (int trackIndex, Animation animation, bool loop, TrackEntry last) { + TrackEntry entry = trackEntryPool.Obtain(); + entry.trackIndex = trackIndex; + entry.animation = animation; + entry.loop = loop; + entry.holdPrevious = false; + + entry.eventThreshold = 0; + entry.alphaAttachmentThreshold = 0; + entry.mixAttachmentThreshold = 0; + entry.mixDrawOrderThreshold = 0; + + entry.animationStart = 0; + entry.animationEnd = animation.Duration; + entry.animationLast = -1; + entry.nextAnimationLast = -1; + + entry.delay = 0; + entry.trackTime = 0; + entry.trackLast = -1; + entry.nextTrackLast = -1; + entry.trackEnd = float.MaxValue; + entry.timeScale = 1; + + entry.alpha = 1; + entry.interruptAlpha = 1; + entry.mixTime = 0; + entry.mixDuration = last == null ? 0 : data.GetMix(last.animation, animation); + entry.mixBlend = MixBlend.Replace; + return entry; + } + + /// Removes the next entry and all entries after it for the specified entry. + public void ClearNext (TrackEntry entry) { + TrackEntry next = entry.next; + while (next != null) { + queue.Dispose(next); + next = next.next; + } + entry.next = null; + } + + private void AnimationsChanged () { + animationsChanged = false; + + // Process in the order that animations are applied. + propertyIds.Clear(); + int n = tracks.Count; + TrackEntry[] tracksItems = tracks.Items; + for (int i = 0; i < n; i++) { + TrackEntry entry = tracksItems[i]; + if (entry == null) continue; + while (entry.mixingFrom != null) // Move to last entry, then iterate in reverse. + entry = entry.mixingFrom; + do { + if (entry.mixingTo == null || entry.mixBlend != MixBlend.Add) ComputeHold(entry); + entry = entry.mixingTo; + } while (entry != null); + } + } + + private void ComputeHold (TrackEntry entry) { + TrackEntry to = entry.mixingTo; + Timeline[] timelines = entry.animation.timelines.Items; + int timelinesCount = entry.animation.timelines.Count; + int[] timelineMode = entry.timelineMode.Resize(timelinesCount).Items; + entry.timelineHoldMix.Clear(); + TrackEntry[] timelineHoldMix = entry.timelineHoldMix.Resize(timelinesCount).Items; + HashSet propertyIds = this.propertyIds; + + if (to != null && to.holdPrevious) { + for (int i = 0; i < timelinesCount; i++) + timelineMode[i] = propertyIds.AddAll(timelines[i].PropertyIds) ? AnimationState.HoldFirst : AnimationState.HoldSubsequent; + + return; + } + + // outer: + for (int i = 0; i < timelinesCount; i++) { + Timeline timeline = timelines[i]; + String[] ids = timeline.PropertyIds; + if (!propertyIds.AddAll(ids)) + timelineMode[i] = AnimationState.Subsequent; + else if (to == null || timeline is AttachmentTimeline || timeline is DrawOrderTimeline + || timeline is EventTimeline || !to.animation.HasTimeline(ids)) { + timelineMode[i] = AnimationState.First; + } else { + for (TrackEntry next = to.mixingTo; next != null; next = next.mixingTo) { + if (next.animation.HasTimeline(ids)) continue; + if (next.mixDuration > 0) { + timelineMode[i] = AnimationState.HoldMix; + timelineHoldMix[i] = next; + goto continue_outer; // continue outer; + } + break; + } + timelineMode[i] = AnimationState.HoldFirst; + } + continue_outer: { } + } + } + + /// The track entry for the animation currently playing on the track, or null if no animation is currently playing. + public TrackEntry GetCurrent (int trackIndex) { + if (trackIndex >= tracks.Count) return null; + return tracks.Items[trackIndex]; + } + + /// Discards all listener notifications that have not yet been delivered. This can be useful to call from an + /// AnimationState event subscriber when it is known that further notifications that may have been already queued for delivery + /// are not wanted because new animations are being set. + /// + public void ClearListenerNotifications () { + queue.Clear(); + } + + /// + /// Multiplier for the delta time when the animation state is updated, causing time for all animations and mixes to play slower + /// or faster. Defaults to 1. + /// + /// See TrackEntry for affecting a single animation. + /// + public float TimeScale { get { return timeScale; } set { timeScale = value; } } + + /// The to look up mix durations. + public AnimationStateData Data { + get { + return data; + } + set { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + this.data = value; + } + } + + /// A list of tracks that have animations, which may contain nulls. + public ExposedList Tracks { get { return tracks; } } + + override public string ToString () { + System.Text.StringBuilder buffer = new System.Text.StringBuilder(); + TrackEntry[] tracksItems = tracks.Items; + for (int i = 0, n = tracks.Count; i < n; i++) { + TrackEntry entry = tracksItems[i]; + if (entry == null) continue; + if (buffer.Length > 0) buffer.Append(", "); + buffer.Append(entry.ToString()); + } + if (buffer.Length == 0) return ""; + return buffer.ToString(); + } + } + + /// + /// + /// Stores settings and other state for the playback of an animation on an track. + /// + /// References to a track entry must not be kept after the event occurs. + /// + public class TrackEntry : Pool.IPoolable { + internal Animation animation; + + internal TrackEntry previous, next, mixingFrom, mixingTo; + // difference to libgdx reference: delegates are used for event callbacks instead of 'AnimationStateListener listener'. + /// See + /// API Reference documentation pages here for details. Usage in C# and spine-unity is explained + /// here + /// on the spine-unity documentation pages. + public event AnimationState.TrackEntryDelegate Start, Interrupt, End, Dispose, Complete; + public event AnimationState.TrackEntryEventDelegate Event; + internal void OnStart () { if (Start != null) Start(this); } + internal void OnInterrupt () { if (Interrupt != null) Interrupt(this); } + internal void OnEnd () { if (End != null) End(this); } + internal void OnDispose () { if (Dispose != null) Dispose(this); } + internal void OnComplete () { if (Complete != null) Complete(this); } + internal void OnEvent (Event e) { if (Event != null) Event(this, e); } + + internal int trackIndex; + + internal bool loop, holdPrevious, reverse, shortestRotation; + internal float eventThreshold, mixAttachmentThreshold, alphaAttachmentThreshold, mixDrawOrderThreshold; + internal float animationStart, animationEnd, animationLast, nextAnimationLast; + internal float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale = 1f; + internal float alpha, mixTime, mixDuration, interruptAlpha, totalAlpha; + internal MixBlend mixBlend = MixBlend.Replace; + internal readonly ExposedList timelineMode = new ExposedList(); + internal readonly ExposedList timelineHoldMix = new ExposedList(); + internal readonly ExposedList timelinesRotation = new ExposedList(); + + // IPoolable.Reset() + public void Reset () { + previous = null; + next = null; + mixingFrom = null; + mixingTo = null; + animation = null; + // replaces 'listener = null;' since delegates are used for event callbacks + Start = null; + Interrupt = null; + End = null; + Dispose = null; + Complete = null; + Event = null; + timelineMode.Clear(); + timelineHoldMix.Clear(); + timelinesRotation.Clear(); + } + + /// The index of the track where this entry is either current or queued. + /// + public int TrackIndex { get { return trackIndex; } } + + /// The animation to apply for this track entry. + public Animation Animation { get { return animation; } } + + /// + /// If true, the animation will repeat. If false it will not, instead its last frame is applied if played beyond its + /// duration. + public bool Loop { get { return loop; } set { loop = value; } } + + /// + /// + /// Seconds to postpone playing the animation. When this track entry is the current track entry, Delay + /// postpones incrementing the . When this track entry is queued, Delay is the time from + /// the start of the previous animation to when this track entry will become the current track entry (ie when the previous + /// track entry >= this track entry's Delay). + /// + /// affects the delay. + /// + /// When using with a delay <= 0, the delay + /// is set using the mix duration from the . If is set afterward, the delay + /// may need to be adjusted. + public float Delay { get { return delay; } set { delay = value; } } + + /// + /// Current time in seconds this track entry has been the current track entry. The track time determines + /// . The track time can be set to start the animation at a time other than 0, without affecting + /// looping. + public float TrackTime { get { return trackTime; } set { trackTime = value; } } + + /// + /// + /// The track time in seconds when this animation will be removed from the track. Defaults to the highest possible float + /// value, meaning the animation will be applied until a new animation is set or the track is cleared. If the track end time + /// is reached, no other animations are queued for playback, and mixing from any previous animations is complete, then the + /// properties keyed by the animation are set to the setup pose and the track is cleared. + /// + /// It may be desired to use rather than have the animation + /// abruptly cease being applied. + /// + public float TrackEnd { get { return trackEnd; } set { trackEnd = value; } } + + /// + /// If this track entry is non-looping, the track time in seconds when is reached, or the current + /// if it has already been reached. If this track entry is looping, the track time when this + /// animation will reach its next (the next loop completion). + public float TrackComplete { + get { + 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. + } + } + + /// + /// + /// Seconds when this animation starts, both initially and after looping. Defaults to 0. + /// + /// When changing the AnimationStart time, it often makes sense to set to the same + /// value to prevent timeline keys before the start time from triggering. + /// + public float AnimationStart { get { return animationStart; } set { animationStart = value; } } + + /// + /// Seconds for the last frame of this animation. Non-looping animations won't play past this time. Looping animations will + /// loop back to at this time. Defaults to the animation . + /// + public float AnimationEnd { get { return animationEnd; } set { animationEnd = value; } } + + /// + /// 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 AnimationLast time (exclusive) and + /// AnimationTime (inclusive). Defaults to -1 to ensure triggers on frame 0 happen the first time this animation + /// is applied. + public float AnimationLast { + get { return animationLast; } + set { + animationLast = value; + nextAnimationLast = value; + } + } + + /// + /// Uses to compute the AnimationTime. When the TrackTime is 0, the + /// AnimationTime is equal to the AnimationStart time. + /// + /// The animationTime is between and , except if this + /// track entry is non-looping and is >= to the animation , then + /// animationTime continues to increase past . + /// + public float AnimationTime { + get { + if (loop) { + float duration = animationEnd - animationStart; + if (duration == 0) return animationStart; + return (trackTime % duration) + animationStart; + } + float animationTime = trackTime + animationStart; + return animationEnd >= animation.duration ? animationTime : Math.Min(animationTime, animationEnd); + } + } + + /// + /// + /// Multiplier for the delta time when this track entry is updated, causing time for this animation to pass slower or + /// faster. Defaults to 1. + /// + /// Values < 0 are not supported. To play an animation in reverse, use . + /// + /// is not affected by track entry time scale, so may need to be adjusted to + /// match the animation speed. + /// + /// When using with a Delay <= 0, the + /// is set using the mix duration from the , assuming time scale to be 1. If + /// the time scale is not 1, the delay may need to be adjusted. + /// + /// See AnimationState for affecting all animations. + /// + public float TimeScale { get { return timeScale; } set { timeScale = value; } } + + /// + /// + /// Values < 1 mix this animation with the skeleton's current pose (usually the pose resulting from lower tracks). Defaults + /// to 1, which overwrites the skeleton's current pose with this animation. + /// + /// Typically track 0 is used to completely pose the skeleton, then alpha is 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. + /// + public float Alpha { get { return alpha; } set { alpha = value; } } + + public float InterruptAlpha { get { return interruptAlpha; } } + + /// + /// When the mix percentage ( / ) is less than the + /// EventThreshold, event timelines are applied while this animation is being mixed out. Defaults to 0, so event + /// timelines are not applied while this animation is being mixed out. + /// + public float EventThreshold { get { return eventThreshold; } set { eventThreshold = value; } } + + /// + /// When is greater than AlphaAttachmentThreshold, attachment timelines are applied. + /// Defaults to 0, so attachment timelines are always applied. + /// + public float AlphaAttachmentThreshold { get { return alphaAttachmentThreshold; } set { alphaAttachmentThreshold = value; } } + + /// + /// When the mix percentage ( / ) is less than the + /// MixAttachmentThreshold, attachment timelines are applied while this animation is being mixed out. Defaults + /// to 0, so attachment timelines are not applied while this animation is being mixed out. + /// + public float MixAttachmentThreshold { get { return mixAttachmentThreshold; } set { mixAttachmentThreshold = value; } } + + /// + /// When the mix percentage ( / ) is less than the + /// MixDrawOrderThreshold, draw order timelines are applied while this animation is being mixed out. Defaults to + /// 0, so draw order timelines are not applied while this animation is being mixed out. + /// + public float MixDrawOrderThreshold { get { return mixDrawOrderThreshold; } set { mixDrawOrderThreshold = value; } } + + /// + /// The animation queued to start after this animation, or null if there is none. next makes up a doubly linked + /// list. + /// + /// See to truncate the list. + public TrackEntry Next { get { return next; } } + + /// + /// The animation queued to play before this animation, or null. previous makes up a doubly linked list. + public TrackEntry Previous { get { return previous; } } + + /// Returns true if this track entry has been applied at least once. + /// + public bool WasApplied { + get { return nextTrackLast != -1; } + } + + /// Returns true if there is a track entry that will become the current track entry during the + /// next . + public bool IsNextReady { + get { + return (next != null) && (nextTrackLast - next.delay >= 0); + } + } + + /// + /// Returns true if at least one loop has been completed. + /// + public bool IsComplete { + get { return trackTime >= animationEnd - animationStart; } + } + + /// + /// Seconds from 0 to the when mixing from the previous animation to this animation. May be + /// slightly more than MixDuration when the mix is complete. + public float MixTime { get { return mixTime; } set { mixTime = value; } } + + /// + /// + /// 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 MixDuration can be set manually rather than use the value from + /// . In that case, the MixDuration can be set for a new + /// track entry only before is first called. + /// + /// When using with a Delay <= 0, the + /// is set using the mix duration from the . If mixDuration is set + /// afterward, the delay may need to be adjusted. For example: + /// entry.Delay = entry.previous.TrackComplete - entry.MixDuration; + /// Alternatively, can be used to recompute the delay: + /// entry.SetMixDuration(0.25f, 0); + /// + public float MixDuration { get { return mixDuration; } set { mixDuration = value; } } + + /// Sets both and . + /// If > 0, sets . If <= 0, the delay set is the duration of the previous track + /// entry minus the specified mix duration plus the specified delay (ie the mix ends at + /// (delay = 0) or before (delay < 0) the previous track entry duration). If the previous + /// entry is looping, its next loop completion is used instead of its duration. + public void SetMixDuration (float mixDuration, float delay) { + this.mixDuration = mixDuration; + if (previous != null && delay <= 0) delay += previous.TrackComplete - mixDuration; + this.delay = delay; + } + + /// + /// + /// Controls how properties keyed in the animation are mixed with lower tracks. Defaults to . + /// + /// Track entries on track 0 ignore this setting and always use . + /// + /// The MixBlend can be set for a new track entry only before is first + /// called. + /// + public MixBlend MixBlend { get { return mixBlend; } set { mixBlend = value; } } + + /// + /// The track entry for the previous animation when mixing from the previous animation to this animation, or null if no + /// mixing is currently occurring. When mixing from multiple animations, MixingFrom makes up a linked list. + public TrackEntry MixingFrom { get { return mixingFrom; } } + + /// + /// The track entry for the next animation when mixing from this animation to the next animation, or null if no mixing is + /// currently occurring. When mixing to multiple animations, MixingTo makes up a linked list. + public TrackEntry MixingTo { get { return mixingTo; } } + + /// + /// + /// 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. + /// + public bool HoldPrevious { get { return holdPrevious; } set { holdPrevious = value; } } + + /// + /// If true, the animation will be applied in reverse. Events are not fired when an animation is applied in reverse. + public bool Reverse { get { return reverse; } set { reverse = value; } } + + /// + /// If true, mixing rotation between tracks always uses the shortest rotation direction. If the rotation is animated, the + /// shortest rotation direction may change during the mix. + /// + /// If false, the shortest rotation direction is remembered when the mix starts and the same direction is used for the rest + /// of the mix. Defaults to false. + public bool ShortestRotation { get { return shortestRotation; } set { shortestRotation = value; } } + + /// Returns true if this entry is for the empty animation. See , + /// , and . + /// + public bool IsEmptyAnimation { get { return animation == AnimationState.EmptyAnimation; } } + + /// + /// + /// 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 and starting animations on other tracks. + /// + /// Mixing with 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. + /// + public void ResetRotationDirections () { + timelinesRotation.Clear(); + } + + override public string ToString () { + return animation == null ? "" : animation.name; + } + + // Note: This method is required by SpineAnimationStateMixerBehaviour, + // which is part of the timeline extension package. Thus the internal member variable + // nextTrackLast is not accessible. We favor providing this method + // over exposing nextTrackLast as public property, which would rather confuse users. + public void AllowImmediateQueue () { + if (nextTrackLast < 0) nextTrackLast = 0; + } + } + + class EventQueue { + private readonly List eventQueueEntries = new List(); + internal bool drainDisabled; + + private readonly AnimationState state; + private readonly Pool trackEntryPool; + internal event Action AnimationsChanged; + + internal EventQueue (AnimationState state, Action HandleAnimationsChanged, Pool trackEntryPool) { + this.state = state; + this.AnimationsChanged += HandleAnimationsChanged; + this.trackEntryPool = trackEntryPool; + } + + internal void Start (TrackEntry entry) { + eventQueueEntries.Add(new EventQueueEntry(EventType.Start, entry)); + if (AnimationsChanged != null) AnimationsChanged(); + } + + internal void Interrupt (TrackEntry entry) { + eventQueueEntries.Add(new EventQueueEntry(EventType.Interrupt, entry)); + } + + internal void End (TrackEntry entry) { + eventQueueEntries.Add(new EventQueueEntry(EventType.End, entry)); + if (AnimationsChanged != null) AnimationsChanged(); + } + + internal void Dispose (TrackEntry entry) { + eventQueueEntries.Add(new EventQueueEntry(EventType.Dispose, entry)); + } + + internal void Complete (TrackEntry entry) { + eventQueueEntries.Add(new EventQueueEntry(EventType.Complete, entry)); + } + + internal void Event (TrackEntry entry, Event e) { + eventQueueEntries.Add(new EventQueueEntry(EventType.Event, entry, e)); + } + + /// Raises all events in the queue and drains the queue. + internal void Drain () { + if (drainDisabled) return; + drainDisabled = true; + + List eventQueueEntries = this.eventQueueEntries; + AnimationState state = this.state; + + // Don't cache eventQueueEntries.Count so callbacks can queue their own events (eg, call SetAnimation in AnimationState_Complete). + for (int i = 0; i < eventQueueEntries.Count; i++) { + EventQueueEntry queueEntry = eventQueueEntries[i]; + TrackEntry trackEntry = queueEntry.entry; + + switch (queueEntry.type) { + case EventType.Start: + trackEntry.OnStart(); + state.OnStart(trackEntry); + break; + case EventType.Interrupt: + trackEntry.OnInterrupt(); + state.OnInterrupt(trackEntry); + break; + case EventType.End: + trackEntry.OnEnd(); + state.OnEnd(trackEntry); + goto case EventType.Dispose; // Fall through. (C#) + case EventType.Dispose: + trackEntry.OnDispose(); + state.OnDispose(trackEntry); + trackEntryPool.Free(trackEntry); + break; + case EventType.Complete: + trackEntry.OnComplete(); + state.OnComplete(trackEntry); + break; + case EventType.Event: + trackEntry.OnEvent(queueEntry.e); + state.OnEvent(trackEntry, queueEntry.e); + break; + } + } + eventQueueEntries.Clear(); + + drainDisabled = false; + } + + internal void Clear () { + eventQueueEntries.Clear(); + } + + struct EventQueueEntry { + public EventType type; + public TrackEntry entry; + public Event e; + + public EventQueueEntry (EventType eventType, TrackEntry trackEntry, Event e = null) { + this.type = eventType; + this.entry = trackEntry; + this.e = e; + } + } + + enum EventType { + Start, Interrupt, End, Dispose, Complete, Event + } + } + + class Pool where T : class, new() { + public readonly int max; + readonly Stack freeObjects; + + public int Count { get { return freeObjects.Count; } } + public int Peak { get; private set; } + + public Pool (int initialCapacity = 16, int max = int.MaxValue) { + freeObjects = new Stack(initialCapacity); + this.max = max; + } + + public T Obtain () { + return freeObjects.Count == 0 ? new T() : freeObjects.Pop(); + } + + public void Free (T obj) { + if (obj == null) throw new ArgumentNullException("obj", "obj cannot be null"); + if (freeObjects.Count < max) { + freeObjects.Push(obj); + Peak = Math.Max(Peak, freeObjects.Count); + } + Reset(obj); + } + + public void Clear () { + freeObjects.Clear(); + } + + protected void Reset (T obj) { + IPoolable poolable = obj as IPoolable; + if (poolable != null) poolable.Reset(); + } + + public interface IPoolable { + void Reset (); + } + } + + public static class HashSetExtensions { + public static bool AddAll (this HashSet set, T[] addSet) { + bool anyItemAdded = false; + for (int i = 0, n = addSet.Length; i < n; ++i) { + T item = addSet[i]; + anyItemAdded |= set.Add(item); + } + return anyItemAdded; + } + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/AnimationStateData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/AnimationStateData.cs new file mode 100644 index 0000000..9d07f40 --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/AnimationStateData.cs @@ -0,0 +1,114 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +using System; +using System.Collections.Generic; + +namespace Spine4_2_33 { + + /// Stores mix (crossfade) durations to be applied when AnimationState animations are changed. + public class AnimationStateData { + internal SkeletonData skeletonData; + readonly Dictionary animationToMixTime = new Dictionary(AnimationPairComparer.Instance); + internal float defaultMix; + + /// The SkeletonData to look up animations when they are specified by name. + public SkeletonData SkeletonData { get { return skeletonData; } } + + /// + /// The mix duration to use when no mix duration has been specifically defined between two animations. + public float DefaultMix { get { return defaultMix; } set { defaultMix = value; } } + + public AnimationStateData (SkeletonData skeletonData) { + if (skeletonData == null) throw new ArgumentException("skeletonData cannot be null.", "skeletonData"); + this.skeletonData = skeletonData; + } + + /// Sets a mix duration by animation names. + public void SetMix (string fromName, string toName, float duration) { + Animation from = skeletonData.FindAnimation(fromName); + if (from == null) throw new ArgumentException("Animation not found: " + fromName, "fromName"); + Animation to = skeletonData.FindAnimation(toName); + if (to == null) throw new ArgumentException("Animation not found: " + toName, "toName"); + SetMix(from, to, duration); + } + + /// Sets a mix duration when changing from the specified animation to the other. + /// See TrackEntry.MixDuration. + public void SetMix (Animation from, Animation to, float duration) { + if (from == null) throw new ArgumentNullException("from", "from cannot be null."); + if (to == null) throw new ArgumentNullException("to", "to cannot be null."); + AnimationPair key = new AnimationPair(from, to); + animationToMixTime.Remove(key); + animationToMixTime.Add(key, 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. + /// + public float GetMix (Animation from, Animation to) { + if (from == null) throw new ArgumentNullException("from", "from cannot be null."); + if (to == null) throw new ArgumentNullException("to", "to cannot be null."); + AnimationPair key = new AnimationPair(from, to); + float duration; + if (animationToMixTime.TryGetValue(key, out duration)) return duration; + return defaultMix; + } + + public struct AnimationPair { + public readonly Animation a1; + public readonly Animation a2; + + public AnimationPair (Animation a1, Animation a2) { + this.a1 = a1; + this.a2 = a2; + } + + public override string ToString () { + return a1.name + "->" + a2.name; + } + } + + // Avoids boxing in the dictionary. + public class AnimationPairComparer : IEqualityComparer { + public static readonly AnimationPairComparer Instance = new AnimationPairComparer(); + + bool IEqualityComparer.Equals (AnimationPair x, AnimationPair y) { + return ReferenceEquals(x.a1, y.a1) && ReferenceEquals(x.a2, y.a2); + } + + int IEqualityComparer.GetHashCode (AnimationPair obj) { + // from Tuple.CombineHashCodes // return (((h1 << 5) + h1) ^ h2); + int h1 = obj.a1.GetHashCode(); + return (((h1 << 5) + h1) ^ obj.a2.GetHashCode()); + } + } + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Atlas.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Atlas.cs new file mode 100644 index 0000000..e526884 --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Atlas.cs @@ -0,0 +1,372 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +#if (UNITY_5 || UNITY_5_3_OR_NEWER || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1) +#define IS_UNITY +#endif + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Reflection; + +#if WINDOWS_STOREAPP +using System.Threading.Tasks; +using Windows.Storage; +#endif + +namespace Spine4_2_33 { + public class Atlas : IEnumerable { + readonly List pages = new List(); + List regions = new List(); + TextureLoader textureLoader; + + #region IEnumerable implementation + public IEnumerator GetEnumerator () { + return regions.GetEnumerator(); + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () { + return regions.GetEnumerator(); + } + #endregion + + public List Regions { get { return regions; } } + public List Pages { get { return pages; } } + +#if !(IS_UNITY) +#if WINDOWS_STOREAPP + private async Task ReadFile (string path, TextureLoader textureLoader) { + var folder = Windows.ApplicationModel.Package.Current.InstalledLocation; + var file = await folder.GetFileAsync(path).AsTask().ConfigureAwait(false); + using (StreamReader reader = new StreamReader(await file.OpenStreamForReadAsync().ConfigureAwait(false))) { + try { + Atlas atlas = new Atlas(reader, Path.GetDirectoryName(path), textureLoader); + this.pages = atlas.pages; + this.regions = atlas.regions; + this.textureLoader = atlas.textureLoader; + } catch (Exception ex) { + throw new Exception("Error reading atlas file: " + path, ex); + } + } + } + + public Atlas (string path, TextureLoader textureLoader) { + this.ReadFile(path, textureLoader).Wait(); + } +#else + public Atlas (string path, TextureLoader textureLoader) { +#if WINDOWS_PHONE + Stream stream = Microsoft.Xna.Framework.TitleContainer.OpenStream(path); + using (StreamReader reader = new StreamReader(stream)) { +#else + using (StreamReader reader = new StreamReader(path)) { +#endif // WINDOWS_PHONE + try { + Atlas atlas = new Atlas(reader, Path.GetDirectoryName(path), textureLoader); + this.pages = atlas.pages; + this.regions = atlas.regions; + this.textureLoader = atlas.textureLoader; + } catch (Exception ex) { + throw new Exception("Error reading atlas file: " + path, ex); + } + } + } +#endif // WINDOWS_STOREAPP +#endif + + public Atlas (List pages, List regions) { + if (pages == null) throw new ArgumentNullException("pages", "pages cannot be null."); + if (regions == null) throw new ArgumentNullException("regions", "regions cannot be null."); + this.pages = pages; + this.regions = regions; + this.textureLoader = null; + } + + public Atlas (TextReader reader, string imagesDir, TextureLoader textureLoader) { + if (reader == null) throw new ArgumentNullException("reader", "reader cannot be null."); + if (imagesDir == null) throw new ArgumentNullException("imagesDir", "imagesDir cannot be null."); + if (textureLoader == null) throw new ArgumentNullException("textureLoader", "textureLoader cannot be null."); + this.textureLoader = textureLoader; + + string[] entry = new string[5]; + AtlasPage page = null; + AtlasRegion region = null; + + Dictionary pageFields = new Dictionary(5); + pageFields.Add("size", () => { + page.width = int.Parse(entry[1], CultureInfo.InvariantCulture); + page.height = int.Parse(entry[2], CultureInfo.InvariantCulture); + }); + pageFields.Add("format", () => { + page.format = (Format)Enum.Parse(typeof(Format), entry[1], false); + }); + pageFields.Add("filter", () => { + page.minFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), entry[1], false); + page.magFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), entry[2], false); + }); + pageFields.Add("repeat", () => { + if (entry[1].IndexOf('x') != -1) page.uWrap = TextureWrap.Repeat; + if (entry[1].IndexOf('y') != -1) page.vWrap = TextureWrap.Repeat; + }); + pageFields.Add("pma", () => { + page.pma = entry[1] == "true"; + }); + + Dictionary regionFields = new Dictionary(8); + regionFields.Add("xy", () => { // Deprecated, use bounds. + region.x = int.Parse(entry[1], CultureInfo.InvariantCulture); + region.y = int.Parse(entry[2], CultureInfo.InvariantCulture); + }); + regionFields.Add("size", () => { // Deprecated, use bounds. + region.width = int.Parse(entry[1], CultureInfo.InvariantCulture); + region.height = int.Parse(entry[2], CultureInfo.InvariantCulture); + }); + regionFields.Add("bounds", () => { + region.x = int.Parse(entry[1], CultureInfo.InvariantCulture); + region.y = int.Parse(entry[2], CultureInfo.InvariantCulture); + region.width = int.Parse(entry[3], CultureInfo.InvariantCulture); + region.height = int.Parse(entry[4], CultureInfo.InvariantCulture); + }); + regionFields.Add("offset", () => { // Deprecated, use offsets. + region.offsetX = int.Parse(entry[1], CultureInfo.InvariantCulture); + region.offsetY = int.Parse(entry[2], CultureInfo.InvariantCulture); + }); + regionFields.Add("orig", () => { // Deprecated, use offsets. + region.originalWidth = int.Parse(entry[1], CultureInfo.InvariantCulture); + region.originalHeight = int.Parse(entry[2], CultureInfo.InvariantCulture); + }); + regionFields.Add("offsets", () => { + region.offsetX = int.Parse(entry[1], CultureInfo.InvariantCulture); + region.offsetY = int.Parse(entry[2], CultureInfo.InvariantCulture); + region.originalWidth = int.Parse(entry[3], CultureInfo.InvariantCulture); + region.originalHeight = int.Parse(entry[4], CultureInfo.InvariantCulture); + }); + regionFields.Add("rotate", () => { + string value = entry[1]; + if (value == "true") + region.degrees = 90; + else if (value != "false") + region.degrees = int.Parse(value, CultureInfo.InvariantCulture); + }); + regionFields.Add("index", () => { + region.index = int.Parse(entry[1], CultureInfo.InvariantCulture); + }); + + string line = reader.ReadLine(); + // Ignore empty lines before first entry. + while (line != null && line.Trim().Length == 0) + line = reader.ReadLine(); + // Header entries. + while (true) { + if (line == null || line.Trim().Length == 0) break; + if (ReadEntry(entry, line) == 0) break; // Silently ignore all header fields. + line = reader.ReadLine(); + } + // Page and region entries. + List names = null; + List values = null; + while (true) { + if (line == null) break; + if (line.Trim().Length == 0) { + page = null; + line = reader.ReadLine(); + } else if (page == null) { + page = new AtlasPage(); + page.name = line.Trim(); + while (true) { + if (ReadEntry(entry, line = reader.ReadLine()) == 0) break; + Action field; + if (pageFields.TryGetValue(entry[0], out field)) field(); // Silently ignore unknown page fields. + } + textureLoader.Load(page, Path.Combine(imagesDir, page.name)); + pages.Add(page); + } else { + region = new AtlasRegion(); + region.page = page; + region.name = line; + while (true) { + int count = ReadEntry(entry, line = reader.ReadLine()); + if (count == 0) break; + Action field; + if (regionFields.TryGetValue(entry[0], out field)) + field(); + else { + if (names == null) { + names = new List(8); + values = new List(8); + } + names.Add(entry[0]); + int[] entryValues = new int[count]; + for (int i = 0; i < count; i++) + int.TryParse(entry[i + 1], NumberStyles.Any, CultureInfo.InvariantCulture, out entryValues[i]); // Silently ignore non-integer values. + values.Add(entryValues); + } + } + if (region.originalWidth == 0 && region.originalHeight == 0) { + region.originalWidth = region.width; + region.originalHeight = region.height; + } + if (names != null && names.Count > 0) { + region.names = names.ToArray(); + region.values = values.ToArray(); + names.Clear(); + values.Clear(); + } + region.u = region.x / (float)page.width; + region.v = region.y / (float)page.height; + if (region.degrees == 90) { + region.u2 = (region.x + region.height) / (float)page.width; + region.v2 = (region.y + region.width) / (float)page.height; + + int tempSwap = region.packedWidth; + region.packedWidth = region.packedHeight; + region.packedHeight = tempSwap; + } else { + region.u2 = (region.x + region.width) / (float)page.width; + region.v2 = (region.y + region.height) / (float)page.height; + } + regions.Add(region); + } + } + } + + static private int ReadEntry (string[] entry, string line) { + if (line == null) return 0; + line = line.Trim(); + if (line.Length == 0) return 0; + int colon = line.IndexOf(':'); + if (colon == -1) return 0; + entry[0] = line.Substring(0, colon).Trim(); + for (int i = 1, lastMatch = colon + 1; ; i++) { + int comma = line.IndexOf(',', lastMatch); + if (comma == -1) { + entry[i] = line.Substring(lastMatch).Trim(); + return i; + } + entry[i] = line.Substring(lastMatch, comma - lastMatch).Trim(); + lastMatch = comma + 1; + if (i == 4) return 4; + } + } + + public void FlipV () { + for (int i = 0, n = regions.Count; i < n; i++) { + AtlasRegion region = regions[i]; + region.v = 1 - region.v; + region.v2 = 1 - region.v2; + } + } + + /// 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. + /// The region, or null. + public AtlasRegion FindRegion (string name) { + for (int i = 0, n = regions.Count; i < n; i++) + if (regions[i].name == name) return regions[i]; + return null; + } + + public void Dispose () { + if (textureLoader == null) return; + for (int i = 0, n = pages.Count; i < n; i++) + textureLoader.Unload(pages[i].rendererObject); + } + } + + public enum Format { + Alpha, + Intensity, + LuminanceAlpha, + RGB565, + RGBA4444, + RGB888, + RGBA8888 + } + + public enum TextureFilter { + Nearest, + Linear, + MipMap, + MipMapNearestNearest, + MipMapLinearNearest, + MipMapNearestLinear, + MipMapLinearLinear + } + + public enum TextureWrap { + MirroredRepeat, + ClampToEdge, + Repeat + } + + public class AtlasPage { + public string name; + public int width, height; + public Format format = Format.RGBA8888; + public TextureFilter minFilter = TextureFilter.Nearest; + public TextureFilter magFilter = TextureFilter.Nearest; + public TextureWrap uWrap = TextureWrap.ClampToEdge; + public TextureWrap vWrap = TextureWrap.ClampToEdge; + public bool pma; + public object rendererObject; + + public AtlasPage Clone () { + return MemberwiseClone() as AtlasPage; + } + } + + public class AtlasRegion : TextureRegion { + public AtlasPage page; + public string name; + public int x, y; + public float offsetX, offsetY; + public int originalWidth, originalHeight; + public int packedWidth { get { return width; } set { width = value; } } + public int packedHeight { get { return height; } set { height = value; } } + public int degrees; + public bool rotate; + public int index; + public string[] names; + public int[][] values; + + override public int OriginalWidth { get { return originalWidth; } } + override public int OriginalHeight { get { return originalHeight; } } + + public AtlasRegion Clone () { + return MemberwiseClone() as AtlasRegion; + } + } + + public interface TextureLoader { + void Load (AtlasPage page, string path); + void Unload (Object texture); + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/AtlasAttachmentLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/AtlasAttachmentLoader.cs new file mode 100644 index 0000000..44fc4c8 --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/AtlasAttachmentLoader.cs @@ -0,0 +1,109 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +using System; + +namespace Spine4_2_33 { + + /// + /// An AttachmentLoader that configures attachments using texture regions from an Atlas. + /// See Loading Skeleton Data in the Spine Runtimes Guide. + /// + public class AtlasAttachmentLoader : AttachmentLoader { + private Atlas[] atlasArray; + + public AtlasAttachmentLoader (params Atlas[] atlasArray) { + if (atlasArray == null) throw new ArgumentNullException("atlas", "atlas array cannot be null."); + this.atlasArray = atlasArray; + } + + private void LoadSequence (string name, string basePath, Sequence sequence) { + TextureRegion[] regions = sequence.Regions; + for (int i = 0, n = regions.Length; i < n; i++) { + string path = sequence.GetPath(basePath, i); + regions[i] = FindRegion(path); + if (regions[i] == null) throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name)); + } + } + + public RegionAttachment NewRegionAttachment (Skin skin, string name, string path, Sequence sequence) { + RegionAttachment attachment = new RegionAttachment(name); + if (sequence != null) + LoadSequence(name, path, sequence); + else { + AtlasRegion region = FindRegion(path); + if (region == null) + throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name)); + attachment.Region = region; + } + return attachment; + } + + public MeshAttachment NewMeshAttachment (Skin skin, string name, string path, Sequence sequence) { + MeshAttachment attachment = new MeshAttachment(name); + if (sequence != null) + LoadSequence(name, path, sequence); + else { + AtlasRegion region = FindRegion(path); + if (region == null) + throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name)); + attachment.Region = region; + } + return attachment; + } + + public BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, string name) { + return new BoundingBoxAttachment(name); + } + + public PathAttachment NewPathAttachment (Skin skin, string name) { + return new PathAttachment(name); + } + + public PointAttachment NewPointAttachment (Skin skin, string name) { + return new PointAttachment(name); + } + + public ClippingAttachment NewClippingAttachment (Skin skin, string name) { + return new ClippingAttachment(name); + } + + public AtlasRegion FindRegion (string name) { + AtlasRegion region; + + for (int i = 0; i < atlasArray.Length; i++) { + region = atlasArray[i].FindRegion(name); + if (region != null) + return region; + } + + return null; + } + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/Attachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/Attachment.cs new file mode 100644 index 0000000..687b2c4 --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/Attachment.cs @@ -0,0 +1,56 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +using System; + +namespace Spine4_2_33 { + + /// The base class for all attachments. + abstract public class Attachment { + /// The attachment's name. + public string Name { get; } + + protected Attachment (string name) { + if (name == null) throw new ArgumentNullException("name", "name cannot be null"); + this.Name = name; + } + + /// Copy constructor. + protected Attachment (Attachment other) { + Name = other.Name; + } + + override public string ToString () { + return Name; + } + + /// Returns a copy of the attachment. + public abstract Attachment Copy (); + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/AttachmentLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/AttachmentLoader.cs new file mode 100644 index 0000000..ee0497b --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/AttachmentLoader.cs @@ -0,0 +1,48 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +namespace Spine4_2_33 { + public interface AttachmentLoader { + /// May be null to not load any attachment. + RegionAttachment NewRegionAttachment (Skin skin, string name, string path, Sequence sequence); + + /// May be null to not load any attachment. + MeshAttachment NewMeshAttachment (Skin skin, string name, string path, Sequence sequence); + + /// May be null to not load any attachment. + BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, string name); + + /// May be null to not load any attachment + PathAttachment NewPathAttachment (Skin skin, string name); + + PointAttachment NewPointAttachment (Skin skin, string name); + + ClippingAttachment NewClippingAttachment (Skin skin, string name); + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/AttachmentType.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/AttachmentType.cs new file mode 100644 index 0000000..42258d1 --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/AttachmentType.cs @@ -0,0 +1,34 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +namespace Spine4_2_33 { + public enum AttachmentType { + Region, Boundingbox, Mesh, Linkedmesh, Path, Point, Clipping, Sequence + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/BoundingBoxAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/BoundingBoxAttachment.cs new file mode 100644 index 0000000..cd2e3b9 --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/BoundingBoxAttachment.cs @@ -0,0 +1,48 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +using System; + +namespace Spine4_2_33 { + /// Attachment that has a polygon for bounds checking. + public class BoundingBoxAttachment : VertexAttachment { + public BoundingBoxAttachment (string name) + : base(name) { + } + + /// Copy constructor. + protected BoundingBoxAttachment (BoundingBoxAttachment other) + : base(other) { + } + + public override Attachment Copy () { + return new BoundingBoxAttachment(this); + } + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/ClippingAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/ClippingAttachment.cs new file mode 100644 index 0000000..6e484f6 --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/ClippingAttachment.cs @@ -0,0 +1,51 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +using System; + +namespace Spine4_2_33 { + public class ClippingAttachment : VertexAttachment { + internal SlotData endSlot; + + public SlotData EndSlot { get { return endSlot; } set { endSlot = value; } } + + public ClippingAttachment (string name) : base(name) { + } + + /// Copy constructor. + protected ClippingAttachment (ClippingAttachment other) + : base(other) { + endSlot = other.endSlot; + } + + public override Attachment Copy () { + return new ClippingAttachment(this); + } + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/IHasTextureRegion.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/IHasTextureRegion.cs new file mode 100644 index 0000000..58fb95d --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/IHasTextureRegion.cs @@ -0,0 +1,56 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +using System; +using System.Text; + +namespace Spine4_2_33 { + public interface IHasTextureRegion { + /// The name used to find the + string Path { get; set; } + /// + /// Sets the region used to draw the attachment. After setting the region or if the region's properties are changed, + /// must be called. + /// + TextureRegion Region { get; set; } + + /// + /// Updates any values the attachment calculates using the . Must be called after setting the + /// or if the region's properties are changed. + /// + void UpdateRegion (); + + float R { get; set; } + float G { get; set; } + float B { get; set; } + float A { get; set; } + + Sequence Sequence { get; set; } + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/MeshAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/MeshAttachment.cs new file mode 100644 index 0000000..898d13f --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/MeshAttachment.cs @@ -0,0 +1,220 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +using System; + +namespace Spine4_2_33 { + /// Attachment that displays a texture region using a mesh. + public class MeshAttachment : VertexAttachment, IHasTextureRegion { + internal TextureRegion region; + internal string path; + internal float[] regionUVs, uvs; + internal int[] triangles; + internal float r = 1, g = 1, b = 1, a = 1; + internal int hullLength; + private MeshAttachment parentMesh; + private Sequence sequence; + + public TextureRegion Region { + get { return region; } + set { + if (value == null) throw new ArgumentNullException("region", "region cannot be null."); + region = value; + } + } + public int HullLength { get { return hullLength; } set { hullLength = value; } } + public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } } + /// The UV pair for each vertex, normalized within the entire texture. + /// + public float[] UVs { get { return uvs; } set { uvs = value; } } + public int[] Triangles { get { return triangles; } set { triangles = value; } } + + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } + + public string Path { get { return path; } set { path = value; } } + public Sequence Sequence { get { return sequence; } set { sequence = value; } } + + public MeshAttachment ParentMesh { + get { return parentMesh; } + set { + parentMesh = value; + if (value != null) { + bones = value.bones; + vertices = value.vertices; + worldVerticesLength = value.worldVerticesLength; + regionUVs = value.regionUVs; + triangles = value.triangles; + HullLength = value.HullLength; + Edges = value.Edges; + Width = value.Width; + Height = value.Height; + } + } + } + + // Nonessential. + public int[] Edges { get; set; } + public float Width { get; set; } + public float Height { get; set; } + + public MeshAttachment (string name) + : base(name) { + } + + /// Copy constructor. Use if the other mesh is a linked mesh. + protected MeshAttachment (MeshAttachment other) + : base(other) { + + if (parentMesh != null) throw new ArgumentException("Use newLinkedMesh to copy a linked mesh."); + + region = other.region; + path = other.path; + r = other.r; + g = other.g; + b = other.b; + a = other.a; + + regionUVs = new float[other.regionUVs.Length]; + Array.Copy(other.regionUVs, 0, regionUVs, 0, regionUVs.Length); + + uvs = new float[other.uvs.Length]; + Array.Copy(other.uvs, 0, uvs, 0, uvs.Length); + + triangles = new int[other.triangles.Length]; + Array.Copy(other.triangles, 0, triangles, 0, triangles.Length); + + hullLength = other.hullLength; + sequence = other.sequence == null ? null : new Sequence(other.sequence); + + // Nonessential. + if (other.Edges != null) { + Edges = new int[other.Edges.Length]; + Array.Copy(other.Edges, 0, Edges, 0, Edges.Length); + } + Width = other.Width; + Height = other.Height; + } + + + public void UpdateRegion () { + float[] regionUVs = this.regionUVs; + if (this.uvs == null || this.uvs.Length != regionUVs.Length) this.uvs = new float[regionUVs.Length]; + float[] uvs = this.uvs; + int n = uvs.Length; + float u, v, width, height; + + if (region is AtlasRegion) { + u = this.region.u; + v = this.region.v; + AtlasRegion region = (AtlasRegion)this.region; + // Note: difference from reference implementation. + // Covers rotation since region.width and height are already setup accordingly. + float textureWidth = this.region.width / (region.u2 - region.u); + float textureHeight = this.region.height / (region.v2 - region.v); + switch (region.degrees) { + case 90: + u -= (region.originalHeight - region.offsetY - region.packedWidth) / textureWidth; + v -= (region.originalWidth - region.offsetX - region.packedHeight) / textureHeight; + width = region.originalHeight / textureWidth; + height = region.originalWidth / textureHeight; + for (int i = 0; i < n; i += 2) { + uvs[i] = u + regionUVs[i + 1] * width; + uvs[i + 1] = v + (1 - regionUVs[i]) * height; + } + return; + case 180: + u -= (region.originalWidth - region.offsetX - region.packedWidth) / textureWidth; + v -= region.offsetY / textureHeight; + width = region.originalWidth / textureWidth; + height = region.originalHeight / textureHeight; + for (int 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: + u -= region.offsetY / textureWidth; + v -= region.offsetX / textureHeight; + width = region.originalHeight / textureWidth; + height = region.originalWidth / textureHeight; + for (int i = 0; i < n; i += 2) { + uvs[i] = u + (1 - regionUVs[i + 1]) * width; + uvs[i + 1] = v + regionUVs[i] * height; + } + return; + } + u -= region.offsetX / textureWidth; + v -= (region.originalHeight - region.offsetY - region.packedHeight) / textureHeight; + width = region.originalWidth / textureWidth; + height = region.originalHeight / textureHeight; + } else if (region == null) { + u = v = 0; + width = height = 1; + } else { + u = region.u; + v = region.v; + width = region.u2 - u; + height = region.v2 - v; + } + for (int i = 0; i < n; i += 2) { + uvs[i] = u + regionUVs[i] * width; + uvs[i + 1] = v + regionUVs[i + 1] * height; + } + } + + /// If the attachment has a , the region may be changed. + override public void ComputeWorldVertices (Slot slot, int start, int count, float[] worldVertices, int offset, int stride = 2) { + if (sequence != null) sequence.Apply(slot, this); + base.ComputeWorldVertices(slot, start, count, worldVertices, offset, stride); + } + + /// Returns a new mesh with this mesh set as the . + public MeshAttachment NewLinkedMesh () { + MeshAttachment mesh = new MeshAttachment(Name); + + mesh.timelineAttachment = timelineAttachment; + mesh.region = region; + mesh.path = path; + mesh.r = r; + mesh.g = g; + mesh.b = b; + mesh.a = a; + mesh.ParentMesh = parentMesh != null ? parentMesh : this; + if (mesh.Region != null) mesh.UpdateRegion(); + return mesh; + } + + public override Attachment Copy () { + return parentMesh != null ? NewLinkedMesh() : new MeshAttachment(this); + } + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/PathAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/PathAttachment.cs new file mode 100644 index 0000000..f2871b1 --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/PathAttachment.cs @@ -0,0 +1,65 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +using System; +using System.Collections.Generic; + +namespace Spine4_2_33 { + public class PathAttachment : VertexAttachment { + internal float[] lengths; + internal bool closed, constantSpeed; + + /// The length in the setup pose from the start of the path to the end of each curve. + public float[] Lengths { get { return lengths; } set { lengths = value; } } + /// If true, the start and end knots are connected. + public bool Closed { get { return closed; } set { closed = value; } } + /// If true, additional calculations are performed to make computing positions along the path more accurate and movement along + /// the path have a constant speed. + public bool ConstantSpeed { get { return constantSpeed; } set { constantSpeed = value; } } + + public PathAttachment (String name) + : base(name) { + } + + /// Copy constructor. + protected PathAttachment (PathAttachment other) + : base(other) { + + lengths = new float[other.lengths.Length]; + Array.Copy(other.lengths, 0, lengths, 0, lengths.Length); + + closed = other.closed; + constantSpeed = other.constantSpeed; + } + + public override Attachment Copy () { + return new PathAttachment(this); + } + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/PointAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/PointAttachment.cs new file mode 100644 index 0000000..db12c6f --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/PointAttachment.cs @@ -0,0 +1,73 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +using System; + +namespace Spine4_2_33 { + /// + /// 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 Point Attachments in the Spine User Guide. + ///

+ public class PointAttachment : Attachment { + internal float x, y, rotation; + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public float Rotation { get { return rotation; } set { rotation = value; } } + + public PointAttachment (string name) + : base(name) { + } + + /// Copy constructor. + protected PointAttachment (PointAttachment other) + : base(other) { + x = other.x; + y = other.y; + rotation = other.rotation; + } + + public void ComputeWorldPosition (Bone bone, out float ox, out float oy) { + bone.LocalToWorld(this.x, this.y, out ox, out oy); + } + + public float ComputeWorldRotation (Bone bone) { + float r = rotation * MathUtils.DegRad, cos = (float)Math.Cos(r), sin = (float)Math.Sin(r); + float x = cos * bone.a + sin * bone.b; + float y = cos * bone.c + sin * bone.d; + return MathUtils.Atan2Deg(y, x); + } + + public override Attachment Copy () { + return new PointAttachment(this); + } + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/RegionAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/RegionAttachment.cs new file mode 100644 index 0000000..e74f65e --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/RegionAttachment.cs @@ -0,0 +1,220 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +using System; + +namespace Spine4_2_33 { + /// Attachment that displays a texture region. + public class RegionAttachment : Attachment, IHasTextureRegion { + public const int BLX = 0, BLY = 1; + public const int ULX = 2, ULY = 3; + public const int URX = 4, URY = 5; + public const int BRX = 6, BRY = 7; + + internal TextureRegion region; + internal float x, y, rotation, scaleX = 1, scaleY = 1, width, height; + internal float[] offset = new float[8], uvs = new float[8]; + internal float r = 1, g = 1, b = 1, a = 1; + internal Sequence sequence; + + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public float Rotation { get { return rotation; } set { rotation = value; } } + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + public float Width { get { return width; } set { width = value; } } + public float Height { get { return height; } set { height = value; } } + + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } + + public string Path { get; set; } + public TextureRegion Region { get { return region; } set { region = value; } } + + /// For each of the 4 vertices, a pair of x,y values that is the local position of the vertex. + /// + public float[] Offset { get { return offset; } } + public float[] UVs { get { return uvs; } } + public Sequence Sequence { get { return sequence; } set { sequence = value; } } + + public RegionAttachment (string name) + : base(name) { + } + + /// Copy constructor. + public RegionAttachment (RegionAttachment other) + : base(other) { + region = other.region; + Path = other.Path; + x = other.x; + y = other.y; + scaleX = other.scaleX; + scaleY = other.scaleY; + rotation = other.rotation; + width = other.width; + height = other.height; + Array.Copy(other.uvs, 0, uvs, 0, 8); + Array.Copy(other.offset, 0, offset, 0, 8); + r = other.r; + g = other.g; + b = other.b; + a = other.a; + sequence = other.sequence == null ? null : new Sequence(other.sequence); + } + + /// Calculates the and using the region and the attachment's transform. Must be called if the + /// region, the region's properties, or the transform are changed. + public void UpdateRegion () { + float[] uvs = this.uvs; + 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 width = Width, height = Height; + float localX2 = width / 2; + float localY2 = height / 2; + float localX = -localX2; + float localY = -localY2; + bool rotated = false; + if (region is AtlasRegion) { + AtlasRegion region = (AtlasRegion)this.region; + localX += region.offsetX / region.originalWidth * width; + localY += region.offsetY / region.originalHeight * height; + if (region.degrees == 90) { + rotated = true; + localX2 -= (region.originalWidth - region.offsetX - region.packedHeight) / region.originalWidth * width; + localY2 -= (region.originalHeight - region.offsetY - region.packedWidth) / region.originalHeight * height; + } else { + localX2 -= (region.originalWidth - region.offsetX - region.packedWidth) / region.originalWidth * width; + localY2 -= (region.originalHeight - region.offsetY - region.packedHeight) / region.originalHeight * height; + } + } + float scaleX = ScaleX, scaleY = ScaleY; + localX *= scaleX; + localY *= scaleY; + localX2 *= scaleX; + localY2 *= scaleY; + float r = Rotation * MathUtils.DegRad, cos = (float)Math.Cos(r), sin = (float)Math.Sin(r); + float x = X, y = Y; + 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; + float[] offset = this.offset; + offset[BLX] = localXCos - localYSin; + offset[BLY] = localYCos + localXSin; + offset[ULX] = localXCos - localY2Sin; + offset[ULY] = localY2Cos + localXSin; + offset[URX] = localX2Cos - localY2Sin; + offset[URY] = localY2Cos + localX2Sin; + offset[BRX] = localX2Cos - localYSin; + offset[BRY] = localYCos + localX2Sin; + + if (rotated) { + uvs[BLX] = region.u2; + uvs[BLY] = region.v; + uvs[ULX] = region.u2; + uvs[ULY] = region.v2; + uvs[URX] = region.u; + uvs[URY] = region.v2; + uvs[BRX] = region.u; + uvs[BRY] = region.v; + } else { + uvs[BLX] = region.u2; + uvs[BLY] = region.v2; + uvs[ULX] = region.u; + uvs[ULY] = region.v2; + uvs[URX] = region.u; + uvs[URY] = region.v; + uvs[BRX] = region.u2; + uvs[BRY] = region.v; + } + } + + /// + /// Transforms the attachment's four vertices to world coordinates. If the attachment has a the region may + /// be changed. + /// The parent bone. + /// The output world vertices. Must have a length greater than or equal to offset + 8. + /// The worldVertices index to begin writing values. + /// The number of worldVertices entries between the value pairs written. + public void ComputeWorldVertices (Slot slot, float[] worldVertices, int offset, int stride = 2) { + if (sequence != null) sequence.Apply(slot, this); + + float[] vertexOffset = this.offset; + Bone bone = slot.Bone; + float bwx = bone.worldX, bwy = bone.worldY; + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + float offsetX, offsetY; + + // Vertex order is different from RegionAttachment.java + offsetX = vertexOffset[BRX]; // 0 + offsetY = vertexOffset[BRY]; // 1 + worldVertices[offset] = offsetX * a + offsetY * b + bwx; // bl + worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; + offset += stride; + + offsetX = vertexOffset[BLX]; // 2 + offsetY = vertexOffset[BLY]; // 3 + worldVertices[offset] = offsetX * a + offsetY * b + bwx; // ul + worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; + offset += stride; + + offsetX = vertexOffset[ULX]; // 4 + offsetY = vertexOffset[ULY]; // 5 + worldVertices[offset] = offsetX * a + offsetY * b + bwx; // ur + worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; + offset += stride; + + offsetX = vertexOffset[URX]; // 6 + offsetY = vertexOffset[URY]; // 7 + worldVertices[offset] = offsetX * a + offsetY * b + bwx; // br + worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; + //offset += stride; + } + + public override Attachment Copy () { + return new RegionAttachment(this); + } + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/Sequence.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/Sequence.cs new file mode 100644 index 0000000..917f49b --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/Sequence.cs @@ -0,0 +1,95 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +using System; +using System.Text; + +namespace Spine4_2_33 { + public class Sequence { + static int nextID = 0; + static readonly Object nextIdLock = new Object(); + + internal readonly int id; + internal readonly TextureRegion[] regions; + internal int start, digits, setupIndex; + + public int Start { get { return start; } set { start = value; } } + public int Digits { get { return digits; } set { digits = value; } } + /// The index of the region to show for the setup pose. + public int SetupIndex { get { return setupIndex; } set { setupIndex = value; } } + public TextureRegion[] Regions { get { return regions; } } + /// Returns a unique ID for this attachment. + public int Id { get { return id; } } + + public Sequence (int count) { + lock (Sequence.nextIdLock) { + id = Sequence.nextID++; + } + regions = new TextureRegion[count]; + } + + /// Copy constructor. + public Sequence (Sequence other) { + lock (Sequence.nextIdLock) { + id = Sequence.nextID++; + } + regions = new TextureRegion[other.regions.Length]; + Array.Copy(other.regions, 0, regions, 0, regions.Length); + + start = other.start; + digits = other.digits; + setupIndex = other.setupIndex; + } + + public void Apply (Slot slot, IHasTextureRegion attachment) { + int index = slot.SequenceIndex; + if (index == -1) index = setupIndex; + if (index >= regions.Length) index = regions.Length - 1; + TextureRegion region = regions[index]; + if (attachment.Region != region) { + attachment.Region = region; + attachment.UpdateRegion(); + } + } + + public string GetPath (string basePath, int index) { + StringBuilder buffer = new StringBuilder(basePath.Length + digits); + buffer.Append(basePath); + string frame = (start + index).ToString(); + for (int i = digits - frame.Length; i > 0; i--) + buffer.Append('0'); + buffer.Append(frame); + return buffer.ToString(); + } + } + + public enum SequenceMode { + Hold, Once, Loop, Pingpong, OnceReverse, LoopReverse, PingpongReverse + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/VertexAttachment.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/VertexAttachment.cs new file mode 100644 index 0000000..78e1246 --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Attachments/VertexAttachment.cs @@ -0,0 +1,158 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +using System; + +namespace Spine4_2_33 { + /// >An attachment with vertices that are transformed by one or more bones and can be deformed by a slot's + /// . + public abstract class VertexAttachment : Attachment { + static int nextID = 0; + static readonly Object nextIdLock = new Object(); + + internal readonly int id; + internal VertexAttachment timelineAttachment; + internal int[] bones; + internal float[] vertices; + internal int worldVerticesLength; + + /// Gets a unique ID for this attachment. + public int Id { get { return id; } } + public int[] Bones { get { return bones; } set { bones = value; } } + public float[] Vertices { get { return vertices; } set { vertices = value; } } + public int WorldVerticesLength { get { return worldVerticesLength; } set { worldVerticesLength = value; } } + /// Timelines for the timeline attachment are also applied to this attachment. + /// May be null if no attachment-specific timelines should be applied. + public VertexAttachment TimelineAttachment { get { return timelineAttachment; } set { timelineAttachment = value; } } + + public VertexAttachment (string name) + : base(name) { + + lock (VertexAttachment.nextIdLock) { + id = VertexAttachment.nextID++; + } + timelineAttachment = this; + } + + /// Copy constructor. + public VertexAttachment (VertexAttachment other) + : base(other) { + + lock (VertexAttachment.nextIdLock) { + id = VertexAttachment.nextID++; + } + timelineAttachment = other.timelineAttachment; + if (other.bones != null) { + bones = new int[other.bones.Length]; + Array.Copy(other.bones, 0, bones, 0, bones.Length); + } else + bones = null; + + if (other.vertices != null) { + vertices = new float[other.vertices.Length]; + Array.Copy(other.vertices, 0, vertices, 0, vertices.Length); + } else + vertices = null; + + worldVerticesLength = other.worldVerticesLength; + } + + public void ComputeWorldVertices (Slot slot, float[] worldVertices) { + ComputeWorldVertices(slot, 0, worldVerticesLength, worldVertices, 0); + } + + /// + /// Transforms the attachment's local to world coordinates. If the slot's is + /// not empty, it is used to deform the vertices. + /// + /// See World transforms in the Spine + /// Runtimes Guide. + /// + /// The index of the first value to transform. Each vertex has 2 values, x and y. + /// The number of world vertex values to output. Must be less than or equal to - start. + /// The output world vertices. Must have a length greater than or equal to + . + /// The index to begin writing values. + /// The number of entries between the value pairs written. + public virtual void ComputeWorldVertices (Slot slot, int start, int count, float[] worldVertices, int offset, int stride = 2) { + count = offset + (count >> 1) * stride; + ExposedList deformArray = slot.deform; + float[] vertices = this.vertices; + int[] bones = this.bones; + if (bones == null) { + if (deformArray.Count > 0) vertices = deformArray.Items; + Bone bone = slot.bone; + float x = bone.worldX, y = bone.worldY; + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + for (int vv = start, w = offset; w < count; vv += 2, w += stride) { + float vx = vertices[vv], 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 (int i = 0; i < start; i += 2) { + int n = bones[v]; + v += n + 1; + skip += n; + } + Bone[] skeletonBones = slot.bone.skeleton.bones.Items; + if (deformArray.Count == 0) { + for (int w = offset, b = skip * 3; w < count; w += stride) { + float wx = 0, wy = 0; + int n = bones[v++]; + n += v; + for (; v < n; v++, b += 3) { + Bone bone = skeletonBones[bones[v]]; + float vx = vertices[b], vy = vertices[b + 1], 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 { + float[] deform = deformArray.Items; + for (int w = offset, b = skip * 3, f = skip << 1; w < count; w += stride) { + float wx = 0, wy = 0; + int n = bones[v++]; + n += v; + for (; v < n; v++, b += 3, f += 2) { + Bone bone = skeletonBones[bones[v]]; + float vx = vertices[b] + deform[f], vy = vertices[b + 1] + deform[f + 1], 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; + } + } + } + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/BlendMode.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/BlendMode.cs new file mode 100644 index 0000000..c2e233f --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/BlendMode.cs @@ -0,0 +1,34 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +namespace Spine4_2_33 { + public enum BlendMode { + Normal, Additive, Multiply, Screen + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Bone.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Bone.cs new file mode 100644 index 0000000..b6060fe --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Bone.cs @@ -0,0 +1,458 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +using System; + +namespace Spine4_2_33 { + using Physics = Skeleton.Physics; + + /// + /// 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. + /// + /// + public class Bone : IUpdatable { + static public bool yDown; + + internal BoneData data; + internal Skeleton skeleton; + internal Bone parent; + internal ExposedList children = new ExposedList(); + internal float x, y, rotation, scaleX, scaleY, shearX, shearY; + internal float ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY; + + internal float a, b, worldX; + internal float c, d, worldY; + internal Inherit inherit; + + internal bool sorted, active; + + public BoneData Data { get { return data; } } + public Skeleton Skeleton { get { return skeleton; } } + public Bone Parent { get { return parent; } } + public ExposedList Children { get { return children; } } + public bool Active { get { return active; } } + /// The local X translation. + public float X { get { return x; } set { x = value; } } + /// The local Y translation. + public float Y { get { return y; } set { y = value; } } + /// The local rotation. + public float Rotation { get { return rotation; } set { rotation = value; } } + + /// The local scaleX. + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + + /// The local scaleY. + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + + /// The local shearX. + public float ShearX { get { return shearX; } set { shearX = value; } } + + /// The local shearY. + public float ShearY { get { return shearY; } set { shearY = value; } } + + /// Controls how parent world transforms affect this bone. + public Inherit Inherit { get { return inherit; } set { inherit = value; } } + + /// The rotation, as calculated by any constraints. + public float AppliedRotation { get { return arotation; } set { arotation = value; } } + + /// The applied local x translation. + public float AX { get { return ax; } set { ax = value; } } + + /// The applied local y translation. + public float AY { get { return ay; } set { ay = value; } } + + /// The applied local scaleX. + public float AScaleX { get { return ascaleX; } set { ascaleX = value; } } + + /// The applied local scaleY. + public float AScaleY { get { return ascaleY; } set { ascaleY = value; } } + + /// The applied local shearX. + public float AShearX { get { return ashearX; } set { ashearX = value; } } + + /// The applied local shearY. + public float AShearY { get { return ashearY; } set { ashearY = value; } } + + /// Part of the world transform matrix for the X axis. If changed, should be called. + public float A { get { return a; } set { a = value; } } + /// Part of the world transform matrix for the Y axis. If changed, should be called. + public float B { get { return b; } set { b = value; } } + /// Part of the world transform matrix for the X axis. If changed, should be called. + public float C { get { return c; } set { c = value; } } + /// Part of the world transform matrix for the Y axis. If changed, should be called. + public float D { get { return d; } set { d = value; } } + + /// The world X position. If changed, should be called. + public float WorldX { get { return worldX; } set { worldX = value; } } + /// The world Y position. If changed, should be called. + public float WorldY { get { return worldY; } set { worldY = value; } } + /// The world rotation for the X axis, calculated using and . + public float WorldRotationX { get { return MathUtils.Atan2Deg(c, a); } } + /// The world rotation for the Y axis, calculated using and . + public float WorldRotationY { get { return MathUtils.Atan2Deg(d, b); } } + + /// Returns the magnitide (always positive) of the world scale X. + public float WorldScaleX { get { return (float)Math.Sqrt(a * a + c * c); } } + /// Returns the magnitide (always positive) of the world scale Y. + public float WorldScaleY { get { return (float)Math.Sqrt(b * b + d * d); } } + + public Bone (BoneData data, Skeleton skeleton, Bone parent) { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + this.skeleton = skeleton; + this.parent = parent; + SetToSetupPose(); + } + + /// Copy constructor. Does not copy the bones. + /// May be null. + public Bone (Bone bone, Skeleton skeleton, Bone parent) { + if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.skeleton = skeleton; + this.parent = parent; + data = bone.data; + x = bone.x; + y = bone.y; + rotation = bone.rotation; + scaleX = bone.scaleX; + scaleY = bone.scaleY; + shearX = bone.shearX; + shearY = bone.shearY; + inherit = bone.inherit; + } + + /// Computes the world transform using the parent bone and this bone's local applied transform. + public void Update (Physics physics) { + UpdateWorldTransform(ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY); + } + + /// Computes the world transform using the parent bone and this bone's local transform. + public void UpdateWorldTransform () { + UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY); + } + + /// Computes the world transform using the parent bone and the specified local transform. The applied transform is set to the + /// specified local transform. Child bones are not updated. + /// + /// See World transforms in the Spine + /// Runtimes Guide. + public void UpdateWorldTransform (float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY) { + ax = x; + ay = y; + arotation = rotation; + ascaleX = scaleX; + ascaleY = scaleY; + ashearX = shearX; + ashearY = shearY; + + Bone parent = this.parent; + if (parent == null) { // Root bone. + Skeleton skeleton = this.skeleton; + float sx = skeleton.scaleX, sy = skeleton.ScaleY; + float rx = (rotation + shearX) * MathUtils.DegRad; + float ry = (rotation + 90 + shearY) * MathUtils.DegRad; + a = (float)Math.Cos(rx) * scaleX * sx; + b = (float)Math.Cos(ry) * scaleY * sx; + c = (float)Math.Sin(rx) * scaleX * sy; + d = (float)Math.Sin(ry) * scaleY * sy; + worldX = x * sx + skeleton.x; + worldY = y * sy + skeleton.y; + return; + } + + float 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) * MathUtils.DegRad; + float ry = (rotation + 90 + shearY) * MathUtils.DegRad; + float la = (float)Math.Cos(rx) * scaleX; + float lb = (float)Math.Cos(ry) * scaleY; + float lc = (float)Math.Sin(rx) * scaleX; + float ld = (float)Math.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) * MathUtils.DegRad; + float ry = (rotation + 90 + shearY) * MathUtils.DegRad; + a = (float)Math.Cos(rx) * scaleX; + b = (float)Math.Cos(ry) * scaleY; + c = (float)Math.Sin(rx) * scaleX; + d = (float)Math.Sin(ry) * scaleY; + break; + } + case Inherit.NoRotationOrReflection: { + float sx = 1 / skeleton.scaleX, sy = 1 / skeleton.ScaleY; + pa *= sx; + pc *= sy; + float s = pa * pa + pc * pc, prx; + if (s > 0.0001f) { + s = Math.Abs(pa * pd * sy - pb * sx * pc) / s; + pb = pc * s; + pd = pa * s; + prx = MathUtils.Atan2Deg(pc, pa); + } else { + pa = 0; + pc = 0; + prx = 90 - MathUtils.Atan2Deg(pd, pb); + } + float rx = (rotation + shearX - prx) * MathUtils.DegRad; + float ry = (rotation + shearY - prx + 90) * MathUtils.DegRad; + float la = (float)Math.Cos(rx) * scaleX; + float lb = (float)Math.Cos(ry) * scaleY; + float lc = (float)Math.Sin(rx) * scaleX; + float ld = (float)Math.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 *= MathUtils.DegRad; + float cos = (float)Math.Cos(rotation), sin = (float)Math.Sin(rotation); + float za = (pa * cos + pb * sin) / skeleton.scaleX; + float zc = (pc * cos + pd * sin) / skeleton.ScaleY; + float s = (float)Math.Sqrt(za * za + zc * zc); + if (s > 0.00001f) s = 1 / s; + za *= s; + zc *= s; + s = (float)Math.Sqrt(za * za + zc * zc); + if (inherit == Inherit.NoScale && (pa * pd - pb * pc < 0) != (skeleton.scaleX < 0 != skeleton.ScaleY < 0)) s = -s; + rotation = MathUtils.PI / 2 + MathUtils.Atan2(zc, za); + float zb = (float)Math.Cos(rotation) * s; + float zd = (float)Math.Sin(rotation) * s; + shearX *= MathUtils.DegRad; + shearY = (90 + shearY) * MathUtils.DegRad; + float la = (float)Math.Cos(shearX) * scaleX; + float lb = (float)Math.Cos(shearY) * scaleY; + float lc = (float)Math.Sin(shearX) * scaleX; + float ld = (float)Math.Sin(shearY) * scaleY; + a = za * la + zb * lc; + b = za * lb + zb * ld; + c = zc * la + zd * lc; + d = zc * lb + zd * ld; + break; + } + } + a *= skeleton.scaleX; + b *= skeleton.scaleX; + c *= skeleton.ScaleY; + d *= skeleton.ScaleY; + } + + /// Sets this bone's local transform to the setup pose. + public void SetToSetupPose () { + BoneData data = this.data; + x = data.x; + y = data.y; + rotation = data.rotation; + scaleX = data.scaleX; + scaleY = data.ScaleY; + shearX = data.shearX; + shearY = data.shearY; + inherit = data.inherit; + } + + /// + /// Computes the applied transform values from the world transform. + /// + /// If the world transform is modified (by a constraint, , etc) then this method should be called so + /// the applied transform matches the world transform. The applied transform may be needed by other code (eg to apply another + /// constraint). + /// + /// Some information is ambiguous in the world transform, such as -1,-1 scale versus 180 rotation. The applied transform after + /// calling this method is equivalent to the local transform used to compute the world transform, but may not be identical. + /// + public void UpdateAppliedTransform () { + Bone parent = this.parent; + if (parent == null) { + ax = worldX - skeleton.x; + ay = worldY - skeleton.y; + float a = this.a, b = this.b, c = this.c, d = this.d; + arotation = MathUtils.Atan2Deg(c, a); + ascaleX = (float)Math.Sqrt(a * a + c * c); + ascaleY = (float)Math.Sqrt(b * b + d * d); + ashearX = 0; + ashearY = MathUtils.Atan2Deg(a * b + c * d, a * d - b * c); + return; + } + + 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 = Math.Abs(pa * pd - pb * pc) / (pa * pa + pc * pc); + float skeletonScaleY = skeleton.ScaleY; + pb = -pc * skeleton.scaleX * s / skeletonScaleY; + pd = pa * skeletonScaleY * s / skeleton.scaleX; + pid = 1 / (pa * pd - pb * pc); + ia = pd * pid; + ib = pb * pid; + break; + } + case Inherit.NoScale: + case Inherit.NoScaleOrReflection: { + float r = rotation * MathUtils.DegRad, cos = (float)Math.Cos(r), sin = (float)Math.Sin(r); + pa = (pa * cos + pb * sin) / skeleton.scaleX; + pc = (pc * cos + pd * sin) / skeleton.ScaleY; + float s = (float)Math.Sqrt(pa * pa + pc * pc); + if (s > 0.00001f) s = 1 / s; + pa *= s; + pc *= s; + s = (float)Math.Sqrt(pa * pa + pc * pc); + if (inherit == Inherit.NoScale && pid < 0 != (skeleton.scaleX < 0 != skeleton.ScaleY < 0)) s = -s; + r = MathUtils.PI / 2 + MathUtils.Atan2(pc, pa); + pb = (float)Math.Cos(r) * s; + pd = (float)Math.Sin(r) * s; + pid = 1 / (pa * pd - pb * pc); + ia = pd * pid; + ib = pb * pid; + ic = pc * pid; + id = pa * pid; + break; + } + } + ra = ia * a - ib * c; + rb = ia * b - ib * d; + rc = id * c - ic * a; + rd = id * d - ic * b; + } + + ashearX = 0; + ascaleX = (float)Math.Sqrt(ra * ra + rc * rc); + if (ascaleX > 0.0001f) { + float det = ra * rd - rb * rc; + ascaleY = det / ascaleX; + ashearY = -MathUtils.Atan2Deg(ra * rb + rc * rd, det); + arotation = MathUtils.Atan2Deg(rc, ra); + } else { + ascaleX = 0; + ascaleY = (float)Math.Sqrt(rb * rb + rd * rd); + ashearY = 0; + arotation = 90 - MathUtils.Atan2Deg(rd, rb); + } + } + + /// Transforms a point from world coordinates to the bone's local coordinates. + public void WorldToLocal (float worldX, float worldY, out float localX, out float localY) { + float a = this.a, b = this.b, c = this.c, d = this.d; + float det = a * d - b * c; + float x = worldX - this.worldX, y = worldY - this.worldY; + localX = (x * d - y * b) / det; + localY = (y * a - x * c) / det; + } + + /// Transforms a point from the bone's local coordinates to world coordinates. + public void LocalToWorld (float localX, float localY, out float worldX, out float worldY) { + worldX = localX * a + localY * b + this.worldX; + worldY = localX * c + localY * d + this.worldY; + } + + /// Transforms a point from world coordinates to the parent bone's local coordinates. + public void WorldToParent (float worldX, float worldY, out float parentX, out float parentY) { + if (parent == null) { + parentX = worldX; + parentY = worldY; + } else { + parent.WorldToLocal(worldX, worldY, out parentX, out parentY); + } + } + + /// Transforms a point from the parent bone's coordinates to world coordinates. + public void ParentToWorld (float parentX, float parentY, out float worldX, out float worldY) { + if (parent == null) { + worldX = parentX; + worldY = parentY; + } else { + parent.LocalToWorld(parentX, parentY, out worldX, out worldY); + } + } + + /// Transforms a world rotation to a local rotation. + public float WorldToLocalRotation (float worldRotation) { + worldRotation *= MathUtils.DegRad; + float sin = (float)Math.Sin(worldRotation), cos = (float)Math.Cos(worldRotation); + return MathUtils.Atan2Deg(a * sin - c * cos, d * cos - b * sin) + rotation - shearX; + } + + /// Transforms a local rotation to a world rotation. + public float LocalToWorldRotation (float localRotation) { + localRotation = (localRotation - rotation - shearX) * MathUtils.DegRad; + float sin = (float)Math.Sin(localRotation), cos = (float)Math.Cos(localRotation); + return MathUtils.Atan2Deg(cos * c + sin * d, cos * a + sin * b); + } + + /// + /// Rotates the world transform the specified amount. + /// + /// After changes are made to the world transform, should be called and + /// will need to be called on any child bones, recursively. + /// + public void RotateWorld (float degrees) { + degrees *= MathUtils.DegRad; + float sin = (float)Math.Sin(degrees), cos = (float)Math.Cos(degrees); + float ra = a, rb = b; + a = cos * ra - sin * c; + b = cos * rb - sin * d; + c = sin * ra + cos * c; + d = sin * rb + cos * d; + } + + override public string ToString () { + return data.name; + } + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/BoneData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/BoneData.cs new file mode 100644 index 0000000..21dbeb8 --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/BoneData.cs @@ -0,0 +1,113 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +using System; + +namespace Spine4_2_33 { + public class BoneData { + internal int index; + internal string name; + internal BoneData parent; + internal float length; + internal float x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY; + internal Inherit inherit = Inherit.Normal; + internal bool skinRequired; + + /// The index of the bone in Skeleton.Bones + public int Index { get { return index; } } + + /// The name of the bone, which is unique across all bones in the skeleton. + public string Name { get { return name; } } + + /// May be null. + public BoneData Parent { get { return parent; } } + + public float Length { get { return length; } set { length = value; } } + + /// Local X translation. + public float X { get { return x; } set { x = value; } } + + /// Local Y translation. + public float Y { get { return y; } set { y = value; } } + + /// Local rotation in degrees, counter clockwise. + public float Rotation { get { return rotation; } set { rotation = value; } } + + /// Local scaleX. + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + + /// Local scaleY. + public float ScaleY { get { return scaleY; } set { scaleY = value; } } + + /// Local shearX. + public float ShearX { get { return shearX; } set { shearX = value; } } + + /// Local shearY. + public float ShearY { get { return shearY; } set { shearY = value; } } + + /// Determines how parent world transforms affect this bone. + public Inherit Inherit { get { return inherit; } set { inherit = value; } } + + /// When true, only updates this bone if the contains + /// this bone. + /// + public bool SkinRequired { get { return skinRequired; } set { skinRequired = value; } } + + /// May be null. + public BoneData (int index, string name, BoneData parent) { + if (index < 0) throw new ArgumentException("index must be >= 0", "index"); + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.index = index; + this.name = name; + this.parent = parent; + } + + override public string ToString () { + return name; + } + } + + public enum Inherit { + Normal, + OnlyTranslation, + NoRotationOrReflection, + NoScale, + NoScaleOrReflection + } + + public class InheritEnum { + public static readonly Inherit[] Values = { + Inherit.Normal, + Inherit.OnlyTranslation, + Inherit.NoRotationOrReflection, + Inherit.NoScale, + Inherit.NoScaleOrReflection + }; + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/ConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/ConstraintData.cs new file mode 100644 index 0000000..4645223 --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/ConstraintData.cs @@ -0,0 +1,61 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +using System; +using System.Collections.Generic; + +namespace Spine4_2_33 { + /// The base class for all constraint datas. + public abstract class ConstraintData { + internal readonly string name; + internal int order; + internal bool skinRequired; + + public ConstraintData (string name) { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + } + + /// The constraint's name, which is unique across all constraints in the skeleton of the same type. + public string Name { get { return name; } } + + /// The ordinal of this constraint for the order a skeleton's constraints will be applied by + /// . + public int Order { get { return order; } set { order = value; } } + + /// When true, only updates this constraint if the + /// contains this constraint. + /// + public bool SkinRequired { get { return skinRequired; } set { skinRequired = value; } } + + override public string ToString () { + return name; + } + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Event.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Event.cs new file mode 100644 index 0000000..b3ef177 --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Event.cs @@ -0,0 +1,64 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +using System; + +namespace Spine4_2_33 { + /// Stores the current pose values for an Event. + public class Event { + internal readonly EventData data; + internal readonly float time; + internal int intValue; + internal float floatValue; + internal string stringValue; + internal float volume; + internal float balance; + + public EventData Data { get { return data; } } + /// The animation time this event was keyed. + public float Time { get { return time; } } + + public int Int { get { return intValue; } set { intValue = value; } } + public float Float { get { return floatValue; } set { floatValue = value; } } + public string String { get { return stringValue; } set { stringValue = value; } } + + public float Volume { get { return volume; } set { volume = value; } } + public float Balance { get { return balance; } set { balance = value; } } + + public Event (float time, EventData data) { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + this.time = time; + this.data = data; + } + + override public string ToString () { + return this.data.Name; + } + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/EventData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/EventData.cs new file mode 100644 index 0000000..307a426 --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/EventData.cs @@ -0,0 +1,56 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +using System; + +namespace Spine4_2_33 { + /// Stores the setup pose values for an Event. + public class EventData { + internal string name; + + /// The name of the event, which is unique across all events in the skeleton. + public string Name { get { return name; } } + public int Int { get; set; } + public float Float { get; set; } + public string @String { get; set; } + + public string AudioPath { get; set; } + public float Volume { get; set; } + public float Balance { get; set; } + + public EventData (string name) { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + } + + override public string ToString () { + return Name; + } + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/ExposedList.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/ExposedList.cs new file mode 100644 index 0000000..fc534f2 --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/ExposedList.cs @@ -0,0 +1,637 @@ +// +// System.Collections.Generic.List +// +// Authors: +// Ben Maurer (bmaurer@ximian.com) +// Martin Baulig (martin@ximian.com) +// Carlos Alberto Cortez (calberto.cortez@gmail.com) +// David Waite (mass@akuma.org) +// +// Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com) +// Copyright (C) 2005 David Waite +// +// Permission is hereby granted, free 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. +// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Spine4_2_33 { + [DebuggerDisplay("Count={Count}")] + public class ExposedList : IEnumerable { + public T[] Items; + public int Count; + private const int DefaultCapacity = 4; + private static readonly T[] EmptyArray = new T[0]; + private int version; + + public ExposedList () { + Items = EmptyArray; + } + + public ExposedList (IEnumerable collection) { + CheckCollection(collection); + + // initialize to needed size (if determinable) + ICollection c = collection as ICollection; + if (c == null) { + Items = EmptyArray; + AddEnumerable(collection); + } else { + Items = new T[c.Count]; + AddCollection(c); + } + } + + public ExposedList (int capacity) { + if (capacity < 0) + throw new ArgumentOutOfRangeException("capacity"); + Items = new T[capacity]; + } + + internal ExposedList (T[] data, int size) { + Items = data; + Count = size; + } + + public void Add (T item) { + // If we check to see if we need to grow before trying to grow + // we can speed things up by 25% + if (Count == Items.Length) + GrowIfNeeded(1); + Items[Count++] = item; + version++; + } + + public void GrowIfNeeded (int addedCount) { + int minimumSize = Count + addedCount; + if (minimumSize > Items.Length) + Capacity = Math.Max(Math.Max(Capacity * 2, DefaultCapacity), minimumSize); + } + + public ExposedList Resize (int newSize) { + int itemsLength = Items.Length; + T[] oldItems = Items; + if (newSize > itemsLength) { + Array.Resize(ref Items, newSize); + } else if (newSize < itemsLength) { + // Allow nulling of T reference type to allow GC. + for (int i = newSize; i < itemsLength; i++) + oldItems[i] = default(T); + } + Count = newSize; + return this; + } + + public void EnsureCapacity (int min) { + if (Items.Length < min) { + int newCapacity = Items.Length == 0 ? DefaultCapacity : Items.Length * 2; + //if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength; + if (newCapacity < min) newCapacity = min; + Capacity = newCapacity; + } + } + + private void CheckRange (int index, int count) { + if (index < 0) + throw new ArgumentOutOfRangeException("index"); + + if (count < 0) + throw new ArgumentOutOfRangeException("count"); + + if ((uint)index + (uint)count > (uint)Count) + throw new ArgumentException("index and count exceed length of list"); + } + + private void AddCollection (ICollection collection) { + int collectionCount = collection.Count; + if (collectionCount == 0) + return; + + GrowIfNeeded(collectionCount); + collection.CopyTo(Items, Count); + Count += collectionCount; + } + + private void AddEnumerable (IEnumerable enumerable) { + foreach (T t in enumerable) { + Add(t); + } + } + + // Additional overload provided because ExposedList only implements IEnumerable, + // leading to sub-optimal behavior: It grows multiple times as it assumes not + // to know the final size ahead of insertion. + public void AddRange (ExposedList list) { + CheckCollection(list); + + int collectionCount = list.Count; + if (collectionCount == 0) + return; + + GrowIfNeeded(collectionCount); + list.CopyTo(Items, Count); + Count += collectionCount; + + version++; + } + + public void AddRange (IEnumerable collection) { + CheckCollection(collection); + + ICollection c = collection as ICollection; + if (c != null) + AddCollection(c); + else + AddEnumerable(collection); + version++; + } + + public int BinarySearch (T item) { + return Array.BinarySearch(Items, 0, Count, item); + } + + public int BinarySearch (T item, IComparer comparer) { + return Array.BinarySearch(Items, 0, Count, item, comparer); + } + + public int BinarySearch (int index, int count, T item, IComparer comparer) { + CheckRange(index, count); + return Array.BinarySearch(Items, index, count, item, comparer); + } + + public void Clear (bool clearArray = true) { + if (clearArray) + Array.Clear(Items, 0, Items.Length); + + Count = 0; + version++; + } + + public bool Contains (T item) { + return Array.IndexOf(Items, item, 0, Count) != -1; + } + + public ExposedList ConvertAll (Converter converter) { + if (converter == null) + throw new ArgumentNullException("converter"); + ExposedList u = new ExposedList(Count); + u.Count = Count; + T[] items = Items; + TOutput[] uItems = u.Items; + for (int i = 0; i < Count; i++) + uItems[i] = converter(items[i]); + return u; + } + + public void CopyTo (T[] array) { + Array.Copy(Items, 0, array, 0, Count); + } + + public void CopyTo (T[] array, int arrayIndex) { + Array.Copy(Items, 0, array, arrayIndex, Count); + } + + public void CopyTo (int index, T[] array, int arrayIndex, int count) { + CheckRange(index, count); + Array.Copy(Items, index, array, arrayIndex, count); + } + + public bool Exists (Predicate match) { + CheckMatch(match); + return GetIndex(0, Count, match) != -1; + } + + public T Find (Predicate match) { + CheckMatch(match); + int i = GetIndex(0, Count, match); + return (i != -1) ? Items[i] : default(T); + } + + private static void CheckMatch (Predicate match) { + if (match == null) + throw new ArgumentNullException("match"); + } + + public ExposedList FindAll (Predicate match) { + CheckMatch(match); + return FindAllList(match); + } + + private ExposedList FindAllList (Predicate match) { + ExposedList results = new ExposedList(); + for (int i = 0; i < Count; i++) + if (match(Items[i])) + results.Add(Items[i]); + + return results; + } + + public int FindIndex (Predicate match) { + CheckMatch(match); + return GetIndex(0, Count, match); + } + + public int FindIndex (int startIndex, Predicate match) { + CheckMatch(match); + CheckIndex(startIndex); + return GetIndex(startIndex, Count - startIndex, match); + } + + public int FindIndex (int startIndex, int count, Predicate match) { + CheckMatch(match); + CheckRange(startIndex, count); + return GetIndex(startIndex, count, match); + } + + private int GetIndex (int startIndex, int count, Predicate match) { + int end = startIndex + count; + for (int i = startIndex; i < end; i++) + if (match(Items[i])) + return i; + + return -1; + } + + public T FindLast (Predicate match) { + CheckMatch(match); + int i = GetLastIndex(0, Count, match); + return i == -1 ? default(T) : Items[i]; + } + + public int FindLastIndex (Predicate match) { + CheckMatch(match); + return GetLastIndex(0, Count, match); + } + + public int FindLastIndex (int startIndex, Predicate match) { + CheckMatch(match); + CheckIndex(startIndex); + return GetLastIndex(0, startIndex + 1, match); + } + + public int FindLastIndex (int startIndex, int count, Predicate match) { + CheckMatch(match); + int start = startIndex - count + 1; + CheckRange(start, count); + return GetLastIndex(start, count, match); + } + + private int GetLastIndex (int startIndex, int count, Predicate match) { + // unlike FindLastIndex, takes regular params for search range + for (int i = startIndex + count; i != startIndex;) + if (match(Items[--i])) + return i; + return -1; + } + + public void ForEach (Action action) { + if (action == null) + throw new ArgumentNullException("action"); + for (int i = 0; i < Count; i++) + action(Items[i]); + } + + public Enumerator GetEnumerator () { + return new Enumerator(this); + } + + public ExposedList GetRange (int index, int count) { + CheckRange(index, count); + T[] tmpArray = new T[count]; + Array.Copy(Items, index, tmpArray, 0, count); + return new ExposedList(tmpArray, count); + } + + public int IndexOf (T item) { + return Array.IndexOf(Items, item, 0, Count); + } + + public int IndexOf (T item, int index) { + CheckIndex(index); + return Array.IndexOf(Items, item, index, Count - index); + } + + public int IndexOf (T item, int index, int count) { + if (index < 0) + throw new ArgumentOutOfRangeException("index"); + + if (count < 0) + throw new ArgumentOutOfRangeException("count"); + + if ((uint)index + (uint)count > (uint)Count) + throw new ArgumentOutOfRangeException("index and count exceed length of list"); + + return Array.IndexOf(Items, item, index, count); + } + + private void Shift (int start, int delta) { + if (delta < 0) + start -= delta; + + if (start < Count) + Array.Copy(Items, start, Items, start + delta, Count - start); + + Count += delta; + + if (delta < 0) + Array.Clear(Items, Count, -delta); + } + + private void CheckIndex (int index) { + if (index < 0 || (uint)index > (uint)Count) + throw new ArgumentOutOfRangeException("index"); + } + + public void Insert (int index, T item) { + CheckIndex(index); + if (Count == Items.Length) + GrowIfNeeded(1); + Shift(index, 1); + Items[index] = item; + version++; + } + + private void CheckCollection (IEnumerable collection) { + if (collection == null) + throw new ArgumentNullException("collection"); + } + + public void InsertRange (int index, IEnumerable collection) { + CheckCollection(collection); + CheckIndex(index); + if (collection == this) { + T[] buffer = new T[Count]; + CopyTo(buffer, 0); + GrowIfNeeded(Count); + Shift(index, buffer.Length); + Array.Copy(buffer, 0, Items, index, buffer.Length); + } else { + ICollection c = collection as ICollection; + if (c != null) + InsertCollection(index, c); + else + InsertEnumeration(index, collection); + } + version++; + } + + private void InsertCollection (int index, ICollection collection) { + int collectionCount = collection.Count; + GrowIfNeeded(collectionCount); + + Shift(index, collectionCount); + collection.CopyTo(Items, index); + } + + private void InsertEnumeration (int index, IEnumerable enumerable) { + foreach (T t in enumerable) + Insert(index++, t); + } + + public int LastIndexOf (T item) { + return Array.LastIndexOf(Items, item, Count - 1, Count); + } + + public int LastIndexOf (T item, int index) { + CheckIndex(index); + return Array.LastIndexOf(Items, item, index, index + 1); + } + + public int LastIndexOf (T item, int index, int count) { + if (index < 0) + throw new ArgumentOutOfRangeException("index", index, "index is negative"); + + if (count < 0) + throw new ArgumentOutOfRangeException("count", count, "count is negative"); + + if (index - count + 1 < 0) + throw new ArgumentOutOfRangeException("count", count, "count is too large"); + + return Array.LastIndexOf(Items, item, index, count); + } + + public bool Remove (T item) { + int loc = IndexOf(item); + if (loc != -1) + RemoveAt(loc); + + return loc != -1; + } + + public int RemoveAll (Predicate match) { + CheckMatch(match); + int i = 0; + int j = 0; + + // Find the first item to remove + for (i = 0; i < Count; i++) + if (match(Items[i])) + break; + + if (i == Count) + return 0; + + version++; + + // Remove any additional items + for (j = i + 1; j < Count; j++) { + if (!match(Items[j])) + Items[i++] = Items[j]; + } + if (j - i > 0) + Array.Clear(Items, i, j - i); + + Count = i; + return (j - i); + } + + public void RemoveAt (int index) { + if (index < 0 || (uint)index >= (uint)Count) + throw new ArgumentOutOfRangeException("index"); + Shift(index, -1); + Array.Clear(Items, Count, 1); + version++; + } + + // Spine Added Method + // Based on Stack.Pop(); https://referencesource.microsoft.com/#mscorlib/system/collections/stack.cs + /// Pops the last item of the list. If the list is empty, Pop throws an InvalidOperationException. + public T Pop () { + if (Count == 0) + throw new InvalidOperationException("List is empty. Nothing to pop."); + + int i = Count - 1; + T item = Items[i]; + Items[i] = default(T); + Count--; + version++; + return item; + } + + public void RemoveRange (int index, int count) { + CheckRange(index, count); + if (count > 0) { + Shift(index, -count); + Array.Clear(Items, Count, count); + version++; + } + } + + public void Reverse () { + Array.Reverse(Items, 0, Count); + version++; + } + + public void Reverse (int index, int count) { + CheckRange(index, count); + Array.Reverse(Items, index, count); + version++; + } + + public void Sort () { + Array.Sort(Items, 0, Count, Comparer.Default); + version++; + } + + public void Sort (IComparer comparer) { + Array.Sort(Items, 0, Count, comparer); + version++; + } + + public void Sort (Comparison comparison) { + Array.Sort(Items, comparison); + version++; + } + + public void Sort (int index, int count, IComparer comparer) { + CheckRange(index, count); + Array.Sort(Items, index, count, comparer); + version++; + } + + public T[] ToArray () { + T[] t = new T[Count]; + Array.Copy(Items, t, Count); + + return t; + } + + public void TrimExcess () { + Capacity = Count; + } + + public bool TrueForAll (Predicate match) { + CheckMatch(match); + + for (int i = 0; i < Count; i++) + if (!match(Items[i])) + return false; + + return true; + } + + public int Capacity { + get { + return Items.Length; + } + set { + if ((uint)value < (uint)Count) + throw new ArgumentOutOfRangeException(); + + Array.Resize(ref Items, value); + } + } + + #region Interface implementations. + + IEnumerator IEnumerable.GetEnumerator () { + return GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator () { + return GetEnumerator(); + } + + #endregion + + public struct Enumerator : IEnumerator, IDisposable { + private ExposedList l; + private int next; + private int ver; + private T current; + + internal Enumerator (ExposedList l) + : this() { + this.l = l; + ver = l.version; + } + + public void Dispose () { + l = null; + } + + private void VerifyState () { + if (l == null) + throw new ObjectDisposedException(GetType().FullName); + if (ver != l.version) + throw new InvalidOperationException( + "Collection was modified; enumeration operation may not execute."); + } + + public bool MoveNext () { + VerifyState(); + + if (next < 0) + return false; + + if (next < l.Count) { + current = l.Items[next++]; + return true; + } + + next = -1; + return false; + } + + public T Current { + get { + return current; + } + } + + void IEnumerator.Reset () { + VerifyState(); + next = 0; + } + + object IEnumerator.Current { + get { + VerifyState(); + if (next <= 0) + throw new InvalidOperationException(); + return current; + } + } + } + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/IUpdatable.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/IUpdatable.cs new file mode 100644 index 0000000..5580c62 --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/IUpdatable.cs @@ -0,0 +1,47 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +namespace Spine4_2_33 { + using Physics = Skeleton.Physics; + + /// The interface for items updated by . + public interface IUpdatable { + /// Determines how physics and other non-deterministic updates are applied. + void Update (Physics physics); + + /// Returns false when this item won't be updated by + /// because a skin is required and the + /// active skin does not contain this item. + /// + /// + /// + /// + bool Active { get; } + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/IkConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/IkConstraint.cs new file mode 100644 index 0000000..0a5624a --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/IkConstraint.cs @@ -0,0 +1,386 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +using System; + +namespace Spine4_2_33 { + using Physics = Skeleton.Physics; + + /// + /// + /// Stores the current pose for an IK constraint. An IK constraint adjusts the rotation of 1 or 2 constrained bones so the tip of + /// the last bone is as close to the target bone as possible. + /// + /// See IK constraints in the Spine User Guide. + /// + public class IkConstraint : IUpdatable { + internal readonly IkConstraintData data; + internal readonly ExposedList bones = new ExposedList(); + internal Bone target; + internal int bendDirection; + internal bool compress, stretch; + internal float mix = 1, softness; + + internal bool active; + + public IkConstraint (IkConstraintData data, Skeleton skeleton) { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + this.data = data; + + bones = new ExposedList(data.bones.Count); + foreach (BoneData boneData in data.bones) + bones.Add(skeleton.bones.Items[boneData.index]); + + target = skeleton.bones.Items[data.target.index]; + + mix = data.mix; + softness = data.softness; + bendDirection = data.bendDirection; + compress = data.compress; + stretch = data.stretch; + } + + /// Copy constructor. + public IkConstraint (IkConstraint constraint, Skeleton skeleton) + : this(constraint.data, skeleton) { + + mix = constraint.mix; + softness = constraint.softness; + bendDirection = constraint.bendDirection; + compress = constraint.compress; + stretch = constraint.stretch; + } + + public void SetToSetupPose () { + IkConstraintData data = this.data; + mix = data.mix; + softness = data.softness; + bendDirection = data.bendDirection; + compress = data.compress; + stretch = data.stretch; + } + + public void Update (Physics physics) { + if (mix == 0) return; + Bone target = this.target; + Bone[] bones = this.bones.Items; + switch (this.bones.Count) { + case 1: + Apply(bones[0], target.worldX, target.worldY, compress, stretch, data.uniform, mix); + break; + case 2: + Apply(bones[0], bones[1], target.worldX, target.worldY, bendDirection, stretch, data.uniform, softness, mix); + break; + } + } + + /// The bones that will be modified by this IK constraint. + public ExposedList Bones { + get { return bones; } + } + + /// The bone that is the IK target. + public Bone Target { + get { return target; } + set { target = value; } + } + + /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. + /// + /// For two bone IK: if the parent bone has local nonuniform scale, the child bone's local Y translation is set to 0. + /// + public float Mix { + get { return mix; } + set { mix = value; } + } + + /// For two bone IK, the target bone's distance from the maximum reach of the bones where rotation begins to slow. The bones + /// will not straighten completely until the target is this far out of range. + public float Softness { + get { return softness; } + set { softness = value; } + } + + /// For two bone IK, controls the bend direction of the IK bones, either 1 or -1. + public int BendDirection { + get { return bendDirection; } + set { bendDirection = value; } + } + + /// For one bone IK, when true and the target is too close, the bone is scaled to reach it. + public bool Compress { + get { return compress; } + set { compress = value; } + } + + /// When true and the target is out of range, the parent bone is scaled to reach it. + /// + /// For two bone IK: 1) the child bone's local Y translation is set to 0, + /// 2) stretch is not applied if is > 0, + /// and 3) if the parent bone has local nonuniform scale, stretch is not applied. + /// + public bool Stretch { + get { return stretch; } + set { stretch = value; } + } + + public bool Active { + get { return active; } + } + + /// The IK constraint's setup pose data. + public IkConstraintData Data { + get { return data; } + } + + override public string ToString () { + return data.name; + } + + /// Applies 1 bone IK. The target is specified in the world coordinate system. + static public void Apply (Bone bone, float targetX, float targetY, bool compress, bool stretch, bool uniform, + float alpha) { + if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); + Bone p = bone.parent; + + 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) * Math.Sign(bone.skeleton.ScaleX); + ty = (targetY - bone.worldY) * Math.Sign(bone.skeleton.ScaleY); + break; + case Inherit.NoRotationOrReflection: { + float s = Math.Abs(pa * pd - pb * pc) / Math.Max(0.0001f, pa * pa + pc * pc); + float sa = pa / bone.skeleton.scaleX; + float sc = pc / bone.skeleton.ScaleY; + pb = -sc * s * bone.skeleton.scaleX; + pd = sa * s * bone.skeleton.ScaleY; + rotationIK += MathUtils.Atan2Deg(sc, sa); + goto default; // Fall through. + } + default: { + float x = targetX - p.worldX, y = targetY - p.worldY; + float d = pa * pd - pb * pc; + if (Math.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; + } + break; + } + } + + rotationIK += MathUtils.Atan2Deg(ty, tx); + if (bone.ascaleX < 0) rotationIK += 180; + if (rotationIK > 180) + rotationIK -= 360; + else if (rotationIK < -180) // + rotationIK += 360; + + float sx = bone.ascaleX, sy = bone.ascaleY; + if (compress || stretch) { + switch (bone.inherit) { + case Inherit.NoScale: + case Inherit.NoScaleOrReflection: + tx = targetX - bone.worldX; + ty = targetY - bone.worldY; + break; + } + float b = bone.data.length * sx; + if (b > 0.0001f) { + float dd = tx * tx + ty * ty; + if ((compress && dd < b * b) || (stretch && dd > b * b)) { + float s = ((float)Math.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); + } + + /// Applies 2 bone IK. The target is specified in the world coordinate system. + /// A direct descendant of the parent bone. + static public void Apply (Bone parent, Bone child, float targetX, float targetY, int bendDir, bool stretch, bool uniform, + float softness, float alpha) { + if (parent == null) throw new ArgumentNullException("parent", "parent cannot be null."); + if (child == null) throw new ArgumentNullException("child", "child cannot be null."); + if (parent.inherit != Inherit.Normal || child.inherit != Inherit.Normal) return; + float px = parent.ax, py = parent.ay, psx = parent.ascaleX, psy = parent.ascaleY, sx = psx, sy = psy, csx = child.ascaleX; + int os1, os2, s2; + if (psx < 0) { + psx = -psx; + os1 = 180; + s2 = -1; + } else { + os1 = 0; + s2 = 1; + } + if (psy < 0) { + psy = -psy; + s2 = -s2; + } + if (csx < 0) { + csx = -csx; + os2 = 180; + } else + os2 = 0; + float cx = child.ax, cy, cwx, cwy, a = parent.a, b = parent.b, c = parent.c, d = parent.d; + bool u = Math.Abs(psx - psy) <= 0.0001f; + if (!u || stretch) { + cy = 0; + cwx = a * cx + parent.worldX; + cwy = c * cx + parent.worldY; + } else { + cy = child.ay; + cwx = a * cx + b * cy + parent.worldX; + cwy = c * cx + d * cy + parent.worldY; + } + Bone pp = parent.parent; + a = pp.a; + b = pp.b; + c = pp.c; + d = pp.d; + float id = a * d - b * c, x = cwx - pp.worldX, y = cwy - pp.worldY; + id = Math.Abs(id) <= 0.0001f ? 0 : 1 / id; + float dx = (x * d - y * b) * id - px, dy = (y * a - x * c) * id - py; + float l1 = (float)Math.Sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2; + if (l1 < 0.0001f) { + 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; + float tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py; + float dd = tx * tx + ty * ty; + if (softness != 0) { + softness *= psx * (csx + 1) * 0.5f; + float td = (float)Math.Sqrt(dd), sd = td - l1 - l2 * psx + softness; + if (sd > 0) { + float p = Math.Min(1, sd / (softness * 2)) - 1; + p = (sd - softness * (1 - p * p)) / td; + tx -= p * tx; + ty -= p * ty; + dd = tx * tx + ty * ty; + } + } + if (u) { + l2 *= psx; + float cos = (dd - l1 * l1 - l2 * l2) / (2 * l1 * l2); + if (cos < -1) { + cos = -1; + a2 = MathUtils.PI * bendDir; + } else if (cos > 1) { + cos = 1; + a2 = 0; + if (stretch) { + a = ((float)Math.Sqrt(dd) / (l1 + l2) - 1) * alpha + 1; + sx *= a; + if (uniform) sy *= a; + } + } else + a2 = (float)Math.Acos(cos) * bendDir; + a = l1 + l2 * cos; + b = l2 * (float)Math.Sin(a2); + a1 = (float)Math.Atan2(ty * a - tx * b, tx * a + ty * b); + } else { + a = psx * l2; + b = psy * l2; + float aa = a * a, bb = b * b, ta = (float)Math.Atan2(ty, tx); + c = bb * l1 * l1 + aa * dd - aa * bb; + float c1 = -2 * bb * l1, c2 = bb - aa; + d = c1 * c1 - 4 * c2 * c; + if (d >= 0) { + float q = (float)Math.Sqrt(d); + if (c1 < 0) q = -q; + q = -(c1 + q) * 0.5f; + float r0 = q / c2, r1 = c / q; + float r = Math.Abs(r0) < Math.Abs(r1) ? r0 : r1; + r0 = dd - r * r; + if (r0 >= 0) { + y = (float)Math.Sqrt(r0) * bendDir; + a1 = ta - (float)Math.Atan2(y, r); + a2 = (float)Math.Atan2(y / psy, (r - l1) / psx); + goto break_outer; // break outer; + } + } + float minAngle = MathUtils.PI, minX = l1 - a, minDist = minX * minX, minY = 0; + float maxAngle = 0, maxX = l1 + a, maxDist = maxX * maxX, maxY = 0; + c = -a * l1 / (aa - bb); + if (c >= -1 && c <= 1) { + c = (float)Math.Acos(c); + x = a * (float)Math.Cos(c) + l1; + y = b * (float)Math.Sin(c); + d = x * x + y * y; + if (d < minDist) { + minAngle = c; + minDist = d; + minX = x; + minY = y; + } + if (d > maxDist) { + maxAngle = c; + maxDist = d; + maxX = x; + maxY = y; + } + } + if (dd <= (minDist + maxDist) * 0.5f) { + a1 = ta - (float)Math.Atan2(minY * bendDir, minX); + a2 = minAngle * bendDir; + } else { + a1 = ta - (float)Math.Atan2(maxY * bendDir, maxX); + a2 = maxAngle * bendDir; + } + } + break_outer: + float os = (float)Math.Atan2(cy, cx) * s2; + float rotation = parent.arotation; + a1 = (a1 - os) * MathUtils.RadDeg + os1 - rotation; + if (a1 > 180) + a1 -= 360; + else if (a1 < -180) + a1 += 360; + parent.UpdateWorldTransform(px, py, rotation + a1 * alpha, sx, sy, 0, 0); + rotation = child.arotation; + a2 = ((a2 + os) * MathUtils.RadDeg - child.ashearX) * s2 + os2 - rotation; + if (a2 > 180) + a2 -= 360; + else if (a2 < -180) + a2 += 360; + child.UpdateWorldTransform(cx, cy, rotation + a2 * alpha, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY); + } + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/IkConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/IkConstraintData.cs new file mode 100644 index 0000000..ead1d5d --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/IkConstraintData.cs @@ -0,0 +1,103 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +using System; +using System.Collections.Generic; + +namespace Spine4_2_33 { + /// Stores the setup pose for an IkConstraint. + public class IkConstraintData : ConstraintData { + internal ExposedList bones = new ExposedList(); + internal BoneData target; + internal int bendDirection; + internal bool compress, stretch, uniform; + internal float mix, softness; + + public IkConstraintData (string name) : base(name) { + } + + /// The bones that are constrained by this IK Constraint. + public ExposedList Bones { + get { return bones; } + } + + /// The bone that is the IK target. + public BoneData Target { + get { return target; } + set { target = value; } + } + + /// + /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. + /// + /// For two bone IK: if the parent bone has local nonuniform scale, the child bone's local Y translation is set to 0. + /// + public float Mix { + get { return mix; } + set { mix = value; } + } + + /// For two bone IK, the target bone's distance from the maximum reach of the bones where rotation begins to slow. The bones + /// will not straighten completely until the target is this far out of range. + public float Softness { + get { return softness; } + set { softness = value; } + } + + /// For two bone IK, controls the bend direction of the IK bones, either 1 or -1. + public int BendDirection { + get { return bendDirection; } + set { bendDirection = value; } + } + + /// For one bone IK, when true and the target is too close, the bone is scaled to reach it. + public bool Compress { + get { return compress; } + set { compress = value; } + } + + /// When true and the target is out of range, the parent bone is scaled to reach it. + /// + /// For two bone IK: 1) the child bone's local Y translation is set to 0, + /// 2) stretch is not applied if is > 0, + /// and 3) if the parent bone has local nonuniform scale, stretch is not applied. + public bool Stretch { + get { return stretch; } + set { stretch = value; } + } + + /// + /// When true and or is used, the bone is scaled on both the X and Y axes. + /// + public bool Uniform { + get { return uniform; } + set { uniform = value; } + } + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Json.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Json.cs new file mode 100644 index 0000000..a96d862 --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Json.cs @@ -0,0 +1,519 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Text; + +namespace Spine4_2_33 { + public static class Json { + public static object Deserialize (TextReader text) { + SharpJson.JsonDecoder parser = new SharpJson.JsonDecoder(); + parser.parseNumbersAsFloat = true; + return parser.Decode(text.ReadToEnd()); + } + } +} + +/** + * Copyright (c) 2016 Adriano Tinoco d'Oliveira Rezende + * + * Based on the JSON parser by Patrick van Bergen + * http://techblog.procurios.nl/k/news/view/14605/14863/how-do-i-write-my-own-parser-(for-json).html + * + * Changes made: + * + * - Optimized parser speed (deserialize roughly near 3x faster than original) + * - Added support to handle lexer/parser error messages with line numbers + * - Added more fine grained control over type conversions during the parsing + * - Refactory API (Separate Lexer code from Parser code and the Encoder from Decoder) + * + * Permission is hereby granted, free 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. + */ +namespace Spine4_2_33 +{ + class Lexer { + public enum Token { + None, + Null, + True, + False, + Colon, + Comma, + String, + Number, + CurlyOpen, + CurlyClose, + SquaredOpen, + SquaredClose, + }; + + public bool hasError { + get { + return !success; + } + } + + public int lineNumber { + get; + private set; + } + + public bool parseNumbersAsFloat { + get; + set; + } + + char[] json; + int index = 0; + bool success = true; + char[] stringBuffer = new char[4096]; + + public Lexer (string text) { + Reset(); + + json = text.ToCharArray(); + parseNumbersAsFloat = false; + } + + public void Reset () { + index = 0; + lineNumber = 1; + success = true; + } + + public string ParseString () { + int idx = 0; + StringBuilder builder = null; + + SkipWhiteSpaces(); + + // " + char c = json[index++]; + + bool failed = false; + bool complete = false; + + while (!complete && !failed) { + if (index == json.Length) + break; + + c = json[index++]; + if (c == '"') { + complete = true; + break; + } else if (c == '\\') { + if (index == json.Length) + break; + + c = json[index++]; + + switch (c) { + case '"': + stringBuffer[idx++] = '"'; + break; + case '\\': + stringBuffer[idx++] = '\\'; + break; + case '/': + stringBuffer[idx++] = '/'; + break; + case 'b': + stringBuffer[idx++] = '\b'; + break; + case 'f': + stringBuffer[idx++] = '\f'; + break; + case 'n': + stringBuffer[idx++] = '\n'; + break; + case 'r': + stringBuffer[idx++] = '\r'; + break; + case 't': + stringBuffer[idx++] = '\t'; + break; + case 'u': + int remainingLength = json.Length - index; + if (remainingLength >= 4) { + string hex = new string(json, index, 4); + + // XXX: handle UTF + stringBuffer[idx++] = (char)Convert.ToInt32(hex, 16); + + // skip 4 chars + index += 4; + } else { + failed = true; + } + break; + } + } else { + stringBuffer[idx++] = c; + } + + if (idx >= stringBuffer.Length) { + if (builder == null) + builder = new StringBuilder(); + + builder.Append(stringBuffer, 0, idx); + idx = 0; + } + } + + if (!complete) { + success = false; + return null; + } + + if (builder != null) + return builder.ToString(); + else + return new string(stringBuffer, 0, idx); + } + + string GetNumberString() + { + SkipWhiteSpaces(); + + int lastIndex = GetLastIndexOfNumber(index); + int charLength = (lastIndex - index) + 1; + + string result = new string(json, index, charLength); + + index = lastIndex + 1; + + return result; + } + + public float ParseFloatNumber () { + float number; + string str = GetNumberString(); + + if (!float.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out number)) + return 0; + + return number; + } + + public double ParseDoubleNumber () { + double number; + string str = GetNumberString(); + + if (!double.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out number)) + return 0; + + return number; + } + + int GetLastIndexOfNumber (int index) { + int lastIndex; + + for (lastIndex = index; lastIndex < json.Length; lastIndex++) { + char ch = json[lastIndex]; + + if ((ch < '0' || ch > '9') && ch != '+' && ch != '-' + && ch != '.' && ch != 'e' && ch != 'E') + break; + } + + return lastIndex - 1; + } + + void SkipWhiteSpaces () { + for (; index < json.Length; index++) { + char ch = json[index]; + + if (ch == '\n') + lineNumber++; + + if (!char.IsWhiteSpace(json[index])) + break; + } + } + + public Token LookAhead () { + SkipWhiteSpaces(); + + int savedIndex = index; + return NextToken(json, ref savedIndex); + } + + public Token NextToken () { + SkipWhiteSpaces(); + return NextToken(json, ref index); + } + + static Token NextToken (char[] json, ref int index) { + if (index == json.Length) + return Token.None; + + char c = json[index++]; + + switch (c) { + case '{': + return Token.CurlyOpen; + case '}': + return Token.CurlyClose; + case '[': + return Token.SquaredOpen; + case ']': + return Token.SquaredClose; + case ',': + return Token.Comma; + case '"': + return Token.String; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + return Token.Number; + case ':': + return Token.Colon; + } + + index--; + + int remainingLength = json.Length - index; + + // false + if (remainingLength >= 5) { + if (json[index] == 'f' && + json[index + 1] == 'a' && + json[index + 2] == 'l' && + json[index + 3] == 's' && + json[index + 4] == 'e') { + index += 5; + return Token.False; + } + } + + // true + if (remainingLength >= 4) { + if (json[index] == 't' && + json[index + 1] == 'r' && + json[index + 2] == 'u' && + json[index + 3] == 'e') { + index += 4; + return Token.True; + } + } + + // null + if (remainingLength >= 4) { + if (json[index] == 'n' && + json[index + 1] == 'u' && + json[index + 2] == 'l' && + json[index + 3] == 'l') { + index += 4; + return Token.Null; + } + } + + return Token.None; + } + } + + public class JsonDecoder { + public string errorMessage { + get; + private set; + } + + public bool parseNumbersAsFloat { + get; + set; + } + + Lexer lexer; + + public JsonDecoder () { + errorMessage = null; + parseNumbersAsFloat = false; + } + + public object Decode (string text) { + errorMessage = null; + + lexer = new Lexer(text); + lexer.parseNumbersAsFloat = parseNumbersAsFloat; + + return ParseValue(); + } + + public static object DecodeText (string text) { + JsonDecoder builder = new JsonDecoder(); + return builder.Decode(text); + } + + IDictionary ParseObject () { + Dictionary table = new Dictionary(); + + // { + lexer.NextToken(); + + while (true) { + Lexer.Token token = lexer.LookAhead(); + + switch (token) { + case Lexer.Token.None: + TriggerError("Invalid token"); + return null; + case Lexer.Token.Comma: + lexer.NextToken(); + break; + case Lexer.Token.CurlyClose: + lexer.NextToken(); + return table; + default: + // name + string name = EvalLexer(lexer.ParseString()); + + if (errorMessage != null) + return null; + + // : + token = lexer.NextToken(); + + if (token != Lexer.Token.Colon) { + TriggerError("Invalid token; expected ':'"); + return null; + } + + // value + object value = ParseValue(); + + if (errorMessage != null) + return null; + + table[name] = value; + break; + } + } + + //return null; // Unreachable code + } + + IList ParseArray () { + List array = new List(); + + // [ + lexer.NextToken(); + + while (true) { + Lexer.Token token = lexer.LookAhead(); + + switch (token) { + case Lexer.Token.None: + TriggerError("Invalid token"); + return null; + case Lexer.Token.Comma: + lexer.NextToken(); + break; + case Lexer.Token.SquaredClose: + lexer.NextToken(); + return array; + default: + object value = ParseValue(); + + if (errorMessage != null) + return null; + + array.Add(value); + break; + } + } + + //return null; // Unreachable code + } + + object ParseValue () { + switch (lexer.LookAhead()) { + case Lexer.Token.String: + return EvalLexer(lexer.ParseString()); + case Lexer.Token.Number: + if (parseNumbersAsFloat) + return EvalLexer(lexer.ParseFloatNumber()); + else + return EvalLexer(lexer.ParseDoubleNumber()); + case Lexer.Token.CurlyOpen: + return ParseObject(); + case Lexer.Token.SquaredOpen: + return ParseArray(); + case Lexer.Token.True: + lexer.NextToken(); + return true; + case Lexer.Token.False: + lexer.NextToken(); + return false; + case Lexer.Token.Null: + lexer.NextToken(); + return null; + case Lexer.Token.None: + break; + } + + TriggerError("Unable to parse value"); + return null; + } + + void TriggerError (string message) { + errorMessage = string.Format("Error: '{0}' at line {1}", + message, lexer.lineNumber); + } + + T EvalLexer (T value) { + if (lexer.hasError) + TriggerError("Lexical error ocurred"); + + return value; + } + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/MathUtils.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/MathUtils.cs new file mode 100644 index 0000000..142ea40 --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/MathUtils.cs @@ -0,0 +1,184 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +//#define USE_FAST_SIN_COS_ATAN2_APPROXIMATIONS + +using System; + +namespace Spine4_2_33 { + public static class MathUtils { + public const float PI = 3.1415927f; + public const float PI2 = PI * 2; + public const float InvPI2 = 1 / PI2; + public const float RadDeg = 180f / PI; + public const float DegRad = PI / 180; + + static Random random = new Random(); + +#if USE_FAST_SIN_COS_ATAN2_APPROXIMATIONS + const int SIN_BITS = 14; // 16KB. Adjust for accuracy. + const int SIN_MASK = ~(-1 << SIN_BITS); + const int SIN_COUNT = SIN_MASK + 1; + const float RadFull = PI * 2; + const float DegFull = 360; + const float RadToIndex = SIN_COUNT / RadFull; + const float DegToIndex = SIN_COUNT / DegFull; + static float[] sin = new float[SIN_COUNT]; + + static MathUtils () { + for (int i = 0; i < SIN_COUNT; i++) + sin[i] = (float)Math.Sin((i + 0.5f) / SIN_COUNT * RadFull); + for (int i = 0; i < 360; i += 90) + sin[(int)(i * DegToIndex) & SIN_MASK] = (float)Math.Sin(i * DegRad); + } + + /// Returns the sine of a given angle in radians from a lookup table. + static public float Sin (float radians) { + return sin[(int)(radians * RadToIndex) & SIN_MASK]; + } + + /// Returns the cosine of a given angle in radians from a lookup table. + static public float Cos (float radians) { + return sin[(int)((radians + PI / 2) * RadToIndex) & SIN_MASK]; + } + + /// Returns the sine of a given angle in degrees from a lookup table. + static public float SinDeg (float degrees) { + return sin[(int)(degrees * DegToIndex) & SIN_MASK]; + } + + /// Returns the cosine of a given angle in degrees from a lookup table. + static public float CosDeg (float degrees) { + return sin[(int)((degrees + 90) * DegToIndex) & SIN_MASK]; + } + + static public float Atan2Deg (float y, float x) { + return Atan2(y, x) * RadDeg; + } + + /// 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 public float Atan2 (float y, float x) { + if (x == 0f) { + if (y > 0f) return PI / 2; + if (y == 0f) return 0f; + return -PI / 2; + } + float atan, z = y / x; + if (Math.Abs(z) < 1f) { + atan = z / (1f + 0.28f * z * z); + if (x < 0f) return atan + (y < 0f ? -PI : PI); + return atan; + } + atan = PI / 2 - z / (z * z + 0.28f); + return y < 0f ? atan - PI : atan; + } +#else + /// Returns the sine of a given angle in radians. + static public float Sin (float radians) { + return (float)Math.Sin(radians); + } + + /// Returns the cosine of a given angle in radians. + static public float Cos (float radians) { + return (float)Math.Cos(radians); + } + + /// Returns the sine of a given angle in degrees. + static public float SinDeg (float degrees) { + return (float)Math.Sin(degrees * DegRad); + } + + /// Returns the cosine of a given angle in degrees. + static public float CosDeg (float degrees) { + return (float)Math.Cos(degrees * DegRad); + } + + + static public float Atan2Deg (float y, float x) { + return (float)Math.Atan2(y, x) * RadDeg; + } + + + /// Returns the atan2 using Math.Atan2. + static public float Atan2 (float y, float x) { + return (float)Math.Atan2(y, x); + } +#endif + static public float Clamp (float value, float min, float max) { + if (value < min) return min; + if (value > max) return max; + return value; + } + + static public float RandomTriangle (float min, float max) { + return RandomTriangle(min, max, (min + max) * 0.5f); + } + + static public float RandomTriangle (float min, float max, float mode) { + float u = (float)random.NextDouble(); + float d = max - min; + if (u <= (mode - min) / d) return min + (float)Math.Sqrt(u * d * (mode - min)); + return max - (float)Math.Sqrt((1 - u) * d * (max - mode)); + } + } + + public abstract class IInterpolation { + public static IInterpolation Pow2 = new Pow(2); + public static IInterpolation Pow2Out = new PowOut(2); + + protected abstract float Apply (float a); + + public float Apply (float start, float end, float a) { + return start + (end - start) * Apply(a); + } + } + + public class Pow : IInterpolation { + public float Power { get; set; } + + public Pow (float power) { + Power = power; + } + + protected override float Apply (float a) { + if (a <= 0.5f) return (float)Math.Pow(a * 2, Power) / 2; + return (float)Math.Pow((a - 1) * 2, Power) / (Power % 2 == 0 ? -2 : 2) + 1; + } + } + + public class PowOut : Pow { + public PowOut (float power) : base(power) { + } + + protected override float Apply (float a) { + return (float)Math.Pow(a - 1, Power) * (Power % 2 == 0 ? -1 : 1) + 1; + } + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/MonoGameLoader/MeshBatcher.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/MonoGameLoader/MeshBatcher.cs new file mode 100644 index 0000000..be9c697 --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/MonoGameLoader/MeshBatcher.cs @@ -0,0 +1,195 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System; +using System.Collections.Generic; + +namespace Spine4_2_33 { + public struct VertexPositionColorTextureColor : IVertexType { + public Vector3 Position; + public Color Color; + public Vector2 TextureCoordinate; + public Color Color2; + + public readonly static VertexDeclaration VertexDeclaration = new VertexDeclaration + ( + new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0), + new VertexElement(12, VertexElementFormat.Color, VertexElementUsage.Color, 0), + new VertexElement(16, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0), + new VertexElement(24, VertexElementFormat.Color, VertexElementUsage.Color, 1) + ); + + VertexDeclaration IVertexType.VertexDeclaration { + get { return VertexDeclaration; } + } + } + + // #region License + // /* + // Microsoft Public License (Ms-PL) + // MonoGame - Copyright � 2009 The MonoGame Team + // + // All rights reserved. + // + // This license governs use of the accompanying software. If you use the software, you accept this license. If you do not + // accept the license, do not use the software. + // + // 1. Definitions + // The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under + // U.S. copyright law. + // + // A "contribution" is the original software, or any additions or changes to the software. + // A "contributor" is any person that distributes its contribution under this license. + // "Licensed patents" are a contributor's patent claims that read directly on its contribution. + // + // 2. Grant of Rights + // (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, + // each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. + // (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, + // each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. + // + // 3. Conditions and Limitations + // (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. + // (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, + // your patent license from such contributor to the software ends automatically. + // (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution + // notices that are present in the software. + // (D) If you distribute any portion of the software in source code form, you may do so only under this license by including + // a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object + // code form, you may only do so under a license that complies with this license. + // (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees + // or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent + // permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular + // purpose and non-infringement. + // */ + // #endregion License + // + + /// Draws batched meshes. + public class MeshBatcher { + private readonly List items; + private readonly Queue freeItems; + private VertexPositionColorTextureColor[] vertexArray = { }; + private short[] triangles = { }; + + public MeshBatcher () { + items = new List(256); + freeItems = new Queue(256); + EnsureCapacity(256, 512); + } + + /// Returns a pooled MeshItem. + public MeshItem NextItem (int vertexCount, int triangleCount) { + MeshItem item = freeItems.Count > 0 ? freeItems.Dequeue() : new MeshItem(); + if (item.vertices.Length < vertexCount) item.vertices = new VertexPositionColorTextureColor[vertexCount]; + if (item.triangles.Length < triangleCount) item.triangles = new int[triangleCount]; + item.vertexCount = vertexCount; + item.triangleCount = triangleCount; + items.Add(item); + return item; + } + + private void EnsureCapacity (int vertexCount, int triangleCount) { + if (vertexArray.Length < vertexCount) vertexArray = new VertexPositionColorTextureColor[vertexCount]; + if (triangles.Length < triangleCount) triangles = new short[triangleCount]; + } + + public void Draw (GraphicsDevice device) { + if (items.Count == 0) return; + + int itemCount = items.Count; + int vertexCount = 0, triangleCount = 0; + for (int i = 0; i < itemCount; i++) { + MeshItem item = items[i]; + vertexCount += item.vertexCount; + triangleCount += item.triangleCount; + } + EnsureCapacity(vertexCount, triangleCount); + + vertexCount = 0; + triangleCount = 0; + Texture2D lastTexture = null; + for (int i = 0; i < itemCount; i++) { + MeshItem item = items[i]; + int itemVertexCount = item.vertexCount; + + if (item.texture != lastTexture || vertexCount + itemVertexCount > short.MaxValue) { + FlushVertexArray(device, vertexCount, triangleCount); + vertexCount = 0; + triangleCount = 0; + lastTexture = item.texture; + device.Textures[0] = lastTexture; + if (item.textureLayers != null) { + for (int layer = 1; layer < item.textureLayers.Length; ++layer) + device.Textures[layer] = item.textureLayers[layer]; + } + } + + int[] itemTriangles = item.triangles; + int itemTriangleCount = item.triangleCount; + for (int ii = 0, t = triangleCount; ii < itemTriangleCount; ii++, t++) + triangles[t] = (short)(itemTriangles[ii] + vertexCount); + triangleCount += itemTriangleCount; + + Array.Copy(item.vertices, 0, vertexArray, vertexCount, itemVertexCount); + vertexCount += itemVertexCount; + } + FlushVertexArray(device, vertexCount, triangleCount); + } + + public void AfterLastDrawPass () { + int itemCount = items.Count; + for (int i = 0; i < itemCount; i++) { + var item = items[i]; + item.texture = null; + freeItems.Enqueue(item); + } + items.Clear(); + } + + private void FlushVertexArray (GraphicsDevice device, int vertexCount, int triangleCount) { + if (vertexCount == 0) return; + device.DrawUserIndexedPrimitives( + PrimitiveType.TriangleList, + vertexArray, 0, vertexCount, + triangles, 0, triangleCount / 3, + VertexPositionColorTextureColor.VertexDeclaration); + } + } + + public class MeshItem { + public Texture2D texture = null; + public Texture2D[] textureLayers = null; + public int vertexCount, triangleCount; + public VertexPositionColorTextureColor[] vertices = { }; + public int[] triangles = { }; + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/MonoGameLoader/ShapeRenderer.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/MonoGameLoader/ShapeRenderer.cs new file mode 100644 index 0000000..1996d9b --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/MonoGameLoader/ShapeRenderer.cs @@ -0,0 +1,165 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Spine4_2_33 { + /// + /// Batch drawing of lines and shapes that can be derived from lines. + /// + /// Call drawing methods in between Begin()/End() + /// + public class ShapeRenderer { + GraphicsDevice device; + List vertices = new List(); + Color color = Color.White; + BasicEffect effect; + public BasicEffect Effect { get { return effect; } set { effect = value; } } + + public ShapeRenderer (GraphicsDevice device) { + this.device = device; + this.effect = new BasicEffect(device); + effect.World = Matrix.Identity; + effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); + effect.TextureEnabled = false; + effect.VertexColorEnabled = true; + } + + public void SetColor (Color color) { + this.color = color; + } + + public void Begin () { + device.RasterizerState = new RasterizerState(); + device.BlendState = BlendState.AlphaBlend; + } + + public void Line (float x1, float y1, float x2, float y2, float z = 0f) { + vertices.Add(new VertexPositionColor(new Vector3(x1, y1, z), color)); + vertices.Add(new VertexPositionColor(new Vector3(x2, y2, z), color)); + } + + /** Calls {@link #circle(float, float, float, int)} by estimating the number of segments needed for a smooth circle. */ + public void Circle (float x, float y, float radius, float z = 0f) { + Circle(x, y, radius, Math.Max(1, (int)(6 * (float)Math.Pow(radius, 1.0f / 3.0f))), z); + } + + /** Draws a circle using {@link ShapeType#Line} or {@link ShapeType#Filled}. */ + public void Circle (float x, float y, float radius, int segments, float z = 0f) { + if (segments <= 0) throw new ArgumentException("segments must be > 0."); + float angle = 2 * MathUtils.PI / segments; + float cos = MathUtils.Cos(angle); + float sin = MathUtils.Sin(angle); + float cx = radius, cy = 0; + float temp = 0; + + for (int i = 0; i < segments; i++) { + vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, z), color)); + temp = cx; + cx = cos * cx - sin * cy; + cy = sin * temp + cos * cy; + vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, z), color)); + } + vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, z), color)); + + temp = cx; + cx = radius; + cy = 0; + vertices.Add(new VertexPositionColor(new Vector3(x + cx, y + cy, z), color)); + } + + public void Triangle (float x1, float y1, float x2, float y2, float x3, float y3, float z = 0f) { + vertices.Add(new VertexPositionColor(new Vector3(x1, y1, z), color)); + vertices.Add(new VertexPositionColor(new Vector3(x2, y2, z), color)); + + vertices.Add(new VertexPositionColor(new Vector3(x2, y2, z), color)); + vertices.Add(new VertexPositionColor(new Vector3(x3, y3, z), color)); + + vertices.Add(new VertexPositionColor(new Vector3(x3, y3, z), color)); + vertices.Add(new VertexPositionColor(new Vector3(x1, y1, z), color)); + } + + public void X (float x, float y, float len, float z = 0f) { + Line(x + len, y + len, x - len, y - len, z); + Line(x - len, y + len, x + len, y - len, z); + } + + public void Polygon (float[] polygonVertices, int offset, int count, float z = 0f) { + if (count < 3) throw new ArgumentException("Polygon must contain at least 3 vertices"); + + offset <<= 1; + + var firstX = polygonVertices[offset]; + var firstY = polygonVertices[offset + 1]; + var last = offset + count; + + for (int i = offset, n = offset + count; i < n; i += 2) { + var x1 = polygonVertices[i]; + var y1 = polygonVertices[i + 1]; + + var x2 = 0f; + var y2 = 0f; + + if (i + 2 >= last) { + x2 = firstX; + y2 = firstY; + } else { + x2 = polygonVertices[i + 2]; + y2 = polygonVertices[i + 3]; + } + + Line(x1, y1, x2, y2, z); + } + } + + public void Rect (float x, float y, float width, float height, float z = 0f) { + Line(x, y, x + width, y, z); + Line(x + width, y, x + width, y + height, z); + Line(x + width, y + height, x, y + height, z); + Line(x, y + height, x, y, z); + } + + public void End () { + if (vertices.Count == 0) return; + var verticesArray = vertices.ToArray(); + + foreach (EffectPass pass in effect.CurrentTechnique.Passes) { + pass.Apply(); + device.DrawUserPrimitives(PrimitiveType.LineList, verticesArray, 0, verticesArray.Length / 2); + } + + vertices.Clear(); + } + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/MonoGameLoader/SkeletonDebugRenderer.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/MonoGameLoader/SkeletonDebugRenderer.cs new file mode 100644 index 0000000..512487b --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/MonoGameLoader/SkeletonDebugRenderer.cs @@ -0,0 +1,234 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Spine4_2_33 { + public class SkeletonDebugRenderer { + ShapeRenderer renderer; + + public static Color boneLineColor = new Color(1f, 0f, 0f, 1f); + public static Color boneOriginColor = new Color(0f, 1f, 0f, 1f); + public static Color attachmentLineColor = new Color(0f, 0f, 1f, 0.5f); + public static Color triangleLineColor = new Color(1f, 0.64f, 0f, 0.5f); + public static Color pathColor = new Color(1f, 0.5f, 0f, 1f); + public static Color clipColor = new Color(0.8f, 0f, 0f, 1f); + public static Color clipDecomposedColor = new Color(0.8f, 0.8f, 0f, 1f); + public static Color aabbColor = new Color(0f, 1f, 0f, 0.5f); + + public BasicEffect Effect { get { return renderer.Effect; } set { renderer.Effect = value; } } + + /// A Z position offset added at each vertex. + private float z = 0.0f; + public float Z { get { return z; } set { z = value; } } + + public bool DrawBones { get; set; } + public bool DrawRegionAttachments { get; set; } + public bool DrawBoundingBoxes { get; set; } + public bool DrawMeshHull { get; set; } + public bool DrawMeshTriangles { get; set; } + public bool DrawPaths { get; set; } + public bool DrawClipping { get; set; } + public bool DrawClippingDecomposed { get; set; } + public bool DrawSkeletonXY { get; set; } + public void DisableAll () { + DrawBones = false; + DrawRegionAttachments = false; + DrawBoundingBoxes = false; + DrawMeshHull = false; + DrawMeshTriangles = false; + DrawPaths = false; + DrawClipping = false; + DrawSkeletonXY = false; + } + + public void EnableAll () { + DrawBones = true; + DrawRegionAttachments = true; + DrawBoundingBoxes = true; + DrawMeshHull = true; + DrawMeshTriangles = true; + DrawPaths = true; + DrawClipping = true; + DrawSkeletonXY = true; + } + + private float[] vertices = new float[1024 * 2]; + private SkeletonBounds bounds = new SkeletonBounds(); + private Triangulator triangulator = new Triangulator(); + + public SkeletonDebugRenderer (GraphicsDevice device) { + renderer = new ShapeRenderer(device); + EnableAll(); + } + + public void Begin () { + renderer.Begin(); + } + + public void Draw (Skeleton skeleton) { + var skeletonX = skeleton.X; + var skeletonY = skeleton.Y; + + var bones = skeleton.Bones; + if (DrawBones) { + renderer.SetColor(boneLineColor); + for (int i = 0, n = bones.Count; i < n; i++) { + var bone = bones.Items[i]; + if (bone.Parent == null) continue; + var x = bone.Data.Length * bone.A + bone.WorldX; + var y = bone.Data.Length * bone.C + bone.WorldY; + renderer.Line(bone.WorldX, bone.WorldY, x, y, z); + } + if (DrawSkeletonXY) renderer.X(skeletonX, skeletonY, 4, z); + } + + if (DrawRegionAttachments) { + renderer.SetColor(attachmentLineColor); + var slots = skeleton.Slots; + for (int i = 0, n = slots.Count; i < n; i++) { + var slot = slots.Items[i]; + var attachment = slot.Attachment; + if (attachment is RegionAttachment) { + var regionAttachment = (RegionAttachment)attachment; + var vertices = this.vertices; + regionAttachment.ComputeWorldVertices(slot, vertices, 0, 2); + renderer.Line(vertices[0], vertices[1], vertices[2], vertices[3], z); + renderer.Line(vertices[2], vertices[3], vertices[4], vertices[5], z); + renderer.Line(vertices[4], vertices[5], vertices[6], vertices[7], z); + renderer.Line(vertices[6], vertices[7], vertices[0], vertices[1], z); + } + } + } + + if (DrawMeshHull || DrawMeshTriangles) { + var slots = skeleton.Slots; + for (int i = 0, n = slots.Count; i < n; i++) { + var slot = slots.Items[i]; + var attachment = slot.Attachment; + if (!(attachment is MeshAttachment)) continue; + var mesh = (MeshAttachment)attachment; + var world = vertices = vertices.Length < mesh.WorldVerticesLength ? new float[mesh.WorldVerticesLength] : vertices; + mesh.ComputeWorldVertices(slot, 0, mesh.WorldVerticesLength, world, 0, 2); + int[] triangles = mesh.Triangles; + var hullLength = mesh.HullLength; + if (DrawMeshTriangles) { + renderer.SetColor(triangleLineColor); + for (int ii = 0, nn = triangles.Count(); ii < nn; ii += 3) { + int v1 = triangles[ii] * 2, v2 = triangles[ii + 1] * 2, v3 = triangles[ii + 2] * 2; + renderer.Triangle(world[v1], world[v1 + 1], // + world[v2], world[v2 + 1], // + world[v3], world[v3 + 1], // + z + ); + } + } + if (DrawMeshHull && hullLength > 0) { + renderer.SetColor(attachmentLineColor); + hullLength = (hullLength >> 1) * 2; + float lastX = vertices[hullLength - 2], lastY = vertices[hullLength - 1]; + for (int ii = 0, nn = hullLength; ii < nn; ii += 2) { + float x = vertices[ii], y = vertices[ii + 1]; + renderer.Line(x, y, lastX, lastY, z); + lastX = x; + lastY = y; + } + } + } + } + + if (DrawBoundingBoxes) { + var bounds = this.bounds; + bounds.Update(skeleton, true); + renderer.SetColor(aabbColor); + renderer.Rect(bounds.MinX, bounds.MinY, bounds.Width, bounds.Height, z); + var polygons = bounds.Polygons; + var boxes = bounds.BoundingBoxes; + for (int i = 0, n = polygons.Count; i < n; i++) { + var polygon = polygons.Items[i]; + renderer.Polygon(polygon.Vertices, 0, polygon.Count, z); + } + } + + if (DrawBones) { + renderer.SetColor(boneOriginColor); + for (int i = 0, n = bones.Count; i < n; i++) { + var bone = bones.Items[i]; + renderer.Circle(bone.WorldX, bone.WorldY, 3, z); + } + } + + if (DrawClipping) { + var slots = skeleton.Slots; + renderer.SetColor(clipColor); + for (int i = 0, n = slots.Count; i < n; i++) { + var slot = slots.Items[i]; + var attachment = slot.Attachment; + if (!(attachment is ClippingAttachment)) continue; + var clip = (ClippingAttachment)attachment; + var nn = clip.WorldVerticesLength; + var world = vertices = vertices.Length < nn ? new float[nn] : vertices; + clip.ComputeWorldVertices(slot, 0, nn, world, 0, 2); + ExposedList clippingPolygon = new ExposedList(); + for (int ii = 0; ii < nn; ii += 2) { + var x = world[ii]; + var y = world[ii + 1]; + var x2 = world[(ii + 2) % nn]; + var y2 = world[(ii + 3) % nn]; + renderer.Line(x, y, x2, y2, z); + clippingPolygon.Add(x); + clippingPolygon.Add(y); + } + + if (DrawClippingDecomposed) { + SkeletonClipping.MakeClockwise(clippingPolygon); + var triangles = triangulator.Triangulate(clippingPolygon); + var clippingPolygons = triangulator.Decompose(clippingPolygon, triangles); + renderer.SetColor(clipDecomposedColor); + foreach (var polygon in clippingPolygons) { + SkeletonClipping.MakeClockwise(polygon); + polygon.Add(polygon.Items[0]); + polygon.Add(polygon.Items[1]); + renderer.Polygon(polygon.Items, 0, polygon.Count >> 1, z); + } + } + } + } + } + + public void End () { + renderer.End(); + } + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/MonoGameLoader/SkeletonRenderer.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/MonoGameLoader/SkeletonRenderer.cs new file mode 100644 index 0000000..4f4f269 --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/MonoGameLoader/SkeletonRenderer.cs @@ -0,0 +1,249 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System; +using System.Collections.Generic; + +namespace Spine4_2_33 { + /// Draws region and mesh attachments. + public class SkeletonRenderer { + private const int TL = 0; + private const int TR = 1; + private const int BL = 2; + private const int BR = 3; + + SkeletonClipping clipper = new SkeletonClipping(); + /// Returns the used by this renderer for use with e.g. + /// + /// + public SkeletonClipping SkeletonClipping { get { return clipper; } } + + GraphicsDevice device; + MeshBatcher batcher; + public MeshBatcher Batcher { get { return batcher; } } + RasterizerState rasterizerState; + float[] vertices = new float[8]; + int[] quadTriangles = { 0, 1, 2, 2, 3, 0 }; + BlendState defaultBlendState; + BlendState blendStateMultiply = null; + + Effect effect; + public Effect Effect { get { return effect; } set { effect = value; } } + public IVertexEffect VertexEffect { get; set; } + + private bool premultipliedAlpha; + public bool PremultipliedAlpha { get { return premultipliedAlpha; } set { premultipliedAlpha = value; } } + + /// Attachments are rendered back to front in the x/y plane by the SkeletonRenderer. + /// Each attachment is offset by a customizable z-spacing value on the z-axis to avoid z-fighting + /// in shaders with ZWrite enabled. Typical values lie in the range [-0.1, 0]. + private float zSpacing = 0.0f; + public float ZSpacing { get { return zSpacing; } set { zSpacing = value; } } + + /// A Z position offset added at each vertex. + private float z = 0.0f; + public float Z { get { return z; } set { z = value; } } + + public SkeletonRenderer (GraphicsDevice device) { + this.device = device; + + batcher = new MeshBatcher(); + + var basicEffect = new BasicEffect(device); + basicEffect.World = Matrix.Identity; + basicEffect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up); + basicEffect.TextureEnabled = true; + basicEffect.VertexColorEnabled = true; + effect = basicEffect; + + rasterizerState = new RasterizerState(); + rasterizerState.CullMode = CullMode.None; + + Bone.yDown = true; + } + + public void Begin () { + defaultBlendState = premultipliedAlpha ? BlendState.AlphaBlend : BlendState.NonPremultiplied; + if (blendStateMultiply == null) { + blendStateMultiply = new BlendState(); + blendStateMultiply.ColorBlendFunction = BlendFunction.Max; + blendStateMultiply.ColorSourceBlend = Blend.DestinationColor; + blendStateMultiply.ColorDestinationBlend = Blend.Zero; + } + + device.RasterizerState = rasterizerState; + device.BlendState = defaultBlendState; + } + + public void End () { + foreach (EffectPass pass in effect.CurrentTechnique.Passes) { + pass.Apply(); + batcher.Draw(device); + } + batcher.AfterLastDrawPass(); + } + + public void Draw (Skeleton skeleton) { + var drawOrder = skeleton.DrawOrder; + var drawOrderItems = skeleton.DrawOrder.Items; + float skeletonR = skeleton.R, skeletonG = skeleton.G, skeletonB = skeleton.B, skeletonA = skeleton.A; + Color color = new Color(); + + if (VertexEffect != null) VertexEffect.Begin(skeleton); + + for (int i = 0, n = drawOrder.Count; i < n; i++) { + Slot slot = drawOrderItems[i]; + Attachment attachment = slot.Attachment; + float attachmentZOffset = z + zSpacing * i; + + float attachmentColorR, attachmentColorG, attachmentColorB, attachmentColorA; + object textureObject = null; + int verticesCount = 0; + float[] vertices = this.vertices; + int indicesCount = 0; + int[] indices = null; + float[] uvs = null; + + if (attachment is RegionAttachment) { + RegionAttachment regionAttachment = (RegionAttachment)attachment; + attachmentColorR = regionAttachment.R; attachmentColorG = regionAttachment.G; attachmentColorB = regionAttachment.B; attachmentColorA = regionAttachment.A; + regionAttachment.ComputeWorldVertices(slot, vertices, 0, 2); + verticesCount = 4; + indicesCount = 6; + indices = quadTriangles; + uvs = regionAttachment.UVs; + AtlasRegion region = (AtlasRegion)regionAttachment.Region; + textureObject = region.page.rendererObject; + } else if (attachment is MeshAttachment) { + MeshAttachment mesh = (MeshAttachment)attachment; + attachmentColorR = mesh.R; attachmentColorG = mesh.G; attachmentColorB = mesh.B; attachmentColorA = mesh.A; + int vertexCount = mesh.WorldVerticesLength; + if (vertices.Length < vertexCount) vertices = new float[vertexCount]; + verticesCount = vertexCount >> 1; + mesh.ComputeWorldVertices(slot, vertices); + indicesCount = mesh.Triangles.Length; + indices = mesh.Triangles; + uvs = mesh.UVs; + AtlasRegion region = (AtlasRegion)mesh.Region; + textureObject = region.page.rendererObject; + } else if (attachment is ClippingAttachment) { + ClippingAttachment clip = (ClippingAttachment)attachment; + clipper.ClipStart(slot, clip); + continue; + } else { + continue; + } + + // set blend state + BlendState blend; + switch (slot.Data.BlendMode) { + case BlendMode.Additive: + blend = BlendState.Additive; + break; + case BlendMode.Multiply: + blend = blendStateMultiply; + break; + default: + blend = defaultBlendState; + break; + } + if (device.BlendState != blend) { + End(); + device.BlendState = blend; + } + + // calculate color + float a = skeletonA * slot.A * attachmentColorA; + if (premultipliedAlpha) { + color = new Color( + skeletonR * slot.R * attachmentColorR * a, + skeletonG * slot.G * attachmentColorG * a, + skeletonB * slot.B * attachmentColorB * a, a); + } else { + color = new Color( + skeletonR * slot.R * attachmentColorR, + skeletonG * slot.G * attachmentColorG, + skeletonB * slot.B * attachmentColorB, a); + } + + Color darkColor = new Color(); + if (slot.HasSecondColor) { + if (premultipliedAlpha) { + darkColor = new Color(slot.R2 * a, slot.G2 * a, slot.B2 * a); + } else { + darkColor = new Color(slot.R2 * a, slot.G2 * a, slot.B2 * a); + } + } + darkColor.A = premultipliedAlpha ? (byte)255 : (byte)0; + + // clip + if (clipper.IsClipping) { + clipper.ClipTriangles(vertices, indices, indicesCount, uvs); + vertices = clipper.ClippedVertices.Items; + verticesCount = clipper.ClippedVertices.Count >> 1; + indices = clipper.ClippedTriangles.Items; + indicesCount = clipper.ClippedTriangles.Count; + uvs = clipper.ClippedUVs.Items; + } + + if (verticesCount == 0 || indicesCount == 0) + continue; + + // submit to batch + MeshItem item = batcher.NextItem(verticesCount, indicesCount); + if (textureObject is Texture2D) + item.texture = (Texture2D)textureObject; + else { + item.textureLayers = (Texture2D[])textureObject; + item.texture = item.textureLayers[0]; + } + for (int ii = 0, nn = indicesCount; ii < nn; ii++) { + item.triangles[ii] = indices[ii]; + } + VertexPositionColorTextureColor[] itemVertices = item.vertices; + for (int ii = 0, v = 0, nn = verticesCount << 1; v < nn; ii++, v += 2) { + itemVertices[ii].Color = color; + itemVertices[ii].Color2 = darkColor; + itemVertices[ii].Position.X = vertices[v]; + itemVertices[ii].Position.Y = vertices[v + 1]; + itemVertices[ii].Position.Z = attachmentZOffset; + itemVertices[ii].TextureCoordinate.X = uvs[v]; + itemVertices[ii].TextureCoordinate.Y = uvs[v + 1]; + if (VertexEffect != null) VertexEffect.Transform(ref itemVertices[ii]); + } + + clipper.ClipEnd(slot); + } + clipper.ClipEnd(); + if (VertexEffect != null) VertexEffect.End(); + } + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/MonoGameLoader/Util.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/MonoGameLoader/Util.cs new file mode 100644 index 0000000..884d853 --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/MonoGameLoader/Util.cs @@ -0,0 +1,79 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System; +using System.IO; + +#if WINDOWS_STOREAPP +using System.Threading.Tasks; +using Windows.Storage; +#endif + +namespace Spine4_2_33 { + + static public class Util { +#if WINDOWS_STOREAPP + private static async Task LoadFile(GraphicsDevice device, String path) { + var folder = Windows.ApplicationModel.Package.Current.InstalledLocation; + var file = await folder.GetFileAsync(path).AsTask().ConfigureAwait(false); + try { + return Util.LoadTexture(device, await file.OpenStreamForReadAsync().ConfigureAwait(false)); + } catch (Exception ex) { + throw new Exception("Error reading texture file: " + path, ex); + } + } + + static public Texture2D LoadTexture (GraphicsDevice device, String path) { + return LoadFile(device, path).Result; + } +#else + static public Texture2D LoadTexture (GraphicsDevice device, String path) { + +#if WINDOWS_PHONE + Stream stream = Microsoft.Xna.Framework.TitleContainer.OpenStream(path); + using (Stream input = stream) { +#else + using (Stream input = new FileStream(path, FileMode.Open, FileAccess.Read)) { +#endif + try { + return Util.LoadTexture(device, input); + } catch (Exception ex) { + throw new Exception("Error reading texture file: " + path, ex); + } + } + } +#endif + + static public Texture2D LoadTexture (GraphicsDevice device, Stream input) { + return Texture2D.FromStream(device, input); + } + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/MonoGameLoader/VertexEffect.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/MonoGameLoader/VertexEffect.cs new file mode 100644 index 0000000..1366eae --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/MonoGameLoader/VertexEffect.cs @@ -0,0 +1,97 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Spine4_2_33 { + public interface IVertexEffect { + void Begin (Skeleton skeleton); + void Transform (ref VertexPositionColorTextureColor vertex); + void End (); + } + + public class JitterEffect : IVertexEffect { + public float JitterX { get; set; } + public float JitterY { get; set; } + + public JitterEffect (float jitterX, float jitterY) { + JitterX = jitterX; + JitterY = jitterY; + } + + public void Begin (Skeleton skeleton) { + } + + public void End () { + } + + public void Transform (ref VertexPositionColorTextureColor vertex) { + vertex.Position.X += MathUtils.RandomTriangle(-JitterX, JitterY); + vertex.Position.Y += MathUtils.RandomTriangle(-JitterX, JitterY); + } + } + + public class SwirlEffect : IVertexEffect { + private float worldX, worldY, angle; + + public float Radius { get; set; } + public float Angle { get { return angle; } set { angle = value * MathUtils.DegRad; } } + public float CenterX { get; set; } + public float CenterY { get; set; } + public IInterpolation Interpolation { get; set; } + + public SwirlEffect (float radius) { + Radius = radius; + Interpolation = IInterpolation.Pow2; + } + + public void Begin (Skeleton skeleton) { + worldX = skeleton.X + CenterX; + worldY = skeleton.Y + CenterY; + } + + public void End () { + } + + public void Transform (ref VertexPositionColorTextureColor vertex) { + float x = vertex.Position.X - worldX; + float y = vertex.Position.Y - worldY; + float dist = (float)Math.Sqrt(x * x + y * y); + if (dist < Radius) { + float theta = Interpolation.Apply(0, angle, (Radius - dist) / Radius); + float cos = MathUtils.Cos(theta), sin = MathUtils.Sin(theta); + vertex.Position.X = cos * x - sin * y + worldX; + vertex.Position.Y = sin * x + cos * y + worldY; + } + } + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/MonoGameLoader/XnaTextureLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/MonoGameLoader/XnaTextureLoader.cs new file mode 100644 index 0000000..3e705fa --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/MonoGameLoader/XnaTextureLoader.cs @@ -0,0 +1,93 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System; +using System.IO; + +namespace Spine4_2_33 { + public class XnaTextureLoader : TextureLoader { + GraphicsDevice device; + string[] textureLayerSuffixes = null; + + /// + /// Constructor. + /// + /// The graphics device to be used. + /// If true multiple textures layers + /// (e.g. a diffuse/albedo texture and a normal map) are loaded instead of a single texture. + /// Names are constructed based on suffixes added according to the textureSuffixes parameter. + /// If loadMultipleTextureLayers is true, the strings of this array + /// define the path name suffix of each layer to be loaded. Array size must be equal to the number of layers to be loaded. + /// The first array entry is the suffix to be replaced (e.g. "_albedo", or "" for a first layer without a suffix), + /// subsequent array entries contain the suffix to replace the first entry with (e.g. "_normals"). + /// + /// An example would be: + /// new string[] { "", "_normals" } for loading a base diffuse texture named "skeletonname.png" and + /// a normalmap named "skeletonname_normals.png". + public XnaTextureLoader (GraphicsDevice device, bool loadMultipleTextureLayers = false, string[] textureSuffixes = null) { + this.device = device; + if (loadMultipleTextureLayers) + this.textureLayerSuffixes = textureSuffixes; + } + + public void Load (AtlasPage page, String path) { + Texture2D texture = Util.LoadTexture(device, path); + page.width = texture.Width; + page.height = texture.Height; + + if (textureLayerSuffixes == null) { + page.rendererObject = texture; + } else { + Texture2D[] textureLayersArray = new Texture2D[textureLayerSuffixes.Length]; + textureLayersArray[0] = texture; + for (int layer = 1; layer < textureLayersArray.Length; ++layer) { + string layerPath = GetLayerName(path, textureLayerSuffixes[0], textureLayerSuffixes[layer]); + textureLayersArray[layer] = Util.LoadTexture(device, layerPath); + } + page.rendererObject = textureLayersArray; + } + } + + public void Unload (Object texture) { + ((Texture2D)texture).Dispose(); + } + + private string GetLayerName (string firstLayerPath, string firstLayerSuffix, string replacementSuffix) { + + int suffixLocation = firstLayerPath.LastIndexOf(firstLayerSuffix + "."); + if (suffixLocation == -1) { + throw new Exception(string.Concat("Error composing texture layer name: first texture layer name '", firstLayerPath, + "' does not contain suffix to be replaced: '", firstLayerSuffix, "'")); + } + return firstLayerPath.Remove(suffixLocation, firstLayerSuffix.Length).Insert(suffixLocation, replacementSuffix); + } + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/PathConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/PathConstraint.cs new file mode 100644 index 0000000..3b21e71 --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/PathConstraint.cs @@ -0,0 +1,518 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +using System; + +namespace Spine4_2_33 { + using Physics = Skeleton.Physics; + + /// + /// + /// Stores the current pose for a path constraint. A path constraint adjusts the rotation, translation, and scale of the + /// constrained bones so they follow a . + /// + /// See Path constraints in the Spine User Guide. + /// + public class PathConstraint : IUpdatable { + const int NONE = -1, BEFORE = -2, AFTER = -3; + const float Epsilon = 0.00001f; + + internal readonly PathConstraintData data; + internal readonly ExposedList bones; + internal Slot target; + internal float position, spacing, mixRotate, mixX, mixY; + + internal bool active; + + internal readonly ExposedList spaces = new ExposedList(), positions = new ExposedList(); + internal readonly ExposedList world = new ExposedList(), curves = new ExposedList(), lengths = new ExposedList(); + internal readonly float[] segments = new float[10]; + + public PathConstraint (PathConstraintData data, Skeleton skeleton) { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + + bones = new ExposedList(data.Bones.Count); + foreach (BoneData boneData in data.bones) + bones.Add(skeleton.bones.Items[boneData.index]); + + target = skeleton.slots.Items[data.target.index]; + + position = data.position; + spacing = data.spacing; + mixRotate = data.mixRotate; + mixX = data.mixX; + mixY = data.mixY; + } + + /// Copy constructor. + public PathConstraint (PathConstraint constraint, Skeleton skeleton) + : this(constraint.data, skeleton) { + + position = constraint.position; + spacing = constraint.spacing; + mixRotate = constraint.mixRotate; + mixX = constraint.mixX; + mixY = constraint.mixY; + } + + public static void ArraysFill (float[] a, int fromIndex, int toIndex, float val) { + for (int i = fromIndex; i < toIndex; i++) + a[i] = val; + } + + public void SetToSetupPose () { + PathConstraintData data = this.data; + position = data.position; + spacing = data.spacing; + mixRotate = data.mixRotate; + mixX = data.mixX; + mixY = data.mixY; + } + + public void Update (Physics physics) { + PathAttachment attachment = target.Attachment as PathAttachment; + if (attachment == null) return; + + float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY; + if (mixRotate == 0 && mixX == 0 && mixY == 0) return; + + PathConstraintData data = this.data; + bool tangents = data.rotateMode == RotateMode.Tangent, scale = data.rotateMode == RotateMode.ChainScale; + int boneCount = this.bones.Count, spacesCount = tangents ? boneCount : boneCount + 1; + Bone[] bonesItems = this.bones.Items; + float[] spaces = this.spaces.Resize(spacesCount).Items, lengths = scale ? this.lengths.Resize(boneCount).Items : null; + float spacing = this.spacing; + switch (data.spacingMode) { + case SpacingMode.Percent: + if (scale) { + for (int i = 0, n = spacesCount - 1; i < n; i++) { + Bone bone = bonesItems[i]; + float setupLength = bone.data.length; + float x = setupLength * bone.a, y = setupLength * bone.c; + lengths[i] = (float)Math.Sqrt(x * x + y * y); + } + } + ArraysFill(spaces, 1, spacesCount, spacing); + break; + case SpacingMode.Proportional: { + float sum = 0; + for (int i = 0, n = spacesCount - 1; i < n;) { + Bone bone = bonesItems[i]; + float setupLength = bone.data.length; + if (setupLength < PathConstraint.Epsilon) { + if (scale) lengths[i] = 0; + spaces[++i] = spacing; + } else { + float x = setupLength * bone.a, y = setupLength * bone.c; + float length = (float)Math.Sqrt(x * x + y * y); + if (scale) lengths[i] = length; + spaces[++i] = length; + sum += length; + } + } + if (sum > 0) { + sum = spacesCount / sum * spacing; + for (int i = 1; i < spacesCount; i++) + spaces[i] *= sum; + } + break; + } + default: { + bool lengthSpacing = data.spacingMode == SpacingMode.Length; + for (int i = 0, n = spacesCount - 1; i < n;) { + Bone bone = bonesItems[i]; + float setupLength = bone.data.length; + if (setupLength < PathConstraint.Epsilon) { + if (scale) lengths[i] = 0; + spaces[++i] = spacing; + } else { + float x = setupLength * bone.a, y = setupLength * bone.c; + float length = (float)Math.Sqrt(x * x + y * y); + if (scale) lengths[i] = length; + spaces[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length / setupLength; + } + } + break; + } + } + + float[] positions = ComputeWorldPositions(attachment, spacesCount, tangents); + float boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation; + bool tip; + if (offsetRotation == 0) { + tip = data.rotateMode == RotateMode.Chain; + } else { + tip = false; + Bone p = target.bone; + offsetRotation *= p.a * p.d - p.b * p.c > 0 ? MathUtils.DegRad : -MathUtils.DegRad; + } + for (int i = 0, p = 3; i < boneCount; i++, p += 3) { + Bone bone = bonesItems[i]; + bone.worldX += (boneX - bone.worldX) * mixX; + bone.worldY += (boneY - bone.worldY) * mixY; + float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY; + if (scale) { + float length = lengths[i]; + if (length >= PathConstraint.Epsilon) { + float s = ((float)Math.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 = MathUtils.Atan2(dy, dx); + r -= MathUtils.Atan2(c, a); + if (tip) { + cos = MathUtils.Cos(r); + sin = MathUtils.Sin(r); + float length = bone.data.length; + boneX += (length * (cos * a - sin * c) - dx) * mixRotate; + boneY += (length * (sin * a + cos * c) - dy) * mixRotate; + } else + r += offsetRotation; + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) // + r += MathUtils.PI2; + r *= mixRotate; + cos = MathUtils.Cos(r); + sin = MathUtils.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(); + } + } + + float[] ComputeWorldPositions (PathAttachment path, int spacesCount, bool tangents) { + Slot target = this.target; + float position = this.position; + float[] spaces = this.spaces.Items, output = this.positions.Resize(spacesCount * 3 + 2).Items, world; + bool closed = path.Closed; + int verticesLength = path.WorldVerticesLength, curveCount = verticesLength / 6, prevCurve = NONE; + + float pathLength, multiplier; + if (!path.ConstantSpeed) { + float[] lengths = path.Lengths; + curveCount -= closed ? 1 : 2; + pathLength = lengths[curveCount]; + + if (data.positionMode == PositionMode.Percent) position *= pathLength; + + switch (data.spacingMode) { + case SpacingMode.Percent: + multiplier = pathLength; + break; + case SpacingMode.Proportional: + multiplier = pathLength / spacesCount; + break; + default: + multiplier = 1; + break; + } + + world = this.world.Resize(8).Items; + 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 %= pathLength; + if (p < 0) p += pathLength; + curve = 0; + } else if (p < 0) { + if (prevCurve != BEFORE) { + prevCurve = BEFORE; + path.ComputeWorldVertices(target, 2, 4, world, 0, 2); + } + AddBeforePosition(p, world, 0, output, o); + continue; + } else if (p > pathLength) { + if (prevCurve != AFTER) { + prevCurve = AFTER; + path.ComputeWorldVertices(target, verticesLength - 6, 4, world, 0, 2); + } + AddAfterPosition(p - pathLength, world, 0, output, 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, 2); + path.ComputeWorldVertices(target, 0, 4, world, 4, 2); + } else + path.ComputeWorldVertices(target, curve * 6 + 2, 8, world, 0, 2); + } + AddCurvePosition(p, world[0], world[1], world[2], world[3], world[4], world[5], world[6], world[7], output, o, + tangents || (i > 0 && space < PathConstraint.Epsilon)); + } + return output; + } + + // World vertices. + if (closed) { + verticesLength += 2; + world = this.world.Resize(verticesLength).Items; + path.ComputeWorldVertices(target, 2, verticesLength - 4, world, 0, 2); + path.ComputeWorldVertices(target, 0, 2, world, verticesLength - 4, 2); + world[verticesLength - 2] = world[0]; + world[verticesLength - 1] = world[1]; + } else { + curveCount--; + verticesLength -= 4; + world = this.world.Resize(verticesLength).Items; + path.ComputeWorldVertices(target, 2, verticesLength, world, 0, 2); + } + + // Curve lengths. + float[] curves = this.curves.Resize(curveCount).Items; + 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 += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx; + dfy += ddfy; + pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + dfx += ddfx + dddfx; + dfy += ddfy + dddfy; + pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + curves[i] = pathLength; + x1 = x2; + y1 = y2; + } + + if (data.positionMode == PositionMode.Percent) position *= pathLength; + + switch (data.spacingMode) { + case SpacingMode.Percent: + multiplier = pathLength; + break; + case SpacingMode.Proportional: + multiplier = pathLength / spacesCount; + break; + default: + multiplier = 1; + break; + } + + float[] segments = this.segments; + 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 %= pathLength; + if (p < 0) p += pathLength; + curve = 0; + } else if (p < 0) { + AddBeforePosition(p, world, 0, output, o); + continue; + } else if (p > pathLength) { + AddAfterPosition(p - pathLength, world, verticesLength - 4, output, 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 = (float)Math.Sqrt(dfx * dfx + dfy * dfy); + segments[0] = curveLength; + for (ii = 1; ii < 8; ii++) { + dfx += ddfx; + dfy += ddfy; + ddfx += dddfx; + ddfy += dddfy; + curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + segments[ii] = curveLength; + } + dfx += ddfx; + dfy += ddfy; + curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy); + segments[8] = curveLength; + dfx += ddfx + dddfx; + dfy += ddfy + dddfy; + curveLength += (float)Math.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, output, o, tangents || (i > 0 && space < PathConstraint.Epsilon)); + } + return output; + } + + static void AddBeforePosition (float p, float[] temp, int i, float[] output, int o) { + float x1 = temp[i], y1 = temp[i + 1], dx = temp[i + 2] - x1, dy = temp[i + 3] - y1, r = MathUtils.Atan2(dy, dx); + output[o] = x1 + p * MathUtils.Cos(r); + output[o + 1] = y1 + p * MathUtils.Sin(r); + output[o + 2] = r; + } + + static void AddAfterPosition (float p, float[] temp, int i, float[] output, int o) { + float x1 = temp[i + 2], y1 = temp[i + 3], dx = x1 - temp[i], dy = y1 - temp[i + 1], r = MathUtils.Atan2(dy, dx); + output[o] = x1 + p * MathUtils.Cos(r); + output[o + 1] = y1 + p * MathUtils.Sin(r); + output[o + 2] = r; + } + + static void AddCurvePosition (float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2, + float[] output, int o, bool tangents) { + if (p < PathConstraint.Epsilon || float.IsNaN(p)) { + output[o] = x1; + output[o + 1] = y1; + output[o + 2] = (float)Math.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.001f) + output[o + 2] = (float)Math.Atan2(cy1 - y1, cx1 - x1); + else + output[o + 2] = (float)Math.Atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt)); + } + } + + /// The position along the path. + public float Position { get { return position; } set { position = value; } } + /// The spacing between bones. + public float Spacing { get { return spacing; } set { spacing = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotations. + public float MixRotate { get { return mixRotate; } set { mixRotate = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. + public float MixX { get { return mixX; } set { mixX = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. + public float MixY { get { return mixY; } set { mixY = value; } } + /// The bones that will be modified by this path constraint. + public ExposedList Bones { get { return bones; } } + /// The slot whose path attachment will be used to constrained the bones. + public Slot Target { get { return target; } set { target = value; } } + public bool Active { get { return active; } } + /// The path constraint's setup pose data. + public PathConstraintData Data { get { return data; } } + + override public string ToString () { + return data.name; + } + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/PathConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/PathConstraintData.cs new file mode 100644 index 0000000..c6a79d6 --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/PathConstraintData.cs @@ -0,0 +1,72 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +using System; + +namespace Spine4_2_33 { + public class PathConstraintData : ConstraintData { + internal ExposedList bones = new ExposedList(); + internal SlotData target; + internal PositionMode positionMode; + internal SpacingMode spacingMode; + internal RotateMode rotateMode; + internal float offsetRotation; + internal float position, spacing, mixRotate, mixX, mixY; + + public PathConstraintData (string name) : base(name) { + } + + public ExposedList Bones { get { return bones; } } + public SlotData Target { get { return target; } set { target = value; } } + public PositionMode PositionMode { get { return positionMode; } set { positionMode = value; } } + public SpacingMode SpacingMode { get { return spacingMode; } set { spacingMode = value; } } + public RotateMode RotateMode { get { return rotateMode; } set { rotateMode = value; } } + public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } + public float Position { get { return position; } set { position = value; } } + public float Spacing { get { return spacing; } set { spacing = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. + public float RotateMix { get { return mixRotate; } set { mixRotate = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. + public float MixX { get { return mixX; } set { mixX = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. + public float MixY { get { return mixY; } set { mixY = value; } } + } + + public enum PositionMode { + Fixed, Percent + } + + public enum SpacingMode { + Length, Fixed, Percent, Proportional + } + + public enum RotateMode { + Tangent, Chain, ChainScale + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/PhysicsConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/PhysicsConstraint.cs new file mode 100644 index 0000000..74d222a --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/PhysicsConstraint.cs @@ -0,0 +1,326 @@ + +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +using System; + +namespace Spine4_2_33 { + using Physics = Skeleton.Physics; + + /// + /// Stores the current pose for a physics constraint. A physics constraint applies physics to bones. + /// + /// See Physics constraints in the Spine User Guide. + /// + public class PhysicsConstraint : IUpdatable { + internal readonly PhysicsConstraintData data; + public Bone bone; + internal float inertia, strength, damping, massInverse, wind, gravity, mix; + + bool reset = true; + float ux, uy, cx, cy, tx, ty; + float xOffset, xVelocity; + float yOffset, yVelocity; + float rotateOffset, rotateVelocity; + float scaleOffset, scaleVelocity; + + internal bool active; + + readonly Skeleton skeleton; + float remaining, lastTime; + + public PhysicsConstraint (PhysicsConstraintData data, Skeleton skeleton) { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + this.skeleton = skeleton; + + bone = skeleton.bones.Items[data.bone.index]; + + inertia = data.inertia; + strength = data.strength; + damping = data.damping; + massInverse = data.massInverse; + wind = data.wind; + gravity = data.gravity; + mix = data.mix; + } + + /// Copy constructor. + public PhysicsConstraint (PhysicsConstraint constraint, Skeleton skeleton) + : this(constraint.data, skeleton) { + + inertia = constraint.inertia; + strength = constraint.strength; + damping = constraint.damping; + massInverse = constraint.massInverse; + wind = constraint.wind; + gravity = constraint.gravity; + mix = constraint.mix; + } + + public void Reset () { + remaining = 0; + lastTime = skeleton.time; + reset = true; + xOffset = 0; + xVelocity = 0; + yOffset = 0; + yVelocity = 0; + rotateOffset = 0; + rotateVelocity = 0; + scaleOffset = 0; + scaleVelocity = 0; + } + + public void SetToSetupPose () { + PhysicsConstraintData data = this.data; + inertia = data.inertia; + strength = data.strength; + damping = data.damping; + massInverse = data.massInverse; + wind = data.wind; + gravity = data.gravity; + mix = data.mix; + } + + /// + /// Translates the physics constraint so next forces are applied as if the bone moved an additional + /// amount in world space. + /// + public void Translate (float x, float y) { + ux -= x; + uy -= y; + cx -= x; + cy -= y; + } + + /// + /// Rotates the physics constraint so next forces are applied as if the bone rotated around the + /// specified point in world space. + /// + public void Rotate (float x, float y, float degrees) { + float r = degrees * MathUtils.DegRad, cos = (float)Math.Cos(r), sin = (float)Math.Sin(r); + float dx = cx - x, dy = cy - y; + Translate(dx * cos - dy * sin - dx, dx * sin + dy * cos - dy); + } + + /// Applies the constraint to the constrained bones. + public void Update (Physics physics) { + float mix = this.mix; + if (mix == 0) return; + + bool x = data.x > 0, y = data.y > 0, rotateOrShearX = data.rotate > 0 || data.shearX > 0, scaleX = data.scaleX > 0; + Bone bone = this.bone; + float l = bone.data.length; + + switch (physics) { + case Physics.None: + return; + case Physics.Reset: + Reset(); + goto case Physics.Update; // Fall through. + case Physics.Update: + Skeleton skeleton = this.skeleton; + float delta = Math.Max(skeleton.time - lastTime, 0); + remaining += delta; + lastTime = skeleton.time; + + 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.data.referenceScale, d = -1; + float qx = data.limit * delta, qy = qx * Math.Abs(skeleton.ScaleY); + qx *= Math.Abs(skeleton.ScaleX); + + 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) { + d = (float)Math.Pow(damping, 60 * t); + float m = massInverse * t, e = strength, w = wind * f, g = (Bone.yDown ? -gravity : gravity) * f; + 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 = (float)Math.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 = (float)Math.Atan2(dy + ty, dx + tx) - ca - rotateOffset * mr; + rotateOffset += (r - (float)Math.Ceiling(r * MathUtils.InvPI2 - 0.5f) * MathUtils.PI2) * i; + r = rotateOffset * mr + ca; + c = (float)Math.Cos(r); + s = (float)Math.Sin(r); + if (scaleX) { + r = l * bone.WorldScaleX; + if (r > 0) scaleOffset += (dx * c + dy * s) * i / r; + } + } else { + c = (float)Math.Cos(ca); + s = (float)Math.Sin(ca); + float r = l * bone.WorldScaleX; + if (r > 0) scaleOffset += (dx * c + dy * s) * i / r; + } + a = remaining; + if (a >= t) { + if (d == -1) d = (float)Math.Pow(damping, 60 * t); + float m = massInverse * t, e = strength, w = wind, g = (Bone.yDown ? -gravity : gravity), h = l / f; + 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 = (float)Math.Cos(r); + s = (float)Math.Sin(r); + } else if (a < t) // + break; + } + } + } + remaining = a; + } + cx = bone.worldX; + cy = bone.worldY; + break; + case 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, c, a; + if (data.shearX > 0) { + float r = 0; + if (data.rotate > 0) { + r = o * data.rotate; + s = (float)Math.Sin(r); + c = (float)Math.Cos(r); + a = bone.b; + bone.b = c * a - s * bone.d; + bone.d = s * a + c * bone.d; + } + r += o * data.shearX; + s = (float)Math.Sin(r); + c = (float)Math.Cos(r); + a = bone.a; + bone.a = c * a - s * bone.c; + bone.c = s * a + c * bone.c; + } else { + o *= data.rotate; + s = (float)Math.Sin(o); + c = (float)Math.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.Pose) { + tx = l * bone.a; + ty = l * bone.c; + } + bone.UpdateAppliedTransform(); + } + + /// The bone constrained by this physics constraint. + public Bone Bone { get { return bone; } set { bone = value; } } + public float Inertia { get { return inertia; } set { inertia = value; } } + public float Strength { get { return strength; } set { strength = value; } } + public float Damping { get { return damping; } set { damping = value; } } + public float MassInverse { get { return massInverse; } set { massInverse = value; } } + public float Wind { get { return wind; } set { wind = value; } } + public float Gravity { get { return gravity; } set { gravity = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained poses. + public float Mix { get { return mix; } set { mix = value; } } + public bool Active { get { return active; } } + + + /// The physics constraint's setup pose data. + public PhysicsConstraintData getData () { + return data; + } + + /// The physics constraint's setup pose data. + public PhysicsConstraintData Data { get { return data; } } + + override public string ToString () { + return data.name; + } + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/PhysicsConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/PhysicsConstraintData.cs new file mode 100644 index 0000000..994b213 --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/PhysicsConstraintData.cs @@ -0,0 +1,71 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +namespace Spine4_2_33 { + /// + /// Stores the setup pose for a . + /// + /// See Physics constraints in the Spine User Guide. + /// + public class PhysicsConstraintData : ConstraintData { + internal BoneData bone; + internal float x, y, rotate, scaleX, shearX, limit; + internal float step, inertia, strength, damping, massInverse, wind, gravity, mix; + internal bool inertiaGlobal, strengthGlobal, dampingGlobal, massGlobal, windGlobal, gravityGlobal, mixGlobal; + + public PhysicsConstraintData (string name) : base(name) { + } + + /// The bone constrained by this physics constraint. + public BoneData Bone { get { return bone; } } + + public float Step { get { return step; } set { step = value; } } + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public float Rotate { get { return rotate; } set { rotate = value; } } + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + public float ShearX { get { return shearX; } set { shearX = value; } } + public float Limit { get { return limit; } set { limit = value; } } + public float Inertia { get { return inertia; } set { inertia = value; } } + public float Strength { get { return strength; } set { strength = value; } } + public float Damping { get { return damping; } set { damping = value; } } + public float MassInverse { get { return massInverse; } set { massInverse = value; } } + public float Wind { get { return wind; } set { wind = value; } } + public float Gravity { get { return gravity; } set { gravity = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained poses. + public float Mix { get { return mix; } set { mix = value; } } + public bool InertiaGlobal { get { return inertiaGlobal; } set { inertiaGlobal = value; } } + public bool StrengthGlobal { get { return strengthGlobal; } set { strengthGlobal = value; } } + public bool DampingGlobal { get { return dampingGlobal; } set { dampingGlobal = value; } } + public bool MassGlobal { get { return massGlobal; } set { massGlobal = value; } } + public bool WindGlobal { get { return windGlobal; } set { windGlobal = value; } } + public bool GravityGlobal { get { return gravityGlobal; } set { gravityGlobal = value; } } + public bool MixGlobal { get { return mixGlobal; } set { mixGlobal = value; } } + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Skeleton.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Skeleton.cs new file mode 100644 index 0000000..be64ca0 --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Skeleton.cs @@ -0,0 +1,782 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +using System; + +namespace Spine4_2_33 { + public class Skeleton { + static private readonly int[] quadTriangles = { 0, 1, 2, 2, 3, 0 }; + internal SkeletonData data; + internal ExposedList bones; + internal ExposedList slots; + internal ExposedList drawOrder; + internal ExposedList ikConstraints; + internal ExposedList transformConstraints; + internal ExposedList pathConstraints; + internal ExposedList physicsConstraints; + internal ExposedList updateCache = new ExposedList(); + internal Skin skin; + internal float r = 1, g = 1, b = 1, a = 1; + internal float x, y, scaleX = 1, time; + /// Private to enforce usage of ScaleY getter taking Bone.yDown into account. + private float scaleY = 1; + + /// The skeleton's setup pose data. + public SkeletonData Data { get { return data; } } + /// The skeleton's bones, sorted parent first. The root bone is always the first bone. + public ExposedList Bones { get { return bones; } } + /// The list of bones and constraints, sorted in the order they should be updated, + /// as computed by . + public ExposedList UpdateCacheList { get { return updateCache; } } + /// The skeleton's slots. + public ExposedList Slots { get { return slots; } } + /// The skeleton's slots in the order they should be drawn. + /// The returned array may be modified to change the draw order. + public ExposedList DrawOrder { get { return drawOrder; } } + /// The skeleton's IK constraints. + public ExposedList IkConstraints { get { return ikConstraints; } } + /// The skeleton's path constraints. + public ExposedList PathConstraints { get { return pathConstraints; } } + /// The skeleton's physics constraints. + public ExposedList PhysicsConstraints { get { return physicsConstraints; } } + /// The skeleton's transform constraints. + public ExposedList TransformConstraints { get { return transformConstraints; } } + + /// The skeleton's current skin. May be null. See + public Skin Skin { + /// The skeleton's current skin. May be null. + get { return skin; } + /// Sets a skin, . + set { SetSkin(value); } + } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } + /// The skeleton X position, which is added to the root bone worldX position. + /// + /// Bones that do not inherit translation are still affected by this property. + public float X { get { return x; } set { x = value; } } + /// The skeleton Y position, which is added to the root bone worldY position. + /// + /// Bones that do not inherit translation are still affected by this property. + public float Y { get { return y; } set { y = value; } } + /// Scales the entire skeleton on the X axis. + /// + /// Bones that do not inherit scale are still affected by this property. + public float ScaleX { get { return scaleX; } set { scaleX = value; } } + /// Scales the entire skeleton on the Y axis. + /// + /// Bones that do not inherit scale are still affected by this property. + public float ScaleY { get { return scaleY * (Bone.yDown ? -1 : 1); } set { scaleY = value; } } + + [Obsolete("Use ScaleX instead. FlipX is when ScaleX is negative.")] + public bool FlipX { get { return scaleX < 0; } set { scaleX = value ? -1f : 1f; } } + + [Obsolete("Use ScaleY instead. FlipY is when ScaleY is negative.")] + public bool FlipY { get { return scaleY < 0; } set { scaleY = value ? -1f : 1f; } } + /// Returns the skeleton's time. This is used for time-based manipulations, such as . + /// + public float Time { get { return time; } set { time = value; } } + + /// Returns the root bone, or null if the skeleton has no bones. + public Bone RootBone { + get { return bones.Count == 0 ? null : bones.Items[0]; } + } + + public Skeleton (SkeletonData data) { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + this.data = data; + + bones = new ExposedList(data.bones.Count); + Bone[] bonesItems = this.bones.Items; + foreach (BoneData boneData in data.bones) { + Bone bone; + if (boneData.parent == null) { + bone = new Bone(boneData, this, null); + } else { + Bone parent = bonesItems[boneData.parent.index]; + bone = new Bone(boneData, this, parent); + parent.children.Add(bone); + } + this.bones.Add(bone); + } + + slots = new ExposedList(data.slots.Count); + drawOrder = new ExposedList(data.slots.Count); + foreach (SlotData slotData in data.slots) { + Bone bone = bonesItems[slotData.boneData.index]; + Slot slot = new Slot(slotData, bone); + slots.Add(slot); + drawOrder.Add(slot); + } + + ikConstraints = new ExposedList(data.ikConstraints.Count); + foreach (IkConstraintData ikConstraintData in data.ikConstraints) + ikConstraints.Add(new IkConstraint(ikConstraintData, this)); + + transformConstraints = new ExposedList(data.transformConstraints.Count); + foreach (TransformConstraintData transformConstraintData in data.transformConstraints) + transformConstraints.Add(new TransformConstraint(transformConstraintData, this)); + + pathConstraints = new ExposedList(data.pathConstraints.Count); + foreach (PathConstraintData pathConstraintData in data.pathConstraints) + pathConstraints.Add(new PathConstraint(pathConstraintData, this)); + + physicsConstraints = new ExposedList(data.physicsConstraints.Count); + foreach (PhysicsConstraintData physicsConstraintData in data.physicsConstraints) + physicsConstraints.Add(new PhysicsConstraint(physicsConstraintData, this)); + + UpdateCache(); + } + + /// Copy constructor. + public Skeleton (Skeleton skeleton) { + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + data = skeleton.data; + + bones = new ExposedList(skeleton.bones.Count); + foreach (Bone bone in skeleton.bones) { + Bone newBone; + if (bone.parent == null) + newBone = new Bone(bone, this, null); + else { + Bone parent = bones.Items[bone.parent.data.index]; + newBone = new Bone(bone, this, parent); + parent.children.Add(newBone); + } + bones.Add(newBone); + } + + slots = new ExposedList(skeleton.slots.Count); + Bone[] bonesItems = bones.Items; + foreach (Slot slot in skeleton.slots) { + Bone bone = bonesItems[slot.bone.data.index]; + slots.Add(new Slot(slot, bone)); + } + + drawOrder = new ExposedList(slots.Count); + Slot[] slotsItems = slots.Items; + foreach (Slot slot in skeleton.drawOrder) + drawOrder.Add(slotsItems[slot.data.index]); + + ikConstraints = new ExposedList(skeleton.ikConstraints.Count); + foreach (IkConstraint ikConstraint in skeleton.ikConstraints) + ikConstraints.Add(new IkConstraint(ikConstraint, skeleton)); + + transformConstraints = new ExposedList(skeleton.transformConstraints.Count); + foreach (TransformConstraint transformConstraint in skeleton.transformConstraints) + transformConstraints.Add(new TransformConstraint(transformConstraint, skeleton)); + + pathConstraints = new ExposedList(skeleton.pathConstraints.Count); + foreach (PathConstraint pathConstraint in skeleton.pathConstraints) + pathConstraints.Add(new PathConstraint(pathConstraint, skeleton)); + + physicsConstraints = new ExposedList(skeleton.physicsConstraints.Count); + foreach (PhysicsConstraint physicsConstraint in skeleton.physicsConstraints) + physicsConstraints.Add(new PhysicsConstraint(physicsConstraint, skeleton)); + + skin = skeleton.skin; + r = skeleton.r; + g = skeleton.g; + b = skeleton.b; + a = skeleton.a; + x = skeleton.x; + y = skeleton.y; + scaleX = skeleton.scaleX; + scaleY = skeleton.scaleY; + time = skeleton.time; + + UpdateCache(); + } + + /// Caches information about bones and constraints. Must be called if the is modified or if bones, constraints, or + /// constraints, or weighted path attachments are added or removed. + public void UpdateCache () { + ExposedList updateCache = this.updateCache; + updateCache.Clear(); + + int boneCount = this.bones.Count; + Bone[] bones = this.bones.Items; + for (int i = 0; i < boneCount; i++) { + Bone bone = bones[i]; + bone.sorted = bone.data.skinRequired; + bone.active = !bone.sorted; + } + if (skin != null) { + BoneData[] skinBones = skin.bones.Items; + for (int i = 0, n = skin.bones.Count; i < n; i++) { + Bone bone = bones[skinBones[i].index]; + do { + bone.sorted = false; + bone.active = true; + bone = bone.parent; + } while (bone != null); + } + } + + int ikCount = this.ikConstraints.Count, transformCount = this.transformConstraints.Count, pathCount = this.pathConstraints.Count, + physicsCount = this.physicsConstraints.Count; + IkConstraint[] ikConstraints = this.ikConstraints.Items; + TransformConstraint[] transformConstraints = this.transformConstraints.Items; + PathConstraint[] pathConstraints = this.pathConstraints.Items; + PhysicsConstraint[] physicsConstraints = this.physicsConstraints.Items; + int constraintCount = ikCount + transformCount + pathCount + physicsCount; + for (int i = 0; i < constraintCount; i++) { + for (int ii = 0; ii < ikCount; ii++) { + IkConstraint constraint = ikConstraints[ii]; + if (constraint.data.order == i) { + SortIkConstraint(constraint); + goto continue_outer; + } + } + for (int ii = 0; ii < transformCount; ii++) { + TransformConstraint constraint = transformConstraints[ii]; + if (constraint.data.order == i) { + SortTransformConstraint(constraint); + goto continue_outer; + } + } + for (int ii = 0; ii < pathCount; ii++) { + PathConstraint constraint = pathConstraints[ii]; + if (constraint.data.order == i) { + SortPathConstraint(constraint); + goto continue_outer; + } + } + for (int ii = 0; ii < physicsCount; ii++) { + PhysicsConstraint constraint = physicsConstraints[ii]; + if (constraint.data.order == i) { + SortPhysicsConstraint(constraint); + goto continue_outer; + } + } + continue_outer: { } + } + + for (int i = 0; i < boneCount; i++) + SortBone(bones[i]); + } + + private void SortIkConstraint (IkConstraint constraint) { + constraint.active = constraint.target.active + && (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data))); + if (!constraint.active) return; + + Bone target = constraint.target; + SortBone(target); + + ExposedList constrained = constraint.bones; + Bone parent = constrained.Items[0]; + SortBone(parent); + + if (constrained.Count == 1) { + updateCache.Add(constraint); + SortReset(parent.children); + } else { + Bone child = constrained.Items[constrained.Count - 1]; + SortBone(child); + + updateCache.Add(constraint); + + SortReset(parent.children); + child.sorted = true; + } + } + + private void SortTransformConstraint (TransformConstraint constraint) { + constraint.active = constraint.target.active + && (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data))); + if (!constraint.active) return; + + SortBone(constraint.target); + + Bone[] constrained = constraint.bones.Items; + int boneCount = constraint.bones.Count; + if (constraint.data.local) { + for (int i = 0; i < boneCount; i++) { + Bone child = constrained[i]; + SortBone(child.parent); + SortBone(child); + } + } else { + for (int i = 0; i < boneCount; i++) + SortBone(constrained[i]); + } + + updateCache.Add(constraint); + + for (int i = 0; i < boneCount; i++) + SortReset(constrained[i].children); + for (int i = 0; i < boneCount; i++) + constrained[i].sorted = true; + } + + private void SortPathConstraint (PathConstraint constraint) { + constraint.active = constraint.target.bone.active + && (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data))); + if (!constraint.active) return; + + Slot slot = constraint.target; + int slotIndex = slot.data.index; + Bone slotBone = slot.bone; + if (skin != null) SortPathConstraintAttachment(skin, slotIndex, slotBone); + if (data.defaultSkin != null && data.defaultSkin != skin) + SortPathConstraintAttachment(data.defaultSkin, slotIndex, slotBone); + + Attachment attachment = slot.attachment; + if (attachment is PathAttachment) SortPathConstraintAttachment(attachment, slotBone); + + Bone[] constrained = constraint.bones.Items; + int boneCount = constraint.bones.Count; + for (int i = 0; i < boneCount; i++) + SortBone(constrained[i]); + + updateCache.Add(constraint); + + for (int i = 0; i < boneCount; i++) + SortReset(constrained[i].children); + for (int i = 0; i < boneCount; i++) + constrained[i].sorted = true; + } + + private void SortPathConstraintAttachment (Skin skin, int slotIndex, Bone slotBone) { + foreach (Skin.SkinEntry entry in skin.Attachments) + if (entry.SlotIndex == slotIndex) SortPathConstraintAttachment(entry.Attachment, slotBone); + } + + private void SortPathConstraintAttachment (Attachment attachment, Bone slotBone) { + if (!(attachment is PathAttachment)) return; + int[] pathBones = ((PathAttachment)attachment).bones; + if (pathBones == null) + SortBone(slotBone); + else { + Bone[] bones = this.bones.Items; + for (int i = 0, n = pathBones.Length; i < n;) { + int nn = pathBones[i++]; + nn += i; + while (i < nn) + SortBone(bones[pathBones[i++]]); + } + } + } + + private void SortPhysicsConstraint (PhysicsConstraint constraint) { + Bone bone = constraint.bone; + constraint.active = bone.active + && (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data))); + if (!constraint.active) return; + + SortBone(bone); + + updateCache.Add(constraint); + + SortReset(bone.children); + bone.sorted = true; + } + + private void SortBone (Bone bone) { + if (bone.sorted) return; + Bone parent = bone.parent; + if (parent != null) SortBone(parent); + bone.sorted = true; + updateCache.Add(bone); + } + + private static void SortReset (ExposedList bones) { + Bone[] bonesItems = bones.Items; + for (int i = 0, n = bones.Count; i < n; i++) { + Bone bone = bonesItems[i]; + if (!bone.active) continue; + if (bone.sorted) SortReset(bone.children); + bone.sorted = false; + } + } + + /// + /// Updates the world transform for each bone and applies all constraints. + /// + /// See World transforms in the Spine + /// Runtimes Guide. + /// + public void UpdateWorldTransform (Physics physics) { + Bone[] bones = this.bones.Items; + for (int i = 0, n = this.bones.Count; 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; + } + + IUpdatable[] updateCache = this.updateCache.Items; + for (int i = 0, n = this.updateCache.Count; i < n; i++) + updateCache[i].Update(physics); + } + + /// + /// Temporarily sets the root bone as a child of the specified bone, then updates the world transform for each bone and applies + /// all constraints. + /// + public void UpdateWorldTransform (Physics physics, Bone parent) { + if (parent == null) throw new ArgumentNullException("parent", "parent cannot be null."); + + // Apply the parent bone transform to the root bone. The root bone always inherits scale, rotation and reflection. + Bone rootBone = this.RootBone; + 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) * MathUtils.DegRad; + float ry = (rootBone.rotation + 90 + rootBone.shearY) * MathUtils.DegRad; + float la = (float)Math.Cos(rx) * rootBone.scaleX; + float lb = (float)Math.Cos(ry) * rootBone.scaleY; + float lc = (float)Math.Sin(rx) * rootBone.scaleX; + float ld = (float)Math.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. + IUpdatable[] updateCache = this.updateCache.Items; + for (int i = 0, n = this.updateCache.Count; i < n; i++) { + IUpdatable updatable = updateCache[i]; + if (updatable != rootBone) updatable.Update(physics); + } + } + + /// + /// Calls for each physics constraint. + /// + public void PhysicsTranslate (float x, float y) { + PhysicsConstraint[] physicsConstraints = this.physicsConstraints.Items; + for (int i = 0, n = this.physicsConstraints.Count; i < n; i++) + physicsConstraints[i].Translate(x, y); + } + + /// + /// Calls for each physics constraint. + /// + public void PhysicsRotate (float x, float y, float degrees) { + PhysicsConstraint[] physicsConstraints = this.physicsConstraints.Items; + for (int i = 0, n = this.physicsConstraints.Count; i < n; i++) + physicsConstraints[i].Rotate(x, y, degrees); + } + + /// Increments the skeleton's . + public void Update (float delta) { + time += delta; + } + + /// Sets the bones, constraints, and slots to their setup pose values. + public void SetToSetupPose () { + SetBonesToSetupPose(); + SetSlotsToSetupPose(); + } + + /// Sets the bones and constraints to their setup pose values. + public void SetBonesToSetupPose () { + Bone[] bones = this.bones.Items; + for (int i = 0, n = this.bones.Count; i < n; i++) + bones[i].SetToSetupPose(); + + IkConstraint[] ikConstraints = this.ikConstraints.Items; + for (int i = 0, n = this.ikConstraints.Count; i < n; i++) + ikConstraints[i].SetToSetupPose(); + + TransformConstraint[] transformConstraints = this.transformConstraints.Items; + for (int i = 0, n = this.transformConstraints.Count; i < n; i++) + transformConstraints[i].SetToSetupPose(); + + PathConstraint[] pathConstraints = this.pathConstraints.Items; + for (int i = 0, n = this.pathConstraints.Count; i < n; i++) + pathConstraints[i].SetToSetupPose(); + + PhysicsConstraint[] physicsConstraints = this.physicsConstraints.Items; + for (int i = 0, n = this.physicsConstraints.Count; i < n; i++) + physicsConstraints[i].SetToSetupPose(); + } + + public void SetSlotsToSetupPose () { + Slot[] slots = this.slots.Items; + int n = this.slots.Count; + Array.Copy(slots, 0, drawOrder.Items, 0, n); + for (int i = 0; i < n; i++) + slots[i].SetToSetupPose(); + } + + /// Finds a bone by comparing each bone's name. It is more efficient to cache the results of this method than to call it + /// repeatedly. + /// May be null. + public Bone FindBone (string boneName) { + if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); + Bone[] bones = this.bones.Items; + for (int i = 0, n = this.bones.Count; i < n; i++) { + Bone bone = bones[i]; + if (bone.data.name == boneName) return bone; + } + return null; + } + + /// Finds a slot by comparing each slot's name. It is more efficient to cache the results of this method than to call it + /// repeatedly. + /// May be null. + public Slot FindSlot (string slotName) { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + Slot[] slots = this.slots.Items; + for (int i = 0, n = this.slots.Count; i < n; i++) { + Slot slot = slots[i]; + if (slot.data.name == slotName) return slot; + } + return null; + } + + /// Sets a skin by name (see ). + public void SetSkin (string skinName) { + Skin foundSkin = data.FindSkin(skinName); + if (foundSkin == null) throw new ArgumentException("Skin not found: " + skinName, "skinName"); + SetSkin(foundSkin); + } + + /// + /// Sets the skin used to look up attachments before looking in the . If the + /// skin is changed, is called. + /// + /// 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 + /// . + /// Also, often 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. + /// + /// May be null. + public void SetSkin (Skin newSkin) { + if (newSkin == skin) return; + if (newSkin != null) { + if (skin != null) + newSkin.AttachAll(this, skin); + else { + Slot[] slots = this.slots.Items; + for (int i = 0, n = this.slots.Count; i < n; i++) { + Slot slot = slots[i]; + string name = slot.data.attachmentName; + if (name != null) { + Attachment attachment = newSkin.GetAttachment(i, name); + if (attachment != null) slot.Attachment = attachment; + } + } + } + } + skin = newSkin; + UpdateCache(); + } + + /// Finds an attachment by looking in the and using the slot name and attachment name. + /// May be null. + public Attachment GetAttachment (string slotName, string attachmentName) { + return GetAttachment(data.FindSlot(slotName).index, attachmentName); + } + + /// Finds an attachment by looking in the skin and skeletonData.defaultSkin using the slot index and attachment name.First the skin is checked and if the attachment was not found, the default skin is checked. + /// May be null. + public Attachment GetAttachment (int slotIndex, string attachmentName) { + if (attachmentName == null) throw new ArgumentNullException("attachmentName", "attachmentName cannot be null."); + if (skin != null) { + Attachment attachment = skin.GetAttachment(slotIndex, attachmentName); + if (attachment != null) return attachment; + } + return data.defaultSkin != null ? data.defaultSkin.GetAttachment(slotIndex, attachmentName) : null; + } + + /// A convenience method to set an attachment by finding the slot with FindSlot, finding the attachment with GetAttachment, then setting the slot's slot.Attachment. + /// May be null to clear the slot's attachment. + public void SetAttachment (string slotName, string attachmentName) { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + Slot[] slots = this.slots.Items; + for (int i = 0, n = this.slots.Count; i < n; i++) { + Slot slot = slots[i]; + if (slot.data.name == slotName) { + Attachment attachment = null; + if (attachmentName != null) { + attachment = GetAttachment(i, attachmentName); + if (attachment == null) throw new Exception("Attachment not found: " + attachmentName + ", for slot: " + slotName); + } + slot.Attachment = attachment; + return; + } + } + throw new Exception("Slot not found: " + slotName); + } + + /// Finds an IK constraint by comparing each IK constraint's name. It is more efficient to cache the results of this method + /// than to call it repeatedly. + /// May be null. + public IkConstraint FindIkConstraint (string constraintName) { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + IkConstraint[] ikConstraints = this.ikConstraints.Items; + for (int i = 0, n = this.ikConstraints.Count; i < n; i++) { + IkConstraint ikConstraint = ikConstraints[i]; + if (ikConstraint.data.name == constraintName) return ikConstraint; + } + return null; + } + + /// Finds a transform constraint by comparing each transform constraint's name. It is more efficient to cache the results of + /// this method than to call it repeatedly. + /// May be null. + public TransformConstraint FindTransformConstraint (string constraintName) { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + TransformConstraint[] transformConstraints = this.transformConstraints.Items; + for (int i = 0, n = this.transformConstraints.Count; i < n; i++) { + TransformConstraint transformConstraint = transformConstraints[i]; + if (transformConstraint.data.Name == constraintName) return transformConstraint; + } + return null; + } + + /// Finds a path constraint by comparing each path constraint's name. It is more efficient to cache the results of this method + /// than to call it repeatedly. + /// May be null. + public PathConstraint FindPathConstraint (string constraintName) { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + PathConstraint[] pathConstraints = this.pathConstraints.Items; + for (int i = 0, n = this.pathConstraints.Count; i < n; i++) { + PathConstraint constraint = pathConstraints[i]; + if (constraint.data.Name.Equals(constraintName)) return constraint; + } + return null; + } + + /// Finds a physics constraint by comparing each physics constraint's name. It is more efficient to cache the results of this + /// method than to call it repeatedly. + /// May be null. + public PhysicsConstraint FindPhysicsConstraint (String constraintName) { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + PhysicsConstraint[] physicsConstraints = this.physicsConstraints.Items; + for (int i = 0, n = this.physicsConstraints.Count; i < n; i++) { + PhysicsConstraint constraint = physicsConstraints[i]; + if (constraint.data.name.Equals(constraintName)) return constraint; + } + return null; + } + + /// Returns the axis aligned bounding box (AABB) of the region and mesh attachments for the current pose. + /// The horizontal distance between the skeleton origin and the left side of the AABB. + /// The vertical distance between the skeleton origin and the bottom side of the AABB. + /// The width of the AABB + /// The height of the AABB. + /// Reference to hold a float[]. May be a null reference. This method will assign it a new float[] with the appropriate size as needed. + public void GetBounds (out float x, out float y, out float width, out float height, ref float[] vertexBuffer, + SkeletonClipping clipper = null) { + + float[] temp = vertexBuffer; + temp = temp ?? new float[8]; + Slot[] drawOrder = this.drawOrder.Items; + float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; + for (int i = 0, n = this.drawOrder.Count; i < n; i++) { + Slot slot = drawOrder[i]; + if (!slot.bone.active) continue; + int verticesLength = 0; + float[] vertices = null; + int[] triangles = null; + Attachment attachment = slot.attachment; + RegionAttachment region = attachment as RegionAttachment; + if (region != null) { + verticesLength = 8; + vertices = temp; + if (vertices.Length < 8) vertices = temp = new float[8]; + region.ComputeWorldVertices(slot, temp, 0, 2); + triangles = quadTriangles; + } else { + MeshAttachment mesh = attachment as MeshAttachment; + if (mesh != null) { + verticesLength = mesh.WorldVerticesLength; + vertices = temp; + if (vertices.Length < verticesLength) vertices = temp = new float[verticesLength]; + mesh.ComputeWorldVertices(slot, 0, verticesLength, temp, 0, 2); + triangles = mesh.Triangles; + } else if (clipper != null) { + ClippingAttachment clip = attachment as ClippingAttachment; + if (clip != null) { + clipper.ClipStart(slot, clip); + continue; + } + } + } + + if (vertices != null) { + if (clipper != null && clipper.IsClipping) { + clipper.ClipTriangles(vertices, triangles, triangles.Length); + vertices = clipper.ClippedVertices.Items; + verticesLength = clipper.ClippedVertices.Count; + } + + for (int ii = 0; ii < verticesLength; ii += 2) { + float vx = vertices[ii], vy = vertices[ii + 1]; + minX = Math.Min(minX, vx); + minY = Math.Min(minY, vy); + maxX = Math.Max(maxX, vx); + maxY = Math.Max(maxY, vy); + } + } + if (clipper != null) clipper.ClipEnd(slot); + } + if (clipper != null) clipper.ClipEnd(); + x = minX; + y = minY; + width = maxX - minX; + height = maxY - minY; + vertexBuffer = temp; + } + + override public string ToString () { + return data.name; + } + + /// Determines how physics and other non-deterministic updates are applied. + public enum Physics { + /// Physics are not updated or applied. + None, + + /// Physics are reset to the current pose. + Reset, + + /// Physics are updated and the pose from physics is applied. + Update, + + /// Physics are not updated but the pose from physics is applied. + Pose + } + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/SkeletonBinary.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/SkeletonBinary.cs new file mode 100644 index 0000000..6271fdc --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/SkeletonBinary.cs @@ -0,0 +1,1379 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +#if (UNITY_5 || UNITY_5_3_OR_NEWER || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1) +#define IS_UNITY +#endif + +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.Serialization; + +#if WINDOWS_STOREAPP +using System.Threading.Tasks; +using Windows.Storage; +#endif + +namespace Spine4_2_33 { + public class SkeletonBinary : SkeletonLoader { + public const int BONE_ROTATE = 0; + public const int BONE_TRANSLATE = 1; + public const int BONE_TRANSLATEX = 2; + public const int BONE_TRANSLATEY = 3; + public const int BONE_SCALE = 4; + public const int BONE_SCALEX = 5; + public const int BONE_SCALEY = 6; + public const int BONE_SHEAR = 7; + public const int BONE_SHEARX = 8; + public const int BONE_SHEARY = 9; + public const int BONE_INHERIT = 10; + + public const int SLOT_ATTACHMENT = 0; + public const int SLOT_RGBA = 1; + public const int SLOT_RGB = 2; + public const int SLOT_RGBA2 = 3; + public const int SLOT_RGB2 = 4; + public const int SLOT_ALPHA = 5; + + public const int ATTACHMENT_DEFORM = 0; + public const int ATTACHMENT_SEQUENCE = 1; + + public const int PATH_POSITION = 0; + public const int PATH_SPACING = 1; + public const int PATH_MIX = 2; + + public const int PHYSICS_INERTIA = 0; + public const int PHYSICS_STRENGTH = 1; + public const int PHYSICS_DAMPING = 2; + public const int PHYSICS_MASS = 4; + public const int PHYSICS_WIND = 5; + public const int PHYSICS_GRAVITY = 6; + public const int PHYSICS_MIX = 7; + public const int PHYSICS_RESET = 8; + + public const int CURVE_LINEAR = 0; + public const int CURVE_STEPPED = 1; + public const int CURVE_BEZIER = 2; + + private readonly List linkedMeshes = new List(); + + public SkeletonBinary (AttachmentLoader attachmentLoader) + : base(attachmentLoader) { + } + + public SkeletonBinary (params Atlas[] atlasArray) + : base(atlasArray) { + } + +#if !ISUNITY && WINDOWS_STOREAPP + private async Task ReadFile(string path) { + var folder = Windows.ApplicationModel.Package.Current.InstalledLocation; + using (BufferedStream input = new BufferedStream(await folder.GetFileAsync(path).AsTask().ConfigureAwait(false))) { + SkeletonData skeletonData = ReadSkeletonData(input); + skeletonData.Name = Path.GetFileNameWithoutExtension(path); + return skeletonData; + } + } + + public override SkeletonData ReadSkeletonData (string path) { + return this.ReadFile(path).Result; + } +#else + public override SkeletonData ReadSkeletonData (string path) { +#if WINDOWS_PHONE + using (BufferedStream input = new BufferedStream(Microsoft.Xna.Framework.TitleContainer.OpenStream(path))) { +#else + using (FileStream input = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) { +#endif + SkeletonData skeletonData = ReadSkeletonData(input); + skeletonData.name = Path.GetFileNameWithoutExtension(path); + return skeletonData; + } + } +#endif // WINDOWS_STOREAPP + + /// Returns the version string of binary skeleton data. + public static string GetVersionString (Stream file) { + if (file == null) throw new ArgumentNullException("file"); + + SkeletonInput input = new SkeletonInput(file); + return input.GetVersionString(); + } + + public SkeletonData ReadSkeletonData (Stream file) { + if (file == null) throw new ArgumentNullException("file"); + float scale = this.scale; + + SkeletonData skeletonData = new SkeletonData(); + SkeletonInput input = new SkeletonInput(file); + + long hash = input.ReadLong(); + skeletonData.hash = hash == 0 ? null : hash.ToString(); + skeletonData.version = input.ReadString(); + if (skeletonData.version.Length == 0) skeletonData.version = null; + // early return for old 3.8 format instead of reading past the end + if (skeletonData.version.Length > 13) return null; + skeletonData.x = input.ReadFloat(); + skeletonData.y = input.ReadFloat(); + skeletonData.width = input.ReadFloat(); + skeletonData.height = input.ReadFloat(); + skeletonData.referenceScale = input.ReadFloat() * scale; + + bool nonessential = input.ReadBoolean(); + + if (nonessential) { + skeletonData.fps = input.ReadFloat(); + + skeletonData.imagesPath = input.ReadString(); + if (string.IsNullOrEmpty(skeletonData.imagesPath)) skeletonData.imagesPath = null; + + skeletonData.audioPath = input.ReadString(); + if (string.IsNullOrEmpty(skeletonData.audioPath)) skeletonData.audioPath = null; + } + + int n; + Object[] o; + + // Strings. + o = input.strings = new String[n = input.ReadInt(true)]; + for (int i = 0; i < n; i++) + o[i] = input.ReadString(); + + // Bones. + BoneData[] bones = skeletonData.bones.Resize(n = input.ReadInt(true)).Items; + for (int i = 0; i < n; i++) { + String name = input.ReadString(); + BoneData parent = i == 0 ? null : bones[input.ReadInt(true)]; + BoneData data = new BoneData(i, name, parent); + data.rotation = input.ReadFloat(); + data.x = input.ReadFloat() * scale; + data.y = input.ReadFloat() * scale; + data.scaleX = input.ReadFloat(); + data.scaleY = input.ReadFloat(); + data.shearX = input.ReadFloat(); + data.shearY = input.ReadFloat(); + data.Length = input.ReadFloat() * scale; + data.inherit = InheritEnum.Values[input.ReadInt(true)]; + data.skinRequired = input.ReadBoolean(); + if (nonessential) { // discard non-essential data + input.ReadInt(); // Color.rgba8888ToColor(data.color, input.readInt()); + input.ReadString(); // data.icon = input.readString(); + input.ReadBoolean(); // data.visible = input.readBoolean(); + } + bones[i] = data; + } + + // Slots. + SlotData[] slots = skeletonData.slots.Resize(n = input.ReadInt(true)).Items; + for (int i = 0; i < n; i++) { + String slotName = input.ReadString(); + + BoneData boneData = bones[input.ReadInt(true)]; + SlotData slotData = new SlotData(i, slotName, boneData); + int color = input.ReadInt(); + slotData.r = ((color & 0xff000000) >> 24) / 255f; + slotData.g = ((color & 0x00ff0000) >> 16) / 255f; + slotData.b = ((color & 0x0000ff00) >> 8) / 255f; + slotData.a = ((color & 0x000000ff)) / 255f; + + int darkColor = input.ReadInt(); // 0x00rrggbb + if (darkColor != -1) { + slotData.hasSecondColor = true; + slotData.r2 = ((darkColor & 0x00ff0000) >> 16) / 255f; + slotData.g2 = ((darkColor & 0x0000ff00) >> 8) / 255f; + slotData.b2 = ((darkColor & 0x000000ff)) / 255f; + } + + slotData.attachmentName = input.ReadStringRef(); + slotData.blendMode = (BlendMode)input.ReadInt(true); + if (nonessential) { + input.ReadBoolean(); // data.visible = input.readBoolean(); data.path = path; + } + slots[i] = slotData; + } + + // IK constraints. + o = skeletonData.ikConstraints.Resize(n = input.ReadInt(true)).Items; + for (int i = 0, nn; i < n; i++) { + IkConstraintData data = new IkConstraintData(input.ReadString()); + data.order = input.ReadInt(true); + BoneData[] constraintBones = data.bones.Resize(nn = input.ReadInt(true)).Items; + for (int ii = 0; ii < nn; ii++) + constraintBones[ii] = bones[input.ReadInt(true)]; + data.target = bones[input.ReadInt(true)]; + int flags = input.Read(); + 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 ? input.ReadFloat() : 1; + if ((flags & 128) != 0) data.softness = input.ReadFloat() * scale; + o[i] = data; + } + + // Transform constraints. + o = skeletonData.transformConstraints.Resize(n = input.ReadInt(true)).Items; + for (int i = 0, nn; i < n; i++) { + TransformConstraintData data = new TransformConstraintData(input.ReadString()); + data.order = input.ReadInt(true); + BoneData[] constraintBones = data.bones.Resize(nn = input.ReadInt(true)).Items; + for (int ii = 0; ii < nn; ii++) + constraintBones[ii] = bones[input.ReadInt(true)]; + data.target = bones[input.ReadInt(true)]; + int flags = input.Read(); + data.skinRequired = (flags & 1) != 0; + data.local = (flags & 2) != 0; + data.relative = (flags & 4) != 0; + if ((flags & 8) != 0) data.offsetRotation = input.ReadFloat(); + if ((flags & 16) != 0) data.offsetX = input.ReadFloat() * scale; + if ((flags & 32) != 0) data.offsetY = input.ReadFloat() * scale; + if ((flags & 64) != 0) data.offsetScaleX = input.ReadFloat(); + if ((flags & 128) != 0) data.offsetScaleY = input.ReadFloat(); + flags = input.Read(); + if ((flags & 1) != 0) data.offsetShearY = input.ReadFloat(); + if ((flags & 2) != 0) data.mixRotate = input.ReadFloat(); + if ((flags & 4) != 0) data.mixX = input.ReadFloat(); + if ((flags & 8) != 0) data.mixY = input.ReadFloat(); + if ((flags & 16) != 0) data.mixScaleX = input.ReadFloat(); + if ((flags & 32) != 0) data.mixScaleY = input.ReadFloat(); + if ((flags & 64) != 0) data.mixShearY = input.ReadFloat(); + o[i] = data; + } + + // Path constraints + o = skeletonData.pathConstraints.Resize(n = input.ReadInt(true)).Items; + for (int i = 0, nn; i < n; i++) { + PathConstraintData data = new PathConstraintData(input.ReadString()); + data.order = input.ReadInt(true); + data.skinRequired = input.ReadBoolean(); + BoneData[] constraintBones = data.bones.Resize(nn = input.ReadInt(true)).Items; + for (int ii = 0; ii < nn; ii++) + constraintBones[ii] = bones[input.ReadInt(true)]; + data.target = slots[input.ReadInt(true)]; + int flags = input.Read(); + data.positionMode = (PositionMode)Enum.GetValues(typeof(PositionMode)).GetValue(flags & 1); + data.spacingMode = (SpacingMode)Enum.GetValues(typeof(SpacingMode)).GetValue((flags >> 1) & 3); + data.rotateMode = (RotateMode)Enum.GetValues(typeof(RotateMode)).GetValue((flags >> 3) & 3); + if ((flags & 128) != 0) data.offsetRotation = input.ReadFloat(); + + data.position = input.ReadFloat(); + if (data.positionMode == PositionMode.Fixed) data.position *= scale; + data.spacing = input.ReadFloat(); + if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale; + data.mixRotate = input.ReadFloat(); + data.mixX = input.ReadFloat(); + data.mixY = input.ReadFloat(); + o[i] = data; + } + + // Physics constraints. + o = skeletonData.physicsConstraints.Resize(n = input.ReadInt(true)).Items; + for (int i = 0; i < n; i++) { + PhysicsConstraintData data = new PhysicsConstraintData(input.ReadString()); + data.order = input.ReadInt(true); + data.bone = bones[input.ReadInt(true)]; + int flags = input.Read(); + data.skinRequired = (flags & 1) != 0; + if ((flags & 2) != 0) data.x = input.ReadFloat(); + if ((flags & 4) != 0) data.y = input.ReadFloat(); + if ((flags & 8) != 0) data.rotate = input.ReadFloat(); + if ((flags & 16) != 0) data.scaleX = input.ReadFloat(); + if ((flags & 32) != 0) data.shearX = input.ReadFloat(); + data.limit = ((flags & 64) != 0 ? input.ReadFloat() : 5000) * scale; + data.step = 1f / input.ReadUByte(); + data.inertia = input.ReadFloat(); + data.strength = input.ReadFloat(); + data.damping = input.ReadFloat(); + data.massInverse = (flags & 128) != 0 ? input.ReadFloat() : 1; + data.wind = input.ReadFloat(); + data.gravity = input.ReadFloat(); + flags = input.Read(); + 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 ? input.ReadFloat() : 1; + o[i] = data; + } + + // Default skin. + Skin defaultSkin = ReadSkin(input, skeletonData, true, nonessential); + if (defaultSkin != null) { + skeletonData.defaultSkin = defaultSkin; + skeletonData.skins.Add(defaultSkin); + } + + // Skins. + { + int i = skeletonData.skins.Count; + o = skeletonData.skins.Resize(n = i + input.ReadInt(true)).Items; + for (; i < n; i++) + o[i] = ReadSkin(input, skeletonData, false, nonessential); + } + + // Linked meshes. + n = linkedMeshes.Count; + for (int i = 0; i < n; i++) { + LinkedMesh linkedMesh = linkedMeshes[i]; + Skin skin = skeletonData.skins.Items[linkedMesh.skinIndex]; + Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); + if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); + linkedMesh.mesh.TimelineAttachment = linkedMesh.inheritTimelines ? (VertexAttachment)parent : linkedMesh.mesh; + linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; + if (linkedMesh.mesh.Sequence == null) linkedMesh.mesh.UpdateRegion(); + } + linkedMeshes.Clear(); + + // Events. + o = skeletonData.events.Resize(n = input.ReadInt(true)).Items; + for (int i = 0; i < n; i++) { + EventData data = new EventData(input.ReadString()); + data.Int = input.ReadInt(false); + data.Float = input.ReadFloat(); + data.String = input.ReadString(); + data.AudioPath = input.ReadString(); + if (data.AudioPath != null) { + data.Volume = input.ReadFloat(); + data.Balance = input.ReadFloat(); + } + o[i] = data; + } + + // Animations. + o = skeletonData.animations.Resize(n = input.ReadInt(true)).Items; + for (int i = 0; i < n; i++) + o[i] = ReadAnimation(input.ReadString(), input, skeletonData); + + return skeletonData; + } + + /// May be null. + private Skin ReadSkin (SkeletonInput input, SkeletonData skeletonData, bool defaultSkin, bool nonessential) { + + Skin skin; + int slotCount; + + if (defaultSkin) { + slotCount = input.ReadInt(true); + if (slotCount == 0) return null; + skin = new Skin("default"); + } else { + skin = new Skin(input.ReadString()); + + if (nonessential) input.ReadInt(); // discard, Color.rgba8888ToColor(skin.color, input.readInt()); + + Object[] bones = skin.bones.Resize(input.ReadInt(true)).Items; + BoneData[] bonesItems = skeletonData.bones.Items; + for (int i = 0, n = skin.bones.Count; i < n; i++) + bones[i] = bonesItems[input.ReadInt(true)]; + + IkConstraintData[] ikConstraintsItems = skeletonData.ikConstraints.Items; + for (int i = 0, n = input.ReadInt(true); i < n; i++) + skin.constraints.Add(ikConstraintsItems[input.ReadInt(true)]); + TransformConstraintData[] transformConstraintsItems = skeletonData.transformConstraints.Items; + for (int i = 0, n = input.ReadInt(true); i < n; i++) + skin.constraints.Add(transformConstraintsItems[input.ReadInt(true)]); + PathConstraintData[] pathConstraintsItems = skeletonData.pathConstraints.Items; + for (int i = 0, n = input.ReadInt(true); i < n; i++) + skin.constraints.Add(pathConstraintsItems[input.ReadInt(true)]); + PhysicsConstraintData[] physicsConstraintsItems = skeletonData.physicsConstraints.Items; + for (int i = 0, n = input.ReadInt(true); i < n; i++) + skin.constraints.Add(physicsConstraintsItems[input.ReadInt(true)]); + skin.constraints.TrimExcess(); + + slotCount = input.ReadInt(true); + } + for (int i = 0; i < slotCount; i++) { + int slotIndex = input.ReadInt(true); + for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) { + String name = input.ReadStringRef(); + Attachment attachment = ReadAttachment(input, skeletonData, skin, slotIndex, name, nonessential); + if (attachment != null) skin.SetAttachment(slotIndex, name, attachment); + } + } + return skin; + } + + private Attachment ReadAttachment (SkeletonInput input, SkeletonData skeletonData, Skin skin, int slotIndex, + String attachmentName, bool nonessential) { + float scale = this.scale; + + int flags = input.ReadUByte(); + string name = (flags & 8) != 0 ? input.ReadStringRef() : attachmentName; + + switch ((AttachmentType)(flags & 0x7)) { // 0b111 + case AttachmentType.Region: { + string path = (flags & 16) != 0 ? input.ReadStringRef() : null; + uint color = (flags & 32) != 0 ? (uint)input.ReadInt() : 0xffffffff; + Sequence sequence = (flags & 64) != 0 ? ReadSequence(input) : null; + float rotation = (flags & 128) != 0 ? input.ReadFloat() : 0; + float x = input.ReadFloat(); + float y = input.ReadFloat(); + float scaleX = input.ReadFloat(); + float scaleY = input.ReadFloat(); + float width = input.ReadFloat(); + float height = input.ReadFloat(); + + if (path == null) path = name; + RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path, sequence); + if (region == null) return null; + region.Path = path; + region.x = x * scale; + region.y = y * scale; + region.scaleX = scaleX; + region.scaleY = scaleY; + region.rotation = rotation; + region.width = width * scale; + region.height = height * scale; + region.r = ((color & 0xff000000) >> 24) / 255f; + region.g = ((color & 0x00ff0000) >> 16) / 255f; + region.b = ((color & 0x0000ff00) >> 8) / 255f; + region.a = ((color & 0x000000ff)) / 255f; + region.sequence = sequence; + if (sequence == null) region.UpdateRegion(); + return region; + } + case AttachmentType.Boundingbox: { + Vertices vertices = ReadVertices(input, (flags & 16) != 0); + if (nonessential) input.ReadInt(); // discard, int color = nonessential ? input.readInt() : 0; + + BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); + if (box == null) return null; + box.worldVerticesLength = vertices.length; + box.vertices = vertices.vertices; + box.bones = vertices.bones; + // skipped porting: if (nonessential) Color.rgba8888ToColor(box.getColor(), color); + return box; + } + case AttachmentType.Mesh: { + string path = (flags & 16) != 0 ? input.ReadStringRef() : name; + uint color = (flags & 32) != 0 ? (uint)input.ReadInt() : 0xffffffff; + Sequence sequence = (flags & 64) != 0 ? ReadSequence(input) : null; + int hullLength = input.ReadInt(true); + Vertices vertices = ReadVertices(input, (flags & 128) != 0); + float[] uvs = ReadFloatArray(input, vertices.length, 1); + int[] triangles = ReadShortArray(input, (vertices.length - hullLength - 2) * 3); + + int[] edges = null; + float width = 0, height = 0; + if (nonessential) { + edges = ReadShortArray(input, input.ReadInt(true)); + width = input.ReadFloat(); + height = input.ReadFloat(); + } + + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path, sequence); + if (mesh == null) return null; + mesh.Path = path; + mesh.r = ((color & 0xff000000) >> 24) / 255f; + mesh.g = ((color & 0x00ff0000) >> 16) / 255f; + mesh.b = ((color & 0x0000ff00) >> 8) / 255f; + mesh.a = ((color & 0x000000ff)) / 255f; + mesh.bones = vertices.bones; + mesh.vertices = vertices.vertices; + mesh.WorldVerticesLength = vertices.length; + mesh.triangles = triangles; + mesh.regionUVs = uvs; + if (sequence == null) mesh.UpdateRegion(); + mesh.HullLength = hullLength << 1; + mesh.Sequence = sequence; + if (nonessential) { + mesh.Edges = edges; + mesh.Width = width * scale; + mesh.Height = height * scale; + } + return mesh; + } + case AttachmentType.Linkedmesh: { + String path = (flags & 16) != 0 ? input.ReadStringRef() : name; + uint color = (flags & 32) != 0 ? (uint)input.ReadInt() : 0xffffffff; + Sequence sequence = (flags & 64) != 0 ? ReadSequence(input) : null; + bool inheritTimelines = (flags & 128) != 0; + int skinIndex = input.ReadInt(true); + string parent = input.ReadStringRef(); + float width = 0, height = 0; + if (nonessential) { + width = input.ReadFloat(); + height = input.ReadFloat(); + } + + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path, sequence); + if (mesh == null) return null; + mesh.Path = path; + mesh.r = ((color & 0xff000000) >> 24) / 255f; + mesh.g = ((color & 0x00ff0000) >> 16) / 255f; + mesh.b = ((color & 0x0000ff00) >> 8) / 255f; + mesh.a = ((color & 0x000000ff)) / 255f; + mesh.Sequence = sequence; + if (nonessential) { + mesh.Width = width * scale; + mesh.Height = height * scale; + } + linkedMeshes.Add(new LinkedMesh(mesh, skinIndex, slotIndex, parent, inheritTimelines)); + return mesh; + } + case AttachmentType.Path: { + bool closed = (flags & 16) != 0; + bool constantSpeed = (flags & 32) != 0; + Vertices vertices = ReadVertices(input, (flags & 64) != 0); + float[] lengths = new float[vertices.length / 6]; + for (int i = 0, n = lengths.Length; i < n; i++) + lengths[i] = input.ReadFloat() * scale; + if (nonessential) input.ReadInt(); //int color = nonessential ? input.ReadInt() : 0; + + PathAttachment path = attachmentLoader.NewPathAttachment(skin, name); + if (path == null) return null; + path.closed = closed; + path.constantSpeed = constantSpeed; + path.worldVerticesLength = vertices.length; + path.vertices = vertices.vertices; + path.bones = vertices.bones; + path.lengths = lengths; + // skipped porting: if (nonessential) Color.rgba8888ToColor(path.getColor(), color); + return path; + } + case AttachmentType.Point: { + float rotation = input.ReadFloat(); + float x = input.ReadFloat(); + float y = input.ReadFloat(); + if (nonessential) input.ReadInt(); //int color = nonessential ? input.ReadInt() : 0; + + PointAttachment point = attachmentLoader.NewPointAttachment(skin, name); + if (point == null) return null; + point.x = x * scale; + point.y = y * scale; + point.rotation = rotation; + // skipped porting: if (nonessential) point.color = color; + return point; + } + case AttachmentType.Clipping: { + int endSlotIndex = input.ReadInt(true); + Vertices vertices = ReadVertices(input, (flags & 16) != 0); + if (nonessential) input.ReadInt(); + + ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name); + if (clip == null) return null; + clip.EndSlot = skeletonData.slots.Items[endSlotIndex]; + clip.worldVerticesLength = vertices.length; + clip.vertices = vertices.vertices; + clip.bones = vertices.bones; + // skipped porting: if (nonessential) Color.rgba8888ToColor(clip.getColor(), color); + return clip; + } + } + return null; + } + + private Sequence ReadSequence (SkeletonInput input) { + Sequence sequence = new Sequence(input.ReadInt(true)); + sequence.Start = input.ReadInt(true); + sequence.Digits = input.ReadInt(true); + sequence.SetupIndex = input.ReadInt(true); + return sequence; + } + + private Vertices ReadVertices (SkeletonInput input, bool weighted) { + float scale = this.scale; + int vertexCount = input.ReadInt(true); + Vertices vertices = new Vertices(); + vertices.length = vertexCount << 1; + if (!weighted) { + vertices.vertices = ReadFloatArray(input, vertices.length, scale); + return vertices; + } + ExposedList weights = new ExposedList(vertices.length * 3 * 3); + ExposedList bonesArray = new ExposedList(vertices.length * 3); + for (int i = 0; i < vertexCount; i++) { + int boneCount = input.ReadInt(true); + bonesArray.Add(boneCount); + for (int ii = 0; ii < boneCount; ii++) { + bonesArray.Add(input.ReadInt(true)); + weights.Add(input.ReadFloat() * scale); + weights.Add(input.ReadFloat() * scale); + weights.Add(input.ReadFloat()); + } + } + + vertices.vertices = weights.ToArray(); + vertices.bones = bonesArray.ToArray(); + return vertices; + } + + private float[] ReadFloatArray (SkeletonInput input, int n, float scale) { + float[] array = new float[n]; + if (scale == 1) { + for (int i = 0; i < n; i++) + array[i] = input.ReadFloat(); + } else { + for (int i = 0; i < n; i++) + array[i] = input.ReadFloat() * scale; + } + return array; + } + + private int[] ReadShortArray (SkeletonInput input, int n) { + int[] array = new int[n]; + for (int i = 0; i < n; i++) + array[i] = input.ReadInt(true); + return array; + } + + /// SerializationException will be thrown when a Vertex attachment is not found. + /// Throws IOException when a read operation fails. + private Animation ReadAnimation (String name, SkeletonInput input, SkeletonData skeletonData) { + ExposedList timelines = new ExposedList(input.ReadInt(true)); + float scale = this.scale; + + // Slot timelines. + for (int i = 0, n = input.ReadInt(true); i < n; i++) { + int slotIndex = input.ReadInt(true); + for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) { + int timelineType = input.ReadUByte(), frameCount = input.ReadInt(true), frameLast = frameCount - 1; + switch (timelineType) { + case SLOT_ATTACHMENT: { + AttachmentTimeline timeline = new AttachmentTimeline(frameCount, slotIndex); + for (int frame = 0; frame < frameCount; frame++) + timeline.SetFrame(frame, input.ReadFloat(), input.ReadStringRef()); + timelines.Add(timeline); + break; + } + case SLOT_RGBA: { + RGBATimeline timeline = new RGBATimeline(frameCount, input.ReadInt(true), slotIndex); + float time = input.ReadFloat(); + float r = input.Read() / 255f, g = input.Read() / 255f; + float b = input.Read() / 255f, a = input.Read() / 255f; + for (int frame = 0, bezier = 0; ; frame++) { + timeline.SetFrame(frame, time, r, g, b, a); + if (frame == frameLast) break; + float time2 = input.ReadFloat(); + float r2 = input.Read() / 255f, g2 = input.Read() / 255f; + float b2 = input.Read() / 255f, a2 = input.Read() / 255f; + switch (input.ReadUByte()) { + 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); + break; + } + time = time2; + r = r2; + g = g2; + b = b2; + a = a2; + } + timelines.Add(timeline); + break; + } + case SLOT_RGB: { + RGBTimeline timeline = new RGBTimeline(frameCount, input.ReadInt(true), slotIndex); + float time = input.ReadFloat(); + float r = input.Read() / 255f, g = input.Read() / 255f, b = input.Read() / 255f; + for (int frame = 0, bezier = 0; ; frame++) { + timeline.SetFrame(frame, time, r, g, b); + if (frame == frameLast) break; + float time2 = input.ReadFloat(); + float r2 = input.Read() / 255f, g2 = input.Read() / 255f, b2 = input.Read() / 255f; + switch (input.ReadUByte()) { + 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); + break; + } + time = time2; + r = r2; + g = g2; + b = b2; + } + timelines.Add(timeline); + break; + } + case SLOT_RGBA2: { + RGBA2Timeline timeline = new RGBA2Timeline(frameCount, input.ReadInt(true), slotIndex); + float time = input.ReadFloat(); + float r = input.Read() / 255f, g = input.Read() / 255f; + float b = input.Read() / 255f, a = input.Read() / 255f; + float r2 = input.Read() / 255f, g2 = input.Read() / 255f, b2 = input.Read() / 255f; + for (int frame = 0, bezier = 0; ; frame++) { + timeline.SetFrame(frame, time, r, g, b, a, r2, g2, b2); + if (frame == frameLast) break; + float time2 = input.ReadFloat(); + float nr = input.Read() / 255f, ng = input.Read() / 255f; + float nb = input.Read() / 255f, na = input.Read() / 255f; + float nr2 = input.Read() / 255f, ng2 = input.Read() / 255f, nb2 = input.Read() / 255f; + switch (input.ReadUByte()) { + 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); + break; + } + time = time2; + r = nr; + g = ng; + b = nb; + a = na; + r2 = nr2; + g2 = ng2; + b2 = nb2; + } + timelines.Add(timeline); + break; + } + case SLOT_RGB2: { + RGB2Timeline timeline = new RGB2Timeline(frameCount, input.ReadInt(true), slotIndex); + float time = input.ReadFloat(); + float r = input.Read() / 255f, g = input.Read() / 255f, b = input.Read() / 255f; + float r2 = input.Read() / 255f, g2 = input.Read() / 255f, b2 = input.Read() / 255f; + for (int frame = 0, bezier = 0; ; frame++) { + timeline.SetFrame(frame, time, r, g, b, r2, g2, b2); + if (frame == frameLast) break; + float time2 = input.ReadFloat(); + float nr = input.Read() / 255f, ng = input.Read() / 255f, nb = input.Read() / 255f; + float nr2 = input.Read() / 255f, ng2 = input.Read() / 255f, nb2 = input.Read() / 255f; + switch (input.ReadUByte()) { + 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); + break; + } + time = time2; + r = nr; + g = ng; + b = nb; + r2 = nr2; + g2 = ng2; + b2 = nb2; + } + timelines.Add(timeline); + break; + } + case SLOT_ALPHA: { + AlphaTimeline timeline = new AlphaTimeline(frameCount, input.ReadInt(true), slotIndex); + float time = input.ReadFloat(), a = input.Read() / 255f; + for (int frame = 0, bezier = 0; ; frame++) { + timeline.SetFrame(frame, time, a); + if (frame == frameLast) break; + float time2 = input.ReadFloat(); + float a2 = input.Read() / 255f; + switch (input.ReadUByte()) { + case CURVE_STEPPED: + timeline.SetStepped(frame); + break; + case CURVE_BEZIER: + SetBezier(input, timeline, bezier++, frame, 0, time, time2, a, a2, 1); + break; + } + time = time2; + a = a2; + } + timelines.Add(timeline); + break; + } + } + } + } + + // Bone timelines. + for (int i = 0, n = input.ReadInt(true); i < n; i++) { + int boneIndex = input.ReadInt(true); + for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) { + int type = input.ReadUByte(), frameCount = input.ReadInt(true); + if (type == BONE_INHERIT) { + InheritTimeline timeline = new InheritTimeline(frameCount, boneIndex); + for (int frame = 0; frame < frameCount; frame++) + timeline.SetFrame(frame, input.ReadFloat(), InheritEnum.Values[input.ReadUByte()]); + timelines.Add(timeline); + continue; + } + int bezierCount = input.ReadInt(true); + switch (type) { + case BONE_ROTATE: + ReadTimeline(input, timelines, new RotateTimeline(frameCount, bezierCount, boneIndex), 1); + break; + case BONE_TRANSLATE: + ReadTimeline(input, timelines, new TranslateTimeline(frameCount, bezierCount, boneIndex), scale); + break; + case BONE_TRANSLATEX: + ReadTimeline(input, timelines, new TranslateXTimeline(frameCount, bezierCount, boneIndex), scale); + break; + case BONE_TRANSLATEY: + ReadTimeline(input, timelines, new TranslateYTimeline(frameCount, bezierCount, boneIndex), scale); + break; + case BONE_SCALE: + ReadTimeline(input, timelines, new ScaleTimeline(frameCount, bezierCount, boneIndex), 1); + break; + case BONE_SCALEX: + ReadTimeline(input, timelines, new ScaleXTimeline(frameCount, bezierCount, boneIndex), 1); + break; + case BONE_SCALEY: + ReadTimeline(input, timelines, new ScaleYTimeline(frameCount, bezierCount, boneIndex), 1); + break; + case BONE_SHEAR: + ReadTimeline(input, timelines, new ShearTimeline(frameCount, bezierCount, boneIndex), 1); + break; + case BONE_SHEARX: + ReadTimeline(input, timelines, new ShearXTimeline(frameCount, bezierCount, boneIndex), 1); + break; + case BONE_SHEARY: + ReadTimeline(input, timelines, new ShearYTimeline(frameCount, bezierCount, boneIndex), 1); + break; + } + } + } + + // IK constraint timelines. + for (int i = 0, n = input.ReadInt(true); i < n; i++) { + int index = input.ReadInt(true), frameCount = input.ReadInt(true), frameLast = frameCount - 1; + IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount, input.ReadInt(true), index); + int flags = input.Read(); + float time = input.ReadFloat(), mix = (flags & 1) != 0 ? ((flags & 2) != 0 ? input.ReadFloat() : 1) : 0; + float softness = (flags & 4) != 0 ? input.ReadFloat() * 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 = input.Read(); + float time2 = input.ReadFloat(), mix2 = (flags & 1) != 0 ? ((flags & 2) != 0 ? input.ReadFloat() : 1) : 0; + float softness2 = (flags & 4) != 0 ? input.ReadFloat() * 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 = input.ReadInt(true); i < n; i++) { + int index = input.ReadInt(true), frameCount = input.ReadInt(true), frameLast = frameCount - 1; + TransformConstraintTimeline timeline = new TransformConstraintTimeline(frameCount, input.ReadInt(true), index); + float time = input.ReadFloat(), mixRotate = input.ReadFloat(), mixX = input.ReadFloat(), mixY = input.ReadFloat(), + mixScaleX = input.ReadFloat(), mixScaleY = input.ReadFloat(), mixShearY = input.ReadFloat(); + for (int frame = 0, bezier = 0; ; frame++) { + timeline.SetFrame(frame, time, mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY); + if (frame == frameLast) break; + float time2 = input.ReadFloat(), mixRotate2 = input.ReadFloat(), mixX2 = input.ReadFloat(), mixY2 = input.ReadFloat(), + mixScaleX2 = input.ReadFloat(), mixScaleY2 = input.ReadFloat(), mixShearY2 = input.ReadFloat(); + switch (input.ReadUByte()) { + 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); + break; + } + 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 = input.ReadInt(true); i < n; i++) { + int index = input.ReadInt(true); + PathConstraintData data = skeletonData.pathConstraints.Items[index]; + for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) { + int type = input.ReadUByte(), frameCount = input.ReadInt(true), bezierCount = input.ReadInt(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 = input.ReadFloat(), mixRotate = input.ReadFloat(), mixX = input.ReadFloat(), mixY = input.ReadFloat(); + for (int frame = 0, bezier = 0, frameLast = timeline.FrameCount - 1; ; frame++) { + timeline.SetFrame(frame, time, mixRotate, mixX, mixY); + if (frame == frameLast) break; + float time2 = input.ReadFloat(), mixRotate2 = input.ReadFloat(), mixX2 = input.ReadFloat(), + mixY2 = input.ReadFloat(); + switch (input.ReadUByte()) { + 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); + break; + } + time = time2; + mixRotate = mixRotate2; + mixX = mixX2; + mixY = mixY2; + } + timelines.Add(timeline); + break; + } + } + } + + // Physics timelines. + for (int i = 0, n = input.ReadInt(true); i < n; i++) { + int index = input.ReadInt(true) - 1; + for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) { + int type = input.ReadUByte(), frameCount = input.ReadInt(true); + if (type == PHYSICS_RESET) { + PhysicsConstraintResetTimeline timeline = new PhysicsConstraintResetTimeline(frameCount, index); + for (int frame = 0; frame < frameCount; frame++) + timeline.SetFrame(frame, input.ReadFloat()); + timelines.Add(timeline); + continue; + } + int bezierCount = input.ReadInt(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); + break; + } + } + } + + // Attachment timelines. + for (int i = 0, n = input.ReadInt(true); i < n; i++) { + Skin skin = skeletonData.skins.Items[input.ReadInt(true)]; + for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) { + int slotIndex = input.ReadInt(true); + for (int iii = 0, nnn = input.ReadInt(true); iii < nnn; iii++) { + String attachmentName = input.ReadStringRef(); + Attachment attachment = skin.GetAttachment(slotIndex, attachmentName); + if (attachment == null) throw new SerializationException("Timeline attachment not found: " + attachmentName); + + int timelineType = input.ReadUByte(), frameCount = input.ReadInt(true), frameLast = frameCount - 1; + switch (timelineType) { + case ATTACHMENT_DEFORM: { + VertexAttachment vertexAttachment = (VertexAttachment)attachment; + bool weighted = vertexAttachment.Bones != null; + float[] vertices = vertexAttachment.Vertices; + int deformLength = weighted ? (vertices.Length / 3) << 1 : vertices.Length; + + DeformTimeline timeline = new DeformTimeline(frameCount, input.ReadInt(true), slotIndex, vertexAttachment); + + float time = input.ReadFloat(); + for (int frame = 0, bezier = 0; ; frame++) { + float[] deform; + int end = input.ReadInt(true); + if (end == 0) + deform = weighted ? new float[deformLength] : vertices; + else { + deform = new float[deformLength]; + int start = input.ReadInt(true); + end += start; + if (scale == 1) { + for (int v = start; v < end; v++) + deform[v] = input.ReadFloat(); + } else { + for (int v = start; v < end; v++) + deform[v] = input.ReadFloat() * scale; + } + if (!weighted) { + for (int v = 0, vn = deform.Length; v < vn; v++) + deform[v] += vertices[v]; + } + } + timeline.SetFrame(frame, time, deform); + if (frame == frameLast) break; + float time2 = input.ReadFloat(); + switch (input.ReadUByte()) { + case CURVE_STEPPED: + timeline.SetStepped(frame); + break; + case CURVE_BEZIER: + SetBezier(input, timeline, bezier++, frame, 0, time, time2, 0, 1, 1); + break; + } + time = time2; + } + timelines.Add(timeline); + break; + } + case ATTACHMENT_SEQUENCE: { + SequenceTimeline timeline = new SequenceTimeline(frameCount, slotIndex, attachment); + for (int frame = 0; frame < frameCount; frame++) { + float time = input.ReadFloat(); + int modeAndIndex = input.ReadInt(); + timeline.SetFrame(frame, time, (SequenceMode)(modeAndIndex & 0xf), modeAndIndex >> 4, + input.ReadFloat()); + } + timelines.Add(timeline); + break; + } // end case + } // end switch + } + } + } + + // Draw order timeline. + int drawOrderCount = input.ReadInt(true); + if (drawOrderCount > 0) { + DrawOrderTimeline timeline = new DrawOrderTimeline(drawOrderCount); + int slotCount = skeletonData.slots.Count; + for (int i = 0; i < drawOrderCount; i++) { + float time = input.ReadFloat(); + int offsetCount = input.ReadInt(true); + int[] drawOrder = new int[slotCount]; + for (int ii = slotCount - 1; ii >= 0; ii--) + drawOrder[ii] = -1; + int[] unchanged = new int[slotCount - offsetCount]; + int originalIndex = 0, unchangedIndex = 0; + for (int ii = 0; ii < offsetCount; ii++) { + int slotIndex = input.ReadInt(true); + // Collect unchanged items. + while (originalIndex != slotIndex) + unchanged[unchangedIndex++] = originalIndex++; + // Set changed items. + drawOrder[originalIndex + input.ReadInt(true)] = originalIndex++; + } + // Collect remaining unchanged items. + while (originalIndex < slotCount) + unchanged[unchangedIndex++] = originalIndex++; + // Fill in unchanged items. + for (int ii = 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 = input.ReadInt(true); + if (eventCount > 0) { + EventTimeline timeline = new EventTimeline(eventCount); + for (int i = 0; i < eventCount; i++) { + float time = input.ReadFloat(); + EventData eventData = skeletonData.events.Items[input.ReadInt(true)]; + Event e = new Event(time, eventData); + e.intValue = input.ReadInt(false); + e.floatValue = input.ReadFloat(); + e.stringValue = input.ReadString(); + if (e.stringValue == null) e.stringValue = eventData.String; + if (e.Data.AudioPath != null) { + e.volume = input.ReadFloat(); + e.balance = input.ReadFloat(); + } + timeline.SetFrame(i, e); + } + timelines.Add(timeline); + } + + float duration = 0; + Timeline[] items = timelines.Items; + for (int i = 0, n = timelines.Count; i < n; i++) + duration = Math.Max(duration, items[i].Duration); + return new Animation(name, timelines, duration); + } + + /// Throws IOException when a read operation fails. + private void ReadTimeline (SkeletonInput input, ExposedList timelines, CurveTimeline1 timeline, float scale) { + float time = input.ReadFloat(), value = input.ReadFloat() * scale; + for (int frame = 0, bezier = 0, frameLast = timeline.FrameCount - 1; ; frame++) { + timeline.SetFrame(frame, time, value); + if (frame == frameLast) break; + float time2 = input.ReadFloat(), value2 = input.ReadFloat() * scale; + switch (input.ReadUByte()) { + case CURVE_STEPPED: + timeline.SetStepped(frame); + break; + case CURVE_BEZIER: + SetBezier(input, timeline, bezier++, frame, 0, time, time2, value, value2, scale); + break; + } + time = time2; + value = value2; + } + timelines.Add(timeline); + } + + /// Throws IOException when a read operation fails. + private void ReadTimeline (SkeletonInput input, ExposedList timelines, CurveTimeline2 timeline, float scale) { + float time = input.ReadFloat(), value1 = input.ReadFloat() * scale, value2 = input.ReadFloat() * scale; + for (int frame = 0, bezier = 0, frameLast = timeline.FrameCount - 1; ; frame++) { + timeline.SetFrame(frame, time, value1, value2); + if (frame == frameLast) break; + float time2 = input.ReadFloat(), nvalue1 = input.ReadFloat() * scale, nvalue2 = input.ReadFloat() * scale; + switch (input.ReadUByte()) { + 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); + break; + } + time = time2; + value1 = nvalue1; + value2 = nvalue2; + } + timelines.Add(timeline); + } + + /// Throws IOException when a read operation fails. + void SetBezier (SkeletonInput input, CurveTimeline timeline, int bezier, int frame, int value, float time1, float time2, + float value1, float value2, float scale) { + timeline.SetBezier(bezier, frame, value, time1, value1, input.ReadFloat(), input.ReadFloat() * scale, input.ReadFloat(), + input.ReadFloat() * scale, time2, value2); + } + + internal class Vertices { + public int length; + public int[] bones; + public float[] vertices; + } + + internal class SkeletonInput { + private byte[] chars = new byte[32]; + private byte[] bytesBigEndian = new byte[8]; + internal string[] strings; + Stream input; + + public SkeletonInput (Stream input) { + this.input = input; + } + + public int Read () { + return input.ReadByte(); + } + + /// Explicit unsigned byte variant to prevent pitfalls porting Java reference implementation + /// where byte is signed vs C# where byte is unsigned. + public byte ReadUByte () { + return (byte)input.ReadByte(); + } + + /// Explicit signed byte variant to prevent pitfalls porting Java reference implementation + /// where byte is signed vs C# where byte is unsigned. + public sbyte ReadSByte () { + int value = input.ReadByte(); + if (value == -1) throw new EndOfStreamException(); + return (sbyte)value; + } + + public bool ReadBoolean () { + return input.ReadByte() != 0; + } + + public float ReadFloat () { + input.Read(bytesBigEndian, 0, 4); + chars[3] = bytesBigEndian[0]; + chars[2] = bytesBigEndian[1]; + chars[1] = bytesBigEndian[2]; + chars[0] = bytesBigEndian[3]; + return BitConverter.ToSingle(chars, 0); + } + + public int ReadInt () { + input.Read(bytesBigEndian, 0, 4); + return (bytesBigEndian[0] << 24) + + (bytesBigEndian[1] << 16) + + (bytesBigEndian[2] << 8) + + bytesBigEndian[3]; + } + + public long ReadLong () { + input.Read(bytesBigEndian, 0, 8); + return ((long)(bytesBigEndian[0]) << 56) + + ((long)(bytesBigEndian[1]) << 48) + + ((long)(bytesBigEndian[2]) << 40) + + ((long)(bytesBigEndian[3]) << 32) + + ((long)(bytesBigEndian[4]) << 24) + + ((long)(bytesBigEndian[5]) << 16) + + ((long)(bytesBigEndian[6]) << 8) + + (long)(bytesBigEndian[7]); + } + + public int ReadInt (bool optimizePositive) { + int b = input.ReadByte(); + int result = b & 0x7F; + if ((b & 0x80) != 0) { + b = input.ReadByte(); + result |= (b & 0x7F) << 7; + if ((b & 0x80) != 0) { + b = input.ReadByte(); + result |= (b & 0x7F) << 14; + if ((b & 0x80) != 0) { + b = input.ReadByte(); + result |= (b & 0x7F) << 21; + if ((b & 0x80) != 0) result |= (input.ReadByte() & 0x7F) << 28; + } + } + } + return optimizePositive ? result : ((result >> 1) ^ -(result & 1)); + } + + public string ReadString () { + int byteCount = ReadInt(true); + switch (byteCount) { + case 0: + return null; + case 1: + return ""; + } + byteCount--; + byte[] buffer = this.chars; + if (buffer.Length < byteCount) buffer = new byte[byteCount]; + ReadFully(buffer, 0, byteCount); + return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount); + } + + /// May be null. + public String ReadStringRef () { + int index = ReadInt(true); + return index == 0 ? null : strings[index - 1]; + } + + public void ReadFully (byte[] buffer, int offset, int length) { + while (length > 0) { + int count = input.Read(buffer, offset, length); + if (count <= 0) throw new EndOfStreamException(); + offset += count; + length -= count; + } + } + + /// Returns the version string of binary skeleton data. + public string GetVersionString () { + try { + // try reading 4.0+ format + long initialPosition = input.Position; + ReadLong(); // long hash + + long stringPosition = input.Position; + int stringByteCount = ReadInt(true); + input.Position = stringPosition; + if (stringByteCount <= 13) { + string version = ReadString(); + if (char.IsDigit(version[0])) + return version; + } + // fallback to old version format + input.Position = initialPosition; + return GetVersionStringOld3X(); + } catch (Exception e) { + throw new ArgumentException("Stream does not contain valid binary Skeleton Data.\n" + e, "input"); + } + } + + /// Returns old 3.8 and earlier format version string of binary skeleton data. + public string GetVersionStringOld3X () { + // Hash. + int byteCount = ReadInt(true); + if (byteCount > 1) input.Position += byteCount - 1; + + // Version. + byteCount = ReadInt(true); + if (byteCount > 1 && byteCount <= 13) { + byteCount--; + byte[] buffer = new byte[byteCount]; + ReadFully(buffer, 0, byteCount); + return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount); + } + throw new ArgumentException("Stream does not contain valid binary Skeleton Data."); + } + } + + private class LinkedMesh { + internal string parent; + internal int skinIndex, slotIndex; + internal MeshAttachment mesh; + internal bool inheritTimelines; + + public LinkedMesh (MeshAttachment mesh, int skinIndex, int slotIndex, string parent, bool inheritTimelines) { + this.mesh = mesh; + this.skinIndex = skinIndex; + this.slotIndex = slotIndex; + this.parent = parent; + this.inheritTimelines = inheritTimelines; + } + } + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/SkeletonBounds.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/SkeletonBounds.cs new file mode 100644 index 0000000..660297a --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/SkeletonBounds.cs @@ -0,0 +1,233 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +using System; + +namespace Spine4_2_33 { + + /// + /// 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. + /// + public class SkeletonBounds { + private ExposedList polygonPool = new ExposedList(); + private float minX, minY, maxX, maxY; + + public ExposedList BoundingBoxes { get; private set; } + public ExposedList Polygons { get; private set; } + public float MinX { get { return minX; } set { minX = value; } } + public float MinY { get { return minY; } set { minY = value; } } + public float MaxX { get { return maxX; } set { maxX = value; } } + public float MaxY { get { return maxY; } set { maxY = value; } } + public float Width { get { return maxX - minX; } } + public float Height { get { return maxY - minY; } } + + public SkeletonBounds () { + BoundingBoxes = new ExposedList(); + Polygons = new ExposedList(); + } + + /// + /// Clears any previous polygons, finds all visible bounding box attachments, + /// and computes the world vertices for each bounding box's polygon. + /// The skeleton. + /// + /// If true, the axis aligned bounding box containing all the polygons is computed. + /// If false, the SkeletonBounds AABB methods will always return true. + /// + public void Update (Skeleton skeleton, bool updateAabb) { + ExposedList boundingBoxes = BoundingBoxes; + ExposedList polygons = Polygons; + Slot[] slots = skeleton.slots.Items; + int slotCount = skeleton.slots.Count; + + boundingBoxes.Clear(); + for (int i = 0, n = polygons.Count; i < n; i++) + polygonPool.Add(polygons.Items[i]); + polygons.Clear(); + + for (int i = 0; i < slotCount; i++) { + Slot slot = slots[i]; + if (!slot.bone.active) continue; + BoundingBoxAttachment boundingBox = slot.attachment as BoundingBoxAttachment; + if (boundingBox == null) continue; + boundingBoxes.Add(boundingBox); + + Polygon polygon = null; + int poolCount = polygonPool.Count; + if (poolCount > 0) { + polygon = polygonPool.Items[poolCount - 1]; + polygonPool.RemoveAt(poolCount - 1); + } else + polygon = new Polygon(); + polygons.Add(polygon); + + int count = boundingBox.worldVerticesLength; + polygon.Count = count; + if (polygon.Vertices.Length < count) polygon.Vertices = new float[count]; + boundingBox.ComputeWorldVertices(slot, polygon.Vertices); + } + + if (updateAabb) { + AabbCompute(); + } else { + minX = int.MinValue; + minY = int.MinValue; + maxX = int.MaxValue; + maxY = int.MaxValue; + } + } + + private void AabbCompute () { + float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue; + Polygon[] polygons = Polygons.Items; + for (int i = 0, n = Polygons.Count; i < n; i++) { + Polygon polygon = polygons[i]; + float[] vertices = polygon.Vertices; + for (int ii = 0, nn = polygon.Count; ii < nn; ii += 2) { + float x = vertices[ii]; + float y = vertices[ii + 1]; + minX = Math.Min(minX, x); + minY = Math.Min(minY, y); + maxX = Math.Max(maxX, x); + maxY = Math.Max(maxY, y); + } + } + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /// Returns true if the axis aligned bounding box contains the point. + public bool AabbContainsPoint (float x, float y) { + return x >= minX && x <= maxX && y >= minY && y <= maxY; + } + + /// Returns true if the axis aligned bounding box intersects the line segment. + public bool AabbIntersectsSegment (float x1, float y1, float x2, float y2) { + float minX = this.minX; + float minY = this.minY; + float maxX = this.maxX; + float maxY = this.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; + } + + /// Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds. + public bool AabbIntersectsSkeleton (SkeletonBounds bounds) { + return minX < bounds.maxX && maxX > bounds.minX && minY < bounds.maxY && maxY > bounds.minY; + } + + /// Returns true if the polygon contains the point. + public bool ContainsPoint (Polygon polygon, float x, float y) { + float[] 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; + } + + /// 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 returns true. + public BoundingBoxAttachment ContainsPoint (float x, float y) { + Polygon[] polygons = Polygons.Items; + for (int i = 0, n = Polygons.Count; i < n; i++) + if (ContainsPoint(polygons[i], x, y)) return BoundingBoxes.Items[i]; + return null; + } + + /// 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 returns true. + public BoundingBoxAttachment IntersectsSegment (float x1, float y1, float x2, float y2) { + Polygon[] polygons = Polygons.Items; + for (int i = 0, n = Polygons.Count; i < n; i++) + if (IntersectsSegment(polygons[i], x1, y1, x2, y2)) return BoundingBoxes.Items[i]; + return null; + } + + /// Returns true if the polygon contains the line segment. + public bool IntersectsSegment (Polygon polygon, float x1, float y1, float x2, float y2) { + float[] vertices = polygon.Vertices; + int 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 (int 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; + } + + public Polygon GetPolygon (BoundingBoxAttachment attachment) { + int index = BoundingBoxes.IndexOf(attachment); + return index == -1 ? null : Polygons.Items[index]; + } + } + + public class Polygon { + public float[] Vertices { get; set; } + public int Count { get; set; } + + public Polygon () { + Vertices = new float[16]; + } + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/SkeletonClipping.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/SkeletonClipping.cs new file mode 100644 index 0000000..b784a40 --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/SkeletonClipping.cs @@ -0,0 +1,353 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +using System; + +namespace Spine4_2_33 { + public class SkeletonClipping { + internal readonly Triangulator triangulator = new Triangulator(); + internal readonly ExposedList clippingPolygon = new ExposedList(); + internal readonly ExposedList clipOutput = new ExposedList(128); + internal readonly ExposedList clippedVertices = new ExposedList(128); + internal readonly ExposedList clippedTriangles = new ExposedList(128); + internal readonly ExposedList clippedUVs = new ExposedList(128); + internal readonly ExposedList scratch = new ExposedList(); + + internal ClippingAttachment clipAttachment; + internal ExposedList> clippingPolygons; + + public ExposedList ClippedVertices { get { return clippedVertices; } } + public ExposedList ClippedTriangles { get { return clippedTriangles; } } + public ExposedList ClippedUVs { get { return clippedUVs; } } + + public bool IsClipping { get { return clipAttachment != null; } } + + public int ClipStart (Slot slot, ClippingAttachment clip) { + if (clipAttachment != null) return 0; + clipAttachment = clip; + + int n = clip.worldVerticesLength; + float[] vertices = clippingPolygon.Resize(n).Items; + clip.ComputeWorldVertices(slot, 0, n, vertices, 0, 2); + MakeClockwise(clippingPolygon); + clippingPolygons = triangulator.Decompose(clippingPolygon, triangulator.Triangulate(clippingPolygon)); + foreach (ExposedList polygon in clippingPolygons) { + MakeClockwise(polygon); + polygon.Add(polygon.Items[0]); + polygon.Add(polygon.Items[1]); + } + return clippingPolygons.Count; + } + + public void ClipEnd (Slot slot) { + if (clipAttachment != null && clipAttachment.endSlot == slot.data) ClipEnd(); + } + + public void ClipEnd () { + if (clipAttachment == null) return; + clipAttachment = null; + clippingPolygons = null; + clippedVertices.Clear(); + clippedTriangles.Clear(); + clippingPolygon.Clear(); + } + + public void ClipTriangles (float[] vertices, int[] triangles, int trianglesLength) { + ExposedList clipOutput = this.clipOutput, clippedVertices = this.clippedVertices; + ExposedList clippedTriangles = this.clippedTriangles; + ExposedList[] polygons = clippingPolygons.Items; + int polygonsCount = clippingPolygons.Count; + + int index = 0; + clippedVertices.Clear(); + clippedTriangles.Clear(); + for (int i = 0; i < trianglesLength; i += 3) { + int vertexOffset = triangles[i] << 1; + float x1 = vertices[vertexOffset], y1 = vertices[vertexOffset + 1]; + + vertexOffset = triangles[i + 1] << 1; + float x2 = vertices[vertexOffset], y2 = vertices[vertexOffset + 1]; + + vertexOffset = triangles[i + 2] << 1; + float x3 = vertices[vertexOffset], y3 = vertices[vertexOffset + 1]; + + for (int p = 0; p < polygonsCount; p++) { + int s = clippedVertices.Count; + if (Clip(x1, y1, x2, y2, x3, y3, polygons[p], clipOutput)) { + int clipOutputLength = clipOutput.Count; + if (clipOutputLength == 0) continue; + + int clipOutputCount = clipOutputLength >> 1; + float[] clipOutputItems = clipOutput.Items; + float[] clippedVerticesItems = clippedVertices.Resize(s + clipOutputCount * 2).Items; + for (int ii = 0; ii < clipOutputLength; ii += 2, s += 2) { + float x = clipOutputItems[ii], y = clipOutputItems[ii + 1]; + clippedVerticesItems[s] = x; + clippedVerticesItems[s + 1] = y; + } + + s = clippedTriangles.Count; + int[] clippedTrianglesItems = clippedTriangles.Resize(s + 3 * (clipOutputCount - 2)).Items; + clipOutputCount--; + for (int ii = 1; ii < clipOutputCount; ii++, s += 3) { + clippedTrianglesItems[s] = index; + clippedTrianglesItems[s + 1] = index + ii; + clippedTrianglesItems[s + 2] = index + ii + 1; + } + index += clipOutputCount + 1; + } else { + float[] clippedVerticesItems = clippedVertices.Resize(s + 3 * 2).Items; + clippedVerticesItems[s] = x1; + clippedVerticesItems[s + 1] = y1; + clippedVerticesItems[s + 2] = x2; + clippedVerticesItems[s + 3] = y2; + clippedVerticesItems[s + 4] = x3; + clippedVerticesItems[s + 5] = y3; + + s = clippedTriangles.Count; + int[] clippedTrianglesItems = clippedTriangles.Resize(s + 3).Items; + clippedTrianglesItems[s] = index; + clippedTrianglesItems[s + 1] = index + 1; + clippedTrianglesItems[s + 2] = index + 2; + index += 3; + break; + } + } + } + } + + public void ClipTriangles (float[] vertices, int[] triangles, int trianglesLength, float[] uvs) { + ExposedList clipOutput = this.clipOutput, clippedVertices = this.clippedVertices; + ExposedList clippedTriangles = this.clippedTriangles; + ExposedList[] polygons = clippingPolygons.Items; + int polygonsCount = clippingPolygons.Count; + + int index = 0; + clippedVertices.Clear(); + clippedUVs.Clear(); + clippedTriangles.Clear(); + + for (int i = 0; i < trianglesLength; i += 3) { + int vertexOffset = triangles[i] << 1; + float x1 = vertices[vertexOffset], y1 = vertices[vertexOffset + 1]; + float u1 = uvs[vertexOffset], v1 = uvs[vertexOffset + 1]; + + vertexOffset = triangles[i + 1] << 1; + float x2 = vertices[vertexOffset], y2 = vertices[vertexOffset + 1]; + float u2 = uvs[vertexOffset], v2 = uvs[vertexOffset + 1]; + + vertexOffset = triangles[i + 2] << 1; + float x3 = vertices[vertexOffset], y3 = vertices[vertexOffset + 1]; + float u3 = uvs[vertexOffset], v3 = uvs[vertexOffset + 1]; + + for (int p = 0; p < polygonsCount; p++) { + int s = clippedVertices.Count; + if (Clip(x1, y1, x2, y2, x3, y3, polygons[p], clipOutput)) { + int clipOutputLength = clipOutput.Count; + 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)); + + int clipOutputCount = clipOutputLength >> 1; + float[] clipOutputItems = clipOutput.Items; + float[] clippedVerticesItems = clippedVertices.Resize(s + clipOutputCount * 2).Items; + float[] clippedUVsItems = clippedUVs.Resize(s + clipOutputCount * 2).Items; + for (int ii = 0; ii < clipOutputLength; ii += 2, s += 2) { + float x = clipOutputItems[ii], y = clipOutputItems[ii + 1]; + clippedVerticesItems[s] = x; + clippedVerticesItems[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; + clippedUVsItems[s] = u1 * a + u2 * b + u3 * c; + clippedUVsItems[s + 1] = v1 * a + v2 * b + v3 * c; + } + + s = clippedTriangles.Count; + int[] clippedTrianglesItems = clippedTriangles.Resize(s + 3 * (clipOutputCount - 2)).Items; + clipOutputCount--; + for (int ii = 1; ii < clipOutputCount; ii++, s += 3) { + clippedTrianglesItems[s] = index; + clippedTrianglesItems[s + 1] = index + ii; + clippedTrianglesItems[s + 2] = index + ii + 1; + } + index += clipOutputCount + 1; + } else { + float[] clippedVerticesItems = clippedVertices.Resize(s + 3 * 2).Items; + float[] clippedUVsItems = clippedUVs.Resize(s + 3 * 2).Items; + clippedVerticesItems[s] = x1; + clippedVerticesItems[s + 1] = y1; + clippedVerticesItems[s + 2] = x2; + clippedVerticesItems[s + 3] = y2; + clippedVerticesItems[s + 4] = x3; + clippedVerticesItems[s + 5] = y3; + + clippedUVsItems[s] = u1; + clippedUVsItems[s + 1] = v1; + clippedUVsItems[s + 2] = u2; + clippedUVsItems[s + 3] = v2; + clippedUVsItems[s + 4] = u3; + clippedUVsItems[s + 5] = v3; + + s = clippedTriangles.Count; + int[] clippedTrianglesItems = clippedTriangles.Resize(s + 3).Items; + clippedTrianglesItems[s] = index; + clippedTrianglesItems[s + 1] = index + 1; + clippedTrianglesItems[s + 2] = index + 2; + index += 3; + break; + } + } + } + } + + ///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. + internal bool Clip (float x1, float y1, float x2, float y2, float x3, float y3, ExposedList clippingArea, ExposedList output) { + ExposedList originalOutput = output; + bool clipped = false; + + // Avoid copy at the end. + ExposedList input = null; + if (clippingArea.Count % 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(); + + int clippingVerticesLast = clippingArea.Count - 4; + float[] clippingVertices = clippingArea.Items; + for (int i = 0; ; i += 2) { + float edgeX = clippingVertices[i], edgeY = clippingVertices[i + 1]; + float ex = edgeX - clippingVertices[i + 2], ey = edgeY - clippingVertices[i + 3]; + + int outputStart = output.Count; + float[] inputVertices = input.Items; + for (int ii = 0, nn = input.Count - 2; ii < nn;) { + + float inputX = inputVertices[ii], inputY = inputVertices[ii + 1]; + ii += 2; + float inputX2 = inputVertices[ii], inputY2 = inputVertices[ii + 1]; + bool 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); + continue; + } + } 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.Count) { // All edges outside. + originalOutput.Clear(); + return true; + } + + output.Add(output.Items[0]); + output.Add(output.Items[1]); + + if (i == clippingVerticesLast) break; + ExposedList temp = output; + output = input; + output.Clear(); + input = temp; + } + + if (originalOutput != output) { + originalOutput.Clear(); + for (int i = 0, n = output.Count - 2; i < n; i++) + originalOutput.Add(output.Items[i]); + } else + originalOutput.Resize(originalOutput.Count - 2); + + return clipped; + } + + public static void MakeClockwise (ExposedList polygon) { + float[] vertices = polygon.Items; + int verticeslength = polygon.Count; + + float area = vertices[verticeslength - 2] * vertices[1] - vertices[0] * vertices[verticeslength - 1], p1x, p1y, p2x, p2y; + for (int i = 0, n = verticeslength - 3; i < n; i += 2) { + p1x = vertices[i]; + p1y = vertices[i + 1]; + p2x = vertices[i + 2]; + p2y = vertices[i + 3]; + area += p1x * p2y - p2x * p1y; + } + if (area < 0) return; + + for (int i = 0, lastX = verticeslength - 2, n = verticeslength >> 1; i < n; i += 2) { + float x = vertices[i], y = vertices[i + 1]; + int other = lastX - i; + vertices[i] = vertices[other]; + vertices[i + 1] = vertices[other + 1]; + vertices[other] = x; + vertices[other + 1] = y; + } + } + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/SkeletonData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/SkeletonData.cs new file mode 100644 index 0000000..2d2a077 --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/SkeletonData.cs @@ -0,0 +1,240 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +using System; + +namespace Spine4_2_33 { + + /// Stores the setup pose and all of the stateless data for a skeleton. + public class SkeletonData { + internal string name; + internal ExposedList bones = new ExposedList(); // Ordered parents first + internal ExposedList slots = new ExposedList(); // Setup pose draw order. + internal ExposedList skins = new ExposedList(); + internal Skin defaultSkin; + internal ExposedList events = new ExposedList(); + internal ExposedList animations = new ExposedList(); + internal ExposedList ikConstraints = new ExposedList(); + internal ExposedList transformConstraints = new ExposedList(); + internal ExposedList pathConstraints = new ExposedList(); + internal ExposedList physicsConstraints = new ExposedList(); + internal float x, y, width, height, referenceScale = 100; + internal string version, hash; + + // Nonessential. + internal float fps; + internal string imagesPath, audioPath; + + /// The skeleton's name, which by default is the name of the skeleton data file when possible, or null when a name hasn't been + /// set. + public string Name { get { return name; } set { name = value; } } + + /// The skeleton's bones, sorted parent first. The root bone is always the first bone. + public ExposedList Bones { get { return bones; } } + + /// The skeleton's slots in the setup pose draw order. + public ExposedList Slots { get { return slots; } } + + /// All skins, including the default skin. + public ExposedList Skins { get { return skins; } set { skins = value; } } + + /// + /// The skeleton's default skin. + /// By default this skin contains all attachments that were not in a skin in Spine. + /// + /// May be null. + public Skin DefaultSkin { get { return defaultSkin; } set { defaultSkin = value; } } + + /// The skeleton's events. + public ExposedList Events { get { return events; } set { events = value; } } + /// The skeleton's animations. + public ExposedList Animations { get { return animations; } set { animations = value; } } + /// The skeleton's IK constraints. + public ExposedList IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } } + /// The skeleton's transform constraints. + public ExposedList TransformConstraints { get { return transformConstraints; } set { transformConstraints = value; } } + /// The skeleton's path constraints. + public ExposedList PathConstraints { get { return pathConstraints; } set { pathConstraints = value; } } + /// The skeleton's physics constraints. + public ExposedList PhysicsConstraints { get { return physicsConstraints; } set { physicsConstraints = value; } } + + public float X { get { return x; } set { x = value; } } + public float Y { get { return y; } set { y = value; } } + public float Width { get { return width; } set { width = value; } } + public float Height { get { return height; } set { height = value; } } + + /// Baseline scale factor for applying distance-dependent effects on non-scalable properties, such as angle or scale. Default + /// is 100. + public float ReferenceScale { get { return referenceScale; } set { referenceScale = value; } } + + /// The Spine version used to export this data, or null. + public string Version { get { return version; } set { version = value; } } + + /// The skeleton data hash. This value will change if any of the skeleton data has changed. + /// May be null. + public string Hash { get { return hash; } set { hash = value; } } + + public string ImagesPath { get { return imagesPath; } set { imagesPath = value; } } + + /// The path to the audio directory as defined in Spine. Available only when nonessential data was exported. + /// May be null. + public string AudioPath { get { return audioPath; } set { audioPath = value; } } + + /// The dopesheet FPS in Spine, or zero if nonessential data was not exported. + public float Fps { get { return fps; } set { fps = value; } } + + // --- Bones + + /// + /// 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. + /// May be null. + public BoneData FindBone (string boneName) { + if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null."); + BoneData[] bones = this.bones.Items; + for (int i = 0, n = this.bones.Count; i < n; i++) { + BoneData bone = bones[i]; + if (bone.name == boneName) return bone; + } + return null; + } + + // --- Slots + + /// May be null. + public SlotData FindSlot (string slotName) { + if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null."); + SlotData[] slots = this.slots.Items; + for (int i = 0, n = this.slots.Count; i < n; i++) { + SlotData slot = slots[i]; + if (slot.name == slotName) return slot; + } + return null; + } + + // --- Skins + + /// May be null. + public Skin FindSkin (string skinName) { + if (skinName == null) throw new ArgumentNullException("skinName", "skinName cannot be null."); + foreach (Skin skin in skins) + if (skin.name == skinName) return skin; + return null; + } + + // --- Events + + /// May be null. + public EventData FindEvent (string eventDataName) { + if (eventDataName == null) throw new ArgumentNullException("eventDataName", "eventDataName cannot be null."); + foreach (EventData eventData in events) + if (eventData.name == eventDataName) return eventData; + return null; + } + + // --- Animations + + /// May be null. + public Animation FindAnimation (string animationName) { + if (animationName == null) throw new ArgumentNullException("animationName", "animationName cannot be null."); + Animation[] animations = this.animations.Items; + for (int i = 0, n = this.animations.Count; i < n; i++) { + Animation animation = animations[i]; + if (animation.name == animationName) return animation; + } + return null; + } + + // --- IK constraints + + /// May be null. + public IkConstraintData FindIkConstraint (string constraintName) { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + IkConstraintData[] ikConstraints = this.ikConstraints.Items; + for (int i = 0, n = this.ikConstraints.Count; i < n; i++) { + IkConstraintData ikConstraint = ikConstraints[i]; + if (ikConstraint.name == constraintName) return ikConstraint; + } + return null; + } + + // --- Transform constraints + + /// May be null. + public TransformConstraintData FindTransformConstraint (string constraintName) { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + TransformConstraintData[] transformConstraints = this.transformConstraints.Items; + for (int i = 0, n = this.transformConstraints.Count; i < n; i++) { + TransformConstraintData transformConstraint = transformConstraints[i]; + if (transformConstraint.name == constraintName) return transformConstraint; + } + return null; + } + + // --- Path constraints + + /// + /// Finds a path constraint by comparing each path constraint's name. It is more efficient to cache the results of this method + /// than to call it multiple times. + /// + /// May be null. + public PathConstraintData FindPathConstraint (string constraintName) { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + PathConstraintData[] pathConstraints = this.pathConstraints.Items; + for (int i = 0, n = this.pathConstraints.Count; i < n; i++) { + PathConstraintData constraint = pathConstraints[i]; + if (constraint.name.Equals(constraintName)) return constraint; + } + return null; + } + + // --- Physics constraints + + /// + /// Finds a physics constraint by comparing each physics constraint's name. It is more efficient to cache the results of this + /// method than to call it multiple times. + /// + /// May be null. + public PhysicsConstraintData FindPhysicsConstraint (String constraintName) { + if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null."); + PhysicsConstraintData[] physicsConstraints = this.physicsConstraints.Items; + for (int i = 0, n = this.physicsConstraints.Count; i < n; i++) { + PhysicsConstraintData constraint = (PhysicsConstraintData)physicsConstraints[i]; + if (constraint.name.Equals(constraintName)) return constraint; + } + return null; + } + + // --- + + override public string ToString () { + return name ?? base.ToString(); + } + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/SkeletonJson.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/SkeletonJson.cs new file mode 100644 index 0000000..4769dda --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/SkeletonJson.cs @@ -0,0 +1,1363 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +#if (UNITY_5 || UNITY_5_3_OR_NEWER || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1) +#define IS_UNITY +#endif + +using System; +using System.Collections.Generic; +using System.IO; + +#if WINDOWS_STOREAPP +using System.Threading.Tasks; +using Windows.Storage; +#endif + +namespace Spine4_2_33 { + + /// + /// Loads skeleton data in the Spine JSON format. + /// + /// JSON is human readable but the binary format is much smaller on disk and faster to load. See . + /// + /// See Spine JSON format and + /// JSON and binary data in the Spine + /// Runtimes Guide. + /// + public class SkeletonJson : SkeletonLoader { + private readonly List linkedMeshes = new List(); + + public SkeletonJson (AttachmentLoader attachmentLoader) + : base(attachmentLoader) { + } + + public SkeletonJson (params Atlas[] atlasArray) + : base(atlasArray) { + } + +#if !IS_UNITY && WINDOWS_STOREAPP + private async Task ReadFile(string path) { + var folder = Windows.ApplicationModel.Package.Current.InstalledLocation; + var file = await folder.GetFileAsync(path).AsTask().ConfigureAwait(false); + using (StreamReader reader = new StreamReader(await file.OpenStreamForReadAsync().ConfigureAwait(false))) { + SkeletonData skeletonData = ReadSkeletonData(reader); + skeletonData.Name = Path.GetFileNameWithoutExtension(path); + return skeletonData; + } + } + + public override SkeletonData ReadSkeletonData (string path) { + return this.ReadFile(path).Result; + } +#else + public override SkeletonData ReadSkeletonData (string path) { +#if WINDOWS_PHONE + using (StreamReader reader = new StreamReader(Microsoft.Xna.Framework.TitleContainer.OpenStream(path))) { +#else + using (StreamReader reader = new StreamReader(new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))) { +#endif + SkeletonData skeletonData = ReadSkeletonData(reader); + skeletonData.name = Path.GetFileNameWithoutExtension(path); + return skeletonData; + } + } +#endif + + public SkeletonData ReadSkeletonData (TextReader reader) { + if (reader == null) throw new ArgumentNullException("reader", "reader cannot be null."); + + float scale = this.scale; + SkeletonData skeletonData = new SkeletonData(); + + Dictionary root = Json.Deserialize(reader) as Dictionary; + if (root == null) throw new Exception("Invalid JSON."); + + // Skeleton. + if (root.ContainsKey("skeleton")) { + Dictionary skeletonMap = (Dictionary)root["skeleton"]; + skeletonData.hash = (string)skeletonMap["hash"]; + skeletonData.version = (string)skeletonMap["spine"]; + skeletonData.x = GetFloat(skeletonMap, "x", 0); + skeletonData.y = GetFloat(skeletonMap, "y", 0); + skeletonData.width = GetFloat(skeletonMap, "width", 0); + skeletonData.height = GetFloat(skeletonMap, "height", 0); + skeletonData.referenceScale = GetFloat(skeletonMap, "referenceScale", 100) * scale; + skeletonData.fps = GetFloat(skeletonMap, "fps", 30); + skeletonData.imagesPath = GetString(skeletonMap, "images", null); + skeletonData.audioPath = GetString(skeletonMap, "audio", null); + } + + // Bones. + if (root.ContainsKey("bones")) { + foreach (Dictionary boneMap in (List)root["bones"]) { + BoneData parent = null; + if (boneMap.ContainsKey("parent")) { + parent = skeletonData.FindBone((string)boneMap["parent"]); + if (parent == null) + throw new Exception("Parent bone not found: " + boneMap["parent"]); + } + BoneData data = new BoneData(skeletonData.Bones.Count, (string)boneMap["name"], parent); + data.length = GetFloat(boneMap, "length", 0) * scale; + data.x = GetFloat(boneMap, "x", 0) * scale; + data.y = GetFloat(boneMap, "y", 0) * scale; + data.rotation = GetFloat(boneMap, "rotation", 0); + data.scaleX = GetFloat(boneMap, "scaleX", 1); + data.scaleY = GetFloat(boneMap, "scaleY", 1); + data.shearX = GetFloat(boneMap, "shearX", 0); + data.shearY = GetFloat(boneMap, "shearY", 0); + + string inheritString = GetString(boneMap, "inherit", Inherit.Normal.ToString()); + data.inherit = (Inherit)Enum.Parse(typeof(Inherit), inheritString, true); + data.skinRequired = GetBoolean(boneMap, "skin", false); + + skeletonData.bones.Add(data); + } + } + + // Slots. + if (root.ContainsKey("slots")) { + foreach (Dictionary slotMap in (List)root["slots"]) { + string slotName = (string)slotMap["name"]; + string boneName = (string)slotMap["bone"]; + BoneData boneData = skeletonData.FindBone(boneName); + if (boneData == null) throw new Exception("Slot bone not found: " + boneName); + SlotData data = new SlotData(skeletonData.Slots.Count, slotName, boneData); + + if (slotMap.ContainsKey("color")) { + string color = (string)slotMap["color"]; + data.r = ToColor(color, 0); + data.g = ToColor(color, 1); + data.b = ToColor(color, 2); + data.a = ToColor(color, 3); + } + + if (slotMap.ContainsKey("dark")) { + string color2 = (string)slotMap["dark"]; + data.r2 = ToColor(color2, 0, 6); // expectedLength = 6. ie. "RRGGBB" + data.g2 = ToColor(color2, 1, 6); + data.b2 = ToColor(color2, 2, 6); + data.hasSecondColor = true; + } + + data.attachmentName = GetString(slotMap, "attachment", null); + if (slotMap.ContainsKey("blend")) + data.blendMode = (BlendMode)Enum.Parse(typeof(BlendMode), (string)slotMap["blend"], true); + else + data.blendMode = BlendMode.Normal; + //data.visible = slotMap.getBoolean("visible", true); + skeletonData.slots.Add(data); + } + } + + // IK constraints. + if (root.ContainsKey("ik")) { + foreach (Dictionary constraintMap in (List)root["ik"]) { + IkConstraintData data = new IkConstraintData((string)constraintMap["name"]); + data.order = GetInt(constraintMap, "order", 0); + data.skinRequired = GetBoolean(constraintMap, "skin", false); + + if (constraintMap.ContainsKey("bones")) { + foreach (string boneName in (List)constraintMap["bones"]) { + BoneData bone = skeletonData.FindBone(boneName); + if (bone == null) throw new Exception("IK bone not found: " + boneName); + data.bones.Add(bone); + } + } + + string targetName = (string)constraintMap["target"]; + data.target = skeletonData.FindBone(targetName); + if (data.target == null) throw new Exception("IK target bone not found: " + targetName); + data.mix = GetFloat(constraintMap, "mix", 1); + data.softness = GetFloat(constraintMap, "softness", 0) * scale; + data.bendDirection = GetBoolean(constraintMap, "bendPositive", true) ? 1 : -1; + data.compress = GetBoolean(constraintMap, "compress", false); + data.stretch = GetBoolean(constraintMap, "stretch", false); + data.uniform = GetBoolean(constraintMap, "uniform", false); + + skeletonData.ikConstraints.Add(data); + } + } + + // Transform constraints. + if (root.ContainsKey("transform")) { + foreach (Dictionary constraintMap in (List)root["transform"]) { + TransformConstraintData data = new TransformConstraintData((string)constraintMap["name"]); + data.order = GetInt(constraintMap, "order", 0); + data.skinRequired = GetBoolean(constraintMap, "skin", false); + + if (constraintMap.ContainsKey("bones")) { + foreach (string boneName in (List)constraintMap["bones"]) { + BoneData bone = skeletonData.FindBone(boneName); + if (bone == null) throw new Exception("Transform constraint bone not found: " + boneName); + data.bones.Add(bone); + } + } + + string targetName = (string)constraintMap["target"]; + data.target = skeletonData.FindBone(targetName); + if (data.target == null) throw new Exception("Transform constraint target bone not found: " + targetName); + + data.local = GetBoolean(constraintMap, "local", false); + data.relative = GetBoolean(constraintMap, "relative", false); + + data.offsetRotation = GetFloat(constraintMap, "rotation", 0); + data.offsetX = GetFloat(constraintMap, "x", 0) * scale; + data.offsetY = GetFloat(constraintMap, "y", 0) * scale; + data.offsetScaleX = GetFloat(constraintMap, "scaleX", 0); + data.offsetScaleY = GetFloat(constraintMap, "scaleY", 0); + data.offsetShearY = GetFloat(constraintMap, "shearY", 0); + + data.mixRotate = GetFloat(constraintMap, "mixRotate", 1); + data.mixX = GetFloat(constraintMap, "mixX", 1); + data.mixY = GetFloat(constraintMap, "mixY", data.mixX); + data.mixScaleX = GetFloat(constraintMap, "mixScaleX", 1); + data.mixScaleY = GetFloat(constraintMap, "mixScaleY", data.mixScaleX); + data.mixShearY = GetFloat(constraintMap, "mixShearY", 1); + + skeletonData.transformConstraints.Add(data); + } + } + + // Path constraints. + if (root.ContainsKey("path")) { + foreach (Dictionary constraintMap in (List)root["path"]) { + PathConstraintData data = new PathConstraintData((string)constraintMap["name"]); + data.order = GetInt(constraintMap, "order", 0); + data.skinRequired = GetBoolean(constraintMap, "skin", false); + + if (constraintMap.ContainsKey("bones")) { + foreach (string boneName in (List)constraintMap["bones"]) { + BoneData bone = skeletonData.FindBone(boneName); + if (bone == null) throw new Exception("Path bone not found: " + boneName); + data.bones.Add(bone); + } + } + + string targetName = (string)constraintMap["target"]; + data.target = skeletonData.FindSlot(targetName); + if (data.target == null) throw new Exception("Path target slot not found: " + targetName); + + data.positionMode = (PositionMode)Enum.Parse(typeof(PositionMode), GetString(constraintMap, "positionMode", "percent"), true); + data.spacingMode = (SpacingMode)Enum.Parse(typeof(SpacingMode), GetString(constraintMap, "spacingMode", "length"), true); + data.rotateMode = (RotateMode)Enum.Parse(typeof(RotateMode), GetString(constraintMap, "rotateMode", "tangent"), true); + data.offsetRotation = GetFloat(constraintMap, "rotation", 0); + data.position = GetFloat(constraintMap, "position", 0); + if (data.positionMode == PositionMode.Fixed) data.position *= scale; + data.spacing = GetFloat(constraintMap, "spacing", 0); + if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale; + data.mixRotate = GetFloat(constraintMap, "mixRotate", 1); + data.mixX = GetFloat(constraintMap, "mixX", 1); + data.mixY = GetFloat(constraintMap, "mixY", data.mixX); + + skeletonData.pathConstraints.Add(data); + } + } + + // Physics constraints. + if (root.ContainsKey("physics")) { + foreach (Dictionary constraintMap in (List)root["physics"]) { + PhysicsConstraintData data = new PhysicsConstraintData((string)constraintMap["name"]); + data.order = GetInt(constraintMap, "order", 0); + data.skinRequired = GetBoolean(constraintMap, "skin", false); + + string boneName = (string)constraintMap["bone"]; + data.bone = skeletonData.FindBone(boneName); + if (data.bone == null) throw new Exception("Physics bone not found: " + boneName); + + data.x = GetFloat(constraintMap, "x", 0); + data.y = GetFloat(constraintMap, "y", 0); + data.rotate = GetFloat(constraintMap, "rotate", 0); + data.scaleX = GetFloat(constraintMap, "scaleX", 0); + data.shearX = GetFloat(constraintMap, "shearX", 0); + data.limit = GetFloat(constraintMap, "limit", 5000) * scale; + data.step = 1f / GetInt(constraintMap, "fps", 60); + data.inertia = GetFloat(constraintMap, "inertia", 1); + data.strength = GetFloat(constraintMap, "strength", 100); + data.damping = GetFloat(constraintMap, "damping", 1); + data.massInverse = 1f / GetFloat(constraintMap, "mass", 1); + data.wind = GetFloat(constraintMap, "wind", 0); + data.gravity = GetFloat(constraintMap, "gravity", 0); + data.mix = GetFloat(constraintMap, "mix", 1); + data.inertiaGlobal = GetBoolean(constraintMap, "inertiaGlobal", false); + data.strengthGlobal = GetBoolean(constraintMap, "strengthGlobal", false); + data.dampingGlobal = GetBoolean(constraintMap, "dampingGlobal", false); + data.massGlobal = GetBoolean(constraintMap, "massGlobal", false); + data.windGlobal = GetBoolean(constraintMap, "windGlobal", false); + data.gravityGlobal = GetBoolean(constraintMap, "gravityGlobal", false); + data.mixGlobal = GetBoolean(constraintMap, "mixGlobal", false); + + skeletonData.physicsConstraints.Add(data); + } + } + + // Skins. + if (root.ContainsKey("skins")) { + foreach (Dictionary skinMap in (List)root["skins"]) { + Skin skin = new Skin((string)skinMap["name"]); + if (skinMap.ContainsKey("bones")) { + foreach (string entryName in (List)skinMap["bones"]) { + BoneData bone = skeletonData.FindBone(entryName); + if (bone == null) throw new Exception("Skin bone not found: " + entryName); + skin.bones.Add(bone); + } + } + skin.bones.TrimExcess(); + if (skinMap.ContainsKey("ik")) { + foreach (string entryName in (List)skinMap["ik"]) { + IkConstraintData constraint = skeletonData.FindIkConstraint(entryName); + if (constraint == null) throw new Exception("Skin IK constraint not found: " + entryName); + skin.constraints.Add(constraint); + } + } + if (skinMap.ContainsKey("transform")) { + foreach (string entryName in (List)skinMap["transform"]) { + TransformConstraintData constraint = skeletonData.FindTransformConstraint(entryName); + if (constraint == null) throw new Exception("Skin transform constraint not found: " + entryName); + skin.constraints.Add(constraint); + } + } + if (skinMap.ContainsKey("path")) { + foreach (string entryName in (List)skinMap["path"]) { + PathConstraintData constraint = skeletonData.FindPathConstraint(entryName); + if (constraint == null) throw new Exception("Skin path constraint not found: " + entryName); + skin.constraints.Add(constraint); + } + } + if (skinMap.ContainsKey("physics")) { + foreach (string entryName in (List)skinMap["physics"]) { + PhysicsConstraintData constraint = skeletonData.FindPhysicsConstraint(entryName); + if (constraint == null) throw new Exception("Skin physics constraint not found: " + entryName); + skin.constraints.Add(constraint); + } + } + skin.constraints.TrimExcess(); + if (skinMap.ContainsKey("attachments")) { + foreach (KeyValuePair slotEntry in (Dictionary)skinMap["attachments"]) { + int slotIndex = FindSlotIndex(skeletonData, slotEntry.Key); + foreach (KeyValuePair entry in ((Dictionary)slotEntry.Value)) { + try { + Attachment attachment = ReadAttachment((Dictionary)entry.Value, skin, slotIndex, entry.Key, skeletonData); + if (attachment != null) skin.SetAttachment(slotIndex, entry.Key, attachment); + } catch (Exception e) { + throw new Exception("Error reading attachment: " + entry.Key + ", skin: " + skin, e); + } + } + } + } + skeletonData.skins.Add(skin); + if (skin.name == "default") skeletonData.defaultSkin = skin; + } + } + + // Linked meshes. + for (int i = 0, n = linkedMeshes.Count; i < n; i++) { + LinkedMesh linkedMesh = linkedMeshes[i]; + Skin skin = linkedMesh.skin == null ? skeletonData.defaultSkin : skeletonData.FindSkin(linkedMesh.skin); + if (skin == null) throw new Exception("Slot not found: " + linkedMesh.skin); + Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent); + if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent); + linkedMesh.mesh.TimelineAttachment = linkedMesh.inheritTimelines ? (VertexAttachment)parent : linkedMesh.mesh; + linkedMesh.mesh.ParentMesh = (MeshAttachment)parent; + if (linkedMesh.mesh.Region != null) linkedMesh.mesh.UpdateRegion(); + } + linkedMeshes.Clear(); + + // Events. + if (root.ContainsKey("events")) { + foreach (KeyValuePair entry in (Dictionary)root["events"]) { + Dictionary entryMap = (Dictionary)entry.Value; + EventData data = new EventData(entry.Key); + data.Int = GetInt(entryMap, "int", 0); + data.Float = GetFloat(entryMap, "float", 0); + data.String = GetString(entryMap, "string", string.Empty); + data.AudioPath = GetString(entryMap, "audio", null); + if (data.AudioPath != null) { + data.Volume = GetFloat(entryMap, "volume", 1); + data.Balance = GetFloat(entryMap, "balance", 0); + } + skeletonData.events.Add(data); + } + } + + // Animations. + if (root.ContainsKey("animations")) { + foreach (KeyValuePair entry in (Dictionary)root["animations"]) { + try { + ReadAnimation((Dictionary)entry.Value, entry.Key, skeletonData); + } catch (Exception e) { + throw new Exception("Error reading animation: " + entry.Key + "\n" + e.Message, e); + } + } + } + + skeletonData.bones.TrimExcess(); + skeletonData.slots.TrimExcess(); + skeletonData.skins.TrimExcess(); + skeletonData.events.TrimExcess(); + skeletonData.animations.TrimExcess(); + skeletonData.ikConstraints.TrimExcess(); + return skeletonData; + } + + private Attachment ReadAttachment (Dictionary map, Skin skin, int slotIndex, string name, SkeletonData skeletonData) { + float scale = this.scale; + name = GetString(map, "name", name); + + string typeName = GetString(map, "type", "region"); + AttachmentType type = (AttachmentType)Enum.Parse(typeof(AttachmentType), typeName, true); + + switch (type) { + case AttachmentType.Region: { + string path = GetString(map, "path", name); + object sequenceJson; + map.TryGetValue("sequence", out sequenceJson); + Sequence sequence = ReadSequence(sequenceJson); + RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path, sequence); + if (region == null) return null; + region.Path = path; + region.x = GetFloat(map, "x", 0) * scale; + region.y = GetFloat(map, "y", 0) * scale; + region.scaleX = GetFloat(map, "scaleX", 1); + region.scaleY = GetFloat(map, "scaleY", 1); + region.rotation = GetFloat(map, "rotation", 0); + region.width = GetFloat(map, "width", 32) * scale; + region.height = GetFloat(map, "height", 32) * scale; + region.sequence = sequence; + + if (map.ContainsKey("color")) { + string color = (string)map["color"]; + region.r = ToColor(color, 0); + region.g = ToColor(color, 1); + region.b = ToColor(color, 2); + region.a = ToColor(color, 3); + } + + if (region.Region != null) region.UpdateRegion(); + return region; + } + case AttachmentType.Boundingbox: + BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name); + if (box == null) return null; + ReadVertices(map, box, GetInt(map, "vertexCount", 0) << 1); + return box; + case AttachmentType.Mesh: + case AttachmentType.Linkedmesh: { + string path = GetString(map, "path", name); + object sequenceJson; + map.TryGetValue("sequence", out sequenceJson); + Sequence sequence = ReadSequence(sequenceJson); + MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path, sequence); + if (mesh == null) return null; + mesh.Path = path; + + if (map.ContainsKey("color")) { + string color = (string)map["color"]; + mesh.r = ToColor(color, 0); + mesh.g = ToColor(color, 1); + mesh.b = ToColor(color, 2); + mesh.a = ToColor(color, 3); + } + + mesh.Width = GetFloat(map, "width", 0) * scale; + mesh.Height = GetFloat(map, "height", 0) * scale; + mesh.Sequence = sequence; + + string parent = GetString(map, "parent", null); + if (parent != null) { + linkedMeshes.Add(new LinkedMesh(mesh, GetString(map, "skin", null), slotIndex, parent, GetBoolean(map, "timelines", true))); + return mesh; + } + + float[] uvs = GetFloatArray(map, "uvs", 1); + ReadVertices(map, mesh, uvs.Length); + mesh.triangles = GetIntArray(map, "triangles"); + mesh.regionUVs = uvs; + if (mesh.Region != null) mesh.UpdateRegion(); + + if (map.ContainsKey("hull")) mesh.HullLength = GetInt(map, "hull", 0) << 1; + if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges"); + return mesh; + } + case AttachmentType.Path: { + PathAttachment pathAttachment = attachmentLoader.NewPathAttachment(skin, name); + if (pathAttachment == null) return null; + pathAttachment.closed = GetBoolean(map, "closed", false); + pathAttachment.constantSpeed = GetBoolean(map, "constantSpeed", true); + + int vertexCount = GetInt(map, "vertexCount", 0); + ReadVertices(map, pathAttachment, vertexCount << 1); + + // potential BOZO see Java impl + pathAttachment.lengths = GetFloatArray(map, "lengths", scale); + return pathAttachment; + } + case AttachmentType.Point: { + PointAttachment point = attachmentLoader.NewPointAttachment(skin, name); + if (point == null) return null; + point.x = GetFloat(map, "x", 0) * scale; + point.y = GetFloat(map, "y", 0) * scale; + point.rotation = GetFloat(map, "rotation", 0); + + //string color = GetString(map, "color", null); + //if (color != null) point.color = color; + return point; + } + case AttachmentType.Clipping: { + ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name); + if (clip == null) return null; + + string end = GetString(map, "end", null); + if (end != null) { + SlotData slot = skeletonData.FindSlot(end); + if (slot == null) throw new Exception("Clipping end slot not found: " + end); + clip.EndSlot = slot; + } + + ReadVertices(map, clip, GetInt(map, "vertexCount", 0) << 1); + + //string color = GetString(map, "color", null); + // if (color != null) clip.color = color; + return clip; + } + } + return null; + } + + public static Sequence ReadSequence (object sequenceJson) { + Dictionary map = sequenceJson as Dictionary; + if (map == null) return null; + Sequence sequence = new Sequence(GetInt(map, "count")); + sequence.start = GetInt(map, "start", 1); + sequence.digits = GetInt(map, "digits", 0); + sequence.setupIndex = GetInt(map, "setup", 0); + return sequence; + } + + private void ReadVertices (Dictionary map, VertexAttachment attachment, int verticesLength) { + attachment.WorldVerticesLength = verticesLength; + float[] vertices = GetFloatArray(map, "vertices", 1); + float scale = Scale; + if (verticesLength == vertices.Length) { + if (scale != 1) { + for (int i = 0; i < vertices.Length; i++) { + vertices[i] *= scale; + } + } + attachment.vertices = vertices; + return; + } + ExposedList weights = new ExposedList(verticesLength * 3 * 3); + ExposedList bones = new ExposedList(verticesLength * 3); + for (int i = 0, n = vertices.Length; i < n;) { + int boneCount = (int)vertices[i++]; + bones.Add(boneCount); + for (int nn = i + (boneCount << 2); i < nn; i += 4) { + bones.Add((int)vertices[i]); + weights.Add(vertices[i + 1] * this.Scale); + weights.Add(vertices[i + 2] * this.Scale); + weights.Add(vertices[i + 3]); + } + } + attachment.bones = bones.ToArray(); + attachment.vertices = weights.ToArray(); + } + + private int FindSlotIndex (SkeletonData skeletonData, string slotName) { + SlotData[] slots = skeletonData.slots.Items; + for (int i = 0, n = skeletonData.slots.Count; i < n; i++) + if (slots[i].name == slotName) return i; + throw new Exception("Slot not found: " + slotName); + } + + private void ReadAnimation (Dictionary map, string name, SkeletonData skeletonData) { + float scale = this.scale; + ExposedList timelines = new ExposedList(); + + // Slot timelines. + if (map.ContainsKey("slots")) { + foreach (KeyValuePair entry in (Dictionary)map["slots"]) { + string slotName = entry.Key; + int slotIndex = FindSlotIndex(skeletonData, slotName); + Dictionary timelineMap = (Dictionary)entry.Value; + foreach (KeyValuePair timelineEntry in timelineMap) { + List values = (List)timelineEntry.Value; + int frames = values.Count; + if (frames == 0) continue; + string timelineName = (string)timelineEntry.Key; + if (timelineName == "attachment") { + AttachmentTimeline timeline = new AttachmentTimeline(frames, slotIndex); + int frame = 0; + foreach (Dictionary keyMap in values) { + timeline.SetFrame(frame++, GetFloat(keyMap, "time", 0), GetString(keyMap, "name", null)); + } + timelines.Add(timeline); + + } else if (timelineName == "rgba") { + RGBATimeline timeline = new RGBATimeline(frames, frames << 2, slotIndex); + + List.Enumerator keyMapEnumerator = values.GetEnumerator(); + keyMapEnumerator.MoveNext(); + Dictionary keyMap = (Dictionary)keyMapEnumerator.Current; + float time = GetFloat(keyMap, "time", 0); + string color = (string)keyMap["color"]; + float r = ToColor(color, 0); + float g = ToColor(color, 1); + float b = ToColor(color, 2); + float a = ToColor(color, 3); + for (int frame = 0, bezier = 0; ; frame++) { + timeline.SetFrame(frame, time, r, g, b, a); + if (!keyMapEnumerator.MoveNext()) { + timeline.Shrink(bezier); + break; + } + Dictionary nextMap = (Dictionary)keyMapEnumerator.Current; + + float time2 = GetFloat(nextMap, "time", 0); + color = (string)nextMap["color"]; + float nr = ToColor(color, 0); + float ng = ToColor(color, 1); + float nb = ToColor(color, 2); + float na = ToColor(color, 3); + + if (keyMap.ContainsKey("curve")) { + object curve = keyMap["curve"]; + bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 3, time, time2, a, na, 1); + } + time = time2; + r = nr; + g = ng; + b = nb; + a = na; + keyMap = nextMap; + } + timelines.Add(timeline); + + } else if (timelineName == "rgb") { + RGBTimeline timeline = new RGBTimeline(frames, frames * 3, slotIndex); + + List.Enumerator keyMapEnumerator = values.GetEnumerator(); + keyMapEnumerator.MoveNext(); + Dictionary keyMap = (Dictionary)keyMapEnumerator.Current; + float time = GetFloat(keyMap, "time", 0); + string color = (string)keyMap["color"]; + float r = ToColor(color, 0, 6); + float g = ToColor(color, 1, 6); + float b = ToColor(color, 2, 6); + for (int frame = 0, bezier = 0; ; frame++) { + timeline.SetFrame(frame, time, r, g, b); + if (!keyMapEnumerator.MoveNext()) { + timeline.Shrink(bezier); + break; + } + Dictionary nextMap = (Dictionary)keyMapEnumerator.Current; + + float time2 = GetFloat(nextMap, "time", 0); + color = (string)nextMap["color"]; + float nr = ToColor(color, 0, 6); + float ng = ToColor(color, 1, 6); + float nb = ToColor(color, 2, 6); + + if (keyMap.ContainsKey("curve")) { + object curve = keyMap["curve"]; + bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1); + } + time = time2; + r = nr; + g = ng; + b = nb; + keyMap = nextMap; + } + timelines.Add(timeline); + + } else if (timelineName == "alpha") { + List.Enumerator keyMapEnumerator = values.GetEnumerator(); + keyMapEnumerator.MoveNext(); + timelines.Add(ReadTimeline(ref keyMapEnumerator, new AlphaTimeline(frames, frames, slotIndex), 0, 1)); + + } else if (timelineName == "rgba2") { + RGBA2Timeline timeline = new RGBA2Timeline(frames, frames * 7, slotIndex); + + List.Enumerator keyMapEnumerator = values.GetEnumerator(); + keyMapEnumerator.MoveNext(); + Dictionary keyMap = (Dictionary)keyMapEnumerator.Current; + float time = GetFloat(keyMap, "time", 0); + string color = (string)keyMap["light"]; + float r = ToColor(color, 0); + float g = ToColor(color, 1); + float b = ToColor(color, 2); + float a = ToColor(color, 3); + color = (string)keyMap["dark"]; + float r2 = ToColor(color, 0, 6); + float g2 = ToColor(color, 1, 6); + float b2 = ToColor(color, 2, 6); + for (int frame = 0, bezier = 0; ; frame++) { + timeline.SetFrame(frame, time, r, g, b, a, r2, g2, b2); + if (!keyMapEnumerator.MoveNext()) { + timeline.Shrink(bezier); + break; + } + Dictionary nextMap = (Dictionary)keyMapEnumerator.Current; + + float time2 = GetFloat(nextMap, "time", 0); + color = (string)nextMap["light"]; + float nr = ToColor(color, 0); + float ng = ToColor(color, 1); + float nb = ToColor(color, 2); + float na = ToColor(color, 3); + color = (string)nextMap["dark"]; + float nr2 = ToColor(color, 0, 6); + float ng2 = ToColor(color, 1, 6); + float nb2 = ToColor(color, 2, 6); + + if (keyMap.ContainsKey("curve")) { + object curve = keyMap["curve"]; + bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 3, time, time2, a, na, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 4, time, time2, r2, nr2, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 5, time, time2, g2, ng2, 1); + bezier = ReadCurve(curve, 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; + keyMap = nextMap; + } + timelines.Add(timeline); + + } else if (timelineName == "rgb2") { + RGB2Timeline timeline = new RGB2Timeline(frames, frames * 6, slotIndex); + + List.Enumerator keyMapEnumerator = values.GetEnumerator(); + keyMapEnumerator.MoveNext(); + Dictionary keyMap = (Dictionary)keyMapEnumerator.Current; + float time = GetFloat(keyMap, "time", 0); + string color = (string)keyMap["light"]; + float r = ToColor(color, 0, 6); + float g = ToColor(color, 1, 6); + float b = ToColor(color, 2, 6); + color = (string)keyMap["dark"]; + float r2 = ToColor(color, 0, 6); + float g2 = ToColor(color, 1, 6); + float b2 = ToColor(color, 2, 6); + for (int frame = 0, bezier = 0; ; frame++) { + timeline.SetFrame(frame, time, r, g, b, r2, g2, b2); + if (!keyMapEnumerator.MoveNext()) { + timeline.Shrink(bezier); + break; + } + Dictionary nextMap = (Dictionary)keyMapEnumerator.Current; + + float time2 = GetFloat(nextMap, "time", 0); + color = (string)nextMap["light"]; + float nr = ToColor(color, 0, 6); + float ng = ToColor(color, 1, 6); + float nb = ToColor(color, 2, 6); + color = (string)nextMap["dark"]; + float nr2 = ToColor(color, 0, 6); + float ng2 = ToColor(color, 1, 6); + float nb2 = ToColor(color, 2, 6); + + if (keyMap.ContainsKey("curve")) { + object curve = keyMap["curve"]; + bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 3, time, time2, r2, nr2, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 4, time, time2, g2, ng2, 1); + bezier = ReadCurve(curve, timeline, bezier, frame, 5, time, time2, b2, nb2, 1); + } + time = time2; + r = nr; + g = ng; + b = nb; + r2 = nr2; + g2 = ng2; + b2 = nb2; + keyMap = nextMap; + } + timelines.Add(timeline); + + } else + throw new Exception("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")"); + } + } + } + + // Bone timelines. + if (map.ContainsKey("bones")) { + foreach (KeyValuePair entry in (Dictionary)map["bones"]) { + string boneName = entry.Key; + int boneIndex = -1; + BoneData[] bones = skeletonData.bones.Items; + for (int i = 0, n = skeletonData.bones.Count; i < n; i++) { + if (bones[i].name == boneName) { + boneIndex = i; + break; + } + } + if (boneIndex == -1) throw new Exception("Bone not found: " + boneName); + Dictionary timelineMap = (Dictionary)entry.Value; + foreach (KeyValuePair timelineEntry in timelineMap) { + List values = (List)timelineEntry.Value; + List.Enumerator keyMapEnumerator = values.GetEnumerator(); + if (!keyMapEnumerator.MoveNext()) continue; + int frames = values.Count; + string timelineName = (string)timelineEntry.Key; + if (timelineName == "rotate") + timelines.Add(ReadTimeline(ref keyMapEnumerator, new RotateTimeline(frames, frames, boneIndex), 0, 1)); + else if (timelineName == "translate") { + TranslateTimeline timeline = new TranslateTimeline(frames, frames << 1, boneIndex); + timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, "x", "y", 0, scale)); + } else if (timelineName == "translatex") { + timelines + .Add(ReadTimeline(ref keyMapEnumerator, new TranslateXTimeline(frames, frames, boneIndex), 0, scale)); + } else if (timelineName == "translatey") { + timelines + .Add(ReadTimeline(ref keyMapEnumerator, new TranslateYTimeline(frames, frames, boneIndex), 0, scale)); + } else if (timelineName == "scale") { + ScaleTimeline timeline = new ScaleTimeline(frames, frames << 1, boneIndex); + timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, "x", "y", 1, 1)); + } else if (timelineName == "scalex") + timelines.Add(ReadTimeline(ref keyMapEnumerator, new ScaleXTimeline(frames, frames, boneIndex), 1, 1)); + else if (timelineName == "scaley") + timelines.Add(ReadTimeline(ref keyMapEnumerator, new ScaleYTimeline(frames, frames, boneIndex), 1, 1)); + else if (timelineName == "shear") { + ShearTimeline timeline = new ShearTimeline(frames, frames << 1, boneIndex); + timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, "x", "y", 0, 1)); + } else if (timelineName == "shearx") + timelines.Add(ReadTimeline(ref keyMapEnumerator, new ShearXTimeline(frames, frames, boneIndex), 0, 1)); + else if (timelineName == "sheary") + timelines.Add(ReadTimeline(ref keyMapEnumerator, new ShearYTimeline(frames, frames, boneIndex), 0, 1)); + else if (timelineName == "inherit") { + InheritTimeline timeline = new InheritTimeline(frames, boneIndex); + for (int frame = 0; ; frame++) { + Dictionary keyMap = (Dictionary)keyMapEnumerator.Current; + float time = GetFloat(keyMap, "time", 0); + Inherit inherit = (Inherit)Enum.Parse(typeof(Inherit), GetString(keyMap, "inherit", Inherit.Normal.ToString()), true); + timeline.SetFrame(frame, time, inherit); + if (!keyMapEnumerator.MoveNext()) { + break; + } + } + timelines.Add(timeline); + } else + throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")"); + } + } + } + + // IK constraint timelines. + if (map.ContainsKey("ik")) { + foreach (KeyValuePair timelineMap in (Dictionary)map["ik"]) { + List values = (List)timelineMap.Value; + List.Enumerator keyMapEnumerator = values.GetEnumerator(); + if (!keyMapEnumerator.MoveNext()) continue; + Dictionary keyMap = (Dictionary)keyMapEnumerator.Current; + IkConstraintData constraint = skeletonData.FindIkConstraint(timelineMap.Key); + IkConstraintTimeline timeline = new IkConstraintTimeline(values.Count, values.Count << 1, + skeletonData.IkConstraints.IndexOf(constraint)); + float time = GetFloat(keyMap, "time", 0); + float mix = GetFloat(keyMap, "mix", 1), softness = GetFloat(keyMap, "softness", 0) * scale; + for (int frame = 0, bezier = 0; ; frame++) { + timeline.SetFrame(frame, time, mix, softness, GetBoolean(keyMap, "bendPositive", true) ? 1 : -1, + GetBoolean(keyMap, "compress", false), GetBoolean(keyMap, "stretch", false)); + if (!keyMapEnumerator.MoveNext()) { + timeline.Shrink(bezier); + break; + } + Dictionary nextMap = (Dictionary)keyMapEnumerator.Current; + float time2 = GetFloat(nextMap, "time", 0); + float mix2 = GetFloat(nextMap, "mix", 1), softness2 = GetFloat(nextMap, "softness", 0) * scale; + if (keyMap.ContainsKey("curve")) { + object curve = keyMap["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. + if (map.ContainsKey("transform")) { + foreach (KeyValuePair timelineMap in (Dictionary)map["transform"]) { + List values = (List)timelineMap.Value; + List.Enumerator keyMapEnumerator = values.GetEnumerator(); + if (!keyMapEnumerator.MoveNext()) continue; + Dictionary keyMap = (Dictionary)keyMapEnumerator.Current; + TransformConstraintData constraint = skeletonData.FindTransformConstraint(timelineMap.Key); + TransformConstraintTimeline timeline = new TransformConstraintTimeline(values.Count, values.Count * 6, + skeletonData.TransformConstraints.IndexOf(constraint)); + float time = GetFloat(keyMap, "time", 0); + float mixRotate = GetFloat(keyMap, "mixRotate", 1), mixShearY = GetFloat(keyMap, "mixShearY", 1); + float mixX = GetFloat(keyMap, "mixX", 1), mixY = GetFloat(keyMap, "mixY", mixX); + float mixScaleX = GetFloat(keyMap, "mixScaleX", 1), mixScaleY = GetFloat(keyMap, "mixScaleY", mixScaleX); + for (int frame = 0, bezier = 0; ; frame++) { + timeline.SetFrame(frame, time, mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY); + if (!keyMapEnumerator.MoveNext()) { + timeline.Shrink(bezier); + break; + } + Dictionary nextMap = (Dictionary)keyMapEnumerator.Current; + float time2 = GetFloat(nextMap, "time", 0); + float mixRotate2 = GetFloat(nextMap, "mixRotate", 1), mixShearY2 = GetFloat(nextMap, "mixShearY", 1); + float mixX2 = GetFloat(nextMap, "mixX", 1), mixY2 = GetFloat(nextMap, "mixY", mixX2); + float mixScaleX2 = GetFloat(nextMap, "mixScaleX", 1), mixScaleY2 = GetFloat(nextMap, "mixScaleY", mixScaleX2); + if (keyMap.ContainsKey("curve")) { + object curve = keyMap["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. + if (map.ContainsKey("path")) { + foreach (KeyValuePair constraintMap in (Dictionary)map["path"]) { + PathConstraintData constraint = skeletonData.FindPathConstraint(constraintMap.Key); + if (constraint == null) throw new Exception("Path constraint not found: " + constraintMap.Key); + int constraintIndex = skeletonData.pathConstraints.IndexOf(constraint); + Dictionary timelineMap = (Dictionary)constraintMap.Value; + foreach (KeyValuePair timelineEntry in timelineMap) { + List values = (List)timelineEntry.Value; + List.Enumerator keyMapEnumerator = values.GetEnumerator(); + if (!keyMapEnumerator.MoveNext()) continue; + + int frames = values.Count; + string timelineName = (string)timelineEntry.Key; + if (timelineName == "position") { + CurveTimeline1 timeline = new PathConstraintPositionTimeline(frames, frames, constraintIndex); + timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, 0, constraint.positionMode == PositionMode.Fixed ? scale : 1)); + } else if (timelineName == "spacing") { + CurveTimeline1 timeline = new PathConstraintSpacingTimeline(frames, frames, constraintIndex); + timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, 0, + constraint.spacingMode == SpacingMode.Length || constraint.spacingMode == SpacingMode.Fixed ? scale : 1)); + } else if (timelineName == "mix") { + PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(frames, frames * 3, constraintIndex); + Dictionary keyMap = (Dictionary)keyMapEnumerator.Current; + float time = GetFloat(keyMap, "time", 0); + float mixRotate = GetFloat(keyMap, "mixRotate", 1); + float mixX = GetFloat(keyMap, "mixX", 1), mixY = GetFloat(keyMap, "mixY", mixX); + for (int frame = 0, bezier = 0; ; frame++) { + timeline.SetFrame(frame, time, mixRotate, mixX, mixY); + if (!keyMapEnumerator.MoveNext()) { + timeline.Shrink(bezier); + break; + } + Dictionary nextMap = (Dictionary)keyMapEnumerator.Current; + float time2 = GetFloat(nextMap, "time", 0); + float mixRotate2 = GetFloat(nextMap, "mixRotate", 1); + float mixX2 = GetFloat(nextMap, "mixX", 1), mixY2 = GetFloat(nextMap, "mixY", mixX2); + if (keyMap.ContainsKey("curve")) { + object curve = keyMap["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); + } + time = time2; + mixRotate = mixRotate2; + mixX = mixX2; + mixY = mixY2; + keyMap = nextMap; + } + timelines.Add(timeline); + } + } + } + } + + // Physics constraint timelines. + if (map.ContainsKey("physics")) { + foreach (KeyValuePair constraintMap in (Dictionary)map["physics"]) { + int index = -1; + if (!string.IsNullOrEmpty(constraintMap.Key)) { + PhysicsConstraintData constraint = skeletonData.FindPhysicsConstraint(constraintMap.Key); + if (constraint == null) throw new Exception("Physics constraint not found: " + constraintMap.Key); + index = skeletonData.physicsConstraints.IndexOf(constraint); + } + Dictionary timelineMap = (Dictionary)constraintMap.Value; + foreach (KeyValuePair timelineEntry in timelineMap) { + List values = (List)timelineEntry.Value; + List.Enumerator keyMapEnumerator = values.GetEnumerator(); + if (!keyMapEnumerator.MoveNext()) continue; + + int frames = values.Count; + string timelineName = (string)timelineEntry.Key; + if (timelineName == "reset") { + PhysicsConstraintResetTimeline timeline1 = new PhysicsConstraintResetTimeline(frames, index); + int frame = 0; + foreach (Dictionary keyMap in values) { + timeline1.SetFrame(frame++, GetFloat(keyMap, "time", 0)); + } + timelines.Add(timeline1); + continue; + } + + CurveTimeline1 timeline; + if (timelineName == "inertia") + timeline = new PhysicsConstraintInertiaTimeline(frames, frames, index); + else if (timelineName == "strength") + timeline = new PhysicsConstraintStrengthTimeline(frames, frames, index); + else if (timelineName == "damping") + timeline = new PhysicsConstraintDampingTimeline(frames, frames, index); + else if (timelineName == "mass") + timeline = new PhysicsConstraintMassTimeline(frames, frames, index); + else if (timelineName == "wind") + timeline = new PhysicsConstraintWindTimeline(frames, frames, index); + else if (timelineName == "gravity") + timeline = new PhysicsConstraintGravityTimeline(frames, frames, index); + else if (timelineName == "mix") // + timeline = new PhysicsConstraintMixTimeline(frames, frames, index); + else + continue; + timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, 0, 1)); + } + } + } + + // Attachment timelines. + if (map.ContainsKey("attachments")) { + foreach (KeyValuePair attachmentsMap in (Dictionary)map["attachments"]) { + Skin skin = skeletonData.FindSkin(attachmentsMap.Key); + foreach (KeyValuePair slotMap in (Dictionary)attachmentsMap.Value) { + SlotData slot = skeletonData.FindSlot(slotMap.Key); + if (slot == null) throw new Exception("Slot not found: " + slotMap.Key); + foreach (KeyValuePair attachmentMap in (Dictionary)slotMap.Value) { + Attachment attachment = skin.GetAttachment(slot.index, attachmentMap.Key); + if (attachment == null) throw new Exception("Timeline attachment not found: " + attachmentMap.Key); + foreach (KeyValuePair timelineMap in (Dictionary)attachmentMap.Value) { + List values = (List)timelineMap.Value; + List.Enumerator keyMapEnumerator = values.GetEnumerator(); + if (!keyMapEnumerator.MoveNext()) continue; + Dictionary keyMap = (Dictionary)keyMapEnumerator.Current; + int frames = values.Count; + string timelineName = timelineMap.Key; + if (timelineName == "deform") { + VertexAttachment vertexAttachment = (VertexAttachment)attachment; + bool weighted = vertexAttachment.bones != null; + float[] vertices = vertexAttachment.vertices; + int deformLength = weighted ? (vertices.Length / 3) << 1 : vertices.Length; + + DeformTimeline timeline = new DeformTimeline(frames, frames, slot.Index, vertexAttachment); + float time = GetFloat(keyMap, "time", 0); + for (int frame = 0, bezier = 0; ; frame++) { + float[] deform; + if (!keyMap.ContainsKey("vertices")) { + deform = weighted ? new float[deformLength] : vertices; + } else { + deform = new float[deformLength]; + int start = GetInt(keyMap, "offset", 0); + float[] verticesValue = GetFloatArray(keyMap, "vertices", 1); + Array.Copy(verticesValue, 0, deform, start, verticesValue.Length); + if (scale != 1) { + for (int i = start, n = i + verticesValue.Length; i < n; i++) + deform[i] *= scale; + } + + if (!weighted) { + for (int i = 0; i < deformLength; i++) + deform[i] += vertices[i]; + } + } + + timeline.SetFrame(frame, time, deform); + if (!keyMapEnumerator.MoveNext()) { + timeline.Shrink(bezier); + break; + } + Dictionary nextMap = (Dictionary)keyMapEnumerator.Current; + float time2 = GetFloat(nextMap, "time", 0); + if (keyMap.ContainsKey("curve")) { + object curve = keyMap["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, slot.index, attachment); + float lastDelay = 0; + for (int frame = 0; keyMap != null; keyMap = keyMapEnumerator.MoveNext() ? + (Dictionary)keyMapEnumerator.Current : null, frame++) { + + float delay = GetFloat(keyMap, "delay", lastDelay); + SequenceMode sequenceMode = (SequenceMode)Enum.Parse(typeof(SequenceMode), + GetString(keyMap, "mode", "hold"), true); + timeline.SetFrame(frame, GetFloat(keyMap, "time", 0), + sequenceMode, GetInt(keyMap, "index", 0), delay); + lastDelay = delay; + } + timelines.Add(timeline); + } + } + } + } + } + } + + // Draw order timeline. + if (map.ContainsKey("drawOrder")) { + List values = (List)map["drawOrder"]; + DrawOrderTimeline timeline = new DrawOrderTimeline(values.Count); + int slotCount = skeletonData.slots.Count; + int frame = 0; + foreach (Dictionary keyMap in values) { + int[] drawOrder = null; + if (keyMap.ContainsKey("offsets")) { + drawOrder = new int[slotCount]; + for (int i = slotCount - 1; i >= 0; i--) + drawOrder[i] = -1; + List offsets = (List)keyMap["offsets"]; + int[] unchanged = new int[slotCount - offsets.Count]; + int originalIndex = 0, unchangedIndex = 0; + foreach (Dictionary offsetMap in offsets) { + int slotIndex = FindSlotIndex(skeletonData, (string)offsetMap["slot"]); + // Collect unchanged items. + while (originalIndex != slotIndex) + unchanged[unchangedIndex++] = originalIndex++; + // Set changed items. + int index = originalIndex + (int)(float)offsetMap["offset"]; + drawOrder[index] = originalIndex++; + } + // Collect remaining unchanged items. + while (originalIndex < slotCount) + unchanged[unchangedIndex++] = originalIndex++; + // Fill in unchanged items. + for (int i = slotCount - 1; i >= 0; i--) + if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex]; + } + timeline.SetFrame(frame, GetFloat(keyMap, "time", 0), drawOrder); + ++frame; + } + timelines.Add(timeline); + } + + // Event timeline. + if (map.ContainsKey("events")) { + List eventsMap = (List)map["events"]; + EventTimeline timeline = new EventTimeline(eventsMap.Count); + int frame = 0; + foreach (Dictionary keyMap in eventsMap) { + EventData eventData = skeletonData.FindEvent((string)keyMap["name"]); + if (eventData == null) throw new Exception("Event not found: " + keyMap["name"]); + Event e = new Event(GetFloat(keyMap, "time", 0), eventData) { + intValue = GetInt(keyMap, "int", eventData.Int), + floatValue = GetFloat(keyMap, "float", eventData.Float), + stringValue = GetString(keyMap, "string", eventData.String) + }; + if (e.data.AudioPath != null) { + e.volume = GetFloat(keyMap, "volume", eventData.Volume); + e.balance = GetFloat(keyMap, "balance", eventData.Balance); + } + timeline.SetFrame(frame, e); + ++frame; + } + timelines.Add(timeline); + } + timelines.TrimExcess(); + float duration = 0; + Timeline[] items = timelines.Items; + for (int i = 0, n = timelines.Count; i < n; i++) + duration = Math.Max(duration, items[i].Duration); + skeletonData.animations.Add(new Animation(name, timelines, duration)); + } + + static Timeline ReadTimeline (ref List.Enumerator keyMapEnumerator, CurveTimeline1 timeline, float defaultValue, float scale) { + Dictionary keyMap = (Dictionary)keyMapEnumerator.Current; + float time = GetFloat(keyMap, "time", 0); + float value = GetFloat(keyMap, "value", defaultValue) * scale; + for (int frame = 0, bezier = 0; ; frame++) { + timeline.SetFrame(frame, time, value); + if (!keyMapEnumerator.MoveNext()) { + timeline.Shrink(bezier); + return timeline; + } + Dictionary nextMap = (Dictionary)keyMapEnumerator.Current; + float time2 = GetFloat(nextMap, "time", 0); + float value2 = GetFloat(nextMap, "value", defaultValue) * scale; + if (keyMap.ContainsKey("curve")) { + object curve = keyMap["curve"]; + bezier = ReadCurve(curve, timeline, bezier, frame, 0, time, time2, value, value2, scale); + } + time = time2; + value = value2; + keyMap = nextMap; + } + } + + static Timeline ReadTimeline (ref List.Enumerator keyMapEnumerator, CurveTimeline2 timeline, String name1, String name2, float defaultValue, + float scale) { + + Dictionary keyMap = (Dictionary)keyMapEnumerator.Current; + float time = GetFloat(keyMap, "time", 0); + float value1 = GetFloat(keyMap, name1, defaultValue) * scale, value2 = GetFloat(keyMap, name2, defaultValue) * scale; + for (int frame = 0, bezier = 0; ; frame++) { + timeline.SetFrame(frame, time, value1, value2); + if (!keyMapEnumerator.MoveNext()) { + timeline.Shrink(bezier); + return timeline; + } + Dictionary nextMap = (Dictionary)keyMapEnumerator.Current; + float time2 = GetFloat(nextMap, "time", 0); + float nvalue1 = GetFloat(nextMap, name1, defaultValue) * scale, nvalue2 = GetFloat(nextMap, name2, defaultValue) * scale; + if (keyMap.ContainsKey("curve")) { + object curve = keyMap["curve"]; + 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; + } + } + + static int ReadCurve (object curve, CurveTimeline timeline, int bezier, int frame, int value, float time1, float time2, + float value1, float value2, float scale) { + + string curveString = curve as string; + if (curveString != null) { + if (curveString == "stepped") timeline.SetStepped(frame); + return bezier; + } + List curveValues = (List)curve; + int i = value << 2; + float cx1 = (float)curveValues[i]; + float cy1 = (float)curveValues[i + 1] * scale; + float cx2 = (float)curveValues[i + 2]; + float cy2 = (float)curveValues[i + 3] * scale; + SetBezier(timeline, frame, value, bezier, time1, value1, cx1, cy1, cx2, cy2, time2, value2); + return bezier + 1; + } + + 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) { + timeline.SetBezier(bezier, frame, value, time1, value1, cx1, cy1, cx2, cy2, time2, value2); + } + + static float[] GetFloatArray (Dictionary map, string name, float scale) { + List list = (List)map[name]; + float[] values = new float[list.Count]; + if (scale == 1) { + for (int i = 0, n = list.Count; i < n; i++) + values[i] = (float)list[i]; + } else { + for (int i = 0, n = list.Count; i < n; i++) + values[i] = (float)list[i] * scale; + } + return values; + } + + static int[] GetIntArray (Dictionary map, string name) { + List list = (List)map[name]; + int[] values = new int[list.Count]; + for (int i = 0, n = list.Count; i < n; i++) + values[i] = (int)(float)list[i]; + return values; + } + + static float GetFloat (Dictionary map, string name, float defaultValue) { + if (!map.ContainsKey(name)) return defaultValue; + return (float)map[name]; + } + + static int GetInt (Dictionary map, string name, int defaultValue) { + if (!map.ContainsKey(name)) return defaultValue; + return (int)(float)map[name]; + } + + static int GetInt (Dictionary map, string name) { + if (!map.ContainsKey(name)) throw new ArgumentException("Named value not found: " + name); + return (int)(float)map[name]; + } + + static bool GetBoolean (Dictionary map, string name, bool defaultValue) { + if (!map.ContainsKey(name)) return defaultValue; + return (bool)map[name]; + } + + static string GetString (Dictionary map, string name, string defaultValue) { + if (!map.ContainsKey(name)) return defaultValue; + return (string)map[name]; + } + + static float ToColor (string hexString, int colorIndex, int expectedLength = 8) { + if (hexString.Length < expectedLength) + throw new ArgumentException("Color hexadecimal length must be " + expectedLength + ", received: " + hexString, "hexString"); + return Convert.ToInt32(hexString.Substring(colorIndex * 2, 2), 16) / (float)255; + } + + private class LinkedMesh { + internal string parent, skin; + internal int slotIndex; + internal MeshAttachment mesh; + internal bool inheritTimelines; + + public LinkedMesh (MeshAttachment mesh, string skin, int slotIndex, string parent, bool inheritTimelines) { + this.mesh = mesh; + this.skin = skin; + this.slotIndex = slotIndex; + this.parent = parent; + this.inheritTimelines = inheritTimelines; + } + } + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/SkeletonLoader.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/SkeletonLoader.cs new file mode 100644 index 0000000..268acc0 --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/SkeletonLoader.cs @@ -0,0 +1,75 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; + +namespace Spine4_2_33 { + + /// + /// Base class for loading skeleton data from a file. + /// + /// SeeJSON and binary data in the + /// Spine Runtimes Guide. + /// + public abstract class SkeletonLoader { + protected readonly AttachmentLoader attachmentLoader; + protected float scale = 1; + + /// Creates a skeleton loader that loads attachments using an with the specified atlas. + /// + public SkeletonLoader (params Atlas[] atlasArray) { + attachmentLoader = new AtlasAttachmentLoader(atlasArray); + } + + /// Creates a skeleton loader that loads attachments using the specified attachment loader. + /// See Loading skeleton data in the + /// Spine Runtimes Guide. + public SkeletonLoader (AttachmentLoader attachmentLoader) { + if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader", "attachmentLoader cannot be null."); + this.attachmentLoader = attachmentLoader; + } + + /// Scales bone positions, image sizes, and translations as they are loaded. This allows different size images to be used at + /// runtime than were used in Spine. + /// + /// See Scaling in the Spine Runtimes Guide. + /// + public float Scale { + get { return scale; } + set { + if (scale == 0) throw new ArgumentNullException("scale", "scale cannot be 0."); + this.scale = value; + } + } + + public abstract SkeletonData ReadSkeletonData (string path); + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Skin.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Skin.cs new file mode 100644 index 0000000..16795d9 --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Skin.cs @@ -0,0 +1,204 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Spine4_2_33 { + /// Stores attachments by slot index and attachment name. + /// See SkeletonData , Skeleton , and + /// Runtime skins in the Spine Runtimes Guide. + /// + public class Skin { + internal string name; + // Difference to reference implementation: using Dictionary instead of HashSet. + // Reason is that there is no efficient way to replace or access an already added element, losing any benefits. + private Dictionary attachments = new Dictionary(SkinKeyComparer.Instance); + internal readonly ExposedList bones = new ExposedList(); + internal readonly ExposedList constraints = new ExposedList(); + + public string Name { get { return name; } } + /// Returns all attachments contained in this skin. + public ICollection Attachments { get { return attachments.Values; } } + public ExposedList Bones { get { return bones; } } + public ExposedList Constraints { get { return constraints; } } + + public Skin (string name) { + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + this.name = name; + } + + /// 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. + public void SetAttachment (int slotIndex, string name, Attachment attachment) { + if (attachment == null) throw new ArgumentNullException("attachment", "attachment cannot be null."); + attachments[new SkinKey(slotIndex, name)] = new SkinEntry(slotIndex, name, attachment); + } + + /// Adds all attachments, bones, and constraints from the specified skin to this skin. + public void AddSkin (Skin skin) { + foreach (BoneData data in skin.bones) + if (!bones.Contains(data)) bones.Add(data); + + foreach (ConstraintData data in skin.constraints) + if (!constraints.Contains(data)) constraints.Add(data); + + foreach (KeyValuePair item in skin.attachments) { + SkinEntry entry = item.Value; + SetAttachment(entry.slotIndex, entry.name, entry.attachment); + } + } + + /// Adds all attachments from the specified skin to this skin. Attachments are deep copied. + public void CopySkin (Skin skin) { + foreach (BoneData data in skin.bones) + if (!bones.Contains(data)) bones.Add(data); + + foreach (ConstraintData data in skin.constraints) + if (!constraints.Contains(data)) constraints.Add(data); + + foreach (KeyValuePair item in skin.attachments) { + SkinEntry entry = item.Value; + if (entry.attachment is MeshAttachment) { + SetAttachment(entry.slotIndex, entry.name, + entry.attachment != null ? ((MeshAttachment)entry.attachment).NewLinkedMesh() : null); + } else + SetAttachment(entry.slotIndex, entry.name, entry.attachment != null ? entry.attachment.Copy() : null); + } + } + + /// Returns the attachment for the specified slot index and name, or null. + /// May be null. + public Attachment GetAttachment (int slotIndex, string name) { + SkinEntry entry; + bool containsKey = attachments.TryGetValue(new SkinKey(slotIndex, name), out entry); + return containsKey ? entry.attachment : null; + } + + /// Removes the attachment in the skin for the specified slot index and name, if any. + public void RemoveAttachment (int slotIndex, string name) { + attachments.Remove(new SkinKey(slotIndex, name)); + } + + /// Returns all attachments in this skin for the specified slot index. + /// The target slotIndex. To find the slot index, use and . + public void GetAttachments (int slotIndex, List attachments) { + if (slotIndex < 0) throw new ArgumentException("slotIndex must be >= 0."); + if (attachments == null) throw new ArgumentNullException("attachments", "attachments cannot be null."); + foreach (KeyValuePair item in this.attachments) { + SkinEntry entry = item.Value; + if (entry.slotIndex == slotIndex) attachments.Add(entry); + } + } + + /// Clears all attachments, bones, and constraints. + public void Clear () { + attachments.Clear(); + bones.Clear(); + constraints.Clear(); + } + + override public string ToString () { + return name; + } + + /// Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached. + internal void AttachAll (Skeleton skeleton, Skin oldSkin) { + Slot[] slots = skeleton.slots.Items; + foreach (KeyValuePair item in oldSkin.attachments) { + SkinEntry entry = item.Value; + int slotIndex = entry.slotIndex; + Slot slot = slots[slotIndex]; + if (slot.Attachment == entry.attachment) { + Attachment attachment = GetAttachment(slotIndex, entry.name); + if (attachment != null) slot.Attachment = attachment; + } + } + } + + /// Stores an entry in the skin consisting of the slot index, name, and attachment. + public struct SkinEntry { + internal readonly int slotIndex; + internal readonly string name; + internal readonly Attachment attachment; + + public SkinEntry (int slotIndex, string name, Attachment attachment) { + this.slotIndex = slotIndex; + this.name = name; + this.attachment = attachment; + } + + public int SlotIndex { + get { + return slotIndex; + } + } + + /// The name the attachment is associated with, equivalent to the skin placeholder name in the Spine editor. + public String Name { + get { + return name; + } + } + + public Attachment Attachment { + get { + return attachment; + } + } + } + + private struct SkinKey { + internal readonly int slotIndex; + internal readonly string name; + internal readonly int hashCode; + + public SkinKey (int slotIndex, string name) { + if (slotIndex < 0) throw new ArgumentException("slotIndex must be >= 0."); + if (name == null) throw new ArgumentNullException("name", "name cannot be null"); + this.slotIndex = slotIndex; + this.name = name; + this.hashCode = name.GetHashCode() + slotIndex * 37; + } + } + + class SkinKeyComparer : IEqualityComparer { + internal static readonly SkinKeyComparer Instance = new SkinKeyComparer(); + + bool IEqualityComparer.Equals (SkinKey e1, SkinKey e2) { + return e1.slotIndex == e2.slotIndex && string.Equals(e1.name, e2.name, StringComparison.Ordinal); + } + + int IEqualityComparer.GetHashCode (SkinKey e) { + return e.hashCode; + } + } + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Slot.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Slot.cs new file mode 100644 index 0000000..05ff9a3 --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Slot.cs @@ -0,0 +1,204 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +using System; + +namespace Spine4_2_33 { + + /// + /// Stores a slot's current pose. Slots organize attachments for purposes and provide a place to store + /// state for an attachment.State cannot be stored in an attachment itself because attachments are stateless and may be shared + /// across multiple skeletons. + /// + public class Slot { + internal SlotData data; + internal Bone bone; + internal float r, g, b, a; + internal float r2, g2, b2; + internal bool hasSecondColor; + internal Attachment attachment; + internal int sequenceIndex; + internal ExposedList deform = new ExposedList(); + internal int attachmentState; + + public Slot (SlotData data, Bone bone) { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); + this.data = data; + this.bone = bone; + + // darkColor = data.darkColor == null ? null : new Color(); + if (data.hasSecondColor) { + r2 = g2 = b2 = 0; + } + + SetToSetupPose(); + } + + /// Copy constructor. + public Slot (Slot slot, Bone bone) { + if (slot == null) throw new ArgumentNullException("slot", "slot cannot be null."); + if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null."); + data = slot.data; + this.bone = bone; + r = slot.r; + g = slot.g; + b = slot.b; + a = slot.a; + + // darkColor = slot.darkColor == null ? null : new Color(slot.darkColor); + if (slot.hasSecondColor) { + r2 = slot.r2; + g2 = slot.g2; + b2 = slot.b2; + } else { + r2 = g2 = b2 = 0; + } + hasSecondColor = slot.hasSecondColor; + + attachment = slot.attachment; + sequenceIndex = slot.sequenceIndex; + deform.AddRange(slot.deform); + } + + /// The slot's setup pose data. + public SlotData Data { get { return data; } } + /// The bone this slot belongs to. + public Bone Bone { get { return bone; } } + /// The skeleton this slot belongs to. + public Skeleton Skeleton { get { return bone.skeleton; } } + /// The color used to tint the slot's attachment. If is set, this is used as the light color for two + /// color tinting. + public float R { get { return r; } set { r = value; } } + /// The color used to tint the slot's attachment. If is set, this is used as the light color for two + /// color tinting. + public float G { get { return g; } set { g = value; } } + /// The color used to tint the slot's attachment. If is set, this is used as the light color for two + /// color tinting. + public float B { get { return b; } set { b = value; } } + /// The color used to tint the slot's attachment. If is set, this is used as the light color for two + /// color tinting. + public float A { get { return a; } set { a = value; } } + + public void ClampColor () { + r = MathUtils.Clamp(r, 0, 1); + g = MathUtils.Clamp(g, 0, 1); + b = MathUtils.Clamp(b, 0, 1); + a = MathUtils.Clamp(a, 0, 1); + } + + /// The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used. + /// + public float R2 { get { return r2; } set { r2 = value; } } + /// The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used. + /// + public float G2 { get { return g2; } set { g2 = value; } } + /// The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used. + /// + public float B2 { get { return b2; } set { b2 = value; } } + /// Whether R2 G2 B2 are used to tint the slot's attachment for two color tinting. False if two color tinting is not used. + public bool HasSecondColor { get { return data.hasSecondColor; } set { data.hasSecondColor = value; } } + + public void ClampSecondColor () { + r2 = MathUtils.Clamp(r2, 0, 1); + g2 = MathUtils.Clamp(g2, 0, 1); + b2 = MathUtils.Clamp(b2, 0, 1); + } + + /// + /// The current attachment for the slot, or null if the slot has no attachment. + /// If the attachment is changed, resets and clears the . + /// The deform is not cleared if the old attachment has the same as the + /// specified attachment. + public Attachment Attachment { + /// The current attachment for the slot, or null if the slot has no attachment. + get { return attachment; } + /// + /// Sets the slot's attachment and, if the attachment changed, resets and clears the . + /// The deform is not cleared if the old attachment has the same as the + /// specified attachment. + /// May be null. + set { + if (attachment == value) return; + if (!(value is VertexAttachment) || !(this.attachment is VertexAttachment) + || ((VertexAttachment)value).TimelineAttachment != ((VertexAttachment)this.attachment).TimelineAttachment) { + deform.Clear(); + } + this.attachment = value; + sequenceIndex = -1; + } + } + + /// + /// The index of the texture region to display when the slot's attachment has a . -1 represents the + /// . + /// + public int SequenceIndex { get { return sequenceIndex; } set { sequenceIndex = value; } } + + /// Vertices to deform the slot's attachment. For an unweighted mesh, the entries are local positions for each vertex. For a + /// weighted mesh, the entries are an offset for each vertex which will be added to the mesh's local vertex positions. + /// + /// See and . + public ExposedList Deform { + get { + return deform; + } + set { + if (deform == null) throw new ArgumentNullException("deform", "deform cannot be null."); + deform = value; + } + } + + /// Sets this slot to the setup pose. + public void SetToSetupPose () { + r = data.r; + g = data.g; + b = data.b; + a = data.a; + + // if (darkColor != null) darkColor.set(data.darkColor); + if (HasSecondColor) { + r2 = data.r2; + g2 = data.g2; + b2 = data.b2; + } + + if (data.attachmentName == null) + Attachment = null; + else { + attachment = null; + Attachment = bone.skeleton.GetAttachment(data.index, data.attachmentName); + } + } + + override public string ToString () { + return data.name; + } + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/SlotData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/SlotData.cs new file mode 100644 index 0000000..b6f0ff4 --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/SlotData.cs @@ -0,0 +1,80 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +using System; + +namespace Spine4_2_33 { + public class SlotData { + internal int index; + internal string name; + internal BoneData boneData; + internal float r = 1, g = 1, b = 1, a = 1; + internal float r2 = 0, g2 = 0, b2 = 0; + internal bool hasSecondColor = false; + internal string attachmentName; + internal BlendMode blendMode; + + // Nonessential. + // bool visible = true; + + /// The index of the slot in . + public int Index { get { return index; } } + /// The name of the slot, which is unique across all slots in the skeleton. + public string Name { get { return name; } } + /// The bone this slot belongs to. + public BoneData BoneData { get { return boneData; } } + public float R { get { return r; } set { r = value; } } + public float G { get { return g; } set { g = value; } } + public float B { get { return b; } set { b = value; } } + public float A { get { return a; } set { a = value; } } + + public float R2 { get { return r2; } set { r2 = value; } } + public float G2 { get { return g2; } set { g2 = value; } } + public float B2 { get { return b2; } set { b2 = value; } } + public bool HasSecondColor { get { return hasSecondColor; } set { hasSecondColor = value; } } + + /// The name of the attachment that is visible for this slot in the setup pose, or null if no attachment is visible. + public String AttachmentName { get { return attachmentName; } set { attachmentName = value; } } + /// The blend mode for drawing the slot's attachment. + public BlendMode BlendMode { get { return blendMode; } set { blendMode = value; } } + + public SlotData (int index, String name, BoneData boneData) { + if (index < 0) throw new ArgumentException("index must be >= 0.", "index"); + if (name == null) throw new ArgumentNullException("name", "name cannot be null."); + if (boneData == null) throw new ArgumentNullException("boneData", "boneData cannot be null."); + this.index = index; + this.name = name; + this.boneData = boneData; + } + + override public string ToString () { + return name; + } + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/TextureRegion.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/TextureRegion.cs new file mode 100644 index 0000000..ebe36bc --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/TextureRegion.cs @@ -0,0 +1,49 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +#if (UNITY_5 || UNITY_5_3_OR_NEWER || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1) +#define IS_UNITY +#endif + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Reflection; + + +namespace Spine4_2_33 { + public class TextureRegion { + public int width, height; + public float u, v, u2, v2; + + virtual public int OriginalWidth { get { return width; } } + virtual public int OriginalHeight { get { return height; } } + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/TransformConstraint.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/TransformConstraint.cs new file mode 100644 index 0000000..f4cbd78 --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/TransformConstraint.cs @@ -0,0 +1,312 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +using System; + +namespace Spine4_2_33 { + using Physics = Skeleton.Physics; + + /// + /// + /// Stores the current pose for a transform constraint. A transform constraint adjusts the world transform of the constrained + /// bones to match that of the target bone. + /// + /// See Transform constraints in the Spine User Guide. + /// + public class TransformConstraint : IUpdatable { + internal readonly TransformConstraintData data; + internal readonly ExposedList bones; + internal Bone target; + internal float mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY; + + internal bool active; + + public TransformConstraint (TransformConstraintData data, Skeleton skeleton) { + if (data == null) throw new ArgumentNullException("data", "data cannot be null."); + if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null."); + this.data = data; + + bones = new ExposedList(); + foreach (BoneData boneData in data.bones) + bones.Add(skeleton.bones.Items[boneData.index]); + + target = skeleton.bones.Items[data.target.index]; + + mixRotate = data.mixRotate; + mixX = data.mixX; + mixY = data.mixY; + mixScaleX = data.mixScaleX; + mixScaleY = data.mixScaleY; + mixShearY = data.mixShearY; + } + + /// Copy constructor. + public TransformConstraint (TransformConstraint constraint, Skeleton skeleton) + : this(constraint.data, skeleton) { + + mixRotate = constraint.mixRotate; + mixX = constraint.mixX; + mixY = constraint.mixY; + mixScaleX = constraint.mixScaleX; + mixScaleY = constraint.mixScaleY; + mixShearY = constraint.mixShearY; + } + + public void SetToSetupPose () { + TransformConstraintData data = this.data; + mixRotate = data.mixRotate; + mixX = data.mixX; + mixY = data.mixY; + mixScaleX = data.mixScaleX; + mixScaleY = data.mixScaleY; + mixShearY = data.mixShearY; + } + + public void Update (Physics physics) { + if (mixRotate == 0 && mixX == 0 && mixY == 0 && mixScaleX == 0 && mixScaleY == 0 && mixShearY == 0) return; + if (data.local) { + if (data.relative) + ApplyRelativeLocal(); + else + ApplyAbsoluteLocal(); + } else { + if (data.relative) + ApplyRelativeWorld(); + else + ApplyAbsoluteWorld(); + } + } + + void ApplyAbsoluteWorld () { + float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX, + mixScaleY = this.mixScaleY, mixShearY = this.mixShearY; + bool translate = mixX != 0 || mixY != 0; + + Bone target = this.target; + float ta = target.a, tb = target.b, tc = target.c, td = target.d; + float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad; + float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect; + + Bone[] bones = this.bones.Items; + for (int i = 0, n = this.bones.Count; i < n; i++) { + Bone bone = bones[i]; + + if (mixRotate != 0) { + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + float r = MathUtils.Atan2(tc, ta) - MathUtils.Atan2(c, a) + offsetRotation; + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) // + r += MathUtils.PI2; + r *= mixRotate; + float cos = MathUtils.Cos(r), sin = MathUtils.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; //Vector2 temp = this.temp; + target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY)); + bone.worldX += (tx - bone.worldX) * mixX; + bone.worldY += (ty - bone.worldY) * mixY; + } + + if (mixScaleX != 0) { + float s = (float)Math.Sqrt(bone.a * bone.a + bone.c * bone.c); + if (s != 0) s = (s + ((float)Math.Sqrt(ta * ta + tc * tc) - s + data.offsetScaleX) * mixScaleX) / s; + bone.a *= s; + bone.c *= s; + } + if (mixScaleY != 0) { + float s = (float)Math.Sqrt(bone.b * bone.b + bone.d * bone.d); + if (s != 0) s = (s + ((float)Math.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 = MathUtils.Atan2(d, b); + float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta) - (by - MathUtils.Atan2(bone.c, bone.a)); + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) // + r += MathUtils.PI2; + r = by + (r + offsetShearY) * mixShearY; + float s = (float)Math.Sqrt(b * b + d * d); + bone.b = MathUtils.Cos(r) * s; + bone.d = MathUtils.Sin(r) * s; + } + + bone.UpdateAppliedTransform(); + } + } + + void ApplyRelativeWorld () { + float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX, + mixScaleY = this.mixScaleY, mixShearY = this.mixShearY; + bool translate = mixX != 0 || mixY != 0; + + Bone target = this.target; + float ta = target.a, tb = target.b, tc = target.c, td = target.d; + float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad; + float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect; + + Bone[] bones = this.bones.Items; + for (int i = 0, n = this.bones.Count; i < n; i++) { + Bone bone = bones[i]; + + if (mixRotate != 0) { + float a = bone.a, b = bone.b, c = bone.c, d = bone.d; + float r = MathUtils.Atan2(tc, ta) + offsetRotation; + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) // + r += MathUtils.PI2; + r *= mixRotate; + float cos = MathUtils.Cos(r), sin = MathUtils.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; //Vector2 temp = this.temp; + target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY)); + bone.worldX += tx * mixX; + bone.worldY += ty * mixY; + } + + if (mixScaleX != 0) { + float s = ((float)Math.Sqrt(ta * ta + tc * tc) - 1 + data.offsetScaleX) * mixScaleX + 1; + bone.a *= s; + bone.c *= s; + } + if (mixScaleY != 0) { + float s = ((float)Math.Sqrt(tb * tb + td * td) - 1 + data.offsetScaleY) * mixScaleY + 1; + bone.b *= s; + bone.d *= s; + } + + if (mixShearY > 0) { + float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta); + if (r > MathUtils.PI) + r -= MathUtils.PI2; + else if (r < -MathUtils.PI) // + r += MathUtils.PI2; + float b = bone.b, d = bone.d; + r = MathUtils.Atan2(d, b) + (r - MathUtils.PI / 2 + offsetShearY) * mixShearY; + float s = (float)Math.Sqrt(b * b + d * d); + bone.b = MathUtils.Cos(r) * s; + bone.d = MathUtils.Sin(r) * s; + } + + bone.UpdateAppliedTransform(); + } + } + + void ApplyAbsoluteLocal () { + float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX, + mixScaleY = this.mixScaleY, mixShearY = this.mixShearY; + + Bone target = this.target; + + Bone[] bones = this.bones.Items; + for (int i = 0, n = this.bones.Count; i < n; i++) { + Bone bone = bones[i]; + + float rotation = bone.arotation; + if (mixRotate != 0) rotation += (target.arotation - rotation + data.offsetRotation) * 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) shearY += (target.ashearY - shearY + data.offsetShearY) * mixShearY; + + bone.UpdateWorldTransform(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY); + } + } + + void ApplyRelativeLocal () { + float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX, + mixScaleY = this.mixScaleY, mixShearY = this.mixShearY; + + Bone target = this.target; + + Bone[] bones = this.bones.Items; + for (int i = 0, n = this.bones.Count; i < n; i++) { + Bone bone = bones[i]; + + 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); + } + } + + /// The bones that will be modified by this transform constraint. + public ExposedList Bones { get { return bones; } } + /// The target bone whose world transform will be copied to the constrained bones. + public Bone Target { get { return target; } set { target = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. + public float MixRotate { get { return mixRotate; } set { mixRotate = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. + public float MixX { get { return mixX; } set { mixX = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. + public float MixY { get { return mixY; } set { mixY = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained scale X. + public float MixScaleX { get { return mixScaleX; } set { mixScaleX = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained scale Y. + public float MixScaleY { get { return mixScaleY; } set { mixScaleY = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained shear Y. + public float MixShearY { get { return mixShearY; } set { mixShearY = value; } } + public bool Active { get { return active; } } + /// The transform constraint's setup pose data. + public TransformConstraintData Data { get { return data; } } + + override public string ToString () { + return data.name; + } + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/TransformConstraintData.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/TransformConstraintData.cs new file mode 100644 index 0000000..c329773 --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/TransformConstraintData.cs @@ -0,0 +1,68 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +using System; + +namespace Spine4_2_33 { + public class TransformConstraintData : ConstraintData { + internal ExposedList bones = new ExposedList(); + internal BoneData target; + internal float mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY; + internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY; + internal bool relative, local; + + public ExposedList Bones { get { return bones; } } + public BoneData Target { get { return target; } set { target = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained rotation. + public float MixRotate { get { return mixRotate; } set { mixRotate = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation X. + public float MixX { get { return mixX; } set { mixX = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y. + public float MixY { get { return mixY; } set { mixY = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained scale X. + public float MixScaleX { get { return mixScaleX; } set { mixScaleX = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained scale Y. + public float MixScaleY { get { return mixScaleY; } set { mixScaleY = value; } } + /// A percentage (0-1) that controls the mix between the constrained and unconstrained shear Y. + public float MixShearY { get { return mixShearY; } set { mixShearY = value; } } + + public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } } + public float OffsetX { get { return offsetX; } set { offsetX = value; } } + public float OffsetY { get { return offsetY; } set { offsetY = value; } } + public float OffsetScaleX { get { return offsetScaleX; } set { offsetScaleX = value; } } + public float OffsetScaleY { get { return offsetScaleY; } set { offsetScaleY = value; } } + public float OffsetShearY { get { return offsetShearY; } set { offsetShearY = value; } } + + public bool Relative { get { return relative; } set { relative = value; } } + public bool Local { get { return local; } set { local = value; } } + + public TransformConstraintData (string name) : base(name) { + } + } +} diff --git a/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Triangulator.cs b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Triangulator.cs new file mode 100644 index 0000000..d4f1d6c --- /dev/null +++ b/SpineViewerWPF/SpineLibrary/spine-runtimes-4.2.33/Triangulator.cs @@ -0,0 +1,275 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, 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. + *****************************************************************************/ + +using System; + +namespace Spine4_2_33 { + public class Triangulator { + private readonly ExposedList> convexPolygons = new ExposedList>(); + private readonly ExposedList> convexPolygonsIndices = new ExposedList>(); + + private readonly ExposedList indicesArray = new ExposedList(); + private readonly ExposedList isConcaveArray = new ExposedList(); + private readonly ExposedList triangles = new ExposedList(); + + private readonly Pool> polygonPool = new Pool>(); + private readonly Pool> polygonIndicesPool = new Pool>(); + + public ExposedList Triangulate (ExposedList verticesArray) { + float[] vertices = verticesArray.Items; + int vertexCount = verticesArray.Count >> 1; + + ExposedList indicesArray = this.indicesArray; + indicesArray.Clear(); + int[] indices = indicesArray.Resize(vertexCount).Items; + for (int i = 0; i < vertexCount; i++) + indices[i] = i; + + ExposedList isConcaveArray = this.isConcaveArray; + bool[] isConcave = isConcaveArray.Resize(vertexCount).Items; + for (int i = 0, n = vertexCount; i < n; ++i) + isConcave[i] = IsConcave(i, vertexCount, vertices, indices); + + ExposedList triangles = this.triangles; + triangles.Clear(); + triangles.EnsureCapacity(Math.Max(0, vertexCount - 2) << 2); + + while (vertexCount > 3) { + // Find ear tip. + int previous = vertexCount - 1, i = 0, next = 1; + + // outer: + while (true) { + if (!isConcave[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 (int ii = (next + 1) % vertexCount; ii != previous; ii = (ii + 1) % vertexCount) { + if (!isConcave[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 (!isConcave[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]); + indicesArray.RemoveAt(i); + isConcaveArray.RemoveAt(i); + vertexCount--; + + int previousIndex = (vertexCount + i - 1) % vertexCount; + int nextIndex = i == vertexCount ? 0 : i; + isConcave[previousIndex] = IsConcave(previousIndex, vertexCount, vertices, indices); + isConcave[nextIndex] = IsConcave(nextIndex, vertexCount, vertices, indices); + } + + if (vertexCount == 3) { + triangles.Add(indices[2]); + triangles.Add(indices[0]); + triangles.Add(indices[1]); + } + + return triangles; + } + + public ExposedList> Decompose (ExposedList verticesArray, ExposedList triangles) { + float[] vertices = verticesArray.Items; + ExposedList> convexPolygons = this.convexPolygons; + for (int i = 0, n = convexPolygons.Count; i < n; i++) + polygonPool.Free(convexPolygons.Items[i]); + convexPolygons.Clear(); + + ExposedList> convexPolygonsIndices = this.convexPolygonsIndices; + for (int i = 0, n = convexPolygonsIndices.Count; i < n; i++) + polygonIndicesPool.Free(convexPolygonsIndices.Items[i]); + convexPolygonsIndices.Clear(); + + ExposedList polygonIndices = polygonIndicesPool.Obtain(); + polygonIndices.Clear(); + + ExposedList polygon = polygonPool.Obtain(); + polygon.Clear(); + + // Merge subsequent triangles if they form a triangle fan. + int fanBaseIndex = -1, lastWinding = 0; + int[] trianglesItems = triangles.Items; + for (int i = 0, n = triangles.Count; i < n; i += 3) { + int t1 = trianglesItems[i] << 1, t2 = trianglesItems[i + 1] << 1, t3 = trianglesItems[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) { + int o = polygon.Count - 4; + float[] p = polygon.Items; + 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.Count > 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.Count > 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 (int i = 0, n = convexPolygons.Count; i < n; i++) { + polygonIndices = convexPolygonsIndices.Items[i]; + if (polygonIndices.Count == 0) continue; + int firstIndex = polygonIndices.Items[0]; + int lastIndex = polygonIndices.Items[polygonIndices.Count - 1]; + + polygon = convexPolygons.Items[i]; + int o = polygon.Count - 4; + float[] p = polygon.Items; + 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 winding = Winding(prevPrevX, prevPrevY, prevX, prevY, firstX, firstY); + + for (int ii = 0; ii < n; ii++) { + if (ii == i) continue; + ExposedList otherIndices = convexPolygonsIndices.Items[ii]; + if (otherIndices.Count != 3) continue; + int otherFirstIndex = otherIndices.Items[0]; + int otherSecondIndex = otherIndices.Items[1]; + int otherLastIndex = otherIndices.Items[2]; + + ExposedList otherPoly = convexPolygons.Items[ii]; + float x3 = otherPoly.Items[otherPoly.Count - 2], y3 = otherPoly.Items[otherPoly.Count - 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 == winding && winding2 == winding) { + 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 = convexPolygons.Count - 1; i >= 0; i--) { + polygon = convexPolygons.Items[i]; + if (polygon.Count == 0) { + convexPolygons.RemoveAt(i); + polygonPool.Free(polygon); + polygonIndices = convexPolygonsIndices.Items[i]; + convexPolygonsIndices.RemoveAt(i); + polygonIndicesPool.Free(polygonIndices); + } + } + + return convexPolygons; + } + + static private bool IsConcave (int index, int vertexCount, float[] vertices, int[] 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]); + } + + static private bool PositiveArea (float p1x, float p1y, float p2x, float p2y, float p3x, float p3y) { + return p1x * (p3y - p2y) + p2x * (p1y - p3y) + p3x * (p2y - p1y) >= 0; + } + + static private int 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/SpineViewerWPF/SpineViewerWPF.csproj b/SpineViewerWPF/SpineViewerWPF.csproj index 8981a4f..49eae7e 100644 --- a/SpineViewerWPF/SpineViewerWPF.csproj +++ b/SpineViewerWPF/SpineViewerWPF.csproj @@ -76,4 +76,7 @@ True + + + \ No newline at end of file diff --git a/SpineViewerWPF/Views/UCPlayer.xaml.cs b/SpineViewerWPF/Views/UCPlayer.xaml.cs index f5c8f93..3f979ea 100644 --- a/SpineViewerWPF/Views/UCPlayer.xaml.cs +++ b/SpineViewerWPF/Views/UCPlayer.xaml.cs @@ -74,6 +74,9 @@ public UCPlayer() case "4.1.00": player = new Player_4_1_00(); break; + case "4.2.33": + player = new Player_4_2_33(); + break; } App.appXC.Initialize += player.Initialize; diff --git a/SpineViewerWPF/Windows/Open.xaml b/SpineViewerWPF/Windows/Open.xaml index 29ea1cd..1da1fe7 100644 --- a/SpineViewerWPF/Windows/Open.xaml +++ b/SpineViewerWPF/Windows/Open.xaml @@ -45,6 +45,7 @@ + From f9073b9a6e152f349bdd3e20ba84dab1e8b3af9f Mon Sep 17 00:00:00 2001 From: Eleiyas Date: Fri, 31 Jan 2025 16:48:40 +0000 Subject: [PATCH 4/5] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 080e3eb..b811f8c 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ Requirements: * Custom import of WpfXnaControl.dll file as original NuGet package is massively outdated * Cleaned code * Updated ReadMe + * Implemented Spine Runtime 4.2.33 ## Issue: * \_(:3」∠)\_ From 4cf1a9edb083da4b2ce1973cb6903c1a1673bb9c Mon Sep 17 00:00:00 2001 From: Eleiyas Date: Fri, 31 Jan 2025 19:30:48 +0000 Subject: [PATCH 5/5] Cleaning, IDE Message Fixes --- SpineViewerWPF/MainWindow.xaml | 20 +- SpineViewerWPF/MainWindow.xaml.cs | 80 +- .../Properties/Settings.Designer.cs | 10 +- SpineViewerWPF/PublicFunction/Common.cs | 95 +- SpineViewerWPF/PublicFunction/GlobalValue.cs | 77 +- .../PublicFunction/Player/Player_4_2_33.cs | 26 +- .../spine-runtimes-4.2.33/Animation.cs | 6063 +++++++++-------- .../spine-runtimes-4.2.33/AnimationState.cs | 3114 +++++---- .../AnimationStateData.cs | 148 +- .../spine-runtimes-4.2.33/Atlas.cs | 606 +- .../Attachments/AtlasAttachmentLoader.cs | 145 +- .../Attachments/Attachment.cs | 43 +- .../Attachments/AttachmentLoader.cs | 28 +- .../Attachments/AttachmentType.cs | 10 +- .../Attachments/BoundingBoxAttachment.cs | 35 +- .../Attachments/ClippingAttachment.cs | 37 +- .../Attachments/IHasTextureRegion.cs | 45 +- .../Attachments/MeshAttachment.cs | 400 +- .../Attachments/PathAttachment.cs | 58 +- .../Attachments/PointAttachment.cs | 79 +- .../Attachments/RegionAttachment.cs | 391 +- .../Attachments/Sequence.cs | 118 +- .../Attachments/VertexAttachment.cs | 254 +- .../spine-runtimes-4.2.33/BlendMode.cs | 10 +- .../spine-runtimes-4.2.33/Bone.cs | 894 +-- .../spine-runtimes-4.2.33/BoneData.cs | 168 +- .../spine-runtimes-4.2.33/ConstraintData.cs | 51 +- .../spine-runtimes-4.2.33/Event.cs | 58 +- .../spine-runtimes-4.2.33/EventData.cs | 44 +- .../spine-runtimes-4.2.33/ExposedList.cs | 1295 ++-- .../spine-runtimes-4.2.33/IUpdatable.cs | 32 +- .../spine-runtimes-4.2.33/IkConstraint.cs | 713 +- .../spine-runtimes-4.2.33/IkConstraintData.cs | 136 +- .../spine-runtimes-4.2.33/Json.cs | 956 +-- .../spine-runtimes-4.2.33/MathUtils.cs | 197 +- .../MonoGameLoader/MeshBatcher.cs | 340 +- .../MonoGameLoader/ShapeRenderer.cs | 283 +- .../MonoGameLoader/SkeletonDebugRenderer.cs | 430 +- .../MonoGameLoader/SkeletonRenderer.cs | 461 +- .../MonoGameLoader/Util.cs | 41 +- .../MonoGameLoader/VertexEffect.cs | 118 +- .../MonoGameLoader/XnaTextureLoader.cs | 119 +- .../spine-runtimes-4.2.33/PathConstraint.cs | 1041 +-- .../PathConstraintData.cs | 78 +- .../PhysicsConstraint.cs | 577 +- .../PhysicsConstraintData.cs | 79 +- .../spine-runtimes-4.2.33/Skeleton.cs | 1595 +++-- .../spine-runtimes-4.2.33/SkeletonBinary.cs | 2773 ++++---- .../spine-runtimes-4.2.33/SkeletonBounds.cs | 429 +- .../spine-runtimes-4.2.33/SkeletonClipping.cs | 688 +- .../spine-runtimes-4.2.33/SkeletonData.cs | 435 +- .../spine-runtimes-4.2.33/SkeletonJson.cs | 2795 ++++---- .../spine-runtimes-4.2.33/SkeletonLoader.cs | 80 +- .../spine-runtimes-4.2.33/Skin.cs | 373 +- .../spine-runtimes-4.2.33/Slot.cs | 364 +- .../spine-runtimes-4.2.33/SlotData.cs | 88 +- .../spine-runtimes-4.2.33/TextureRegion.cs | 22 +- .../TransformConstraint.cs | 588 +- .../TransformConstraintData.cs | 69 +- .../spine-runtimes-4.2.33/Triangulator.cs | 515 +- SpineViewerWPF/SpineViewerWPF.csproj | 15 +- SpineViewerWPF/SpineViewerWPF.csproj.user | 5 + 62 files changed, 16401 insertions(+), 14436 deletions(-) diff --git a/SpineViewerWPF/MainWindow.xaml b/SpineViewerWPF/MainWindow.xaml index c35e545..fab4562 100644 --- a/SpineViewerWPF/MainWindow.xaml +++ b/SpineViewerWPF/MainWindow.xaml @@ -16,10 +16,10 @@ - + - + @@ -40,9 +40,9 @@